From 98ba5099e0e96061d57b66e091b65431c430e3cf Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Thu, 18 Dec 2025 10:08:09 +0100 Subject: [PATCH 1/5] Update Mill launch scripts --- mill.bat | 103 ++++++++------------- millw | 268 ++++++++++++------------------------------------------- 2 files changed, 92 insertions(+), 279 deletions(-) diff --git a/mill.bat b/mill.bat index 7f99598658..f4e4b58e04 100755 --- a/mill.bat +++ b/mill.bat @@ -1,40 +1,8 @@ @echo off -rem This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. -rem -rem This script determines the Mill version to use by trying these sources -rem - env-variable `MILL_VERSION` -rem - local file `.mill-version` -rem - local file `.config/mill-version` -rem - `mill-version` from YAML frontmatter of current buildfile -rem - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) -rem - env-variable `DEFAULT_MILL_VERSION` -rem -rem If a version has the suffix '-native' a native binary will be used. -rem If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. -rem If no such suffix is found, the script will pick a default based on version and platform. -rem -rem Once a version was determined, it tries to use either -rem - a system-installed mill, if found and it's version matches -rem - an already downloaded version under %USERPROFILE%\.mill\download -rem -rem If no working mill version was found on the system, -rem this script downloads a binary file from Maven Central or Github Pages (this is version dependent) -rem into a cache location (%USERPROFILE%\.mill\download). -rem -rem Mill Project URL: https://github.com/com-lihaoyi/mill -rem Script Version: 1.0.5 -rem -rem If you want to improve this script, please also contribute your changes back! -rem This script was generated from: dist/scripts/src/mill.bat -rem -rem Licensed under the Apache License, Version 2.0 - -rem setlocal seems to be unavailable on Windows 95/98/ME -rem but I don't think we need to support them in 2019 setlocal enabledelayedexpansion -if [!DEFAULT_MILL_VERSION!]==[] ( set "DEFAULT_MILL_VERSION=0.12.16" ) +if [!DEFAULT_MILL_VERSION!]==[] ( set "DEFAULT_MILL_VERSION=1.0.6" ) if [!MILL_GITHUB_RELEASE_CDN!]==[] ( set "MILL_GITHUB_RELEASE_CDN=" ) @@ -65,9 +33,24 @@ if [!MILL_VERSION!]==[] ( if exist .config\mill-version ( set /p MILL_VERSION=<.config\mill-version ) else ( - if not "%MILL_BUILD_SCRIPT%"=="" ( + rem Determine which config file to use for version extraction + set "MILL_VERSION_CONFIG_FILE=" + set "MILL_VERSION_SEARCH_PATTERN=" + + if exist build.mill.yaml ( + set "MILL_VERSION_CONFIG_FILE=build.mill.yaml" + set "MILL_VERSION_SEARCH_PATTERN=mill-version:" + ) else ( + if not "%MILL_BUILD_SCRIPT%"=="" ( + set "MILL_VERSION_CONFIG_FILE=%MILL_BUILD_SCRIPT%" + set "MILL_VERSION_SEARCH_PATTERN=//\|.*mill-version" + ) + ) + + rem Process the config file if found + if not "!MILL_VERSION_CONFIG_FILE!"=="" ( rem Find the line and process it - for /f "tokens=*" %%a in ('findstr /R /C:"//\|.*mill-version" "%MILL_BUILD_SCRIPT%"') do ( + for /f "tokens=*" %%a in ('findstr /R /C:"!MILL_VERSION_SEARCH_PATTERN!" "!MILL_VERSION_CONFIG_FILE!"') do ( set "line=%%a" rem --- 1. Replicate sed 's/.*://' --- @@ -85,7 +68,7 @@ if [!MILL_VERSION!]==[] ( set "line=!line:'=!" set "line=!line:"=!" - rem --- 4. NEW: Replicate sed's trim/space removal --- + rem --- 4. Replicate sed's trim/space removal --- rem Remove all space characters from the result. This is more robust. set "MILL_VERSION=!line: =!" @@ -95,27 +78,23 @@ if [!MILL_VERSION!]==[] ( :version_found rem no-op - ) else ( - rem no-op ) ) ) ) if [!MILL_VERSION!]==[] ( - echo No mill version specified. >&2 - echo You should provide a version via a '//^| mill-version: ' comment or a '.mill-version' file. >&2 set MILL_VERSION=%DEFAULT_MILL_VERSION% ) -if [!MILL_DOWNLOAD_PATH!]==[] set MILL_DOWNLOAD_PATH=%USERPROFILE%\.mill\download +if [!MILL_FINAL_DOWNLOAD_FOLDER!]==[] set MILL_FINAL_DOWNLOAD_FOLDER=%USERPROFILE%\.cache\mill\download rem without bat file extension, cmd doesn't seem to be able to run it set "MILL_NATIVE_SUFFIX=-native" set "MILL_JVM_SUFFIX=-jvm" -set "FULL_MILL_VERSION=%MILL_VERSION%" -set "MILL_EXT=.bat" +set "MILL_FULL_VERSION=%MILL_VERSION%" +set "MILL_DOWNLOAD_EXT=.bat" set "ARTIFACT_SUFFIX=" REM Check if MILL_VERSION contains MILL_NATIVE_SUFFIX echo !MILL_VERSION! | findstr /C:"%MILL_NATIVE_SUFFIX%" >nul @@ -125,7 +104,7 @@ if !errorlevel! equ 0 ( REM https://github.com/oracle/graal/issues/9215 IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( set "ARTIFACT_SUFFIX=-native-windows-amd64" - set "MILL_EXT=.exe" + set "MILL_DOWNLOAD_EXT=.exe" ) else ( rem no-op ) @@ -153,7 +132,7 @@ if !errorlevel! equ 0 ( if "!SKIP_VERSION!"=="false" ( IF /I NOT "%PROCESSOR_ARCHITECTURE%"=="ARM64" ( set "ARTIFACT_SUFFIX=-native-windows-amd64" - set "MILL_EXT=.exe" + set "MILL_DOWNLOAD_EXT=.exe" ) ) else ( rem no-op @@ -161,7 +140,7 @@ if !errorlevel! equ 0 ( ) ) -set MILL=%MILL_DOWNLOAD_PATH%\!FULL_MILL_VERSION!!MILL_EXT! +set MILL=%MILL_FINAL_DOWNLOAD_FOLDER%\!MILL_FULL_VERSION!!MILL_DOWNLOAD_EXT! set MILL_RESOLVE_DOWNLOAD= @@ -257,34 +236,26 @@ if [!MILL_RESOLVE_DOWNLOAD!]==[true] ( ) rem there seems to be no way to generate a unique temporary file path (on native Windows) - set MILL_DOWNLOAD_FILE=%MILL%.tmp + if defined MILL_OUTPUT_DIR ( + set MILL_TEMP_DOWNLOAD_FILE=%MILL_OUTPUT_DIR%\mill-temp-download + if not exist "%MILL_OUTPUT_DIR%" mkdir "%MILL_OUTPUT_DIR%" + ) else ( + set MILL_TEMP_DOWNLOAD_FILE=out\mill-bootstrap-download + if not exist "out" mkdir "out" + ) echo Downloading mill !MILL_VERSION! from !MILL_DOWNLOAD_URL! ... 1>&2 - if not exist "%MILL_DOWNLOAD_PATH%" mkdir "%MILL_DOWNLOAD_PATH%" - rem curl is bundled with recent Windows 10 - rem but I don't think we can expect all the users to have it in 2019 - where /Q curl - if !ERRORLEVEL! EQU 0 ( - curl -f -L "!MILL_DOWNLOAD_URL!" -o "!MILL_DOWNLOAD_FILE!" - ) else ( - rem bitsadmin seems to be available on Windows 7 - rem without /dynamic, github returns 403 - rem bitsadmin is sometimes needlessly slow but it looks better with /priority foreground - bitsadmin /transfer millDownloadJob /dynamic /priority foreground "!MILL_DOWNLOAD_URL!" "!MILL_DOWNLOAD_FILE!" - ) - if not exist "!MILL_DOWNLOAD_FILE!" ( - echo Could not download mill !MILL_VERSION! 1>&2 - exit /b 1 - ) + curl -f -L "!MILL_DOWNLOAD_URL!" -o "!MILL_TEMP_DOWNLOAD_FILE!" - move /y "!MILL_DOWNLOAD_FILE!" "%MILL%" + if not exist "%MILL_FINAL_DOWNLOAD_FOLDER%" mkdir "%MILL_FINAL_DOWNLOAD_FOLDER%" + move /y "!MILL_TEMP_DOWNLOAD_FILE!" "%MILL%" - set MILL_DOWNLOAD_FILE= + set MILL_TEMP_DOWNLOAD_FILE= set MILL_DOWNLOAD_SUFFIX= ) -set MILL_DOWNLOAD_PATH= +set MILL_FINAL_DOWNLOAD_FOLDER= set MILL_VERSION= set MILL_REPO_URL= diff --git a/millw b/millw index 97db4e237e..1062824b51 100755 --- a/millw +++ b/millw @@ -1,58 +1,15 @@ #!/usr/bin/env sh -# This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. -# -# This script determines the Mill version to use by trying these sources -# - env-variable `MILL_VERSION` -# - local file `.mill-version` -# - local file `.config/mill-version` -# - `mill-version` from YAML frontmatter of current buildfile -# - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) -# - env-variable `DEFAULT_MILL_VERSION` -# -# If a version has the suffix '-native' a native binary will be used. -# If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. -# If no such suffix is found, the script will pick a default based on version and platform. -# -# Once a version was determined, it tries to use either -# - a system-installed mill, if found and it's version matches -# - an already downloaded version under ~/.cache/mill/download -# -# If no working mill version was found on the system, -# this script downloads a binary file from Maven Central or Github Pages (this is version dependent) -# into a cache location (~/.cache/mill/download). -# -# Mill Project URL: https://github.com/com-lihaoyi/mill -# Script Version: 1.0.5 -# -# If you want to improve this script, please also contribute your changes back! -# This script was generated from: dist/scripts/src/mill.sh -# -# Licensed under the Apache License, Version 2.0 - set -e -if [ -z "${DEFAULT_MILL_VERSION}" ] ; then - DEFAULT_MILL_VERSION="0.12.16" -fi - +if [ -z "${DEFAULT_MILL_VERSION}" ] ; then DEFAULT_MILL_VERSION="1.0.6"; fi -if [ -z "${GITHUB_RELEASE_CDN}" ] ; then - GITHUB_RELEASE_CDN="" -fi +if [ -z "${GITHUB_RELEASE_CDN}" ] ; then GITHUB_RELEASE_CDN=""; fi +if [ -z "$MILL_MAIN_CLI" ] ; then MILL_MAIN_CLI="${0}"; fi MILL_REPO_URL="https://github.com/com-lihaoyi/mill" -if [ -z "${CURL_CMD}" ] ; then - CURL_CMD=curl -fi - -# Explicit commandline argument takes precedence over all other methods -if [ "$1" = "--mill-version" ] ; then - echo "The --mill-version option is no longer supported." 1>&2 -fi - MILL_BUILD_SCRIPT="" if [ -f "build.mill" ] ; then @@ -63,77 +20,37 @@ elif [ -f "build.sc" ] ; then MILL_BUILD_SCRIPT="build.sc" fi -# Please note, that if a MILL_VERSION is already set in the environment, -# We reuse it's value and skip searching for a value. +# `s/.*://`: +# This is a greedy match that removes everything from the beginning of the line up to (and including) the last +# colon (:). This effectively isolates the value part of the declaration. +# +# `s/#.*//`: +# This removes any comments at the end of the line. +# +# `s/['\"]//g`: +# This removes all single and double quotes from the string, wherever they appear (g is for "global"). +# +# `s/^[[:space:]]*//; s/[[:space:]]*$//`: +# These two expressions trim any leading or trailing whitespace ([[:space:]] matches spaces and tabs). +TRIM_VALUE_SED="s/.*://; s/#.*//; s/['\"]//g; s/^[[:space:]]*//; s/[[:space:]]*$//" -# If not already set, read .mill-version file if [ -z "${MILL_VERSION}" ] ; then if [ -f ".mill-version" ] ; then MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" elif [ -f ".config/mill-version" ] ; then MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" + elif [ -f "build.mill.yaml" ] ; then + MILL_VERSION="$(grep -E "mill-version:" "build.mill.yaml" | sed -E "$TRIM_VALUE_SED")" elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then - # `s/.*://`: - # This is a greedy match that removes everything from the beginning of the line up to (and including) the last - # colon (:). This effectively isolates the value part of the declaration. - # - # `s/#.*//`: - # This removes any comments at the end of the line. - # - # `s/['\"]//g`: - # This removes all single and double quotes from the string, wherever they appear (g is for "global"). - # - # `s/^[[:space:]]*//; s/[[:space:]]*$//`: - # These two expressions trim any leading or trailing whitespace ([[:space:]] matches spaces and tabs). - MILL_VERSION="$(grep -E "//\|.*mill-version" "${MILL_BUILD_SCRIPT}" | sed -E "s/.*://; s/#.*//; s/['\"]//g; s/^[[:space:]]*//; s/[[:space:]]*$//")" + MILL_VERSION="$(grep -E "//\|.*mill-version" "${MILL_BUILD_SCRIPT}" | sed -E "$TRIM_VALUE_SED")" fi fi -MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" - -if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then - MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" -fi - -# If not already set, try to fetch newest from Github -if [ -z "${MILL_VERSION}" ] ; then - # TODO: try to load latest version from release page - echo "No mill version specified." 1>&2 - echo "You should provide a version via a '//| mill-version: ' comment or a '.mill-version' file." 1>&2 - - mkdir -p "${MILL_DOWNLOAD_PATH}" - LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( - # we might be on OSX or BSD which don't have -d option for touch - # but probably a -A [-][[hh]mm]SS - touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" - ) || ( - # in case we still failed, we retry the first touch command with the intention - # to show the (previously suppressed) error message - LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" - ) +if [ -z "${MILL_VERSION}" ] ; then MILL_VERSION="${DEFAULT_MILL_VERSION}"; fi - # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 - # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then - if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then - # we know a current latest version - MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) - fi +MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" - if [ -z "${MILL_VERSION}" ] ; then - # we don't know a current latest version - echo "Retrieving latest mill version ..." 1>&2 - LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" - MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) - fi - - if [ -z "${MILL_VERSION}" ] ; then - # Last resort - MILL_VERSION="${DEFAULT_MILL_VERSION}" - echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 - else - echo "Using mill version ${MILL_VERSION}" 1>&2 - fi -fi +if [ -z "${MILL_FINAL_DOWNLOAD_FOLDER}" ] ; then MILL_FINAL_DOWNLOAD_FOLDER="${MILL_USER_CACHE_DIR}/download"; fi MILL_NATIVE_SUFFIX="-native" MILL_JVM_SUFFIX="-jvm" @@ -141,17 +58,11 @@ FULL_MILL_VERSION=$MILL_VERSION ARTIFACT_SUFFIX="" set_artifact_suffix(){ if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then - if [ "$(uname -m)" = "aarch64" ]; then - ARTIFACT_SUFFIX="-native-linux-aarch64" - else - ARTIFACT_SUFFIX="-native-linux-amd64" - fi + if [ "$(uname -m)" = "aarch64" ]; then ARTIFACT_SUFFIX="-native-linux-aarch64" + else ARTIFACT_SUFFIX="-native-linux-amd64"; fi elif [ "$(uname)" = "Darwin" ]; then - if [ "$(uname -m)" = "arm64" ]; then - ARTIFACT_SUFFIX="-native-mac-aarch64" - else - ARTIFACT_SUFFIX="-native-mac-amd64" - fi + if [ "$(uname -m)" = "arm64" ]; then ARTIFACT_SUFFIX="-native-mac-aarch64" + else ARTIFACT_SUFFIX="-native-mac-amd64"; fi else echo "This native mill launcher supports only Linux and macOS." 1>&2 exit 1 @@ -188,136 +99,67 @@ case "$MILL_VERSION" in ;; esac -MILL="${MILL_DOWNLOAD_PATH}/$MILL_VERSION$ARTIFACT_SUFFIX" - -try_to_use_system_mill() { - if [ "$(uname)" != "Linux" ]; then - return 0 - fi - - MILL_IN_PATH="$(command -v mill || true)" - - if [ -z "${MILL_IN_PATH}" ]; then - return 0 - fi - - SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") - if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then - # MILL_IN_PATH is (very likely) a shell script and not the mill - # executable, ignore it. - return 0 - fi - - SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") - SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") - SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") - - if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then - mkdir -p "${MILL_USER_CACHE_DIR}" - fi - - SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" - if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then - parseSystemMillInfo() { - LINE_NUMBER="${1}" - # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the - # variable definition in that line in two halves and return - # the value, and finally remove the quotes. - sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ - cut -d= -f2 |\ - sed 's/"\(.*\)"/\1/' - } - - CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) - CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) - CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) - CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) - - if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ - && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ - && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then - if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then - MILL="${SYSTEM_MILL_PATH}" - return 0 - else - return 0 - fi - fi - fi - - SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') - - cat < "${SYSTEM_MILL_INFO_FILE}" -CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" -CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" -CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" -CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" -EOF - - if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then - MILL="${SYSTEM_MILL_PATH}" - fi -} -try_to_use_system_mill +MILL="${MILL_FINAL_DOWNLOAD_FOLDER}/$MILL_VERSION$ARTIFACT_SUFFIX" # If not already downloaded, download it if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then case $MILL_VERSION in 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) - DOWNLOAD_SUFFIX="" - DOWNLOAD_FROM_MAVEN=0 + MILL_DOWNLOAD_SUFFIX="" + MILL_DOWNLOAD_FROM_MAVEN=0 ;; 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=0 + MILL_DOWNLOAD_SUFFIX="-assembly" + MILL_DOWNLOAD_FROM_MAVEN=0 ;; *) - DOWNLOAD_SUFFIX="-assembly" - DOWNLOAD_FROM_MAVEN=1 + MILL_DOWNLOAD_SUFFIX="-assembly" + MILL_DOWNLOAD_FROM_MAVEN=1 ;; esac case $MILL_VERSION in 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) - DOWNLOAD_EXT="jar" + MILL_DOWNLOAD_EXT="jar" ;; 0.12.* ) - DOWNLOAD_EXT="exe" + MILL_DOWNLOAD_EXT="exe" ;; 0.* ) - DOWNLOAD_EXT="jar" + MILL_DOWNLOAD_EXT="jar" ;; *) - DOWNLOAD_EXT="exe" + MILL_DOWNLOAD_EXT="exe" ;; esac - DOWNLOAD_FILE=$(mktemp mill.XXXXXX) - if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then - DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${DOWNLOAD_EXT}" + MILL_TEMP_DOWNLOAD_FILE="${MILL_OUTPUT_DIR:-out}/mill-temp-download" + mkdir -p "$(dirname "${MILL_TEMP_DOWNLOAD_FILE}")" + + if [ "$MILL_DOWNLOAD_FROM_MAVEN" = "1" ] ; then + MILL_DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${MILL_DOWNLOAD_EXT}" else MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') - DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" + MILL_DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${MILL_DOWNLOAD_SUFFIX}" unset MILL_VERSION_TAG fi + if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then - echo $DOWNLOAD_URL + echo $MILL_DOWNLOAD_URL echo $MILL exit 0 fi - # TODO: handle command not found - echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 - ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" - chmod +x "${DOWNLOAD_FILE}" - mkdir -p "${MILL_DOWNLOAD_PATH}" - mv "${DOWNLOAD_FILE}" "${MILL}" - unset DOWNLOAD_FILE - unset DOWNLOAD_SUFFIX -fi + echo "Downloading mill ${MILL_VERSION} from ${MILL_DOWNLOAD_URL} ..." 1>&2 + curl -f -L -o "${MILL_TEMP_DOWNLOAD_FILE}" "${MILL_DOWNLOAD_URL}" + + chmod +x "${MILL_TEMP_DOWNLOAD_FILE}" + + mkdir -p "${MILL_FINAL_DOWNLOAD_FOLDER}" + mv "${MILL_TEMP_DOWNLOAD_FILE}" "${MILL}" -if [ -z "$MILL_MAIN_CLI" ] ; then - MILL_MAIN_CLI="${0}" + unset MILL_TEMP_DOWNLOAD_FILE + unset MILL_DOWNLOAD_SUFFIX fi MILL_FIRST_ARG="" @@ -327,7 +169,7 @@ if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] | shift fi -unset MILL_DOWNLOAD_PATH +unset MILL_FINAL_DOWNLOAD_FOLDER unset MILL_OLD_DOWNLOAD_PATH unset OLD_MILL unset MILL_VERSION From 02b3cb864b411e0ab91ff5c73d4a37239f2e75f7 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 19 Dec 2025 09:56:31 +0100 Subject: [PATCH 2/5] WIP --- .github/scripts/check-cross-version-deps.sc | 4 +- .github/workflows/ci.yml | 2 +- .mill-version | 2 +- build.mill.scala | 495 +++++++++++--------- mill | 2 +- project/deps/package.mill.scala | 9 +- project/publish/package.mill.scala | 48 +- project/settings/package.mill.scala | 92 ++-- 8 files changed, 354 insertions(+), 300 deletions(-) diff --git a/.github/scripts/check-cross-version-deps.sc b/.github/scripts/check-cross-version-deps.sc index 64f16dcea5..64c23335ba 100755 --- a/.github/scripts/check-cross-version-deps.sc +++ b/.github/scripts/check-cross-version-deps.sc @@ -11,9 +11,9 @@ val modules = for { module <- modules } { println(s"Checking for $module...") - val depRegex = "\\[\\d+]\\s+[│└├─\\S\\s]+\\s([\\w.-]+):([\\w.-]+):([\\w\\s\\S.-]+)".r + val depRegex = "[│└├─\\S\\s]+\\s([\\w.-]+):([\\w.-]+):([\\w\\s\\S.-]+)".r val scalaDepSuffixRegex = "^(.+?)(_[23](?:\\.\\d{2})?)?$".r - val deps = os.proc(os.pwd / "mill", "-i", s"$module.ivyDepsTree") + val deps = os.proc(os.pwd / "mill", "-i", s"$module.showMvnDepsTree") .call(cwd = os.pwd) .out .lines() diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5a4a0fff20..c8cc0a6b5c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1455,7 +1455,7 @@ jobs: - name: Check native-image config format run: ./mill -i __.checkNativeImageConfFormat - name: Check Ammonite availability - run: ./mill -i 'dummy.amm[_].resolvedRunIvyDeps' + run: ./mill -i 'dummy.amm[_].resolvedRunMvnDeps' - name: Check for cross Scala version conflicts run: .github/scripts/check-cross-version-deps.sc - name: Scalafix check diff --git a/.mill-version b/.mill-version index bcc7c361e1..af0b7ddbff 100644 --- a/.mill-version +++ b/.mill-version @@ -1 +1 @@ -0.12.16 +1.0.6 diff --git a/build.mill.scala b/build.mill.scala index b19a0f8cdd..f0dda4fdb9 100644 --- a/build.mill.scala +++ b/build.mill.scala @@ -1,9 +1,12 @@ +//| mvnDeps: +//| - io.github.alexarchambault.mill::mill-native-image::0.2.4 +//| - io.github.alexarchambault.mill::mill-native-image-upload:0.2.4 +//| - com.goyeau::mill-scalafix::0.6.0 +//| - com.lumidion::sonatype-central-client-requests:0.6.0 +//| - io.get-coursier:coursier-launcher_2.13:2.1.25-M21 +//| - org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r package build -import $packages._ -import $ivy.`com.lihaoyi::mill-contrib-bloop:$MILL_VERSION` -import $ivy.`io.get-coursier::coursier-launcher:2.1.25-M21` -import $ivy.`io.github.alexarchambault.mill::mill-native-image-upload:0.1.31-1` import build.ci.publishVersion import build.project.deps import deps.{Cli, Deps, Docker, InternalDeps, Java, Scala, TestDeps} @@ -15,6 +18,7 @@ import settings.{ FormatNativeImageConf, HasTests, LocalRepo, + LocatedInModules, PublishLocalNoFluff, ScalaCliCrossSbtModule, ScalaCliScalafixModule, @@ -33,39 +37,35 @@ import java.io.File import java.net.URL import java.nio.charset.Charset import java.util.Locale -import de.tobiasroeser.mill.vcs.version.VcsVersion import io.github.alexarchambault.millnativeimage.upload.Upload -import mill._ -import mill.api.Loose -import scalalib.{publish => _, _} -import mill.contrib.bloop.Bloop -import mill.define.Task.Simple -import mill.testrunner.TestResult +import mill.* +import mill.api.{BuildCtx, ModuleCtx, Task} +import mill.scalalib.* +import scalalib.{publish as _, *} +import mill.javalib.testrunner.TestResult +import mill.util.{Tasks, VcsVersion} import _root_.scala.util.{Properties, Using} - -// Tell mill modules are under modules/ -implicit def millModuleBasePath: define.Ctx.BasePath = - define.Ctx.BasePath(super.millModuleBasePath.value / "modules") +import _root_.scala.util.{Properties, Using} object cli extends Cross[Cli](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -trait CrossScalaDefault { _: mill.define.Cross[_] => +trait CrossScalaDefault { self: Cross[?] => def crossScalaDefaultVersion: String override def defaultCrossSegments: Seq[String] = Seq(crossScalaDefaultVersion) } -trait CrossScalaDefaultToInternal extends CrossScalaDefault { _: mill.define.Cross[_] => +trait CrossScalaDefaultToInternal extends CrossScalaDefault { self: Cross[?] => override def crossScalaDefaultVersion: String = Scala.defaultInternal } -trait CrossScalaDefaultToRunner extends CrossScalaDefault { _: mill.define.Cross[_] => +trait CrossScalaDefaultToRunner extends CrossScalaDefault { self: Cross[?] => override def crossScalaDefaultVersion: String = Scala.runnerScala3 } // Publish a bootstrapped, executable jar for a restricted environments object cliBootstrapped extends ScalaCliPublishModule { - override def unmanagedClasspath: T[Agg[PathRef]] = + override def unmanagedClasspath: T[Seq[PathRef]] = Task(cli(Scala.defaultInternal).nativeImageClassPath()) override def jar: T[PathRef] = assembly() @@ -80,9 +80,7 @@ object cliBootstrapped extends ScalaCliPublishModule { Assembly.Rule.ExcludePattern(".*\\.semanticdb") ) ++ super.assemblyRules - override def resources: T[Seq[PathRef]] = Task.Sources { - super.resources() ++ Seq(propertiesFilesResources()) - } + override def resources: T[Seq[PathRef]] = super.resources() ++ Seq(propertiesFilesResources()) def propertiesFilesResources: T[PathRef] = Task(persistent = true) { val dir = Task.dest / "resources" @@ -98,27 +96,33 @@ object `specification-level` extends Cross[SpecificationLevel](Scala.scala3MainV with CrossScalaDefaultToInternal object `build-macros` extends Cross[BuildMacros](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -object config extends Cross[Config](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -object options extends Cross[Options](Scala.scala3MainVersions) with CrossScalaDefaultToInternal +object config extends Cross[Config](Scala.scala3MainVersions) + with CrossScalaDefaultToInternal +object options extends Cross[Options](Scala.scala3MainVersions) + with CrossScalaDefaultToInternal object directives extends Cross[Directives](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -object core extends Cross[Core](Scala.scala3MainVersions) with CrossScalaDefaultToInternal +object core extends Cross[Core](Scala.scala3MainVersions) + with CrossScalaDefaultToInternal object `build-module` extends Cross[Build](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -object runner extends Cross[Runner](Scala.runnerScalaVersions) with CrossScalaDefaultToRunner +object runner extends Cross[Runner](Scala.runnerScalaVersions) + with CrossScalaDefaultToRunner object `test-runner` extends Cross[TestRunner](Scala.runnerScalaVersions) with CrossScalaDefaultToRunner object `tasty-lib` extends Cross[TastyLib](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -object `scala-cli-bsp` extends JavaModule with ScalaCliPublishModule { - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Seq( +object `scala-cli-bsp` extends JavaModule + with ScalaCliPublishModule + with LocatedInModules { + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.bsp4j ) } object integration extends CliIntegration { object test extends IntegrationScalaTests { - override def ivyDeps: T[Loose.Agg[Dep]] = super.ivyDeps() ++ Seq( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.coursierArchiveCache, Deps.jgit, Deps.jsoup @@ -126,10 +130,8 @@ object integration extends CliIntegration { } object docker extends CliIntegrationDocker { object test extends ScalaCliTests { - override def sources: T[Seq[PathRef]] = Task.Sources { - super.sources() ++ integration.sources() - } - def tmpDirBase: T[PathRef] = Task(persistent = true) { + override def sources: T[Seq[PathRef]] = super.sources() ++ integration.sources() + def tmpDirBase: T[PathRef] = Task(persistent = true) { PathRef(Task.dest / "working-dir") } override def forkEnv: T[Map[String, String]] = super.forkEnv() ++ Seq( @@ -142,10 +144,8 @@ object integration extends CliIntegration { object `docker-slim` extends CliIntegrationDocker { object test extends ScalaCliTests { - override def sources: T[Seq[PathRef]] = Task.Sources { - integration.docker.test.sources() - } - def tmpDirBase: T[PathRef] = Task(persistent = true) { + override def sources: T[Seq[PathRef]] = integration.docker.test.sources() + def tmpDirBase: T[PathRef] = Task(persistent = true) { PathRef(Task.dest / "working-dir") } override def forkEnv: T[Map[String, String]] = super.forkEnv() ++ Seq( @@ -160,8 +160,9 @@ object integration extends CliIntegration { object `docs-tests` extends Cross[DocsTests](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -trait DocsTests extends CrossSbtModule with ScalaCliScalafixModule with HasTests { main => - override def ivyDeps: T[Agg[Dep]] = Agg( +trait DocsTests extends CrossSbtModule with ScalaCliScalafixModule with LocatedInModules + with HasTests { main => + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.fansi, Deps.osLib, Deps.pprint @@ -200,26 +201,28 @@ trait DocsTests extends CrossSbtModule with ScalaCliScalafixModule with HasTests object test extends ScalaCliTests with ScalaCliScalafixModule { override def forkEnv: T[Map[String, String]] = super.forkEnv() ++ extraEnv() ++ Seq( - "SCALA_CLI_EXAMPLES" -> (Task.workspace / "examples").toString, - "SCALA_CLI_GIF_SCENARIOS" -> (Task.workspace / "gifs" / "scenarios").toString, - "SCALA_CLI_WEBSITE_IMG" -> (Task.workspace / "website" / "static" / "img").toString, - "SCALA_CLI_GIF_RENDERER_DOCKER_DIR" -> (Task.workspace / "gifs").toString, - "SCALA_CLI_SVG_RENDERER_DOCKER_DIR" -> (Task.workspace / "gifs" / "svg_render").toString + "SCALA_CLI_EXAMPLES" -> (BuildCtx.workspaceRoot / "examples").toString, + "SCALA_CLI_GIF_SCENARIOS" -> (BuildCtx.workspaceRoot / "gifs" / "scenarios").toString, + "SCALA_CLI_WEBSITE_IMG" -> (BuildCtx.workspaceRoot / "website" / "static" / "img").toString, + "SCALA_CLI_GIF_RENDERER_DOCKER_DIR" -> (BuildCtx.workspaceRoot / "gifs").toString, + "SCALA_CLI_SVG_RENDERER_DOCKER_DIR" -> (BuildCtx.workspaceRoot / "gifs" / "svg_render").toString ) - override def resources: T[Seq[PathRef]] = Task.Sources { - // Adding markdown directories here, so that they're watched for changes in watch mode - Seq( - PathRef(Task.workspace / "website" / "docs" / "commands"), - PathRef(Task.workspace / "website" / "docs" / "cookbooks") - ) ++ super.resources() + private def customResources: T[Seq[PathRef]] = { + val customPaths: Seq[os.Path] = Seq( + BuildCtx.workspaceRoot / "website" / "docs" / "commands", + BuildCtx.workspaceRoot / "website" / "docs" / "cookbooks" + ) + Task.Sources(customPaths*) } + override def resources: T[Seq[PathRef]] = + // Adding markdown directories here, so that they're watched for changes in watch mode + super.resources() ++ customResources() } } -object packager extends ScalaModule with Bloop.Module { - override def skipBloop = true +object packager extends ScalaModule { override def scalaVersion: T[String] = Scala.scala3Lts - override def ivyDeps: T[Agg[Dep]] = Agg( + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.scalaPackagerCli ) override def mainClass: T[Option[String]] = Some("packager.cli.PackagerCli") @@ -228,13 +231,15 @@ object packager extends ScalaModule with Bloop.Module { object `generate-reference-doc` extends Cross[GenerateReferenceDoc](Scala.scala3MainVersions) with CrossScalaDefaultToInternal -trait GenerateReferenceDoc extends CrossSbtModule with ScalaCliScalafixModule { +trait GenerateReferenceDoc extends CrossSbtModule + with LocatedInModules + with ScalaCliScalafixModule { override def moduleDeps: Seq[JavaModule] = Seq( cli(crossScalaVersion) ) override def repositoriesTask: Task[Seq[Repository]] = Task.Anon(super.repositoriesTask() ++ customRepositories) - override def ivyDeps: T[Agg[Dep]] = Agg( + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.argonautShapeless, Deps.caseApp, Deps.munit @@ -246,45 +251,40 @@ trait GenerateReferenceDoc extends CrossSbtModule with ScalaCliScalafixModule { ) } -object dummy extends Module { +object dummy extends LocatedInModules { // dummy projects to get scala steward updates for Ammonite and scalafmt, whose // versions are used in the fmt and repl commands, and ensure Ammonite is available // for all Scala versions we support. object amm extends Cross[Amm](Scala.listMaxAmmoniteScalaVersion) - trait Amm extends Cross.Module[String] with CrossScalaModule with Bloop.Module { + trait Amm extends Cross.Module[String] with CrossScalaModule { override def crossScalaVersion: String = crossValue - override def skipBloop = true - override def ivyDeps: T[Agg[Dep]] = { + override def mvnDeps: T[Seq[Dep]] = { val ammoniteDep = if (crossValue == Scala.scala3Lts) Deps.ammoniteForScala3Lts else Deps.ammonite - Agg(ammoniteDep) + Seq(ammoniteDep) } } - object scalafmt extends ScalaModule with Bloop.Module { - override def skipBloop = true + object scalafmt extends ScalaModule { override def scalaVersion: T[String] = Scala.defaultInternal - override def ivyDeps: T[Agg[Dep]] = Agg( + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.scalafmtCli ) } - object pythonInterface extends JavaModule with Bloop.Module { - override def skipBloop = true - override def ivyDeps: T[Agg[Dep]] = Agg( + object pythonInterface extends JavaModule { + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.pythonInterface ) } - object scalaPy extends ScalaModule with Bloop.Module { - override def skipBloop = true + object scalaPy extends ScalaModule { override def scalaVersion: T[String] = Scala.defaultInternal - override def ivyDeps: T[Agg[Dep]] = Agg( + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.scalaPy ) } - object scalafix extends ScalaModule with Bloop.Module { - override def skipBloop = true + object scalafix extends ScalaModule { override def scalaVersion: T[String] = Scala.defaultInternal - override def ivyDeps: T[Agg[Dep]] = Agg( + override def mvnDeps: T[Seq[Dep]] = Seq( Deps.scalafixInterfaces ) } @@ -293,11 +293,12 @@ object dummy extends Module { trait BuildMacros extends ScalaCliCrossSbtModule with ScalaCliPublishModule with ScalaCliScalafixModule - with HasTests { + with HasTests + with LocatedInModules { override def crossScalaVersion: String = crossValue - override def compileIvyDeps: T[Agg[Dep]] = Task { - if (crossScalaVersion.startsWith("3")) super.compileIvyDeps() - else super.compileIvyDeps() ++ Agg(Deps.scalaReflect(crossScalaVersion)) + override def compileMvnDeps: T[Seq[Dep]] = Task { + if (crossScalaVersion.startsWith("3")) super.compileMvnDeps() + else super.compileMvnDeps() ++ Seq(Deps.scalaReflect(crossScalaVersion)) } object test extends ScalaCliTests with ScalaCliScalafixModule { @@ -306,7 +307,7 @@ trait BuildMacros extends ScalaCliCrossSbtModule } def testNegativeCompilation(): Command[Unit] = Task.Command(exclusive = true) { - val base = Task.workspace / "modules" / "build-macros" / "src" + val base = BuildCtx.workspaceRoot / "modules" / "build-macros" / "src" val negativeTests = Seq( "MismatchedLeft.scala" -> Seq( "Found: +EE1".r, @@ -333,7 +334,7 @@ trait BuildMacros extends ScalaCliCrossSbtModule ).call( check = false, mergeErrIntoOut = true, - cwd = Task.workspace + cwd = BuildCtx.workspaceRoot ) val compileResult = compile() if (compileResult.exitCode != 0) { @@ -382,7 +383,8 @@ trait ProtoBuildModule extends ScalaCliPublishModule with HasTests trait Core extends ScalaCliCrossSbtModule with ScalaCliPublishModule with HasTests - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def crossScalaVersion: String = crossValue override def moduleDeps: Seq[SonatypeCentralPublishModule] = Seq( @@ -398,7 +400,7 @@ trait Core extends ScalaCliCrossSbtModule override def repositoriesTask: Task[Seq[Repository]] = Task.Anon(super.repositoriesTask() ++ deps.customRepositories) - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.bloopRifle.exclude(("org.scala-lang.modules", "scala-collection-compat_2.13")), Deps.collectionCompat, Deps.coursierJvm @@ -419,7 +421,7 @@ trait Core extends ScalaCliCrossSbtModule Deps.scalaJsLogging, Deps.swoval ) - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Seq( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros ) @@ -583,7 +585,8 @@ trait Core extends ScalaCliCrossSbtModule trait Directives extends ScalaCliCrossSbtModule with ScalaCliPublishModule with HasTests - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def crossScalaVersion: String = crossValue override def moduleDeps: Seq[SonatypeCentralPublishModule] = Seq( options(crossScalaVersion), @@ -595,11 +598,11 @@ trait Directives extends ScalaCliCrossSbtModule super.scalacOptions() ++ asyncScalacOptions(crossScalaVersion) } - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Agg( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros, Deps.svm ) - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( // Deps.asm, Deps.bloopConfig, Deps.jsoniterCore, @@ -611,7 +614,7 @@ trait Directives extends ScalaCliCrossSbtModule Task.Anon(super.repositoriesTask() ++ deps.customRepositories) object test extends ScalaCliTests { - override def ivyDeps: T[Loose.Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.pprint ) override def runClasspath: T[Seq[PathRef]] = Task { @@ -646,17 +649,21 @@ trait Directives extends ScalaCliCrossSbtModule trait Config extends ScalaCliCrossSbtModule with ScalaCliPublishModule - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def crossScalaVersion: String = crossValue override def moduleDeps: Seq[SonatypeCentralPublishModule] = Seq(`specification-level`(crossScalaVersion)) - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg(Deps.jsoniterCore) - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Agg(Deps.jsoniterMacros) + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq(Deps.jsoniterCore) + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq(Deps.jsoniterMacros) override def scalacOptions: T[Seq[String]] = super.scalacOptions() ++ Seq("-deprecation") } -trait Options extends ScalaCliCrossSbtModule with ScalaCliPublishModule with HasTests - with ScalaCliScalafixModule { +trait Options extends ScalaCliCrossSbtModule + with ScalaCliPublishModule + with HasTests + with ScalaCliScalafixModule + with LocatedInModules { override def crossScalaVersion: String = crossValue override def moduleDeps: Seq[SonatypeCentralPublishModule] = Seq( core(crossScalaVersion) @@ -668,11 +675,11 @@ trait Options extends ScalaCliCrossSbtModule with ScalaCliPublishModule with Has super.scalacOptions() ++ asyncScalacOptions(crossScalaVersion) } - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.bloopConfig, Deps.signingCliShared ) - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Seq( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros ) @@ -691,9 +698,10 @@ trait Options extends ScalaCliCrossSbtModule with ScalaCliPublishModule with Has trait Build extends ScalaCliCrossSbtModule with ScalaCliPublishModule with HasTests - with ScalaCliScalafixModule { - override def crossScalaVersion: String = crossValue - override def millSourcePath: os.Path = super.millSourcePath / os.up / "build" + with ScalaCliScalafixModule + with LocatedInModules { + override def crossScalaVersion: String = crossValue + override def moduleDir: os.Path = super.moduleDir / os.up / "build" override def moduleDeps: Seq[SonatypeCentralPublishModule] = Seq( options(crossScalaVersion), directives(crossScalaVersion), @@ -705,11 +713,11 @@ trait Build extends ScalaCliCrossSbtModule super.scalacOptions() ++ asyncScalacOptions(crossScalaVersion) } - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Agg( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros, Deps.svm ) - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.asm, Deps.collectionCompat, Deps.javaClassName, @@ -729,7 +737,7 @@ trait Build extends ScalaCliCrossSbtModule object test extends ScalaCliTests with ScalaCliScalafixModule { override def scalacOptions: T[Seq[String]] = super.scalacOptions() ++ Seq("-deprecation") - override def ivyDeps: T[Loose.Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.pprint, Deps.slf4jNop ) @@ -775,12 +783,14 @@ trait Build extends ScalaCliCrossSbtModule } trait SpecificationLevel extends ScalaCliCrossSbtModule - with ScalaCliPublishModule { + with ScalaCliPublishModule + with LocatedInModules { override def crossScalaVersion: String = crossValue } trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers - with FormatNativeImageConf { + with FormatNativeImageConf + with LocatedInModules { // Copied from Mill: https://github.com/com-lihaoyi/mill/blob/ea367c09bd31a30464ca901cb29863edde5340be/scalalib/src/mill/scalalib/JavaModule.scala#L792 def debug(port: Int, args: Task[Args] = Task.Anon(Args())): Command[Unit] = Task.Command { try mill.api.Result.Success( @@ -878,9 +888,7 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers } PathRef(dir) } - override def resources: T[Seq[PathRef]] = Task.Sources { - super.resources() ++ Seq(defaultFilesResources()) - } + override def resources: T[Seq[PathRef]] = super.resources() ++ Seq(defaultFilesResources()) override def scalacOptions: T[Seq[String]] = Task { super.scalacOptions() ++ asyncScalacOptions(crossScalaVersion) ++ Seq("-deprecation") @@ -897,7 +905,7 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers override def repositoriesTask: Task[Seq[Repository]] = Task.Anon(super.repositoriesTask() ++ customRepositories) - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.caseApp, Deps.coursierLauncher, Deps.coursierProxySetup, @@ -916,16 +924,16 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers Deps.scala3Graal, // TODO: drop this if we ever bump internal JDK to 24+ Deps.scala3GraalProcessor // TODO: drop this if we ever bump internal JDK to 24+ ) - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Agg( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros, Deps.svm ) override def mainClass: T[Option[String]] = Some("scala.cli.ScalaCli") - private def scala3GraalProcessorClassPath: T[Agg[PathRef]] = T { - resolveDeps(T { + private def scala3GraalProcessorClassPath: T[Seq[PathRef]] = Task { + resolveDeps(Task { val bind = bindDependency() - Agg(Deps.scala3GraalProcessor).map(bind) + Seq(Deps.scala3GraalProcessor).map(bind) })() } @@ -953,7 +961,7 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers super.runClasspath() ++ Seq(localRepoJar()) } - override def compileIvyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def compileMvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.jsoniterMacros ) @@ -964,8 +972,11 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers } } -trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests - with ScalaCliScalafixModule { +trait CliIntegration extends SbtModule + with ScalaCliPublishModule + with HasTests + with ScalaCliScalafixModule + with LocatedInModules { override def scalaVersion: T[String] = sv def sv: String = Scala.scala3Lts @@ -977,12 +988,12 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests super.scalacOptions() ++ Seq("-deprecation") } - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.osLib ) trait IntegrationScalaTests extends super.ScalaCliTests with ScalaCliScalafixModule { - override def ivyDeps: T[Loose.Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.bsp4j, Deps.coursier .exclude(("com.github.plokhotnyuk.jsoniter-scala", "jsoniter-scala-macros")), @@ -993,7 +1004,7 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests Deps.slf4jNop, Deps.usingDirectives ) - override def compileIvyDeps: T[Agg[Dep]] = super.compileIvyDeps() ++ Seq( + override def compileMvnDeps: T[Seq[Dep]] = super.compileMvnDeps() ++ Seq( Deps.jsoniterMacros ) override def forkEnv: T[Map[String, String]] = super.forkEnv() ++ Seq( @@ -1006,7 +1017,7 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests val dir = Task.dest / "constants" val dest = dir / "Constants.scala" val mostlyStaticDockerfile = - Task.workspace / ".github" / "scripts" / "docker" / "ScalaCliSlimDockerFile" + BuildCtx.workspaceRoot / ".github" / "scripts" / "docker" / "ScalaCliSlimDockerFile" assert( os.exists(mostlyStaticDockerfile), s"Error: $mostlyStaticDockerfile not found" @@ -1099,48 +1110,61 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests override def generatedSources: T[Seq[PathRef]] = super.generatedSources() ++ Seq(constantsFile()) - override def test(args: String*): Command[(String, Seq[TestResult])] = jvm(args: _*) + override def testForked(args: String*): Command[(msg: String, results: Seq[TestResult])] = + jvm(args*) def forcedLauncher: T[PathRef] = Task(persistent = true) { val ext = if (Properties.isWin) ".exe" else "" val launcher = Task.dest / s"scala-cli$ext" - if (!os.exists(launcher)) { - val dir = Option(System.getenv("SCALA_CLI_IT_FORCED_LAUNCHER_DIRECTORY")).getOrElse { - sys.error("SCALA_CLI_IT_FORCED_LAUNCHER_DIRECTORY not set") + if !os.exists(launcher) then + BuildCtx.withFilesystemCheckerDisabled { + val dir = Option(System.getenv("SCALA_CLI_IT_FORCED_LAUNCHER_DIRECTORY")).getOrElse { + sys.error("SCALA_CLI_IT_FORCED_LAUNCHER_DIRECTORY not set") + } + System.err.println(s"SCALA_CLI_IT_FORCED_LAUNCHER_DIRECTORY was set to $dir") + val content = importedLauncher(dir, BuildCtx.workspaceRoot) + System.err.println(s"writing launcher to $launcher") + os.write( + launcher, + content, + createFolders = true, + perms = if (Properties.isWin) null else "rwxr-xr-x" + ) } - val content = importedLauncher(dir, Task.workspace) - os.write( - launcher, - content, - createFolders = true, - perms = if (Properties.isWin) null else "rwxr-xr-x" - ) - } PathRef(launcher) } def forcedStaticLauncher: T[PathRef] = Task(persistent = true) { val launcher = Task.dest / "scala-cli" - if (!os.exists(launcher)) { - val dir = Option(System.getenv("SCALA_CLI_IT_FORCED_STATIC_LAUNCHER_DIRECTORY")).getOrElse { - sys.error("SCALA_CLI_IT_FORCED_STATIC_LAUNCHER_DIRECTORY not set") + if !os.exists(launcher) then + BuildCtx.withFilesystemCheckerDisabled { + val dir = + Option(System.getenv("SCALA_CLI_IT_FORCED_STATIC_LAUNCHER_DIRECTORY")).getOrElse { + sys.error("SCALA_CLI_IT_FORCED_STATIC_LAUNCHER_DIRECTORY not set") + } + System.err.println(s"SCALA_CLI_IT_FORCED_STATIC_LAUNCHER_DIRECTORY was set to $dir") + val content = importedLauncher(dir, BuildCtx.workspaceRoot) + System.err.println(s"writing launcher to $launcher") + os.write(launcher, content, createFolders = true) } - val content = importedLauncher(dir, Task.workspace) - os.write(launcher, content, createFolders = true) - } PathRef(launcher) } def forcedMostlyStaticLauncher: T[PathRef] = Task(persistent = true) { val launcher = Task.dest / "scala-cli" - if (!os.exists(launcher)) { - val dir = - Option(System.getenv("SCALA_CLI_IT_FORCED_MOSTLY_STATIC_LAUNCHER_DIRECTORY")).getOrElse { - sys.error("SCALA_CLI_IT_FORCED_MOSTLY_STATIC_LAUNCHER_DIRECTORY not set") - } - val content = importedLauncher(dir, Task.workspace) - os.write(launcher, content, createFolders = true) - } + if !os.exists(launcher) then + BuildCtx.withFilesystemCheckerDisabled { + val dir = + Option(System.getenv("SCALA_CLI_IT_FORCED_MOSTLY_STATIC_LAUNCHER_DIRECTORY")).getOrElse { + sys.error("SCALA_CLI_IT_FORCED_MOSTLY_STATIC_LAUNCHER_DIRECTORY not set") + } + System.err.println( + s"SCALA_CLI_IT_FORCED_MOSTLY_STATIC_LAUNCHER_DIRECTORY was set to $dir" + ) + val content = importedLauncher(dir, BuildCtx.workspaceRoot) + System.err.println(s"writing launcher to $launcher") + os.write(launcher, content, createFolders = true) + } PathRef(launcher) } @@ -1193,58 +1217,66 @@ trait CliIntegration extends SbtModule with ScalaCliPublishModule with HasTests private def testArgs(args: Seq[String], launcher: os.Path, cliKind: String): Seq[String] = extraTestArgs(launcher, cliKind) ++ debugTestArgs(args) - def jvm(args: String*): Command[(String, Seq[TestResult])] = Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.jvm().path, "jvm")), - Task.Anon(Seq.empty[String]) - )() - } - def jvmBootstrapped(args: String*): Command[(String, Seq[TestResult])] = Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.jvmBootstrapped().path, "jvmBootstrapped")), - Task.Anon(Seq.empty[String]) - )() - } - def native(args: String*): Command[(String, Seq[TestResult])] = Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.native().path, "native")), - Task.Anon(Seq.empty[String]) - )() - } - def nativeStatic(args: String*): Command[(String, Seq[TestResult])] = Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.nativeStatic().path, "native-static")), - Task.Anon(Seq.empty[String]) - )() - } - def nativeMostlyStatic(args: String*): Command[(String, Seq[TestResult])] = Task.Command { - testTask( - Task.Anon(args ++ testArgs( - args, - Launchers.nativeMostlyStatic().path, - "native-mostly-static" - )), - Task.Anon(Seq.empty[String]) - )() - } + def jvm(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.jvm().path, "jvm")), + Task.Anon(Seq.empty[String]) + )() + } + + def jvmBootstrapped(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.jvmBootstrapped().path, "jvmBootstrapped")), + Task.Anon(Seq.empty[String]) + )() + } + + def native(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.native().path, "native")), + Task.Anon(Seq.empty[String]) + )() + } + + def nativeStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.nativeStatic().path, "native-static")), + Task.Anon(Seq.empty[String]) + )() + } + + def nativeMostlyStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { + testTask( + Task.Anon(args ++ testArgs( + args, + Launchers.nativeMostlyStatic().path, + "native-mostly-static" + )), + Task.Anon(Seq.empty[String]) + )() + } } } trait CliIntegrationDocker extends SbtModule with ScalaCliPublishModule with HasTests { override def scalaVersion: T[String] = Scala.scala3Lts - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.osLib ) } trait Runner extends CrossSbtModule with ScalaCliPublishModule - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def scalacOptions: T[Seq[String]] = Task { super.scalacOptions() ++ Seq("-deprecation") } override def mainClass: T[Option[String]] = Some("scala.cli.runner.Runner") - override def sources: T[Seq[PathRef]] = Task.Sources { + override def sources: T[Seq[PathRef]] = { val scala3DirName = if (crossScalaVersion.contains("-RC")) "scala-3-unstable" else "scala-3-stable" val extraDirs = Seq(PathRef(moduleDir / "src" / "main" / scala3DirName)) @@ -1254,11 +1286,12 @@ trait Runner extends CrossSbtModule trait TestRunner extends CrossSbtModule with ScalaCliPublishModule - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def scalacOptions: T[Seq[String]] = Task { super.scalacOptions() ++ Seq("-deprecation") } - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.asm, Deps.collectionCompat, Deps.testInterface @@ -1268,7 +1301,8 @@ trait TestRunner extends CrossSbtModule trait TastyLib extends ScalaCliCrossSbtModule with ScalaCliPublishModule - with ScalaCliScalafixModule { + with ScalaCliScalafixModule + with LocatedInModules { override def crossScalaVersion: String = crossValue def constantsFile: T[PathRef] = Task(persistent = true) { val dir = Task.dest / "constants" @@ -1305,7 +1339,7 @@ object `local-repo` extends LocalRepo { } // Helper CI commands -def publishSonatype(tasks: mill.main.Tasks[PublishModule.PublishData]) = Task.Command { +def publishSonatype(tasks: Tasks[PublishModule.PublishData]) = Task.Command { val taskNames = tasks.value.map(_.toString()) System.err.println( s"""Tasks producing artifacts to be included in the bundle: @@ -1316,16 +1350,16 @@ def publishSonatype(tasks: mill.main.Tasks[PublishModule.PublishData]) = Task.Co val bundleName = s"$organization-$ghName-$pv" System.err.println(s"Publishing bundle: $bundleName") publish.publishSonatype( - data = define.Task.sequence(tasks.value)(), + data = Task.sequence(tasks.value)(), log = Task.ctx().log, - workspace = Task.workspace, + workspace = BuildCtx.workspaceRoot, env = Task.env, bundleName = bundleName ) } -def copyTo(task: mill.main.Tasks[PathRef], dest: String): Command[Unit] = Task.Command { - val destPath = os.Path(dest, Task.workspace) +def copyTo(task: Tasks[PathRef], dest: String): Command[Unit] = Task.Command { + val destPath = os.Path(dest, BuildCtx.workspaceRoot) if (task.value.length > 1) sys.error("Expected a single task") val ref = task.value.head() @@ -1334,7 +1368,7 @@ def copyTo(task: mill.main.Tasks[PathRef], dest: String): Command[Unit] = Task.C } def writePackageVersionTo(dest: String): Command[Unit] = Task.Command { - val destPath = os.Path(dest, Task.workspace) + val destPath = os.Path(dest, BuildCtx.workspaceRoot) val rawVersion = cli(Scala.defaultInternal).publishVersion() val version = if (rawVersion.contains("+")) rawVersion.stripSuffix("-SNAPSHOT") @@ -1343,7 +1377,7 @@ def writePackageVersionTo(dest: String): Command[Unit] = Task.Command { } def writeShortPackageVersionTo(dest: String): Command[Unit] = Task.Command { - val destPath = os.Path(dest, Task.workspace) + val destPath = os.Path(dest, BuildCtx.workspaceRoot) val rawVersion = cli(Scala.defaultInternal).publishVersion() val version = rawVersion.takeWhile(c => c != '-' && c != '+') os.write.over(destPath, version) @@ -1384,7 +1418,7 @@ def copyLauncher(directory: String = "artifacts"): Command[os.Path] = Task.Comma directory = directory, name = "scala-cli", compress = true, - workspace = Task.workspace + workspace = BuildCtx.workspaceRoot ) } @@ -1392,7 +1426,7 @@ def copyJvmLauncher(directory: String = "artifacts"): Command[Unit] = Task.Comma val launcher = cli(Scala.defaultInternal).standaloneLauncher().path os.copy( launcher, - os.Path(directory, Task.workspace) / s"scala-cli$platformExecutableJarExtension", + os.Path(directory, BuildCtx.workspaceRoot) / s"scala-cli$platformExecutableJarExtension", createFolders = true, replaceExisting = true ) @@ -1401,7 +1435,7 @@ def copyJvmBootstrappedLauncher(directory: String = "artifacts"): Command[Unit] val launcher = cliBootstrapped.jar().path os.copy( launcher, - os.Path(directory, Task.workspace) / s"scala-cli.jar", + os.Path(directory, BuildCtx.workspaceRoot) / s"scala-cli.jar", createFolders = true, replaceExisting = true ) @@ -1410,7 +1444,7 @@ def copyJvmBootstrappedLauncher(directory: String = "artifacts"): Command[Unit] def uploadLaunchers(directory: String = "artifacts"): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val path = os.Path(directory, Task.workspace) + val path = os.Path(directory, BuildCtx.workspaceRoot) val launchers = os.list(path).filter(os.isFile(_)).map { path => path -> path.last } @@ -1419,16 +1453,16 @@ def uploadLaunchers(directory: String = "artifacts"): Command[Unit] = Task.Comma else ("v" + version, false) System.err.println(s"Uploading to tag $tag (overwrite assets: $overwriteAssets)") Upload.upload(ghOrg, ghName, ghToken(), tag, dryRun = false, overwrite = overwriteAssets)( - launchers: _* + launchers* ) } -def unitTests(): Command[(String, Seq[TestResult])] = Task.Command { - `build-module`(Scala.defaultInternal).test.test()() - `build-macros`(Scala.defaultInternal).test.test()() - cli(Scala.defaultInternal).test.test()() - directives(Scala.defaultInternal).test.test()() - options(Scala.defaultInternal).test.test()() +def unitTests(): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + `build-module`(Scala.defaultInternal).test.testForked()() + `build-macros`(Scala.defaultInternal).test.testForked()() + cli(Scala.defaultInternal).test.testForked()() + directives(Scala.defaultInternal).test.testForked()() + options(Scala.defaultInternal).test.testForked()() } def scala(args: Task[Args] = Task.Anon(Args())) = Task.Command { @@ -1444,10 +1478,8 @@ def defaultNativeImage(): Command[PathRef] = cli(Scala.defaultInternal).nativeImage() } -def nativeIntegrationTests(): Command[(String, Seq[TestResult])] = - Task.Command { - integration.test.native()() - } +def nativeIntegrationTests(): Command[(msg: String, results: Seq[TestResult])] = + integration.test.native() def copyDefaultLauncher(directory: String = "artifacts"): Command[os.Path] = Task.Command { @@ -1461,7 +1493,7 @@ def copyMostlyStaticLauncher(directory: String = "artifacts"): Command[os.Path] directory = directory, name = "scala-cli", compress = true, - workspace = Task.workspace, + workspace = BuildCtx.workspaceRoot, suffix = "-mostly-static" ) } @@ -1473,7 +1505,7 @@ def copyStaticLauncher(directory: String = "artifacts"): Command[os.Path] = Task directory = directory, name = "scala-cli", compress = true, - workspace = Task.workspace, + workspace = BuildCtx.workspaceRoot, suffix = "-static" ) } @@ -1516,7 +1548,7 @@ object ci extends Module { def updateScalaCliSetup(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target-scala-cli-setup" + val targetDir = BuildCtx.workspaceRoot / "target-scala-cli-setup" val mainDir = targetDir / "scala-cli-setup" val setupScriptPath = mainDir / "src" / "main.ts" @@ -1545,7 +1577,7 @@ object ci extends Module { def updateStandaloneLauncher(): Command[os.CommandResult] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val scalaCliDir = targetDir / "scala-cli" val standaloneLauncherPath = scalaCliDir / "scala-cli.sh" val standaloneWindowsLauncherPath = scalaCliDir / "scala-cli.bat" @@ -1614,7 +1646,7 @@ object ci extends Module { def updateScalaCliBrewFormula(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val homebrewFormulaDir = targetDir / "homebrew-scala-cli" // clean target directory @@ -1634,13 +1666,15 @@ object ci extends Module { val arm64LauncherURL = s"https://github.com/Virtuslab/scala-cli/releases/download/v$version/scala-cli-aarch64-apple-darwin.gz" - val x86LauncherPath = os.Path("artifacts", Task.workspace) / "scala-cli-x86_64-apple-darwin.gz" + val x86LauncherPath = + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-x86_64-apple-darwin.gz" val arm64LauncherPath = - os.Path("artifacts", Task.workspace) / "scala-cli-aarch64-apple-darwin.gz" + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-aarch64-apple-darwin.gz" val (x86Sha256, arm64Sha256) = brewLaunchersSha(x86LauncherPath, arm64LauncherPath, targetDir) - val templateFormulaPath = Task.workspace / ".github" / "scripts" / "scala-cli.rb.template" - val template = os.read(templateFormulaPath) + val templateFormulaPath = + BuildCtx.workspaceRoot / ".github" / "scripts" / "scala-cli.rb.template" + val template = os.read(templateFormulaPath) val updatedFormula = template .replace("@X86_LAUNCHER_URL@", x86LauncherURL) @@ -1657,7 +1691,7 @@ object ci extends Module { def updateScalaExperimentalBrewFormula(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val homebrewFormulaDir = targetDir / "homebrew-scala-experimental" // clean homebrew-scala-experimental directory @@ -1677,12 +1711,13 @@ object ci extends Module { val arm64LauncherURL = s"https://github.com/Virtuslab/scala-cli/releases/download/v$version/scala-cli-aarch64-apple-darwin.gz" - val x86LauncherPath = os.Path("artifacts", Task.workspace) / "scala-cli-x86_64-apple-darwin.gz" + val x86LauncherPath = + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-x86_64-apple-darwin.gz" val arm64LauncherPath = - os.Path("artifacts", Task.workspace) / "scala-cli-aarch64-apple-darwin.gz" + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-aarch64-apple-darwin.gz" val (x86Sha256, arm64Sha256) = brewLaunchersSha(x86LauncherPath, arm64LauncherPath, targetDir) - val templateFormulaPath = Task.workspace / ".github" / "scripts" / "scala.rb.template" + val templateFormulaPath = BuildCtx.workspaceRoot / ".github" / "scripts" / "scala.rb.template" val template = os.read(templateFormulaPath) val updatedFormula = template @@ -1700,7 +1735,7 @@ object ci extends Module { def updateInstallationScript(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val packagesDir = targetDir / "scala-cli-packages" val installationScriptPath = packagesDir / "scala-setup.sh" @@ -1727,7 +1762,7 @@ object ci extends Module { def updateDebianPackages(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val packagesDir = targetDir / "scala-cli-packages" val debianDir = packagesDir / "debian" @@ -1745,7 +1780,7 @@ object ci extends Module { // copy deb package to repository os.copy( - os.Path("artifacts", Task.workspace) / "scala-cli-x86_64-pc-linux.deb", + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-x86_64-pc-linux.deb", debianDir / s"scala-cli_$version.deb" ) @@ -1795,12 +1830,12 @@ object ci extends Module { def updateChocolateyPackage(): Command[os.CommandResult] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val packagesDir = Task.workspace / "target" / "scala-cli-packages" - val chocoDir = Task.workspace / ".github" / "scripts" / "choco" + val packagesDir = BuildCtx.workspaceRoot / "target" / "scala-cli-packages" + val chocoDir = BuildCtx.workspaceRoot / ".github" / "scripts" / "choco" val msiPackagePath = packagesDir / s"scala-cli_$version.msi" os.copy( - Task.workspace / "artifacts" / "scala-cli-x86_64-pc-win32.msi", + BuildCtx.workspaceRoot / "artifacts" / "scala-cli-x86_64-pc-win32.msi", msiPackagePath, createFolders = true ) @@ -1839,7 +1874,7 @@ object ci extends Module { def updateCentOsPackages(): Command[Unit] = Task.Command { val version = cli(Scala.defaultInternal).publishVersion() - val targetDir = Task.workspace / "target" + val targetDir = BuildCtx.workspaceRoot / "target" val packagesDir = targetDir / "scala-cli-packages" val centOsDir = packagesDir / "CentOS" @@ -1857,7 +1892,7 @@ object ci extends Module { // copy rpm package to repository os.copy( - os.Path("artifacts", Task.workspace) / "scala-cli-x86_64-pc-linux.rpm", + os.Path("artifacts", BuildCtx.workspaceRoot) / "scala-cli-x86_64-pc-linux.rpm", centOsDir / "Packages" / s"scala-cli_$version.rpm" ) @@ -1928,7 +1963,7 @@ object ci extends Module { .mkString(System.lineSeparator()) ) } - val destDir = os.Path(directory, Task.workspace) + val destDir = os.Path(directory, BuildCtx.workspaceRoot) os.copy(orig, destDir / distName, createFolders = true, replaceExisting = true) } def writeWixConfigExtra(dest: String = "wix-visual-cpp-redist.xml"): Command[Unit] = @@ -1972,7 +2007,7 @@ object ci extends Module { | | |""".stripMargin - val dest0 = os.Path(dest, Task.workspace) + val dest0 = os.Path(dest, BuildCtx.workspaceRoot) os.write.over(dest0, content.getBytes(Charset.defaultCharset()), createFolders = true) } def setShouldPublish(): Command[Unit] = publish.setShouldPublish() @@ -1991,9 +2026,9 @@ object ci extends Module { "--ttl", "0" ) - val baseJavaHome = os.Path(command.!!.trim, Task.workspace) + val baseJavaHome = os.Path(command.!!.trim, BuildCtx.workspaceRoot) System.err.println(s"Initial Java home $baseJavaHome") - val destJavaHome = os.Path(dest, Task.workspace) + val destJavaHome = os.Path(dest, BuildCtx.workspaceRoot) os.copy(baseJavaHome, destJavaHome, createFolders = true) System.err.println(s"New Java home $destJavaHome") destJavaHome @@ -2001,10 +2036,10 @@ object ci extends Module { def checkScalaVersions(): Command[Unit] = Task.Command { website.checkMainScalaVersions( - Task.workspace / "website" / "docs" / "reference" / "scala-versions.md" + BuildCtx.workspaceRoot / "website" / "docs" / "reference" / "scala-versions.md" ) website.checkScalaJsVersions( - Task.workspace / "website" / "docs" / "guides" / "advanced" / "scala-js.md" + BuildCtx.workspaceRoot / "website" / "docs" / "guides" / "advanced" / "scala-js.md" ) } } diff --git a/mill b/mill index edc610a8f8..1c03f803e2 100755 --- a/mill +++ b/mill @@ -84,7 +84,7 @@ export USE_NATIVE_IMAGE_JAVA_PLATFORM_MODULE_SYSTEM=false DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" &> /dev/null && pwd)" if [[ $IS_WINDOWS ]]; then - exec "$DIR/millw" "$@" + exec "$DIR/mill.bat" "$@" else exec "$DIR/millw" $COMMAND fi diff --git a/project/deps/package.mill.scala b/project/deps/package.mill.scala index 183a94bff9..d3df27477e 100644 --- a/project/deps/package.mill.scala +++ b/project/deps/package.mill.scala @@ -1,7 +1,8 @@ package build.project.deps -import coursier.version.Version -import mill._ -import scalalib._ + +import mill.* +import mill.api.BuildInfo +import scalalib.* object Cli { def runnerScala30LegacyVersion = @@ -115,7 +116,7 @@ object TestDeps { object InternalDeps { object Versions { - def mill: String = _root_.mill.main.BuildInfo.millVersion + def mill: String = BuildInfo.millVersion def lefouMillwRef = "166bcdf5741de8569e0630e18c3b2ef7e252cd96" } } diff --git a/project/publish/package.mill.scala b/project/publish/package.mill.scala index 0151871a20..19bbd20dcd 100644 --- a/project/publish/package.mill.scala +++ b/project/publish/package.mill.scala @@ -1,20 +1,20 @@ package build.project.publish -import $ivy.`de.tototec::de.tobiasroeser.mill.vcs.version::0.4.0` -import $ivy.`org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r` + import build.project.settings import com.lumidion.sonatype.central.client.core.{PublishingType, SonatypeCredentials} import settings.{PublishLocalNoFluff, workspaceDirName} -import de.tobiasroeser.mill.vcs.version._ -import mill._ +import mill.* import mill.javalib.publish.Artifact -import scalalib._ +import mill.util.{Tasks, VcsVersion} +import scalalib.* import org.eclipse.jgit.api.Git +import mill.api.{BuildCtx, ModuleCtx, Task} import java.nio.charset.Charset -import scala.concurrent.duration._ -import scala.jdk.CollectionConverters._ +import scala.concurrent.duration.* +import scala.jdk.CollectionConverters.* -lazy val (ghOrg: String, ghName: String) = { +def gh: (ghOrg: String, ghName: String) = { def default = ("VirtusLab", "scala-cli") val isCI = System.getenv("CI") != null if (isCI) { @@ -62,7 +62,9 @@ lazy val (ghOrg: String, ghName: String) = { default } -private def computePublishVersion(state: VcsState, simple: Boolean): String = +lazy val (ghOrg: String, ghName: String) = gh + +private def computePublishVersion(state: VcsVersion.State, simple: Boolean): String = if (state.commitsSinceLastTag > 0) if (simple) { val versionOrEmpty = state.lastTag @@ -140,16 +142,20 @@ trait ScalaCliPublishModule extends SonatypeCentralPublishModule with PublishLoc ) ) override def publishVersion: T[String] = finalPublishVersion() - override def sourceJar: T[PathRef] = Task { - import mill.util.Jvm.createJar - val allSources0 = allSources().map(_.path).filter(os.exists).toSet - createJar( - allSources0 ++ resources().map(_.path).filter(os.exists), - manifest(), - (input, relPath) => - !allSources0(input) || - (!relPath.segments.contains(".scala") && !relPath.segments.contains(workspaceDirName)) - ) + + override def sourceJar: T[PathRef] = Task { + PathRef { + import mill.util.Jvm.createJar + val allSources0 = allSources().map(_.path).filter(os.exists) + createJar( + jar = Task.dest / "out.jar", + inputPaths = allSources0 ++ resources().map(_.path).filter(os.exists), + manifest = manifest(), + fileFilter = (input, relPath) => + !allSources0.toSet(input) || + (!relPath.segments.contains(".scala") && !relPath.segments.contains(workspaceDirName)) + ) + } } } @@ -211,7 +217,7 @@ def publishSonatype( publisher.publishAll( publishingType = publishingType, singleBundleName = finalBundleName, - artifacts = artifacts: _* + artifacts = artifacts* ) } @@ -235,7 +241,7 @@ def setShouldPublish() = Task.Command { val charSet = Charset.defaultCharset() val nl = System.lineSeparator() os.write.append( - os.Path(envFile, Task.workspace), + os.Path(envFile, BuildCtx.workspaceRoot), s"SHOULD_PUBLISH=${shouldPublish()}$nl".getBytes(charSet) ) } diff --git a/project/settings/package.mill.scala b/project/settings/package.mill.scala index 1b0941638e..d1d3d29126 100644 --- a/project/settings/package.mill.scala +++ b/project/settings/package.mill.scala @@ -1,6 +1,5 @@ package build.project.settings -import $ivy.`com.goyeau::mill-scalafix::0.5.1` -import $ivy.`io.github.alexarchambault.mill::mill-native-image::0.1.31-1` + import build.project.deps import deps.{ Deps, @@ -14,18 +13,18 @@ import build.project.utils import utils.isArmArchitecture import com.goyeau.mill.scalafix.ScalafixModule import coursier.Repository -import de.tobiasroeser.mill.vcs.version.{VcsState, VcsVersion} import io.github.alexarchambault.millnativeimage.NativeImage -import mill._ -import mill.api.Loose -import mill.scalalib._ -import upickle.default._ +import mill.* +import mill.scalalib.* +import upickle.default.* import java.io.File import java.nio.charset.StandardCharsets import java.util.Locale import scala.annotation.unused import scala.util.Properties +import mill.util.{Tasks, VcsVersion} +import mill.api.{BuildCtx, Task} private def isCI = System.getenv("CI") != null @@ -100,12 +99,12 @@ def cs: T[String] = Task(persistent = true) { val archiveCache = coursier.cache.ArchiveCache().withCache(cache) val task = cache.logger.using(archiveCache.get(coursier.util.Artifact(url))) val maybeFile = - try task.unsafeRun()(cache.ec) + try task.unsafeRun()(using cache.ec) catch { case t: Throwable => throw new Exception(s"Error getting and extracting $url", t) } - val f = maybeFile.fold(ex => throw new Exception(ex), os.Path(_, Task.workspace)) + val f = maybeFile.fold(ex => throw new Exception(ex), os.Path(_, BuildCtx.workspaceRoot)) val exec = if (Properties.isWin && os.isDir(f) && f.last.endsWith(".zip")) os.list(f).find(_.last.endsWith(".exe")).getOrElse( @@ -142,6 +141,13 @@ def platformSuffix: String = { def localRepoResourcePath = "local-repo.zip" +trait LocatedInModules extends Module { + override def moduleDir: os.Path = { + val oldModuleDir: os.Path = super.moduleDir + oldModuleDir / os.up / "modules" / oldModuleDir.last + } +} + trait CliLaunchers extends SbtModule { self => def launcherTypeResourcePath: os.RelPath = @@ -149,6 +155,7 @@ trait CliLaunchers extends SbtModule { self => def defaultFilesResourcePath: os.RelPath = os.rel / "scala" / "cli" / "commands" / "publish" trait CliNativeImage extends NativeImage { + override def generateNativeImageWithFileSystemChecker: Boolean = false def writeDefaultNativeImageScript(scriptDest: String): Command[Unit] = Task.Command(super.writeNativeImageScript(scriptDest, "")()) @@ -278,14 +285,14 @@ trait CliLaunchers extends SbtModule { self => os.makeDir.all(dir) if (Properties.isWin) { - copyLibsodiumStaticTo(cs(), dir, Task.workspace) - copyLibsodiumjniTo(cs(), dir, Task.workspace) - copyCsjniutilTo(cs(), dir, Task.workspace) + copyLibsodiumStaticTo(cs(), dir, BuildCtx.workspaceRoot) + copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) + copyCsjniutilTo(cs(), dir, BuildCtx.workspaceRoot) } if (launcherKind == "static") { - copyAlpineLibsodiumTo(cs(), dir, Task.workspace) - copyLibsodiumjniTo(cs(), dir, Task.workspace) + copyAlpineLibsodiumTo(cs(), dir, BuildCtx.workspaceRoot) + copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) } PathRef(dir) @@ -354,7 +361,7 @@ trait CliLaunchers extends SbtModule { self => } def buildHelperImage: T[Unit] = Task { os.proc("docker", "build", "-t", Docker.customMuslBuilderImageName, ".") - .call(cwd = Task.workspace / "project" / "musl-image", stdout = os.Inherit) + .call(cwd = BuildCtx.workspaceRoot / "project" / "musl-image", stdout = os.Inherit) () } override def writeDefaultNativeImageScript(scriptDest: String): Command[Unit] = @@ -382,7 +389,7 @@ trait CliLaunchers extends SbtModule { self => mainClass().getOrElse(sys.error("Don't know what main class to use")) } - def transitiveJarsAgg: T[Agg[PathRef]] = { + def transitiveJarsSeq: T[Seq[PathRef]] = { def allModuleDeps(todo: List[JavaModule]): List[JavaModule] = todo match { case Nil => Nil @@ -391,7 +398,7 @@ trait CliLaunchers extends SbtModule { self => } Task { - mill.define.Task.traverse(allModuleDeps(this :: Nil).distinct)(m => + Task.traverse(allModuleDeps(this :: Nil).distinct)(m => Task.Anon(m.jar()) )() } @@ -428,11 +435,13 @@ trait CliLaunchers extends SbtModule { self => cp, mainClass0 ) ++ args - os.proc(command.map(x => x: os.Shellable): _*).call( + os.proc(command.map(x => x: os.Shellable)*).call( stdin = os.Inherit, stdout = os.Inherit ) - Task.log.streams.out.println(s"Config generated in ${outputDir.relativeTo(Task.workspace)}") + Task.log.streams.out.println( + s"Config generated in ${outputDir.relativeTo(BuildCtx.workspaceRoot)}" + ) } @unused @@ -440,7 +449,7 @@ trait CliLaunchers extends SbtModule { self => val cp = jarClassPath().map(_.path).mkString(File.pathSeparator) val mainClass0 = mainClass().getOrElse(sys.error("No main class")) val command = Seq("java", "-cp", cp, mainClass0) ++ args - os.proc(command.map(x => x: os.Shellable): _*).call( + os.proc(command.map(x => x: os.Shellable)*).call( stdin = os.Inherit, stdout = os.Inherit ) @@ -451,7 +460,7 @@ trait CliLaunchers extends SbtModule { self => } def jarClassPath: T[Seq[PathRef]] = Task { - val cp = runClasspath() ++ transitiveJarsAgg() + val cp = runClasspath() ++ transitiveJarsSeq() cp.filter(ref => os.exists(ref.path) && !os.isDir(ref.path)) } @@ -479,7 +488,7 @@ trait CliLaunchers extends SbtModule { self => } def standaloneLauncher: T[PathRef] = Task { - val cachePath = os.Path(coursier.cache.FileCache().location, Task.workspace) + val cachePath = os.Path(coursier.cache.FileCache().location, BuildCtx.workspaceRoot) def urlOf(path: os.Path): Option[String] = if (path.startsWith(cachePath)) { val segments = path.relativeTo(cachePath).segments @@ -531,7 +540,7 @@ trait HasTests extends SbtModule { super.scalacOptions() ++ extraOptions } trait ScalaCliTests extends ScalaCliModule with super.SbtTests with TestModule.Munit { - override def ivyDeps: T[Agg[Dep]] = super.ivyDeps() ++ Agg( + override def mvnDeps: T[Seq[Dep]] = super.mvnDeps() ++ Seq( Deps.expecty, Deps.munit ) @@ -558,13 +567,16 @@ trait PublishLocalNoFluff extends SonatypeCentralPublishModule { } // adapted from https://github.com/com-lihaoyi/mill/blob/fea79f0515dda1def83500f0f49993e93338c3de/scalalib/src/PublishModule.scala#L70-L85 // writes empty zips as source and doc JARs - def publishLocalNoFluff(localIvyRepo: String = null): define.Command[PathRef] = Task.Command { + def publishLocalNoFluff(localIvyRepo: String = null): Command[PathRef] = Task.Command { import mill.scalalib.publish.LocalIvyPublisher val publisher = localIvyRepo match { case null => LocalIvyPublisher case repo => - new LocalIvyPublisher(os.Path(repo.replace("{VERSION}", publishVersion()), Task.workspace)) + new LocalIvyPublisher(os.Path( + repo.replace("{VERSION}", publishVersion()), + BuildCtx.workspaceRoot + )) } publisher.publishLocal( @@ -572,9 +584,9 @@ trait PublishLocalNoFluff extends SonatypeCentralPublishModule { sourcesJar = emptyZip().path, docJar = emptyZip().path, pom = pom().path, - ivy = ivy().path, + ivy = Right(ivy().path), artifact = artifactMetadata(), - extras = extraPublish() + publishInfos = Seq.empty ) jar() @@ -585,13 +597,13 @@ trait LocalRepo extends Module { def stubsModules: Seq[PublishLocalNoFluff] def version: T[String] - def localRepo: T[Seq[PathRef]] = Task { - val repoRoot = os.rel / "out" / "repo" / "{VERSION}" - val tasks = stubsModules.map(_.publishLocalNoFluff(repoRoot.toString)) - define.Task.sequence(tasks) + def localRepo: T[Seq[PathRef]] = { + val repoRoot = os.rel / "out" / "repo" / "{VERSION}" + val tasks: Seq[Command[PathRef]] = stubsModules.map(_.publishLocalNoFluff(repoRoot.toString)) + Task.sequence(tasks)() } - private def vcsState: T[VcsState] = + private def vcsState: T[VcsVersion.State] = if (isCI) Task(persistent = true) { VcsVersion.vcsState() @@ -604,7 +616,7 @@ trait LocalRepo extends Module { val repoVer = vcsState().format() val ver = version() localRepo() - val repoDir = Task.workspace / "out" / "repo" / ver + val repoDir = BuildCtx.workspaceRoot / "out" / "repo" / ver val destDir = Task.dest / ver / "repo.zip" val dest = destDir / "repo.zip" @@ -702,12 +714,12 @@ private def doFormatNativeImageConf(dir: os.Path, format: Boolean): List[os.Path entries -= "methods" ujson.Obj(entries) } - ujson.Arr(values: _*) + ujson.Arr(values*) } else if (sortByName(name)) json.arrOpt.fold(json) { arr => val values = arr.toVector.sortBy(_("name").str) - ujson.Arr(values: _*) + ujson.Arr(values*) } else json @@ -749,7 +761,7 @@ trait FormatNativeImageConf extends JavaModule { for (f <- needsFormatting) System.err.println( s" ${ - if (f.startsWith(Task.workspace)) f.relativeTo(Task.workspace).toString + if (f.startsWith(BuildCtx.workspaceRoot)) f.relativeTo(BuildCtx.workspaceRoot).toString else f.toString }" ) @@ -777,18 +789,18 @@ trait ScalaCliScalafixModule extends ScalafixModule { def scalafixConfig: T[Option[os.Path]] = Task { if (scalaVersion().startsWith("2.")) super.scalafixConfig() - else Some(Task.workspace / ".scalafix3.conf") + else Some(BuildCtx.workspaceRoot / ".scalafix3.conf") } - def scalacPluginIvyDeps: T[Loose.Agg[Dep]] = super.scalacPluginIvyDeps() ++ { + def scalacPluginMvnDeps: T[Seq[Dep]] = super.scalacPluginMvnDeps() ++ { if (scalaVersion().startsWith("2.")) Seq(Deps.semanticDbScalac) else Nil } // Explicitly setting sourceroot, so that Scala CLI doesn't use a wrong one. - // Using Task.workspace is more or less required, for scalafix stuff to work fine. + // Using BuildCtx.workspaceRoot is more or less required, for scalafix stuff to work fine. def scalacOptions: T[Seq[String]] = Task { val sv = scalaVersion() val isScala2 = sv.startsWith("2.") - val sourceRoot = Task.workspace + val sourceRoot = BuildCtx.workspaceRoot val parentOptions = { val l = super.scalacOptions() if (isScala2) l.filterNot(_.startsWith("-P:semanticdb:sourceroot:")) From fd3b85f3987c0af8919f4e27c7d7566b96043e80 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 19 Dec 2025 10:23:41 +0100 Subject: [PATCH 3/5] WIP --- build.mill.scala | 64 ++++++++--------- project/settings/package.mill.scala | 102 +++++++++++++++------------- 2 files changed, 87 insertions(+), 79 deletions(-) diff --git a/build.mill.scala b/build.mill.scala index f0dda4fdb9..7186fdf20d 100644 --- a/build.mill.scala +++ b/build.mill.scala @@ -931,10 +931,10 @@ trait Cli extends CrossSbtModule with ProtoBuildModule with CliLaunchers override def mainClass: T[Option[String]] = Some("scala.cli.ScalaCli") private def scala3GraalProcessorClassPath: T[Seq[PathRef]] = Task { - resolveDeps(Task { + defaultResolver().classpath { val bind = bindDependency() Seq(Deps.scala3GraalProcessor).map(bind) - })() + } } override def nativeImageClassPath: T[Seq[PathRef]] = Task { @@ -1217,47 +1217,47 @@ trait CliIntegration extends SbtModule private def testArgs(args: Seq[String], launcher: os.Path, cliKind: String): Seq[String] = extraTestArgs(launcher, cliKind) ++ debugTestArgs(args) - def jvm(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + def jvm(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.jvm().path, "jvm")), + Task.Anon(Seq.empty[String]) + )() + } + + def jvmBootstrapped(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { testTask( - Task.Anon(args ++ testArgs(args, Launchers.jvm().path, "jvm")), + Task.Anon(args ++ testArgs(args, Launchers.jvmBootstrapped().path, "jvmBootstrapped")), Task.Anon(Seq.empty[String]) )() } - def jvmBootstrapped(args: String*): Command[(msg: String, results: Seq[TestResult])] = - Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.jvmBootstrapped().path, "jvmBootstrapped")), - Task.Anon(Seq.empty[String]) - )() - } + def native(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + testTask( + Task.Anon(args ++ testArgs(args, Launchers.native().path, "native")), + Task.Anon(Seq.empty[String]) + )() + } - def native(args: String*): Command[(msg: String, results: Seq[TestResult])] = Task.Command { + def nativeStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { testTask( - Task.Anon(args ++ testArgs(args, Launchers.native().path, "native")), + Task.Anon(args ++ testArgs(args, Launchers.nativeStatic().path, "native-static")), Task.Anon(Seq.empty[String]) )() } - def nativeStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = - Task.Command { - testTask( - Task.Anon(args ++ testArgs(args, Launchers.nativeStatic().path, "native-static")), - Task.Anon(Seq.empty[String]) - )() - } - - def nativeMostlyStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = - Task.Command { - testTask( - Task.Anon(args ++ testArgs( - args, - Launchers.nativeMostlyStatic().path, - "native-mostly-static" - )), - Task.Anon(Seq.empty[String]) - )() - } + def nativeMostlyStatic(args: String*): Command[(msg: String, results: Seq[TestResult])] = + Task.Command { + testTask( + Task.Anon(args ++ testArgs( + args, + Launchers.nativeMostlyStatic().path, + "native-mostly-static" + )), + Task.Anon(Seq.empty[String]) + )() + } } } diff --git a/project/settings/package.mill.scala b/project/settings/package.mill.scala index d1d3d29126..f1dc48289f 100644 --- a/project/settings/package.mill.scala +++ b/project/settings/package.mill.scala @@ -579,16 +579,22 @@ trait PublishLocalNoFluff extends SonatypeCentralPublishModule { )) } - publisher.publishLocal( - jar = jar().path, - sourcesJar = emptyZip().path, - docJar = emptyZip().path, - pom = pom().path, - ivy = Right(ivy().path), - artifact = artifactMetadata(), - publishInfos = Seq.empty + val artifact = artifactMetadata() + val jarPath = jar().path + val pomPath = pom().path + val ivyPath = ivy().path + val emptyPath = emptyZip().path + + val contents = Map[os.SubPath, Array[Byte]]( + os.sub / s"${artifact.id}-${artifact.version}.jar" -> os.read.bytes(jarPath), + os.sub / s"${artifact.id}-${artifact.version}-sources.jar" -> os.read.bytes(emptyPath), + os.sub / s"${artifact.id}-${artifact.version}-javadoc.jar" -> os.read.bytes(emptyPath), + os.sub / s"${artifact.id}-${artifact.version}.pom" -> os.read.bytes(pomPath), + os.sub / "ivy.xml" -> os.read.bytes(ivyPath) ) + publisher.publishLocal(artifact, contents) + jar() } } @@ -613,48 +619,50 @@ trait LocalRepo extends Module { VcsVersion.vcsState() } def localRepoZip: T[PathRef] = Task { - val repoVer = vcsState().format() - val ver = version() - localRepo() - val repoDir = BuildCtx.workspaceRoot / "out" / "repo" / ver - val destDir = Task.dest / ver / "repo.zip" - val dest = destDir / "repo.zip" - - import java.io._ - import java.util.zip._ - os.makeDir.all(destDir) - var fos: FileOutputStream = null - var zos: ZipOutputStream = null - try { - fos = new FileOutputStream(dest.toIO) - zos = new ZipOutputStream(new BufferedOutputStream(fos)) - - val versionEntry = new ZipEntry("version") - versionEntry.setTime(0L) - zos.putNextEntry(versionEntry) - zos.write(repoVer.getBytes(StandardCharsets.UTF_8)) - zos.flush() - - os.walk(repoDir).filter(_ != repoDir).foreach { p => - val isDir = os.isDir(p) - val name = p.relativeTo(repoDir).toString + (if (isDir) "/" else "") - val entry = new ZipEntry(name) - entry.setTime(os.mtime(p)) - zos.putNextEntry(entry) - if (!isDir) { - zos.write(os.read.bytes(p)) - zos.flush() + BuildCtx.withFilesystemCheckerDisabled { + val repoVer = vcsState().format() + val ver = version() + localRepo() + val repoDir = BuildCtx.workspaceRoot / "out" / "repo" / ver + val destDir = Task.dest / ver / "repo.zip" + val dest = destDir / "repo.zip" + + import java.io._ + import java.util.zip._ + os.makeDir.all(destDir) + var fos: FileOutputStream = null + var zos: ZipOutputStream = null + try { + fos = new FileOutputStream(dest.toIO) + zos = new ZipOutputStream(new BufferedOutputStream(fos)) + + val versionEntry = new ZipEntry("version") + versionEntry.setTime(0L) + zos.putNextEntry(versionEntry) + zos.write(repoVer.getBytes(StandardCharsets.UTF_8)) + zos.flush() + + os.walk(repoDir).filter(_ != repoDir).foreach { p => + val isDir = os.isDir(p) + val name = p.relativeTo(repoDir).toString + (if (isDir) "/" else "") + val entry = new ZipEntry(name) + entry.setTime(os.mtime(p)) + zos.putNextEntry(entry) + if (!isDir) { + zos.write(os.read.bytes(p)) + zos.flush() + } + zos.closeEntry() } - zos.closeEntry() + zos.finish() + } + finally { + if (zos != null) zos.close() + if (fos != null) fos.close() } - zos.finish() - } - finally { - if (zos != null) zos.close() - if (fos != null) fos.close() - } - PathRef(dest) + PathRef(dest) + } } def localRepoJar: T[PathRef] = Task { From 9fb06522be42ab248df196a2d4e96404a20a3318 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 19 Dec 2025 12:38:11 +0100 Subject: [PATCH 4/5] WIP --- project/settings/package.mill.scala | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/project/settings/package.mill.scala b/project/settings/package.mill.scala index f1dc48289f..a91df7dda5 100644 --- a/project/settings/package.mill.scala +++ b/project/settings/package.mill.scala @@ -281,21 +281,23 @@ trait CliLaunchers extends SbtModule { self => proc.call(stdin = os.Inherit, stdout = os.Inherit) } def staticLibDir: T[PathRef] = Task { - val dir = nativeImageDockerWorkingDir() / staticLibDirName - os.makeDir.all(dir) + BuildCtx.withFilesystemCheckerDisabled { + val dir = nativeImageDockerWorkingDir() / staticLibDirName + os.makeDir.all(dir) + + if (Properties.isWin) { + copyLibsodiumStaticTo(cs(), dir, BuildCtx.workspaceRoot) + copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) + copyCsjniutilTo(cs(), dir, BuildCtx.workspaceRoot) + } - if (Properties.isWin) { - copyLibsodiumStaticTo(cs(), dir, BuildCtx.workspaceRoot) - copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) - copyCsjniutilTo(cs(), dir, BuildCtx.workspaceRoot) - } + if (launcherKind == "static") { + copyAlpineLibsodiumTo(cs(), dir, BuildCtx.workspaceRoot) + copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) + } - if (launcherKind == "static") { - copyAlpineLibsodiumTo(cs(), dir, BuildCtx.workspaceRoot) - copyLibsodiumjniTo(cs(), dir, BuildCtx.workspaceRoot) + PathRef(dir) } - - PathRef(dir) } } From bee3b1bf2ef8db9280a9748ee98514d9896155e6 Mon Sep 17 00:00:00 2001 From: Piotr Chabelski Date: Fri, 19 Dec 2025 15:18:22 +0100 Subject: [PATCH 5/5] Clean usual suspect dependencies on the CI after every test suite to save space --- .../test/scala/scala/cli/integration/ScalaCliSuite.scala | 6 ++++-- .../src/test/scala/scala/cli/integration/TestUtil.scala | 5 +++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/modules/integration/src/test/scala/scala/cli/integration/ScalaCliSuite.scala b/modules/integration/src/test/scala/scala/cli/integration/ScalaCliSuite.scala index 45da79c1a5..83c676ebab 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/ScalaCliSuite.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/ScalaCliSuite.scala @@ -3,7 +3,6 @@ package scala.cli.integration import java.util.concurrent.TimeUnit import scala.concurrent.duration.{Duration, FiniteDuration} -import scala.util.Properties abstract class ScalaCliSuite extends munit.FunSuite { implicit class BeforeEachOpts(munitContext: BeforeEach) { @@ -33,8 +32,11 @@ abstract class ScalaCliSuite extends munit.FunSuite { override def afterAll(): Unit = { super.afterAll() // Clean up cached JDKs after all tests have run on Linux native CI runners - if isCI && Properties.isLinux then TestUtil.cleanCachedJdks() + if isCI then TestUtil.cleanCachedJdks() else System.err.println("Skipping cached JDKs cleanup") + if isCI then TestUtil.cleanCachedUsualSuspectDeps() + else System.err.println("Skipping cached JDKs cleanup") + } } diff --git a/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala b/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala index 1a3e3c0396..51080402ef 100644 --- a/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala +++ b/modules/integration/src/test/scala/scala/cli/integration/TestUtil.scala @@ -465,4 +465,9 @@ object TestUtil { System.err.println("Cleaning cached JDKs in Coursier cache…") cleanCoursierCache(Seq("zulu", "temurin", "adoptium", "corretto", "liberica", "graalvm")) } + + def cleanCachedUsualSuspectDeps(): Unit = { + System.err.println("Cleaning usual suspect dependencies in Coursier cache…") + cleanCoursierCache(Seq("lihaoyi", "plokhotnyuk", "scalameta", "chuusai", "circe", "junit")) + } }