Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Write runtime dependencies in ivy.xml #4077

Merged
merged 8 commits into from
Dec 6, 2024
Merged
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
2 changes: 1 addition & 1 deletion build.mill
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ object Deps {

val coursierVersion = "2.1.19"
val coursier = ivy"io.get-coursier::coursier:$coursierVersion"
val coursierInterface = ivy"io.get-coursier:interface:1.0.25"
val coursierInterface = ivy"io.get-coursier:interface:1.0.26"
val coursierJvm = ivy"io.get-coursier::coursier-jvm:$coursierVersion"

val cask = ivy"com.lihaoyi::cask:0.9.4"
Expand Down
18 changes: 14 additions & 4 deletions scalalib/src/mill/scalalib/PublishModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -70,23 +70,33 @@ trait PublishModule extends JavaModule { outer =>

def publishXmlDeps: Task[Agg[Dependency]] = Task.Anon {
val ivyPomDeps =
processedIvyDeps().map(_.toDep).map(resolvePublishDependency.apply().apply(_))
processedIvyDeps().map(_.toDep)
.map(resolvePublishDependency.apply().apply(_))

val runIvyPomDeps = runIvyDeps()
.map(resolvePublishDependency.apply().apply(_))
.filter(!ivyPomDeps.contains(_))

val compileIvyPomDeps = compileIvyDeps()
.map(resolvePublishDependency.apply().apply(_))
.filter(!ivyPomDeps.contains(_))
.map(_.copy(scope = Scope.Provided))

val modulePomDeps = T.sequence(moduleDepsChecked.collect {
case m: PublishModule => m.publishSelfDependency
})()
val compileModulePomDeps = T.sequence(compileModuleDepsChecked.collect {
case m: PublishModule => m.publishSelfDependency
})()
val runModulePomDeps = T.sequence(runModuleDepsChecked.collect {
case m: PublishModule => m.publishSelfDependency
})()

ivyPomDeps ++ compileIvyPomDeps ++
ivyPomDeps ++
compileIvyPomDeps.map(_.copy(scope = Scope.Provided)) ++
runIvyPomDeps.map(_.copy(scope = Scope.Runtime)) ++
modulePomDeps.map(Dependency(_, Scope.Compile)) ++
compileModulePomDeps.map(Dependency(_, Scope.Provided))
compileModulePomDeps.map(Dependency(_, Scope.Provided)) ++
runModulePomDeps.map(Dependency(_, Scope.Runtime))
}

/**
Expand Down
15 changes: 8 additions & 7 deletions scalalib/src/mill/scalalib/publish/Ivy.scala
Original file line number Diff line number Diff line change
Expand Up @@ -78,11 +78,11 @@ object Ivy {
private def renderDependency(dep: Dependency): Elem = {
if (dep.exclusions.isEmpty)
<dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={
s"${depIvyConf(dep)}->${dep.configuration.getOrElse("default(compile)")}"
depIvyConf(dep)
} />
else
<dependency org={dep.artifact.group} name={dep.artifact.id} rev={dep.artifact.version} conf={
s"${depIvyConf(dep)}->${dep.configuration.getOrElse("default(compile)")}"
depIvyConf(dep)
}>
{dep.exclusions.map(ex => <exclude org={ex._1} name={ex._2} matcher="exact"/>)}
</dependency>
Expand All @@ -92,12 +92,13 @@ object Ivy {
<override org={override0.organization} module={override0.name} rev={override0.version} />

private def depIvyConf(d: Dependency): String = {
if (d.optional) "optional"
def target(value: String) = d.configuration.getOrElse(value)
if (d.optional) s"optional->${target("runtime")}"
else d.scope match {
case Scope.Compile => "compile"
case Scope.Provided => "provided"
case Scope.Test => "test"
case Scope.Runtime => "runtime"
case Scope.Compile => s"compile->${target("compile")};runtime->${target("runtime")}"
case Scope.Provided => s"provided->${target("compile")}"
case Scope.Test => s"test->${target("runtime")}"
case Scope.Runtime => s"runtime->${target("runtime")}"
}
}

Expand Down
131 changes: 130 additions & 1 deletion scalalib/test/src/mill/scalalib/PublishModuleTests.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ import utest._
import utest.framework.TestPath

import java.io.PrintStream

import scala.jdk.CollectionConverters._
import scala.xml.NodeSeq

object PublishModuleTests extends TestSuite {
Expand Down Expand Up @@ -77,6 +79,36 @@ object PublishModuleTests extends TestSuite {
}
}

trait TestPublishModule extends PublishModule {
def publishVersion = "0.1.0-SNAPSHOT"
def pomSettings = PomSettings(
organization = "com.lihaoyi.pubmodtests",
description = "test thing",
url = "https://github.com/com-lihaoyi/mill",
licenses = Seq(License.Common.Apache2),
versionControl = VersionControl.github("com-lihaoyi", "mill"),
developers = Nil
)
}
object compileAndRuntimeStuff extends TestBaseModule {
object main extends JavaModule with TestPublishModule {
def ivyDeps = Agg(
ivy"org.slf4j:slf4j-api:2.0.15"
)
def runIvyDeps = Agg(
ivy"ch.qos.logback:logback-classic:1.5.12"
)
}

object transitive extends JavaModule with TestPublishModule {
def moduleDeps = Seq(main)
}

object runtimeTransitive extends JavaModule with TestPublishModule {
def runModuleDeps = Seq(main)
}
}

val resourcePath = os.Path(sys.env("MILL_TEST_RESOURCE_DIR")) / "publish"

def tests: Tests = Tests {
Expand Down Expand Up @@ -185,7 +217,7 @@ object PublishModuleTests extends TestSuite {
val ivyXml = scala.xml.XML.loadFile(result.value.path.toString)
val deps: NodeSeq = (ivyXml \ "dependencies" \ "dependency")
assert(deps.exists(n =>
(n \ "@conf").text == "compile->default(compile)" &&
(n \ "@conf").text == "compile->compile;runtime->runtime" &&
(n \ "@name").text == "scala-library" && (n \ "@org").text == "org.scala-lang"
))
}
Expand All @@ -209,6 +241,103 @@ object PublishModuleTests extends TestSuite {
// )
}
}

test("scopes") - UnitTester(compileAndRuntimeStuff, null).scoped { eval =>
def assertClassPathContains(cp: Seq[os.Path], fileName: String) =
assert(cp.map(_.last).contains(fileName))
def assertClassPathDoesntContain(cp: Seq[os.Path], prefix: String) =
assert(cp.map(_.last).forall(!_.startsWith(prefix)))

def nothingClassPathCheck(cp: Seq[os.Path]): Unit = {
assertClassPathDoesntContain(cp, "slf4j")
assertClassPathDoesntContain(cp, "logback")
}
def compileClassPathCheck(cp: Seq[os.Path]): Unit = {
assertClassPathContains(cp, "slf4j-api-2.0.15.jar")
assertClassPathDoesntContain(cp, "logback")
}
def runtimeClassPathCheck(cp: Seq[os.Path]): Unit = {
assertClassPathContains(cp, "slf4j-api-2.0.15.jar")
assertClassPathContains(cp, "logback-classic-1.5.12.jar")
}

val compileCp =
eval(compileAndRuntimeStuff.main.compileClasspath).toTry.get.value.toSeq.map(_.path)
val runtimeCp =
eval(compileAndRuntimeStuff.main.runClasspath).toTry.get.value.toSeq.map(_.path)

compileClassPathCheck(compileCp)
runtimeClassPathCheck(runtimeCp)

val ivy2Repo = eval.evaluator.workspace / "ivy2Local"
val m2Repo = eval.evaluator.workspace / "m2Local"

eval(compileAndRuntimeStuff.main.publishLocal(ivy2Repo.toString)).toTry.get
eval(compileAndRuntimeStuff.transitive.publishLocal(ivy2Repo.toString)).toTry.get
eval(compileAndRuntimeStuff.runtimeTransitive.publishLocal(ivy2Repo.toString)).toTry.get
eval(compileAndRuntimeStuff.main.publishM2Local(m2Repo.toString)).toTry.get
eval(compileAndRuntimeStuff.transitive.publishM2Local(m2Repo.toString)).toTry.get
eval(compileAndRuntimeStuff.runtimeTransitive.publishM2Local(m2Repo.toString)).toTry.get

def localRepoCp(localRepo: coursierapi.Repository, moduleName: String, config: String) = {
val dep = coursierapi.Dependency.of("com.lihaoyi.pubmodtests", moduleName, "0.1.0-SNAPSHOT")
coursierapi.Fetch.create()
.addDependencies(dep)
.addRepositories(localRepo)
.withResolutionParams(
coursierapi.ResolutionParams.create()
.withDefaultConfiguration(if (config.isEmpty) null else config)
)
.fetch()
.asScala
.map(os.Path(_))
.toSeq
}
def ivy2Cp(moduleName: String, config: String) =
localRepoCp(
coursierapi.IvyRepository.of(ivy2Repo.toNIO.toUri.toASCIIString + "[defaultPattern]"),
moduleName,
config
)
def m2Cp(moduleName: String, config: String) =
localRepoCp(
coursierapi.MavenRepository.of(m2Repo.toNIO.toUri.toASCIIString),
moduleName,
config
)

val ivy2CompileCp = ivy2Cp("main", "compile")
val ivy2RunCp = ivy2Cp("main", "runtime")
val m2CompileCp = m2Cp("main", "compile")
val m2RunCp = m2Cp("main", "runtime")

compileClassPathCheck(ivy2CompileCp)
compileClassPathCheck(m2CompileCp)
runtimeClassPathCheck(ivy2RunCp)
runtimeClassPathCheck(m2RunCp)

val ivy2TransitiveCompileCp = ivy2Cp("transitive", "compile")
val ivy2TransitiveRunCp = ivy2Cp("transitive", "runtime")
val m2TransitiveCompileCp = m2Cp("transitive", "compile")
val m2TransitiveRunCp = m2Cp("transitive", "runtime")

compileClassPathCheck(ivy2TransitiveCompileCp)
compileClassPathCheck(m2TransitiveCompileCp)
runtimeClassPathCheck(ivy2TransitiveRunCp)
runtimeClassPathCheck(m2TransitiveRunCp)

val ivy2RuntimeTransitiveCompileCp = ivy2Cp("runtimeTransitive", "compile")
val ivy2RuntimeTransitiveRunCp = ivy2Cp("runtimeTransitive", "runtime")
val m2RuntimeTransitiveCompileCp = m2Cp("runtimeTransitive", "compile")
val m2RuntimeTransitiveRunCp = m2Cp("runtimeTransitive", "runtime")

// runtime dependency on the main module - doesn't pull anything from it
// at compile time, hence the nothingClassPathCheck-s
nothingClassPathCheck(ivy2RuntimeTransitiveCompileCp)
nothingClassPathCheck(m2RuntimeTransitiveCompileCp)
runtimeClassPathCheck(ivy2RuntimeTransitiveRunCp)
runtimeClassPathCheck(m2RuntimeTransitiveRunCp)
}
}

}
Loading