diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 000000000..753cb3b3b --- /dev/null +++ b/.dockerignore @@ -0,0 +1,11 @@ +.git +**/node_modules +**/obj +**/bin +**/dist +TestResults +artifacts +*.tar +.copilot +.buildkit-cache +scratch diff --git a/.github/workflows/Build-Test-And-Deploy.yaml b/.github/workflows/Build-Test-And-Deploy.yaml index 07255243e..6f767733d 100644 --- a/.github/workflows/Build-Test-And-Deploy.yaml +++ b/.github/workflows/Build-Test-And-Deploy.yaml @@ -15,6 +15,9 @@ jobs: build-and-test: runs-on: ubuntu-latest environment: "BuildAndUploadImage" + permissions: + actions: write # required for docker/build-push-action GHA cache writes + contents: read steps: - uses: actions/checkout@v6 @@ -49,6 +52,35 @@ jobs: - name: Set up Docker Buildx uses: docker/setup-buildx-action@v4 + id: setup-buildx + + - name: Cache Docker build mounts (main) + if: github.ref == 'refs/heads/main' + uses: actions/cache@v5 + id: cache-mounts-main + with: + path: .buildkit-cache + key: buildkit-cache-${{ hashFiles('Dockerfile', 'Directory.Packages.props', 'NuGet.config', 'src/**/package-lock.json') }} + restore-keys: | + buildkit-cache- + + - name: Restore Docker build mounts (PR) + if: github.event_name == 'pull_request' || github.event_name == 'merge_group' + uses: actions/cache/restore@v5 + id: cache-mounts-pr + with: + path: .buildkit-cache + key: buildkit-cache-${{ hashFiles('Dockerfile', 'Directory.Packages.props', 'NuGet.config', 'src/**/package-lock.json') }} + restore-keys: | + buildkit-cache- + + - name: Inject Docker cache mounts + uses: reproducible-containers/buildkit-cache-dance@v3 + with: + builder: ${{ steps.setup-buildx.outputs.name }} + cache-dir: .buildkit-cache + dockerfile: Dockerfile + skip-extraction: ${{ github.ref != 'refs/heads/main' || steps.cache-mounts-main.outputs.cache-hit == 'true' }} # Build but no push with a PR - name: Docker build (no push) @@ -58,21 +90,22 @@ jobs: push: false tags: temp-pr-validation file: ./Dockerfile + cache-from: type=gha,scope=try-main - # Only build for dev registry — prod gets the image via az acr import in deploy-production + # Only build for dev registry — prod gets the image via az acr import in deploy-production - name: Build Container Image - if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' + if: github.ref == 'refs/heads/main' uses: docker/build-push-action@v7 with: tags: ${{ vars.DEVCONTAINER_REGISTRY }}/try:${{ github.sha }},${{ vars.DEVCONTAINER_REGISTRY }}/try:latest file: ./Dockerfile context: . outputs: type=docker,dest=${{ github.workspace }}/tryimage.tar - cache-from: type=gha - cache-to: type=gha,mode=max + cache-from: type=gha,scope=try-main + cache-to: type=gha,mode=max,scope=try-main - name: Upload artifact - if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' + if: github.ref == 'refs/heads/main' uses: actions/upload-artifact@v7.0.1 with: name: tryimage @@ -138,7 +171,7 @@ jobs: artifact-name: 'integration-test-playlists' deploy-development: - if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: [build-and-test, build-and-test-windows, integration-tests] concurrency: @@ -189,7 +222,7 @@ jobs: --image "${{ vars.DEVCONTAINER_REGISTRY }}/try:${{ github.sha }}" deploy-production: - if: github.event_name != 'pull_request_target' && github.event_name != 'pull_request' + if: github.ref == 'refs/heads/main' runs-on: ubuntu-latest needs: deploy-development concurrency: diff --git a/Dockerfile b/Dockerfile index a4721c59b..dc6f3bbd0 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,27 +1,50 @@ +# syntax=docker/dockerfile:1 FROM mcr.microsoft.com/dotnet/sdk:10.0-azurelinux3.0 AS build-env WORKDIR /App -# Copy everything -COPY . ./ - # Make sure we run bash CMD ["bash"] -# Make sure we get all the updates and tools we need to build -RUN tdnf install gawk -y -# This is Node v16. For 18, use nodejs18. -RUN tdnf install nodejs -y -RUN tdnf install npm -y -RUN tdnf clean all +# Install all required build tools in a single layer (rarely changes — stays cached) +# This is Node v16. For 18, use nodejs18. +RUN --mount=type=cache,id=try-tdnf,target=/var/cache/tdnf,sharing=locked \ + tdnf install -y gawk nodejs npm + +# Copy only the files needed to restore dependencies. +# These layers are cached until a manifest file changes, so routine source edits +# don't re-run the expensive restore steps below. +COPY NuGet.config global.json Directory.Build.props Directory.Build.targets Directory.Packages.props TryDotNet.sln ./ +COPY eng/ ./eng/ + +# .csproj files — one COPY per project to preserve directory structure +COPY src/Microsoft.TryDotNet/Microsoft.TryDotNet.csproj src/Microsoft.TryDotNet/ +COPY src/Microsoft.TryDotNet.FileIntegration.Tests/Microsoft.TryDotNet.FileIntegration.Tests.csproj src/Microsoft.TryDotNet.FileIntegration.Tests/ +COPY src/Microsoft.TryDotNet.IntegrationTests/Microsoft.TryDotNet.IntegrationTests.csproj src/Microsoft.TryDotNet.IntegrationTests/ +COPY src/Microsoft.TryDotNet.SimulatorGenerator/Microsoft.TryDotNet.SimulatorGenerator.csproj src/Microsoft.TryDotNet.SimulatorGenerator/ +COPY src/Microsoft.TryDotNet.Tests/Microsoft.TryDotNet.Tests.csproj src/Microsoft.TryDotNet.Tests/ +COPY src/Microsoft.TryDotNet.WasmRunner/Microsoft.TryDotNet.WasmRunner.csproj src/Microsoft.TryDotNet.WasmRunner/ -# Build javascript library -RUN /App/build-js.sh +# npm manifests +COPY src/microsoft-trydotnet/package.json src/microsoft-trydotnet/package-lock.json src/microsoft-trydotnet/ +COPY src/microsoft-trydotnet-editor/package.json src/microsoft-trydotnet-editor/package-lock.json src/microsoft-trydotnet-editor/ +COPY src/microsoft-trydotnet-styles/package.json src/microsoft-trydotnet-styles/package-lock.json src/microsoft-trydotnet-styles/ +COPY src/microsoft-learn-mock/package.json src/microsoft-learn-mock/package-lock.json src/microsoft-learn-mock/ + +# Restore NuGet packages. The cache mount persists the package cache across local +# builds; in CI the layer itself is cached by the GHA cache backend. +RUN --mount=type=cache,id=try-nuget,target=/root/.nuget/packages \ + dotnet restore --configfile /App/NuGet.config /App/TryDotNet.sln + +# Copy all remaining source (changes frequently — only layers below rebuild on edits) +COPY . ./ -# Restore -RUN dotnet restore --configfile /App/NuGet.config /App/TryDotNet.sln +# Build javascript library. The npm cache mount speeds up repeated local builds. +RUN --mount=type=cache,id=try-npm,target=/root/.npm \ + /App/build-js.sh -# Build and publish a release -RUN dotnet publish -c Release -o out /App/src/Microsoft.TryDotNet +# Publish only what we deploy +RUN --mount=type=cache,id=try-nuget,target=/root/.nuget/packages \ + dotnet publish -c Release --no-restore -o out /App/src/Microsoft.TryDotNet # Build runtime image FROM mcr.microsoft.com/dotnet/sdk:10.0-azurelinux3.0 @@ -31,9 +54,9 @@ WORKDIR /App # Make sure we run bash CMD ["bash"] -# Make sure we get all the tools we need -RUN tdnf install procps -y -RUN tdnf clean all +# Install runtime tools in a single layer +RUN --mount=type=cache,id=try-tdnf,target=/var/cache/tdnf,sharing=locked \ + tdnf install -y procps # Copy from build image COPY --from=build-env /App/out .