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 "SunOS": 87 if (name.endsWith(".so")) { 88 objectFileName = name.substring(0, name.length() - ".so".length()); 89 } 90 objectFileName = objectFileName + ".o"; 91 linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld"; 92 linkerCmd = linkerPath + " -shared -o " + libraryFileName + " " + objectFileName; 93 linkerCheck = linkerPath + " -V"; 94 break; 95 case "Mac OS X": 96 if (name.endsWith(".dylib")) { 97 objectFileName = name.substring(0, name.length() - ".dylib".length()); 98 } 99 objectFileName = objectFileName + ".o"; 100 linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld"; 101 linkerCmd = linkerPath + " -dylib -o " + libraryFileName + " " + objectFileName; 102 linkerCheck = linkerPath + " -v"; 103 break; 104 default: 105 if (options.osName.startsWith("Windows")) { 106 if (name.endsWith(".dll")) { 107 objectFileName = name.substring(0, name.length() - ".dll".length()); 108 } 109 objectFileName = objectFileName + ".obj"; 110 linkerPath = (options.linkerpath != null) ? options.linkerpath : getWindowsLinkPath(); 111 if (linkerPath == null) { 112 throw new InternalError("Can't locate Microsoft Visual Studio amd64 link.exe"); 113 } 114 linkerCmd = linkerPath + " /DLL /OPT:NOREF /NOLOGO /NOENTRY" + " /OUT:" + libraryFileName + " " + objectFileName; 115 linkerCheck = null; // link.exe presence is verified already 116 break; 117 } else { 118 throw new InternalError("Unsupported platform: " + options.osName); 119 } 120 } 121 122 // Check linker presence on platforms by printing its version 123 if (linkerCheck != null) { 124 Process p = Runtime.getRuntime().exec(linkerCheck); 125 final int exitCode = p.waitFor(); 126 if (exitCode != 0) { 127 throw new InternalError(getString(p.getErrorStream())); 128 } 129 } 130 } 131 132 void link() throws Exception { 133 Process p = Runtime.getRuntime().exec(linkerCmd); 134 final int exitCode = p.waitFor(); 135 if (exitCode != 0) { 136 String errorMessage = getString(p.getErrorStream()); 137 if (errorMessage.isEmpty()) { 138 errorMessage = getString(p.getInputStream()); 139 } 140 throw new InternalError(errorMessage); 141 } 142 File objFile = new File(objectFileName); 143 boolean keepObjFile = Boolean.parseBoolean(System.getProperty("aot.keep.objFile", "false")); 144 if (objFile.exists() && !keepObjFile) { 145 if (!objFile.delete()) { 146 throw new InternalError("Failed to delete " + objectFileName + " file"); 147 } 148 } 149 // Make non-executable for all. 150 File libFile = new File(libraryFileName); 151 if (libFile.exists() && !options.osName.startsWith("Windows")) { 152 if (!libFile.setExecutable(false, false)) { 153 throw new InternalError("Failed to change attribute for " + libraryFileName + " file"); 154 } 155 } 156 157 } 158 159 /** 160 * Search for Visual Studio link.exe Search Order is: VS2017+, VS2013, VS2015, VS2012. 161 */ 162 private static String getWindowsLinkPath() throws Exception { 163 try { 164 Path vc141NewerLinker = getVC141AndNewerLinker(); 165 if (vc141NewerLinker != null) { 166 return vc141NewerLinker.toString(); 167 } 168 } catch (Exception e) { 169 e.printStackTrace(); 170 } 171 172 String link = "\\VC\\bin\\amd64\\link.exe"; 173 174 /** 175 * First try searching the paths pointed to by the VS environment variables. 176 */ 177 for (VSVERSIONS vs : VSVERSIONS.values()) { 178 String vspath = System.getenv(vs.getEnvVariable()); 179 if (vspath != null) { 180 File commonTools = new File(vspath); 181 File vsRoot = commonTools.getParentFile().getParentFile(); 182 File linkPath = new File(vsRoot, link); 183 if (linkPath.exists()) { 184 return linkPath.getPath(); 185 } 186 } 187 } 188 189 /** 190 * If we didn't find via the VS environment variables, try the well known paths 191 */ 192 for (VSVERSIONS vs : VSVERSIONS.values()) { 193 String wkp = vs.getWellKnownPath(); 194 if (new File(wkp).exists()) { 195 return wkp; 196 } 197 } 198 199 return null; 200 } 201 202 private static Path getVC141AndNewerLinker() throws Exception { 203 String programFilesX86 = System.getenv("ProgramFiles(x86)"); 204 if (programFilesX86 == null) { 205 throw new InternalError("Could not read the ProgramFiles(x86) environment variable"); 206 } 207 Path vswhere = Paths.get(programFilesX86 + "\\Microsoft Visual Studio\\Installer\\vswhere.exe"); 208 if (!Files.exists(vswhere)) { 209 return null; 210 } 211 212 ProcessBuilder processBuilder = new ProcessBuilder(vswhere.toString(), "-requires", 213 "Microsoft.VisualStudio.Component.VC.Tools.x86.x64", "-property", "installationPath", "-latest"); 214 processBuilder.redirectOutput(ProcessBuilder.Redirect.PIPE); 215 processBuilder.redirectError(ProcessBuilder.Redirect.PIPE); 216 Process process = processBuilder.start(); 217 final int exitCode = process.waitFor(); 218 if (exitCode != 0) { 219 String errorMessage = getString(process.getErrorStream()); 220 if (errorMessage.isEmpty()) { 221 errorMessage = getString(process.getInputStream()); 222 } 223 throw new InternalError(errorMessage); 224 } 225 226 String installationPath = getLines(process.getInputStream()).findFirst().orElseThrow(() -> new InternalError("Unexpected empty output from vswhere")); 227 Path vcToolsVersionFilePath = Paths.get(installationPath, "VC\\Auxiliary\\Build\\Microsoft.VCToolsVersion.default.txt"); 228 List<String> vcToolsVersionFileLines = Files.readAllLines(vcToolsVersionFilePath); 229 if (vcToolsVersionFileLines.isEmpty()) { 230 throw new InternalError(vcToolsVersionFilePath.toString() + " is empty"); 231 } 232 String vcToolsVersion = vcToolsVersionFileLines.get(0); 233 Path linkPath = Paths.get(installationPath, "VC\\Tools\\MSVC", vcToolsVersion, "bin\\Hostx64\\x64\\link.exe"); 234 if (!Files.exists(linkPath)) { 235 throw new InternalError("Linker at path " + linkPath.toString() + " does not exist"); 236 } 237 238 return linkPath; 239 } 240 241 // @formatter:off (workaround for Eclipse formatting bug) 242 enum VSVERSIONS { 243 VS2013("VS120COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\link.exe"), 244 VS2015("VS140COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\link.exe"), 245 VS2012("VS110COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 11.0\\VC\\bin\\amd64\\link.exe"); 246 247 private final String envvariable; 248 private final String wkp; 249 250 VSVERSIONS(String envvariable, String wellknownpath) { 251 this.envvariable = envvariable; 252 this.wkp = wellknownpath; 253 } 254 255 String getEnvVariable() { 256 return envvariable; 257 } 258 259 String getWellKnownPath() { 260 return wkp; 261 } 262 } 263 // @formatter:on 264 }