Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
48 commits
Select commit Hold shift + click to select a range
b6468bf
Rename test class mill.api.Opt to TestOpt
lefou Nov 5, 2025
6d5c781
Introduce new mill.api.opt with Opt, OptGroup, Opts and OptSyntax
lefou Nov 5, 2025
7fbd722
WIP Replace `Seq[String]` used for options by `mill.api.opt.Opts`
lefou Nov 5, 2025
84c9c7e
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 5, 2025
57af960
[autofix.ci] apply automated fixes (attempt 2/3)
autofix-ci[bot] Nov 5, 2025
3bdb2e9
Don't use Opts for errorProneOptions
lefou Nov 5, 2025
3e40f64
Fix ErrorProneModule
lefou Nov 5, 2025
033832a
Experiemnt with OptGroup.apply
lefou Nov 5, 2025
ef8a23b
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 5, 2025
b8c7b76
make test path non-user specific
lefou Nov 6, 2025
9810fb8
More Opts construction tweaks
lefou Nov 6, 2025
8af03cd
WIP
lefou Nov 6, 2025
ce9c8a5
[autofix.ci] apply automated fixes
autofix-ci[bot] Nov 6, 2025
c0e15f6
Fix test
lefou Nov 6, 2025
a72cdc0
fix test
lefou Nov 6, 2025
1159e4a
Use a more compact JSON format
lefou Nov 6, 2025
dd67e4d
adapt examples
lefou Nov 6, 2025
c9e11a4
Adapt BuildWriter
lefou Nov 6, 2025
bc05953
Update expected
lefou Nov 6, 2025
e37054a
Avoid mutating parameters
lefou Nov 6, 2025
34a4320
Add imports
lefou Nov 6, 2025
39838ca
Add imports
lefou Nov 6, 2025
e05881f
Add imports
lefou Nov 6, 2025
917a78b
Merge branch 'main' into tr-poc-args-type
lefou Nov 7, 2025
37846fd
Fix test
lefou Nov 7, 2025
c8cc3be
More Seq -> Opts fixes
lefou Nov 7, 2025
138cda3
Fix opts in YAML
lefou Nov 7, 2025
1274f24
Fix opts usage
lefou Nov 7, 2025
7a777a6
Fix import
lefou Nov 7, 2025
e639d90
Add support to read Opts from simplified JSON, like `[ "opt1", "opt2" ]`
lefou Nov 8, 2025
3480bd8
fix example
lefou Nov 8, 2025
410dee8
Revert "Fix opts in YAML"
lefou Nov 8, 2025
4c9f723
fix test
lefou Nov 8, 2025
1926d92
Use Opts for `ScalaNativeModule.native{Compile,Linking}Options`
lefou Nov 8, 2025
2c4d96e
fix tests
lefou Nov 8, 2025
5441806
fix tests
lefou Nov 8, 2025
2f75896
Merge branch 'main' into tr-poc-args-type
lefou Nov 9, 2025
008972d
Move setDaemon calls to use-site
lefou Nov 9, 2025
2e91883
Fix potential logic error (used variable)
lefou Nov 9, 2025
dc065d5
Read and show a ignore reason from the `ignoreErrorsOnCI` file
lefou Nov 9, 2025
ac90771
Ignore bin-incompat mill-scalafix example for now
lefou Nov 9, 2025
288aa4e
Update expected test outcomes
lefou Nov 9, 2025
59b5e1c
Test fixes
lefou Nov 9, 2025
1d4c160
Added `mkPath` to test case
lefou Nov 9, 2025
63f1abf
fix tests
lefou Nov 9, 2025
bf8723b
.
lefou Nov 9, 2025
2ec8189
Futher minimize json serialization
lefou Nov 9, 2025
5085667
.
lefou Nov 9, 2025
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 contrib/jmh/src/mill/contrib/jmh/JmhModule.scala
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ trait JmhModule extends JavaModule {
def generateBenchmarkSources =
Task {
val dest = Task.ctx().dest
val forkedArgs = forkArgs().toSeq
val forkedArgs = forkArgs().toStringSeq
val sourcesDir = dest / "jmh_sources"
val resourcesDir = dest / "jmh_resources"

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package mill.contrib.scoverage
import coursier.Repository
import mill.*
import mill.api.{BuildCtx, PathRef, Result}
import mill.api.opt.*
import mill.contrib.scoverage.api.ScoverageReportWorkerApi2.ReportType
import mill.javalib.api.JvmWorkerUtil
import mill.scalalib.{Dep, DepSyntax, JavaModule, ScalaModule}
Expand Down Expand Up @@ -186,18 +187,18 @@ trait ScoverageModule extends ScalaModule { outer: ScalaModule =>
Task { outer.scalacPluginMvnDeps() ++ outer.scoveragePluginDeps() }

/** Add the scoverage specific plugin settings (`dataDir`). */
override def scalacOptions: T[Seq[String]] =
override def scalacOptions: T[Opts] =
Task {
val extras =
if (isScala3()) {
Seq(
s"-coverage-out:${data().path.toIO.getPath()}",
s"-sourceroot:${BuildCtx.workspaceRoot}"
Opts(
opt"-coverage-out:${data().path}",
opt"-sourceroot:${BuildCtx.workspaceRoot}"
)
} else {
Seq(
s"-P:scoverage:dataDir:${data().path.toIO.getPath()}",
s"-P:scoverage:sourceRoot:${BuildCtx.workspaceRoot}"
Opts(
opt"-P:scoverage:dataDir:${data().path}",
opt"-P:scoverage:sourceRoot:${BuildCtx.workspaceRoot}"
)
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ trait JavaModuleApi extends ModuleApi with GenIdeaModuleApi {

def transitiveModuleCompileModuleDeps: Seq[JavaModuleApi]

def javacOptions: TaskApi[Seq[String]]
def mandatoryJavacOptions: TaskApi[Seq[String]]
def javacOptions: TaskApi[OptsApi]
def mandatoryJavacOptions: TaskApi[OptsApi]

// BSP Tasks that sometimes need to be customized

Expand Down
12 changes: 12 additions & 0 deletions core/api/daemon/src/mill/api/daemon/internal/OptsApi.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package mill.api.daemon.internal

trait OptsApi {
def toStringSeq: Seq[String]
def value: Seq[OptGroupApi]
}

trait OptGroupApi {}

trait OptApi {
def toString(): String
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package mill.api.daemon.internal.bsp

import mill.api.daemon.internal.{EvaluatorApi, ModuleApi, TaskApi}
import mill.api.daemon.internal.{EvaluatorApi, ModuleApi, OptApi, OptsApi, TaskApi}

import java.nio.file.Path

Expand Down Expand Up @@ -34,21 +34,25 @@ trait BspJavaModuleApi extends ModuleApi {
)
: TaskApi[EvaluatorApi => (
classesPath: Path,
javacOptions: Seq[String],
javacOptions: OptsApi,
classpath: Seq[String]
)]

private[mill] def bspBuildTargetScalacOptions(
needsToMergeResourcesIntoCompileDest: Boolean,
enableJvmCompileClasspathProvider: Boolean,
clientWantsSemanticDb: Boolean
): TaskApi[(Seq[String], EvaluatorApi => Seq[String], EvaluatorApi => java.nio.file.Path)]
): TaskApi[(
scalacOptionsTask: OptsApi,
compileClasspathTask: EvaluatorApi => Seq[String],
classPathTask: EvaluatorApi => java.nio.file.Path
)]

private[mill] def bspBuildTargetScalaMainClasses
: TaskApi[(
classes: Seq[String],
forkArgs: Seq[String],
forkEnv: Map[String, String]
forkArgs: OptsApi,
forkEnv: Map[String, OptApi]
)]

private[mill] def bspLoggingTest: TaskApi[Unit]
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package mill.api.daemon.internal.bsp

import mill.api.daemon.internal.{ModuleApi, TaskApi}
import mill.api.daemon.internal.{ModuleApi, OptApi, OptsApi, TaskApi}

import java.nio.file.Path

Expand All @@ -10,18 +10,18 @@ trait BspRunModuleApi extends ModuleApi {

private[mill] def bspJvmRunEnvironment: TaskApi[(
runClasspath: Seq[Path],
forkArgs: Seq[String],
forkArgs: OptsApi,
forkWorkingDir: Path,
forkEnv: Map[String, String],
forkEnv: Map[String, OptApi],
mainClass: Option[String],
localMainClasses: Seq[String]
)]

private[mill] def bspJvmTestEnvironment: TaskApi[(
runClasspath: Seq[Path],
forkArgs: Seq[String],
forkArgs: OptsApi,
forkWorkingDir: Path,
forkEnv: Map[String, String],
forkEnv: Map[String, OptApi],
mainClass: Option[String],
testEnvVars: Option[(
mainClass: String,
Expand Down
117 changes: 117 additions & 0 deletions core/api/src/mill/api/opt/Opt.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
package mill.api.opt

import mill.api.daemon.internal.OptApi
import mill.api.JsonFormatters.given

import scala.annotation.targetName
import scala.language.implicitConversions

case class Opt private (value: Seq[Opt.OptTypes]) extends OptApi {
override def toString(): String = value.mkString("")

def map(conv: Opt.OptTypes => Opt.OptTypes): Opt = Opt(value.map(conv)*)

private def startString: String =
value.takeWhile(_.isInstanceOf[String]).collect { case s: String => s }.mkString("")

def startsWith(prefix: String): Boolean = startString.startsWith(prefix)

def mapStartString(rep: String => String): Opt = {
val rest = value.dropWhile(_.isInstanceOf[String])
Opt.apply((rep(startString) +: rest)*)
}

def containsPaths: Boolean = value.exists {
case _: os.Path => true
case _ => false
}
}

object Opt {

type OptTypes = (String | os.Path)

@targetName("applyVarArg")
def apply(value: OptTypes*): Opt = {
// TODO: merge sequential strings
new Opt(value.filter {
case s: String if s.isEmpty => false
case _ => true
})
}

/**
* Constructs a path from multiple path elements and a separator string.
* Can be used to render classpaths.
* Each path component will still be handled properly, e.g. mapped according to the current [[MappedPaths]] mapping.
*/
def mkPath(paths: Seq[os.Path], prefix: String = "", sep: String, suffix: String = ""): Opt = {
var needSep = false
Opt(
(
Seq(prefix) ++
paths.flatMap { path =>
if (needSep)
Seq(sep, path)
else {
needSep = true
Seq(path)
}
} ++ Seq(suffix)
)*
)
}

def mkPlatformPath(paths: Seq[os.Path]): Opt = mkPath(paths, sep = java.io.File.pathSeparator)

// given jsonReadWriter: upickle.ReadWriter[Opt] =
// upickle.readwriter[Seq[(Option[String], Option[os.Path])]].bimap(
// _.value.map {
// case path: os.Path => (None, Some(path))
// case str: String => (Some(str), None)
// },
// seq =>
// Opt(seq.map {
// case (Some(str), _) => str
// case (_, Some(path)) => path
// }*)
// )

given jsonReadWriter: upickle.ReadWriter[Opt] =
upickle.readwriter[ujson.Value].bimap(
opt =>
if (!opt.containsPaths) ujson.Str(opt.toString())
else opt.value.map {
case str: String => ujson.Str(str)
case path: os.Path => ujson.Obj("path" -> upickle.transform(path).to[ujson.Value])
},
{
case ujson.Str(opt) => Opt(opt)
case arr: ujson.Arr =>
val elems = arr.value.map {
case ujson.Str(opt) => opt
case ujson.Obj(map) => upickle.read[os.Path](map("path"))
}
Opt(elems.toSeq*)
}
)

// given stringToOpt: Conversion[String, Opt] = (value: String) => Opt(value)
// given osPathToOpt: Conversion[os.Path, Opt] = (value: os.Path) => Opt(value)

// implicit def IterableToOpt[T](s: Iterable[T])(using f: T => Opt): Opt =
// Opt(s.toSeq.flatMap(f(_).value))

implicit def AllToOpt(o: String | os.Path | Opt): Opt = o match {
case s: String => Opt(s)
case p: os.Path => Opt(p)
case o: Opt => o
}

// implicit def StringToOpt(s: String): Opt = Opt(s)
//
// implicit def OsPathToOpt(p: os.Path): Opt = Opt(p)
//
// implicit def OptToOpt(o: Opt): Opt = o

}
75 changes: 75 additions & 0 deletions core/api/src/mill/api/opt/OptGroup.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package mill.api.opt

import mill.api.daemon.internal.OptGroupApi

import scala.annotation.targetName
import scala.language.implicitConversions

/**
* A set of options, which are used together
*/
case class OptGroup private (value: Seq[Opt]) extends OptGroupApi {

override def toString(): String = value.mkString("(", ", ", ")")

def isEmpty: Boolean = value.isEmpty

def size: Int = value.size

def containsPaths: Boolean = value.exists(_.containsPaths)

def head: Opt = value.head
def headOption: Option[Opt] = value.headOption

def toStringSeq: Seq[String] = value.map(_.toString())

def concat(suffix: OptGroup): OptGroup = new OptGroup(value ++ suffix.value)

@`inline` final def ++(suffix: OptGroup): OptGroup = concat(suffix)

}

object OptGroup {
@targetName("applyVarAar")
def apply(opts: (String | os.Path | Opt | Seq[(String | os.Path | Opt)])*): OptGroup = {
val opts0 = opts.flatMap {
case s: String => Seq(Opt(s))
case p: os.Path => Seq(Opt(p))
case o: Opt => Seq(o)
case o: Seq[(String | os.Path | Opt)] =>
o.map {
case s: String => Opt(s)
case p: os.Path => Opt(p)
case o: Opt => o
}
}
new OptGroup(opts0)
}
// @targetName("applyIterable")
// def apply[T](opts: T*)(using f: T => Opt): OptGroup = new OptGroup(opts.map(f(_)))

def when(cond: Boolean)(value: Opt*): OptGroup = if (cond) OptGroup(value*) else OptGroup()

// given optsToOptGroup: Conversion[(OptTypes, OptTypes), OptGroup] =
// (tuple: (OptTypes, OptTypes)) =>
// OptGroup(Opt(tuple._1), Opt(tuple._2))

// implicit def StringToOptGroup(s: String): OptGroup = OptGroup(Seq(Opt(s)))
//
// implicit def OsPathToOptGroup(p: os.Path): OptGroup = OptGroup(Seq(Opt(p)))
//
// implicit def OptToOptGroup(o: Opt): OptGroup = OptGroup(Seq(o))
//
// implicit def IterableToOptGroup[T](s: Iterable[T])(using f: T => OptGroup): OptGroup =
// OptGroup(s.toSeq.flatMap(f(_).value))

// implicit def ArrayToOptGroup[T](s: Array[T])(using f: T => OptGroup): OptGroup =
// OptGroup(s.flatMap(f(_).value))

given jsonReadWriter: upickle.ReadWriter[OptGroup] =
upickle.readwriter[Seq[Opt]].bimap(
_.value,
OptGroup(_*)
)

}
16 changes: 16 additions & 0 deletions core/api/src/mill/api/opt/OptSyntax.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package mill.api.opt

import scala.language.implicitConversions

implicit class OptSyntax(ctx: StringContext) extends AnyVal {
def opt(opts: Any*): Opt = {
val vals = ctx.parts.take(opts.length).zip(opts).flatMap { case (p, a) => Seq(p, a) } ++
ctx.parts.drop(opts.length)

val elems: Seq[(String | os.Path)] = vals.flatMap {
case path: os.Path => Seq(path)
case s => Seq(s.toString).filter(_.nonEmpty)
}
Opt(elems*)
}
}
Loading