diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index c21d04bd..79ad7dda 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -3,7 +3,7 @@ "isRoot": true, "tools": { "powershell": { - "version": "7.5.0", + "version": "7.5.2", "commands": [ "pwsh" ], diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 78a791bc..d40ad5e9 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -1,5 +1,5 @@ # Refer to https://hub.docker.com/_/microsoft-dotnet-sdk for available versions -FROM mcr.microsoft.com/dotnet/sdk:9.0.200-noble +FROM mcr.microsoft.com/dotnet/sdk:9.0.304-noble@sha256:0b7186a7247bf8c07085fd700613bb0425a6f8f6467a0342c12a535e767da803 # Installing mono makes `dotnet test` work without errors even for net472. # But installing it takes a long time, so it's excluded by default. diff --git a/.github/actions/publish-artifacts/action.yaml b/.github/actions/publish-artifacts/action.yaml index 2a75c4d2..1f345fe6 100644 --- a/.github/actions/publish-artifacts/action.yaml +++ b/.github/actions/publish-artifacts/action.yaml @@ -14,46 +14,46 @@ runs: - name: 📢 Upload project.assets.json files if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: projectAssetsJson-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/projectAssetsJson continue-on-error: true - name: 📢 Upload variables - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: variables-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/Variables continue-on-error: true - name: 📢 Upload build_logs if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: build_logs-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/build_logs continue-on-error: true - name: 📢 Upload testResults if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: testResults-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/testResults continue-on-error: true - name: 📢 Upload coverageResults if: always() - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: coverageResults-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/coverageResults continue-on-error: true - name: 📢 Upload symbols - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: symbols-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/symbols continue-on-error: true - name: 📢 Upload deployables - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4 with: name: deployables-${{ runner.os }} path: ${{ runner.temp }}/_artifacts/deployables diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 00000000..08593502 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,23 @@ +# Copilot instructions for this repository + +## High level guidance + +* Review the `CONTRIBUTING.md` file for instructions to build and test the software. +* Set the `NBGV_GitEngine` environment variable to `Disabled` before running any `dotnet` or `msbuild` commands. + +## Software Design + +* Design APIs to be highly testable, and all functionality should be tested. +* Avoid introducing binary breaking changes in public APIs of projects under `src` unless their project files have `IsPackable` set to `false`. + +## Testing + +* There should generally be one test project (under the `test` directory) per shipping project (under the `src` directory). Test projects are named after the project being tested with a `.Test` suffix. +* Tests should use the Xunit testing framework. +* Some tests are known to be unstable. When running tests, you should skip the unstable ones by running `dotnet test --filter "TestCategory!=FailsInCloudTest"`. + +## Coding style + +* Honor StyleCop rules and fix any reported build warnings *after* getting tests to pass. +* In C# files, use namespace *statements* instead of namespace *blocks* for all new files. +* Add API doc comments to all new public and internal members. diff --git a/.github/workflows/copilot-setup-steps.yml b/.github/workflows/copilot-setup-steps.yml new file mode 100644 index 00000000..eb69d92e --- /dev/null +++ b/.github/workflows/copilot-setup-steps.yml @@ -0,0 +1,41 @@ +name: 💪🏼 Copilot Setup Steps + +# Automatically run the setup steps when they are changed to allow for easy validation, and +# allow manual testing through the repository's "Actions" tab +on: + workflow_dispatch: + push: + branches: + - main + paths: + - .github/workflows/copilot-setup-steps.yml + pull_request: + paths: + - .github/workflows/copilot-setup-steps.yml + +jobs: + # The job MUST be called `copilot-setup-steps` or it will not be picked up by Copilot. + copilot-setup-steps: + runs-on: ubuntu-latest + # Set the permissions to the lowest permissions possible needed for your steps. + # Copilot will be given its own token for its operations. + permissions: + # If you want to clone the repository as part of your setup steps, for example to install dependencies, you'll need the `contents: read` permission. If you don't clone the repository in your setup steps, Copilot will do this for you automatically after the steps complete. + contents: read + + # You can define any steps you want, and they will run before the agent starts. + # If you do not check out your code, Copilot will do this for you. + steps: + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 + with: + fetch-depth: 0 # avoid shallow clone so nbgv can do its work. + - name: ⚙ Install prerequisites + run: | + ./init.ps1 -UpgradePrerequisites -NoNuGetCredProvider + dotnet --info + + # Print mono version if it is present. + if (Get-Command mono -ErrorAction SilentlyContinue) { + mono --version + } + shell: pwsh diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml index 70b779b5..a7155c4c 100644 --- a/.github/workflows/docs.yml +++ b/.github/workflows/docs.yml @@ -5,13 +5,6 @@ on: branches: - main -# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages -permissions: - actions: read - pages: write - id-token: write - contents: read - # Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. # However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. concurrency: @@ -20,12 +13,18 @@ concurrency: jobs: publish-docs: + # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages + permissions: + actions: read + pages: write + id-token: write + contents: read environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: ⚙ Install prerequisites @@ -35,10 +34,10 @@ jobs: name: 📚 Generate documentation - name: Upload artifact - uses: actions/upload-pages-artifact@v3 + uses: actions/upload-pages-artifact@7b1f4a764d45c48632c6b24a0339c27f5614fb0b # v4 with: path: docfx/_site - name: Deploy to GitHub Pages id: deployment - uses: actions/deploy-pages@v4 + uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4 diff --git a/.github/workflows/docs_validate.yml b/.github/workflows/docs_validate.yml index 2425d742..7c50b3b7 100644 --- a/.github/workflows/docs_validate.yml +++ b/.github/workflows/docs_validate.yml @@ -13,11 +13,11 @@ jobs: name: 📚 Doc validation runs-on: ubuntu-24.04 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. - name: 🔗 Markup Link Checker (mlc) - uses: becheran/mlc@v0.21.0 + uses: becheran/mlc@18a06b3aa2901ca197de59c8b0b1f54fdba6b3fa # v1.0.0 with: args: --do-not-warn-for-redirect-to https://learn.microsoft.com*,https://dotnet.microsoft.com/*,https://dev.azure.com/*,https://app.codecov.io/* -p docfx -i https://aka.ms/onboardsupport,https://aka.ms/spot,https://msrc.microsoft.com/*,https://www.microsoft.com/msrc*,https://microsoft.com/msrc* - name: ⚙ Install prerequisites diff --git a/.github/workflows/libtemplate-update.yml b/.github/workflows/libtemplate-update.yml index 53df80ff..f5cf8666 100644 --- a/.github/workflows/libtemplate-update.yml +++ b/.github/workflows/libtemplate-update.yml @@ -17,7 +17,7 @@ jobs: contents: write pull-requests: write steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@08c6903cd8c0fde910a37f88322edcfb5dd907a8 # v5 with: fetch-depth: 0 # avoid shallow clone so nbgv can do its work. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index c6a5edd8..00000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,88 +0,0 @@ -name: 🎁 Release - -on: - release: - types: [published] - workflow_dispatch: - inputs: - ship_run_id: - description: ID of the GitHub workflow run to ship - required: true - -run-name: ${{ github.ref_name }} - -permissions: - actions: read - contents: write - -jobs: - release: - runs-on: ubuntu-24.04 - steps: - - name: ⚙️ Initialization - shell: pwsh - run: | - if ('${{ secrets.NUGET_API_KEY }}') { - Write-Host "NUGET_API_KEY secret detected. NuGet packages will be pushed." - echo "NUGET_API_KEY_DEFINED=true" >> $env:GITHUB_ENV - } - - - name: 🔎 Search for build of ${{ github.ref }} - shell: pwsh - id: findrunid - env: - GH_TOKEN: ${{ github.token }} - run: | - if ('${{ inputs.ship_run_id }}') { - $runid = '${{ inputs.ship_run_id }}' - } else { - $restApiRoot = '/repos/${{ github.repository }}' - - # Resolve the tag reference to a commit sha - $resolvedRef = gh api ` - -H "Accept: application/vnd.github+json" ` - -H "X-GitHub-Api-Version: 2022-11-28" ` - $restApiRoot/git/ref/tags/${{ github.ref_name }} ` - | ConvertFrom-Json - $commitSha = $resolvedRef.object.sha - - Write-Host "Resolved ${{ github.ref_name }} to $commitSha" - - $releases = gh run list -R ${{ github.repository }} -c $commitSha -w .github/workflows/build.yml -s success --json databaseId,startedAt ` - | ConvertFrom-Json | Sort-Object startedAt -Descending - - if ($releases.length -eq 0) { - Write-Error "No successful builds found for ${{ github.ref }}." - } elseif ($releases.length -gt 1) { - Write-Warning "More than one successful run found for ${{ github.ref }}. Artifacts from the most recent successful run will ship." - } - - $runid = $releases[0].databaseId - } - - Write-Host "Using artifacts from run-id: $runid" - - Echo "runid=$runid" >> $env:GITHUB_OUTPUT - - - name: 🔻 Download deployables artifacts - uses: actions/download-artifact@v4 - with: - name: deployables-Linux - path: ${{ runner.temp }}/deployables - run-id: ${{ steps.findrunid.outputs.runid }} - github-token: ${{ github.token }} - - - name: 💽 Upload artifacts to release - shell: pwsh - if: ${{ github.event_name == 'release' && github.event.release.assets_url != '' }} - env: - GH_TOKEN: ${{ github.token }} - run: | - Get-ChildItem '${{ runner.temp }}/deployables' -File -Recurse |% { - Write-Host "Uploading $($_.Name) to release..." - gh release -R ${{ github.repository }} upload "${{ github.ref_name }}" $_.FullName - } - - - name: 🚀 Push NuGet packages - run: dotnet nuget push ${{ runner.temp }}/deployables/*.nupkg --source https://api.nuget.org/v3/index.json -k '${{ secrets.NUGET_API_KEY }}' - if: ${{ env.NUGET_API_KEY_DEFINED == 'true' }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b5419a8e..4ff0bec6 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -58,9 +58,9 @@ Push the tag. When your repo is hosted by GitHub and you are using GitHub Actions, you should create a GitHub Release using the standard GitHub UI. Having previously used `nbgv tag` and pushing the tag will help you identify the precise commit and name to use for this release. -After publishing the release, the `.github\workflows\release.yml` workflow will be automatically triggered, which will: +After publishing the release, the `.github/workflows/release.yml` workflow will be automatically triggered, which will: -1. Find the most recent `.github\workflows\build.yml` GitHub workflow run of the tagged release. +1. Find the most recent `.github/workflows/build.yml` GitHub workflow run of the tagged release. 1. Upload the `deployables` artifact from that workflow run to your GitHub Release. 1. If you have `NUGET_API_KEY` defined as a secret variable for your repo or org, any nuget packages in the `deployables` artifact will be pushed to nuget.org. @@ -87,3 +87,17 @@ Configuration is in the `.github/renovate.json` file. When changing the renovate.json file, follow [these validation steps](https://docs.renovatebot.com/config-validation/). If Renovate is not creating pull requests when you expect it to, check that the [Renovate GitHub App](https://github.com/apps/renovate) is configured for your account or repo. + +## Merging latest from Library.Template + +### Maintaining your repo based on this template + +The best way to keep your repo in sync with Library.Template's evolving features and best practices is to periodically merge the template into your repo: +` +```ps1 +git fetch +git checkout origin/main +./tools/MergeFrom-Template.ps1 +# resolve any conflicts, then commit the merge commit. +git push origin -u HEAD +``` diff --git a/CodeQL.yml b/CodeQL.yml new file mode 100644 index 00000000..903500b5 --- /dev/null +++ b/CodeQL.yml @@ -0,0 +1,3 @@ +path_classifiers: + library: + - 'test/**' diff --git a/Directory.Build.props b/Directory.Build.props index b26ee20d..0c806729 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -7,6 +7,7 @@ $(RepoRootPath)bin\$(MSBuildProjectName)\ $(RepoRootPath)bin\Packages\$(Configuration)\NuGet\ $(RepoRootPath)bin\Packages\$(Configuration)\Vsix\$(Platform)\ + $(VSIXOutputPath) enable enable latest @@ -20,8 +21,7 @@ true - - false + true $(MSBuildThisFileDirectory) @@ -40,6 +40,11 @@ snupkg + + 13 + 16.9 + + @@ -58,4 +63,6 @@ $(RepositoryUrl)/releases/tag/v$(Version) + + diff --git a/Directory.Build.targets b/Directory.Build.targets index 27b0fad1..b4afce0a 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -1,13 +1,7 @@ - - 13 - 16.9 - - - diff --git a/Directory.Packages.props b/Directory.Packages.props index e6ca3729..71a28dbc 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -5,7 +5,7 @@ true true - 2.0.187 + 2.0.199 61.0.15-preview 0.12.8-experimental @@ -37,10 +37,10 @@ - + - - + + diff --git a/SUPPORT.md b/SUPPORT.md index 6f77c1e9..84d8d36d 100644 --- a/SUPPORT.md +++ b/SUPPORT.md @@ -2,9 +2,9 @@ ## How to file issues and get help -This project uses GitHub Issues to track bugs and feature requests. Please search the existing -issues before filing new issues to avoid duplicates. For new issues, file your bug or -feature request as a new Issue. +This project uses GitHub Issues to track bugs and feature requests. +Please search the existing issues before filing new issues to avoid duplicates. +For new issues, file your bug or feature request as a new Issue. For help and questions about using this project, please file a GitHub issue. diff --git a/azure-pipelines/NuGetSbom.props b/azure-pipelines/NuGetSbom.props new file mode 100644 index 00000000..dbfee864 --- /dev/null +++ b/azure-pipelines/NuGetSbom.props @@ -0,0 +1,6 @@ + + + true + 2 + + diff --git a/azure-pipelines/NuGetSbom.targets b/azure-pipelines/NuGetSbom.targets deleted file mode 100644 index a2599e88..00000000 --- a/azure-pipelines/NuGetSbom.targets +++ /dev/null @@ -1,12 +0,0 @@ - - - true - $(TargetsForTfmSpecificBuildOutput);IncludeSbomInNupkg - - - - - - - - diff --git a/azure-pipelines/build.yml b/azure-pipelines/build.yml index 56dbc6ee..56e074bc 100644 --- a/azure-pipelines/build.yml +++ b/azure-pipelines/build.yml @@ -71,6 +71,10 @@ parameters: type: boolean default: true +- name: PublishCodeCoverage + type: boolean + default: true + # Whether this is a special one-off build for inserting into VS for a validation insertion PR (that will never be merged). - name: SkipCodesignVerify type: boolean @@ -116,6 +120,7 @@ jobs: zipSources: false ${{ if parameters.RealSign }}: signType: real + signWithProd: true ${{ else }}: signType: test sbom: @@ -138,6 +143,9 @@ jobs: - ${{ if parameters.EnableOptProf }}: - powershell: Write-Host "##vso[task.setvariable variable=PROFILINGINPUTSDROPNAME]$(tools/variables/ProfilingInputsDropName.ps1)" displayName: ⚙ Set ProfilingInputsDropName for optprof + sdl: + binskim: + analyzeTargetGlob: $(Build.ArtifactStagingDirectory)\symbols-Windows\** outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: @@ -222,6 +230,7 @@ jobs: signing: enabled: false # enable when building unique artifacts on this agent that must be signed signType: real + signWithProd: true outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - ${{ each artifact_name in parameters.artifact_names }}: @@ -240,6 +249,7 @@ jobs: parameters: Is1ESPT: ${{ parameters.Is1ESPT }} RunTests: ${{ parameters.RunTests }} + BuildRequiresAccessToken: ${{ parameters.RealSign }} # Real signing on non-Windows machines requires passing through access token to build steps that sign - ${{ if parameters.EnableDotNetFormatCheck }}: - script: dotnet format --verify-no-changes displayName: 💅 Verify formatted code @@ -259,6 +269,7 @@ jobs: signing: enabled: false # enable when building unique artifacts on this agent that must be signed signType: real + signWithProd: true outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - ${{ each artifact_name in parameters.artifact_names }}: @@ -277,6 +288,7 @@ jobs: parameters: Is1ESPT: ${{ parameters.Is1ESPT }} RunTests: ${{ parameters.RunTests }} + BuildRequiresAccessToken: ${{ parameters.RealSign }} # Real signing on non-Windows machines requires passing through access token to build steps that sign - job: WrapUp dependsOn: @@ -287,8 +299,16 @@ jobs: - macOS pool: ${{ parameters.windowsPool }} # Use Windows agent because PublishSymbols task requires it (https://github.com/microsoft/azure-pipelines-tasks/issues/13821). condition: succeededOrFailed() + variables: + ONEES_ENFORCED_CODEQL_ENABLED: false # CodeQL runs on build jobs, we don't need it here ${{ if eq(variables['system.collectionId'], '011b8bdf-6d56-4f87-be0d-0092136884d9') }}: templateContext: + ${{ if not(parameters.RealSign) }}: + mb: + signing: # if the build is test-signed, install the signing plugin so that CSVTestSignPolicy.xml is available + enabled: true + zipSources: false + signType: test outputParentDirectory: $(Build.ArtifactStagingDirectory) outputs: - output: pipelineArtifact @@ -307,7 +327,7 @@ jobs: parameters: EnableLinuxBuild: ${{ parameters.EnableLinuxBuild }} EnableMacOSBuild: ${{ parameters.EnableMacOSBuild }} - - ${{ if parameters.RunTests }}: + - ${{ if and(parameters.RunTests, parameters.PublishCodeCoverage) }}: - template: publish-codecoverage.yml parameters: EnableLinuxBuild: ${{ parameters.EnableLinuxBuild }} diff --git a/azure-pipelines/dotnet.yml b/azure-pipelines/dotnet.yml index 180e36c2..947cfc1a 100644 --- a/azure-pipelines/dotnet.yml +++ b/azure-pipelines/dotnet.yml @@ -5,11 +5,17 @@ parameters: default: false - name: Is1ESPT type: boolean +- name: BuildRequiresAccessToken + type: boolean + default: false steps: -- script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog" +- script: dotnet build -t:build,pack --no-restore -c $(BuildConfiguration) -warnAsError -warnNotAsError:NU1901,NU1902,NU1903,NU1904,LOCTASK002 /bl:"$(Build.ArtifactStagingDirectory)/build_logs/build.binlog" displayName: 🛠 dotnet build + ${{ if parameters.BuildRequiresAccessToken }}: + env: + SYSTEM_ACCESSTOKEN: $(System.AccessToken) - ${{ if not(parameters.IsOptProf) }}: - powershell: tools/dotnet-test-cloud.ps1 -Configuration $(BuildConfiguration) -Agent $(Agent.JobName) -PublishResults diff --git a/azure-pipelines/libtemplate-update.yml b/azure-pipelines/libtemplate-update.yml index 3d7fb7e3..384be7cf 100644 --- a/azure-pipelines/libtemplate-update.yml +++ b/azure-pipelines/libtemplate-update.yml @@ -35,6 +35,8 @@ extends: name: AzurePipelines-EO demands: - ImageOverride -equals 1ESPT-Windows2022 + credscan: + enabled: false stages: - stage: Merge diff --git a/azure-pipelines/microbuild.after.yml b/azure-pipelines/microbuild.after.yml index e2107433..025de4f5 100644 --- a/azure-pipelines/microbuild.after.yml +++ b/azure-pipelines/microbuild.after.yml @@ -9,7 +9,7 @@ parameters: type: boolean steps: -- ${{ if not(parameters.SkipCodesignVerify) }}: # skip CodesignVerify on validation builds because we don't even test-sign nupkg's. +- ${{ if not(parameters.SkipCodesignVerify) }}: - task: MicroBuildCodesignVerify@3 displayName: 🔍 Verify Signed Files inputs: diff --git a/azure-pipelines/microbuild.before.yml b/azure-pipelines/microbuild.before.yml index 05acd319..d09310b1 100644 --- a/azure-pipelines/microbuild.before.yml +++ b/azure-pipelines/microbuild.before.yml @@ -17,9 +17,9 @@ parameters: steps: - ${{ if and(not(parameters.IsOptProf), ne(variables['Build.Reason'], 'PullRequest')) }}: # notice@0 requires CG detection to run first, and non-default branches don't inject it automatically. - - ${{ if ne(variables['Build.SourceBranch'], 'refs/heads/main') }}: - - task: ComponentGovernanceComponentDetection@0 - displayName: 🔍 Component Detection + # default branch injection (main) is happening too late for notice@0 to run successfully. Adding this as a workaround. + - task: ComponentGovernanceComponentDetection@0 + displayName: 🔍 Component Detection - task: notice@0 displayName: 🛠️ Generate NOTICE file @@ -42,7 +42,7 @@ steps: - task: MicroBuildSigningPlugin@4 inputs: - signType: Real + signType: Test zipSources: false displayName: 🔧 Install MicroBuild Signing Plugin diff --git a/azure-pipelines/official.yml b/azure-pipelines/official.yml index 2e490fe3..beb8d6b3 100644 --- a/azure-pipelines/official.yml +++ b/azure-pipelines/official.yml @@ -27,6 +27,10 @@ parameters: displayName: Include APIScan with compliance tools type: boolean default: false # enable in individual repos only AFTER updating TSAOptions.json with your own values +- name: PublishCodeCoverage + displayName: Publish code coverage + type: boolean + default: true resources: repositories: @@ -53,7 +57,7 @@ extends: suppression: suppressionFile: $(System.DefaultWorkingDirectory)\azure-pipelines\falsepositives.gdnsuppress sbom: - enabled: true + enabled: false # Skip 1ES SBOM because microbuild has our own sbom system stages: - stage: Build variables: @@ -77,6 +81,7 @@ extends: os: macOS EnableMacOSBuild: ${{ parameters.EnableMacOSBuild }} RunTests: ${{ parameters.RunTests }} + PublishCodeCoverage: ${{ parameters.PublishCodeCoverage }} - template: /azure-pipelines/prepare-insertion-stages.yml@self parameters: RealSign: true diff --git a/azure-pipelines/prepare-insertion-stages.yml b/azure-pipelines/prepare-insertion-stages.yml index a10a8979..4e89ebf7 100644 --- a/azure-pipelines/prepare-insertion-stages.yml +++ b/azure-pipelines/prepare-insertion-stages.yml @@ -18,6 +18,8 @@ stages: - job: symbol_archive displayName: Archive symbols pool: VSEngSS-MicroBuild2022-1ES + variables: + ONEES_ENFORCED_CODEQL_ENABLED: false # CodeQL runs on build stages, we don't need it here steps: - checkout: none - download: current @@ -61,6 +63,8 @@ stages: ${{ else }}: nuGetFeedType: internal publishVstsFeed: vs-impl # Leave this as-is, since non-signed builds must not be pushed to public feeds. + variables: + ONEES_ENFORCED_CODEQL_ENABLED: false # CodeQL runs on build stages, we don't need it here steps: - checkout: none - download: current diff --git a/azure-pipelines/unofficial.yml b/azure-pipelines/unofficial.yml index ff3c2b5d..d7232735 100644 --- a/azure-pipelines/unofficial.yml +++ b/azure-pipelines/unofficial.yml @@ -37,6 +37,10 @@ parameters: displayName: Enable Production SDL type: boolean default: false +- name: PublishCodeCoverage + displayName: Publish code coverage + type: boolean + default: true resources: repositories: @@ -53,6 +57,8 @@ extends: parameters: sdl: sourceAnalysisPool: VSEngSS-MicroBuild2022-1ES + credscan: + enabled: false suppression: suppressionFile: $(System.DefaultWorkingDirectory)\azure-pipelines\falsepositives.gdnsuppress enableProductionSDL: ${{ parameters.EnableProductionSDL }} @@ -65,7 +71,7 @@ extends: enabled: ${{ parameters.EnableProductionSDL }} exclusionsFile: $(System.DefaultWorkingDirectory)\azure-pipelines\PoliCheckExclusions.xml sbom: - enabled: ${{ parameters.EnableProductionSDL }} + enabled: false # Skip 1ES SBOM because microbuild has our own sbom system stages: - stage: Build variables: @@ -89,3 +95,4 @@ extends: os: macOS EnableMacOSBuild: ${{ parameters.EnableMacOSBuild }} RunTests: ${{ parameters.RunTests }} + PublishCodeCoverage: ${{ parameters.PublishCodeCoverage }} diff --git a/azure-pipelines/vs-insertion.yml b/azure-pipelines/vs-insertion.yml index 0caca433..9b5e08db 100644 --- a/azure-pipelines/vs-insertion.yml +++ b/azure-pipelines/vs-insertion.yml @@ -32,6 +32,8 @@ extends: - job: insertion displayName: VS insertion pool: VSEngSS-MicroBuild2022-1ES + templateContext: + outputParentDirectory: $(Pipeline.Workspace)/CI steps: - checkout: none - powershell: Write-Host "##vso[build.updatebuildnumber]$(resources.pipeline.CI.runName)" diff --git a/azure-pipelines/vs-validation.yml b/azure-pipelines/vs-validation.yml index 1b415b22..b9d46b7c 100644 --- a/azure-pipelines/vs-validation.yml +++ b/azure-pipelines/vs-validation.yml @@ -28,14 +28,15 @@ extends: parameters: sdl: sourceAnalysisPool: VSEngSS-MicroBuild2022-1ES + credscan: + enabled: false stages: - stage: Build variables: - DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true - NUGET_PACKAGES: $(Agent.TempDirectory)/.nuget/packages/ - BuildConfiguration: Release - SkipCodesignVerify: true + - template: /azure-pipelines/BuildStageVariables.yml@self + - name: SkipCodesignVerify + value: true jobs: - template: /azure-pipelines/build.yml@self diff --git a/global.json b/global.json index 6306f36b..f8dd2c72 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "9.0.200", + "version": "9.0.304", "rollForward": "patch", "allowPrerelease": false }, diff --git a/init.ps1 b/init.ps1 index e8c2602e..8bb78ad8 100755 --- a/init.ps1 +++ b/init.ps1 @@ -126,7 +126,7 @@ try { } } - $InstallNuGetPkgScriptPath = "$PSScriptRoot\azure-pipelines\Install-NuGetPackage.ps1" + $InstallNuGetPkgScriptPath = "$PSScriptRoot\tools\Install-NuGetPackage.ps1" $nugetVerbosity = 'quiet' if ($Verbose) { $nugetVerbosity = 'normal' } $MicroBuildPackageSource = 'https://pkgs.dev.azure.com/devdiv/_packaging/MicroBuildToolset%40Local/nuget/v3/index.json' diff --git a/tools/Get-3rdPartySymbolFiles.ps1 b/tools/Get-3rdPartySymbolFiles.ps1 new file mode 100644 index 00000000..ef6bbef2 --- /dev/null +++ b/tools/Get-3rdPartySymbolFiles.ps1 @@ -0,0 +1,91 @@ +Function Get-FileFromWeb([Uri]$Uri, $OutFile) { + $OutDir = Split-Path $OutFile + if (!(Test-Path $OutFile)) { + Write-Verbose "Downloading $Uri..." + if (!(Test-Path $OutDir)) { New-Item -ItemType Directory -Path $OutDir | Out-Null } + try { + (New-Object System.Net.WebClient).DownloadFile($Uri, $OutFile) + } + finally { + # This try/finally causes the script to abort + } + } +} + +Function Unzip($Path, $OutDir) { + $OutDir = (New-Item -ItemType Directory -Path $OutDir -Force).FullName + Add-Type -AssemblyName System.IO.Compression.FileSystem + + # Start by extracting to a temporary directory so that there are no file conflicts. + [System.IO.Compression.ZipFile]::ExtractToDirectory($Path, "$OutDir.out") + + # Now move all files from the temp directory to $OutDir, overwriting any files. + Get-ChildItem -Path "$OutDir.out" -Recurse -File | ForEach-Object { + $destinationPath = Join-Path -Path $OutDir -ChildPath $_.FullName.Substring("$OutDir.out".Length).TrimStart([io.path]::DirectorySeparatorChar, [io.path]::AltDirectorySeparatorChar) + if (!(Test-Path -Path (Split-Path -Path $destinationPath -Parent))) { + New-Item -ItemType Directory -Path (Split-Path -Path $destinationPath -Parent) | Out-Null + } + Move-Item -Path $_.FullName -Destination $destinationPath -Force + } + Remove-Item -Path "$OutDir.out" -Recurse -Force +} + +Function Get-SymbolsFromPackage($id, $version) { + $symbolPackagesPath = "$PSScriptRoot/../obj/SymbolsPackages" + New-Item -ItemType Directory -Path $symbolPackagesPath -Force | Out-Null + $nupkgPath = Join-Path $symbolPackagesPath "$id.$version.nupkg" + $snupkgPath = Join-Path $symbolPackagesPath "$id.$version.snupkg" + $unzippedPkgPath = Join-Path $symbolPackagesPath "$id.$version" + Get-FileFromWeb -Uri "https://www.nuget.org/api/v2/package/$id/$version" -OutFile $nupkgPath + Get-FileFromWeb -Uri "https://www.nuget.org/api/v2/symbolpackage/$id/$version" -OutFile $snupkgPath + + Unzip -Path $nupkgPath -OutDir $unzippedPkgPath + Unzip -Path $snupkgPath -OutDir $unzippedPkgPath + + Get-ChildItem -Recurse -LiteralPath $unzippedPkgPath -Filter *.pdb | % { + # Collect the DLLs/EXEs as well. + $rootName = Join-Path $_.Directory $_.BaseName + if ($rootName.EndsWith('.ni')) { + $rootName = $rootName.Substring(0, $rootName.Length - 3) + } + + $dllPath = "$rootName.dll" + $exePath = "$rootName.exe" + if (Test-Path $dllPath) { + $BinaryImagePath = $dllPath + } + elseif (Test-Path $exePath) { + $BinaryImagePath = $exePath + } + else { + Write-Warning "`"$_`" found with no matching binary file." + $BinaryImagePath = $null + } + + if ($BinaryImagePath) { + Write-Output $BinaryImagePath + Write-Output $_.FullName + } + } +} + +Function Get-PackageVersion($id) { + $versionProps = [xml](Get-Content -LiteralPath $PSScriptRoot\..\Directory.Packages.props) + $version = $versionProps.Project.ItemGroup.PackageVersion | ? { $_.Include -eq $id } | % { $_.Version } + if (!$version) { + Write-Error "No package version found in Directory.Packages.props for the package '$id'" + } + + $version +} + +# All 3rd party packages for which symbols packages are expected should be listed here. +# These must all be sourced from nuget.org, as it is the only feed that supports symbol packages. +$3rdPartyPackageIds = @() + +$3rdPartyPackageIds | % { + $version = Get-PackageVersion $_ + if ($version) { + Get-SymbolsFromPackage -id $_ -version $version + } +} diff --git a/tools/Get-SymbolFiles.ps1 b/tools/Get-SymbolFiles.ps1 index 956f3ca1..09be364b 100644 --- a/tools/Get-SymbolFiles.ps1 +++ b/tools/Get-SymbolFiles.ps1 @@ -43,7 +43,7 @@ $PDBs |% { } } |% { # Collect the DLLs/EXEs as well. - $rootName = "$($_.Directory)/$($_.BaseName)" + $rootName = Join-Path $_.Directory $_.BaseName if ($rootName.EndsWith('.ni')) { $rootName = $rootName.Substring(0, $rootName.Length - 3) } diff --git a/tools/Install-DotNetSdk.ps1 b/tools/Install-DotNetSdk.ps1 index b67ac142..0ebc1a71 100644 --- a/tools/Install-DotNetSdk.ps1 +++ b/tools/Install-DotNetSdk.ps1 @@ -36,7 +36,11 @@ if (!(Test-Path $DotNetInstallScriptRoot)) { New-Item -ItemType Directory -Path $DotNetInstallScriptRoot = Resolve-Path $DotNetInstallScriptRoot # Look up actual required .NET SDK version from global.json -$sdkVersion = & "$PSScriptRoot/variables/DotNetSdkVersion.ps1" +$sdks = @(New-Object PSObject -Property @{ Version = & "$PSScriptRoot/variables/DotNetSdkVersion.ps1" }) + +# Sometimes a repo requires extra SDKs to be installed (e.g. msbuild.locator scenarios running in tests). +# In such a circumstance, a precise SDK version or a channel can be added as in the example below: +# $sdks += New-Object PSObject -Property @{ Channel = '8.0' } If ($IncludeX86 -and ($IsMacOS -or $IsLinux)) { Write-Verbose "Ignoring -IncludeX86 switch because 32-bit runtimes are only supported on Windows." @@ -191,13 +195,16 @@ if ($InstallLocality -eq 'machine') { $DotNetInstallDir = '/usr/share/dotnet' } else { $restartRequired = $false - if ($PSCmdlet.ShouldProcess(".NET SDK $sdkVersion", "Install")) { - Install-DotNet -Version $sdkVersion -Architecture $arch - $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010) - - if ($IncludeX86) { - Install-DotNet -Version $sdkVersion -Architecture x86 + $sdks |% { + if ($_.Version) { $version = $_.Version } else { $version = $_.Channel } + if ($PSCmdlet.ShouldProcess(".NET SDK $_", "Install")) { + Install-DotNet -Version $version -Architecture $arch $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010) + + if ($IncludeX86) { + Install-DotNet -Version $version -Architecture x86 + $restartRequired = $restartRequired -or ($LASTEXITCODE -eq 3010) + } } } @@ -296,29 +303,33 @@ $DotNetInstallScriptPathExpression = "& '$DotNetInstallScriptPathExpression'" $anythingInstalled = $false $global:LASTEXITCODE = 0 -if ($PSCmdlet.ShouldProcess(".NET SDK $sdkVersion", "Install")) { - $anythingInstalled = $true - Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture $arch -InstallDir $DotNetInstallDir $switches" +$sdks |% { + if ($_.Version) { $parameters = '-Version', $_.Version } else { $parameters = '-Channel', $_.Channel } - if ($LASTEXITCODE -ne 0) { - Write-Error ".NET SDK installation failure: $LASTEXITCODE" - exit $LASTEXITCODE - } -} else { - Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture $arch -InstallDir $DotNetInstallDir $switches -DryRun" -} - -if ($IncludeX86) { - if ($PSCmdlet.ShouldProcess(".NET x86 SDK $sdkVersion", "Install")) { + if ($PSCmdlet.ShouldProcess(".NET SDK $_", "Install")) { $anythingInstalled = $true - Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture x86 -InstallDir $DotNetX86InstallDir $switches" + Invoke-Expression -Command "$DotNetInstallScriptPathExpression $parameters -Architecture $arch -InstallDir $DotNetInstallDir $switches" if ($LASTEXITCODE -ne 0) { - Write-Error ".NET x86 SDK installation failure: $LASTEXITCODE" + Write-Error ".NET SDK installation failure: $LASTEXITCODE" exit $LASTEXITCODE } } else { - Invoke-Expression -Command "$DotNetInstallScriptPathExpression -Version $sdkVersion -Architecture x86 -InstallDir $DotNetX86InstallDir $switches -DryRun" + Invoke-Expression -Command "$DotNetInstallScriptPathExpression $parameters -Architecture $arch -InstallDir $DotNetInstallDir $switches -DryRun" + } + + if ($IncludeX86) { + if ($PSCmdlet.ShouldProcess(".NET x86 SDK $_", "Install")) { + $anythingInstalled = $true + Invoke-Expression -Command "$DotNetInstallScriptPathExpression $parameters -Architecture x86 -InstallDir $DotNetX86InstallDir $switches" + + if ($LASTEXITCODE -ne 0) { + Write-Error ".NET x86 SDK installation failure: $LASTEXITCODE" + exit $LASTEXITCODE + } + } else { + Invoke-Expression -Command "$DotNetInstallScriptPathExpression $parameters -Architecture x86 -InstallDir $DotNetX86InstallDir $switches -DryRun" + } } } diff --git a/azure-pipelines/Install-NuGetPackage.ps1 b/tools/Install-NuGetPackage.ps1 similarity index 96% rename from azure-pipelines/Install-NuGetPackage.ps1 rename to tools/Install-NuGetPackage.ps1 index f1db577a..9afde055 100644 --- a/azure-pipelines/Install-NuGetPackage.ps1 +++ b/tools/Install-NuGetPackage.ps1 @@ -33,7 +33,7 @@ Param( [string]$Verbosity='normal' ) -$nugetPath = & "$PSScriptRoot\..\tools\Get-NuGetTool.ps1" +$nugetPath = & "$PSScriptRoot\Get-NuGetTool.ps1" try { Write-Verbose "Installing $PackageId..." diff --git a/tools/artifacts/symbols.ps1 b/tools/artifacts/symbols.ps1 index 9e2c7bd5..b5882678 100644 --- a/tools/artifacts/symbols.ps1 +++ b/tools/artifacts/symbols.ps1 @@ -1,7 +1,10 @@ $BinPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../bin") +$3rdPartyPath = [System.IO.Path]::GetFullPath("$PSScriptRoot/../../obj/SymbolsPackages") if (!(Test-Path $BinPath)) { return } $symbolfiles = & "$PSScriptRoot/../Get-SymbolFiles.ps1" -Path $BinPath | Get-Unique +$3rdPartyFiles = & "$PSScriptRoot/../Get-3rdPartySymbolFiles.ps1" @{ "$BinPath" = $SymbolFiles; + "$3rdPartyPath" = $3rdPartyFiles; }