-
-
Notifications
You must be signed in to change notification settings - Fork 349
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
14 changed files
with
218 additions
and
16 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
= Running Dynamic JVM Code | ||
|
||
While xref:extending/import-ivy-plugins.adoc[import $ivy] is convenient, | ||
it comes with limitations as the JVM library it imports is global to your build: | ||
|
||
1. The library has to be resolved and downloaded before any part of your build starts. | ||
If your codebase is large and most parts of your build don't use that library, | ||
needing to download the library when working on parts that don't need it can be wasteful | ||
2. The library can only have one version across the entire build. This can be an issue if | ||
you need to have multiple versions of the library used in different parts of your build. | ||
e.g. different parts of a large Groovy codebase may use different versions of the Groovy | ||
interpreter, and so the Groovy interpreter cannot be included via `import $ivy` because the | ||
different versions would collide. | ||
3. The library cannot be built as part of your main build. While it is possible to build | ||
it as part of your xref:extending/meta-build.adoc[Meta-Build], that comes with additional | ||
complexity and limitations. In a large codebase, you often end up building modules that | ||
are shared between production deployments as well as local tooling: in such cases | ||
`import $ivy` is not a good fit | ||
In scenarios where these limitations cause issues, Mill provides other ways to run arbitrary | ||
JVM code apart from `import $ivy`. | ||
|
||
|
||
== Subprocesses | ||
|
||
include::partial$example/extending/jvmcode/1-subprocess.adoc[] | ||
|
||
== In-process Isolated Classloaders | ||
|
||
include::partial$example/extending/jvmcode/2-inprocess.adoc[] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,65 @@ | ||
// This example demonstrates how to resolve a third-party library from Maven Central, | ||
// but instead of using xref:extending/import-ivy-plugins.adoc[import $ivy] (which loads the | ||
// library as part of the main build) we use: | ||
// | ||
// * `defaultResolver().resolveDeps` to resolve the dependencies from Maven Central, | ||
// converting `org:name:version` coordinates (and their transitive dependencies) to | ||
// `PathRef`s referring to files on disk | ||
// | ||
// * `Jvm.runSubprocess`, which runs the given classpath files in a subprocess, starting | ||
// from specified `mainClass` | ||
// | ||
// While xref:fundamentals/bundled-libraries.adoc#_os_lib[OS-Lib]'s `os.call` and `os.spawn` APIs | ||
// can be used to create any processes, JVM subprocesses are common enough have enough | ||
// idiosyncracies (e.g. classpaths) that Mill provides helper methods specifically for them. | ||
|
||
package build | ||
import mill._, javalib._ | ||
import mill.util.Jvm | ||
|
||
object foo extends JavaModule { | ||
def groovyClasspath: Task[Agg[PathRef]] = Task{ | ||
defaultResolver().resolveDeps(Agg(ivy"org.codehaus.groovy:groovy:3.0.9")) | ||
} | ||
|
||
def groovyScript = Task.Source(millSourcePath / "generate.groovy") | ||
|
||
def groovyGeneratedResources = Task{ | ||
Jvm.runSubprocess( | ||
mainClass = "groovy.ui.GroovyMain", | ||
classPath = groovyClasspath().map(_.path), | ||
mainArgs = Seq( | ||
groovyScript().path.toString, | ||
"Groovy!", | ||
(Task.dest / "groovy-generated.html").toString | ||
), | ||
workingDir = Task.dest | ||
) | ||
PathRef(Task.dest) | ||
} | ||
|
||
def resources = super.resources() ++ Seq(groovyGeneratedResources()) | ||
} | ||
|
||
// For this example, we use the https://groovy-lang.org/[Groovy] interpreter as our example | ||
// third-party library. While often used as a `groovy` CLI command, Groovy is also available | ||
// on Maven Central at the `org.codehaus.groovy:groovy:3.0.9` coordinates. This lets us pull | ||
// it into our build as a classpath comprising ``PathRef``s to files on disk, and then run the | ||
// Groovy JVM main method (in the class | ||
// https://github.com/apache/groovy/blob/48c8720c04b2c15396a7b37f140e0954418f74d3/src/main/java/groovy/ui/GroovyMain.java#L113[groovy.ui.GroovyMain]) | ||
// passing it our script file `generate.groovy` (wired into our build using an | ||
// xref:fundamentals/tasks.adoc#_sources[Source Task] `groovyScript`) and arguments | ||
// used to configure the generated file and tell the script where to write it to. `generate.groovy` | ||
// generates a file on disk that we then wire into `def resources`, which is read at runtime | ||
// by `foo.run` and printed to the terminal output as shown below: | ||
|
||
/** Usage | ||
|
||
> ./mill foo.run | ||
Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Groovy!</p></body></html> | ||
*/ | ||
|
||
// As mentioned above, `defaultResolver().resolveDeps` and `Jvm.runSubprocess` are an | ||
// alternative to `import $ivy`, providing you more flexibility to resolve dependencies | ||
// on-demand as part of your task graph only when necessary, and keeping it isolated from | ||
// the build in a subprocess preventing classpath collisions. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def htmlContent = "<html><body><h1>Hello!</h1><p>" + args[0] + "</p></body></html>" | ||
|
||
def outputFile = new File(args[1]) | ||
outputFile.write(htmlContent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package foo; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
public class Foo { | ||
|
||
// Read `file.txt` from classpath | ||
public static String groovyGeneratedHtml() throws IOException { | ||
// Get the resource as an InputStream | ||
try (InputStream inputStream = Foo.class.getClassLoader().getResourceAsStream("groovy-generated.html")) { | ||
return new String(inputStream.readAllBytes()); | ||
} | ||
} | ||
|
||
public static void main(String[] args) throws IOException{ | ||
String appClasspathResourceText = Foo.groovyGeneratedHtml(); | ||
System.out.println("Contents of groovy-generated.html is " + appClasspathResourceText); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// This example is similar to the earlier example running the Groovy interpreter in | ||
// a subprocess, but instead of using `Jvm.runSubprocess` we use `Jvm.inprocess` to | ||
// load the Groovy interpreter classpath files into an in-memory in-process classloader. | ||
|
||
package build | ||
import mill._, javalib._ | ||
import mill.util.Jvm | ||
|
||
object foo extends JavaModule { | ||
def groovyClasspath: Task[Agg[PathRef]] = Task{ | ||
defaultResolver().resolveDeps(Agg(ivy"org.codehaus.groovy:groovy:3.0.9")) | ||
} | ||
|
||
def groovyScript = Task.Source(millSourcePath / "generate.groovy") | ||
|
||
def groovyGeneratedResources = Task{ | ||
Jvm.runInprocess(classPath = groovyClasspath().map(_.path)){ classLoader => | ||
classLoader | ||
.loadClass("groovy.ui.GroovyMain") | ||
.getMethod("main", classOf[Array[String]]) | ||
.invoke( | ||
null, | ||
Array[String]( | ||
groovyScript().path.toString, | ||
"Groovy!", | ||
(Task.dest / "groovy-generated.html").toString | ||
) | ||
) | ||
} | ||
|
||
PathRef(Task.dest) | ||
} | ||
|
||
def resources = super.resources() ++ Seq(groovyGeneratedResources()) | ||
} | ||
|
||
// Note that unlike `Jvm.runSubprocess`, `Jvm.runInprocess` does not take a `workingDir` | ||
// on `mainArgs`: it instead provides you an in-memory `classLoader` that contains the | ||
// classpath you gave it. From there, you can use `.loadClass` and `.getMethod` to fish out | ||
// the classes and methods you want, and `.invoke` to call them. | ||
|
||
/** Usage | ||
|
||
> ./mill foo.run | ||
Contents of groovy-generated.html is <html><body><h1>Hello!</h1><p>Groovy!</p></body></html> | ||
*/ | ||
|
||
// `Jvm.runInprocess` has significantly less overhead than `Jvm.runSubprocess`: both in terms | ||
// of wall-clock time and in terms of memory footprint. However, it does have somewhat less | ||
// isolation, as the code is running inside your JVM and cannot be configured to have a separate | ||
// working directory, environment variables, and other process-global configs. Which one is | ||
// better to use differs on a case-by-case basis. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
def htmlContent = "<html><body><h1>Hello!</h1><p>" + args[0] + "</p></body></html>" | ||
|
||
def outputFile = new File(args[1]) | ||
outputFile.write(htmlContent) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package foo; | ||
|
||
import java.io.IOException; | ||
import java.io.InputStream; | ||
|
||
public class Foo { | ||
|
||
// Read `file.txt` from classpath | ||
public static String groovyGeneratedHtml() throws IOException { | ||
// Get the resource as an InputStream | ||
try (InputStream inputStream = Foo.class.getClassLoader().getResourceAsStream("groovy-generated.html")) { | ||
return new String(inputStream.readAllBytes()); | ||
} | ||
} | ||
|
||
public static void main(String[] args) throws IOException{ | ||
String appClasspathResourceText = Foo.groovyGeneratedHtml(); | ||
System.out.println("Contents of groovy-generated.html is " + appClasspathResourceText); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters