Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions core/api/daemon/src/mill/api/daemon/ExecResult.scala
Original file line number Diff line number Diff line change
Expand Up @@ -67,9 +67,10 @@ object ExecResult {
def flatMap[V](f: T => ExecResult[V]): Failing[V]

override def asFailing: Option[ExecResult.Failing[T]] = Some(this)
def throwException: Nothing = this match {
case f: ExecResult.Failure[?] => throw new Result.Exception(f.msg)
case f: ExecResult.Exception => throw f.throwable
def throwException: Nothing = throw exception
private[mill] def exception: Throwable = this match {
case f: ExecResult.Failure[?] => new Result.Exception(f.msg)
case f: ExecResult.Exception => f.throwable
}
}

Expand Down
60 changes: 60 additions & 0 deletions libs/javalib/src/mill/javalib/JavaModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,25 @@ trait JavaModule
*/
def unmanagedClasspath: T[Seq[PathRef]] = Task { Seq.empty[PathRef] }

/**
* Same class path as [[unmanagedClasspath]], but ensures every entry is a JAR file.
*
* If you'd like to add unmanaged class path entries, add them to [[unmanagedClasspath]].
* `unmanagedClasspathAsJars` will pick them up automatically, and convert directory entries
* to JAR files if needed.
*/
private[mill] def unmanagedClasspathAsJars: T[Seq[PathRef]] =
Task {
unmanagedClasspath().zipWithIndex.flatMap {
case (ref, refIdx) if os.isDir(ref.path) =>
val jar = Task.dest / s"$refIdx.jar"
Jvm.createJar(jar, Seq(ref.path))
Seq(PathRef(jar))
case (ref, _) if os.exists(ref.path) => Seq(ref)
case (_, _) => Nil
}
}

/**
* The `coursier.Dependency` to use to refer to this module
*/
Expand Down Expand Up @@ -835,6 +854,24 @@ trait JavaModule
*/
def compileResources: T[Seq[PathRef]] = Task.Sources { "compile-resources" }

/**
* Same class path as [[compileResources]], but ensures every entry is a JAR file.
*
* If you'd like to add compile resources entries, add them to [[compileResources]].
* `compileResourcesAsJars` will pick them up automatically, and convert directory entries
* to JAR files if needed.
*/
private[mill] def compileResourcesAsJars: T[Seq[PathRef]] = Task {
compileResources().zipWithIndex.flatMap {
case (ref, refIdx) if os.isDir(ref.path) =>
val jar = Task.dest / s"$refIdx.jar"
Jvm.createJar(jar, Seq(ref.path))
Seq(PathRef(jar))
case (ref, _) if os.exists(ref.path) => Seq(ref)
case (_, _) => Nil // filtering out non-existing entries, to only keep actual JAR files
}
}

/**
* Folders containing source files that are generated rather than
* handwritten; these files can be generated in this task itself,
Expand Down Expand Up @@ -1018,6 +1055,14 @@ trait JavaModule
*/
def compileClasspath: T[Seq[PathRef]] = Task { compileClasspathTask(CompileFor.Regular)() }

/**
* All classfiles and resources from upstream modules and dependencies
* necessary to compile this module, all packaged as JAR files.
*/
def compileClasspathAsJars: T[Seq[PathRef]] = Task {
resolvedMvnDeps() ++ transitiveJars() ++ localCompileClasspathAsJars()
}

/**
* All classfiles and resources from upstream modules and dependencies
* necessary to compile this module.
Expand Down Expand Up @@ -1053,6 +1098,14 @@ trait JavaModule
compileResources() ++ unmanagedClasspath()
}

/**
* The *input* classfiles/resources from this module, used during compilation,
* excluding upstream modules and third-party dependencies, packaged as JAR files.
*/
def localCompileClasspathAsJars: T[Seq[PathRef]] = Task {
compileResourcesAsJars() ++ unmanagedClasspathAsJars()
}

/**
* Resolved dependencies
*/
Expand Down Expand Up @@ -1127,6 +1180,13 @@ trait JavaModule
localClasspath()
}

override def runClasspathAsJars: T[Seq[PathRef]] = Task {
super[RunModule].runClasspathAsJars() ++ // Remove '[RunModule]' when we can break bin-compat
resolvedRunMvnDeps().toSeq ++
transitiveJars() ++
Seq(jar())
}

/**
* A jar containing only this module's resources and compiled classfiles,
* without those from upstream modules and dependencies
Expand Down
11 changes: 11 additions & 0 deletions libs/javalib/src/mill/javalib/RunModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,20 @@ trait RunModule extends WithJvmWorkerModule with RunModuleApi {
/**
* All classfiles and resources including upstream modules and dependencies
* necessary to run this module's code.
*
* The returned class path can include directories. See `runClasspathAsJars`
* if you'd like the class path to contain only JAR files.
*/
def runClasspath: T[Seq[PathRef]] = Task { Seq.empty[PathRef] }

/**
* All classfiles and resources including upstream modules and dependencies
* necessary to run this module's code.
*
* Unlike `runClasspath`, all class path entries here are JAR files.
*/
def runClasspathAsJars: T[Seq[PathRef]] = Task { Seq.empty[PathRef] }

/**
* The elements of the run classpath which are local to this module.
* This is typically the output of a compilation step and bundles runtime resources.
Expand Down
Empty file.
12 changes: 12 additions & 0 deletions libs/javalib/test/resources/classpath/app/src/app/MyApp.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package app;

import lib.Thing;

public class MyApp {
public static void main(String[] args) throws java.net.URISyntaxException {
String appUri = MyApp.class.getProtectionDomain().getCodeSource().getLocation().toURI().toASCIIString();
String libUri = Thing.class.getProtectionDomain().getCodeSource().getLocation().toURI().toASCIIString();
System.out.println("App URI: " + appUri);
System.out.println("Lib URI: " + libUri);
}
}
Empty file.
2 changes: 2 additions & 0 deletions libs/javalib/test/resources/classpath/build.mill
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import mill.*
import mill.javalib.*
7 changes: 7 additions & 0 deletions libs/javalib/test/resources/classpath/lib/src/lib/Thing.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package lib;

public class Thing {
public static String message() {
return "Foo";
}
}
113 changes: 113 additions & 0 deletions libs/javalib/test/src/mill/javalib/ClasspathTests.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
package mill.javalib

import mill.api.{Discover, PathRef, Task}
import mill.testkit.{TestRootModule, UnitTester}
import mill.util.TokenReaders.*

import utest.*

import java.net.URI
import java.nio.file.Paths
import java.io.ByteArrayOutputStream
import java.io.PrintStream

object ClasspathTests extends TestSuite {

object TestCase extends TestRootModule {
object lib extends JavaModule

object app extends JavaModule {
def moduleDeps = Seq(lib)
def mainClass = Some("app.MyApp")
}

object appAsJars extends JavaModule {
def moduleDeps = Seq(app)
def mainClass = app.mainClass
def compileClasspath = compileClasspathAsJars
def runClasspath = runClasspathAsJars
}

lazy val millDiscover = Discover[this.type]
}

val tests: Tests = Tests {
test("test") {
val sources = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "classpath"

UnitTester(TestCase, sourceRoot = sources).scoped { eval =>
def classPathTaskValue(task: Task[Seq[PathRef]]) =
eval(task)
.left.map(f => throw f.exception)
.merge
.value
.map(_.path.subRelativeTo(TestCase.moduleDir))

val libCompileCp = classPathTaskValue(TestCase.lib.compileClasspath)
val expectedLibCompileCp = Seq(
os.sub / "lib/compile-resources"
)
assert(expectedLibCompileCp == libCompileCp)

val appCompileCp = classPathTaskValue(TestCase.app.compileClasspath)
val expectedAppCompileCp = Seq(
os.sub / "lib/compile-resources",
os.sub / "out/lib/compile.dest/classes",
os.sub / "app/compile-resources"
)
assert(expectedAppCompileCp == appCompileCp)

val appAsJarsCompileCp = classPathTaskValue(TestCase.appAsJars.compileClasspath)
val expectedAppAsJarsCompileCp = Seq(
os.sub / "out/lib/jar.dest/out.jar",
os.sub / "out/app/jar.dest/out.jar",
os.sub / "out/appAsJars/compileResourcesAsJars.dest/0.jar"
)
assert(expectedAppAsJarsCompileCp == appAsJarsCompileCp)
}

def locationFromOutput(kind: String, out: String): os.SubPath = {
val uriStr =
out.linesIterator.find(_.startsWith(s"$kind URI: ")).get.stripPrefix(s"$kind URI: ")
val path = os.Path(Paths.get(new URI(uriStr)))
path.subRelativeTo(TestCase.moduleDir)
}

val mainBaos = new ByteArrayOutputStream
val jarBaos = new ByteArrayOutputStream

UnitTester(
TestCase,
sourceRoot = sources,
outStream = new PrintStream(mainBaos, true)
).scoped { eval =>
eval(TestCase.app.run()).left.map(f => throw f.exception)
}
UnitTester(
TestCase,
sourceRoot = sources,
outStream = new PrintStream(jarBaos, true)
).scoped { eval =>
eval(TestCase.appAsJars.run()).left.map(f => throw f.exception)
}

val mainOut = new String(mainBaos.toByteArray)
val jarOut = new String(jarBaos.toByteArray)

val appDir = locationFromOutput("App", mainOut)
val appJar = locationFromOutput("App", jarOut)
val libDir = locationFromOutput("Lib", mainOut)
val libJar = locationFromOutput("Lib", jarOut)

val expectedAppDir = os.sub / "out/app/compile.dest/classes"
val expectedAppJar = os.sub / "out/app/jar.dest/out.jar"
val expectedLibDir = os.sub / "out/lib/compile.dest/classes"
val expectedLibJar = os.sub / "out/lib/jar.dest/out.jar"

assert(expectedAppDir == appDir)
assert(expectedAppJar == appJar)
assert(expectedLibDir == libDir)
assert(expectedLibJar == libJar)
}
}
}
Loading