Skip to content

Commit 07bac3e

Browse files
authored
Support build header metadata in build.mill.yaml files (#5960)
- `./mill` and `./mill.bat` should parse `mill-version` as a top-level key - `MillLauncherMain` should parse `mill-version`, `mill-jvm-version`, etc. as top-level keys - Any `mill-build` key should be treated as keys to pass to the meta-build Covered by example tests included in the documentation
1 parent df493e3 commit 07bac3e

File tree

12 files changed

+180
-19
lines changed

12 files changed

+180
-19
lines changed

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

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ public static String readBuildHeader(Path buildFile, String errorFileName) {
9393
public static String readBuildHeader(
9494
Path buildFile, String errorFileName, boolean allowNonBuild) {
9595
try {
96+
String fileName = buildFile.getFileName().toString();
97+
98+
// For .yaml files, return the entire file content as YAML
99+
if (fileName.endsWith(".yaml") || fileName.endsWith(".yml")) {
100+
return Files.readString(buildFile);
101+
}
102+
103+
// For other files, extract YAML from //| comments
96104
java.util.List<String> lines = Files.readAllLines(buildFile);
97105
boolean readingBuildHeader = true;
98106
java.util.List<String> output = new ArrayList<>();

dist/scripts/src/mill.bat

Lines changed: 35 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -65,9 +65,9 @@ if [!MILL_VERSION!]==[] (
6565
if exist .config\mill-version (
6666
set /p MILL_VERSION=<.config\mill-version
6767
) else (
68-
if not "%MILL_BUILD_SCRIPT%"=="" (
68+
if exist build.mill.yaml (
6969
rem Find the line and process it
70-
for /f "tokens=*" %%a in ('findstr /R /C:"//\|.*mill-version" "%MILL_BUILD_SCRIPT%"') do (
70+
for /f "tokens=*" %%a in ('findstr /R /C:"mill-version:" "build.mill.yaml"') do (
7171
set "line=%%a"
7272

7373
rem --- 1. Replicate sed 's/.*://' ---
@@ -96,7 +96,39 @@ if [!MILL_VERSION!]==[] (
9696
:version_found
9797
rem no-op
9898
) else (
99-
rem no-op
99+
if not "%MILL_BUILD_SCRIPT%"=="" (
100+
rem Find the line and process it
101+
for /f "tokens=*" %%a in ('findstr /R /C:"//\|.*mill-version" "%MILL_BUILD_SCRIPT%"') do (
102+
set "line=%%a"
103+
104+
rem --- 1. Replicate sed 's/.*://' ---
105+
rem This removes everything up to and including the first colon
106+
set "line=!line:*:=!"
107+
108+
rem --- 2. Replicate sed 's/#.*//' ---
109+
rem Split on '#' and keep the first part
110+
for /f "tokens=1 delims=#" %%b in ("!line!") do (
111+
set "line=%%b"
112+
)
113+
114+
rem --- 3. Replicate sed 's/['"]//g' ---
115+
rem Remove all quotes
116+
set "line=!line:'=!"
117+
set "line=!line:"=!"
118+
119+
rem --- 4. NEW: Replicate sed's trim/space removal ---
120+
rem Remove all space characters from the result. This is more robust.
121+
set "MILL_VERSION=!line: =!"
122+
123+
rem We found the version, so we can exit the loop
124+
goto :version_found
125+
)
126+
127+
:version_found
128+
rem no-op
129+
) else (
130+
rem no-op
131+
)
100132
)
101133
)
102134
)

dist/scripts/src/mill.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,20 @@ if [ -z "${MILL_VERSION}" ] ; then
7272
MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)"
7373
elif [ -f ".config/mill-version" ] ; then
7474
MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)"
75+
elif [ -f "build.mill.yaml" ] ; then
76+
# `s/.*://`:
77+
# This is a greedy match that removes everything from the beginning of the line up to (and including) the last
78+
# colon (:). This effectively isolates the value part of the declaration.
79+
#
80+
# `s/#.*//`:
81+
# This removes any comments at the end of the line.
82+
#
83+
# `s/['\"]//g`:
84+
# This removes all single and double quotes from the string, wherever they appear (g is for "global").
85+
#
86+
# `s/^[[:space:]]*//; s/[[:space:]]*$//`:
87+
# These two expressions trim any leading or trailing whitespace ([[:space:]] matches spaces and tabs).
88+
MILL_VERSION="$(grep -E "mill-version:" "build.mill.yaml" | sed -E "s/.*://; s/#.*//; s/['\"]//g; s/^[[:space:]]*//; s/[[:space:]]*$//")"
7589
elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then
7690
# `s/.*://`:
7791
# This is a greedy match that removes everything from the beginning of the line up to (and including) the last

dist/scripts/test/src/MillVersionFrontmatterTests.scala

Lines changed: 37 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@ object MillVersionFrontmatterTests extends TestSuite {
77
private val millVersion = "1.0.0-RC1"
88

99
val tests: Tests = Tests {
10-
def doTest(frontmatter: String, expectedVersion: Option[String] = Some(millVersion))(using
11-
testValue: TestPath
12-
): Unit = {
10+
def doTest(
11+
frontmatter: String,
12+
expectedVersion: Option[String] = Some(millVersion),
13+
buildFile: String = "build.mill"
14+
)(using testValue: TestPath): Unit = {
1315
val wd = os.pwd / testValue.value
1416
os.makeDir.all(wd)
15-
os.write(wd / "build.mill", frontmatter)
17+
os.write(wd / buildFile, frontmatter)
1618

1719
// If that particular version is not downloaded to the cache, stdout will be polluted by the download messages.
1820
// Thus, we run our own task to print the version.
@@ -71,5 +73,36 @@ object MillVersionFrontmatterTests extends TestSuite {
7173
)
7274

7375
test("withCommentAfterTheBuildHeader") - doTest(s"""//| mill-version: $millVersion # comment""")
76+
77+
test("yaml") - {
78+
test("noFrontmatter") - doTest("", expectedVersion = None, buildFile = "build.mill.yaml")
79+
80+
test("onFirstLine") - doTest(
81+
s"""mill-version: $millVersion""",
82+
buildFile = "build.mill.yaml"
83+
)
84+
85+
test("onSecondLine") - doTest(
86+
s"""
87+
|mill-version: $millVersion
88+
|""".stripMargin,
89+
buildFile = "build.mill.yaml"
90+
)
91+
92+
test("valueQuotedWithSingleQuote") - doTest(
93+
s"""mill-version: '$millVersion'""",
94+
buildFile = "build.mill.yaml"
95+
)
96+
97+
test("valueQuotedWithDoubleQuote") - doTest(
98+
s"""mill-version: "$millVersion"""",
99+
buildFile = "build.mill.yaml"
100+
)
101+
102+
test("withCommentAfterTheVersion") - doTest(
103+
s"""mill-version: $millVersion # comment""",
104+
buildFile = "build.mill.yaml"
105+
)
106+
}
74107
}
75108
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// For simple projects defined with a `build.mill.yaml` file, meta-build configuration
2+
// is put in the `mill-build:` key of the `build.mill.yaml` file:
3+
4+
/** See Also: build.mill.yaml */
5+
6+
/** Usage
7+
8+
> ./mill version
9+
[typer...]
10+
[constructors...]
11+
[erasure...]
12+
[genBCode...]
13+
14+
*/
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
mill-build:
2+
scalacOptions: ["-verbose"]
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
/** See Also: build.mill.yaml */
2+
3+
/** Usage
4+
5+
> ./mill run
6+
Java version: 17.0.6
7+
8+
*/
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
mill-jvm-version: temurin:17.0.6
2+
3+
extends: [JavaModule]
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
public class Main {
2+
public static void main(String[] args) {
3+
System.out.println("Java version: " + System.getProperty("java.version"));
4+
}
5+
}

runner/daemon/src/mill/daemon/MillBuildBootstrap.scala

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,22 @@ class MillBuildBootstrap(
134134
val state =
135135
if (currentRootContainsBuildFile) evaluateRec(depth + 1)
136136
else {
137+
val parsedHeaderData = mill.internal.Util.parsedHeaderData(headerData)
138+
val metaBuildData =
139+
if (
140+
foundRootBuildFileName.endsWith(".yaml") || foundRootBuildFileName.endsWith(
141+
".yml"
142+
)
143+
) {
144+
// For YAML files, extract the mill-build key if it exists, otherwise use empty map
145+
parsedHeaderData.get("mill-build") match {
146+
case Some(millBuildValue: ujson.Obj) => millBuildValue.obj.toMap
147+
case _ => Map.empty[String, ujson.Value]
148+
}
149+
} else {
150+
// For non-YAML files (build.mill), use the entire parsed header data
151+
parsedHeaderData
152+
}
137153
val bootstrapModule =
138154
new MillBuildRootModule.BootstrapModule()(
139155
using
@@ -144,7 +160,7 @@ class MillBuildBootstrap(
144160
upickle.read[Map[
145161
String,
146162
ujson.Value
147-
]](mill.internal.Util.parsedHeaderData(headerData))
163+
]](metaBuildData)
148164
)
149165
)
150166
RunnerState(Some(bootstrapModule), Nil, None, Some(foundRootBuildFileName))

0 commit comments

Comments
 (0)