|
| 1 | +// Althought running JVM bytecode via a one-off isolated classloader has less overhead |
| 2 | +// than running it in a subprocess, the fact that the classloader needs to be created |
| 3 | +// each time adds overhead: newly-created classloaders contain code that is not yet |
| 4 | +// optimized by the JVM. When performance matters, you can put the classloader in a |
| 5 | +// `Task.Worker` to keep it around, allowing the code internally to be optimized and |
| 6 | +// stay optimized without being thrown away each time |
| 7 | + |
| 8 | +// This example is similar to the earlier example running the Groovy interpreter in |
| 9 | +// a subprocess, but instead of using `Jvm.runSubprocess` we use `Jvm.inprocess` to |
| 10 | +// load the Groovy interpreter classpath files into an in-memory in-process classloader: |
| 11 | + |
| 12 | +package build |
| 13 | +import mill._, javalib._ |
| 14 | +import mill.util.Jvm |
| 15 | + |
| 16 | +object coursierModule extends CoursierModule |
| 17 | + |
| 18 | +def groovyClasspath: Task[Agg[PathRef]] = Task{ |
| 19 | + coursierModule.defaultResolver().resolveDeps(Agg(ivy"org.codehaus.groovy:groovy:3.0.9")) |
| 20 | +} |
| 21 | + |
| 22 | +def groovyWorker: Worker[java.net.URLClassLoader] = Task.Worker{ |
| 23 | + mill.api.ClassLoader.create(groovyClasspath().map(_.path.toIO.toURL).toSeq, parent = null) |
| 24 | +} |
| 25 | + |
| 26 | +trait GroovyGenerateJavaModule extends JavaModule { |
| 27 | + def groovyScript = Task.Source(millSourcePath / "generate.groovy") |
| 28 | + |
| 29 | + def groovyGeneratedResources = Task{ |
| 30 | + val oldCl = Thread.currentThread().getContextClassLoader |
| 31 | + Thread.currentThread().setContextClassLoader(groovyWorker()) |
| 32 | + try { |
| 33 | + groovyWorker() |
| 34 | + .loadClass("groovy.ui.GroovyMain") |
| 35 | + .getMethod("main", classOf[Array[String]]) |
| 36 | + .invoke( |
| 37 | + null, |
| 38 | + Array[String]( |
| 39 | + groovyScript().path.toString, |
| 40 | + groovyGenerateArg(), |
| 41 | + (Task.dest / "groovy-generated.html").toString |
| 42 | + ) |
| 43 | + ) |
| 44 | + } finally Thread.currentThread().setContextClassLoader(oldCl) |
| 45 | + PathRef(Task.dest) |
| 46 | + } |
| 47 | + |
| 48 | + def groovyGenerateArg: T[String] |
| 49 | + def resources = super.resources() ++ Seq(groovyGeneratedResources()) |
| 50 | +} |
| 51 | + |
| 52 | +object foo extends GroovyGenerateJavaModule{ |
| 53 | + def groovyGenerateArg = "Foo Groovy!" |
| 54 | +} |
| 55 | +object bar extends GroovyGenerateJavaModule{ |
| 56 | + def groovyGenerateArg = "Bar Groovy!" |
| 57 | +} |
| 58 | + |
| 59 | +// Here we have two modules `foo` and `bar`, each of which makes use of `groovyWorker` |
| 60 | +// to evaluate a groovy script to generate some resources. In this case, we invoke the `main` |
| 61 | +// method of `groovy.ui.GroovyMain`, which also happens to require us to set the |
| 62 | +// `ContextClassLoader` to work. |
| 63 | + |
| 64 | + |
| 65 | +/** Usage |
| 66 | + |
| 67 | +> ./mill foo.run |
| 68 | +Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Foo Groovy!</p></body></html> |
| 69 | + |
| 70 | +> ./mill bar.run |
| 71 | +Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Bar Groovy!</p></body></html> |
| 72 | +*/ |
| 73 | + |
| 74 | + |
| 75 | +// Because the `URLClassLoader` within `groovyWorker` is long-lived, the code within the |
| 76 | +// classloader can be optimized by the JVM runtime, and would have less overhead than if |
| 77 | +// run in separate classloaders via `Jvm.runClassloader`. And because `URLClassLoader` |
| 78 | +// already extends `AutoCloseable`, `groovyWorker` gets treated as an |
| 79 | +// xref:fundamentals/tasks.adoc#_autoclosable_workers[Autocloseable Worker] automatically. |
| 80 | + |
| 81 | +// NOTE: As mentioned in documentation for xref:fundamentals/tasks.adoc#_workers[Worker Tasks], |
| 82 | +// the classloader contained within `groovyWorker` above is *initialized* in a single-thread, |
| 83 | +// but it may be *used* concurrently in a multi-threaded environment. Practically, that means |
| 84 | +// that the classes and methods you are invoking within the classloader do not make use of |
| 85 | +// un-synchronized global mutable variables. |
0 commit comments