aboutsummaryrefslogtreecommitdiff
path: root/nailgun_launcher/Stage0Lib.java
blob: 34af7b0ff155ed493e08530feb19605a16700473 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
package cbt;
import java.io.*;
import java.lang.reflect.*;
import java.net.*;
import java.nio.*;
import java.nio.file.*;
import java.security.*;
import java.util.*;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import static java.io.File.pathSeparator;
import static cbt.NailgunLauncher.*;
import java.nio.file.*;
import java.nio.file.attribute.FileTime;
import static java.lang.Math.min;
import cbt.reflect.TrapSystemExit;

public class Stage0Lib{
  public static void _assert(boolean condition, Object msg){
    if(!condition){
      throw new AssertionError("Assertion failed: "+msg);
    }
  }

  public static int runMain(String cls, String[] args, ClassLoader cl) throws Throwable{
    return TrapSystemExit.run(
      new TrapSystemExit<Integer>(){
        @Override
        public Integer run() throws Throwable{
          cl.loadClass(cls)
            .getMethod("main", String[].class)
            .invoke( null, (Object) args);
          return 0;
        }
        @Override
        public Integer wrap(int exitCode){
          return exitCode;
        }
      }
    );
  }

  public static Object get(Object object, String method) throws Throwable{
    return object.getClass().getMethod( method ).invoke(object);
  }

  public static String classpath( String... files ){
    Arrays.sort(files);
    return mkString( pathSeparator, files );
  }

  public static long lastModified( String... files ){
    List<Long> lastModified = new ArrayList<Long>();
    for( String file: files ){
      lastModified.add( new File(file).lastModified() );
    }
    return Collections.max( lastModified );
  }

  public static ClassLoader loadDependency(
    String url,
    String file,
    String hash,
    ClassLoaderCache classLoaderCache,
    ClassLoader parent,
    String... classpathArray
  ) throws Throwable {
    download(new URL(url), Paths.get(file), hash);

    final long lastModified = lastModified( classpathArray );
    final String classpath = classpath( classpathArray );

    if( !classLoaderCache.containsKey( classpath, lastModified ) )
      classLoaderCache.put( classpath, classLoader( file, parent ), lastModified );

    return classLoaderCache.get( classpath, lastModified );
  }

  public static File write(File file, String content, OpenOption... options) throws Throwable{
    file.getParentFile().mkdirs();
    Files.write(file.toPath(), content.getBytes(), options);
    return file;
  }

  public static long compile(
    long lastModified, String classpath, String target,
    EarlyDependencies earlyDeps, List<File> sourceFiles
  ) throws Throwable{
    File statusFile = new File( new File(target) + ".last-success" );
    long lastCompiled = statusFile.lastModified();

    long maxLastModified = lastModified;
    final long start = System.currentTimeMillis(); // <- before recursing, so we catch them all

    for( File file: sourceFiles ){
      long l = file.lastModified();
      if( l > maxLastModified ) maxLastModified = l;
      // performance optimization because we'll recompile and don't need to check other files
      if( l > lastCompiled ) break;
    }

    if( maxLastModified > lastCompiled ){
      List<String> zincArgs = new ArrayList<String>(
        Arrays.asList(
          new String[]{
            "-scala-compiler", earlyDeps.scalaCompiler_2_11_8_File,
            "-scala-library", earlyDeps.scalaLibrary_2_11_8_File,
            "-scala-extra", earlyDeps.scalaReflect_2_11_8_File,
            "-sbt-interface", earlyDeps.sbtInterface_0_13_13_File,
            "-compiler-interface", earlyDeps.compilerInterface_0_13_13_File,
            "-cp", classpath,
            "-d", target,
            "-S-deprecation",
            "-S-feature",
            "-S-unchecked",
            "-S-language:existentials"
          }
        )
      );

      for( File f: sourceFiles ){
        zincArgs.add(f.toString());
      }

      PrintStream oldOut = System.out;
      try{
        System.setOut(System.err);
        int exitCode = runMain( "com.typesafe.zinc.Main", zincArgs.toArray(new String[zincArgs.size()]), earlyDeps.zinc );
        if( exitCode == 0 ){
          write( statusFile, "" );
          Files.setLastModifiedTime( statusFile.toPath(), FileTime.fromMillis(start) );
        } else {
          System.exit( exitCode );
        }
      } finally {
        System.setOut(oldOut);
      }
      return statusFile.lastModified(); // can't just use `start` here as system time precision is less than milliseconds on OSX
    } else {
      return lastCompiled;
    }
  }

  public static ClassLoader classLoader( String file ) throws Throwable{
    return new CbtURLClassLoader(
      new URL[]{ new URL("file:"+file) }
    );
  }
  public static ClassLoader classLoader( String file, ClassLoader parent ) throws Throwable{
    return new CbtURLClassLoader(
      new URL[]{ new URL("file:"+file) }, parent
    );
  }

  private static String getVarFromEnv(String envKey) {
    String value = System.getenv(envKey);
    if(value == null || value.isEmpty()) {
      value = System.getenv(envKey.toUpperCase());
    }
    return value;
  }

  private static void setProxyfromPropOrEnv(String envKey, String propKeyH, String propKeyP) {
    String proxyHost = System.getProperty(propKeyH);
    String proxyPort = System.getProperty(propKeyP);
    if((proxyHost == null || proxyHost.isEmpty()) && (proxyPort == null || proxyPort.isEmpty())) {
      String envVar = getVarFromEnv(envKey);
      if(envVar != null && !envVar.isEmpty()) {
        String[] proxy = envVar.replaceFirst("^https?://", "").split(":", 2);
        System.setProperty(propKeyH, proxy[0]);
        System.setProperty(propKeyP, proxy[1]);
      }
    }
  }

  public static void installProxySettings() throws URISyntaxException {
    setProxyfromPropOrEnv("http_proxy", "http.proxyHost", "http.proxyPort");
    setProxyfromPropOrEnv("https_proxy", "https.proxyHost", "https.proxyPort");
    String nonHosts = System.getProperty("http.nonProxyHosts");
    if(nonHosts == null || nonHosts.isEmpty()) {
      String envVar = getVarFromEnv("no_proxy");
      if(envVar != null && !envVar.isEmpty()) {
        System.setProperty("http.nonProxyHosts", envVar.replaceAll(",","|"));
      }
    }
  }

  private static final ProxySelector ps = ProxySelector.getDefault();

  public static HttpURLConnection openConnectionConsideringProxy(URL urlString)
      throws IOException, URISyntaxException {
    java.net.Proxy proxy = ps.select(urlString.toURI()).get(0);
    return (HttpURLConnection) urlString.openConnection(proxy);
  }

  public static void download(URL urlString, Path target, String sha1) throws Throwable {
    final Path unverified = Paths.get(target+".unverified");
    if(!Files.exists(target)) {
      new File(target.toString()).getParentFile().mkdirs();
      System.err.println("downloading " + urlString);
      System.err.println("to " + target);
      final InputStream stream = openConnectionConsideringProxy(urlString).getInputStream();
      Files.copy(stream, unverified, StandardCopyOption.REPLACE_EXISTING);
      stream.close();
      final String checksum = sha1(Files.readAllBytes(unverified));
      if(sha1 == null || sha1.toLowerCase().equals(checksum)) {
        Files.move(unverified, target, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE);
      } else {
        System.err.println(unverified + " checksum does not match.\nExpected: |" + sha1 + "|\nFound:    |" + checksum + "|");
        System.exit(1);
      }
    }
  }

  public static String sha1(byte[] bytes) throws Throwable {
    final MessageDigest sha1 = MessageDigest.getInstance("SHA1");
    sha1.update(bytes, 0, bytes.length);
    return (new HexBinaryAdapter()).marshal(sha1.digest()).toLowerCase();
  }

  public static String mkString(String separator, Object[] parts){
    String result = parts[0].toString();
    for(int i = 1; i < parts.length; i++){
      result += separator + parts[i].toString();
    }
    return result;
  }

  public static String[] append( String[] array, String item ){
    String[] copy = Arrays.copyOf(array, array.length + 1);
    copy[array.length] = item;
    return copy;
  }

  public static String[] concat( String[] left, String[] right ){
    String[] result = Arrays.copyOf(left, left.length + right.length);
    System.arraycopy(right, 0, result, left.length, right.length);
    return result;
  }
}