diff --git a/.github/workflows/e2e-cache.yml b/.github/workflows/e2e-cache.yml index 6df10b57..693d5381 100644 --- a/.github/workflows/e2e-cache.yml +++ b/.github/workflows/e2e-cache.yml @@ -207,3 +207,93 @@ jobs: exit 1 fi ls ~/.cache/coursier + mill-save: + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + working-directory: __tests__/cache/mill + strategy: + fail-fast: false + matrix: + os: [macos-13, windows-latest, ubuntu-22.04] + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run setup-java with the cache for mill + uses: ./ + id: setup-java + with: + distribution: 'adopt' + java-version: '11' + cache: mill + - name: Create files to cache + run: ./mill --disable-ticker _.compile + + - name: Check files to cache on macos-latest + if: matrix.os == 'macos-13' + run: | + if [ ! -d ~/.cache/mill/download ]; then + echo "::error::The ~/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + - name: Check files to cache on windows-latest + if: matrix.os == 'windows-latest' + run: | + if [ ! -d %USERPROFILE%/.cache/mill/download ]; then + echo "::error::The %USERPROFILE%/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + - name: Check files to cache on ubuntu-latest + if: matrix.os == 'ubuntu-latest' + run: | + if [ ! -d ~/.cache/mill/download ]; then + echo "::error::The ~/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + mill-restore: + runs-on: ${{ matrix.os }} + defaults: + run: + shell: bash + working-directory: __tests__/cache/mill + strategy: + fail-fast: false + matrix: + os: [macos-13, windows-latest, ubuntu-22.04] + needs: mill-save + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Run setup-java with the cache for mill + uses: ./ + id: setup-java + with: + distribution: 'adopt' + java-version: '11' + cache: mill + + - name: Confirm that ~/.cache/mill/download directory has been made + if: matrix.os == 'macos-13' + run: | + if [ ! -d ~/.cache/mill/download ]; then + echo "::error::The ~/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + ls ~/.cache/mill/download + - name: Confirm that %USERPROFILE%/.cache/mill/download directory has been made + if: matrix.os == 'windows-latest' + run: | + if [ ! -d %USERPROFILE%/.cache/mill/download ]; then + echo "::error::The %USERPROFILE%/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + ls %USERPROFILE%/.cache/mill/download + - name: Confirm that ~/.cache/mill/download directory has been made + if: matrix.os == 'ubuntu-latest' + run: | + if [ ! -d ~/.cache/mill/download ]; then + echo "::error::The ~/.cache/mill/download directory does not exist unexpectedly" + exit 1 + fi + ls ~/.cache/mill/download diff --git a/.gitignore b/.gitignore index 77afd536..1d756738 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,6 @@ +# Ignore Scala IDE files +.metals/ + # Ignore node_modules, ncc is used to compile nodejs modules into a single file node_modules/ __tests__/runner/* diff --git a/README.md b/README.md index bfee02ec..33fb5dbb 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ The `setup-java` action provides the following functionality for GitHub Actions - Caching dependencies managed by Apache Maven. - Caching dependencies managed by Gradle. - Caching dependencies managed by sbt. +- Caching dependencies managed by Mill. - [Maven Toolchains declaration](https://maven.apache.org/guides/mini/guide-using-toolchains.html) for specified JDK versions. This action allows you to work with Java and Scala projects. @@ -48,7 +49,7 @@ For information about the latest releases, recent updates, and newly supported d - `check-latest`: Setting this option makes the action to check for the latest available version for the version spec. - - `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle" or "sbt". + - `cache`: Quick [setup caching](#caching-packages-dependencies) for the dependencies managed through one of the predefined package managers. It can be one of "maven", "gradle", "sbt", or "mill". - `cache-dependency-path`: The path to a dependency file: pom.xml, build.gradle, build.sbt, etc. This option can be used with the `cache` option. If this option is omitted, the action searches for the dependency file in the entire repository. This option supports wildcards and a list of file names for caching multiple dependencies. @@ -132,11 +133,12 @@ Currently, the following distributions are supported: **NOTE:** Oracle JDK 17 licensing varies by patch level. As shown on the [JDK 17 Archive](https://www.oracle.com/java/technologies/javase/jdk17-archive-downloads.html) (versions up to 17.0.12 are under the [NFTC](https://www.oracle.com/downloads/licenses/no-fee-license.html) license) and the [JDK 17.0.13+ Archive](https://www.oracle.com/java/technologies/javase/jdk17-0-13-later-archive-downloads.html) (versions 17.0.13 and later are under the [OTN](https://www.oracle.com/downloads/licenses/javase-license1.html) license). To stay on the free NFTC license, use `distribution: 'oracle'` with `java-version: '17.0.12'` (or earlier) instead of the floating `'17'`. Alternatively, upgrade to Oracle JDK 21+, which remains under the NFTC license. ### Caching packages dependencies -The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are gradle, maven and sbt. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files: +The action has a built-in functionality for caching and restoring dependencies. It uses [toolkit/cache](https://github.com/actions/toolkit/tree/main/packages/cache) under hood for caching dependencies but requires less configuration settings. Supported package managers are Gradle, Maven, sbt, and Mill. The format of the used cache key is `setup-java-${{ platform }}-${{ packageManager }}-${{ fileHash }}`, where the hash is based on the following files: -- gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties` -- maven: `**/pom.xml` +- Gradle: `**/*.gradle*`, `**/gradle-wrapper.properties`, `buildSrc/**/Versions.kt`, `buildSrc/**/Dependencies.kt`, `gradle/*.versions.toml`, and `**/versions.properties` +- Maven: `**/pom.xml` - sbt: all sbt build definition files `**/*.sbt`, `**/project/build.properties`, `**/project/**.scala`, `**/project/**.sbt` +- Mill: `**/build.sc`, `**/*.sc`, `**/mill`, `**/.mill-version`, and `**/.config/mill-version` When the option `cache-dependency-path` is specified, the hash is based on the matching file. This option supports wildcards and a list of file names, and is especially useful for monorepos. @@ -144,7 +146,7 @@ The workflow output `cache-hit` is set to indicate if an exact match was found f The cache input is optional, and caching is turned off by default. -#### Caching gradle dependencies +#### Caching Gradle dependencies ```yaml steps: - uses: actions/checkout@v6 @@ -164,7 +166,7 @@ For projects that require more advanced `Gradle` caching features, such as cachi For setup details and a comprehensive overview of all available features, visit the [setup-gradle documentation](https://github.com/gradle/actions/blob/main/docs/setup-gradle.md). -#### Caching maven dependencies +#### Caching Maven dependencies ```yaml steps: - uses: actions/checkout@v6 @@ -194,6 +196,21 @@ steps: run: sbt package ``` +#### Caching Mill dependencies +```yaml +steps: +- uses: actions/checkout@v4 +- uses: actions/setup-java@v4 + with: + distribution: 'temurin' + java-version: '21' + cache: 'mill' + cache-dependency-path: | # optional + sub-project/build.sc +- name: Build with Mill + run: ./mill _.compile +``` + #### Cache segment restore timeout Usually, cache gets downloaded in multiple segments of fixed sizes. Sometimes, a segment download gets stuck, which causes the workflow job to be stuck. The cache segment download timeout [was introduced](https://github.com/actions/toolkit/tree/main/packages/cache#cache-segment-restore-timeout) to solve this issue as it allows the segment download to get aborted and hence allows the job to proceed with a cache miss. The default value of the cache segment download timeout is set to 10 minutes and can be customized by specifying an environment variable named `SEGMENT_DOWNLOAD_TIMEOUT_MINS` with a timeout value in minutes. diff --git a/__tests__/cache.test.ts b/__tests__/cache.test.ts index df7a59bd..8722b24c 100644 --- a/__tests__/cache.test.ts +++ b/__tests__/cache.test.ts @@ -210,6 +210,48 @@ describe('dependency cache', () => { expect(firstCall).not.toBe(thirdCall); }); }); + describe('for mill', () => { + it('throws error if no build.sc found', async () => { + await expect(restore('mill', '')).rejects.toThrow( + `No file in ${projectRoot( + workspace + )} matched to [**/build.sc,**/*.sc,**/mill,**/.mill-version,**/.config/mill-version], make sure you have checked out the target repository` + ); + }); + it('downloads cache', async () => { + createFile(join(workspace, 'build.sc')); + + await restore('mill', ''); + expect(spyCacheRestore).toHaveBeenCalled(); + expect(spyGlobHashFiles).toHaveBeenCalledWith( + '**/build.sc\n**/*.sc\n**/mill\n**/.mill-version\n**/.config/mill-version' + ); + expect(spyWarning).not.toHaveBeenCalled(); + expect(spyInfo).toHaveBeenCalledWith('mill cache is not found'); + }); + it('detects scala and mill changes under **/mill-build/ folder', async () => { + createFile(join(workspace, 'build.sc')); + createDirectory(join(workspace, 'project')); + createFile(join(workspace, '.config/mill-version')); + + await restore('mill', ''); + const firstCall = spySaveState.mock.calls.toString(); + + spySaveState.mockClear(); + await restore('mill', ''); + const secondCall = spySaveState.mock.calls.toString(); + + // Make sure multiple restores produce the same cache + expect(firstCall).toBe(secondCall); + + spySaveState.mockClear(); + createFile(join(workspace, '.mill-version')); + await restore('mill', ''); + const thirdCall = spySaveState.mock.calls.toString(); + + expect(firstCall).not.toBe(thirdCall); + }); + }); it('downloads cache based on versions.properties', async () => { createFile(join(workspace, 'versions.properties')); diff --git a/__tests__/cache/mill/.gitignore b/__tests__/cache/mill/.gitignore new file mode 100644 index 00000000..89f9ac04 --- /dev/null +++ b/__tests__/cache/mill/.gitignore @@ -0,0 +1 @@ +out/ diff --git a/__tests__/cache/mill/.mill-version b/__tests__/cache/mill/.mill-version new file mode 100644 index 00000000..aa22d3ce --- /dev/null +++ b/__tests__/cache/mill/.mill-version @@ -0,0 +1 @@ +0.12.3 diff --git a/__tests__/cache/mill/build.sc b/__tests__/cache/mill/build.sc new file mode 100644 index 00000000..3c4615c2 --- /dev/null +++ b/__tests__/cache/mill/build.sc @@ -0,0 +1,12 @@ +package build +import mill._, scalalib._ + +object MyProject extends ScalaModule { + def scalaVersion = "2.13.11" + def ivyDeps = Agg(ivy"com.lihaoyi::mainargs:0.6.2") + + object test extends ScalaTests { + def ivyDeps = Agg(ivy"com.lihaoyi::utest:0.8.5") + def testFramework = "utest.runner.Framework" + } +} diff --git a/__tests__/cache/mill/mill b/__tests__/cache/mill/mill new file mode 100755 index 00000000..30e9e7a3 --- /dev/null +++ b/__tests__/cache/mill/mill @@ -0,0 +1,26 @@ +#!/usr/bin/env sh +# This is a wrapper script that automatically downloads Mill from GitHub. +set -e + +if [ -z "$MILL_VERSION" ] ; then + MILL_VERSION="$(head -n 1 .mill-version 2> /dev/null)" +fi + +MILL_DOWNLOAD_PATH="$HOME/.cache/mill/download" +MILL_EXEC_PATH="${MILL_DOWNLOAD_PATH}/$MILL_VERSION" + +if [ ! -x "$MILL_EXEC_PATH" ] ; then + mkdir -p "${MILL_DOWNLOAD_PATH}" + DOWNLOAD_FILE=$MILL_EXEC_PATH-tmp-download + MILL_DOWNLOAD_URL="https://github.com/lihaoyi/mill/releases/download/${MILL_VERSION%%-*}/$MILL_VERSION-assembly" + curl --fail -L -o "$DOWNLOAD_FILE" "$MILL_DOWNLOAD_URL" + chmod +x "$DOWNLOAD_FILE" + mv "$DOWNLOAD_FILE" "$MILL_EXEC_PATH" + unset DOWNLOAD_FILE + unset MILL_DOWNLOAD_URL +fi + +unset MILL_DOWNLOAD_PATH +unset MILL_VERSION + +exec "${MILL_EXEC_PATH}" "$@" diff --git a/src/cache.ts b/src/cache.ts index 7d13839e..ab5a04ca 100644 --- a/src/cache.ts +++ b/src/cache.ts @@ -13,7 +13,7 @@ const CACHE_MATCHED_KEY = 'cache-matched-key'; const CACHE_KEY_PREFIX = 'setup-java'; interface PackageManager { - id: 'maven' | 'gradle' | 'sbt'; + id: 'maven' | 'gradle' | 'sbt' | 'mill'; /** * Paths of the file that specify the files to cache. */ @@ -60,6 +60,21 @@ const supportedPackageManager: PackageManager[] = [ '**/project/**.scala', '**/project/**.sbt' ] + }, + { + id: 'mill', + path: [ + join(os.homedir(), '.cache', 'mill') + ], + pattern: [ + // https://github.com/coursier/cache-action/blob/4e2615869d13561d626ed48655e1a39e5b192b3c/README.md?plain=1#L28-L38 + '**/build.sc', + '**/*.sc', + '**/mill', + '**/.mill-version', + // https://github.com/com-lihaoyi/mill/blob/5b88d1e268e6264e44589c5ac82c0fdbd680fd63/mill#L6-L11 + '**/.config/mill-version' + ] } ];