diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index 12bdf560d..74c6f8e47 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -1,162 +1,281 @@ -name: VFS for Git - -on: - pull_request: - branches: [ master, releases/shipped ] - push: - branches: [ master, releases/shipped ] - workflow_dispatch: - inputs: - git_version: - description: 'Microsoft Git version tag to include in the build (leave empty for default)' - required: false - type: string - -env: - GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.50.1.vfs.0.1' }} - -jobs: - validate: - runs-on: windows-2025 - name: Validation - steps: - - name: Checkout source - uses: actions/checkout@v5 - - - name: Validate Microsoft Git version - shell: pwsh - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - & "$env:GITHUB_WORKSPACE\.github\workflows\scripts\validate_release.ps1" ` - -Repository microsoft/git ` - -Tag $env:GIT_VERSION && ` - Write-Host ::notice title=Validation::Using microsoft/git version $env:GIT_VERSION - - build: - runs-on: windows-2025 - name: Build and Unit Test - needs: validate - - strategy: - matrix: - configuration: [ Debug, Release ] - - steps: - - name: Checkout source - uses: actions/checkout@v5 - with: - path: src - - - name: Install .NET SDK - uses: actions/setup-dotnet@v5 - with: - dotnet-version: 8.0.413 - - - name: Add MSBuild to PATH - uses: microsoft/setup-msbuild@v2.0.0 - - - name: Build VFS for Git - shell: cmd - run: src\scripts\Build.bat ${{ matrix.configuration }} - - - name: Run unit tests - shell: cmd - run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }} - - - name: Create build artifacts - shell: cmd - run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts - - - name: Download microsoft/git installers - shell: cmd - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir artifacts\GVFS.Installers - - - name: Upload functional tests drop - uses: actions/upload-artifact@v4 - with: - name: FunctionalTests_${{ matrix.configuration }} - path: artifacts\GVFS.FunctionalTests - - - name: Upload FastFetch drop - uses: actions/upload-artifact@v4 - with: - name: FastFetch_${{ matrix.configuration }} - path: artifacts\FastFetch - - - name: Upload installers - uses: actions/upload-artifact@v4 - with: - name: Installers_${{ matrix.configuration }} - path: artifacts\GVFS.Installers - - functional_test: - runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-2025' }} - name: Functional Tests - needs: build - - strategy: - matrix: - configuration: [ Debug, Release ] - architecture: [ x86_64, arm64 ] - - steps: - - name: Download installers - uses: actions/download-artifact@v5 - with: - name: Installers_${{ matrix.configuration }} - path: install - - - name: Download functional tests drop - uses: actions/download-artifact@v5 - with: - name: FunctionalTests_${{ matrix.configuration }} - path: ft - - - name: ProjFS details (pre-install) - shell: cmd - run: install\info.bat - - - name: Install product - shell: cmd - run: install\install.bat - - - name: ProjFS details (post-install) - shell: cmd - run: install\info.bat - - - name: Upload installation logs - if: always() - uses: actions/upload-artifact@v4 - with: - name: InstallationLogs_${{ matrix.configuration }}_${{ matrix.architecture }} - path: install\logs - - - name: Run functional tests - shell: cmd - run: | - SET PATH=C:\Program Files\VFS for Git;%PATH% - SET GIT_TRACE2_PERF=C:\temp\git-trace2.log - ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci - - - name: Upload functional test results - if: always() - uses: actions/upload-artifact@v4 - with: - name: FunctionalTests_Results_${{ matrix.configuration }}_${{ matrix.architecture }} - path: TestResult.xml - - - name: Upload Git trace2 output - if: always() - uses: actions/upload-artifact@v4 - with: - name: GitTrace2_${{ matrix.configuration }}_${{ matrix.architecture }} - path: C:\temp\git-trace2.log - - - name: ProjFS details (post-test) - if: always() - shell: cmd - run: install\info.bat +name: VFS for Git + +on: + pull_request: + branches: [ master, releases/shipped ] + push: + branches: [ master, releases/shipped ] + workflow_dispatch: + inputs: + git_version: + description: 'Microsoft Git version tag to include in the build (leave empty for default)' + required: false + type: string + +permissions: + contents: read + +env: + GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.50.1.vfs.0.1' }} + +jobs: + validate: + runs-on: windows-2025 + name: Validation + outputs: + skip: ${{ steps.check.outputs.result }} + + steps: + - name: Look for prior successful runs + id: check + if: github.event.inputs.git_version == '' + uses: actions/github-script@v7 + with: + github-token: ${{secrets.GITHUB_TOKEN}} + result-encoding: string + script: | + /* + * It would be nice if GitHub Actions offered a convenient way to avoid running + * successful workflow runs _again_ for the respective commit (or for a tree-same one): + * We would expect the same outcome in those cases, right? + * + * Let's check for such a scenario: Look for previous runs that have been successful + * and that correspond to the same commit, or at least a tree-same one. If there is + * one, skip running the build and tests _again_. + * + * There are challenges, though: We need to require those _jobs_ to succeed before PRs + * can be merged. You can mark workflow _jobs_ as required on GitHub, but not + * _workflows_. So if those jobs are now simply skipped, the requirement isn't met and + * the PR cannot be merged. We can't just skip the job. Instead, we need to run the job + * _but skip every single step_ so that the job can "succeed". + */ + try { + // Figure out workflow ID, commit and tree + const { data: run } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: context.runId, + }); + const workflow_id = run.workflow_id; + const head_sha = run.head_sha; + const tree_id = run.head_commit.tree_id; + + // See whether there is a successful run for that commit or tree + const { data: runs } = await github.rest.actions.listWorkflowRuns({ + owner: context.repo.owner, + repo: context.repo.repo, + per_page: 500, + workflow_id, + }); + // first look at commit-same runs, then at finished ones, then at in-progress ones + const rank = (a) => (a.status === 'in_progress' ? 0 : (head_sha === a.head_sha ? 2 : 1)) + const demoteInProgressToEnd = (a, b) => (rank(b) - rank(a)) + for (const run of runs.workflow_runs.sort(demoteInProgressToEnd)) { + if (head_sha !== run.head_sha && tree_id !== run.head_commit?.tree_id) continue + if (context.runId === run.id) continue // do not wait for the current run to finish ;-) + if (run.event === 'workflow_dispatch') continue // skip runs that were started manually: they can override the Git version + + if (run.status === 'in_progress') { + // poll until the run is done + const pollIntervalInSeconds = 30 + let seconds = 0 + for (;;) { + console.log(`Found existing, in-progress run at ${run.html_url}; Waiting for it to finish (waited ${seconds} seconds so far)...`) + await new Promise((resolve) => { setTimeout(resolve, pollIntervalInSeconds * 1000) }) + seconds += pollIntervalInSeconds + + const { data: polledRun } = await github.rest.actions.getWorkflowRun({ + owner: context.repo.owner, + repo: context.repo.repo, + run_id: run.id + }) + if (polledRun.status !== 'in_progress') break + } + } + + if (run.status === 'success') return run.html_url + } + return '' + } catch (e) { + core.error(e) + core.warning(e) + } + + - name: Checkout source + if: steps.check.outputs.result == '' + uses: actions/checkout@v5 + + - name: Validate Microsoft Git version + if: steps.check.outputs.result == '' + shell: pwsh + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + & "$env:GITHUB_WORKSPACE\.github\workflows\scripts\validate_release.ps1" ` + -Repository microsoft/git ` + -Tag $env:GIT_VERSION && ` + Write-Host ::notice title=Validation::Using microsoft/git version $env:GIT_VERSION + + build: + runs-on: windows-2025 + name: Build and Unit Test + needs: validate + + strategy: + matrix: + configuration: [ Debug, Release ] + fail-fast: false + + steps: + - name: Skip this job if there is a previous successful run + if: needs.validate.outputs.skip != '' + id: skip + uses: actions/github-script@v7 + with: + script: | + core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`) + return true + + - name: Checkout source + if: steps.skip.outputs.result != 'true' + uses: actions/checkout@v5 + with: + path: src + + - name: Install .NET SDK + if: steps.skip.outputs.result != 'true' + uses: actions/setup-dotnet@v5 + with: + dotnet-version: 8.0.413 + + - name: Add MSBuild to PATH + if: steps.skip.outputs.result != 'true' + uses: microsoft/setup-msbuild@v2.0.0 + + - name: Build VFS for Git + if: steps.skip.outputs.result != 'true' + shell: cmd + run: src\scripts\Build.bat ${{ matrix.configuration }} + + - name: Run unit tests + if: steps.skip.outputs.result != 'true' + shell: cmd + run: src\scripts\RunUnitTests.bat ${{ matrix.configuration }} + + - name: Create build artifacts + if: steps.skip.outputs.result != 'true' + shell: cmd + run: src\scripts\CreateBuildArtifacts.bat ${{ matrix.configuration }} artifacts + + - name: Download microsoft/git installers + if: steps.skip.outputs.result != 'true' + shell: cmd + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + gh release download %GIT_VERSION% --repo microsoft/git --pattern "Git*.exe" --dir artifacts\GVFS.Installers + + - name: Upload functional tests drop + if: steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: FunctionalTests_${{ matrix.configuration }} + path: artifacts\GVFS.FunctionalTests + + - name: Upload FastFetch drop + if: steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: FastFetch_${{ matrix.configuration }} + path: artifacts\FastFetch + + - name: Upload installers + if: steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: Installers_${{ matrix.configuration }} + path: artifacts\GVFS.Installers + + functional_test: + runs-on: ${{ matrix.architecture == 'arm64' && 'windows-11-arm' || 'windows-2025' }} + name: Functional Tests + needs: [validate, build] + + strategy: + matrix: + configuration: [ Debug, Release ] + architecture: [ x86_64, arm64 ] + fail-fast: false # most failures are flaky tests, no need to stop the other jobs from succeeding + + steps: + - name: Skip this job if there is a previous successful run + if: needs.validate.outputs.skip != '' + id: skip + uses: actions/github-script@v7 + with: + script: | + core.info(`Skipping: There already is a successful run: ${{ needs.validate.outputs.skip }}`) + return true + + - name: Download installers + if: steps.skip.outputs.result != 'true' + uses: actions/download-artifact@v5 + with: + name: Installers_${{ matrix.configuration }} + path: install + + - name: Download functional tests drop + if: steps.skip.outputs.result != 'true' + uses: actions/download-artifact@v5 + with: + name: FunctionalTests_${{ matrix.configuration }} + path: ft + + - name: ProjFS details (pre-install) + if: steps.skip.outputs.result != 'true' + shell: cmd + run: install\info.bat + + - name: Install product + if: steps.skip.outputs.result != 'true' + shell: cmd + run: install\install.bat + + - name: ProjFS details (post-install) + if: steps.skip.outputs.result != 'true' + shell: cmd + run: install\info.bat + + - name: Upload installation logs + if: always() && steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: InstallationLogs_${{ matrix.configuration }}_${{ matrix.architecture }} + path: install\logs + + - name: Run functional tests + if: steps.skip.outputs.result != 'true' + shell: cmd + run: | + SET PATH=C:\Program Files\VFS for Git;%PATH% + SET GIT_TRACE2_PERF=C:\temp\git-trace2.log + ft\GVFS.FunctionalTests.exe /result:TestResult.xml --ci + + - name: Upload functional test results + if: always() && steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: FunctionalTests_Results_${{ matrix.configuration }}_${{ matrix.architecture }} + path: TestResult.xml + + - name: Upload Git trace2 output + if: always() && steps.skip.outputs.result != 'true' + uses: actions/upload-artifact@v4 + with: + name: GitTrace2_${{ matrix.configuration }}_${{ matrix.architecture }} + path: C:\temp\git-trace2.log + + - name: ProjFS details (post-test) + if: always() && steps.skip.outputs.result != 'true' + shell: cmd + run: install\info.bat