Skip to content

Commit 290fdcf

Browse files
lihaoyilefouautofix-ci[bot]
authored
Add a YAML header comment syntax for configuration of .mill- files, import $ivy, mill-version (#4969)
This PR adds a YAML 1.2 header comment syntax that subsumes many of the ad-hoc configuration mechanisms we have today: `.mill-version`, `.mill-opts`, `.mill-jvm-opts`, `.mill-jvm-version`, `import $ivy`, and possibly more meta-build overrides or other configuration options in future. ```scala //| mill-version: 0.13.0 //| mill-jvm-version: 17 //| repositories: [$PWD_URI/custom-repo] //| mvnDeps: [org.thymeleaf:thymeleaf:3.1.1.RELEASE] package build import mill._, javalib._ import org.thymeleaf.TemplateEngine import org.thymeleaf.context.Context object foo extends JavaModule { def htmlSnippet = Task { val context = new Context context.setVariable("heading", "hello") context.setVariable("paragraph", "world") new TemplateEngine().process( """<div><h1 th:text="${heading}"></h1><p th:text="${paragraph}"></p></div>""", context ) } def resources = Task { os.write(Task.dest / "snippet.txt", htmlSnippet()) super.resources() ++ Seq(PathRef(Task.dest)) } } ``` The YAML header for any `.mill` file must be a line-comment block starting from the first line of the file and prefixed by `//|`. The prefix is chosen to avoid conflicts with normal comments (`//`), Scala-CLI directives (`//>`), and Java markdown comments (`///`), and to be reminiscent of Scala `|`s commonly used in `.stripMargin` We use YAML 1.2 (via `snakeyaml-engine`) rather than ScalaCLI directives because: - It can map more directly to Mill concepts: `mill-version`, `mill-jvm-version`, `mvnDeps`, and other config keys can be directly in YAML without any mapping. - YAML's hierarchical list/dict/primitive data model maps clearly to Mill's JSON data model for tasks, whereas Scala-CLI directives do not have such a clean correspondence and will require that we manually maintain an explicit mapping - YAML will also be more familiar to non-Scala developers than Scala-CLI's directive syntax, including all the edge cases that are moderately well defined (quoting? escaping? comments? type coercions? multi-line strings?) - Processing by third-party tools will be easier: anyone can write a Python script to strip the prefixes and parse out the YAML using pyyaml or yamlcore, or their equivalents in Node.js/Ruby/Rust/Go/whatever, whereas with Scala-CLI directives you are restricted to a single JVM implementation without spec or documentation All of these concerns were raised in the original Scala-CLI directives discussion but were ignored ([link](https://contributors.scala-lang.org/t/pre-sip-using-directives/5700/5?u=lihaoyi), [link](https://contributors.scala-lang.org/t/pre-sip-scala-cli-as-new-scala-command/5628/92?u=lihaoyi)), so that brings us to today where we have to diverge from their syntax. The choice of YAML over TOML was largely arbitrary. Python chose TOML for their script-header metadata in [PEP-723](https://peps.python.org/pep-0723/), whereas all Markdown implementations ([Github](https://docs.github.com/en/contributing/writing-for-github-docs/using-yaml-frontmatter), [RMarkdown](https://zsmith27.github.io/rmarkdown_crash-course/lesson-4-yaml-headers.html), [Jekyll](https://jekyllrb.com/docs/front-matter/)) chose YAML for their header-metadata syntax. YAML 1.2 seems to fix/mitigate most of the problems with YAML 1.1 (e.g. the `no == false` thing). Kotlin has their own syntax for file-level metadata [`@file:JvmName("Foo")`](https://kotlinlang.org/docs/annotations.html#annotation-use-site-targets), as does Swift [`@Metadata { @DocumentationExtension(mergeBehavior: override) }`](https://www.swift.org/documentation/docc/metadata) Builds on top of #4624 --------- Co-authored-by: Tobias Roeser <le.petit.fou@web.de> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
1 parent 252ccef commit 290fdcf

File tree

135 files changed

+797
-833
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

135 files changed

+797
-833
lines changed

.config/mill-version

Lines changed: 0 additions & 1 deletion
This file was deleted.

build.mill

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
//| mill-version: 0.13.0-M1-43-b217bc
2+
13
package build
24
// imports
35
//import com.github.lolgab.mill.mima.Mima
@@ -230,6 +232,7 @@ object Deps {
230232
val hiltGradlePlugin = mvn"com.google.dagger:hilt-android-gradle-plugin:2.56"
231233

232234
val sbt = mvn"org.scala-sbt:sbt:1.10.10"
235+
val snakeyamlEngine = mvn"org.snakeyaml:snakeyaml-engine:2.9"
233236

234237
object RuntimeDeps {
235238
val dokkaVersion = "2.0.0"

ci/mill-bootstrap.patch

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,18 @@
1+
diff --git a/build.mill b/build.mill
2+
index d815508132a..ba62e147383 100644
3+
--- a/build.mill
4+
+++ b/build.mill
5+
@@ -19,9 +19,8 @@ import mill.define.Cross
6+
import scala.util.Properties
7+
8+
// plugins and dependencies
9+
-import $meta._
10+
+//| meta: true
11+
12+
-import $packages._
13+
14+
object Settings {
15+
val pomOrg = "com.lihaoyi"
116
diff --git a/mill-build/build.mill b/mill-build/build.mill
217
index f32b3cbcf65..3fc9b0097c8 100644
318
--- a/mill-build/build.mill

contrib/package.mill

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,6 @@ import mill.T
1414
import mill.define.Cross
1515
import build.Deps
1616

17-
// plugins and dependencies
18-
import $meta._
19-
2017
/**
2118
* [[build.contrib]] contains user-contributed Mill plugins that satisfy
2219
* a range of use cases not covered by the core Mill builtins. `contrib`

core/constants/package.mill

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import mill.scalalib._
99
* server, and the rest of the Mill codebase.
1010
*/
1111
object `package` extends RootModule with build.MillPublishJavaModule with BuildInfo {
12-
def buildInfoPackageName = "mill.cosntants"
12+
def buildInfoPackageName = "mill.constants"
1313
def buildInfoMembers = Seq(BuildInfo.Value("millVersion", build.millVersion(), "Mill version."))
1414

1515
object test extends JavaTests with TestModule.Junit4 {

core/constants/src/mill/constants/DebugLog.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
* home folder so you can find them
1010
*/
1111
public class DebugLog {
12+
public static synchronized void apply(String s) {
13+
println(s);
14+
}
15+
1216
public static synchronized void println(String s) {
1317
Path path = Paths.get(System.getProperty("user.home"), "mill-debug-log.txt");
1418
try {

core/constants/src/mill/constants/EnvVars.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,6 @@ public class EnvVars {
2121
*/
2222
public static final String MILL_SERVER_TIMEOUT_MILLIS = "MILL_SERVER_TIMEOUT_MILLIS";
2323

24-
public static final String MILL_JVM_VERSION_PATH = "MILL_JVM_VERSION_PATH";
25-
public static final String MILL_JVM_OPTS_PATH = "MILL_JVM_OPTS_PATH";
26-
public static final String MILL_OPTS_PATH = "MILL_OPTS_PATH";
27-
2824
/**
2925
* Output directory where Mill workers' state and Mill tasks output should be
3026
* written to

core/constants/src/mill/constants/Util.java

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@
77
import java.security.MessageDigest;
88
import java.security.NoSuchAlgorithmException;
99
import java.util.Locale;
10+
import java.util.Map;
11+
import java.util.regex.Matcher;
12+
import java.util.regex.Pattern;
1013

1114
public class Util {
1215

@@ -71,4 +74,48 @@ public static String hexArray(byte[] bytes) {
7174
public static boolean hasConsole() {
7275
return hasConsole0;
7376
}
77+
78+
public static String readYamlHeader(java.nio.file.Path buildFile) throws java.io.IOException {
79+
java.util.List<String> lines = java.nio.file.Files.readAllLines(buildFile);
80+
String yamlString = lines.stream()
81+
.filter(line -> line.startsWith("//|"))
82+
.map(line -> line.substring(4)) // Remove the `//|` prefix
83+
.collect(java.util.stream.Collectors.joining("\n"));
84+
85+
return yamlString;
86+
}
87+
88+
private static String envInterpolatorPattern0 = "(\\$|[A-Z_][A-Z0-9_]*)";
89+
private static Pattern envInterpolatorPattern =
90+
Pattern.compile("\\$\\{" + envInterpolatorPattern0 + "\\}|\\$" + envInterpolatorPattern0);
91+
92+
/**
93+
* Interpolate variables in the form of <code>${VARIABLE}</code> based on the given Map <code>env</code>.
94+
* Missing vars will be replaced by the empty string.
95+
*/
96+
public static String interpolateEnvVars(String input, Map<String, String> env0) {
97+
Matcher matcher = envInterpolatorPattern.matcher(input);
98+
// StringBuilder to store the result after replacing
99+
StringBuffer result = new StringBuffer();
100+
101+
Map<String, String> env = new java.util.HashMap<>();
102+
env.putAll(env0);
103+
104+
env.put("MILL_VERSION", mill.constants.BuildInfo.millVersion);
105+
while (matcher.find()) {
106+
String match = matcher.group(1);
107+
if (match == null) match = matcher.group(2);
108+
if (match.equals("$")) {
109+
matcher.appendReplacement(result, "\\$");
110+
} else {
111+
String envVarValue;
112+
mill.constants.DebugLog.println("MATCH " + match);
113+
envVarValue = env.containsKey(match) ? env.get(match) : "";
114+
matcher.appendReplacement(result, envVarValue);
115+
}
116+
}
117+
118+
matcher.appendTail(result); // Append the remaining part of the string
119+
return result.toString();
120+
}
74121
}

core/exec/package.mill

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,5 @@ import mill._
88
*/
99
object `package` extends RootModule with build.MillStableScalaModule {
1010
def moduleDeps = Seq(build.core.define, build.core.internal)
11+
def mvnDeps = Seq(build.Deps.snakeyamlEngine)
1112
}

core/exec/src/mill/exec/Execution.scala

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,8 @@ private[mill] case class Execution(
3737
systemExit: Int => Nothing,
3838
exclusiveSystemStreams: SystemStreams,
3939
getEvaluator: () => EvaluatorApi,
40-
offline: Boolean
40+
offline: Boolean,
41+
headerData: String
4142
) extends GroupExecution with AutoCloseable {
4243

4344
// this (shorter) constructor is used from [[MillBuildBootstrap]] via reflection
@@ -57,7 +58,8 @@ private[mill] case class Execution(
5758
systemExit: Int => Nothing,
5859
exclusiveSystemStreams: SystemStreams,
5960
getEvaluator: () => EvaluatorApi,
60-
offline: Boolean
61+
offline: Boolean,
62+
headerData: String
6163
) = this(
6264
baseLogger,
6365
new JsonArrayLogger.ChromeProfile(os.Path(outPath) / millChromeProfile),
@@ -76,7 +78,8 @@ private[mill] case class Execution(
7678
systemExit,
7779
exclusiveSystemStreams,
7880
getEvaluator,
79-
offline
81+
offline,
82+
headerData
8083
)
8184

8285
def withBaseLogger(newBaseLogger: Logger) = this.copy(baseLogger = newBaseLogger)

0 commit comments

Comments
 (0)