diff options
author | Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-09 21:20:11 -0500 |
---|---|---|
committer | Christopher Vogt <oss.nsp@cvogt.org> | 2017-02-09 22:43:00 -0500 |
commit | e8673866b79f7473391dcee26243eee80d5d3cb6 (patch) | |
tree | 16146affeebdb58cd302a1f8527220c906818c96 /nailgun_launcher | |
parent | bee13ba7a4458482ce00a5c6bae4cd64328c4e5e (diff) | |
download | cbt-e8673866b79f7473391dcee26243eee80d5d3cb6.tar.gz cbt-e8673866b79f7473391dcee26243eee80d5d3cb6.tar.bz2 cbt-e8673866b79f7473391dcee26243eee80d5d3cb6.zip |
idempotent change propagation
using lastModified instead of a non-idempotent needsUpdate flag
this fixes a bug where dependees would not be rebuilt if cbt exited
or was killed after dependencies were already rebuilt.
Diffstat (limited to 'nailgun_launcher')
-rw-r--r-- | nailgun_launcher/BuildStage1Result.java | 8 | ||||
-rw-r--r-- | nailgun_launcher/CbtURLClassLoader.java | 8 | ||||
-rw-r--r-- | nailgun_launcher/ClassLoaderCache.java | 66 | ||||
-rw-r--r-- | nailgun_launcher/EarlyDependencies.java | 140 | ||||
-rw-r--r-- | nailgun_launcher/JavaCache.java | 40 | ||||
-rw-r--r-- | nailgun_launcher/NailgunLauncher.java | 80 | ||||
-rw-r--r-- | nailgun_launcher/Stage0Lib.java | 53 |
7 files changed, 245 insertions, 150 deletions
diff --git a/nailgun_launcher/BuildStage1Result.java b/nailgun_launcher/BuildStage1Result.java index 312871d..64a660d 100644 --- a/nailgun_launcher/BuildStage1Result.java +++ b/nailgun_launcher/BuildStage1Result.java @@ -1,12 +1,14 @@ package cbt; public class BuildStage1Result{ - public Boolean changed; + public long start; + public long stage1LastModified; public ClassLoader classLoader; public String stage1Classpath; public String nailgunClasspath; public String compatibilityClasspath; - public BuildStage1Result( Boolean changed, ClassLoader classLoader, String stage1Classpath, String nailgunClasspath, String compatibilityClasspath ){ - this.changed = changed; + public BuildStage1Result( long start, long stage1LastModified, ClassLoader classLoader, String stage1Classpath, String nailgunClasspath, String compatibilityClasspath ){ + this.start = start; + this.stage1LastModified = stage1LastModified; this.classLoader = classLoader; this.stage1Classpath = stage1Classpath; this.nailgunClasspath = nailgunClasspath; diff --git a/nailgun_launcher/CbtURLClassLoader.java b/nailgun_launcher/CbtURLClassLoader.java index 43d07f4..fac7050 100644 --- a/nailgun_launcher/CbtURLClassLoader.java +++ b/nailgun_launcher/CbtURLClassLoader.java @@ -15,7 +15,7 @@ public class CbtURLClassLoader extends java.net.URLClassLoader{ + "\n)" ); } - JavaCache<Class> cache = new JavaCache<Class>( new ConcurrentHashMap<Object,Object>() ); + ConcurrentHashMap<String,Class> cache = new ConcurrentHashMap<String,Class>(); public Class loadClass(String name) throws ClassNotFoundException{ Class _class = super.loadClass(name); if(_class == null) throw new ClassNotFoundException(name); @@ -24,11 +24,11 @@ public class CbtURLClassLoader extends java.net.URLClassLoader{ public Class loadClass(String name, Boolean resolve) throws ClassNotFoundException{ //System.out.println("loadClass("+name+") on \n"+this); synchronized( cache ){ - if(!cache.contains(name)) + if(!cache.containsKey(name)) try{ - cache.put(super.loadClass(name, resolve), name); + cache.put(name, super.loadClass(name, resolve)); } catch (ClassNotFoundException e){ - cache.put(Object.class, name); + cache.put(name, Object.class); } Class _class = cache.get(name); if(_class == Object.class){ diff --git a/nailgun_launcher/ClassLoaderCache.java b/nailgun_launcher/ClassLoaderCache.java new file mode 100644 index 0000000..6bffad0 --- /dev/null +++ b/nailgun_launcher/ClassLoaderCache.java @@ -0,0 +1,66 @@ +package cbt; + +import java.util.*; +import static java.io.File.pathSeparator; +import static cbt.Stage0Lib.*; + +final public class ClassLoaderCache{ + public Map<Object,Object> hashMap; + final ThreadLocal<HashSet<String>> seen = new ThreadLocal<HashSet<String>>(){ + @Override protected HashSet<String> initialValue(){ + return new HashSet<String>(); + } + }; + + public ClassLoaderCache( + Map<Object,Object> hashMap + ){ + this.hashMap = hashMap; + } + + public ClassLoader get( String key, long timestamp ){ + seen.get().add( key ); + @SuppressWarnings("unchecked") + ClassLoader t = (ClassLoader) hashMap.get( + hashMap.get( key ) + ); + assert hashMap.get(t).equals(timestamp); + return t; + } + + public boolean containsKey( String key, long timestamp ){ + boolean contains = hashMap.containsKey( key ); + if( contains ){ + Object keyObject = hashMap.get( key ); + Object classLoader = hashMap.get( keyObject ); + long oldTimestamp = (long) hashMap.get( classLoader ); + boolean res = oldTimestamp == timestamp; + return res; + } else { + return false; + } + } + + public void put( String key, ClassLoader value, long timestamp ){ + assert !seen.get().contains( key ): "Thread tries to update cache key after observing it: " + key; + LockableJavaKey keyObject = new LockableJavaKey(); + hashMap.put( key, keyObject ); + hashMap.put( keyObject, value ); + hashMap.put( value, timestamp ); + } + + @Override public String toString(){ + StringBuilder res = new StringBuilder(); + res.append("ClassLoaderCache(\n\n"); + for( Object key: hashMap.keySet() ){ + if( key instanceof String ) + res.append( + join( "\n", key.toString().split(":") ) + " -> " + hashMap.get( hashMap.get(key) ) + + "\n\n" + ); + } + res.append("\n\n"); + return res.toString(); + } +} +class LockableJavaKey{} diff --git a/nailgun_launcher/EarlyDependencies.java b/nailgun_launcher/EarlyDependencies.java index fdb54b5..8709e69 100644 --- a/nailgun_launcher/EarlyDependencies.java +++ b/nailgun_launcher/EarlyDependencies.java @@ -4,6 +4,7 @@ import java.io.*; import java.nio.file.*; import java.net.*; import java.security.*; +import java.util.*; import static cbt.Stage0Lib.*; import static cbt.NailgunLauncher.*; @@ -28,7 +29,7 @@ class EarlyDependencies{ String scalaLibrary_2_10_6_File; public EarlyDependencies( - String mavenCache, String mavenUrl, JavaCache<ClassLoader> classLoaderCache, ClassLoader rootClassLoader + String mavenCache, String mavenUrl, ClassLoaderCache classLoaderCache, ClassLoader rootClassLoader ) throws Throwable { scalaReflect_2_11_8_File = mavenCache + "/org/scala-lang/scala-reflect/2.11.8/scala-reflect-2.11.8.jar"; scalaCompiler_2_11_8_File = mavenCache + "/org/scala-lang/scala-compiler/2.11.8/scala-compiler-2.11.8.jar"; @@ -46,95 +47,104 @@ class EarlyDependencies{ download(new URL(mavenUrl + "/org/scala-lang/scala-compiler/2.11.8/scala-compiler-2.11.8.jar"), Paths.get(scalaCompiler_2_11_8_File), "fe1285c9f7b58954c5ef6d80b59063569c065e9a"); // org.scala-lang:scala-library:2.10.6 - download(new URL(mavenUrl + "/org/scala-lang/scala-library/2.10.6/scala-library-2.10.6.jar"), Paths.get(scalaLibrary_2_10_6_File), "421989aa8f95a05a4f894630aad96b8c7b828732"); - String[] scalaLibrary_2_10_6_ClasspathArray = new String[]{scalaLibrary_2_10_6_File}; - String scalaLibrary_2_10_6_Classpath = classpath( scalaLibrary_2_10_6_ClasspathArray ); - ClassLoader scalaLibrary_2_10_6_ = - classLoaderCache.contains( scalaLibrary_2_10_6_Classpath ) - ? classLoaderCache.get( scalaLibrary_2_10_6_Classpath ) - : classLoaderCache.put( classLoader( scalaLibrary_2_10_6_File, rootClassLoader ), scalaLibrary_2_10_6_Classpath ); + ClassLoader scalaLibrary_2_10_6_ = loadDependency( + mavenUrl + "/org/scala-lang/scala-library/2.10.6/scala-library-2.10.6.jar", + scalaLibrary_2_10_6_File, + "421989aa8f95a05a4f894630aad96b8c7b828732", + classLoaderCache, + rootClassLoader, + scalaLibrary_2_10_6_ClasspathArray + ); // org.scala-lang:scala-reflect:2.10.6 - download(new URL(mavenUrl + "/org/scala-lang/scala-reflect/2.10.6/scala-reflect-2.10.6.jar"), Paths.get(scalaReflect_2_10_6_File), "3259f3df0f166f017ef5b2d385445808398c316c"); - String[] scalaReflect_2_10_6_ClasspathArray = new String[]{scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String scalaReflect_2_10_6_Classpath = classpath( scalaReflect_2_10_6_ClasspathArray ); - ClassLoader scalaReflect_2_10_6_ = - classLoaderCache.contains( scalaReflect_2_10_6_Classpath ) - ? classLoaderCache.get( scalaReflect_2_10_6_Classpath ) - : classLoaderCache.put( classLoader( scalaReflect_2_10_6_File, scalaLibrary_2_10_6_ ), scalaReflect_2_10_6_Classpath ); + ClassLoader scalaReflect_2_10_6_ = loadDependency( + mavenUrl + "/org/scala-lang/scala-reflect/2.10.6/scala-reflect-2.10.6.jar", + scalaReflect_2_10_6_File, + "3259f3df0f166f017ef5b2d385445808398c316c", + classLoaderCache, + scalaLibrary_2_10_6_, + scalaReflect_2_10_6_ClasspathArray + ); // com.typesafe.sbt:sbt-interface:0.13.12 - download(new URL(mavenUrl + "/com/typesafe/sbt/sbt-interface/0.13.12/sbt-interface-0.13.12.jar"), Paths.get(sbtInterface_0_13_12_File), "fcc7875c02f0d4641fac0518121bd71475d3909b"); - String[] sbtInterface_0_13_12_ClasspathArray = new String[]{sbtInterface_0_13_12_File, scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String sbtInterface_0_13_12_Classpath = classpath( sbtInterface_0_13_12_ClasspathArray ); - ClassLoader sbtInterface_0_13_12_ = - classLoaderCache.contains( sbtInterface_0_13_12_Classpath ) - ? classLoaderCache.get( sbtInterface_0_13_12_Classpath ) - : classLoaderCache.put( classLoader( sbtInterface_0_13_12_File, scalaReflect_2_10_6_ ), sbtInterface_0_13_12_Classpath ); + ClassLoader sbtInterface_0_13_12_ = loadDependency( + mavenUrl + "/com/typesafe/sbt/sbt-interface/0.13.12/sbt-interface-0.13.12.jar", + sbtInterface_0_13_12_File, + "fcc7875c02f0d4641fac0518121bd71475d3909b", + classLoaderCache, + scalaReflect_2_10_6_, + sbtInterface_0_13_12_ClasspathArray + ); // org.scala-lang:scala-compiler:2.10.6 - download(new URL(mavenUrl + "/org/scala-lang/scala-compiler/2.10.6/scala-compiler-2.10.6.jar"), Paths.get(scalaCompiler_2_10_6_File), "9b15174852f5b6bb1edbf303d5722286a0a54011"); - String[] scalaCompiler_2_10_6_ClasspathArray = new String[]{sbtInterface_0_13_12_File, scalaCompiler_2_10_6_File, scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String scalaCompiler_2_10_6_Classpath = classpath( scalaCompiler_2_10_6_ClasspathArray ); - ClassLoader scalaCompiler_2_10_6_ = - classLoaderCache.contains( scalaCompiler_2_10_6_Classpath ) - ? classLoaderCache.get( scalaCompiler_2_10_6_Classpath ) - : classLoaderCache.put( classLoader( scalaCompiler_2_10_6_File, sbtInterface_0_13_12_ ), scalaCompiler_2_10_6_Classpath ); + ClassLoader scalaCompiler_2_10_6_ = loadDependency( + mavenUrl + "/org/scala-lang/scala-compiler/2.10.6/scala-compiler-2.10.6.jar", + scalaCompiler_2_10_6_File, + "9b15174852f5b6bb1edbf303d5722286a0a54011", + classLoaderCache, + sbtInterface_0_13_12_, + scalaCompiler_2_10_6_ClasspathArray + ); // com.typesafe.sbt:compiler-interface:0.13.12 - download(new URL(mavenUrl + "/com/typesafe/sbt/compiler-interface/0.13.12/compiler-interface-0.13.12-sources.jar"), Paths.get(compilerInterface_0_13_12_File), "d9c3270576e162bf017b146af262364c2db87a32"); - String[] compilerInterface_0_13_12_ClasspathArray = new String[]{compilerInterface_0_13_12_File, sbtInterface_0_13_12_File, scalaCompiler_2_10_6_File, scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String compilerInterface_0_13_12_Classpath = classpath( compilerInterface_0_13_12_ClasspathArray ); - ClassLoader compilerInterface_0_13_12_ = - classLoaderCache.contains( compilerInterface_0_13_12_Classpath ) - ? classLoaderCache.get( compilerInterface_0_13_12_Classpath ) - : classLoaderCache.put( classLoader( compilerInterface_0_13_12_File, scalaCompiler_2_10_6_ ), compilerInterface_0_13_12_Classpath ); + ClassLoader compilerInterface_0_13_12_ = loadDependency( + mavenUrl + "/com/typesafe/sbt/compiler-interface/0.13.12/compiler-interface-0.13.12-sources.jar", + compilerInterface_0_13_12_File, + "d9c3270576e162bf017b146af262364c2db87a32", + classLoaderCache, + scalaCompiler_2_10_6_, + compilerInterface_0_13_12_ClasspathArray + ); // com.typesafe.sbt:incremental-compiler:0.13.12 - download(new URL(mavenUrl + "/com/typesafe/sbt/incremental-compiler/0.13.12/incremental-compiler-0.13.12.jar"), Paths.get(incrementalCompiler_0_13_12_File), "259f6d24a5a3791bb233787d6a8e639c4ab86fe5"); - String[] incrementalCompiler_0_13_12_ClasspathArray = new String[]{compilerInterface_0_13_12_File, incrementalCompiler_0_13_12_File, sbtInterface_0_13_12_File, scalaCompiler_2_10_6_File, scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String incrementalCompiler_0_13_12_Classpath = classpath( incrementalCompiler_0_13_12_ClasspathArray ); - ClassLoader incrementalCompiler_0_13_12_ = - classLoaderCache.contains( incrementalCompiler_0_13_12_Classpath ) - ? classLoaderCache.get( incrementalCompiler_0_13_12_Classpath ) - : classLoaderCache.put( classLoader( incrementalCompiler_0_13_12_File, compilerInterface_0_13_12_ ), incrementalCompiler_0_13_12_Classpath ); + ClassLoader incrementalCompiler_0_13_12_ = loadDependency( + mavenUrl + "/com/typesafe/sbt/incremental-compiler/0.13.12/incremental-compiler-0.13.12.jar", + incrementalCompiler_0_13_12_File, + "259f6d24a5a3791bb233787d6a8e639c4ab86fe5", + classLoaderCache, + compilerInterface_0_13_12_, + incrementalCompiler_0_13_12_ClasspathArray + ); // com.typesafe.zinc:zinc:0.3.12 - download(new URL(mavenUrl + "/com/typesafe/zinc/zinc/0.3.12/zinc-0.3.12.jar"), Paths.get(zinc_0_3_12_File), "c4339e93f5b7273f49ad026248f4fdb1d4d6c7c4"); - String[] zinc_0_3_12_ClasspathArray = new String[]{compilerInterface_0_13_12_File, incrementalCompiler_0_13_12_File, sbtInterface_0_13_12_File, zinc_0_3_12_File, scalaCompiler_2_10_6_File, scalaLibrary_2_10_6_File, scalaReflect_2_10_6_File}; - String zinc_0_3_12_Classpath = classpath( zinc_0_3_12_ClasspathArray ); - ClassLoader zinc_0_3_12_ = - classLoaderCache.contains( zinc_0_3_12_Classpath ) - ? classLoaderCache.get( zinc_0_3_12_Classpath ) - : classLoaderCache.put( classLoader( zinc_0_3_12_File, incrementalCompiler_0_13_12_ ), zinc_0_3_12_Classpath ); + ClassLoader zinc_0_3_12_ = loadDependency( + mavenUrl + "/com/typesafe/zinc/zinc/0.3.12/zinc-0.3.12.jar", + zinc_0_3_12_File, + "c4339e93f5b7273f49ad026248f4fdb1d4d6c7c4", + classLoaderCache, + incrementalCompiler_0_13_12_, + zinc_0_3_12_ClasspathArray + ); // org.scala-lang:scala-library:2.11.8 - download(new URL(mavenUrl + "/org/scala-lang/scala-library/2.11.8/scala-library-2.11.8.jar"), Paths.get(scalaLibrary_2_11_8_File), "ddd5a8bced249bedd86fb4578a39b9fb71480573"); - String[] scalaLibrary_2_11_8_ClasspathArray = new String[]{scalaLibrary_2_11_8_File}; - String scalaLibrary_2_11_8_Classpath = classpath( scalaLibrary_2_11_8_ClasspathArray ); - ClassLoader scalaLibrary_2_11_8_ = - classLoaderCache.contains( scalaLibrary_2_11_8_Classpath ) - ? classLoaderCache.get( scalaLibrary_2_11_8_Classpath ) - : classLoaderCache.put( classLoader( scalaLibrary_2_11_8_File, rootClassLoader ), scalaLibrary_2_11_8_Classpath ); + ClassLoader scalaLibrary_2_11_8_ = loadDependency( + mavenUrl + "/org/scala-lang/scala-library/2.11.8/scala-library-2.11.8.jar", + scalaLibrary_2_11_8_File, + "ddd5a8bced249bedd86fb4578a39b9fb71480573", + classLoaderCache, + rootClassLoader, + scalaLibrary_2_11_8_ClasspathArray + ); // org.scala-lang.modules:scala-xml_2.11:1.0.5 - download(new URL(mavenUrl + "/org/scala-lang/modules/scala-xml_2.11/1.0.5/scala-xml_2.11-1.0.5.jar"), Paths.get(scalaXml_1_0_5_File), "77ac9be4033768cf03cc04fbd1fc5e5711de2459"); - String[] scalaXml_1_0_5_ClasspathArray = new String[]{scalaXml_1_0_5_File, scalaLibrary_2_11_8_File}; - String scalaXml_1_0_5_Classpath = classpath( scalaXml_1_0_5_ClasspathArray ); - ClassLoader scalaXml_1_0_5_ = - classLoaderCache.contains( scalaXml_1_0_5_Classpath ) - ? classLoaderCache.get( scalaXml_1_0_5_Classpath ) - : classLoaderCache.put( classLoader( scalaXml_1_0_5_File, scalaLibrary_2_11_8_ ), scalaXml_1_0_5_Classpath ); - + ClassLoader scalaXml_1_0_5_ = loadDependency( + mavenUrl + "/org/scala-lang/modules/scala-xml_2.11/1.0.5/scala-xml_2.11-1.0.5.jar", + scalaXml_1_0_5_File, + "77ac9be4033768cf03cc04fbd1fc5e5711de2459", + classLoaderCache, + scalaLibrary_2_11_8_, + scalaXml_1_0_5_ClasspathArray + ); + classLoader = scalaXml_1_0_5_; classpathArray = scalaXml_1_0_5_ClasspathArray; diff --git a/nailgun_launcher/JavaCache.java b/nailgun_launcher/JavaCache.java deleted file mode 100644 index 3ba12ab..0000000 --- a/nailgun_launcher/JavaCache.java +++ /dev/null @@ -1,40 +0,0 @@ -package cbt; - -import java.util.*; -import static java.io.File.pathSeparator; -import static cbt.Stage0Lib.*; - -final class JavaCache<T>{ - Map<Object,Object> hashMap; - - public JavaCache( - Map<Object,Object> hashMap - ){ - this.hashMap = hashMap; - } - - public T get( Object key ){ - @SuppressWarnings("unchecked") - T t = (T) hashMap.get( - hashMap.get( key ) - ); - return t; - } - - public Boolean contains( Object key/*, Long timestamp*/ ){ - return hashMap.containsKey( key );/* && ( - (Long) hashMap.get( hashMap.get( hashMap.get(key) ) ) >= timestamp - );*/ - } - - public T put( Object value, Object key/*, Long timestamp*/ ){ - LockableJavaKey keyObject = new LockableJavaKey(); - hashMap.put( key, keyObject ); - hashMap.put( keyObject, value ); - //hashMap.put( value, timestamp ); - @SuppressWarnings("unchecked") - T t = (T) value; - return t; - } -} -class LockableJavaKey{} diff --git a/nailgun_launcher/NailgunLauncher.java b/nailgun_launcher/NailgunLauncher.java index c397810..8a330d8 100644 --- a/nailgun_launcher/NailgunLauncher.java +++ b/nailgun_launcher/NailgunLauncher.java @@ -26,12 +26,12 @@ public class NailgunLauncher{ @SuppressWarnings("unchecked") public static Object getBuild( Object context ) throws Throwable{ BuildStage1Result res = buildStage1( - (Boolean) get(context, "cbtHasChangedCompat"), - (Long) get(context, "startCompat"), + (long) get(context, "cbtLastModified"), + (long) get(context, "start"), ((File) get(context, "cache")).toString() + "/", ((File) get(context, "cbtHome")).toString(), ((File) get(context, "compatibilityTarget")).toString() + "/", - new JavaCache<ClassLoader>( + new ClassLoaderCache( (HashMap) get(context, "persistentCache") ) ); @@ -43,6 +43,8 @@ public class NailgunLauncher{ .invoke(null, context, res); } + public static long nailgunLauncherLastModified = -1; // this initial value should be overwritten, never read + public static void main( String[] args ) throws Throwable { Long _start = System.currentTimeMillis(); if(args[0].equals("check-alive")){ @@ -76,13 +78,18 @@ public class NailgunLauncher{ String CBT_HOME = System.getenv("CBT_HOME"); String cache = CBT_HOME + "/cache/"; String compatibilityTarget = CBT_HOME + "/compatibility/" + TARGET; - // copy cache, so that this thread has a consistent view - // replace before returning, see below - JavaCache<ClassLoader> classLoaderCache = new JavaCache<ClassLoader>( + // copy cache, so that this thread has a consistent view despite other threads + // changing their copies + // replace again before returning, see below + ClassLoaderCache classLoaderCache = new ClassLoaderCache( new HashMap<Object,Object>(classLoaderCacheHashMap) ); + + String nailgunTarget = CBT_HOME + "/" + NAILGUN + TARGET; + long nailgunLauncherLastModified = new File( nailgunTarget + "../classes.last-success" ).lastModified(); + BuildStage1Result res = buildStage1( - false, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache + nailgunLauncherLastModified, start, cache, CBT_HOME, compatibilityTarget, classLoaderCache ); try{ @@ -90,9 +97,9 @@ public class NailgunLauncher{ .classLoader .loadClass("cbt.Stage1") .getMethod( - "run", String[].class, File.class, File.class, BuildStage1Result.class, Long.class, Map.class + "run", String[].class, File.class, File.class, BuildStage1Result.class, Map.class ).invoke( - null, (Object) args, new File(cache), new File(CBT_HOME), res, start, classLoaderCache.hashMap + null, (Object) args, new File(cache), new File(CBT_HOME), res, classLoaderCache.hashMap ); System.exit( exitCode ); @@ -115,7 +122,8 @@ public class NailgunLauncher{ } public static BuildStage1Result buildStage1( - Boolean changed, long start, String cache, String cbtHome, String compatibilityTarget, JavaCache<ClassLoader> classLoaderCache + final long lastModified, final long start, final String cache, final String cbtHome, + final String compatibilityTarget, final ClassLoaderCache classLoaderCache ) throws Throwable { _assert(TARGET != null, "environment variable TARGET not defined"); String nailgunTarget = cbtHome + "/" + NAILGUN + TARGET; @@ -128,9 +136,11 @@ public class NailgunLauncher{ ClassLoader rootClassLoader = new CbtURLClassLoader( new URL[]{}, ClassLoader.getSystemClassLoader().getParent() ); // wrap for caching EarlyDependencies earlyDeps = new EarlyDependencies(mavenCache, mavenUrl, classLoaderCache, rootClassLoader); - ClassLoader compatibilityClassLoader; + long nailgunLauncherLastModified = new File( nailgunTarget + "../classes.last-success" ).lastModified(); + + long compatibilityLastModified; if(!compatibilityTarget.startsWith(cbtHome)){ - compatibilityClassLoader = classLoaderCache.get( compatibilityTarget ); + compatibilityLastModified = new File( compatibilityTarget + "../classes.last-success" ).lastModified(); } else { List<File> compatibilitySourceFiles = new ArrayList<File>(); for( File f: compatibilitySources.listFiles() ){ @@ -138,20 +148,20 @@ public class NailgunLauncher{ compatibilitySourceFiles.add(f); } } - changed = compile(changed, start, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles); - - if( classLoaderCache.contains( compatibilityTarget ) ){ - compatibilityClassLoader = classLoaderCache.get( compatibilityTarget ); - } else { - compatibilityClassLoader = classLoaderCache.put( classLoader(compatibilityTarget, rootClassLoader), compatibilityTarget ); + + compatibilityLastModified = compile( 0L, "", compatibilityTarget, earlyDeps, compatibilitySourceFiles); + + if( !classLoaderCache.containsKey( compatibilityTarget, compatibilityLastModified ) ){ + classLoaderCache.put( compatibilityTarget, classLoader(compatibilityTarget, rootClassLoader), compatibilityLastModified ); } } + final ClassLoader compatibilityClassLoader = classLoaderCache.get( compatibilityTarget, compatibilityLastModified ); String[] nailgunClasspathArray = append( earlyDeps.classpathArray, nailgunTarget ); String nailgunClasspath = classpath( nailgunClasspathArray ); - ClassLoader nailgunClassLoader = new CbtURLClassLoader( new URL[]{}, NailgunLauncher.class.getClassLoader() ); // wrap for caching - if( !classLoaderCache.contains( nailgunClasspath ) ){ - nailgunClassLoader = classLoaderCache.put( nailgunClassLoader, nailgunClasspath ); + final ClassLoader nailgunClassLoader = new CbtURLClassLoader( new URL[]{}, NailgunLauncher.class.getClassLoader() ); // wrap for caching + if( !classLoaderCache.containsKey( nailgunClasspath, nailgunLauncherLastModified ) ){ + classLoaderCache.put( nailgunClasspath, nailgunClassLoader, nailgunLauncherLastModified ); } String[] stage1ClasspathArray = @@ -164,14 +174,24 @@ public class NailgunLauncher{ stage1SourceFiles.add(f); } } - changed = compile(changed, start, stage1Classpath, stage1Target, earlyDeps, stage1SourceFiles); - ClassLoader stage1classLoader; - if( !changed && classLoaderCache.contains( stage1Classpath ) ){ - stage1classLoader = classLoaderCache.get( stage1Classpath ); - } else { - stage1classLoader = + final long stage1BeforeCompiled = System.currentTimeMillis(); + final long stage0LastModified = Math.max( + lastModified, + Math.max( lastModified, compatibilityLastModified ) + ); + final long stage1LastModified = compile( + stage0LastModified, stage1Classpath, stage1Target, earlyDeps, stage1SourceFiles + ); + + if( stage1LastModified < compatibilityLastModified ) + throw new AssertionError( + "Cache invalidation bug: cbt compatibility layer recompiled, but cbt stage1 did not." + ); + + if( !classLoaderCache.containsKey( stage1Classpath, stage1LastModified ) ){ classLoaderCache.put( + stage1Classpath, classLoader( stage1Target, new MultiClassLoader2( @@ -180,12 +200,14 @@ public class NailgunLauncher{ earlyDeps.classLoader ) ), - stage1Classpath + stage1LastModified ); } + final ClassLoader stage1classLoader = classLoaderCache.get( stage1Classpath, stage1LastModified ); return new BuildStage1Result( - changed, + start, + stage1LastModified, stage1classLoader, stage1Classpath, nailgunClasspath, diff --git a/nailgun_launcher/Stage0Lib.java b/nailgun_launcher/Stage0Lib.java index 865b1cb..425ced3 100644 --- a/nailgun_launcher/Stage0Lib.java +++ b/nailgun_launcher/Stage0Lib.java @@ -11,6 +11,7 @@ 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; public class Stage0Lib{ public static void _assert(Boolean condition, Object msg){ @@ -47,25 +48,57 @@ public class Stage0Lib{ return join( 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 Boolean compile( - Boolean changed, Long start, String classpath, String target, + 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 lastSuccessfullCompile = statusFile.lastModified(); + long lastCompiled = statusFile.lastModified(); + + long maxLastModified = lastModified; + final long start = System.currentTimeMillis(); // <- before recursing, so we catch them all + for( File file: sourceFiles ){ - if( file.lastModified() > lastSuccessfullCompile ){ - changed = true; - break; - } + 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(changed){ + + if( maxLastModified > lastCompiled ){ List<String> zincArgs = new ArrayList<String>( Arrays.asList( new String[]{ @@ -100,8 +133,10 @@ public class Stage0Lib{ } 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; } - return changed; } public static ClassLoader classLoader( String file ) throws Throwable{ |