1 /* 2 * Copyright (c) 2018, 2019, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 25 26 package jdk.tools.jaotc; 27 28 import java.io.BufferedReader; 29 import java.io.File; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.nio.file.Files; 33 import java.nio.file.Path; 34 import java.nio.file.Paths; 35 import java.util.List; 36 import java.util.stream.Stream; 37 38 final class Linker { 39 40 private final Options options; 41 private String objectFileName; 42 private String libraryFileName; 43 private String linkerCmd; 44 45 String objFile() { 46 return objectFileName; 47 } 48 49 String libFile() { 50 return libraryFileName; 51 } 52 53 private static Stream<String> getLines(InputStream stream) { 54 return new BufferedReader(new InputStreamReader(stream)).lines(); 55 } 56 57 private static String getString(InputStream stream) { 58 Stream<String> lines = getLines(stream); 59 StringBuilder sb = new StringBuilder(); 60 lines.iterator().forEachRemaining(e -> sb.append(e)); 61 return sb.toString(); 62 } 63 64 Linker(Main main) throws Exception { 65 this.options = main.options; 66 String name = options.outputName; 67 objectFileName = name; 68 libraryFileName = name; 69 70 if (options.linkerpath != null && !(new File(options.linkerpath).exists())) { 71 throw new InternalError("Invalid linker path: " + options.linkerpath); 72 } 73 String linkerPath; 74 String linkerCheck; 75 76 switch (options.osName) { 77 case "Linux": 78 if (name.endsWith(".so")) { 79 objectFileName = name.substring(0, name.length() - ".so".length()); 80 } 81 objectFileName = objectFileName + ".o"; 82 linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld"; 83 linkerCmd = linkerPath + " -shared -z noexecstack -o " + libraryFileName + " " + objectFileName; 84 linkerCheck = linkerPath + " -v"; 85 break; 86 case "Mac OS X": 87 if (name.endsWith(".dylib")) { 88 objectFileName = name.substring(0, name.length() - ".dylib".length()); 89 } 90 objectFileName = objectFileName + ".o"; 91 linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld"; 92 linkerCmd = linkerPath + " -dylib -o " + libraryFileName + " " + objectFileName; 93 linkerCheck = linkerPath + " -v"; 94 break; 95 default: 96 if (options.osName.startsWith("Windows")) { 97 if (name.endsWith(".dll")) { 98 objectFileName = name.substring(0, name.length() - ".dll".length()); 99 } 100 objectFileName = objectFileName + ".obj"; 101 linkerPath = (options.linkerpath != null) ? options.linkerpath : getWindowsLinkPath(); 102 if (linkerPath == null) { 103 throw new InternalError("Can't locate Microsoft Visual Studio amd64 link.exe"); 104 } 105 linkerCmd = linkerPath + " /DLL /OPT:NOREF /NOLOGO /NOENTRY" + " /OUT:" + libraryFileName + " " + objectFileName; 106 linkerCheck = null; // link.exe presence is verified already 107 break; 108 } else { 109 throw new InternalError("Unsupported platform: " + options.osName); 110 } 111 } 112 113 // Check linker presence on platforms by printing its version 114 if (linkerCheck != null) { 115 Process p = Runtime.getRuntime().exec(linkerCheck); 116 final int exitCode = p.waitFor(); 117 if (exitCode != 0) { 118 throw new InternalError(getString(p.getErrorStream())); 119 } 120 } 121 } 122 123 void link() throws Exception { 124 Process p = Runtime.getRuntime().exec(linkerCmd); 125 final int exitCode = p.waitFor(); 126 if (exitCode != 0) { 127 String errorMessage = getString(p.getErrorStream()); 128 if (errorMessage.isEmpty()) { 129 errorMessage = getString(p.getInputStream()); 130 } 131 throw new InternalError(errorMessage); 132 } 133 File objFile = new File(objectFileName); 134 boolean keepObjFile = Boolean.parseBoolean(System.getProperty("aot.keep.objFile", "false")); 135 if (objFile.exists() && !keepObjFile) { 136 if (!objFile.delete()) { 137 throw new InternalError("Failed to delete " + objectFileName + " file"); 138 } 139 } 140 // Make non-executable for all. 141 File libFile = new File(libraryFileName); 142 if (libFile.exists() && !options.osName.startsWith("Windows")) { 143 if (!libFile.setExecutable(false, false)) { 144 throw new InternalError("Failed to change attribute for " + libraryFileName + " file"); 145 } 146 } 147 148 } 149 150 /** 151 * Search for Visual Studio link.exe Search Order is: VS2017+, VS2013, VS2015, VS2012. 152 */ 153 private static String getWindowsLinkPath() throws Exception { 154 try { 155 Path vc141NewerLinker = getVC141AndNewerLinker(); 156 if (vc141NewerLinker != null) { 157 return vc141NewerLinker.toString(); 158 } 159 } catch (Exception e) { 160 e.printStackTrace(); 161 } 162 163 String link = "\\VC\\bin\\amd64\\link.exe"; 164 165 /** 166 * First try searching the paths pointed to by the VS environment variables. 167 */ 168 for (VSVERSIONS vs : VSVERSIONS.values()) { 169 String vspath = System.getenv(vs.getEnvVariable()); 170 if (vspath != null) { 171 File commonTools = new File(vspath); 172 File vsRoot = commonTools.getParentFile().getParentFile(); 173 File linkPath = new File(vsRoot, link); 174 if (linkPath.exists()) { 175 return linkPath.getPath(); 176 } 177 } 178 } 179 180 /** 181 * If we didn't find via the VS environment variables, try the well known paths 182 */ 183 for (VSVERSIONS vs : VSVERSIONS.values()) { 184 String wkp = vs.getWellKnownPath(); 185 if (new File(wkp).exists()) { 186 return wkp; 187 } 188 } 189 190 return null; 191 } 192 193 private static Path getVC141AndNewerLinker() throws Exception { 194 String programFilesX86 = System.getenv("ProgramFiles(x86)"); 195 if (programFilesX86 == null) { 196 throw new InternalError("Could not read the ProgramFiles(x86) environment variable"); 197 } 198 Path vswhere = Paths.get(programFilesX86 + "\\Microsoft Visual Studio\\Installer\\vswhere.exe"); 199 if (!Files.exists(vswhere)) { 200 return null; 201 } 202 203 ProcessBuilder processBuilder = new ProcessBuilder(vswhere.toString(), "-requires", 204 "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath", "-latest"); 205 processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); 206 processBuilder.redirectError(ProcessBuilder.Redirect.PIPE); 207 Process process = processBuilder.start(); 208 final int exitCode = process.waitFor(); 209 if (exitCode != 0) { 210 String errorMessage = getString(process.getErrorStream()); 211 if (errorMessage.isEmpty()) { 212 errorMessage = getString(process.getInputStream()); 213 } 214 throw new InternalError(errorMessage); 215 } 216 217 String installationPath = getLines(process.getInputStream()).findFirst().orElseThrow(() -> new InternalError("Unexpected empty output from vswhere")); 218 Path vcToolsVersionFilePath = Paths.get(installationPath, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); 219 List<String> vcToolsVersionFileLines = Files.readAllLines(vcToolsVersionFilePath); 220 if (vcToolsVersionFileLines.isEmpty()) { 221 throw new InternalError(vcToolsVersionFilePath.toString() + " is empty"); 222 } 223 String vcToolsVersion = vcToolsVersionFileLines.get(0); 224 Path linkPath = Paths.get(installationPath, "VC\\Tools\\MSVC", vcToolsVersion, "bin\\Hostx64\\x64\\link.exe"); 225 if (!Files.exists(linkPath)) { 226 throw new InternalError("Linker at path " + linkPath.toString() + " does not exist"); 227 } 228 229 return linkPath; 230 } 231 232 // @formatter:off (workaround for Eclipse formatting bug) 233 enum VSVERSIONS { 234 VS2013("VS120COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\link.exe"), 235 VS2015("VS140COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\link.exe"), 236 VS2012("VS110COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 11.0\\VC\\bin\\amd64\\link.exe"); 237 238 private final String envvariable; 239 private final String wkp; 240 241 VSVERSIONS(String envvariable, String wellknownpath) { 242 this.envvariable = envvariable; 243 this.wkp = wellknownpath; 244 } 245 246 String getEnvVariable() { 247 return envvariable; 248 } 249 250 String getWellKnownPath() { 251 return wkp; 252 } 253 } 254 // @formatter:on 255 }