From 292be22da011f2b2e07f02fcbb22ab756b39c5a3 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Apr 2026 01:14:55 +0000 Subject: [PATCH 1/2] Add NuGet and npm package caching to CI workflow Add cache: true + cache-dependency-path to all setup-dotnet steps to cache ~/.nuget/packages across job runs. Add cache: npm to all setup-node steps to cache the npm download cache. This eliminates redundant package downloads on every CI run. The NuGet cache key is keyed on the hash of all .fsproj files, so it invalidates automatically when dependencies change. Closes #4486 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/build.yml | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ad3f23a5ff..4433391606 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -24,6 +24,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Restore tools run: dotnet tool restore @@ -45,6 +47,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Restore project run: dotnet restore Fable.sln @@ -64,9 +68,13 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Node.js environment uses: actions/setup-node@v5 + with: + cache: npm - name: Fable Tests - JavaScript (linux) if: matrix.platform == 'ubuntu-latest' @@ -88,9 +96,13 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Node.js environment uses: actions/setup-node@v5 + with: + cache: npm - name: Fable Tests - TypeScript run: ./build.sh test typescript @@ -110,9 +122,13 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Node.js environment uses: actions/setup-node@v5 + with: + cache: npm - name: Fable Tests run: ./build.sh test integration @@ -129,9 +145,13 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Node.js environment uses: actions/setup-node@v5 + with: + cache: npm - name: Fable Tests run: ./build.sh test standalone @@ -154,6 +174,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 @@ -196,9 +218,13 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Node.js environment uses: actions/setup-node@v5 + with: + cache: npm - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -223,6 +249,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Dart SDK uses: dart-lang/setup-dart@v1 @@ -242,6 +270,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Setup Erlang/OTP uses: erlef/setup-beam@v1 @@ -274,6 +304,8 @@ jobs: uses: actions/setup-dotnet@v5 with: global-json-file: global.json + cache: true + cache-dependency-path: "**/*.fsproj" - name: Restore tools run: dotnet tool restore From cd0ded537b318b676068c53467856e4fec0392bf Mon Sep 17 00:00:00 2001 From: Dag Brattli Date: Sat, 18 Jul 2026 17:47:37 +0200 Subject: [PATCH 2/2] ci: extract cached .NET setup into a composite action MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 11 setup-dotnet blocks were byte-identical, comment included. Move them into .github/actions/setup-dotnet so the cache key policy lives in one place instead of eleven copies that drift apart on the next change. Also exclude obj/ from the NuGet key and node_modules/ from the npm key. Restore generates *.nuget.g.props/.targets under obj/, so including them would make the key depend on whether the workspace had been built before — a no-op on a clean CI job, but not if a job ever restores before setup. Widen the npm key to **/package-lock.json: build.sh runs `npm install` in src/fable-standalone and src/fable-compiler-js as well, each with its own committed lockfile, so the root-only default key left those tarballs out. Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/actions/setup-dotnet/action.yml | 24 ++++ .github/workflows/build.yml | 178 ++++++------------------ 2 files changed, 70 insertions(+), 132 deletions(-) create mode 100644 .github/actions/setup-dotnet/action.yml diff --git a/.github/actions/setup-dotnet/action.yml b/.github/actions/setup-dotnet/action.yml new file mode 100644 index 0000000000..8a0f77d967 --- /dev/null +++ b/.github/actions/setup-dotnet/action.yml @@ -0,0 +1,24 @@ +name: Setup .NET +description: Install the .NET SDK pinned by global.json with a cached NuGet package directory. + +runs: + using: composite + steps: + - uses: actions/setup-dotnet@v5 + with: + global-json-file: global.json + cache: true + # Hash .props/.targets alongside .fsproj: not all versions are declared in + # project files. FSharp.Compiler.Service.fsproj interpolates + # $(SystemCollectionsImmutableVersion), $(FSharpCoreShippedPackageVersionValue) + # and others defined in .props, so bumping one of those changes the resolved + # package graph without touching any .fsproj — a .fsproj-only key would not move. + # + # obj/ is excluded because restore generates *.nuget.g.props/.targets there. + # They don't exist at this point in a clean CI job, but including them would + # make the key depend on whether the workspace had been built before. + cache-dependency-path: | + **/*.fsproj + **/*.props + **/*.targets + !**/obj/** diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 3ad252476c..dec8fff10d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -25,18 +25,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Restore tools run: dotnet tool restore @@ -55,18 +44,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Restore project run: dotnet restore Fable.sln @@ -86,23 +64,19 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Node.js environment uses: actions/setup-node@v7 with: cache: npm + # build.sh runs `npm install` in src/fable-standalone and + # src/fable-compiler-js too, each with its own committed lockfile, so + # hashing only the root package-lock.json would leave their tarballs + # out of the cache key. + cache-dependency-path: | + **/package-lock.json + !**/node_modules/** - name: Fable Tests - JavaScript (linux) if: matrix.platform == 'ubuntu-latest' @@ -121,23 +95,19 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Node.js environment uses: actions/setup-node@v7 with: cache: npm + # build.sh runs `npm install` in src/fable-standalone and + # src/fable-compiler-js too, each with its own committed lockfile, so + # hashing only the root package-lock.json would leave their tarballs + # out of the cache key. + cache-dependency-path: | + **/package-lock.json + !**/node_modules/** - name: Fable Tests - TypeScript run: ./build.sh test typescript @@ -154,23 +124,19 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Node.js environment uses: actions/setup-node@v7 with: cache: npm + # build.sh runs `npm install` in src/fable-standalone and + # src/fable-compiler-js too, each with its own committed lockfile, so + # hashing only the root package-lock.json would leave their tarballs + # out of the cache key. + cache-dependency-path: | + **/package-lock.json + !**/node_modules/** - name: Fable Tests run: ./build.sh test integration @@ -184,23 +150,19 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Node.js environment uses: actions/setup-node@v7 with: cache: npm + # build.sh runs `npm install` in src/fable-standalone and + # src/fable-compiler-js too, each with its own committed lockfile, so + # hashing only the root package-lock.json would leave their tarballs + # out of the cache key. + cache-dependency-path: | + **/package-lock.json + !**/node_modules/** - name: Fable Tests run: ./build.sh test standalone @@ -220,18 +182,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Set up Python ${{ matrix.python-version }} uses: actions/setup-python@v6 @@ -274,23 +225,19 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Node.js environment uses: actions/setup-node@v7 with: cache: npm + # build.sh runs `npm install` in src/fable-standalone and + # src/fable-compiler-js too, each with its own committed lockfile, so + # hashing only the root package-lock.json would leave their tarballs + # out of the cache key. + cache-dependency-path: | + **/package-lock.json + !**/node_modules/** - name: Setup Rust toolchain uses: dtolnay/rust-toolchain@stable @@ -312,18 +259,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Dart SDK uses: dart-lang/setup-dart@v1 @@ -340,18 +276,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Setup Erlang/OTP uses: erlef/setup-beam@v1 @@ -381,18 +306,7 @@ jobs: - uses: actions/checkout@v7 - name: Setup .NET - uses: actions/setup-dotnet@v5 - with: - global-json-file: global.json - cache: true - # Version numbers also come from .props/.targets (e.g. - # $(SystemCollectionsImmutableVersion) used by - # FSharp.Compiler.Service.fsproj), so hashing only .fsproj files - # would miss dependency changes made there. - cache-dependency-path: | - **/*.fsproj - **/*.props - **/*.targets + uses: ./.github/actions/setup-dotnet - name: Restore tools run: dotnet tool restore