1/*
2 * Copyright (c) 2017, 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
24package jdk.tools.jaotc;
25
26import java.io.BufferedReader;
27import java.io.File;
28import java.io.InputStream;
29import java.io.InputStreamReader;
30import java.util.stream.Stream;
31
32final class Linker {
33
34    private final Options options;
35    private String objectFileName;
36    private String libraryFileName;
37    private String linkerCmd;
38
39    String objFile() {
40        return objectFileName;
41    }
42
43    String libFile() {
44        return libraryFileName;
45    }
46
47    Linker(Main main) throws Exception {
48        this.options = main.options;
49        String name = options.outputName;
50        objectFileName = name;
51        libraryFileName = name;
52
53        if (options.linkerpath != null && !(new File(options.linkerpath).exists())) {
54            throw new InternalError("Invalid linker path: " + options.linkerpath);
55        }
56        String linkerPath;
57        String linkerCheck;
58
59        switch (options.osName) {
60            case "Linux":
61                if (name.endsWith(".so")) {
62                    objectFileName = name.substring(0, name.length() - ".so".length());
63                }
64                linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld";
65                linkerCmd = linkerPath + " -shared -z noexecstack -o " + libraryFileName + " " + objectFileName;
66                linkerCheck = linkerPath + " -v";
67                break;
68            case "SunOS":
69                if (name.endsWith(".so")) {
70                    objectFileName = name.substring(0, name.length() - ".so".length());
71                }
72                objectFileName = objectFileName + ".o";
73                linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld";
74                linkerCmd = linkerPath + " -shared -o " + libraryFileName + " " + objectFileName;
75                linkerCheck = linkerPath + " -V";
76                break;
77            case "Mac OS X":
78                if (name.endsWith(".dylib")) {
79                    objectFileName = name.substring(0, name.length() - ".dylib".length());
80                }
81                objectFileName = objectFileName + ".o";
82                linkerPath = (options.linkerpath != null) ? options.linkerpath : "ld";
83                linkerCmd = linkerPath + " -dylib -o " + libraryFileName + " " + objectFileName;
84                linkerCheck = linkerPath + " -v";
85                break;
86            default:
87                if (options.osName.startsWith("Windows")) {
88                    if (name.endsWith(".dll")) {
89                        objectFileName = name.substring(0, name.length() - ".dll".length());
90                    }
91                    objectFileName = objectFileName + ".obj";
92                    linkerPath = (options.linkerpath != null) ? options.linkerpath : getWindowsLinkPath();
93                    if (linkerPath == null) {
94                        throw new InternalError("Can't locate Microsoft Visual Studio amd64 link.exe");
95                    }
96                    linkerCmd = linkerPath + " /DLL /OPT:NOREF /NOLOGO /NOENTRY" + " /OUT:" + libraryFileName + " " + objectFileName;
97                    linkerCheck = null; // link.exe presence is verified already
98                    break;
99                } else {
100                    throw new InternalError("Unsupported platform: " + options.osName);
101                }
102        }
103
104        // Check linker presence on platforms by printing its version
105        if (linkerCheck != null) {
106            Process p = Runtime.getRuntime().exec(linkerCheck);
107            final int exitCode = p.waitFor();
108            if (exitCode != 0) {
109                InputStream stderr = p.getErrorStream();
110                BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
111                Stream<String> lines = br.lines();
112                StringBuilder sb = new StringBuilder();
113                lines.iterator().forEachRemaining(e -> sb.append(e));
114                throw new InternalError(sb.toString());
115            }
116        }
117    }
118
119    void link() throws Exception {
120        Process p = Runtime.getRuntime().exec(linkerCmd);
121        final int exitCode = p.waitFor();
122        if (exitCode != 0) {
123            InputStream stderr = p.getErrorStream();
124            if (stderr.read() == -1) {
125                stderr = p.getInputStream();
126            }
127            BufferedReader br = new BufferedReader(new InputStreamReader(stderr));
128            Stream<String> lines = br.lines();
129            StringBuilder sb = new StringBuilder();
130            lines.iterator().forEachRemaining(e -> sb.append(e));
131            throw new InternalError(sb.toString());
132        }
133        File objFile = new File(objectFileName);
134        if (objFile.exists()) {
135            if (!objFile.delete()) {
136                throw new InternalError("Failed to delete " + objectFileName + " file");
137            }
138        }
139        // Make non-executable for all.
140        File libFile = new File(libraryFileName);
141        if (libFile.exists() && !options.osName.startsWith("Windows")) {
142            if (!libFile.setExecutable(false, false)) {
143                throw new InternalError("Failed to change attribute for " + libraryFileName + " file");
144            }
145        }
146
147    }
148
149    /**
150     * Visual Studio supported versions Search Order is: VS2013, VS2015, VS2012
151     */
152    public enum VSVERSIONS {
153        VS2013("VS120COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 12.0\\VC\\bin\\amd64\\link.exe"),
154        VS2015("VS140COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 14.0\\VC\\bin\\amd64\\link.exe"),
155        VS2012("VS110COMNTOOLS", "C:\\Program Files (x86)\\Microsoft Visual Studio 11.0\\VC\\bin\\amd64\\link.exe");
156
157        private final String envvariable;
158        private final String wkp;
159
160        VSVERSIONS(String envvariable, String wellknownpath) {
161            this.envvariable = envvariable;
162            this.wkp = wellknownpath;
163        }
164
165        String EnvVariable() {
166            return envvariable;
167        }
168
169        String WellKnownPath() {
170            return wkp;
171        }
172    }
173
174    /**
175     * Search for Visual Studio link.exe Search Order is: VS2013, VS2015, VS2012
176     */
177    private static String getWindowsLinkPath() {
178        String link = "\\VC\\bin\\amd64\\link.exe";
179
180        /**
181         * First try searching the paths pointed to by the VS environment variables.
182         */
183        for (VSVERSIONS vs : VSVERSIONS.values()) {
184            String vspath = System.getenv(vs.EnvVariable());
185            if (vspath != null) {
186                File commonTools = new File(vspath);
187                File vsRoot = commonTools.getParentFile().getParentFile();
188                File linkPath = new File(vsRoot, link);
189                if (linkPath.exists()) {
190                    return linkPath.getPath();
191                }
192            }
193        }
194
195        /**
196         * If we didn't find via the VS environment variables, try the well known paths
197         */
198        for (VSVERSIONS vs : VSVERSIONS.values()) {
199            String wkp = vs.WellKnownPath();
200            if (new File(wkp).exists()) {
201                return wkp;
202            }
203        }
204
205        return null;
206    }
207
208}
209