diff --git a/.config/tsaoptions.json b/.config/tsaoptions.json index 53a4a691178..3f3b1eb1cde 100644 --- a/.config/tsaoptions.json +++ b/.config/tsaoptions.json @@ -6,5 +6,6 @@ "iterationPath": "DevDiv", "notificationAliases": [ "msbtm@microsoft.com" ], "repositoryName": "MSBuild", - "codebaseName": "MSBuild" + "codebaseName": "MSBuild", + "serviceTreeId": "d0ebbe59-0779-4466-8280-a0ff9cab5550" } diff --git a/.editorconfig b/.editorconfig index e64a0519df7..2927cad5369 100644 --- a/.editorconfig +++ b/.editorconfig @@ -216,7 +216,7 @@ dotnet_analyzer_diagnostic.category-Style.severity = warning dotnet_diagnostic.IDE0004.severity = suggestion # IDE0005: Remove unnecessary usings/imports -dotnet_diagnostic.IDE0005.severity = none +dotnet_diagnostic.IDE0005.severity = warning # Use explicit type instead of 'var' dotnet_diagnostic.IDE0008.severity = suggestion diff --git a/.exp-insertions.yml b/.exp-insertions.yml deleted file mode 100644 index dac0bddd22a..00000000000 --- a/.exp-insertions.yml +++ /dev/null @@ -1,236 +0,0 @@ -# Pipeline creates experimental msbuild insertions. - -trigger: none # Prevents this pipeline from triggering on check-ins -pr: none # don't run this on PR as well - -parameters: - # Dotnet installer channel from which to take the latest dotnet bits. - - name: DotnetInstallerChannel - displayName: Dotnet installer channel - type: string - default: 'none' - # VS version for which to take the latest Retail MSBuild bits. - - name: VSVersionName - displayName: VS Version - type: string - default: 'none' - # Branch from the MSBuild Build CI pipeline. Default: main - # Top run for the branch would be used to create an experimental insertion. - - name: MSBuildBranch - displayName: MSBuild Branch - type: string - default: 'refs/heads/main' - # BuildID from the MSBuild Build CI pipeline. Overrides the choice of MSBuildBranch parameter - - name: MSBuildBuildID - displayName: MSBuild CI Run Override - type: string - default: 'default' - -variables: - - name: _MsBuildCiPipelineId - value: 9434 - - name: _MSBuildConfigFilePathRequestURL - value: 'https://dev.azure.com/cloudbuild/CloudBuild/_apis/git/repositories/CloudBuildConfig/items?versionDescriptor.version=main&path=config/batmon/Q-Prod-Co3/Coordinator/ToolsReleaseConfig-GeneralPublic.json&api-version=5.0' - - name: VSVersion - value: ${{parameters.VSVersionName}} - -pool: - vmImage: windows-latest - -jobs: -- job: CreateExpDotnet - displayName: Create Experimental Dotnet - condition: ne('${{ parameters.DotnetInstallerChannel }}', 'none') - steps: - - powershell: | - mkdir '$(System.ArtifactsDirectory)/installer' - - $dotnetChannel = '${{parameters.DotnetInstallerChannel}}' - $sdks = "dotnet-sdk-win-x64.zip", "dotnet-sdk-linux-x64.tar.gz" - - foreach ($sdk in $sdks) - { - Write-Host "Downloading dotnet $sdk from channel $dotnetChannel" - Invoke-WebRequest ` - -Uri "https://aka.ms/dotnet/$dotnetChannel/daily/$sdk" ` - -OutFile "$(System.ArtifactsDirectory)/installer/$sdk" - } - mkdir '$(Pipeline.Workspace)/artifacts' - displayName: Download latest dotnet sdks - - - task: DownloadBuildArtifacts@1 - inputs: - buildType: specific - project: DevDiv - pipeline: $(_MsBuildCiPipelineId) - ${{ if eq(parameters.MSBuildBuildID, 'default') }}: - buildVersionToDownload: latestFromBranch - branchName: '${{parameters.MSBuildBranch}}' - ${{ else }}: - buildVersionToDownload: specific - buildId: ${{parameters.MSBuildBuildID}} - artifactName: bin - itemPattern: 'MSBuild.Bootstrap/**' - downloadPath: '$(System.ArtifactsDirectory)/msbuild/artifacts/bin' - displayName: Download msbuild artifacts - - - powershell: | - $sdk = "dotnet-sdk-win-x64" - - Write-Host "Extracting $(System.ArtifactsDirectory)/installer/$sdk.zip" - Expand-Archive "$(System.ArtifactsDirectory)/installer/$sdk.zip" -DestinationPath "$(Pipeline.Workspace)/exp-dotnet/$sdk" - - $dotnetDirectory = Get-ChildItem -Directory -Path "$(Pipeline.Workspace)/exp-dotnet/$sdk/sdk" - $dotnetVersion = $dotnetDirectory.Name - Write-Host "Detected dotnet version: $dotnetVersion" - - Write-Host "Updating MSBuild dlls." - $(Build.SourcesDirectory)/scripts/Deploy-MSBuild.ps1 ` - -destination "$(Pipeline.Workspace)/exp-dotnet/$sdk/sdk/$dotnetVersion" ` - -binDirectory "$(System.ArtifactsDirectory)/msbuild/artifacts/bin" ` - -configuration Release ` - -makeBackup $false - - Write-Host "Compressing dotnet sdk files" - Get-ChildItem -Path "$(Pipeline.Workspace)/exp-dotnet/$sdk" | Compress-Archive -DestinationPath "$(Pipeline.Workspace)/artifacts/$sdk.zip" - - displayName: Dogfood msbuild dlls to dotnet sdk win-x64 - - - powershell: | - $sdk = "dotnet-sdk-linux-x64" - - mkdir "$(Pipeline.Workspace)/exp-dotnet/$sdk" - - Write-Host "Extracting $(System.ArtifactsDirectory)/installer/$sdk.tar.gz" - tar -xzvf "$(System.ArtifactsDirectory)/installer/$sdk.tar.gz" -C "$(Pipeline.Workspace)/exp-dotnet/$sdk" - - $dotnetDirectory = Get-ChildItem -Directory -Path $(Pipeline.Workspace)/exp-dotnet/$sdk/sdk - $dotnetVersion = $dotnetDirectory.Name - Write-Host "Detected dotnet version: $dotnetVersion" - - Write-Host "Updating MSBuild dlls." - $(Build.SourcesDirectory)/scripts/Deploy-MSBuild.ps1 ` - -destination "$(Pipeline.Workspace)/exp-dotnet/$sdk/sdk/$dotnetVersion" ` - -binDirectory "$(System.ArtifactsDirectory)/msbuild/artifacts/bin" ` - -configuration Release ` - -makeBackup $false - - Write-Host "Compressing dotnet sdk files" - tar -czvf "$(Pipeline.Workspace)/artifacts/$sdk.tar.gz" -C "$(Pipeline.Workspace)/exp-dotnet/$sdk" . - displayName: Dogfood msbuild dlls to dotnet sdk linux-x64 - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: '$(Pipeline.Workspace)/artifacts' - artifactName: ExperimentalDotnet - parallel: true - condition: always() - displayName: Publish crank assests artifacts - - -- job: CreateExpMSBuild - displayName: "Create Experimental MSBuild" - condition: ne('${{ parameters.VSVersionName }}', 'none') - steps: - - powershell: | - $token = [System.Convert]::ToBase64String([System.Text.Encoding]::ASCII.GetBytes("PAT:$env:ACCESSTOKEN")) - $headers = @{ Authorization = "Basic $token" }; - $response = Invoke-RestMethod -Uri "$(_MSBuildConfigFilePathRequestURL)" -Headers $headers -Method Get - $MSBuildDropPath = $response.Tools.MSBuild.Locations - Write-Host "##vso[task.setvariable variable=MSBuildDropPath]$MSBuildDropPath" - Write-Host "MSBuild Drop Path directory: $MSBuildDropPath" - displayName: Get Retail MSBuild Drop Path - env: - ACCESSTOKEN: $(cloudbuild-token) - - - task: NuGetToolInstaller@1 - displayName: 'Install NuGet.exe' - - - task: NuGetCommand@2 - displayName: Restore internal tools - inputs: - command: restore - feedsToUse: config - restoreSolution: '$(Build.SourcesDirectory)\eng\common\internal\Tools.csproj' - nugetConfigPath: '$(Build.SourcesDirectory)\eng\common\internal\NuGet.config' - restoreDirectory: '$(Build.SourcesDirectory)\.packages' - - # https://eng.ms/docs/cloud-ai-platform/devdiv/one-engineering-system-1es/1es-docs/1es-security-configuration/configuration-guides/pat-burndown-guidance#authentication-from-pipelines - # Requires Azure client 2.x - - task: AzureCLI@2 - displayName: 'Set AzDO.DotnetPerfStarToken' - enabled: true - inputs: - azureSubscription: 'dotnet-perfstar at app.vssps.visualstudio.com' # Azure DevOps service connection - scriptType: 'pscore' - scriptLocation: 'inlineScript' - inlineScript: | - # '499b84ac-1321-427f-aa17-267ca6975798' for Azure DevOps - $token = az account get-access-token --query accessToken --resource 499b84ac-1321-427f-aa17-267ca6975798 -o tsv - Write-Host "Setting AzDO.DotnetPerfStarToken" - Write-Host "##vso[task.setvariable variable=AzDO.DotnetPerfStarToken]${token}" - - - powershell: | - mkdir "$(Pipeline.Workspace)/artifacts" - - $dropAppDirectory = Get-ChildItem -Directory -Path "$(Build.SourcesDirectory)/.packages/drop.app" - $dropAppVersion = $dropAppDirectory.Name - Write-Host "Detected drop.exe version: $dropAppVersion" - - $dropExePath = "$(Build.SourcesDirectory)/.packages/drop.app/$dropAppVersion/lib/net45/drop.exe" - Write-Host "Detected drop.exe path: $dropExePath" - - Write-Host "Downloading VS msbuild" - $patAuthEnvVar = "patVariable" - & "$dropExePath" get --patAuthEnvVar $patAuthEnvVar -u "$(MSBuildDropPath)\$(VSVersion)" -d "$(System.ArtifactsDirectory)/VSMSBuildDrop" - Write-Host "Download of VS msbuild finished" - - Write-Host "Copying VS msbuild to $(Pipeline.Workspace)/VSMSBuild" - Copy-Item -Path "$(System.ArtifactsDirectory)/VSMSBuildDrop/*" -Destination "$(Pipeline.Workspace)/VSMSBuild" -Recurse - Write-Host "Copy of VS msbuild finished" - displayName: Download msbuild vs drop - env: - patVariable: $(AzDO.DotnetPerfStarToken) - - - task: DownloadBuildArtifacts@1 - inputs: - buildType: specific - project: DevDiv - pipeline: $(_MsBuildCiPipelineId) - ${{ if eq(parameters.MSBuildBuildID, 'default') }}: - buildVersionToDownload: latestFromBranch - branchName: '${{parameters.MSBuildBranch}}' - ${{ else }}: - buildVersionToDownload: specific - buildId: ${{parameters.MSBuildBuildID}} - artifactName: bin - itemPattern: | - MSBuild.Bootstrap/*/net472/** - Microsoft.Build.Conversion/*/net472/Microsoft.Build.Conversion.Core.dll - Microsoft.Build.Engine/*/net472/Microsoft.Build.Engine.dll - MSBuildTaskHost/**/MSBuildTaskHost.exe - MSBuildTaskHost/**/MSBuildTaskHost.pdb - MSBuild/*/*/net472/MSBuild.exe* - downloadPath: '$(System.ArtifactsDirectory)/msbuild/artifacts/bin' - displayName: Download msbuild artifacts - - - powershell: | - Write-Host "Updating MSBuild dlls." - $(Build.SourcesDirectory)/scripts/Deploy-MSBuild.ps1 ` - -destination "$(Pipeline.Workspace)/VSMSBuild/$(VSVersion)/MSBuild/Current/Bin" ` - -binDirectory "$(System.ArtifactsDirectory)/msbuild/artifacts/bin" ` - -configuration Release ` - -makeBackup $false - - ls "$(Pipeline.Workspace)/VSMSBuild/$(VSVersion)" - Write-Host "Compressing msbuild files" - Get-ChildItem -Path "$(Pipeline.Workspace)/VSMSBuild/$(VSVersion)" | Compress-Archive -DestinationPath "$(Pipeline.Workspace)/artifacts/MSBuild.zip" - displayName: Dogfood msbuild dlls - - - task: PublishPipelineArtifact@1 - inputs: - targetPath: '$(Pipeline.Workspace)/artifacts' - artifactName: ExperimentalMSBuild - parallel: true - condition: always() - displayName: Publish crank assests artifacts diff --git a/.github/policies/resourceManagement.yml b/.github/policies/resourceManagement.yml index effb189ad06..c65e138b268 100644 --- a/.github/policies/resourceManagement.yml +++ b/.github/policies/resourceManagement.yml @@ -168,5 +168,36 @@ configuration: - addReply: reply: Hello @${issueAuthor}, I noticed that you’re changing an *.swr file or any file under src/Package/MSBuild.VSSetup.*. Please make sure to validate this change by an experimental VS insertion. This is accomplished by pushing to an exp/* branch, which requires write permissions to this repo. description: Remind to run VS Perf DDRITs when deployed assemblies change + - if: + - payloadType: Issues + - labelAdded: + label: breaking-change + then: + - addReply: + reply: >- + Refer to the [.NET SDK breaking change guidelines](https://github.com/dotnet/sdk/blob/main/documentation/project-docs/breaking-change-guidelines.md#required-process-for-all-net-sdk-breaking-changes) + description: Add breaking change doc instructions to issue + - if: + - payloadType: Pull_Request + - labelAdded: + label: breaking-change + then: + - addLabel: + label: needs-breaking-change-doc-created + - addReply: + reply: >- + Added `needs-breaking-change-doc-created` label because this PR has the `breaking-change` label. + + + When you commit this breaking change: + + + 1. [ ] Create and link to this PR and the issue a matching issue in the dotnet/docs repo using the [breaking change documentation template](https://aka.ms/dotnet/docs/new-breaking-change-issue), then remove this `needs-breaking-change-doc-created` label. + + 2. [ ] Ask a committer to mail the `.NET SDK Breaking Change Notification` email list. + + + You can refer to the [.NET SDK breaking change guidelines](https://github.com/dotnet/sdk/blob/main/documentation/project-docs/breaking-change-guidelines.md) + description: Add breaking change instructions to PR. onFailure: onSuccess: diff --git a/.github/workflows/SyncAnalyzerTemplateMSBuildVersion.yml b/.github/workflows/SyncAnalyzerTemplateMSBuildVersion.yml index 24498a60544..d70b263742e 100644 --- a/.github/workflows/SyncAnalyzerTemplateMSBuildVersion.yml +++ b/.github/workflows/SyncAnalyzerTemplateMSBuildVersion.yml @@ -142,14 +142,19 @@ jobs: const { data: pullRequests } = await github.rest.pulls.list({ owner: context.repo.owner, repo: context.repo.repo, - head: newBranch, + head: `${context.repo.owner}:${newBranch}`, base: baseBranch, state: 'open', }); if (pullRequests.length === 0) { + console.log(`No open pull requests found for branch ${newBranch} against ${baseBranch}.`); return true; } else { + // Log pull request details + pullRequests.forEach(pr => { + console.log(`Pull request #${pr.number}: ${pr.title} (created by ${pr.user.login})`); + }); return false; } } diff --git a/.github/workflows/labeler-build-predictor.yml b/.github/workflows/labeler-build-predictor.yml new file mode 100644 index 00000000000..8a12b312db0 --- /dev/null +++ b/.github/workflows/labeler-build-predictor.yml @@ -0,0 +1,17 @@ +name: "Labeler: Build Predictor App" + +on: + # Allow dispatching the workflow via the Actions UI + workflow_dispatch: + inputs: + rebuild: + description: "Force a rebuild of the app" + type: boolean + +jobs: + build-predictor: + permissions: + actions: write + uses: dotnet/issue-labeler/.github/workflows/build-predictor.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + rebuild: ${{ inputs.rebuild }} diff --git a/.github/workflows/labeler-cache-retention.yml b/.github/workflows/labeler-cache-retention.yml new file mode 100644 index 00000000000..26a09ee7244 --- /dev/null +++ b/.github/workflows/labeler-cache-retention.yml @@ -0,0 +1,13 @@ +name: "Labeler: Cache Retention" + +on: + schedule: + - cron: "10 3 * * *" # 3:10 every day (arbitrary time daily, modified to different values in each repository) + + workflow_dispatch: + +jobs: + cache-retention: + # Do not run the workflow on forks outside the 'dotnet' org + if: ${{ github.repository_owner == 'dotnet' }} + uses: dotnet/issue-labeler/.github/workflows/cache-retention.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 diff --git a/.github/workflows/labeler-predict-issues.yml b/.github/workflows/labeler-predict-issues.yml new file mode 100644 index 00000000000..e560988577d --- /dev/null +++ b/.github/workflows/labeler-predict-issues.yml @@ -0,0 +1,33 @@ +name: "Labeler: Predict Issue Labels" + +on: + # Only automatically predict area labels when issues are originally opened + issues: + types: opened + + # Allow dispatching the workflow via the Actions UI, specifying ranges of numbers + workflow_dispatch: + inputs: + issue_numbers: + description: "Issue Numbers (comma-separated list of ranges)" + type: string + model_cache_key: + description: "The cache key suffix to use for loading the model" + type: string + required: true + default: "LIVE" + +jobs: + predict-issues: + # Do not run the workflow on forks outside the 'dotnet' org + if: ${{ github.repository_owner == 'dotnet' && (inputs.issue_numbers || github.event.issue.number) }} + permissions: + issues: write + uses: dotnet/issue-labeler/.github/workflows/predict-issues.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + model_cache_key: ${{ inputs.model_cache_key }} + issue_numbers: ${{ inputs.issue_numbers || github.event.issue.number }} + label_prefix: "Area: " + threshold: 0.40 + # default_label: "needs-area-label" + diff --git a/.github/workflows/labeler-predict-pulls.yml b/.github/workflows/labeler-predict-pulls.yml new file mode 100644 index 00000000000..fba01a5d324 --- /dev/null +++ b/.github/workflows/labeler-predict-pulls.yml @@ -0,0 +1,44 @@ +name: "Labeler: Predict Pull Labels" + +on: + # Per to the following documentation: + # https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/events-that-trigger-workflows#pull_request_target + # + # The `pull_request_target` event runs in the context of the base of the pull request, rather + # than in the context of the merge commit, as the `pull_request` event does. This prevents + # execution of unsafe code from the head of the pull request that could alter the repository + # or steal any secrets you use in your workflow. This event allows your workflow to do things + # like label or comment on pull requests from forks. + # + # Only automatically predict area labels when pull requests are first opened + pull_request_target: + types: opened + branches: + - 'main' + - 'vs*' + + # Allow dispatching the workflow via the Actions UI, specifying ranges of numbers + workflow_dispatch: + inputs: + pull_numbers: + description: "Pull Numbers (comma-separated list of ranges)" + type: string + model_cache_key: + description: "The cache key suffix to use for loading the model" + type: string + required: true + default: "LIVE" + +jobs: + predict-pulls: + # Do not run the workflow on forks outside the 'dotnet' org + if: ${{ github.repository_owner == 'dotnet' && (inputs.pull_numbers || github.event.number) }} + permissions: + pull-requests: write + uses: dotnet/issue-labeler/.github/workflows/predict-pulls.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + model_cache_key: ${{ inputs.model_cache_key }} + pull_numbers: ${{ inputs.pull_numbers || github.event.number }} + label_prefix: "Area: " + threshold: 0.40 + # default_label: "needs-area-label" diff --git a/.github/workflows/labeler-promote.yml b/.github/workflows/labeler-promote.yml new file mode 100644 index 00000000000..97f40afa8f1 --- /dev/null +++ b/.github/workflows/labeler-promote.yml @@ -0,0 +1,42 @@ +name: "Labeler: Promote Models" + +on: + # Dispatched via the Actions UI, promotes the staged models from + # a staging slot into the prediction environment + workflow_dispatch: + inputs: + promote_issues: + description: "Issues: Promote Model" + type: boolean + required: true + promote_pulls: + description: "Pulls: Promote Model" + type: boolean + required: true + model_cache_key: + description: "The cache key suffix to promote into the 'LIVE' cache" + type: string + required: true + default: "staging" + backup_cache_key: + description: "The cache key suffix to use for backing up the currently promoted model" + type: string + default: "backup" + +permissions: + actions: write + +jobs: + labeler-promote-issues: + if: ${{ inputs.promote_issues }} + uses: dotnet/issue-labeler/.github/workflows/promote-issues.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + model_cache_key: ${{ inputs.model_cache_key }} + backup_cache_key: ${{ inputs.backup_cache_key }} + + labeler-promote-pulls: + if: ${{ inputs.promote_pulls }} + uses: dotnet/issue-labeler/.github/workflows/promote-pulls.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + model_cache_key: ${{ inputs.model_cache_key }} + backup_cache_key: ${{ inputs.backup_cache_key }} diff --git a/.github/workflows/labeler-train.yml b/.github/workflows/labeler-train.yml new file mode 100644 index 00000000000..90095eb88ba --- /dev/null +++ b/.github/workflows/labeler-train.yml @@ -0,0 +1,63 @@ +name: "Labeler: Train Models" + +on: + # Dispatched via the Actions UI, stages new models for promotion consideration + # Each step of the workflow can be run independently: Download, Train, and Test + workflow_dispatch: + inputs: + download_issues: + description: "Issues: Download Data" + type: boolean + default: true + train_issues: + description: "Issues: Train Model" + type: boolean + default: true + test_issues: + description: "Issues: Test Model" + type: boolean + default: true + download_pulls: + description: "Pulls: Download Data" + type: boolean + default: true + train_pulls: + description: "Pulls: Train Model" + type: boolean + default: true + test_pulls: + description: "Pulls: Test Model" + type: boolean + default: true + repository: + description: "Repository to train the models from" + + data_limit: + description: "Max number of items to include in the model" + type: number + + cache_key_suffix: + description: "The cache key suffix to use for staging data/models (use 'LIVE' to bypass staging)" + type: string + required: true + default: "staging" + +jobs: + labeler-train: + permissions: + issues: read + pull-requests: read + actions: write + uses: dotnet/issue-labeler/.github/workflows/train.yml@f0c098669828a134c0313adf3f58c1909e555d86 # v1.0.1 + with: + download_issues: ${{ inputs.download_issues }} + train_issues: ${{ inputs.train_issues }} + test_issues: ${{ inputs.test_issues }} + download_pulls: ${{ inputs.download_pulls }} + train_pulls: ${{ inputs.train_pulls }} + test_pulls: ${{ inputs.test_pulls }} + data_limit: ${{ inputs.data_limit && fromJSON(inputs.data_limit) || 0 }} + cache_key_suffix: ${{ inputs.cache_key_suffix }} + repository: ${{ inputs.repository }} + label_prefix: "Area: " + threshold: 0.40 diff --git a/.github/workflows/perfstar-branch.yml b/.github/workflows/perfstar-branch.yml new file mode 100644 index 00000000000..f7f52492417 --- /dev/null +++ b/.github/workflows/perfstar-branch.yml @@ -0,0 +1,55 @@ +name: Create Perf Branch on /perfstar comment + +on: + issue_comment: + types: [created] + +permissions: + contents: write + pull-requests: read + +jobs: + create_perf_branch: + if: | + github.event.issue.pull_request && + github.event.comment.body == '/perfstar' && + contains(fromJSON('["COLLABORATOR", "MEMBER", "OWNER"]'), github.event.comment.author_association) + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Get PR information + id: pr_info + run: | + PR_DATA=$(gh pr view ${{ github.event.issue.number }} --json headRefName,headRefOid,headRepository) + HEAD_REF=$(echo $PR_DATA | jq -r '.headRefName') + HEAD_SHA=$(echo $PR_DATA | jq -r '.headRefOid') + HEAD_REPO=$(echo $PR_DATA | jq -r '.headRepository.nameWithOwner') + + echo "pr_head_branch=${HEAD_REF}" >> $GITHUB_OUTPUT + echo "pr_head_sha=${HEAD_SHA}" >> $GITHUB_OUTPUT + echo "pr_head_repo=${HEAD_REPO}" >> $GITHUB_OUTPUT + echo "new_branch_name=perf/${HEAD_REF}" >> $GITHUB_OUTPUT + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Create and push perf branch + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + + # Ensure we can access the PR's commits (especially important for forks) + git fetch origin pull/${{ github.event.issue.number }}/head:pr-${{ github.event.issue.number }}-head + + # Create branch from PR head + git checkout -b ${{ steps.pr_info.outputs.new_branch_name }} ${{ steps.pr_info.outputs.pr_head_sha }} + + # Merge main branch + git fetch origin main + git merge origin/main --no-ff --no-edit -m "Merge main into ${{ steps.pr_info.outputs.new_branch_name }} for perf testing" + + # Push branch + git push origin ${{ steps.pr_info.outputs.new_branch_name }} diff --git a/.vsts-dotnet-ci.yml b/.vsts-dotnet-ci.yml index 624666c9360..f8f6e6e69a0 100644 --- a/.vsts-dotnet-ci.yml +++ b/.vsts-dotnet-ci.yml @@ -276,23 +276,6 @@ jobs: continueOnError: true condition: eq(variables.onlyDocChanged, 0) -# Unavailable in dnceng-public as of 9/1/2022; should be restored soon. -# - job: RichCodeNavIndex -# displayName: "Windows Code Indexing" -# pool: -# vmImage: 'windows-2022' -# steps: -# - task: BatchScript@1 -# displayName: build.cmd -# inputs: -# filename: 'build.cmd' -# - task: RichCodeNavIndexer@0 -# displayName: RichCodeNav Upload -# inputs: -# languages: 'csharp' -# continueOnError: true -# condition: succeeded() - - job: CoreBootstrappedOnLinux displayName: "Linux Core" dependsOn: IfOnlyDocumentionChanged @@ -311,7 +294,7 @@ jobs: Token: $(dn-bot-dnceng-artifact-feeds-rw) - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) displayName: CI Build - env: + env: MSBUILDUSESERVER: "1" - task: PublishTestResults@2 displayName: Publish .NET Test Results @@ -378,7 +361,7 @@ jobs: Token: $(dn-bot-dnceng-artifact-feeds-rw) - bash: . 'eng/cibuild_bootstrapped_msbuild.sh' --onlyDocChanged $(onlyDocChanged) displayName: CI Build - env: + env: MSBUILDUSESERVER: "1" - task: PublishTestResults@2 displayName: Publish .NET Test Results diff --git a/Directory.Build.targets b/Directory.Build.targets index f2c71b74a0e..3538e50a581 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -7,6 +7,11 @@ $(TargetFileName) + + + false + + diff --git a/azure-pipelines/vs-insertion-experimental.yml b/azure-pipelines/vs-insertion-experimental.yml index 965ceb7d478..ab2ce364131 100644 --- a/azure-pipelines/vs-insertion-experimental.yml +++ b/azure-pipelines/vs-insertion-experimental.yml @@ -2,16 +2,6 @@ trigger: none name: $(Date:yyyyMMdd).$(Rev:r) -# Since our release branch is the one flowing into main -# we will keep our main experimental insertions to make sure everything is alright -schedules: - - cron: '0 3 * * 1,3,5' # Runs every Monday, Wednesday and Friday at 3AM UTC - displayName: Experimental VS insertion main - branches: - include: - - main - always: false # Don't run if there are no code changes - resources: pipelines: - pipeline: 'MSBuild' diff --git a/azure-pipelines/vs-insertion.yml b/azure-pipelines/vs-insertion.yml index 2f2046c4687..2f8d8732f04 100644 --- a/azure-pipelines/vs-insertion.yml +++ b/azure-pipelines/vs-insertion.yml @@ -12,13 +12,13 @@ trigger: none pr: none name: $(Date:yyyyMMdd).$(Rev:r) -# schedules: -# - cron: '0 3 * * 1-5' # Runs every weekday at 3AM UTC -# displayName: Daily VS insertion main -# branches: -# include: -# - main -# always: false # Don't run if there are no code changes +schedules: + - cron: '0 3 * * 1-5' # Runs every weekday at 3AM UTC + displayName: Daily VS insertion main + branches: + include: + - main + always: false # Don't run if there are no code changes resources: pipelines: @@ -66,7 +66,7 @@ variables: # `auto` should work every time and selecting a branch in parameters is likely to fail due to incompatible versions in MSBuild and VS - name: AutoInsertTargetBranch ${{ if eq(variables['Build.SourceBranchName'], 'vs17.14') }}: - value: 'main' + value: 'rel/d17.14' ${{ elseif eq(variables['Build.SourceBranchName'], 'vs17.13') }}: value: 'rel/d17.13' ${{ elseif eq(variables['Build.SourceBranchName'], 'vs17.12') }}: diff --git a/documentation/specs/BuildCheck/Codes.md b/documentation/specs/BuildCheck/Codes.md index 3557170f825..2f0d197c2b1 100644 --- a/documentation/specs/BuildCheck/Codes.md +++ b/documentation/specs/BuildCheck/Codes.md @@ -16,12 +16,12 @@ Report codes are chosen to conform to suggested guidelines. Those guidelines are | [BC0202](#bc0202---property-first-declared-after-it-was-used) | Warning | Project | 9.0.100 | Property first declared after it was used. | | [BC0203](#bc0203----property-declared-but-never-used) | None | Project | 9.0.100 | Property declared but never used. | | [BC0301](#bc0301---building-from-downloads-folder) | None | Project | 9.0.300 | Building from Downloads folder. | - +| [BC0302](#bc0302---building-using-the-exec-task) | Warning | N/A | 9.0.300 | Building using the Exec task. | Notes: * What does the 'N/A' scope mean? The scope of checks are only applicable and configurable in cases where evaluation-time data are being used and the source of the data is determinable and available. Otherwise the scope of whole build is always checked. * How can you alter the default configuration? [Please check the Configuration section of the BuildCheck documentation](./BuildCheck.md#sample-configuration) - * To enable verbose logging in order to troubleshoot issue(s), enable [binary logging](https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Binary-Log.md#msbuild-binary-log-overview + * To enable verbose logging in order to troubleshoot issue(s), enable [binary logging](https://github.com/dotnet/msbuild/blob/main/documentation/wiki/Binary-Log.md#msbuild-binary-log-overview) _Cmd:_ ```cmd dotnet build -bl -check @@ -137,7 +137,6 @@ dotnet build my-multi-target.csproj /p:TargetFramework=net9.0 Make sure the Target Framework is specified appropriately for your project. - ## BC0201 - Usage of undefined property. @@ -197,6 +196,13 @@ Placing project files into Downloads folder (or any other folder that cannot be Place your projects into trusted locations - including cases when you intend to only open the project in IDE. + +## BC0302 - Building using the Exec task. + +"The 'Exec' task should not be used to build projects." + +Building projects using the dotnet/msbuild/nuget CLI in the `Exec` task is not recommended, as it spawns a separate build process that the MSBuild engine cannot track. Please use the [MSBuild task](https://learn.microsoft.com/visualstudio/msbuild/msbuild-task) instead. +


diff --git a/documentation/specs/VS-OpenTelemetry.md b/documentation/specs/VS-OpenTelemetry.md new file mode 100644 index 00000000000..59d1f6e5d17 --- /dev/null +++ b/documentation/specs/VS-OpenTelemetry.md @@ -0,0 +1,198 @@ +# Telemetry via OpenTelemetry design + +VS OTel provide packages compatible with ingesting data to their backend if we instrument it via OpenTelemetry traces (System.Diagnostics.Activity). +VS OTel packages are not open source so we need to conditionally include them in our build only for VS and MSBuild.exe + +> this formatting is a comment describing how the implementation turned out in 17.14 when our original goals were different + +[Onepager](https://github.com/dotnet/msbuild/blob/main/documentation/specs/proposed/telemetry-onepager.md) + +## Concepts + +It's a bit confusing how things are named in OpenTelemetry and .NET and VS Telemetry and what they do. + +| OTel concept | .NET/VS | Description | +| --- | --- | --- | +| Span/Trace | System.Diagnostics.Activity | Trace is a tree of Spans. Activities can be nested.| +| Tracer | System.Diagnostics.ActivitySource | Creates activites. | +| Processor/Exporter | VS OTel provided default config | filters and saves telemetry as files in a desired format | +| TracerProvider | OTel SDK TracerProvider | Singleton that is aware of processors, exporters and Tracers and listens (in .NET a bit looser relationship because it does not create Tracers just hooks to them) | +| Collector | VS OTel Collector | Sends to VS backend | + +## Requirements + +### Performance + +- If not sampled, no infra initialization overhead. +- Avoid allocations when not sampled. +- Has to have no impact on Core without opting into tracing, small impact on Framework +- No regression in VS perf ddrit scenarios. + +> there is an allocation regression when sampled, one of the reasons why it's not enabled by default + +### Privacy + +- Hashing data points that could identify customers (e.g. names of targets) +- Opt out capability + +### Security + +- Providing or/and documenting a method for creating a hook in Framework MSBuild +- If custom hooking solution will be used - document the security implications of hooking custom telemetry Exporters/Collectors in Framework +- other security requirements (transportation, rate limiting, sanitization, data access) are implemented by VS Telemetry library or the backend + +> hooking in Framework not implemented + +### Data handling + +- Implement head [Sampling](https://opentelemetry.io/docs/concepts/sampling/) with the granularity of a MSBuild.exe invocation/VS instance. +- VS Data handle tail sampling in their infrastructure not to overwhelm storage with a lot of build events. + +#### Data points + +The data sent via VS OpenTelemetry is neither a subset neither a superset of what is sent to SDK telemetry and it is not a purpose of this design to unify them. + +##### Basic info + +- Build duration +- Host +- Build success/failure +- Version +- Target (hashed) + +##### Evnironment + +- SAC (Smart app control) enabled + +##### Features + +- BuildCheck enabled +- Tasks runtimes and memory usage +- Tasks summary - whether they come from Nuget or are custom +- Targets summary - how many loaded and executed, how many come from nuget, how many come from metaproject + +The design should allow for easy instrumentation of additional data points. +> current implementation has only one datapoint and that is the whole build `vs/msbuild/build`, the instrumentaiton of additional datapoints is gated by first checking that telemetry is running and using `Activity` classes only in helper methods gated by `[MethodImpl(MethodImplOptions.NoInlining)]` to avoid System.Diagnostics.DiagnosticSource dll load. + +## Core `dotnet build` scenario + +- Telemetry should not be collected via VS OpenTelemetry mechanism because it's already collected in sdk. +- opt in to initialize the ActivitySource to avoid degrading performance. +- [baronfel/otel-startup-hook: A .NET CLR Startup Hook that exports OpenTelemetry metrics via the OTLP Exporter to an OpenTelemetry Collector](https://github.com/baronfel/otel-startup-hook/) and similar enable collecting telemetry data locally by listening to the ActivitySource prefix defined in MSBuild. + +> this hook can be used when the customer specifies that they want to listen to the prefix `Microsoft.VisualStudio.OpenTelemetry.MSBuild`, opt in by setting environment variables `MSBUILD_TELEMETRY_OPTIN=1`,`MSBUILD_TELEMETRY_SAMPLE_RATE=1.0` + +## Standalone MSBuild.exe scenario + +- Initialize and finalize in Xmake.cs + ActivitySource, TracerProvider, VS Collector +- overhead of starting VS collector is nonzero +- head sampling should avoid initializing if not sampled + +## VS in proc (devenv) scenario + +- VS can call `BuildManager` in a thread unsafe way the telemetry implementation has to be mindful of [BuildManager instances acquire its own BuildTelemetry instance by rokonec · Pull Request #8444 · dotnet/msbuild](https://github.com/dotnet/msbuild/pull/8444) + - ensure no race conditions in initialization + - only 1 TracerProvider with VS defined processing should exist +- Visual Studio should be responsible for having a running collector, we don't want this overhead in MSBuild and eventually many will use it + +> this was not achieved in 17.14 so we start collector every time + +## Implementation and MSBuild developer experience + +### ActivitySource names + +- Microsoft.VisualStudio.OpenTelemetry.MSBuild.Default + +### Sampling + +Our estimation from VS and SDK data is that there are 10M-100M build events per day. +For proportion estimation (of fairly common occurence in the builds), with not very strict confidnece (95%) and margin for error (5%) sampling 1:25000 would be enough. + +- this would apply for the DefaultActivitySource +- other ActivitySources could be sampled more frequently to get enough data +- Collecting has a cost, especially in standalone scenario where we have to start the collector. We might decide to undersample in standalone to avoid performance frequent impact. +- We want to avoid that cost when not sampled, therefore we prefer head sampling. +- Enables opt-in and opt-out for guaranteed sample or not sampled. +- nullable ActivitySource, using `?` when working with them, we can be initialized but not sampled -> it will not reinitialize but not collect telemetry. + +- for 17.14 we can't use the new OTel assemblies and their dependencies, so everything has to be opt in. +- eventually OpenTelemetry will be available and usable by default +- We can use experiments in VS to pass the environment variable to initialize + +> Targeted notification can be set that samples 100% of customers to which it is sent + +### Initialization at entrypoints + +- There are 2 entrypoints: + - for VS in BuildManager.BeginBuild + - for standalone in Xmake.cs Main + +### Exiting + +Force flush TracerProvider's exporter in BuildManager.EndBuild. +Dispose collector in Xmake.cs at the end of Main. + +### Configuration + +- Class that's responsible for configuring and initializing telemetry and handles optouts, holding tracer and collector. +- Wrapping source so that it has correct prefixes for VS backend to ingest. + +### Instrumenting + +2 ways of instrumenting: + +#### Instrument areas in code running in the main process + +```csharp +using (Activity? myActivity = OpenTelemetryManager.DefaultActivitySource?.StartActivity(TelemetryConstants.NameFromAConstantToAvoidAllocation)) +{ +// something happens here + +// add data to the trace +myActivity?.WithTag("SpecialEvent","fail") +} +``` + +Interface for classes holding telemetry data + +```csharp +IActivityTelemetryDataHolder data = new SomeData(); +... +myActivity?.WithTags(data); +``` + +> currently this should be gated in a separate method to avoid System.DiagnosticDiagnosticsource dll load. + +#### Default Build activity in EndBuild + +- this activity would always be created at the same point when sdk telemetry is sent in Core +- we can add data to it that we want in general builds +- the desired count of data from this should control the sample rate of DefaultActivitySource + +#### Multiple Activity Sources + +We want to create ActivitySources with different sample rates, this requires either implementation server side or a custom Processor. + +We potentially want apart from the Default ActivitySource: + +1. Other activity sources with different sample rates (in order to get significant data for rarer events such as custom tasks). +2. a way to override sampling decision - ad hoc starting telemetry infrastructure to catch rare events + +- Create a way of using a "HighPrioActivitySource" which would override sampling and initialize Collector in MSBuild.exe scenario/tracerprovider in VS. +- this would enable us to catch rare events + +> not implemented + +### Implementation details + +- `OpenTelemetryManager` - singleton that manages lifetime of OpenTelemetry objects listening to `Activity`ies, start by initializing in `Xmake` or `BuildManager`. +- Task and Target data is forwarded from worker nodes via `TelemetryForwarder` and `InternalTelemetryForwardingLogger` and then aggregated to stats and serialized in `TelemetryDataUtils` and attached to the default `vs/msbuild/build` event. + +## Future work when/if we decide to invest in telemetry again + +- avoid initializing/finalizing collector in VS when there is one running +- multiple levels of sampling for different types of events +- running by default with head sampling (simplifies instrumentation with `Activity`ies) +- implement anonymization consistently in an OTel processor and not ad hoc in each usage +- add datapoints helping perf optimization decisions/ reliability investigations diff --git a/documentation/specs/event-source.md b/documentation/specs/event-source.md index 198791bb356..dcaab9c9cba 100644 --- a/documentation/specs/event-source.md +++ b/documentation/specs/event-source.md @@ -3,6 +3,7 @@ [EventSource](https://docs.microsoft.com/en-us/dotnet/api/system.diagnostics.tracing.eventsource?view=netframework-4.8) is the tool that allows Event Tracing for Windows (ETW) used in MSBuild. Among its useful features, functions with names ending in "start" and "stop" correlate between calls such that it can automatically record how long the event between the two calls took. It also provides an easy way to cheaply opt in or out, log auxiliary messages in addition to time, and add progress updates in the middle of an event as needed. ## EventSource in MSBuild + EventSource is primarily used to profile code. For MSBuild specifically, a major goal is to reduce the time it takes to run, as measured (among other metrics) by the Regression Prevention System (RPS), i.e., running specific scenarios. To find which code segments were likely candidates for improvement, EventSources were added around a mix of code segments. Larger segments that encompass several steps within a build occur nearly every time MSBuild is run and take a long time. They generally run relatively few times. Smaller methods with well-defined purposes may occur numerous times. Profiling both types of events provides both broad strokes to identify large code segments that underperform and, more specifically, which parts of them. Profiled functions include: | Event | Description | @@ -20,7 +21,7 @@ EventSource is primarily used to profile code. For MSBuild specifically, a major | ExecuteTaskYield | Requests to yield the node, often while the task completes other work. | | ExpandGlob | Identifies a list of files that correspond to an item, potentially with a wildcard. | | GenerateResourceOverall | Uses resource APIs to transform resource files into strongly-typed resource classes. | -| LoadDocument | Loads an XMLDocumentWithLocation from a path. +| LoadDocument | Loads an XMLDocumentWithLocation from a path. | | MSBuildExe | Executes MSBuild from the command line. | | MSBuildServerBuild | Executes a build from the MSBuildServer node. | | PacketReadSize | Reports the size of a packet sent between nodes. Note that this does not include time information. | diff --git a/documentation/specs/low-priority-switch.md b/documentation/specs/low-priority-switch.md index ba33826a89e..bec5a72fc74 100644 --- a/documentation/specs/low-priority-switch.md +++ b/documentation/specs/low-priority-switch.md @@ -17,7 +17,6 @@ Visual Studio, on the other hand, should always run at normal priority. This ens 4. Any reused nodes are at the priority they themselves specify. Normal priority nodes are actually at normal priority, and low priority nodes are actually at BelowNormal priority. 5. All nodes are at the priority they should be when being used to build even if a normal priority process had connected to them as normal priority worker nodes, and they are now executing a low priority build. - ## Non-goals Perfect parity between windows and mac or linux. Windows permits processes to raise their own priority or that of another process, whereas other operating systems do not. This is very efficient, so we should use it. As we expect this feature to be used in Visual Studio, we anticipate it being less used on mac and linux, hence not being as high priority to make it just as efficient. @@ -27,6 +26,7 @@ Perfect parity between windows and mac or linux. Windows permits processes to ra Each node (including worker nodes) initially takes its priority from its parent process. Since we now need the priority to align with what it is passed instead of its parent, attempt to adjust priority afterwards if necessary as part of node startup. BuildManager.cs remembers the priority of the previous build it had executed. If that was set to a value that differs from the priority of the current build: + 1. On windows or when decreasing the priority: lowers the priority of all connected nodes 2. On linux and mac when increasing the priority: disconnects from all nodes. diff --git a/documentation/specs/project-cache.md b/documentation/specs/project-cache.md index b0ce961313d..90d19466fdc 100644 --- a/documentation/specs/project-cache.md +++ b/documentation/specs/project-cache.md @@ -1,8 +1,10 @@ -# Summary +# Project Cache + +## Summary Project cache is a new assembly-based plugin extension point in MSBuild which determines whether a build request (a project) can be skipped during build. The main expected benefit is reduced build times via [caching and/or distribution](static-graph.md#weakness-of-the-old-model-caching-and-distributability). -# Motivation +## Motivation As the introduction to [static graph](static-graph.md#what-is-static-graph-for) suggests, large and complex repos expose the weaknesses in MSBuild's scheduling and incrementality models as build times elongate. This project cache plugin lets MSBuild natively communicate with existing tools that enable build caching and/or distribution, enabling true scalability. @@ -10,31 +12,33 @@ Visual Studio is one beneficiary. This plugin inverts dependencies among build s This change also simplifies and unifies user experiences. MSBuild works the same from Visual Studio or the command line without dramatically changing how it works. -# Plugin requirements +## Plugin requirements - The plugin should tell MSBuild whether a build request needs building. If a project is skipped, then the plugin needs to ensure that: - it makes the filesystem look as if the project built - it returns sufficient information back to MSBuild such that MSBuild can construct a valid [`BuildResult`](/src/Build/BackEnd/Shared/BuildResult.cs#L30-L33) for its internal scheduling logic, such that future requests to build a skipped project are served directly from MSBuild's internal caches. -# High-level design +## High-level design Conceptually, there are two parts of caching: "cache get" and "cache add". "Cache get" is MSBuild asking the plugin if it wants to handle a build request, ie by fetching from some cache. "Cache add" is, upon cache miss, MSBuild providing enough information to the plugin during the build of the build request for the plugin to add the results to its cache and safely be able to retrieve it for some future build. The "cache get" functionality was introduced in 16.9, while "cache add" was added in 17.8. -## Plugin discovery +### Plugin discovery - Plugin dlls are discovered by MSBuild via a new special purpose `ProjectCachePlugin` [items](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-items). - These items can get injected into a project's import graph by package managers via the [PackageReference](https://docs.microsoft.com/en-us/nuget/consume-packages/package-references-in-project-files) item. - MSBuild will discover the plugin by searching project evaluations for `ProjectCachePlugin` items. -```xml - - - -``` + + ```xml + + + + ``` + - Programmatic usage of `BuildManager` can also set `BuildParameters.ProjectCacheDescriptor` to apply a plugin to all requests. -## Plugin lifetime +### Plugin lifetime - Plugin instances reside only in the `BuildManager` node. Having it otherwise (plugin instances residing in all nodes) means forcing the plugins to either deal with distributed state or implement a long lived service. We consider this high complexity cost to not be worth it. We also want to avoid serializing the `ProjectInstance` between nodes, which is expensive. - `BuildManager.BeginBuild` calls `ProjectCacheBase.BeginBuildAsync` on all discovered plugins. This allows plugins to start any required initialization work. It does not wait for the plugins to fully initialize, ie it is a "fire-and-forget" call at this point. The first query on the plugin will wait for plugin initialization. @@ -43,10 +47,10 @@ The "cache get" functionality was introduced in 16.9, while "cache add" was adde - The plugin instance will get called in reverse topological sort order (from referenced projects up towards referencing projects). This happens when performing a graph build (`/graph`), Visual Studio solution builds, and commonly in higher build engines. - Only the top-level build requests are checked against the cache. Build requests issued recursively from the top-level requests, for example a project building its dependencies, are not checked against the cache. However, because the build requests are assumed to be issued in reverse topological sort order, those requests should have already been built and present in MSBuild's internal result cache, provided either by the project cache plugin or real builds. A consequence of this is that projects which are not well-described in the graph (e.g. using `` tasks directly) will not benefit from the cache. -## Cache get scenario +### Cache get scenario - For each [`BuildRequestData`](/src/Build/BackEnd/BuildManager/BuildRequestData.cs#L83) ([`ProjectInstance`](/src/Build/Instance/ProjectInstance.cs#L71), Global Properties, Targets) submitted to the [`BuildManager`](/src/Build/BackEnd/BuildManager/BuildManager.cs#L38), MSBuild asks the plugin whether to build the request or not. - + - If the `BuildRequestData` is based on a project path instead of a `ProjectInstance`, the project is evaluated by the `BuildManager`. - If the plugin decides to build, then MSBuild proceeds building the project as usual. - If the plugin decides to skip the build, it needs to return back to MSBuild the target results that the build request would have produced. It can either provide the results directly, or instruct MSBuild to run a set of less expensive targets on the projects with the same effect as the expensive targets ("proxy targets"). @@ -62,7 +66,7 @@ The "cache get" functionality was introduced in 16.9, while "cache add" was adde - Best: A real `BuildResult` from a previous build is provided. This can either be done by serializing the `HandleProjectFinishedAsync`, or when the plugin's infrastructure (e.g. CloudBuild or AnyBuild builder nodes) runs and caches the build, it can tell MSBuild to serialize the BuildResult to a file via [BuildParameters.OutputResultsCacheFile](/src/Build/BackEnd/BuildManager/BuildParameters.cs#L767) or the `/outputResultsCache` command line argument. Then, on cache hits, the plugins deserialize the `BuildResult` and send it back to MSBuild. This is the most correct option, as it requires neither guessing nor proxy targets. Whatever a previous build did, that's exactly what's returned. - Potential Issue: serialization format may change between writing and reading the `BuildResult`, especially if binary serialization is used. -## Cache add scenario +### Cache add scenario - Upon a cache miss, MSBuild will generally handle a request as normal, ie by building it. - MSBuild uses [Detours](https://github.com/microsoft/Detours) to observe file accesses of the worker nodes. To facilitate the plugin being able to handle future builds, it forwards this information as well as the build result to the plugin for it to use as desired, for example to add to a cache. @@ -72,9 +76,10 @@ The "cache get" functionality was introduced in 16.9, while "cache add" was adde - Due to the experimental nature of the feature, `/ReportFileAccesses` is only available with MSBuild.exe (ie. the Visual Studio install; not `dotnet`), only for the x64 flavor (not x86 or arm64), and only from the command-line. The Visual Studio IDE does not set `BuildParameters.ReportFileAccesses`. - As described above, it is recommended to serialize the `BuildResult` from `HandleProjectFinishedAsync` for later replay. -# APIs and calling patterns +## APIs and calling patterns + +### Plugin API -## Plugin API [ProjectCachePluginBase](/src/Build/BackEnd/Components/ProjectCache/ProjectCachePluginBase.cs) is an abstract class which plugin implementors will subclass. See the [Plugin implementation guidance and simple example design](#plugin-implementation-guidance-and-simple-example-design) section for guidance for plugin implementations. @@ -97,14 +102,14 @@ This can then be accessed by the plugin in `BeginBuildAsync` as a dictionary via Note: As it is likely that plugins will be distributed through NuGet packages and those packages would define the `ProjectCachePlugin` item in a props or targets file in the package, it's recommended for plugin authors to have settings backed by MSBuild properties as in the example above. This allows the user to easily configure a plugin simply by setting the properties and including the `PackageReference`. -## Enabling from command line +### Enabling from command line - Requires `/graph` to light up cache get scenarios. - Requires `/reportfileaccesses` to light up cache add scenarios. - The static graph has all the project instances in the same process, making it easy to find and keep plugin instances in one process. - MSBuild constructs the static graph and build bottom up, so by the time a project is considered, all of its references and their build results are already present in the Scheduler. -## Enabling from Visual Studio, a temporary workaround +### Enabling from Visual Studio, a temporary workaround - Ideally, Visual Studio would provide a `ProjectGraph` instance. Until that happens, a workaround is needed. - The workaround logic activates only when MSBuild detects that it's running under VS. @@ -113,29 +118,30 @@ Note: As it is likely that plugins will be distributed through NuGet packages an - Plugins will be given the graph entry points instead of the entire graph in this scenario. - There is currently no way to enable cache add scenarios in Visual Studio. -# Detours (cache add scenario) +## Detours (cache add scenario) In order for MSBuild to observe the file accesses as part of the build, it uses Detours on the worker nodes. In this way the Scheduler node will emit events for all file accesses done by the worker nodes. As the Scheduler knows what build request a worker node is working on at any given moment, it is able to properly associate the file access with a build request and dispatch these augmented events to plugins via the plugins' `HandleFileAccess` and `HandleProcess` implementations. Note that the Scheduler node cannot use Detours on itself, so the in-proc node is disabled when repoting file accesses. Additionally task yielding is disabled since it would leave to improperly associated file accesses. -## Pipe synchronization +### Pipe synchronization Because the Detours implementation being used communicates over a pipe, and nodes communicate over a pipe as well, and pipes are async, there is some coordination required to ensure that file accesses are associated with the proper build request. For example, if a "project finished" signal comes through the node communication pipe, but the detours pipe still has a queue of file accesses which have not been processed yet, those file accesses might be processed after the worker node has moved onto some other project. To address this problem, when a worker node finishes a project it will emit a dummy file access with a specific format known to MSBuild. When the scheduler node receives as "project finished" event over the node communication pipe, it will wait to determine that the project is actually finished until it also receives the dummy file access. This ensures that the all file accesses associated with the project have fully flushed from the pipe before the scheduler determines the project is finished and schedules new work to the worker node (which would trigger new file accesses). -# Plugin implementation guidance and simple example design +## Plugin implementation guidance and simple example design The following will describe a very basic (and not very correct) plugin implementation. In practice, plugins will have to choose the specific level of correctness they're willing to trade off for the ability to get cache hits. Any machine state *could* impact build results, and the plugin implementation will need to determine what state matters and what doesn't. An obvious example to consider would be the content of the project file. An example which has trade-offs would be the processes' environment variables. Even the current time could possibly impact the build ("if Tuesday copy this file"), but if considered caching would be quite infeasible. -## Fingerprinting +### Fingerprinting A "fingerprint" describes each unique input which went into the building a build request. The more granular the fingerprint, the more "correct" the caching is, as described above. In this example, we will only consider the following as inputs, and thus part of the fingerprint: + - The global properties of the build request (eg `Configuration=Debug`, `Platform=AnyCPU`) - The content hash of the project file - The content hash of files defined in specific items we know contribute to the build, like `` and `` @@ -147,13 +153,13 @@ It can make sense for a fingerprint to be a hash of its inputs, so effectively i At the beginning of the build, the plugin's `BeginBuildAsync` method will be called. As part of the `CacheContext`, the plugin is either given the graph or the entry points to the graph for which it can create a graph from. The plugin can use this graph to do some initial processing, like predicting various inputs which a project is likely to use. This information can then be stored to help construct a fingerprint for a build request later. -## Cache storage +### Cache storage Any storage mechanism can be used as a cache implementation, for example [Azure Blob Storage](https://azure.microsoft.com/products/storage/blobs/), or even just the local filesystem. At least in this example the only real requirement is that it can be used effectively as a key-value store. In many cases it can be useful for content to be keyed by its hash, and for the metadata file to be keyed by the fingerprint. In particular when content is keyed by hash, it is effectively deduplicated across multiple copies of the same file, which is common in builds. For illustration purposes, consider our cache implementation is based on a simple filesystem with a separate metadata and content directory inside it. Under the metadata dir, each file is a metadata file where the filename matches the fingerprint it's describing. Under the content dir, each file is a content file where the filename matches the hash of the content itself. -## First build (cache population) +### First build (cache population) In the very first build there will be no cache hits so the "cache add" scenario will be most relevant here. @@ -168,22 +174,24 @@ In our example, we can use the read files to construct a fingerprint for the bui The plugin would then create some metadata describing the outputs (eg. the paths and hashes) and the serialized `BuildResult`, and associate it with the fingerprint and put that assocation in the cache. To illustrate this, consider a project with fingerprint `F` which wrote a single file `O` with hash `H` and had `BuildResult R`. The plugin could create a metadata file `M` which describes the outputs of the build (the path and hash of `O`) as well as the serialized `R`. Using the cache implementation described above, the plugin would write the following two files to the cache: - - `metadata/F -> M:"{outputs: [{path: 'path/to/O', hash: H}], result: R}"` - - `content/H -> O` + +- `metadata/F -> M:"{outputs: [{path: 'path/to/O', hash: H}], result: R}"` +- `content/H -> O` This can then be used for future builds. - ## Second Build (cache hits) - - In the second build we have a populated cache and so it could be possible to get cache hits. +### Second Build (cache hits) - For a given project, `GetCacheResultAsync` will be invoked. The plugin can fingerprint the request and use that fingerprint to look up in its cache. If the cache entry exists, it can declare a cache hit. +In the second build we have a populated cache and so it could be possible to get cache hits. + +For a given project, `GetCacheResultAsync` will be invoked. The plugin can fingerprint the request and use that fingerprint to look up in its cache. If the cache entry exists, it can declare a cache hit. In the example above, if all inputs are the same as in the first build, we should end up with a fingerprint `F`. We look up in the metadata part of the cache (file `metadata/F`) and find that it exists. This means we have a cache hit. We can fetch that metadata `M` from the cache and find that it describes the output with path `O` and hash `H`. The plugin would then copy `content/H` to `O` and return the deserialized `BuildResult R` contained in `M` to MSBuild. If the inputs were not the same as in the first build, for example if a `Compile` item (a .cs file) changed, the fingerprint would be something else besides `F` and so would not have corresponding cache entries for it, indicating a cache miss. This will then go through the "cache add" scenario described above to populate the cache with the new fingerprint. -# Caveats +## Caveats + - Without the "cache add" scenario enabled, the content which powers "cache get" must be populated by some external entity, for example some higher-order build engine. - Absolute paths circulating through the saved build results - Absolute paths will likely break the build, since they'd be captured on the machine that writes to the cache. @@ -193,6 +201,7 @@ If the inputs were not the same as in the first build, for example if a `Compile - Msbuild /graph requires that the [target inference protocol](static-graph.md#inferring-which-targets-to-run-for-a-project-within-the-graph) is good enough. - Small repos will probably be slower with plugin implementations that access the network. Remote distribution and caching will only be worth it for repos that are large enough. -# Potential future work of dubious value +## Potential future work of dubious value + - Enable plugins to work with the just-in-time top down msbuild traversal that msbuild natively does when it's not using `/graph`. - Extend the project cache API to allow skipping individual targets or tasks instead of entire projects. This would allow for smaller specialized plugins, like plugins that only know to distribute, cache, and skip CSC.exe calls. diff --git a/documentation/specs/question.md b/documentation/specs/question.md index 84fac8ed9f3..f46aa910af8 100644 --- a/documentation/specs/question.md +++ b/documentation/specs/question.md @@ -8,15 +8,19 @@ Question switch ask if the next build is up-to-date. It will start a build, but [Fast Up-To-Date Check](https://github.com/dotnet/project-system/blob/cd275918ef9f181f6efab96715a91db7aabec832/docs/up-to-date-check.md) is a system that is implemented by the Project System, that decides, if it needs to run MSBuild. MSBuild takes a non-trival amount of time to load, evaluate, and run through each target and task. Fast Up-To-Date is faster, but can be less accurate, suitable for an IDE and a human interface. It is not accurate enough for a CI. ## Usage + Question mode is designed to be used on the command line. Run your normal build, then run again with /question. -``` + +```cmd msbuild /p:Configuration=Debug Project1.csproj /bl:build.binlog msbuild /p:Configuration=Debug Project1.csproj /bl:incremental.binlog /question ``` + If there are no errors, then your build is up-to-date. If there are errors, then investigate the error. See common errors below. Keep both logs to help with your investigation. ## Custom Tasks + Task author can implement the optional `IIncrementalTask` interface that will expose `FailIfNotIncremental`. `FailIfNotIncremental` is true when /question switch is used. The custom task will need to decide how it want to handle their behavior. For example. If there is already a message describing why the task cannot be skipped, then simply convert the message to a error. Remember to return false to stop the build. For the best reproducibility, do not modify any files on disk. ```C# @@ -32,6 +36,7 @@ else ``` ## Shipping Tasks + When question switch is used, it will modify the shipping task with these behavior. Note: this is still experimental and can change. `Exec` @@ -73,11 +78,11 @@ Error when SkipUnchangedFiles is true. `ZipDirectory` Error if the destination zip file doesn't exists. - ## Common Error + - **Typographical error**. Spelling, casing, or incorrect path. Check if the target inputs and outputs real files. - Inputs and Outputs are sometimes used for Cross Product. Try to move all to Outputs. If not possible, use Returns instead of Inputs. - **Double Checks**. Since target and task could be incremental, if both are implemented, then it can lead task skipping but not the task. For example, a Target has inputs A and outputs B. If A is newer, than B, then the target will start. If the task compares the content of A and B and deems nothing has changed, then B is not updated. If such case, this leads to target rerunning. - **Exec Task** are not Skipable, thus they should be wrapped with Target Inputs and Outputs or other systems. For backwards compatibility, Question will not issue an error. - **FileWritten**. The common clean system will remove files that aren't in the FileWritten itemgroup. Sometimes task output won't be add to FileWritten itemgroup. -- **Build, then Build**. Sometimes, a 2nd build will break up to date. Question after the 2nd build. \ No newline at end of file +- **Build, then Build**. Sometimes, a 2nd build will break up to date. Question after the 2nd build. diff --git a/documentation/specs/rar-core-scenarios.md b/documentation/specs/rar-core-scenarios.md index 3fb19ad7846..48af2458d6d 100644 --- a/documentation/specs/rar-core-scenarios.md +++ b/documentation/specs/rar-core-scenarios.md @@ -139,6 +139,7 @@ effect. Be it eliminating allocations, simplifying tight loops, reordering cases address the elephant in the room: the file I/O resulting from scanning of assemblies, checking their timestamps, and reading/writing on-disk caches. For regular project references the system works as about as efficient as possible. + - In a cold scenario, where there is no state in memory or on disk, the referenced assembly file has to be scanned for its name and dependencies. - In a warm scenario, where there is no state in memory but a disk cache exists, the assembly name and dependencies are read from the cache, together with the corresponding timestamp which is compared to the current timestamp of the assembly file. If they match the cached data is used. @@ -188,6 +189,7 @@ enlistment - the system may prime by building the full solution and then the dev cache and get sub-optimal first-time build performance. Saving of the per-project disk cache may be further optimized by + - Keeping the timestamp of the cache file in memory and skipping the save if the relevant cache items haven't become dirty (i.e. the dependencies have not changed) *and* the timestamp of the cache file hasn't changed since the last save. In hot inner loop scenarios this would reduce the save to a timestamp check. - Saving the file asynchronously, i.e. not blocking the build on completing the save operation. diff --git a/documentation/specs/remote-host-object.md b/documentation/specs/remote-host-object.md index 536535637d2..a64a8836ab4 100644 --- a/documentation/specs/remote-host-object.md +++ b/documentation/specs/remote-host-object.md @@ -4,8 +4,9 @@ A remote host object must be registered in the [Running Object Table (ROT)](http [The registration of interfaces](https://docs.microsoft.com/en-us/dotnet/framework/interop/how-to-register-primary-interop-assemblies) is the only thing interop with COM that need extra care. There are 3 interfaces involved in out-of-proc tasks work: `IVsMSBuildTaskFileManager`, `IPersistFileCheckSum` and `ITaskHost`. `IVsMSBuildTaskFileManager` and `IPersistFileCheckSum` are registered globally in Windows registry by VS existing setup. `ITaskHost` is also configured in VS using registration-free. So the only work is to configure it using registration-free in **MSBuild**. That results the change in msbuild.exe.manifest file and the change to generate tlb file for ITaskHost. -## Annotated additions to the msbuild.exe.manifest file. -``` +## Annotated additions to the msbuild.exe.manifest file + +```xml -- Location of the tlb, it should be in the same directory as msbuild.exe MySdkResolver.dll MySdk.* @@ -23,11 +29,12 @@ By default the resolvers are general. To make all the resolvers from some dll sp Note, that the manifest file, if exists, from ChangeWave 17.4 would have preference over the dll. The sdk discovery works according to the following algorithm: -- First try locate the manifest file and use it. -- If it is not found, we try to locate the dll in the resolver's folder. + +- First try locate the manifest file and use it. +- If it is not found, we try to locate the dll in the resolver's folder. Both xml and dll name should match the following name pattern `...\SdkResolvers\(ResolverName)\(ResolverName).(xml/dll)`. -### Failed SDK Resolution +## Failed SDK Resolution > 🚧 Note > @@ -35,15 +42,15 @@ Both xml and dll name should match the following name pattern `...\SdkResolvers\ SDK resolvers previously attempted to continue when one critically fails (throws an unhandled exception). This lead to misleading error messages such as: -``` +```text warning MSB4242: The SDK resolver "Microsoft.DotNet.MSBuildWorkloadSdkResolver" failed to run. 's' is an invalid start of a property name. Expected a '"'. LineNumber: 14 | BytePositionInLine: 8. error MSB4236: The SDK 'Microsoft.NET.SDK.WorkloadAutoImportPropsLocator' specified could not be found. [C:\foo\bar.csproj] ``` `MSB4236` is a red herring while `MSB4242` is the real error despite being logged as a warning. Because of this, SDK resolvers now fail the build _immediately_ upon unhandled exceptions. These exceptions are propogated as `SdkResolverException`s, and `MSB4242` has been promoted to an error code. The new error message appears like so: -``` -C:\src\temp\8-18>"C:\foo\dotnet-sdk-6.0.100-preview.7.21379.14-win-x64\dotnet.exe" build +```text +C:\src\temp\8-18>"C:\foo\dotnet-sdk-6.0.100-preview.7.21379.14-win-x64\dotnet.exe" build Microsoft (R) Build Engine version 17.0.0-dev-21420-01+5df152759 for .NET Copyright (C) Microsoft Corporation. All rights reserved. @@ -56,4 +63,4 @@ C:\foo\bar.csproj : error MSB4242: SDK Resolver Failure: "The SDK resolver "Micr 1 Error(s) Time Elapsed 00:00:00.15 -``` \ No newline at end of file +``` diff --git a/documentation/specs/single-project-isolated-builds.md b/documentation/specs/single-project-isolated-builds.md index 75b15fc5b82..8a8f9e68413 100644 --- a/documentation/specs/single-project-isolated-builds.md +++ b/documentation/specs/single-project-isolated-builds.md @@ -19,25 +19,27 @@ In a build, the input and output cache files have the same lifetime as the `Conf When loading input cache files, MSBuild merges incoming instances of `ConfigCache`s and `ResultsCache`s into one instance of each with the help of the [`CacheAggregator`](https://github.com/dotnet/msbuild/blob/51df47643a8ee2715ac67fab8d652b25be070cd2/src/Build/BackEnd/BuildManager/CacheAggregator.cs#L15), which enforces the following constraints: + - No duplicate cache entries - Bijection: - - `ConfigCache.Entries.Size == ResultsCache.Entries.Size` - - `BuildResult.ConfigurationId` == `BuildRequestConfiguration.ConfigurationId` + - `ConfigCache.Entries.Size == ResultsCache.Entries.Size` + - `BuildResult.ConfigurationId == BuildRequestConfiguration.ConfigurationId` Note that the output cache file contains a single `BuildResult` with the `TargetResult`s from the project specified to be built in the `BeginBuild` / `EndBuild` session, as any `BuildResult`s obtained through isolation exemption are excluded to prevent potential duplicate input cache entries; Entries from input caches are not transferred to the output cache. -Input cache entries are separated from output cache entries with the composite caches [`ConfigCacheWithOverride`](https://github.com/dotnet/msbuild/blob/main/src/Build/BackEnd/Components/Caching/ConfigCacheWithOverride.cs) and [`ResultsCacheWithOverride`](https://github.com/dotnet/msbuild/blob/main/src/Build/BackEnd/Components/Caching/ResultsCacheWithOverride.cs). Each composite cache contains two underlying caches: a cache where input caches files are loaded into (the override cache), and a cache where new results are written into (the current cache).* In the `ConfigCacheWithOverride`, these caches are instances of `ConfigCache`s and, in the `ResultsCacheWithOverride`, these caches are instances of `ResultsCache`s. A query for a cache entry is first attempted from the override cache and, if unsatisfied, a second attempt is made from the current cache. Writes are only written to the current cache, never into the override cache.* It is illegal for both the current cache and override cache to contain entries for the same project configuration, a constraint that is checked by the two override caches on each cache query. +Input cache entries are separated from output cache entries with the composite caches [`ConfigCacheWithOverride`](https://github.com/dotnet/msbuild/blob/main/src/Build/BackEnd/Components/Caching/ConfigCacheWithOverride.cs) and [`ResultsCacheWithOverride`](https://github.com/dotnet/msbuild/blob/main/src/Build/BackEnd/Components/Caching/ResultsCacheWithOverride.cs). Each composite cache contains two underlying caches: a cache where input caches files are loaded into (the override cache), and a cache where new results are written into (the current cache). *In the `ConfigCacheWithOverride`, these caches are instances of `ConfigCache`s and, in the `ResultsCacheWithOverride`, these caches are instances of `ResultsCache`s. A query for a cache entry is first attempted from the override cache and, if unsatisfied, a second attempt is made from the current cache. Writes are only written to the current cache, never into the override cache.* It is illegal for both the current cache and override cache to contain entries for the same project configuration, a constraint that is checked by the two override caches on each cache query. ## Isolation Implementation [Isolation constraints](static-graph.md##single-project-isolated-builds) are implemented in the `Scheduler` and `TaskBuilder`. [`TaskBuilder.ExecuteInstantiatedTask`](https://github.com/dotnet/msbuild/blob/37c5a9fec416b403212a63f95f15b03dbd5e8b5d/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs#L743) ensures that the `MSBuild` task is only called on projects declared in a `ProjectReference` item. [`Scheduler.CheckIfCacheMissOnReferencedProjectIsAllowedAndErrorIfNot`](https://github.com/dotnet/msbuild/blob/37c5a9fec416b403212a63f95f15b03dbd5e8b5d/src/Build/BackEnd/Components/Scheduler/Scheduler.cs#L1818) ensures that all `MSBuild` tasks are cache hits. ### Isolation Exemption + The `Scheduler` [skips isolation constraints](static-graph.md#exempting-references-from-isolation-constraints) on project references via the: -* `GraphIsolationExemptReference` item. The `RequestBuilder` sets the `SkipStaticGraphIsolationConstraints` property of a `BuildRequest` to `true` if the `RequestBuilder` matches it against a `GraphIsolationExemptReference` item defined in the calling project. Additionally, the `RequestBuilder` marks the `BuildRequest`'s corresponding `BuildRequestConfiguration` as exempt to allow the `TaskBuilder` to verify exemption from isolation constraints. +- `GraphIsolationExemptReference` item. The `RequestBuilder` sets the `SkipStaticGraphIsolationConstraints` property of a `BuildRequest` to `true` if the `RequestBuilder` matches it against a `GraphIsolationExemptReference` item defined in the calling project. Additionally, the `RequestBuilder` marks the `BuildRequest`'s corresponding `BuildRequestConfiguration` as exempt to allow the `TaskBuilder` to verify exemption from isolation constraints. -* `isolate:MessageUponIsolationViolation` switch. The `RequestBuilder` sets the `SkipStaticGraphIsolationConstraints` property of _every_ `BuildRequest` to `true`. The `TaskBuilder` verifies exemption from isolation constraints just by the switch value. +- `isolate:MessageUponIsolationViolation` switch. The `RequestBuilder` sets the `SkipStaticGraphIsolationConstraints` property of _every_ `BuildRequest` to `true`. The `TaskBuilder` verifies exemption from isolation constraints just by the switch value. -\* Except in the following scenario when a `ProjectReference` is exempted from isolation constraints: a dependency project A outputs a cache file F containing a `BuildResult` with `TargetResult`s Tcached for targets t1, t2, ..., tm and a dependent project B uses F as an input cache file but builds and obtains the `TargetResult`s Tnew for targets tm + 1, tm + 2, ..., tn such that 0 < m < n. In this case, Tnew will be placed into the `ResultsCache` containing Tcached to enforce no overlap between the override and current caches in the `ConfigCacheWithOverride`. \ No newline at end of file +\* Except in the following scenario when a `ProjectReference` is exempted from isolation constraints: a dependency project A outputs a cache file F containing a `BuildResult` with `TargetResult`s Tcached for targets t1, t2, ..., tm and a dependent project B uses F as an input cache file but builds and obtains the `TargetResult`s Tnew for targets tm + 1, tm + 2, ..., tn such that 0 < m < n. In this case, Tnew will be placed into the `ResultsCache` containing Tcached to enforce no overlap between the override and current caches in the `ConfigCacheWithOverride`. diff --git a/documentation/specs/static-graph.md b/documentation/specs/static-graph.md index 49acebe57fe..6112072349a 100644 --- a/documentation/specs/static-graph.md +++ b/documentation/specs/static-graph.md @@ -85,6 +85,7 @@ Static graph functionality can be used in three ways: ## Project Graph ### Constructing the project graph + Calculating the project graph will be very similar to the MS internal build engine's existing Traversal logic. For a given evaluated project, all project references will be identified and recursively evaluated (with deduping). Project references are identified via the `ProjectReference` item. @@ -112,14 +113,16 @@ Multitargeting refers to projects that specify multiple build dimensions applica Multitargeting is implemented by having a project reference itself multiple times, once for each combination of multitargeting global properties. This leads to multiple evaluations of the same project, with different global properties. These evaluations can be classified in two groups -1. Multiple inner builds. Each inner build is evaluated with one set of multitargeting global properties (e.g. the `TargetFramework=net472` inner build, or the `TargetFramework=netcoreapp2.2` inner build). -2. One outer build. This evaluation does not have any multitargeting global properties set. It can be viewed as a proxy for the inner builds. Other projects query the outer build in order to learn the set of valid multitargeting global properties (the set of valid inner builds). When the outer build is also the root of the project to project graph, the outer build multicasts the entry target (i.e. `Build`, `Clean`, etc) to all inner builds. + +1. Multiple inner builds. Each inner build is evaluated with one set of multitargeting global properties (e.g. the `TargetFramework=net472` inner build, or the `TargetFramework=netcoreapp2.2` inner build). +2. One outer build. This evaluation does not have any multitargeting global properties set. It can be viewed as a proxy for the inner builds. Other projects query the outer build in order to learn the set of valid multitargeting global properties (the set of valid inner builds). When the outer build is also the root of the project to project graph, the outer build multicasts the entry target (i.e. `Build`, `Clean`, etc) to all inner builds. In order for the graph to represent inner and outer builds as nodes, it imposes a contract on what multitargeting means, and requires the multitargeting supporting SDKs to implement this contract. Multitargeting supporting SDKs MUST implement the following properties and semantics: + - `InnerBuildProperty`. It contains the property name that defines the multitargeting build dimension. - `InnerBuildPropertyValues`. It contains the property name that holds the possible values for the `InnerBuildProperty`. - Project classification: @@ -136,6 +139,7 @@ These specific rules represent the minimal rules required to represent multitarg For example, `InnerBuildProperty` could become `InnerBuildProperties` for SDKs where there's multiple multitargeting global properties. For example, here is a trimmed down `Microsoft.Net.Sdk` multitargeting project: + ```xml @@ -152,14 +156,17 @@ For example, here is a trimmed down `Microsoft.Net.Sdk` multitargeting project: ``` To summarize, there are two main patterns for specifying build dimensions: + 1. Multitargeting based. A multitargeting project self describes supported build dimensions. In this case the SDK needs to specify the multitargeting build dimensions. The graph then extracts innerbuilds from a given outer build. For example, the `TargetFramework` build dimension gets specified this way. 2. Global Property based: A top level set of global properties get applied to the graph entrypoints and get propagated downward through the graph. For example, the `Configuration` and `Platform` build dimensions get specified this way. Why does an outerbuild need to generate speculative edges to all of its innerbuilds? Why can't it use nuget to prune the speculative edges down to the compatible set? + - One big design constraint we imposed on static graph was to keep it agnostic of SDK implementation details. So the graph must not know about particular details of one language's SDK. We wanted a generic design that all language SDKs can leverage. We considered that calling nuget to get the compatible TargetFramework values breaks this rule, as both the concept of "nuget" and the concept of "TargetFramework" are implementation details of the .net SDK. If someone were to write a Java SDK, would "calling nuget to get the compatible TargetFramework" still be relevant? A solution to this is to allow SDKs to configure the graph with an extension point on "how to collapse multiple speculative innerbuild edges into a smaller compatible set", but we didn't have the time to design it yet. - There is a conflicting need between build everything or just building a "TF slice" through the graph. Outer loop builds (CI builds) that publish binaries need to build all the packages for all the supported TFs, so they need the graph to express all possible combinations. Inner loop builds (dev-at-work) can be sliced down to only the TF that the dev is working on in order to reduce build times. Again, we didn't have time to design how to express these two things so we went with "express everything" because that allows both scenarios to work. ### Executing targets on a graph + When building a graph, project references should be built before the projects that reference them, as opposed to the existing msbuild scheduler which builds projects just in time. For example if project A depends on project B, then project B should build first, then project A. Existing msbuild scheduling would start building project A, reach an MSBuild task for project B, yield project A, build project B, then resume project A once unblocked. @@ -169,6 +176,7 @@ Building in this way should make better use of parallelism as all CPU cores can Note that graph cycles are disallowed, even if they're using disconnected targets. This is a breaking change, as today you can have two projects where each project depends on a target from the other project, but that target doesn't depend on the default target or anything in its target graph. #### Command line + `msbuild /graph` - msbuild will create a static graph from the entry point project and build it in topological order with the specified targets. Targets to call on each node are inferred via the rules in [this section](#inferring-which-targets-to-run-for-a-project-within-the-graph). #### APIs @@ -176,11 +184,12 @@ Note that graph cycles are disallowed, even if they're using disconnected target [BuildManager.PendBuildRequest(GraphBuildRequestData requestData)](https://github.com/dotnet/msbuild/blob/37c5a9fec416b403212a63f95f15b03dbd5e8b5d/src/Build/BackEnd/BuildManager/BuildManager.cs#L676) ### Inferring which targets to run for a project within the graph + In the classic MSBuild build (i.e. execution of targets), the referencing project chooses which targets to call on the referenced projects and may call into a project multiple times with different target lists and global properties (examples in [project reference protocol](../ProjectReference-Protocol.md)). This is a top-down traversal of dependencies. These calls are made via the [MSBuild task](https://docs.microsoft.com/en-us/visualstudio/msbuild/msbuild-task?view=vs-2019). When building a graph, projects are built before the projects that reference them. This is a bottom-up traversal. Therefore the graph needs to determine the list of targets to execute on a specific project `B` **before** building the referencing projects that reference `B`. The static graph contains the structural information on which reference projects a referencing project depends on. But it does not contain information on what "depends" means. At build time "depends" means that a referencing evaluated project will call a subset of reference evaluations with some targets. Subset because the static graph is an inferred graph, therefore there are ambiguities during graph construction, and thus it needs to be conservative and represent a superset of the "runtime graph". The "runtime graph" is the actual graph that gets executed during a real build. We cannot know the runtime graph because that would require us to analyze msbuild xml code inside of targets in order to find the `MSBuild task` invocations. This means doing heavy program analysis, like symbolic execution. That would make things very complicated, slower, and would probably introduce even more ambiguity, so a larger superset conservative graph. So we kept it simple and only looked at evaluation time msbuild xml code (i.e. msbuild xml code outside of `` elements). To summarize, the static graph does not have insights into the `MSBuild task` callsites. It does not know callsite specific information such as the `Targets="Foo;Bar"` or `Properties="Foo=Bar"` `MSBuild task` attributes. -Since the graph does not have access to MSBuild task callsites, it does not know what targets will get called for a given graph edge. +Since the graph does not have access to MSBuild task callsites, it does not know what targets will get called for a given graph edge. To infer target information we use a flow analysis to propagate target information down the graph. The flow analysis uses the `ProjectReferenceTargets` protocol (described further down) to infer how one incoming target on a graph node (e.g. `Build`) generates multiple outgoing targets to its referenced nodes (e.g. `GetTargetFrameworks`, `GetNativeManifest`, `Build`). SDKs **must** explicitly describe the project-to-project calling patterns via the `ProjectReferenceTargets` protocol in such a way that a graph based build can correctly infer the entry targets for a graph node. @@ -189,7 +198,7 @@ Each project needs to specify the project reference protocol targets it supports For example, a simple recursive rule would be `A -> A`, which says that a project called with target `A` will call target `A` on its referenced projects. Here's an example execution with two nodes: -``` +```text Execute target A+-->Proj1 A->A + | @@ -202,11 +211,12 @@ Execute target A+-->Proj1 A->A Proj1 depends on Proj2, and we want to build the graph with target `A`. Proj1 gets inspected for the project reference protocol for target `A` (represented to the right of Proj1). The protocol says the referenced projects will be called with `A`. Therefore Proj2 gets called with target `A`. After Proj2 builds, Proj1 then also builds with `A` because Proj1 is an entry point and `A` is what was requested by the user. A project reference protocol may contain multiple targets, for example `A -> B, A`. This means that building `A` on the referencing project will lead to `B` and `A` getting called on the referenced projects. If all nodes in the graph repeat the same rule, then the rule is repeated recursively on all nodes. However, a project can choose to implement the protocol differently. In the following example, the entry targets are: + - Proj4 is called with targets `B, A, C, D`. On multiple references, the incoming targets get concatenated. The order of these target lists does not matter, as MSBuild has non-deterministic p2p ordering, however the order within the target lists does. IE. `B, A, C, D` and `C, D, B, A` are valid, while `A, B, C, D` is not. - Proj3 and Proj2 get called with `B, A`, as specified by the rule in Proj1. - Proj1 builds with `A`, because it's the root of the graph. -``` +```text A+-->Proj1 A->B, A / \ B, A / \ B, A @@ -238,6 +248,7 @@ Here are the rules for the common protocols: `Rebuild` actually calls `Clean` and `Build`, which in turn uses the concatenation of the `Clean` and `Build` mappings. `GetTargetFrameworks` is repeated so only the first call to it remains in the final target list. Restore is a composition of two rules: + - `Restore -> _IsProjectRestoreSupported, _GenerateRestoreProjectPathWalk, _GenerateRestoreGraphProjectEntry` - `_GenerateRestoreProjectPathWalk -> _IsProjectRestoreSupported, _GenerateRestoreProjectPathWalk, _GenerateRestoreGraphProjectEntry` @@ -258,22 +269,24 @@ We'll represent the project reference protocols as `ProjectReferenceTargets` ite #### Multitargeting details A multitargeting project can get called with different targets for the outer build and the inner builds. In this case, the `ProjectReferenceTargets` items containing targets for the outer build are marked with the `OuterBuild=true` metadata. Here are the rules for how targets from `ProjectReferenceTargets` get assigned to different project types: - - *Outer build*: targets with `OuterBuild=true` metadata - - *Dependent inner build*: targets without `OuterBuild=true` metadata - - *Standalone inner build*: the same as non multitargeting builds. - - *Non multitargeting build*: concatenation of targets with `OuterBuild=true` metadata and targets without `OuterBuild=true` metadata + +- *Outer build*: targets with `OuterBuild=true` metadata +- *Dependent inner build*: targets without `OuterBuild=true` metadata +- *Standalone inner build*: the same as non multitargeting builds. +- *Non multitargeting build*: concatenation of targets with `OuterBuild=true` metadata and targets without `OuterBuild=true` metadata **OPEN ISSUE:** Current implementation does not disambiguate between the two types of inner builds, leading to overbuilding certain targets by conservatively treating both inner build types as standalone inner builds. For example, consider the graph of `A (non multitargeting) -> B (multitargeting with 2 innerbuilds) -> C (standalone inner build)`, with the following target propagation rules: -``` + +```text A -> Ao when OuterBuild=true A -> Ai, A ``` According to the graph construction rules defined in the [multitargeting section](#multitargeting), we get the following graph, annotated with the target propagation for target `A`. -``` +```text A+-->ProjA / | \ / | \ @@ -294,6 +307,7 @@ According to the graph construction rules defined in the [multitargeting section ``` ### Underspecified graphs + The intention is that the project graph and the target lists for each node be exactly correct, however MSBuild is quite flexible and particular projects or project types may not adequately describe these for the project graph. If a project calls into another project which either isn't represented in the graph or with a target list which isn't represented by the graph, it will fall back to classical MSBuild behavior and execute that target on the project reference just-in-time. This has the consequence of still requiring all project state be kept in memory in case any arbitrary project wants to execute targets on any other arbitrary project. @@ -301,6 +315,7 @@ If a project calls into another project which either isn't represented in the gr To enable further optimizations (and strictness), graph builds can run [isolated](#isolated-builds) which enforces that the graph be entirely accurate. ### Public API + This is a proposal for what the public API for ProjectGraph may look like: ```csharp @@ -360,6 +375,7 @@ namespace Microsoft.Build.Experimental.Graph ``` ## Isolated builds + Building a project in isolation means enforcing the constraint that whenever a graph node is built, all the target calls that it does on its references **do not execute** because their results are already available. This means that any `BuildResult` objects for project references must be precomputed and somehow provided as inputs to the referencing project. If a project uses the MSBuild task, the build result must be in MSBuild's build result cache instead of just-in-time executing targets on that referenced project. If it is not in the build result cache, an error will be logged and the build will fail. If the project is calling into itself either via `CallTarget` or the MSBuild task with a different set of global properties, this will be allowed to support multitargeting and other build dimensions implemented in a similar way. @@ -367,6 +383,7 @@ If a project uses the MSBuild task, the build result must be in MSBuild's build Because referenced projects and their entry targets are guaranteed to be in the cache, they will not build again. Therefore we do not need to set `/p:BuildProjectReferences=false` or any other gesture that tells SDKs to not do recursive operations. ### Isolated graph builds + When building a graph in isolated mode, the graph is used to traverse and build the projects in the right order, but each individual project is built in isolation. The build result cache will just be in memory exactly as it is today, but on cache miss it will error. This enforces that both the graph and target mappings are complete and correct. Furthermore, running in this mode enforces that each `(project, global properties)` pair is executed only once and must execute all targets needed by all projects which reference that node. This gives it a concrete start and end time, which leads to some potential perf optimizations, like garbage collecting all project state (except the build results) once it finishes building. This can greatly reduce the memory overhead for large builds. @@ -374,9 +391,11 @@ Furthermore, running in this mode enforces that each `(project, global propertie This discrete start and end time also allows for easy integration with [I/O Tracking](#io-tracking) to observe all inputs and outputs for a project. Note however that I/O during target execution, particular target execution which may not normally happen as part of a project's individual build execution, would be attributed to the project reference project rather the project with the project reference. This differs from today's behavior, but seems like a desirable difference anyway. ### Single project isolated builds + When building a single project in isolation, all project references' build results must be provided to the project externally. Specifically, the results will need to be [deserialized](#deserialization) from files and loaded into the build result cache in memory. When MSBuild runs in isolation mode, it fails the build when it detects: + 1. `MSBuild` task calls which cannot be served from the cache. Cache misses are illegal. 2. `MSBuild` task calls to project files which were not defined in the `ProjectReference` item. @@ -389,16 +408,20 @@ These incremental builds could be extended to the entire graph by keeping a proj Details on how isolation and cache files are implemented in MSBuild can be found [here](./static-graph-implementation-details.md). #### APIs + Cache file information is provided via [`BuildParameters`](https://github.com/dotnet/msbuild/blob/2d4dc592a638b809944af10ad1e48e7169e40808/src/Build/BackEnd/BuildManager/BuildParameters.cs#L746-L764). Input caches are applied in `BuildManager.BeginBuild`. Output cache files are written in `BuildManager.EndBuild`. Thus, the scope of the caches are one `BuildManager` `BeginBuild`/`EndBuild` session. Isolation constraints are turned on via [`BuildParameters.IsolateProjects`](https://github.com/dotnet/msbuild/blob/b111470ae61eba02c6102374c2b7d62aebe45f5b/src/Build/BackEnd/BuildManager/BuildParameters.cs#L742). Isolation constraints are also automatically turned on if either input or output cache files are used, except when the `isolate:MessageUponIsolationViolation` switch is used. #### Command line + Caches are provided to MSBuild.exe via the multi value `/inputResultsCaches` and the single value `/outputResultsCache`. Isolation constraints are turned on via `/isolate` (they are also implicitly activated when either input or output caches are used). #### Exempting references from isolation constraints + In certain situations one may want to exempt a reference from isolation constraints. A few potential cases: + - debugging / onboarding to isolation constraints - exempting references whose project files are generated at build times with random names (for example, each WPF project, before the Build target, generates and builds a helper .csproj with a random file name) - relaxing constraints for MSBuild task calling patterns that static graph cannot express (for exemple, if a project is calculating references, or the targets to call on references, at runtime via an arbitrary algorithm) @@ -422,6 +445,7 @@ If multiple projects need to exempt the same reference, all of them need to add For now, self-builds (a project building itself with different global properties) are also exempt from isolation constraints, but this behaviour is of dubious value and might be changed in the future. ## I/O Tracking + To help facilitate caching of build outputs by a higher-order build engine, MSBuild needs to track all I/O that happens as part of a build. **OPEN ISSUE:** This isn't actually true in most scenarios. Today the MS internal build engine can wrap any arbitrary process to track the I/O that happens as part of its execution as well as its children. That's sufficient for all scenarios except compiler servers or an MSBuild server (see below). Additionally, if the MS internal build engine supports any other build type besides MSBuild (or older versions of MSBuild), it will still need to be able to detour the process itself anyway. @@ -429,6 +453,7 @@ To help facilitate caching of build outputs by a higher-order build engine, MSBu **NOTE**: Based on the complexity and challenges involved, the feature of I/O tracking in MSBuild is currently on hold and not scheduled to be implemented. This section intends to describe these challenges and be a dump of the current thinking on the subject. ### Detours + [Detours](https://github.com/microsoft/detours) will be used to intercept Windows API calls to track I/O. This is the same technology that [FileTracker](../../src/Utilities/TrackedDependencies/FileTracker.cs) and [FullTracking](../../src/Build/BackEnd/Components/RequestBuilder/FullTracking.cs) use as well as what the MS internal build engine ("BuildXL Tracker") uses to track I/O. Today FileTracker and FullTracking are currently a bit specific to generating tlogs, and do not collect all the I/O operations we would want to collect like directory enumerations and probes. Additionally, the BuildXL Tracker implementation does not currently have the ability to attach to the currently running process. @@ -438,11 +463,13 @@ Either existing implementation would require some work to fit this scenario. Bec Elsewhere in this spec the final Detours-based file tracking implementation will simply be referred to as "Tracker". ### Isolation requirement + I/O Tracking will only be available when running isolated builds, as the current implementation of project yielding in MSBuild makes it exceedingly difficult to attribute any observed I/O to the correct project. Isolated builds make this feasible since each MSBuild node will be building exactly one project configuration at any given moment and each project configuration has a concrete start and stop time. This allows us to turn on I/O tracking for the MSBuild process and start and stop tracking with the project start and stop. **OPEN ISSUE:** For graph-based isolated builds, project evaluation happens in parallel on the main node. Any I/O that happens as part of evaluation should be reported for that specific project, but there's no good way to do that here. ### Tool servers + Tool servers are long-lived processes which can be reused multiple times across builds. This causes problems for Tracker, as that long-lived process is not a child process of MSBuild, so many I/O operations would be missed. For example, when `SharedCompilation=true`, the Roslyn compiler (csc.exe) will launch in server mode. This causes the `Csc` task to connect to any existing csc.exe process and pass the compilation request over a named pipe. diff --git a/documentation/specs/task-isolation-and-dependencies.md b/documentation/specs/task-isolation-and-dependencies.md index 2ec96c8eb18..2994335dfc8 100644 --- a/documentation/specs/task-isolation-and-dependencies.md +++ b/documentation/specs/task-isolation-and-dependencies.md @@ -1,39 +1,50 @@ # Task isolation + ## Problem definition + Tasks in MSBuild are dynamically loaded assemblies with potentially separate and colliding dependency trees. Currently MSBuild on .NET Core has no isolation between tasks and as such only one version of any given assembly can be loaded. Prime example of this is Newtonsoft.Json which has multiple versions, but all the tasks must agree on it to work. This problem is also described in #1754. ## Solution + Use [`AssemblyLoadContext`](https://docs.microsoft.com/en-us/dotnet/api/system.runtime.loader.assemblyloadcontext?view=netcore-2.2) (ALC) to provide binding isolation for task assemblies. Each task assembly would be loaded into its own ALC instance. -* The ALC would resolve all dependencies of the task assemblies (see dependency resolution below) -* ALC would fallback to the Default for dependencies which the assembly doesn't carry with itself (frameworks and so on) -* ALC would probably have to forcefully fallback for MSBuild assemblies since it's possible that tasks will carry these, but the system requires for the MSBuild assemblies to be shared. + +- The ALC would resolve all dependencies of the task assemblies (see dependency resolution below) +- ALC would fallback to the Default for dependencies which the assembly doesn't carry with itself (frameworks and so on) +- ALC would probably have to forcefully fallback for MSBuild assemblies since it's possible that tasks will carry these, but the system requires for the MSBuild assemblies to be shared. We also want to load groups of tasks which belong together into the same ALC (for example based on their location on disk) to improve performance. This will need some care as there's no guarantee that two random tasks have compatible dependency trees. As implemented, each task assembly is loaded into its own ALC. ## Potential risks -* Has some small probability of causing breaks. Currently all assemblies from all tasks are loaded into the default context and thus are "visible" to everybody. Tasks with following properties might not work: - * Task has a dependency on an assembly, but it doesn't declare this dependency in its .deps.json and this dependency gets loaded through some other task. This is mostly fixable by implementing probing similar to today's behavior. - * Two tasks from different assemblies which somehow rely on sharing certain types. If the new system decides to load these in isolation they won't share types anymore and might not work. -* Performance - task isolation inherently (and by design) leads to loading certain assemblies multiple times. This increases memory pressure and causes additional JITing and other related work. + +- Has some small probability of causing breaks. Currently all assemblies from all tasks are loaded into the default context and thus are "visible" to everybody. Tasks with following properties might not work: + - Task has a dependency on an assembly, but it doesn't declare this dependency in its .deps.json and this dependency gets loaded through some other task. This is mostly fixable by implementing probing similar to today's behavior. + - Two tasks from different assemblies which somehow rely on sharing certain types. If the new system decides to load these in isolation they won't share types anymore and might not work. +- Performance - task isolation inherently (and by design) leads to loading certain assemblies multiple times. This increases memory pressure and causes additional JITing and other related work. ## Additional consideration -* None of these changes would have any effect on MSBuild on .NET Framework -* Task isolation alone could be achieved on existing MSBuild + +- None of these changes would have any effect on MSBuild on .NET Framework +- Task isolation alone could be achieved on existing MSBuild # Task dependency resolution + ## Problem definition + Tasks with complex and specifically platform specific dependencies don't work out of the box. For example if a task uses [`LibGit2Sharp`](https://www.nuget.org/packages/LibGit2Sharp) package it will not work as is. `LibGit2Sharp` has native dependencies which are platform specific. While the package carries all of them, there's no built in support for the task to load the right ones. For example [source link](https://github.com/dotnet/sourcelink/blob/29b3197e824c05d03427c05d56700e4c704233e4/src/Microsoft.Build.Tasks.Git/GitLoaderContext.cs) runs into this problem. ## Solution + .NET Core uses `.deps.json` files to describe dependencies of components. It would be natural to treat task assemblies as components and use associated .deps.json file to determine their dependencies. This would make the system work nicely end to end with the .NET Core CLI/SDK and VS integration. In .NET Core 3 there's a new type [`AssemblyDependencyResolver`](https://github.com/dotnet/coreclr/blob/master/src/System.Private.CoreLib/src/System/Runtime/Loader/AssemblyDependencyResolver.cs) which implements parsing and processing of a `.deps.json` for a component (or assembly). The usage is to create an instance of the resolver pointing to the assembly (in MSBuild case the task assembly). The resolver parses the `.deps.json` and stores the information. It exposes two methods to resolve managed and native dependencies. It was designed to be used as the underlying piece to implement custom ALC. So it would work nicely with task isolation above. ## Potential risks -* Small probability of breaking tasks which have `.deps.json` with them and those are not correct. With this change the file would suddenly be used and could cause either load failures or different versions of assemblies to get loaded. + +- Small probability of breaking tasks which have `.deps.json` with them and those are not correct. With this change the file would suddenly be used and could cause either load failures or different versions of assemblies to get loaded. ## Additional consideration -* Task dependency resolution requires APIs which are only available in .NET Core 3.0 (no plan to backport), as such MSBuild will have to target netcoreapp3.0 to use these APIs. + +- Task dependency resolution requires APIs which are only available in .NET Core 3.0 (no plan to backport), as such MSBuild will have to target netcoreapp3.0 to use these APIs. We decided not to implement `AssemblyDependencyResolver` in the .NET Core 3.x timeframe because of the uncertain impact of the change. We should reconsider in the .NET 5 timeframe. diff --git a/documentation/specs/test-target.md b/documentation/specs/test-target.md index 7726f1f6971..e5818eb6c81 100644 --- a/documentation/specs/test-target.md +++ b/documentation/specs/test-target.md @@ -1,62 +1,77 @@ -## MSBuild Test Target and Task +# MSBuild Test Target and Task + See: [MSBuild Test Target](https://github.com/dotnet/msbuild/pull/9193) -### Motivation +## Motivation + The primary motivation of the MSBuild Test Target is to offer a convienent and standardardized way for executing tests within the msbuild environment. This is inspired by the simplicity of the `dotnet test` command. The proposed command for initiating test within MSBuild would be `msbuild /t:Test` Another significatnt benefit of integrating this target is to faciliatet the caching of test executions, using MSBuild project caching capabilities. This enhancement will optimize the testing process by reducing test runs which could significantly reduce time spent building and testing, as tests would only execute, (after the initial run) if there are changes to those tests. As an example running with [MSBuildCache](https://github.com/microsoft/MSBuildCache) we can cache both build and test executions. Functionally, this means skipping test executions that have been determined to have not changed. Example usage: `msbuild /graph /restore:false /m /nr:false /reportfileaccesses /t:"Build;Test"` -### Design Overview +## Design Overview + The 'Microsoft.Common.Test.targets' file contains a stub test target. -``` + +```xml ``` + This target serves a placeholder and entry point for test target implementations. -#### Conditional Import -* This stub target is conditionally imported, determined by a condition named +### Conditional Import + +- This stub target is conditionally imported, determined by a condition named `$(UseMSBuildTestInfrastructure)`. -* This condition allows for users to opt-in to this test target, which helps to prevent breaking changes, with respect the the target name, since there are likely 'Test' targets that exist in the wild already. +- This condition allows for users to opt-in to this test target, which helps to prevent breaking changes, with respect the the target name, since there are likely 'Test' targets that exist in the wild already. The 'Microsoft.Common.CurrentVersion.targets' file contains. -``` + +```xml false ``` -#### Extensibility for Test Runners -* Test runner implemenations can hook into the provided stub using the `AfterTargets` property. -* This approach enables different test runners to extend the basic funcionarlity of the test target. + +### Extensibility for Test Runners + +- Test runner implemenations can hook into the provided stub using the `AfterTargets` property. +- This approach enables different test runners to extend the basic funcionarlity of the test target. For instance, an implementation for running VSTest would look like: -``` + +```xml ``` -#### Usage Scenario -* Users who wish to utilize this target will set the `$(UseMSBuildTestInfrastructure)` condition in their project file, rsp or via the command line. -* By executing `msbuild /t:Test`, the MSBuild engine will envoke the `Test` taget, which in turn triggers any test runner targets defined to run after it. +### Usage Scenario + +- Users who wish to utilize this target will set the `$(UseMSBuildTestInfrastructure)` condition in their project file, rsp or via the command line. +- By executing `msbuild /t:Test`, the MSBuild engine will envoke the `Test` taget, which in turn triggers any test runner targets defined to run after it. + +## Default Task Implementation -### Default Task Implementation See: [MSBuild Test Task](https://github.com/microsoft/MSBuildSdks/pull/473) -#### Nuget package for default implementaion -* The default implementation will be provided through a nuget package. -* This package will contain an MSBuild Task deigned to execute `vstest.console.exe`. +### Nuget package for default implementaion + +- The default implementation will be provided through a nuget package. +- This package will contain an MSBuild Task deigned to execute `vstest.console.exe`. + +### MSBuild Task Functionality + +- The core of this implemenation is an MSBuild task that interfaces with `vstest.console.exe`. +- This task will accept arguments as properties and pass them directly into the command line test runner. -#### MSBuild Task Functionality -* The core of this implemenation is an MSBuild task that interfaces with `vstest.console.exe`. -* This task will accept arguments as properties and pass them directly into the command line test runner. +### Using The Default Implementation -#### Using The Default Implementation -* Users would install the provided Nuget Package to incorporate it into their projects. -* Add the package to their GlobalPackageReferences or specific projects. -* Once integrated, executing `msbuild /t:Test` would trigger the MSBuild Task, ultimately executing `vstest.console.exe`. +- Users would install the provided Nuget Package to incorporate it into their projects. +- Add the package to their GlobalPackageReferences or specific projects. +- Once integrated, executing `msbuild /t:Test` would trigger the MSBuild Task, ultimately executing `vstest.console.exe`. diff --git a/eng/Build.props b/eng/Build.props index 591a4d41340..fec5d08db7d 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -6,11 +6,12 @@ - + - + + diff --git a/eng/DotNetBuild.props b/eng/DotNetBuild.props index 778419d070a..997ad524331 100644 --- a/eng/DotNetBuild.props +++ b/eng/DotNetBuild.props @@ -1,5 +1,4 @@ - @@ -7,15 +6,4 @@ true - - - - $(InnerBuildArgs) /p:Projects="$(InnerSourceBuildRepoRoot)MSBuild.SourceBuild.slnf" - - - $(InnerBuildArgs) /p:EnablePackageValidation=false - - - diff --git a/eng/Publishing.props b/eng/Publishing.props index 6db69f7afa9..5f9650d32dc 100644 --- a/eng/Publishing.props +++ b/eng/Publishing.props @@ -1,6 +1,9 @@ - - - 3 - - \ No newline at end of file + + + + + + + diff --git a/eng/SourceBuildPrebuiltBaseline.xml b/eng/SourceBuildPrebuiltBaseline.xml index 6bf7dfcbf3a..a3ccc3b79c9 100644 --- a/eng/SourceBuildPrebuiltBaseline.xml +++ b/eng/SourceBuildPrebuiltBaseline.xml @@ -24,6 +24,8 @@ + + diff --git a/eng/Version.Details.xml b/eng/Version.Details.xml index 176bd3256ab..56b1d612471 100644 --- a/eng/Version.Details.xml +++ b/eng/Version.Details.xml @@ -1,10 +1,11 @@ + - + https://github.com/dotnet/source-build-reference-packages - 1cec3b4a8fb07138136a1ca1e04763bfcf7841db + 19eb5ea4e5f9c4e5256843a92805c8c9e942207d @@ -16,125 +17,143 @@ https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + https://github.com/dotnet/runtime - + + - + https://github.com/dotnet/arcade - 5da211e1c42254cb35e7ef3d5a8428fb24853169 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 - + https://github.com/dotnet/arcade - 5da211e1c42254cb35e7ef3d5a8428fb24853169 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 - + https://github.com/dotnet/arcade - 5da211e1c42254cb35e7ef3d5a8428fb24853169 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 - + https://github.com/nuget/nuget.client - 9202ddad5fabd4d7737fa0c717524fbe2455c972 + 7f50923823cb8fe4dab9b6565ece9516407de498 - + https://github.com/dotnet/roslyn - 46223204b646f96104bac46f9dfa4959da9d86ac + d7bde97e39857cfa0fc50ef28aaa289e9eebe091 - + https://github.com/dotnet/roslyn - 46223204b646f96104bac46f9dfa4959da9d86ac + d7bde97e39857cfa0fc50ef28aaa289e9eebe091 - + https://github.com/dotnet/arcade - 5da211e1c42254cb35e7ef3d5a8428fb24853169 + aa61e8c20a869bcc994f8b29eb07d927d2bec6f4 diff --git a/eng/Versions.props b/eng/Versions.props index debbe17cc37..3c6c82d3c1d 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -2,11 +2,16 @@ - 17.14.3release - 17.13.9 + 17.15.0 + 17.14.0-preview-25161-14 15.1.0.0 preview - true + + true true 1.1.87 @@ -20,8 +25,17 @@ true - - + + + 6.1.3 + 4.6.3 + 6.1.2 + 4.6.3 + + + 6.1.0 4.6.0 - 4.6.0 - 6.1.0 - 0.2.104-beta 6.1.0 - 5.0.0 + 4.6.0 @@ -47,12 +58,14 @@ 9.0.0 9.0.0 9.0.0 + 5.0.0 9.0.0 9.0.0 9.0.0 9.0.0 + 0.2.104-beta $([System.Text.RegularExpressions.Regex]::Match($([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)..\global.json')), '"dotnet": "([^"]*)"').Groups.get_Item(1)) 4.2.0-1.22102.8 - 9.0.0-beta.25111.5 - 4.14.0-3.25157.4 - 6.14.0-preview.1.53 + 9.0.0-beta.25208.6 + 4.14.0-3.25218.8 + 6.14.0-preview.1.102 9.0.200-preview.0.24603.3 diff --git a/eng/common/core-templates/steps/generate-sbom.yml b/eng/common/core-templates/steps/generate-sbom.yml index d938b60e1bb..56a09009482 100644 --- a/eng/common/core-templates/steps/generate-sbom.yml +++ b/eng/common/core-templates/steps/generate-sbom.yml @@ -38,7 +38,7 @@ steps: PackageName: ${{ parameters.packageName }} BuildDropPath: ${{ parameters.buildDropPath }} PackageVersion: ${{ parameters.packageVersion }} - ManifestDirPath: ${{ parameters.manifestDirPath }} + ManifestDirPath: ${{ parameters.manifestDirPath }}/$(ARTIFACT_NAME) ${{ if ne(parameters.IgnoreDirectories, '') }}: AdditionalComponentDetectorArgs: '--IgnoreDirectories ${{ parameters.IgnoreDirectories }}' diff --git a/eng/common/generate-sbom-prep.ps1 b/eng/common/generate-sbom-prep.ps1 index 3e5c1c74a1c..a0c7d792a76 100644 --- a/eng/common/generate-sbom-prep.ps1 +++ b/eng/common/generate-sbom-prep.ps1 @@ -4,18 +4,26 @@ Param( . $PSScriptRoot\pipeline-logging-functions.ps1 +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +$ArtifactName = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" +$SafeArtifactName = $ArtifactName -replace '["/:<>\\|?@*"() ]', '_' +$SbomGenerationDir = Join-Path $ManifestDirPath $SafeArtifactName + +Write-Host "Artifact name before : $ArtifactName" +Write-Host "Artifact name after : $SafeArtifactName" + Write-Host "Creating dir $ManifestDirPath" + # create directory for sbom manifest to be placed -if (!(Test-Path -path $ManifestDirPath)) +if (!(Test-Path -path $SbomGenerationDir)) { - New-Item -ItemType Directory -path $ManifestDirPath - Write-Host "Successfully created directory $ManifestDirPath" + New-Item -ItemType Directory -path $SbomGenerationDir + Write-Host "Successfully created directory $SbomGenerationDir" } else{ Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." } Write-Host "Updating artifact name" -$artifact_name = "${env:SYSTEM_STAGENAME}_${env:AGENT_JOBNAME}_SBOM" -replace '["/:<>\\|?@*"() ]', '_' -Write-Host "Artifact name $artifact_name" -Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$artifact_name" +Write-Host "##vso[task.setvariable variable=ARTIFACT_NAME]$SafeArtifactName" diff --git a/eng/common/generate-sbom-prep.sh b/eng/common/generate-sbom-prep.sh index d5c76dc827b..b8ecca72bbf 100644 --- a/eng/common/generate-sbom-prep.sh +++ b/eng/common/generate-sbom-prep.sh @@ -14,19 +14,24 @@ done scriptroot="$( cd -P "$( dirname "$source" )" && pwd )" . $scriptroot/pipeline-logging-functions.sh + +# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. +artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" +safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" manifest_dir=$1 -if [ ! -d "$manifest_dir" ] ; then - mkdir -p "$manifest_dir" - echo "Sbom directory created." $manifest_dir +# Normally - we'd listen to the manifest path given, but 1ES templates will overwrite if this level gets uploaded directly +# with their own overwriting ours. So we create it as a sub directory of the requested manifest path. +sbom_generation_dir="$manifest_dir/$safe_artifact_name" + +if [ ! -d "$sbom_generation_dir" ] ; then + mkdir -p "$sbom_generation_dir" + echo "Sbom directory created." $sbom_generation_dir else Write-PipelineTelemetryError -category 'Build' "Unable to create sbom folder." fi -artifact_name=$SYSTEM_STAGENAME"_"$AGENT_JOBNAME"_SBOM" echo "Artifact name before : "$artifact_name -# replace all special characters with _, some builds use special characters like : in Agent.Jobname, that is not a permissible name while uploading artifacts. -safe_artifact_name="${artifact_name//["/:<>\\|?@*$" ]/_}" echo "Artifact name after : "$safe_artifact_name export ARTIFACT_NAME=$safe_artifact_name echo "##vso[task.setvariable variable=ARTIFACT_NAME]$safe_artifact_name" diff --git a/eng/common/templates-official/job/job.yml b/eng/common/templates-official/job/job.yml index 605692d2fb7..817555505aa 100644 --- a/eng/common/templates-official/job/job.yml +++ b/eng/common/templates-official/job/job.yml @@ -16,6 +16,7 @@ jobs: parameters: PackageVersion: ${{ parameters.packageVersion }} BuildDropPath: ${{ parameters.buildDropPath }} + ManifestDirPath: $(Build.ArtifactStagingDirectory)/sbom publishArtifacts: false # publish artifacts diff --git a/eng/common/tools.ps1 b/eng/common/tools.ps1 index a46b6deb759..22b49e09d09 100644 --- a/eng/common/tools.ps1 +++ b/eng/common/tools.ps1 @@ -42,7 +42,7 @@ [bool]$useInstalledDotNetCli = if (Test-Path variable:useInstalledDotNetCli) { $useInstalledDotNetCli } else { $true } # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.ps1 +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.ps1 [string]$dotnetInstallScriptVersion = if (Test-Path variable:dotnetInstallScriptVersion) { $dotnetInstallScriptVersion } else { 'v1' } # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -262,7 +262,7 @@ function GetDotNetInstallScript([string] $dotnetRoot) { if (!(Test-Path $installScript)) { Create-Directory $dotnetRoot $ProgressPreference = 'SilentlyContinue' # Don't display the console progress UI - it's a huge perf hit - $uri = "https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" + $uri = "https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.ps1" Retry({ Write-Host "GET $uri" diff --git a/eng/common/tools.sh b/eng/common/tools.sh index 1159726a10f..01b09b65796 100755 --- a/eng/common/tools.sh +++ b/eng/common/tools.sh @@ -54,7 +54,7 @@ warn_as_error=${warn_as_error:-true} use_installed_dotnet_cli=${use_installed_dotnet_cli:-true} # Enable repos to use a particular version of the on-line dotnet-install scripts. -# default URL: https://dotnet.microsoft.com/download/dotnet/scripts/v1/dotnet-install.sh +# default URL: https://builds.dotnet.microsoft.com/dotnet/scripts/v1/dotnet-install.sh dotnetInstallScriptVersion=${dotnetInstallScriptVersion:-'v1'} # True to use global NuGet cache instead of restoring packages to repository-local directory. @@ -295,7 +295,7 @@ function with_retries { function GetDotNetInstallScript { local root=$1 local install_script="$root/dotnet-install.sh" - local install_script_url="https://dotnet.microsoft.com/download/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" + local install_script_url="https://builds.dotnet.microsoft.com/dotnet/scripts/$dotnetInstallScriptVersion/dotnet-install.sh" if [[ ! -a "$install_script" ]]; then mkdir -p "$root" diff --git a/eng/dependabot/Packages.props b/eng/dependabot/Packages.props index f5c2c790f82..2c9a1ee8d64 100644 --- a/eng/dependabot/Packages.props +++ b/eng/dependabot/Packages.props @@ -13,8 +13,8 @@ - - + + diff --git a/global.json b/global.json index ee7246df20f..cdd0598fc19 100644 --- a/global.json +++ b/global.json @@ -3,13 +3,13 @@ "allowPrerelease": true }, "tools": { - "dotnet": "9.0.103", + "dotnet": "9.0.105", "vs": { "version": "17.12.0" }, "xcopy-msbuild": "17.12.0" }, "msbuild-sdks": { - "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25111.5" + "Microsoft.DotNet.Arcade.Sdk": "9.0.0-beta.25208.6" } } diff --git a/src/Build.OM.UnitTests/Construction/ConstructionEditing_Tests.cs b/src/Build.OM.UnitTests/Construction/ConstructionEditing_Tests.cs index 6cd24d2c366..aaa2c454210 100644 --- a/src/Build.OM.UnitTests/Construction/ConstructionEditing_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ConstructionEditing_Tests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Shared; diff --git a/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs index f7567f061ac..cc0562f9752 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectFormatting_Tests.cs @@ -6,7 +6,6 @@ using System.IO; using System.Linq; using System.Text; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Shared; diff --git a/src/Build.OM.UnitTests/Construction/ProjectItemGroupElement_tests.cs b/src/Build.OM.UnitTests/Construction/ProjectItemGroupElement_tests.cs index 5be0d1fe5dc..706fb7d2b79 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectItemGroupElement_tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectItemGroupElement_tests.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; - using Microsoft.Build.Construction; using Xunit; diff --git a/src/Build.OM.UnitTests/Construction/ProjectPropertyGroupElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectPropertyGroupElement_Tests.cs index 10b21f0a8ad..35cad10fae7 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectPropertyGroupElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectPropertyGroupElement_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Xunit; diff --git a/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs index 271bbd11e8e..95256f674d9 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectRootElement_Tests.cs @@ -9,7 +9,6 @@ using System.Security.AccessControl; using System.Security.Principal; #endif -using System.Reflection; using System.Text; using System.Threading; using System.Xml; @@ -19,10 +18,7 @@ using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using ProjectCollection = Microsoft.Build.Evaluation.ProjectCollection; -using Shouldly; using Xunit; -using Microsoft.Build.Framework; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.OM.UnitTests/Construction/ProjectTargetElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectTargetElement_Tests.cs index 2cb88649b35..41f835cf229 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectTargetElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectTargetElement_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Shouldly; using Xunit; diff --git a/src/Build.OM.UnitTests/Construction/ProjectTaskElement_Tests.cs b/src/Build.OM.UnitTests/Construction/ProjectTaskElement_Tests.cs index 353d2031a3d..679d410d8de 100644 --- a/src/Build.OM.UnitTests/Construction/ProjectTaskElement_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/ProjectTaskElement_Tests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Xunit; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; diff --git a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs index cfd643aea0e..836d884c5f1 100644 --- a/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs +++ b/src/Build.OM.UnitTests/Construction/SolutionFile_Tests.cs @@ -1,12 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Text; using System.Threading; using Microsoft.Build.Construction; using Microsoft.Build.Exceptions; diff --git a/src/Build.OM.UnitTests/Definition/DefinitionEditing_Tests.cs b/src/Build.OM.UnitTests/Definition/DefinitionEditing_Tests.cs index 3e85948675a..6708096bd7e 100644 --- a/src/Build.OM.UnitTests/Definition/DefinitionEditing_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/DefinitionEditing_Tests.cs @@ -5,12 +5,10 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.OM.UnitTests/Definition/EditingElementsReferencedByOrReferences_Tests.cs b/src/Build.OM.UnitTests/Definition/EditingElementsReferencedByOrReferences_Tests.cs index 018dc9dca91..3c7e530d9d3 100644 --- a/src/Build.OM.UnitTests/Definition/EditingElementsReferencedByOrReferences_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/EditingElementsReferencedByOrReferences_Tests.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Evaluation; using Xunit; diff --git a/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs index 9d795ecaa6f..dcce4d61853 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectItemDefinition_Tests.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; diff --git a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs index 490cc3cce47..75b2e82319d 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectItem_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Definition; using Microsoft.Build.Engine.UnitTests.Globbing; @@ -16,7 +15,6 @@ using Microsoft.Build.UnitTests.Shared; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; #nullable disable diff --git a/src/Build.OM.UnitTests/Definition/ProjectMetadata_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectMetadata_Tests.cs index c0807e37e68..f7bae907db1 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectMetadata_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectMetadata_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Shared; diff --git a/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs b/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs index 3ecc456a3cb..c028fd294cd 100644 --- a/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProjectProperty_Tests.cs @@ -4,11 +4,9 @@ using System; using System.Collections.Generic; using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs b/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs index b22c8bf0ab9..6a337e3bbc9 100644 --- a/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs +++ b/src/Build.OM.UnitTests/Definition/ProtectImports_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Evaluation; using Xunit; diff --git a/src/Build.OM.UnitTests/Instance/ProjectInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectInstance_Tests.cs index b8845cd6244..c7278144dae 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectInstance_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/Instance/ProjectItemInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectItemInstance_Tests.cs index e8259bd3120..0e060a450d7 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectItemInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectItemInstance_Tests.cs @@ -3,19 +3,13 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; -using Microsoft.Build.Definition; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.Shared; -using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; #nullable disable diff --git a/src/Build.OM.UnitTests/Instance/ProjectOnErrorInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectOnErrorInstance_Tests.cs index 14346060f12..457e4bb27fb 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectOnErrorInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectOnErrorInstance_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs index 3e9ab10b3df..fb41c9da8a1 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectTargetInstance_Tests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/Instance/ProjectTaskInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectTaskInstance_Tests.cs index 2acc85a6995..c2e0a60ae10 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectTaskInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectTaskInstance_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/Instance/ProjectTaskOutputItemInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectTaskOutputItemInstance_Tests.cs index 716c675982d..31d85a9e608 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectTaskOutputItemInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectTaskOutputItemInstance_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/Instance/ProjectTaskOutputPropertyInstance_Tests.cs b/src/Build.OM.UnitTests/Instance/ProjectTaskOutputPropertyInstance_Tests.cs index 8c9e4770842..fb17c3b113b 100644 --- a/src/Build.OM.UnitTests/Instance/ProjectTaskOutputPropertyInstance_Tests.cs +++ b/src/Build.OM.UnitTests/Instance/ProjectTaskOutputPropertyInstance_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.OM.UnitTests/NugetRestoreTests.cs b/src/Build.OM.UnitTests/NugetRestoreTests.cs index ee35a83c93a..d5b4b8bbf38 100644 --- a/src/Build.OM.UnitTests/NugetRestoreTests.cs +++ b/src/Build.OM.UnitTests/NugetRestoreTests.cs @@ -1,13 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System.IO; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; using Shouldly; -using System.IO; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; namespace Microsoft.Build.Engine.OM.UnitTests { diff --git a/src/Build.OM.UnitTests/ObjectModelRemoting/RemoteProjectsProviderMock/ExporterMock.cs b/src/Build.OM.UnitTests/ObjectModelRemoting/RemoteProjectsProviderMock/ExporterMock.cs index 05112a6ed62..d21ca7a50d6 100644 --- a/src/Build.OM.UnitTests/ObjectModelRemoting/RemoteProjectsProviderMock/ExporterMock.cs +++ b/src/Build.OM.UnitTests/ObjectModelRemoting/RemoteProjectsProviderMock/ExporterMock.cs @@ -7,9 +7,7 @@ namespace Microsoft.Build.UnitTests.OM.ObjectModelRemoting { using System; using System.Collections.Generic; - using System.IO; using System.Threading; - using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.ObjectModelRemoting; diff --git a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs index 55834d7d650..9a0bb05138a 100644 --- a/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BinaryTranslator_Tests.cs @@ -8,7 +8,6 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.CompilerServices; using Microsoft.Build.BackEnd; using Microsoft.Build.Exceptions; using Microsoft.Build.Framework; diff --git a/src/Build.UnitTests/BackEnd/BuildEventArgTransportSink_Tests.cs b/src/Build.UnitTests/BackEnd/BuildEventArgTransportSink_Tests.cs index 2a2e8822ba1..d36e9d716a3 100644 --- a/src/Build.UnitTests/BackEnd/BuildEventArgTransportSink_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildEventArgTransportSink_Tests.cs @@ -5,9 +5,7 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Logging_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Logging_Tests.cs index 99f6603f61c..d588c980ab4 100644 --- a/src/Build.UnitTests/BackEnd/BuildManager_Logging_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildManager_Logging_Tests.cs @@ -15,7 +15,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; using static Microsoft.Build.UnitTests.ObjectModelHelpers; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs index 2111a5ee369..35bf1f4cc3a 100644 --- a/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildManager_Tests.cs @@ -10,7 +10,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs index b4efa7ea860..b7ea019500a 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEngine_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Threading; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; diff --git a/src/Build.UnitTests/BackEnd/BuildRequestEntry_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequestEntry_Tests.cs index b994f4ceb2c..9e84a022a90 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequestEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequestEntry_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Unittest; using Xunit; diff --git a/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs b/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs index 2090f4c4807..5e103b5ef66 100644 --- a/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildRequest_Tests.cs @@ -9,7 +9,6 @@ using Microsoft.Build.Framework; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs index dec50951300..1b94ac3357f 100644 --- a/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs +++ b/src/Build.UnitTests/BackEnd/BuildResult_Tests.cs @@ -9,7 +9,6 @@ using Microsoft.Build.Exceptions; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Unittest; using Xunit; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; diff --git a/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs b/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs index bbe0f749387..db1dcd72cb4 100644 --- a/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs +++ b/src/Build.UnitTests/BackEnd/CacheAggregator_Tests.cs @@ -6,8 +6,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; using Microsoft.Build.Unittest; using Shouldly; using Xunit; @@ -33,10 +31,10 @@ public void NoCachesProducesEmptyCaches() var aggregation = aggregator.Aggregate(); aggregation.ConfigCache.ShouldNotBeNull(); - aggregation.ConfigCache.GetEnumerator().ToEnumerable().ShouldBeEmpty(); + aggregation.ConfigCache.ShouldBeEmpty(); aggregation.ResultsCache.ShouldNotBeNull(); - aggregation.ResultsCache.GetEnumerator().ToEnumerable().ShouldBeEmpty(); + aggregation.ResultsCache.ShouldBeEmpty(); aggregation.LastConfigurationId.ShouldBe(0); } @@ -246,9 +244,9 @@ private void AssertAggregation((ConfigCache configCache, ResultsCache resultsCac var currentConfigurationIndex = 0; var currentBuildResultIndex = 0; - var aggregatedConfigs = aggregation.ConfigCache.GetEnumerator().ToArray(); + var aggregatedConfigs = aggregation.ConfigCache.ToArray(); - var aggregatedResults = aggregation.ResultsCache.GetEnumerator().ToArray(); + var aggregatedResults = aggregation.ResultsCache.ToArray(); foreach (var (configCache, resultsCache) in inputCaches) { diff --git a/src/Build.UnitTests/BackEnd/CentralForwardingLogger_Tests.cs b/src/Build.UnitTests/BackEnd/CentralForwardingLogger_Tests.cs index da450fb9228..64f34e78d8f 100644 --- a/src/Build.UnitTests/BackEnd/CentralForwardingLogger_Tests.cs +++ b/src/Build.UnitTests/BackEnd/CentralForwardingLogger_Tests.cs @@ -3,7 +3,6 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs b/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs index 3ca18bc9832..bf40853c4e8 100644 --- a/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs +++ b/src/Build.UnitTests/BackEnd/ConfigCache_Tests.cs @@ -101,8 +101,8 @@ public void ConfigCacheShouldBeTranslatable(object obj) TranslationHelpers.GetReadTranslator().Translate(ref copy); // test _configurations - var initialConfigurations = initial.GetEnumerator().ToArray(); - var copiedConfigurations = copy.GetEnumerator().ToArray(); + var initialConfigurations = initial.ToArray(); + var copiedConfigurations = copy.ToArray(); Assert.Equal(copiedConfigurations, initialConfigurations, EqualityComparer.Default); diff --git a/src/Build.UnitTests/BackEnd/ConfigurationMetadata_Tests.cs b/src/Build.UnitTests/BackEnd/ConfigurationMetadata_Tests.cs index 7900e9564cb..859e5507f68 100644 --- a/src/Build.UnitTests/BackEnd/ConfigurationMetadata_Tests.cs +++ b/src/Build.UnitTests/BackEnd/ConfigurationMetadata_Tests.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; diff --git a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs index 6f5fd31c934..2a48fe4d81a 100644 --- a/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs +++ b/src/Build.UnitTests/BackEnd/DebugUtils_tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Shared; using Shouldly; using Xunit; -using Xunit.Abstractions; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/EventRedirectorToSink_Tests.cs b/src/Build.UnitTests/BackEnd/EventRedirectorToSink_Tests.cs index d36c7ee0667..1971cca0763 100644 --- a/src/Build.UnitTests/BackEnd/EventRedirectorToSink_Tests.cs +++ b/src/Build.UnitTests/BackEnd/EventRedirectorToSink_Tests.cs @@ -3,7 +3,6 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/EventSourceTestHelper.cs b/src/Build.UnitTests/BackEnd/EventSourceTestHelper.cs index 8d3929afaba..e2ed8378c07 100644 --- a/src/Build.UnitTests/BackEnd/EventSourceTestHelper.cs +++ b/src/Build.UnitTests/BackEnd/EventSourceTestHelper.cs @@ -1,12 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Diagnostics.Tracing; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Build.Engine.UnitTests.BackEnd { diff --git a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs index 7f0ccecd569..3d65794eb1f 100644 --- a/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs +++ b/src/Build.UnitTests/BackEnd/IntrinsicTask_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Construction; diff --git a/src/Build.UnitTests/BackEnd/LoggingConfigurationTelemetry_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingConfigurationTelemetry_Tests.cs index d6e66cc6ecd..56ec028ddf1 100644 --- a/src/Build.UnitTests/BackEnd/LoggingConfigurationTelemetry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingConfigurationTelemetry_Tests.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable disable -using System; -using System.Globalization; using System.Linq; using Microsoft.Build.Framework.Telemetry; using Shouldly; diff --git a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs index 7ccbcc227b7..020d527ab3f 100644 --- a/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingContext_Tests.cs @@ -3,7 +3,6 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Shouldly; using Xunit; using Xunit.Abstractions; diff --git a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs index eea8d075466..4d42f596bcb 100644 --- a/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingService_Tests.cs @@ -14,7 +14,6 @@ using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Logging; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.BackEnd; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs index fb932fb70d5..3fe5fcd8e6e 100644 --- a/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs +++ b/src/Build.UnitTests/BackEnd/LoggingServicesLogMethod_Tests.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Construction; diff --git a/src/Build.UnitTests/BackEnd/Lookup_Tests.cs b/src/Build.UnitTests/BackEnd/Lookup_Tests.cs index 9ee0264517d..90c0c170a0d 100644 --- a/src/Build.UnitTests/BackEnd/Lookup_Tests.cs +++ b/src/Build.UnitTests/BackEnd/Lookup_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Collections; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.UnitTests/BackEnd/MockHost.cs b/src/Build.UnitTests/BackEnd/MockHost.cs index 1f08b92bf79..3326ddfd49e 100644 --- a/src/Build.UnitTests/BackEnd/MockHost.cs +++ b/src/Build.UnitTests/BackEnd/MockHost.cs @@ -5,10 +5,10 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; -using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Engine.UnitTests.BackEnd; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; +using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.TelemetryInfra; using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; diff --git a/src/Build.UnitTests/BackEnd/NodeConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/NodeConfiguration_Tests.cs index bb0c19a4415..bd3b9855abf 100644 --- a/src/Build.UnitTests/BackEnd/NodeConfiguration_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodeConfiguration_Tests.cs @@ -2,10 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Logging; diff --git a/src/Build.UnitTests/BackEnd/NodeEndpointInProc_Tests.cs b/src/Build.UnitTests/BackEnd/NodeEndpointInProc_Tests.cs index 5a1eb10715b..0c22214c0ee 100644 --- a/src/Build.UnitTests/BackEnd/NodeEndpointInProc_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodeEndpointInProc_Tests.cs @@ -8,9 +8,8 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; -using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; using Xunit; +using LegacyThreadingData = Microsoft.Build.Execution.LegacyThreadingData; #nullable disable @@ -106,6 +105,11 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr throw new NotImplementedException(); } + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + throw new NotImplementedException(); + } + public void RoutePacket(int nodeId, INodePacket packet) { _dataReceivedContext = new DataReceivedContext(Thread.CurrentThread, packet); diff --git a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs index 3da36a531ab..8ca50416de7 100644 --- a/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs +++ b/src/Build.UnitTests/BackEnd/NodePackets_Tests.cs @@ -10,6 +10,7 @@ using Microsoft.Build.Execution; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; +using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared; using Xunit; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; @@ -331,12 +332,12 @@ public void TestTranslation() LogMessagePacket deserializedPacket = tempPacket as LogMessagePacket; packet.Should().BeEquivalentTo(deserializedPacket, options => options - .RespectingRuntimeTypes()); + .PreferringRuntimeMemberTypes()); BuildEventArgs args = packet.NodeBuildEvent?.Value; BuildEventArgs desArgs = deserializedPacket?.NodeBuildEvent?.Value; desArgs.Should().BeEquivalentTo(args, options => options - .RespectingRuntimeTypes() + .PreferringRuntimeMemberTypes() // Since we use struct DictionaryEntry of class TaskItemData, generated DictionaryEntry.Equals compare TaskItemData by references. // Bellow will instruct equivalency test to not use DictionaryEntry.Equals but its public members for equivalency tests. .ComparingByMembers() diff --git a/src/Build.UnitTests/BackEnd/RedirectConsoleWriter_Tests.cs b/src/Build.UnitTests/BackEnd/RedirectConsoleWriter_Tests.cs index 81caa63af47..b8adb10c2a2 100644 --- a/src/Build.UnitTests/BackEnd/RedirectConsoleWriter_Tests.cs +++ b/src/Build.UnitTests/BackEnd/RedirectConsoleWriter_Tests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; using System.Text; using System.Threading.Tasks; using Microsoft.Build.Experimental; diff --git a/src/Build.UnitTests/BackEnd/RequestedProjectState_Tests.cs b/src/Build.UnitTests/BackEnd/RequestedProjectState_Tests.cs index 034b3d1594a..166fd5750bc 100644 --- a/src/Build.UnitTests/BackEnd/RequestedProjectState_Tests.cs +++ b/src/Build.UnitTests/BackEnd/RequestedProjectState_Tests.cs @@ -4,9 +4,7 @@ using System.Collections.Generic; using FluentAssertions; using Microsoft.Build.Execution; -using Shouldly; using Xunit; -using Xunit.Abstractions; namespace Microsoft.Build.UnitTests.BackEnd { diff --git a/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs b/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs index 7fc43eccc59..77f6dc04be6 100644 --- a/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs +++ b/src/Build.UnitTests/BackEnd/ResultsCache_Tests.cs @@ -8,11 +8,8 @@ using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Construction; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; using Microsoft.Build.Unittest; using Shouldly; using Xunit; @@ -80,7 +77,7 @@ public void CacheCanBeEnumerated() result2.AddResultsForTarget("result2target1", BuildResultUtilities.GetEmptyFailingTargetResult()); cache.AddResult(result2); - var results = cache.GetEnumerator().ToArray(); + var results = cache.ToArray(); results.Length.ShouldBe(2); diff --git a/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs b/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs index 7b0cd8745c6..e470f6be153 100644 --- a/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs +++ b/src/Build.UnitTests/BackEnd/Scheduler_Tests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs index 4a891408cc9..10a1c5dcd6c 100644 --- a/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs +++ b/src/Build.UnitTests/BackEnd/SdkResolverService_Tests.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.Configuration; -using System.Diagnostics.Tracing; using System.Linq; using System.Text.RegularExpressions; using System.Threading; diff --git a/src/Build.UnitTests/BackEnd/SdkResultItemComparison_Tests.cs b/src/Build.UnitTests/BackEnd/SdkResultItemComparison_Tests.cs index 6af6e6616b5..a1f051339fa 100644 --- a/src/Build.UnitTests/BackEnd/SdkResultItemComparison_Tests.cs +++ b/src/Build.UnitTests/BackEnd/SdkResultItemComparison_Tests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using Microsoft.Build.Framework; using Shouldly; diff --git a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs index 11d5207ec9b..b39a91f56cb 100644 --- a/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetBuilder_Tests.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Collections; diff --git a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs index b2cb7cd17bd..259b3f530dc 100644 --- a/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetEntry_Tests.cs @@ -7,7 +7,6 @@ using System.IO; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; diff --git a/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs b/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs index 743e165ca0a..d54a77ac90a 100644 --- a/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TargetUpToDateChecker_Tests.cs @@ -7,7 +7,6 @@ using System.Runtime.InteropServices; using System.Runtime.Versioning; using System.Threading; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; diff --git a/src/Build.UnitTests/BackEnd/TaskBuilderTestTask.cs b/src/Build.UnitTests/BackEnd/TaskBuilderTestTask.cs index 64ffc8fba5c..c66854b0206 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilderTestTask.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilderTestTask.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Reflection; using Microsoft.Build.Framework; diff --git a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs index 68a8dea7eb0..f0155787682 100644 --- a/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskBuilder_Tests.cs @@ -7,7 +7,6 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Collections; diff --git a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs index 61b1551576c..b1a2a334e8d 100644 --- a/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskExecutionHost_Tests.cs @@ -8,7 +8,6 @@ using System.Linq; using System.Reflection; using System.Threading; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; diff --git a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs index dcf1f45727d..959142d4a67 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostConfiguration_Tests.cs @@ -6,13 +6,11 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Text; using System.Threading; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs index ebb24ca82e6..401b0f9ea49 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostFactory_Tests.cs @@ -6,7 +6,6 @@ using System.Globalization; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.BackEnd; diff --git a/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs b/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs index b6b6025441b..c475da6dcac 100644 --- a/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs +++ b/src/Build.UnitTests/BackEnd/TaskHostTaskComplete_Tests.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; using Microsoft.Build.Experimental.FileAccess; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using Xunit; diff --git a/src/Build.UnitTests/BackEnd/TaskThatThrows.cs b/src/Build.UnitTests/BackEnd/TaskThatThrows.cs index b837e225740..f62541256cc 100644 --- a/src/Build.UnitTests/BackEnd/TaskThatThrows.cs +++ b/src/Build.UnitTests/BackEnd/TaskThatThrows.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Linq; using System.Reflection; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Build.Engine.UnitTests; diff --git a/src/Build.UnitTests/BackEnd/TranslationHelpers.cs b/src/Build.UnitTests/BackEnd/TranslationHelpers.cs index 7d4736837ce..bdcad9d36bb 100644 --- a/src/Build.UnitTests/BackEnd/TranslationHelpers.cs +++ b/src/Build.UnitTests/BackEnd/TranslationHelpers.cs @@ -9,7 +9,6 @@ using System.Text; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; -using Xunit; #nullable disable diff --git a/src/Build.UnitTests/BinaryLogger_Tests.cs b/src/Build.UnitTests/BinaryLogger_Tests.cs index 3ff827e24aa..9334bcf5314 100644 --- a/src/Build.UnitTests/BinaryLogger_Tests.cs +++ b/src/Build.UnitTests/BinaryLogger_Tests.cs @@ -6,7 +6,6 @@ using System.IO; using System.IO.Compression; using System.Linq; -using System.Reflection; using System.Text; using FakeItEasy; using FluentAssertions; diff --git a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs index d987efa0fab..f500c937ab9 100644 --- a/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs +++ b/src/Build.UnitTests/BuildEnvironmentHelper_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Shared; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/BuildEventArgsDataEnumeration.cs b/src/Build.UnitTests/BuildEventArgsDataEnumeration.cs index 0f760d002c5..83042fcf5f5 100644 --- a/src/Build.UnitTests/BuildEventArgsDataEnumeration.cs +++ b/src/Build.UnitTests/BuildEventArgsDataEnumeration.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections; using System.Collections.Generic; using System.Linq; diff --git a/src/Build.UnitTests/ChangeWaves_Tests.cs b/src/Build.UnitTests/ChangeWaves_Tests.cs index 9bfa098ee0d..d81e765c3ab 100644 --- a/src/Build.UnitTests/ChangeWaves_Tests.cs +++ b/src/Build.UnitTests/ChangeWaves_Tests.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; -using Microsoft.Build.Utilities; using Shouldly; using Xunit; using Xunit.Abstractions; diff --git a/src/Build.UnitTests/Collections/MSBuildNameIgnoreCaseComparer_Tests.cs b/src/Build.UnitTests/Collections/MSBuildNameIgnoreCaseComparer_Tests.cs index be97777e5e3..dfc4482a09f 100644 --- a/src/Build.UnitTests/Collections/MSBuildNameIgnoreCaseComparer_Tests.cs +++ b/src/Build.UnitTests/Collections/MSBuildNameIgnoreCaseComparer_Tests.cs @@ -5,7 +5,6 @@ using Microsoft.Build.Collections; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Xunit; #nullable disable diff --git a/src/Build.UnitTests/Collections/OMcollections_tests.cs b/src/Build.UnitTests/Collections/OMcollections_tests.cs index 354a6bc9464..56273244f51 100644 --- a/src/Build.UnitTests/Collections/OMcollections_tests.cs +++ b/src/Build.UnitTests/Collections/OMcollections_tests.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.BackEnd; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs index 014b8521524..a15fffd391d 100644 --- a/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFile_NewParser_Tests.cs @@ -1,13 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections; using System.Collections.Generic; -using System.IO; using System.Threading; using Microsoft.Build.Construction; -using Microsoft.Build.Exceptions; using Microsoft.Build.Shared; using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; diff --git a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs index 71a769aed3b..facbeff5386 100644 --- a/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs +++ b/src/Build.UnitTests/Construction/SolutionFilter_Tests.cs @@ -15,9 +15,9 @@ using Microsoft.Build.Framework; using Microsoft.Build.Graph; using Microsoft.Build.UnitTests; +using Microsoft.VisualStudio.SolutionPersistence; using Microsoft.VisualStudio.SolutionPersistence.Model; using Microsoft.VisualStudio.SolutionPersistence.Serializer; -using Microsoft.VisualStudio.SolutionPersistence; using Shouldly; using Xunit; using Xunit.Abstractions; diff --git a/src/Build.UnitTests/Definition/ProjectHelpers.cs b/src/Build.UnitTests/Definition/ProjectHelpers.cs index b3be8049606..2f0ccf65fe7 100644 --- a/src/Build.UnitTests/Definition/ProjectHelpers.cs +++ b/src/Build.UnitTests/Definition/ProjectHelpers.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; -using System.Xml; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; diff --git a/src/Build.UnitTests/Definition/ProjectItem_Tests.cs b/src/Build.UnitTests/Definition/ProjectItem_Tests.cs index ce86f880e14..bb33b9a9733 100644 --- a/src/Build.UnitTests/Definition/ProjectItem_Tests.cs +++ b/src/Build.UnitTests/Definition/ProjectItem_Tests.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Xunit; diff --git a/src/Build.UnitTests/Definition/Project_Internal_Tests.cs b/src/Build.UnitTests/Definition/Project_Internal_Tests.cs index 8255424b6a7..fbdc08a9dee 100644 --- a/src/Build.UnitTests/Definition/Project_Internal_Tests.cs +++ b/src/Build.UnitTests/Definition/Project_Internal_Tests.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; using System.Xml; using Microsoft.Build.Construction; using Microsoft.Build.Definition; diff --git a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs index 7ca7b24910b..115f418e3c3 100644 --- a/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs +++ b/src/Build.UnitTests/Definition/ToolsVersion_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Text.RegularExpressions; -using System.Xml; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Construction; @@ -15,7 +14,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; -using Xunit.NetCore.Extensions; using InternalUtilities = Microsoft.Build.Internal.Utilities; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using LoggerMode = Microsoft.Build.BackEnd.Logging.LoggerMode; diff --git a/src/Build.UnitTests/Definition/ToolsetConfigurationReader_Tests.cs b/src/Build.UnitTests/Definition/ToolsetConfigurationReader_Tests.cs index 50aadc89522..f894d193999 100644 --- a/src/Build.UnitTests/Definition/ToolsetConfigurationReader_Tests.cs +++ b/src/Build.UnitTests/Definition/ToolsetConfigurationReader_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; -using Microsoft.Build.Shared; using Xunit; using ToolsetConfigurationSection = Microsoft.Build.Evaluation.ToolsetConfigurationSection; diff --git a/src/Build.UnitTests/Definition/ToolsetReader_Tests.cs b/src/Build.UnitTests/Definition/ToolsetReader_Tests.cs index 833ac2561a1..846be328f53 100644 --- a/src/Build.UnitTests/Definition/ToolsetReader_Tests.cs +++ b/src/Build.UnitTests/Definition/ToolsetReader_Tests.cs @@ -20,7 +20,6 @@ using InvalidToolsetDefinitionException = Microsoft.Build.Exceptions.InvalidToolsetDefinitionException; using InternalUtilities = Microsoft.Build.Internal.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/Definition/Toolset_Tests.cs b/src/Build.UnitTests/Definition/Toolset_Tests.cs index 9c76a26954e..929513584ac 100644 --- a/src/Build.UnitTests/Definition/Toolset_Tests.cs +++ b/src/Build.UnitTests/Definition/Toolset_Tests.cs @@ -9,10 +9,8 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Internal; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests.BackEnd; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/Evaluation/ExpanderFunction_Tests.cs b/src/Build.UnitTests/Evaluation/ExpanderFunction_Tests.cs index 5cc7e4309cc..fe0acc1db0d 100644 --- a/src/Build.UnitTests/Evaluation/ExpanderFunction_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ExpanderFunction_Tests.cs @@ -2,11 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Globalization; -using System.Runtime.InteropServices; using System.Threading; -using Microsoft.Build.Evaluation; - using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs b/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs index c354a5b26e0..53ca81fcccb 100644 --- a/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ImportFromMSBuildExtensionsPath_Tests.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Execution; using Microsoft.Build.Shared; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/Evaluation/IntrinsicFunctionOverload_Tests.cs b/src/Build.UnitTests/Evaluation/IntrinsicFunctionOverload_Tests.cs index c34473dd335..4501f10688c 100644 --- a/src/Build.UnitTests/Evaluation/IntrinsicFunctionOverload_Tests.cs +++ b/src/Build.UnitTests/Evaluation/IntrinsicFunctionOverload_Tests.cs @@ -1,13 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.IO; -using System.Xml; - using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Shouldly; diff --git a/src/Build.UnitTests/Evaluation/ProjectRootElementCache_Tests.cs b/src/Build.UnitTests/Evaluation/ProjectRootElementCache_Tests.cs index 915e38582d5..0709ee7be41 100644 --- a/src/Build.UnitTests/Evaluation/ProjectRootElementCache_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ProjectRootElementCache_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; -using Xunit.NetCore.Extensions; diff --git a/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs b/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs index e082039d337..947d1dc22c6 100644 --- a/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs +++ b/src/Build.UnitTests/Evaluation/SimpleProjectRootElementCache_Tests.cs @@ -5,7 +5,6 @@ using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/Evaluation/ToolsetConfigurationNet5_Tests.cs b/src/Build.UnitTests/Evaluation/ToolsetConfigurationNet5_Tests.cs index 85abd3297a2..6a3ad2f17bd 100644 --- a/src/Build.UnitTests/Evaluation/ToolsetConfigurationNet5_Tests.cs +++ b/src/Build.UnitTests/Evaluation/ToolsetConfigurationNet5_Tests.cs @@ -5,12 +5,11 @@ /* This test is designed especially to test Configuration parsing in net5.0 * which means it WON'T work in net472 and thus we don't run it in net472 */ +using System.Collections.Generic; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; - -using Xunit; -using System.Collections.Generic; using Shouldly; +using Xunit; #nullable disable diff --git a/src/Build.UnitTests/EvaluationProfiler_Tests.cs b/src/Build.UnitTests/EvaluationProfiler_Tests.cs index ff450b8a77b..ba4945a0bde 100644 --- a/src/Build.UnitTests/EvaluationProfiler_Tests.cs +++ b/src/Build.UnitTests/EvaluationProfiler_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Immutable; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; diff --git a/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs b/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs index 72a46b6d23b..80781a43671 100644 --- a/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs +++ b/src/Build.UnitTests/FileUtilitiesRegex_Tests.cs @@ -532,7 +532,6 @@ public void PatternEmptyString_LegacyRegex() { UncPattern.IsMatch(string.Empty).ShouldBeFalse(); StartsWithUncPattern.IsMatch(string.Empty).ShouldBeFalse(); - StartsWithUncPattern.Match(string.Empty).Success.ShouldBeFalse(); } [Fact] diff --git a/src/Build.UnitTests/FixPathOnUnix_Tests.cs b/src/Build.UnitTests/FixPathOnUnix_Tests.cs index 757c2ccde2c..bd5ac2b4224 100644 --- a/src/Build.UnitTests/FixPathOnUnix_Tests.cs +++ b/src/Build.UnitTests/FixPathOnUnix_Tests.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Framework; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs index cc551762e11..d6295254e32 100644 --- a/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs +++ b/src/Build.UnitTests/Globbing/MSBuildGlob_Tests.cs @@ -70,9 +70,12 @@ public void GlobFromRelativeGlobRootNormalizesRootAgainstCurrentDirectory() [Fact] public void GlobFromRootWithInvalidPathThrows() { - foreach (var invalidPathChar in FileUtilities.InvalidPathChars) + for (int i = 0; i < 128; i++) { - Assert.Throws(() => MSBuildGlob.Parse(invalidPathChar.ToString(), "*")); + if (FileUtilities.InvalidPathChars.Contains((char)i)) + { + Assert.Throws(() => MSBuildGlob.Parse(((char)i).ToString(), "*")); + } } } @@ -182,12 +185,15 @@ public void GlobMatchShouldReturnFalseIfArgumentContainsInvalidPathOrFileCharact { var glob = MSBuildGlob.Parse("*"); - foreach (var invalidPathChar in FileUtilities.InvalidPathChars) + for (int i = 0; i < 128; i++) { - Assert.False(glob.IsMatch(invalidPathChar.ToString())); + if (FileUtilities.InvalidPathChars.Contains((char)i)) + { + Assert.False(glob.IsMatch(((char)i).ToString())); + } } - foreach (var invalidFileChar in FileUtilities.InvalidFileNameChars) + foreach (var invalidFileChar in FileUtilities.InvalidFileNameCharsArray) { if (invalidFileChar == '\\' || invalidFileChar == '/') { diff --git a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs index d6344240f1a..d2e63cd06f8 100644 --- a/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs +++ b/src/Build.UnitTests/Graph/GetCompatiblePlatformGraph_Tests.cs @@ -1,24 +1,13 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; using System.IO; -using System.Linq; -using System.Text.RegularExpressions; using Microsoft.Build.Evaluation; -using Microsoft.Build.Exceptions; -using Microsoft.Build.Execution; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Shouldly; using Xunit; -using Xunit.Abstractions; using static Microsoft.Build.Graph.UnitTests.GraphTestingUtilities; -using static Microsoft.Build.Graph.UnitTests.ProjectGraphTests; #nullable disable diff --git a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs index 6d535479b1e..bcf72ce632a 100644 --- a/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs +++ b/src/Build.UnitTests/Graph/GraphLoadedFromSolution_tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using Microsoft.Build.BackEnd; -using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Engine.UnitTests; using Microsoft.Build.Exceptions; diff --git a/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs b/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs index 063df0be739..13bb4275c65 100644 --- a/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs +++ b/src/Build.UnitTests/Graph/IsolateProjects_Tests.cs @@ -7,7 +7,6 @@ using System.Linq; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Shouldly; @@ -338,8 +337,8 @@ public void UndeclaredReferenceBuildResultNotPresentInOutputCache() var deserializedOutputCacheRoot = CacheSerialization.DeserializeCaches(outputCaches[topoSortedProjectGraphNodes[1]]); deserializedOutputCacheDeclaredReference.exception.ShouldBeNull(); deserializedOutputCacheRoot.exception.ShouldBeNull(); - BuildResult[] declaredReferenceBuildResults = deserializedOutputCacheDeclaredReference.ResultsCache.GetEnumerator().ToArray(); - BuildResult[] rootBuildResults = deserializedOutputCacheRoot.ResultsCache.GetEnumerator().ToArray(); + BuildResult[] declaredReferenceBuildResults = deserializedOutputCacheDeclaredReference.ResultsCache.ToArray(); + BuildResult[] rootBuildResults = deserializedOutputCacheRoot.ResultsCache.ToArray(); // Both the root and declared reference projects should only have one build result. declaredReferenceBuildResults.Length.ShouldBe(1); diff --git a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs index b1a0b664c80..f9dd9bf05d4 100644 --- a/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs +++ b/src/Build.UnitTests/Graph/ResultCacheBasedBuilds_Tests.cs @@ -11,7 +11,6 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; using Microsoft.Build.UnitTests; using Shouldly; using Xunit; @@ -356,13 +355,13 @@ public void OutputCacheShouldNotContainInformationFromInputCaches() deserializationInfo.exception.ShouldBeNull(); - var buildResults = deserializationInfo.ResultsCache.GetEnumerator().ToArray(); + var buildResults = deserializationInfo.ResultsCache.ToArray(); buildResults.ShouldHaveSingleItem(); var rootNodeBuildResult = buildResults.First(); rootNodeBuildResult.ResultsByTarget["Build"].Items.Select(i => i.ItemSpec).ToArray().ShouldBe(expectedOutput[rootNode]); - var configEntries = deserializationInfo.ConfigCache.GetEnumerator().ToArray(); + var configEntries = deserializationInfo.ConfigCache.ToArray(); configEntries.ShouldHaveSingleItem(); configEntries.First().ConfigurationId.ShouldBe(rootNodeBuildResult.ConfigurationId); diff --git a/src/Build.UnitTests/Instance/HostServices_Tests.cs b/src/Build.UnitTests/Instance/HostServices_Tests.cs index ed18f318540..e05239f9528 100644 --- a/src/Build.UnitTests/Instance/HostServices_Tests.cs +++ b/src/Build.UnitTests/Instance/HostServices_Tests.cs @@ -13,7 +13,6 @@ using Microsoft.Build.UnitTests.BackEnd; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs index cb3ceade820..49fc0f9bfad 100644 --- a/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs +++ b/src/Build.UnitTests/Instance/ProjectInstance_Internal_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Construction; using Microsoft.Build.Definition; diff --git a/src/Build.UnitTests/Instance/TaskItem_Tests.cs b/src/Build.UnitTests/Instance/TaskItem_Tests.cs index 7699a66b623..af1e84b2397 100644 --- a/src/Build.UnitTests/Instance/TaskItem_Tests.cs +++ b/src/Build.UnitTests/Instance/TaskItem_Tests.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.IO; using System.Linq; -using System.Xml; using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Construction; diff --git a/src/Build.UnitTests/InvalidProjectFileException_Tests.cs b/src/Build.UnitTests/InvalidProjectFileException_Tests.cs index 0987c3e9d6a..7a1d62aab19 100644 --- a/src/Build.UnitTests/InvalidProjectFileException_Tests.cs +++ b/src/Build.UnitTests/InvalidProjectFileException_Tests.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using Microsoft.Build.Exceptions; using Xunit; diff --git a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj index db6518f967b..854159dc86d 100644 --- a/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj +++ b/src/Build.UnitTests/Microsoft.Build.Engine.UnitTests.csproj @@ -18,7 +18,7 @@ - + @@ -31,7 +31,7 @@ - + @@ -40,7 +40,7 @@ - + TargetFramework=$(FullFrameworkTFM) TargetFramework=$(LatestDotNetCoreForMSBuild) @@ -51,7 +51,7 @@ TargetFramework=$(FullFrameworkTFM) TargetFramework=$(LatestDotNetCoreForMSBuild) - +
diff --git a/src/Build.UnitTests/NodeStatus_SizeChange_Tests.cs b/src/Build.UnitTests/NodeStatus_SizeChange_Tests.cs index 458eb4edb27..bbe391ce360 100644 --- a/src/Build.UnitTests/NodeStatus_SizeChange_Tests.cs +++ b/src/Build.UnitTests/NodeStatus_SizeChange_Tests.cs @@ -2,15 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Globalization; -using System.Linq; -using System.Text; using System.Threading.Tasks; using Microsoft.Build.Logging; - -using VerifyTests; using VerifyXunit; using Xunit; diff --git a/src/Build.UnitTests/NodeStatus_Transition_Tests.cs b/src/Build.UnitTests/NodeStatus_Transition_Tests.cs index d811c2e4988..62cc3557215 100644 --- a/src/Build.UnitTests/NodeStatus_Transition_Tests.cs +++ b/src/Build.UnitTests/NodeStatus_Transition_Tests.cs @@ -2,16 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; using System.Text; -using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.Build.Framework.Logging; using Microsoft.Build.Logging; using Shouldly; -using VerifyTests; using VerifyXunit; using Xunit; diff --git a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs index 449bdf1401d..e5ec5d49edd 100644 --- a/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs +++ b/src/Build.UnitTests/ProjectCache/ProjectCacheTests.cs @@ -10,7 +10,6 @@ using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.CommandLine; using Microsoft.Build.Construction; using Microsoft.Build.Execution; using Microsoft.Build.Experimental.ProjectCache; @@ -24,7 +23,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.Sdk; using Task = System.Threading.Tasks.Task; namespace Microsoft.Build.Engine.UnitTests.ProjectCache diff --git a/src/Build.UnitTests/Scanner_Tests.cs b/src/Build.UnitTests/Scanner_Tests.cs index b2b705d2c22..da75fd75436 100644 --- a/src/Build.UnitTests/Scanner_Tests.cs +++ b/src/Build.UnitTests/Scanner_Tests.cs @@ -4,9 +4,6 @@ using System; using Microsoft.Build.Evaluation; using Microsoft.Build.Exceptions; -using Microsoft.Build.Framework; -using Microsoft.Build.Shared; -using Microsoft.Build.Utilities; using Shouldly; using Xunit; diff --git a/src/Build.UnitTests/StaticStopwatch.cs b/src/Build.UnitTests/StaticStopwatch.cs index 63d1bf7bcbd..8846e440b20 100644 --- a/src/Build.UnitTests/StaticStopwatch.cs +++ b/src/Build.UnitTests/StaticStopwatch.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Net.Http.Headers; using Microsoft.Build.Logging; namespace Microsoft.Build.CommandLine.UnitTests; diff --git a/src/Build.UnitTests/BackEnd/OpenTelemetryActivities_Tests.cs b/src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs similarity index 97% rename from src/Build.UnitTests/BackEnd/OpenTelemetryActivities_Tests.cs rename to src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs index cd041632de2..7a567e79495 100644 --- a/src/Build.UnitTests/BackEnd/OpenTelemetryActivities_Tests.cs +++ b/src/Build.UnitTests/Telemetry/OpenTelemetryActivities_Tests.cs @@ -4,12 +4,11 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; -using Xunit; +using Microsoft.Build.Framework.Telemetry; using Shouldly; +using Xunit; -namespace Microsoft.Build.Framework.Telemetry.Tests +namespace Microsoft.Build.Engine.UnitTests.Telemetry { public class ActivityExtensionsTests { @@ -17,7 +16,7 @@ public class ActivityExtensionsTests public void WithTag_ShouldSetUnhashedValue() { var activity = new Activity("TestActivity"); - activity.Start(); + activity.Start(); var telemetryItem = new TelemetryItem( Name: "TestItem", @@ -120,7 +119,7 @@ public void WithStartTime_NullDateTime_ShouldNotSetStartTime() } /// - /// A simple mock for testing IActivityTelemetryDataHolder. + /// A simple mock for testing IActivityTelemetryDataHolder. /// Returns two items: one hashed, one not hashed. /// internal sealed class MockTelemetryDataHolder : IActivityTelemetryDataHolder diff --git a/src/Build.UnitTests/BackEnd/OpenTelemetryManager_Tests.cs b/src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs similarity index 63% rename from src/Build.UnitTests/BackEnd/OpenTelemetryManager_Tests.cs rename to src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs index a2ec5161797..b10cf9465d4 100644 --- a/src/Build.UnitTests/BackEnd/OpenTelemetryManager_Tests.cs +++ b/src/Build.UnitTests/Telemetry/OpenTelemetryManager_Tests.cs @@ -2,18 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; -using Xunit; -using Shouldly; -using Xunit.Abstractions; -using Microsoft.Build.UnitTests.Shared; +using Microsoft.Build.Execution; +using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.UnitTests; +using Shouldly; +using Xunit; -namespace Microsoft.Build.Framework.Telemetry.Tests +namespace Microsoft.Build.Engine.UnitTests.Telemetry { - /// - /// Ensures tests run serially so environment variables and the singleton do not interfere with parallel test runs. - /// + // Putting the tests to a collection ensures tests run serially by default, that's needed to isolate the manager singleton state and env vars in some telemetry tests. [Collection("OpenTelemetryManagerTests")] public class OpenTelemetryManagerTests : IDisposable { @@ -23,46 +23,13 @@ public class OpenTelemetryManagerTests : IDisposable private const string TelemetrySampleRateOverrideEnvVarName = "MSBUILD_TELEMETRY_SAMPLE_RATE"; private const string VS1714TelemetryOptInEnvVarName = "MSBUILD_TELEMETRY_OPTIN"; - private string? preTestFxOptout; - private string? preTestDotnetOptout; - private string? preTestSampleRate; - private string? preTestVS1714TelemetryOptIn; - public OpenTelemetryManagerTests() { - // control environment state before each test - SaveEnvVars(); ResetManagerState(); - ResetEnvVars(); - } - - private void SaveEnvVars() - { - preTestFxOptout = Environment.GetEnvironmentVariable(TelemetryFxOptoutEnvVarName); - preTestDotnetOptout = Environment.GetEnvironmentVariable(DotnetOptOut); - preTestSampleRate = Environment.GetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName); - preTestVS1714TelemetryOptIn = Environment.GetEnvironmentVariable(VS1714TelemetryOptInEnvVarName); - } - - private void RestoreEnvVars() - { - Environment.SetEnvironmentVariable(TelemetryFxOptoutEnvVarName, preTestFxOptout); - Environment.SetEnvironmentVariable(DotnetOptOut, preTestDotnetOptout); - Environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, preTestSampleRate); - Environment.SetEnvironmentVariable(VS1714TelemetryOptInEnvVarName, preTestVS1714TelemetryOptIn); - } - - private void ResetEnvVars() - { - Environment.SetEnvironmentVariable(DotnetOptOut, null); - Environment.SetEnvironmentVariable(TelemetryFxOptoutEnvVarName, null); - Environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, null); - Environment.SetEnvironmentVariable(VS1714TelemetryOptInEnvVarName, null); } public void Dispose() { - RestoreEnvVars(); } [Theory] @@ -73,7 +40,8 @@ public void Dispose() public void Initialize_ShouldSetStateToOptOut_WhenOptOutEnvVarIsTrue(string optoutVar, string value) { // Arrange - Environment.SetEnvironmentVariable(optoutVar, value); + using TestEnvironment environment = TestEnvironment.Create(); + environment.SetEnvironmentVariable(optoutVar, value); // Act OpenTelemetryManager.Instance.Initialize(isStandalone: false); @@ -82,11 +50,13 @@ public void Initialize_ShouldSetStateToOptOut_WhenOptOutEnvVarIsTrue(string opto OpenTelemetryManager.Instance.IsActive().ShouldBeFalse(); } -#if NET +#if NETCOREAPP [Fact] public void Initialize_ShouldSetStateToUnsampled_WhenNoOverrideOnNetCore() { - Environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, null); + using TestEnvironment environment = TestEnvironment.Create(); + environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, null); + environment.SetEnvironmentVariable(DotnetOptOut, null); OpenTelemetryManager.Instance.Initialize(isStandalone: false); @@ -101,8 +71,10 @@ public void Initialize_ShouldSetStateToUnsampled_WhenNoOverrideOnNetCore() public void Initialize_ShouldSetSampleRateOverride_AndCreateActivitySource_WhenRandomBelowOverride(bool standalone) { // Arrange - Environment.SetEnvironmentVariable(VS1714TelemetryOptInEnvVarName, "1"); - Environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, "1.0"); + using TestEnvironment environment = TestEnvironment.Create(); + environment.SetEnvironmentVariable(VS1714TelemetryOptInEnvVarName, "1"); + environment.SetEnvironmentVariable(TelemetrySampleRateOverrideEnvVarName, "1.0"); + environment.SetEnvironmentVariable(DotnetOptOut, null); // Act OpenTelemetryManager.Instance.Initialize(isStandalone: standalone); @@ -115,11 +87,12 @@ public void Initialize_ShouldSetSampleRateOverride_AndCreateActivitySource_WhenR [Fact] public void Initialize_ShouldNoOp_WhenCalledMultipleTimes() { - Environment.SetEnvironmentVariable(DotnetOptOut, "true"); + using TestEnvironment environment = TestEnvironment.Create(); + environment.SetEnvironmentVariable(DotnetOptOut, "true"); OpenTelemetryManager.Instance.Initialize(isStandalone: true); var state1 = OpenTelemetryManager.Instance.IsActive(); - Environment.SetEnvironmentVariable(DotnetOptOut, null); + environment.SetEnvironmentVariable(DotnetOptOut, null); OpenTelemetryManager.Instance.Initialize(isStandalone: true); var state2 = OpenTelemetryManager.Instance.IsActive(); @@ -128,6 +101,23 @@ public void Initialize_ShouldNoOp_WhenCalledMultipleTimes() state2.ShouldBe(false); } + [Fact] + public void TelemetryLoadFailureIsLoggedOnce() + { + OpenTelemetryManager.Instance.LoadFailureExceptionMessage = new System.IO.FileNotFoundException().ToString(); + using BuildManager bm = new BuildManager(); + var deferredMessages = new List(); + bm.BeginBuild(new BuildParameters(), deferredMessages); + deferredMessages.ShouldContain(x => x.Text.Contains("FileNotFound")); + bm.EndBuild(); + bm.BeginBuild(new BuildParameters()); + bm.EndBuild(); + + // should not add message twice + int count = deferredMessages.Count(x => x.Text.Contains("FileNotFound")); + count.ShouldBe(1); + } + /* Helper methods */ /// diff --git a/src/Build.UnitTests/TelemetryTests.cs b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs similarity index 55% rename from src/Build.UnitTests/TelemetryTests.cs rename to src/Build.UnitTests/Telemetry/Telemetry_Tests.cs index d04353d7321..6939eda86e5 100644 --- a/src/Build.UnitTests/TelemetryTests.cs +++ b/src/Build.UnitTests/Telemetry/Telemetry_Tests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; using System.Text.Json; using Microsoft.Build.Execution; @@ -16,11 +17,12 @@ namespace Microsoft.Build.Engine.UnitTests { - public class TelemetryTests + [Collection("OpenTelemetryManagerTests")] + public class Telemetry_Tests { private readonly ITestOutputHelper _output; - public TelemetryTests(ITestOutputHelper output) + public Telemetry_Tests(ITestOutputHelper output) { _output = output; } @@ -82,7 +84,7 @@ public void WorkerNodeTelemetryCollection_BasicTarget() ((int)workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].ExecutionsCount).ShouldBe(1); workerNodeTelemetryData.TasksExecutionData[(TaskOrTargetTelemetryKey)"Microsoft.Build.Tasks.CreateItem"].CumulativeExecutionTime.ShouldBeGreaterThan(TimeSpan.Zero); - workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsCustom && !k.IsFromNugetCache); + workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsCustom && !k.IsNuget); workerNodeTelemetryData.TasksExecutionData.Values .Count(v => v.CumulativeExecutionTime > TimeSpan.Zero || v.ExecutionsCount > 0).ShouldBe(2); } @@ -106,7 +108,7 @@ public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks() - + - + @@ -127,12 +129,12 @@ public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks() - + - + @@ -166,32 +168,151 @@ public void WorkerNodeTelemetryCollection_CustomTargetsAndTasks() workerNodeTelemetryData.TasksExecutionData.Values .Count(v => v.CumulativeExecutionTime > TimeSpan.Zero || v.ExecutionsCount > 0).ShouldBe(3); - workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsFromNugetCache); + workerNodeTelemetryData.TasksExecutionData.Keys.ShouldAllBe(k => !k.IsNuget); } +#if NET + // test in .net core with opentelemetry opted in to avoid sending it but enable listening to it [Fact] - public void Foo() + public void NodeTelemetryE2E() { - WorkerNodeTelemetryData wd = new WorkerNodeTelemetryData( - new Dictionary() - { - { - new TaskOrTargetTelemetryKey("TaskA", false, true), - new TaskExecutionStats(TimeSpan.FromSeconds(2.1554548), 5, 545) - }, - { - new TaskOrTargetTelemetryKey("TaskA", true, false), - new TaskExecutionStats(TimeSpan.FromSeconds(254548), 6, 54545451) - }, - }, - new Dictionary() - { - { new TaskOrTargetTelemetryKey("TargetA", false, true, false), false }, - { new TaskOrTargetTelemetryKey("TargetA", true, true, false), false }, - { new TaskOrTargetTelemetryKey("TargetB", false, false, true), false } - }); - - var holder = TelemetryDataUtils.AsActivityDataHolder(wd, true, true); + using TestEnvironment env = TestEnvironment.Create(); + env.SetEnvironmentVariable("MSBUILD_TELEMETRY_OPTIN", "1"); + env.SetEnvironmentVariable("MSBUILD_TELEMETRY_SAMPLE_RATE", "1.0"); + env.SetEnvironmentVariable("MSBUILD_TELEMETRY_OPTOUT", null); + env.SetEnvironmentVariable("DOTNET_CLI_TELEMETRY_OPTOUT", null); + + // Reset the OpenTelemetryManager state to ensure clean test + var instance = OpenTelemetryManager.Instance; + typeof(OpenTelemetryManager) + .GetField("_telemetryState", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(instance, OpenTelemetryManager.TelemetryState.Uninitialized); + + typeof(OpenTelemetryManager) + .GetProperty("DefaultActivitySource", + System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance) + ?.SetValue(instance, null); + + // track activities through an ActivityListener + var capturedActivities = new List(); + using var listener = new ActivityListener + { + ShouldListenTo = source => source.Name.StartsWith(TelemetryConstants.DefaultActivitySourceNamespace), + Sample = (ref ActivityCreationOptions _) => ActivitySamplingResult.AllData, + ActivityStarted = capturedActivities.Add, + ActivityStopped = _ => { } + }; + ActivitySource.AddActivityListener(listener); + + var testProject = @" + + + + + + + + + + + + "; + + using var testEnv = TestEnvironment.Create(_output); + var projectFile = testEnv.CreateFile("test.proj", testProject).Path; + + // Set up loggers + var projectFinishedLogger = new ProjectFinishedCapturingLogger(); + var buildParameters = new BuildParameters + { + Loggers = new ILogger[] { projectFinishedLogger }, + IsTelemetryEnabled = true + }; + + // Act + using (var buildManager = new BuildManager()) + { + // Phase 1: Begin Build - This initializes telemetry infrastructure + buildManager.BeginBuild(buildParameters); + + // Phase 2: Execute build requests + var buildRequestData1 = new BuildRequestData( + projectFile, + new Dictionary(), + null, + new[] { "Build" }, + null); + + buildManager.BuildRequest(buildRequestData1); + + var buildRequestData2 = new BuildRequestData( + projectFile, + new Dictionary(), + null, + new[] { "Clean" }, + null); + + buildManager.BuildRequest(buildRequestData2); + + // Phase 3: End Build - This puts telemetry to an system.diagnostics activity + buildManager.EndBuild(); + + // Verify build activity were captured by the listener and contain task and target info + capturedActivities.ShouldNotBeEmpty(); + var activity = capturedActivities.FindLast(a => a.DisplayName == "VS/MSBuild/Build").ShouldNotBeNull(); + var tags = activity.Tags.ToDictionary(t => t.Key, t => t.Value); + tags.ShouldNotBeNull(); + + tags.ShouldContainKey("VS.MSBuild.BuildTarget"); + tags["VS.MSBuild.BuildTarget"].ShouldNotBeNullOrEmpty(); + + // Verify task data + tags.ShouldContainKey("VS.MSBuild.Tasks"); + var tasksJson = tags["VS.MSBuild.Tasks"]; + tasksJson.ShouldNotBeNullOrEmpty(); + tasksJson.ShouldContain("Microsoft.Build.Tasks.Message"); + tasksJson.ShouldContain("Microsoft.Build.Tasks.CreateItem"); + + // Parse tasks data for detailed assertions + var tasksData = JsonSerializer.Deserialize(tasksJson); + + // Verify Message task execution metrics - updated for object structure + tasksData.TryGetProperty("Microsoft.Build.Tasks.Message", out var messageTask).ShouldBe(true); + messageTask.GetProperty("ExecutionsCount").GetInt32().ShouldBe(3); + messageTask.GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0); + messageTask.GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThan(0); + messageTask.GetProperty(nameof(TaskOrTargetTelemetryKey.IsCustom)).GetBoolean().ShouldBe(false); + messageTask.GetProperty(nameof(TaskOrTargetTelemetryKey.IsCustom)).GetBoolean().ShouldBe(false); + + // Verify CreateItem task execution metrics - updated for object structure + tasksData.TryGetProperty("Microsoft.Build.Tasks.CreateItem", out var createItemTask).ShouldBe(true); + createItemTask.GetProperty("ExecutionsCount").GetInt32().ShouldBe(1); + createItemTask.GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0); + createItemTask.GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThan(0); + + // Verify Targets summary information + tags.ShouldContainKey("VS.MSBuild.TargetsSummary"); + var targetsSummaryJson = tags["VS.MSBuild.TargetsSummary"]; + targetsSummaryJson.ShouldNotBeNullOrEmpty(); + var targetsSummary = JsonSerializer.Deserialize(targetsSummaryJson); + + // Verify loaded and executed targets counts - match structure in TargetsSummaryConverter.Write + targetsSummary.GetProperty("Loaded").GetProperty("Total").GetInt32().ShouldBe(2); + targetsSummary.GetProperty("Executed").GetProperty("Total").GetInt32().ShouldBe(2); + + // Verify Tasks summary information + tags.ShouldContainKey("VS.MSBuild.TasksSummary"); + var tasksSummaryJson = tags["VS.MSBuild.TasksSummary"]; + tasksSummaryJson.ShouldNotBeNullOrEmpty(); + var tasksSummary = JsonSerializer.Deserialize(tasksSummaryJson); + + // Verify task execution summary metrics based on TasksSummaryConverter.Write structure + tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("ExecutionsCount").GetInt32().ShouldBe(4); + tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("TotalMilliseconds").GetDouble().ShouldBeGreaterThan(0); + tasksSummary.GetProperty("Microsoft").GetProperty("Total").GetProperty("TotalMemoryBytes").GetInt64().ShouldBeGreaterThan(0); + } } + +#endif } } diff --git a/src/Build.UnitTests/TerminalLogger_Tests.cs b/src/Build.UnitTests/TerminalLogger_Tests.cs index 2c04241a604..f63f35e386f 100644 --- a/src/Build.UnitTests/TerminalLogger_Tests.cs +++ b/src/Build.UnitTests/TerminalLogger_Tests.cs @@ -6,13 +6,9 @@ using System.Collections.Generic; using System.Globalization; using System.IO; -using System.Text; -using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.CommandLine.UnitTests; -using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Logging; using Microsoft.Build.UnitTests.Shared; diff --git a/src/Build.UnitTests/TestLoggingContext.cs b/src/Build.UnitTests/TestLoggingContext.cs index 756f61b8284..cc532c44d08 100644 --- a/src/Build.UnitTests/TestLoggingContext.cs +++ b/src/Build.UnitTests/TestLoggingContext.cs @@ -1,11 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; diff --git a/src/Build/BackEnd/BuildManager/BuildManager.cs b/src/Build/BackEnd/BuildManager/BuildManager.cs index 4dbf79918ac..fa6ae4bbbfd 100644 --- a/src/Build/BackEnd/BuildManager/BuildManager.cs +++ b/src/Build/BackEnd/BuildManager/BuildManager.cs @@ -459,7 +459,8 @@ private void UpdatePriority(Process p, ProcessPriorityClass priority) /// Thrown if a build is already in progress. public void BeginBuild(BuildParameters parameters) { - OpenTelemetryManager.Instance.Initialize(isStandalone: false); + InitializeTelemetry(); + if (_previousLowPriority != null) { if (parameters.LowPriority != _previousLowPriority) @@ -677,7 +678,7 @@ IEnumerable AppendDebuggingLoggers(IEnumerable loggers) var logger = new BinaryLogger { Parameters = binlogPath }; - return (loggers ?? [logger]); + return (loggers ?? []).Concat([logger]); } void InitializeCaches() @@ -723,6 +724,26 @@ void InitializeCaches() } } + private void InitializeTelemetry() + { + OpenTelemetryManager.Instance.Initialize(isStandalone: false); + string? failureMessage = OpenTelemetryManager.Instance.LoadFailureExceptionMessage; + if (_deferredBuildMessages != null && + failureMessage != null && + _deferredBuildMessages is ICollection deferredBuildMessagesCollection) + { + deferredBuildMessagesCollection.Add( + new DeferredBuildMessage( + ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( + "OpenTelemetryLoadFailed", + failureMessage), + MessageImportance.Low)); + + // clean up the message from OpenTelemetryManager to avoid double logging it + OpenTelemetryManager.Instance.LoadFailureExceptionMessage = null; + } + } + #if FEATURE_REPORTFILEACCESSES /// /// Configure the build to use I/O tracking for nodes. @@ -779,6 +800,7 @@ private static void AttachDebugger() /// public void CancelAllSubmissions() { + MSBuildEventSource.Log.CancelSubmissionsStart(); CancelAllSubmissions(true); } @@ -2405,7 +2427,7 @@ private void HandleResourceRequest(int node, ResourceRequest request) { // Resource request requires a response and may be blocking. Our continuation is effectively a callback // to be called once at least one core becomes available. - _scheduler!.RequestCores(request.GlobalRequestId, request.NumCores, request.IsBlocking).ContinueWith((Task task) => + _scheduler!.RequestCores(request.GlobalRequestId, request.NumCores, request.IsBlocking).ContinueWith((task) => { var response = new ResourceResponse(request.GlobalRequestId, task.Result); _nodeManager!.SendData(node, response); @@ -2801,12 +2823,11 @@ private NodeConfiguration GetNodeConfiguration() { // Get the remote loggers ILoggingService loggingService = ((IBuildComponentHost)this).GetComponent(BuildComponentType.LoggingService); - var remoteLoggers = new List(loggingService.LoggerDescriptions); _nodeConfiguration = new NodeConfiguration( -1, /* must be assigned by the NodeManager */ _buildParameters, - remoteLoggers.ToArray() + loggingService.LoggerDescriptions.ToArray() #if FEATURE_APPDOMAIN , AppDomain.CurrentDomain.SetupInformation #endif @@ -2990,8 +3011,8 @@ private ILoggingService CreateLoggingService( // We do want to dictate our own forwarding logger (otherwise CentralForwardingLogger with minimum transferred importance MessageImportance.Low is used) // In the future we might optimize for single, in-node build scenario - where forwarding logger is not needed (but it's just quick pass-through) LoggerDescription forwardingLoggerDescription = new LoggerDescription( - loggerClassName: typeof(InternalTelemeteryForwardingLogger).FullName, - loggerAssemblyName: typeof(InternalTelemeteryForwardingLogger).GetTypeInfo().Assembly.GetName().FullName, + loggerClassName: typeof(InternalTelemetryForwardingLogger).FullName, + loggerAssemblyName: typeof(InternalTelemetryForwardingLogger).GetTypeInfo().Assembly.GetName().FullName, loggerAssemblyFile: null, loggerSwitchParameters: null, verbosity: LoggerVerbosity.Quiet); diff --git a/src/Build/BackEnd/BuildManager/BuildParameters.cs b/src/Build/BackEnd/BuildManager/BuildParameters.cs index 38eca66834b..1bfab1b0e52 100644 --- a/src/Build/BackEnd/BuildManager/BuildParameters.cs +++ b/src/Build/BackEnd/BuildManager/BuildParameters.cs @@ -10,8 +10,6 @@ using Microsoft.Build.BackEnd; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; -using Microsoft.Build.Experimental; -using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Experimental.ProjectCache; using Microsoft.Build.Framework; using Microsoft.Build.Graph; diff --git a/src/Build/BackEnd/BuildManager/BuildRequestData.cs b/src/Build/BackEnd/BuildManager/BuildRequestData.cs index 11f54ffbade..ab64b422770 100644 --- a/src/Build/BackEnd/BuildManager/BuildRequestData.cs +++ b/src/Build/BackEnd/BuildManager/BuildRequestData.cs @@ -1,16 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Linq; -using System.Runtime.CompilerServices; using Microsoft.Build.Collections; -using Microsoft.Build.Evaluation; using Microsoft.Build.Experimental.BuildCheck; -using Microsoft.Build.Framework; using Microsoft.Build.Shared; namespace Microsoft.Build.Execution diff --git a/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs b/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs index 719625f43d9..8195c79aa0e 100644 --- a/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs +++ b/src/Build/BackEnd/BuildManager/BuildSubmissionBase.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Threading; -using System.Threading.Tasks; using Microsoft.Build.Shared; namespace Microsoft.Build.Execution diff --git a/src/Build/BackEnd/BuildManager/CacheAggregator.cs b/src/Build/BackEnd/BuildManager/CacheAggregator.cs index aca3b8ef9c7..694e9ef4af1 100644 --- a/src/Build/BackEnd/BuildManager/CacheAggregator.cs +++ b/src/Build/BackEnd/BuildManager/CacheAggregator.cs @@ -3,9 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; -using Microsoft.Build.Internal; using Microsoft.Build.Shared; #nullable disable @@ -55,8 +55,8 @@ public CacheAggregation Aggregate() private void InsertCaches(IConfigCache configCache, IResultsCache resultsCache) { - var configs = configCache.GetEnumerator().ToArray(); - var results = resultsCache.GetEnumerator().ToArray(); + var configs = configCache.ToArray(); + var results = resultsCache.ToArray(); ErrorUtilities.VerifyThrow(configs.Length == results.Length, "Assuming 1-to-1 mapping between configs and results. Otherwise it means the caches are either not minimal or incomplete"); diff --git a/src/Build/BackEnd/BuildManager/GlobalPropertiesLookup.cs b/src/Build/BackEnd/BuildManager/GlobalPropertiesLookup.cs index 1cf11aa2068..a440de531f9 100644 --- a/src/Build/BackEnd/BuildManager/GlobalPropertiesLookup.cs +++ b/src/Build/BackEnd/BuildManager/GlobalPropertiesLookup.cs @@ -1,13 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Collections; namespace Microsoft.Build.Execution diff --git a/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs b/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs index 65a7b72a4dd..b78001c5f3d 100644 --- a/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs +++ b/src/Build/BackEnd/Client/MSBuildClientPacketPump.cs @@ -6,12 +6,14 @@ using System.Collections.Concurrent; using System.IO; using System.Threading; -using Microsoft.Build.Internal; -using Microsoft.Build.Shared; -#if !FEATURE_APM + +#if NET using System.Threading.Tasks; #endif +using Microsoft.Build.Internal; +using Microsoft.Build.Shared; + namespace Microsoft.Build.BackEnd.Client { internal sealed class MSBuildClientPacketPump : INodePacketHandler, INodePacketFactory, IDisposable @@ -123,6 +125,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Deserializes a packet. + /// + /// The packet type. + /// The translator to use as a source for packet data. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes a packet to the appropriate handler. /// @@ -195,7 +207,7 @@ private void RunReadLoop(Stream localStream, ManualResetEvent localPacketPumpShu #if FEATURE_APM IAsyncResult result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null); #else - Task readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length); + Task readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask(); #endif bool continueReading = true; @@ -294,7 +306,7 @@ private void RunReadLoop(Stream localStream, ManualResetEvent localPacketPumpShu #if FEATURE_APM result = localStream.BeginRead(headerByte, 0, headerByte.Length, null, null); #else - readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length); + readTask = CommunicationsUtilities.ReadAsync(localStream, headerByte, headerByte.Length).AsTask(); #endif } } diff --git a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs index c48b89ef797..5e255466d52 100644 --- a/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs +++ b/src/Build/BackEnd/Components/BuildComponentFactoryCollection.cs @@ -5,7 +5,9 @@ using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Experimental.BuildCheck.Infrastructure; +#if FEATURE_REPORTFILEACCESSES using Microsoft.Build.FileAccesses; +#endif using Microsoft.Build.Shared; using Microsoft.Build.TelemetryInfra; diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs index e3b0a6d069e..d892518e9ea 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEngine.cs @@ -3,15 +3,14 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; using System.Threading; using System.Threading.Tasks.Dataflow; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Execution; +using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.Debugging; @@ -1433,7 +1432,7 @@ private void TraceEngine(string format, params object[] stuff) using (StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, @"EngineTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true)) { string message = String.Format(CultureInfo.CurrentCulture, format, stuff); - file.WriteLine("{0}({1})-{2}: {3}", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, DateTime.UtcNow.Ticks, message); + file.WriteLine("{0}({1})-{2}: {3}", Thread.CurrentThread.Name, Environment.CurrentManagedThreadId, DateTime.UtcNow.Ticks, message); file.Flush(); } } diff --git a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs index 28466f039ca..f3e860cb6c7 100644 --- a/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs +++ b/src/Build/BackEnd/Components/BuildRequestEngine/BuildRequestEntry.cs @@ -6,8 +6,8 @@ using System.Diagnostics; using System.IO; using System.Linq; -using Microsoft.Build.Shared; using Microsoft.Build.Execution; +using Microsoft.Build.Shared; using BuildAbortedException = Microsoft.Build.Exceptions.BuildAbortedException; #nullable disable @@ -511,12 +511,13 @@ private void WaitForResult(BuildRequest newRequest, bool addToIssueList) ErrorUtilities.VerifyThrow(addToIssueList, "Requests with unresolved configurations should always be added to the issue list."); _unresolvedConfigurations ??= new Dictionary>(); - if (!_unresolvedConfigurations.ContainsKey(newRequest.ConfigurationId)) + if (!_unresolvedConfigurations.TryGetValue(newRequest.ConfigurationId, out List value)) { - _unresolvedConfigurations.Add(newRequest.ConfigurationId, new List()); + value = new List(); + _unresolvedConfigurations.Add(newRequest.ConfigurationId, value); } - _unresolvedConfigurations[newRequest.ConfigurationId].Add(newRequest); + value.Add(newRequest); } if (addToIssueList) diff --git a/src/Build/BackEnd/Components/Caching/ResultsCacheResponse.cs b/src/Build/BackEnd/Components/Caching/ResultsCacheResponse.cs index 03e123f9141..012ae7af269 100644 --- a/src/Build/BackEnd/Components/Caching/ResultsCacheResponse.cs +++ b/src/Build/BackEnd/Components/Caching/ResultsCacheResponse.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; - using BuildResult = Microsoft.Build.Execution.BuildResult; #nullable disable diff --git a/src/Build/BackEnd/Components/Communications/CurrentHost.cs b/src/Build/BackEnd/Components/Communications/CurrentHost.cs index a9c8336b837..6e08f93e955 100644 --- a/src/Build/BackEnd/Components/Communications/CurrentHost.cs +++ b/src/Build/BackEnd/Components/Communications/CurrentHost.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. #if RUNTIME_TYPE_NETCORE -using System.Diagnostics; using System.IO; using Microsoft.Build.Shared; #endif diff --git a/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs b/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs index 3defd87986c..7df245c49a5 100644 --- a/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs +++ b/src/Build/BackEnd/Components/Communications/DetouredNodeLauncher.cs @@ -6,11 +6,9 @@ using System.Collections; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; using BuildXL.Processes; using BuildXL.Utilities.Core; using Microsoft.Build.Exceptions; -using Microsoft.Build.Experimental.FileAccess; using Microsoft.Build.FileAccesses; using Microsoft.Build.Internal; using Microsoft.Build.Shared; diff --git a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs index 6fdad8b8128..2c90dda389c 100644 --- a/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeEndpointInProc.cs @@ -29,7 +29,7 @@ internal class NodeEndpointInProc : INodeEndpoint /// /// An object for the two inproc endpoints to synchronize on. /// - private static Object s_locker = new Object(); + private static readonly Object s_locker = new Object(); /// /// The current communication status of the node. diff --git a/src/Build/BackEnd/Components/Communications/NodeManager.cs b/src/Build/BackEnd/Components/Communications/NodeManager.cs index b0031746031..31ebde5a1d6 100644 --- a/src/Build/BackEnd/Components/Communications/NodeManager.cs +++ b/src/Build/BackEnd/Components/Communications/NodeManager.cs @@ -255,6 +255,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes the specified packet. This is called by the Inproc node directly since it does not have to do any deserialization /// diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs b/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs index 15c815fb9cf..54f5205c3f3 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderInProc.cs @@ -4,13 +4,14 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.Threading; using Microsoft.Build.Internal; using Microsoft.Build.Shared; #if FEATURE_THREAD_CULTURE using BuildParameters = Microsoft.Build.Execution.BuildParameters; +#else +using System.Globalization; #endif using NodeEngineShutdownReason = Microsoft.Build.Execution.NodeEngineShutdownReason; @@ -291,6 +292,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr ErrorUtilities.ThrowInternalErrorUnreachable(); } + /// + /// Deserializes and routes a packet. Not used in the in-proc node. + /// + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + // Not used + ErrorUtilities.ThrowInternalErrorUnreachable(); + return null; + } + /// /// Routes a packet. /// @@ -382,7 +393,7 @@ private bool InstantiateNode(INodePacketFactory factory) InProcNodeThreadProc(); }); #endif - _inProcNodeThread.Name = String.Format(CultureInfo.CurrentCulture, "In-proc Node ({0})", _componentHost.Name); + _inProcNodeThread.Name = $"In-proc Node ({_componentHost.Name})"; _inProcNodeThread.IsBackground = true; #if FEATURE_THREAD_CULTURE _inProcNodeThread.CurrentCulture = _componentHost.BuildParameters.Culture; diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs index c506179bcd6..1064b2c709a 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcBase.cs @@ -3,27 +3,25 @@ using System; using System.Buffers.Binary; -using System.Collections.Generic; using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; using System.Globalization; using System.IO; using System.IO.Pipes; -using System.Diagnostics; using System.Linq; using System.Threading; using System.Threading.Tasks; -#if FEATURE_PIPE_SECURITY -using System.Security.Principal; -#endif +using Microsoft.Build.BackEnd.Logging; -#if FEATURE_APM +#if NETFRAMEWORK using Microsoft.Build.Eventing; +using System.Security.Principal; #endif + +using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; -using Task = System.Threading.Tasks.Task; -using Microsoft.Build.Framework; -using Microsoft.Build.BackEnd.Logging; #nullable disable @@ -416,7 +414,11 @@ void CreateNodeContext(int nodeId, Process nodeToReuse, Stream nodeStream) /// private string GetProcessesToIgnoreKey(Handshake hostHandshake, int nodeProcessId) { - return hostHandshake.ToString() + "|" + nodeProcessId.ToString(CultureInfo.InvariantCulture); +#if NET + return string.Create(CultureInfo.InvariantCulture, $"{hostHandshake}|{nodeProcessId}"); +#else + return $"{hostHandshake}|{nodeProcessId.ToString(CultureInfo.InvariantCulture)}"; +#endif } #if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY @@ -833,8 +835,17 @@ public async Task WaitForExitAsync(ILoggingService loggingService) { // Wait up to 100ms until all remaining packets are sent. // We don't need to wait long, just long enough for the Task to start running on the ThreadPool. - await Task.WhenAny(_packetWriteDrainTask, Task.Delay(100)); +#if NET + await _packetWriteDrainTask.WaitAsync(TimeSpan.FromMilliseconds(100)).ConfigureAwait(ConfigureAwaitOptions.SuppressThrowing); +#else + using (var cts = new CancellationTokenSource(100)) + { + await Task.WhenAny(_packetWriteDrainTask, Task.Delay(100, cts.Token)); + cts.Cancel(); + } +#endif } + if (_exitPacketState == ExitPacketState.ExitPacketSent) { CommunicationsUtilities.Trace("Waiting for node with pid = {0} to exit", _process.Id); @@ -870,23 +881,6 @@ public async Task WaitForExitAsync(ILoggingService loggingService) _process.KillTree(timeoutMilliseconds: 5000); } -#if FEATURE_APM - /// - /// Completes the asynchronous packet write to the node. - /// - private void PacketWriteComplete(IAsyncResult result) - { - try - { - _serverToClientStream.EndWrite(result); - } - catch (IOException) - { - // Do nothing here because any exception will be caught by the async read handler - } - } -#endif - private bool ProcessHeaderBytesRead(int bytesRead) { if (bytesRead != _headerByte.Length) diff --git a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs index 1d0f0f525d3..95df655f7c9 100644 --- a/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs +++ b/src/Build/BackEnd/Components/Communications/NodeProviderOutOfProcTaskHost.cs @@ -287,6 +287,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr } } + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _localPacketFactory.DeserializePacket(packetType, translator); + } + /// /// Routes the specified packet /// diff --git a/src/Build/BackEnd/Components/Communications/TaskHostNodeManager.cs b/src/Build/BackEnd/Components/Communications/TaskHostNodeManager.cs index e7e66d6b886..66c881052b8 100644 --- a/src/Build/BackEnd/Components/Communications/TaskHostNodeManager.cs +++ b/src/Build/BackEnd/Components/Communications/TaskHostNodeManager.cs @@ -149,6 +149,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr throw new NotSupportedException("not used"); } + /// + /// Takes a serializer, deserializes the packet and routes it to the appropriate handler. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + throw new NotSupportedException("not used"); + } + /// /// Routes the specified packet. This is called by the Inproc node directly since it does not have to do any deserialization /// diff --git a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs index 912c37ee0ca..ab2b1bb2da0 100644 --- a/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs +++ b/src/Build/BackEnd/Components/Communications/TranslatorExtensions.cs @@ -20,7 +20,7 @@ namespace Microsoft.Build.BackEnd /// internal static class TranslatorExtensions { - private static Lazy> parameterlessConstructorCache = new Lazy>(() => new ConcurrentDictionary()); + private static readonly Lazy> parameterlessConstructorCache = new Lazy>(() => new ConcurrentDictionary()); /// /// Translates a PropertyDictionary of ProjectPropertyInstances. diff --git a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs index de28c100d1d..5936bf90ac5 100644 --- a/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs +++ b/src/Build/BackEnd/Components/FileAccesses/FileAccessManager.cs @@ -22,7 +22,7 @@ private record Handlers(Action FileAccessHander, A // is used to mark when the file accesses should be considered complete. Only after both this special file access is seen // and the build result is reported can plugins be notified about project completion. // NOTE! This is currently Windows-specific and will need to change once this feature is opened up to more scenarios. - private static readonly string FileAccessCompletionPrefix = BuildParameters.StartupDirectory[0] + @":\{MSBuildFileAccessCompletion}\"; + private static readonly string FileAccessCompletionPrefix = $@"{BuildParameters.StartupDirectory[0]}:\{{MSBuildFileAccessCompletion}}\"; private IScheduler? _scheduler; private IConfigCache? _configCache; diff --git a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs index e61db2b91b2..bda1135b030 100644 --- a/src/Build/BackEnd/Components/Logging/EventSourceSink.cs +++ b/src/Build/BackEnd/Components/Logging/EventSourceSink.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Experimental.BuildCheck; using Microsoft.Build.Framework; +using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared; using InternalLoggerException = Microsoft.Build.Exceptions.InternalLoggerException; diff --git a/src/Build/BackEnd/Components/Logging/LoggingService.cs b/src/Build/BackEnd/Components/Logging/LoggingService.cs index c08622d49eb..1e4121594e2 100644 --- a/src/Build/BackEnd/Components/Logging/LoggingService.cs +++ b/src/Build/BackEnd/Components/Logging/LoggingService.cs @@ -94,7 +94,7 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler /// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal, /// it is only discoverable with Reflection using the Public flag (go figure!) /// - private static Lazy s_projectStartedEventArgsGlobalProperties = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("GlobalProperties", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy s_projectStartedEventArgsGlobalProperties = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("GlobalProperties", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); /// /// A cached reflection accessor for an internal member. @@ -103,7 +103,7 @@ internal partial class LoggingService : ILoggingService, INodePacketHandler /// We use a BindingFlags.Public flag here because the getter is public, so although the setter is internal, /// it is only discoverable with Reflection using the Public flag (go figure!) /// - private static Lazy s_projectStartedEventArgsToolsVersion = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("ToolsVersion", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); + private static readonly Lazy s_projectStartedEventArgsToolsVersion = new Lazy(() => typeof(ProjectStartedEventArgs).GetProperty("ToolsVersion", BindingFlags.Public | BindingFlags.Instance), LazyThreadSafetyMode.PublicationOnly); #region Data diff --git a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs index 22687557135..2bda5a2e93f 100644 --- a/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/ProjectLoggingContext.cs @@ -3,7 +3,6 @@ using System.Collections; using System.Collections.Generic; -using System.Configuration; using System.Linq; using Microsoft.Build.Collections; using Microsoft.Build.Execution; diff --git a/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs b/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs index d68f04e2bfa..d79a052c5d9 100644 --- a/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs +++ b/src/Build/BackEnd/Components/Logging/TargetLoggingContext.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; #nullable disable diff --git a/src/Build/BackEnd/Components/ProjectCache/CacheContext.cs b/src/Build/BackEnd/Components/ProjectCache/CacheContext.cs index 5f3ff599c99..6a5eb2daaa9 100644 --- a/src/Build/BackEnd/Components/ProjectCache/CacheContext.cs +++ b/src/Build/BackEnd/Components/ProjectCache/CacheContext.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using Microsoft.Build.FileSystem; using Microsoft.Build.Graph; diff --git a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs index bd1cb0fd8d7..e89f7d9fed8 100644 --- a/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs +++ b/src/Build/BackEnd/Components/ProjectCache/ProjectCacheService.cs @@ -16,7 +16,9 @@ using Microsoft.Build.Construction; using Microsoft.Build.Eventing; using Microsoft.Build.Execution; +#if FEATURE_REPORTFILEACCESSES using Microsoft.Build.FileAccesses; +#endif using Microsoft.Build.FileSystem; using Microsoft.Build.Framework; using Microsoft.Build.Graph; @@ -32,7 +34,7 @@ internal sealed class ProjectCacheService : IAsyncDisposable { private static readonly ParallelOptions s_parallelOptions = new() { MaxDegreeOfParallelism = Environment.ProcessorCount }; - private static HashSet s_projectSpecificPropertyNames = new(StringComparer.OrdinalIgnoreCase) { "TargetFramework", "Configuration", "Platform", "TargetPlatform", "OutputType" }; + private static readonly HashSet s_projectSpecificPropertyNames = new(StringComparer.OrdinalIgnoreCase) { "TargetFramework", "Configuration", "Platform", "TargetPlatform", "OutputType" }; private readonly BuildManager _buildManager; private readonly IBuildComponentHost _componentHost; @@ -115,8 +117,7 @@ public void InitializePluginsForGraph( foreach (ProjectCacheDescriptor projectCacheDescriptor in GetProjectCacheDescriptors(node.ProjectInstance)) { // Intentionally fire-and-forget to asynchronously initialize the plugin. Any exceptions will bubble up later when querying. - _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph, buildRequestConfiguration: null, requestedTargets, cancellationToken) - .ContinueWith(t => { }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); + _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph, buildRequestConfiguration: null, requestedTargets, cancellationToken); } }); }, @@ -149,8 +150,7 @@ public void InitializePluginsForVsScenario( projectCacheDescriptor => { // Intentionally fire-and-forget to asynchronously initialize the plugin. Any exceptions will bubble up later when querying. - _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph: null, buildRequestConfiguration, requestedTargets, cancellationToken) - .ContinueWith(t => { }, TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted); + _ = GetProjectCachePluginAsync(projectCacheDescriptor, projectGraph: null, buildRequestConfiguration, requestedTargets, cancellationToken); }); }, cancellationToken); @@ -449,7 +449,7 @@ public void PostCacheRequest(CacheRequest cacheRequest, CancellationToken cancel }, cancellationToken); - async Task<(CacheResult Result, int ProjectContextId)> ProcessCacheRequestAsync() + async ValueTask<(CacheResult Result, int ProjectContextId)> ProcessCacheRequestAsync() { EvaluateProjectIfNecessary(cacheRequest.Submission, cacheRequest.Configuration); @@ -499,7 +499,7 @@ void EvaluateProjectIfNecessary(BuildSubmission submission, BuildRequestConfigur } } - private async Task GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) + private async ValueTask GetCacheResultAsync(BuildRequestData buildRequest, BuildRequestConfiguration buildRequestConfiguration, BuildEventContext buildEventContext, CancellationToken cancellationToken) { ErrorUtilities.VerifyThrowInternalNull(buildRequest.ProjectInstance, nameof(buildRequest.ProjectInstance)); diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs index 423679a1f6e..c33c0ab6563 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupIntrinsicTask.cs @@ -186,7 +186,7 @@ private void ExecuteAdd(ProjectItemGroupTaskItemInstance child, ItemBucket bucke if (// If multiple buckets were expanded - we do not want to repeat same error for same metadatum on a same line bucket.BucketSequenceNumber == 0 && // Referring to unqualified metadata of other item (transform) is fine. - child.Include.IndexOf("@(", StringComparison.Ordinal) == -1) + !child.Include.Contains("@(")) { expanderOptions |= ExpanderOptions.LogOnItemMetadataSelfReference; } diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs index deae62102f0..23f3d96c1f6 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/ItemGroupLoggingHelper.cs @@ -10,7 +10,6 @@ #endif using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; -using Microsoft.Build.Execution; using Microsoft.Build.Framework; using Microsoft.Build.Shared; diff --git a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs index 73f84dd94a0..ac3e5aab535 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/IntrinsicTasks/MSBuild.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.IO; using System.Threading.Tasks; using Microsoft.Build.Framework; @@ -259,7 +258,7 @@ public async Task ExecuteInternal() undefinePropertiesArray = RemoveProperties.Split(MSBuildConstants.SemicolonChar, StringSplitOptions.RemoveEmptyEntries); foreach (string property in undefinePropertiesArray) { - Log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); + Log.LogMessageFromText($" {property}", MessageImportance.Low); } } @@ -296,10 +295,7 @@ public async Task ExecuteInternal() if (BuildInParallel) { skipProjects = new bool[Projects.Length]; - for (int i = 0; i < skipProjects.Length; i++) - { - skipProjects[i] = true; - } + skipProjects.AsSpan().Fill(true); } else { @@ -594,7 +590,7 @@ internal static async Task ExecuteTargets( foreach (string property in propertiesToUndefine) { undefinePropertiesPerProject[i].Add(property); - log.LogMessageFromText(String.Format(CultureInfo.InvariantCulture, " {0}", property), MessageImportance.Low); + log.LogMessageFromText($" {property}", MessageImportance.Low); } } } diff --git a/src/Build/BackEnd/Components/RequestBuilder/ItemBucket.cs b/src/Build/BackEnd/Components/RequestBuilder/ItemBucket.cs index 950b2848a68..7f5ebcbf243 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/ItemBucket.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/ItemBucket.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Collections.ObjectModel; using System.Diagnostics; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; diff --git a/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs b/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs index 6a882eb87f0..596555819ca 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/Lookup.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Threading; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; @@ -1373,7 +1372,7 @@ internal Scope(Lookup lookup, string description, IItemDictionary + _pendingResourceRequests.Enqueue((response) => { responseObject = response; responseEvent.Set(); @@ -1032,22 +1031,9 @@ private BuildResult[] GetResultsForContinuation(FullyQualifiedBuildRequest[] req // The build results will have node request IDs in the same order as the requests were issued, // which is in the array order above. - List resultsList = new List(results.Values); - resultsList.Sort(delegate (BuildResult left, BuildResult right) - { - if (left.NodeRequestId < right.NodeRequestId) - { - return -1; - } - else if (left.NodeRequestId == right.NodeRequestId) - { - return 0; - } - - return 1; - }); - - return resultsList.ToArray(); + BuildResult[] resultsArray = results.Values.ToArray(); + Array.Sort(resultsArray, (left, right) => left.NodeRequestId.CompareTo(right.NodeRequestId)); + return resultsArray; } /// @@ -1120,8 +1106,6 @@ private async Task BuildProject() // logged with the node logging context _projectLoggingContext = null; - MSBuildEventSource.Log.BuildProjectStart(_requestEntry.RequestConfiguration.ProjectFullPath); - try { // Load the project @@ -1159,6 +1143,13 @@ private async Task BuildProject() try { + // Determine the set of targets we need to build + (string name, TargetBuiltReason reason)[] allTargets = _requestEntry.RequestConfiguration + .GetTargetsUsedToBuildRequest(_requestEntry.Request).ToArray(); + if (MSBuildEventSource.Log.IsEnabled()) + { + MSBuildEventSource.Log.BuildProjectStart(_requestEntry.RequestConfiguration.ProjectFullPath, string.Join(", ", allTargets)); + } HandleProjectStarted(buildCheckManager); // Make sure to extract known immutable folders from properties and register them for fast up-to-date check @@ -1176,9 +1167,6 @@ private async Task BuildProject() _requestEntry.Request.BuildEventContext = _projectLoggingContext.BuildEventContext; - // Determine the set of targets we need to build - (string name, TargetBuiltReason reason)[] allTargets = _requestEntry.RequestConfiguration - .GetTargetsUsedToBuildRequest(_requestEntry.Request).ToArray(); ProjectErrorUtilities.VerifyThrowInvalidProject(allTargets.Length > 0, _requestEntry.RequestConfiguration.Project.ProjectFileLocation, "NoTargetSpecified"); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs index aab4fb6d344..65b6903876a 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetBuilder.cs @@ -412,7 +412,7 @@ private async Task ProcessTargetStack(ITaskBuilder taskBuilder) ( !_cancellationToken.IsCancellationRequested && !stopProcessingStack && - _targetsToBuild.Any()) + !_targetsToBuild.IsEmpty) { TargetEntry currentTargetEntry = _targetsToBuild.Peek(); switch (currentTargetEntry.State) @@ -621,7 +621,7 @@ private void PopDependencyTargetsOnTargetFailure(TargetEntry topEntry, TargetRes // Pop down to our parent, since any other dependencies our parent had should no longer // execute. If we encounter an error target on the way down, also stop since the failure // of one error target in a set declared in OnError should not cause the others to stop running. - while ((_targetsToBuild.Any()) && (_targetsToBuild.Peek() != topEntry.ParentEntry) && !_targetsToBuild.Peek().ErrorTarget) + while ((!_targetsToBuild.IsEmpty) && (_targetsToBuild.Peek() != topEntry.ParentEntry) && !_targetsToBuild.Peek().ErrorTarget) { TargetEntry entry = _targetsToBuild.Pop(); entry.LeaveLegacyCallTargetScopes(); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs index c85b4f41f54..ee1dca8557e 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetEntry.cs @@ -148,11 +148,6 @@ internal class TargetEntry : IEquatable /// private bool _isExecuting; - /// - /// The current task builder. - /// - private ITaskBuilder _currentTaskBuilder; - /// /// The constructor. /// @@ -810,52 +805,42 @@ internal void LeaveLegacyCallTargetScopes() /// /// The result of the tasks, based on the last task which ran. /// - private async Task ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) + private async ValueTask ProcessBucket(ITaskBuilder taskBuilder, TargetLoggingContext targetLoggingContext, TaskExecutionMode mode, Lookup lookupForInference, Lookup lookupForExecution) { WorkUnitResultCode aggregatedTaskResult = WorkUnitResultCode.Success; WorkUnitActionCode finalActionCode = WorkUnitActionCode.Continue; WorkUnitResult lastResult = new WorkUnitResult(WorkUnitResultCode.Success, WorkUnitActionCode.Continue, null); - try + int currentTask = 0; + + // Walk through all of the tasks and execute them in order. + for (; (currentTask < _target.Children.Count) && !_cancellationToken.IsCancellationRequested; ++currentTask) { - // Grab the task builder so if cancel is called it will have something to operate on. - _currentTaskBuilder = taskBuilder; + ProjectTargetInstanceChild targetChildInstance = _target.Children[currentTask]; - int currentTask = 0; + // Execute the task. + lastResult = await taskBuilder.ExecuteTask(targetLoggingContext, _requestEntry, _targetBuilderCallback, targetChildInstance, mode, lookupForInference, lookupForExecution, _cancellationToken); - // Walk through all of the tasks and execute them in order. - for (; (currentTask < _target.Children.Count) && !_cancellationToken.IsCancellationRequested; ++currentTask) + if (lastResult.ResultCode == WorkUnitResultCode.Failed) { - ProjectTargetInstanceChild targetChildInstance = _target.Children[currentTask]; - - // Execute the task. - lastResult = await taskBuilder.ExecuteTask(targetLoggingContext, _requestEntry, _targetBuilderCallback, targetChildInstance, mode, lookupForInference, lookupForExecution, _cancellationToken); - - if (lastResult.ResultCode == WorkUnitResultCode.Failed) - { - aggregatedTaskResult = WorkUnitResultCode.Failed; - } - else if (lastResult.ResultCode == WorkUnitResultCode.Success && aggregatedTaskResult != WorkUnitResultCode.Failed) - { - aggregatedTaskResult = WorkUnitResultCode.Success; - } - - if (lastResult.ActionCode == WorkUnitActionCode.Stop) - { - finalActionCode = WorkUnitActionCode.Stop; - break; - } + aggregatedTaskResult = WorkUnitResultCode.Failed; + } + else if (lastResult.ResultCode == WorkUnitResultCode.Success && aggregatedTaskResult != WorkUnitResultCode.Failed) + { + aggregatedTaskResult = WorkUnitResultCode.Success; } - if (_cancellationToken.IsCancellationRequested) + if (lastResult.ActionCode == WorkUnitActionCode.Stop) { - aggregatedTaskResult = WorkUnitResultCode.Canceled; finalActionCode = WorkUnitActionCode.Stop; + break; } } - finally + + if (_cancellationToken.IsCancellationRequested) { - _currentTaskBuilder = null; + aggregatedTaskResult = WorkUnitResultCode.Canceled; + finalActionCode = WorkUnitActionCode.Stop; } return new WorkUnitResult(aggregatedTaskResult, finalActionCode, lastResult.Exception); diff --git a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs index a75c73a91ae..58e250f6c97 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TargetUpToDateChecker.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.IO; using System.Linq; @@ -838,15 +839,14 @@ private void SeparateItemVectorsFromDiscreteItems( } // Do we already have a partition for this? - if (!itemVectorCollection.ContainsKey(itemVectorType)) + if (!itemVectorCollection.TryGetValue(itemVectorType, out ItemVectorPartition itemVectorPartition)) { // Nope, create one. - itemVectorCollection[itemVectorType] = new ItemVectorPartition(MSBuildNameIgnoreCaseComparer.Default); + itemVectorPartition = new ItemVectorPartition(MSBuildNameIgnoreCaseComparer.Default); + itemVectorCollection[itemVectorType] = itemVectorPartition; } - ItemVectorPartition itemVectorPartition = itemVectorCollection[itemVectorType]; - - ErrorUtilities.VerifyThrow(!itemVectorCollection[itemVectorType].ContainsKey(item), "ItemVectorPartition already contains a vector for items with the expression '{0}'", item); + ErrorUtilities.VerifyThrow(!itemVectorPartition.ContainsKey(item), "ItemVectorPartition already contains a vector for items with the expression '{0}'", item); itemVectorPartition[item] = itemVectorContents; ErrorUtilities.VerifyThrow((itemVectorTransforms == null) || (itemVectorCollection.Equals(itemVectorTransforms)) || (itemVectorPartition.Count == 1), @@ -1129,8 +1129,8 @@ private bool IsOutOfDate(string input, string output, string inputItemName, stri { input = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(input)); output = EscapingUtilities.UnescapeAll(FileUtilities.FixFilePath(output)); - ProjectErrorUtilities.VerifyThrowInvalidProject(input.IndexOfAny(Path.GetInvalidPathChars()) == -1, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", input, inputItemName); - ProjectErrorUtilities.VerifyThrowInvalidProject(output.IndexOfAny(Path.GetInvalidPathChars()) == -1, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", output, outputItemName); + ProjectErrorUtilities.VerifyThrowInvalidProject(input.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", input, inputItemName); + ProjectErrorUtilities.VerifyThrowInvalidProject(output.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, _project.ProjectFileLocation, "IllegalCharactersInFileOrDirectory", output, outputItemName); bool outOfDate = (CompareLastWriteTimes(input, output, out bool inputDoesNotExist, out bool outputDoesNotExist) == 1) || inputDoesNotExist; // Only if we are not logging just critical events should we be gathering full details diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs index 3a406115fce..36b4af7301f 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskBuilder.cs @@ -291,7 +291,7 @@ private List CreateListOfParameterValues() /// Called to execute a task within a target. This method instantiates the task, sets its parameters, and executes it. /// /// true, if successful - private async Task ExecuteTask(TaskExecutionMode mode, Lookup lookup) + private async ValueTask ExecuteTask(TaskExecutionMode mode, Lookup lookup) { ErrorUtilities.VerifyThrowArgumentNull(lookup); @@ -366,7 +366,7 @@ private async Task ExecuteTask(TaskExecutionMode mode, Lookup lo /// Execute a single bucket /// /// true if execution succeeded - private async Task ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary lookupHash) + private async ValueTask ExecuteBucket(TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask, Dictionary lookupHash) { // On Intrinsic tasks, we do not allow batchable params, therefore metadata is excluded. ParserOptions parserOptions = (_taskNode == null) ? ParserOptions.AllowPropertiesAndItemLists : ParserOptions.AllowAll; @@ -738,7 +738,7 @@ private void UpdateContinueOnError(ItemBucket bucket, TaskHost taskHost) /// The batching bucket /// The task execution mode /// The result of running the task. - private async Task ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) + private async ValueTask ExecuteInstantiatedTask(TaskExecutionHost taskExecutionHost, TaskLoggingContext taskLoggingContext, TaskHost taskHost, ItemBucket bucket, TaskExecutionMode howToExecuteTask) { UpdateContinueOnError(bucket, taskHost); @@ -859,7 +859,7 @@ private async Task ExecuteInstantiatedTask(TaskExecutionHost tas } else if (type == typeof(ThreadAbortException)) { -#if !NET6_0_OR_GREATER && !NET6_0 // This is redundant but works around https://github.com/dotnet/sdk/issues/20700 +#if !NET Thread.ResetAbort(); #endif _continueOnError = ContinueOnError.ErrorAndStop; diff --git a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs index 45414d7cf5c..a262028f9f9 100644 --- a/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs +++ b/src/Build/BackEnd/Components/RequestBuilder/TaskHost.cs @@ -11,19 +11,20 @@ #endif using System.Diagnostics; using System.Reflection; -using System.Threading; using System.Threading.Tasks; using Microsoft.Build.BackEnd.Components.Caching; using Microsoft.Build.Collections; using Microsoft.Build.Eventing; using Microsoft.Build.Execution; -using Microsoft.Build.FileAccesses; using Microsoft.Build.Framework; -using Microsoft.Build.Experimental.FileAccess; using Microsoft.Build.Shared; using ElementLocation = Microsoft.Build.Construction.ElementLocation; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; using TaskLoggingContext = Microsoft.Build.BackEnd.Logging.TaskLoggingContext; +#if FEATURE_REPORTFILEACCESSES +using Microsoft.Build.Experimental.FileAccess; +using Microsoft.Build.FileAccesses; +#endif #nullable disable @@ -42,7 +43,7 @@ internal class TaskHost : /// /// Help diagnose tasks that log after they return. /// - private static bool s_breakOnLogAfterTaskReturns = Environment.GetEnvironmentVariable("MSBUILDBREAKONLOGAFTERTASKRETURNS") == "1"; + private static readonly bool s_breakOnLogAfterTaskReturns = Environment.GetEnvironmentVariable("MSBUILDBREAKONLOGAFTERTASKRETURNS") == "1"; /// /// The build component host @@ -357,7 +358,7 @@ public void Yield() { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId == -1, "Cannot call Yield() while yielding."); - _yieldThreadId = Thread.CurrentThread.ManagedThreadId; + _yieldThreadId = Environment.CurrentManagedThreadId; MSBuildEventSource.Log.ExecuteTaskYieldStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Yield(); } @@ -386,7 +387,7 @@ public void Reacquire() { IRequestBuilderCallback builderCallback = _requestEntry.Builder as IRequestBuilderCallback; ErrorUtilities.VerifyThrow(_yieldThreadId != -1, "Cannot call Reacquire() before Yield()."); - ErrorUtilities.VerifyThrow(_yieldThreadId == Thread.CurrentThread.ManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Thread.CurrentThread.ManagedThreadId, _yieldThreadId); + ErrorUtilities.VerifyThrow(_yieldThreadId == Environment.CurrentManagedThreadId, "Cannot call Reacquire() on thread {0} when Yield() was called on thread {1}", Environment.CurrentManagedThreadId, _yieldThreadId); MSBuildEventSource.Log.ExecuteTaskYieldStop(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); MSBuildEventSource.Log.ExecuteTaskReacquireStart(_taskLoggingContext.TaskName, _taskLoggingContext.BuildEventContext.TaskId); builderCallback.Reacquire(); diff --git a/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs b/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs index bc5e8f86256..a7b6ddd743e 100644 --- a/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs +++ b/src/Build/BackEnd/Components/Scheduler/ScheduleResponse.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Globalization; using Microsoft.Build.Execution; #nullable disable @@ -237,26 +235,26 @@ public override string ToString() { case ScheduleActionType.ReportResults: case ScheduleActionType.ResumeExecution: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2}", Action, NodeId, Unblocker.BlockedRequestId); + return $"Act: {Action} Node: {NodeId} Request: {Unblocker.BlockedRequestId}"; case ScheduleActionType.Schedule: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId}"; case ScheduleActionType.ScheduleWithConfiguration: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3} Configuration: {4}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId, BuildRequest.ConfigurationId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}"; case ScheduleActionType.CircularDependency: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Node: {1} Request: {2} Parent {3} Configuration: {4}", Action, NodeId, BuildRequest.GlobalRequestId, BuildRequest.ParentGlobalRequestId, BuildRequest.ConfigurationId); + return $"Act: {Action} Node: {NodeId} Request: {BuildRequest.GlobalRequestId} Parent {BuildRequest.ParentGlobalRequestId} Configuration: {BuildRequest.ConfigurationId}"; case ScheduleActionType.SubmissionComplete: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Submission: {1}", Action, BuildResult.SubmissionId); + return $"Act: {Action} Submission: {BuildResult.SubmissionId}"; case ScheduleActionType.CreateNode: - return String.Format(CultureInfo.CurrentCulture, "Act: {0} Count: {1}", Action, NumberOfNodesToCreate); + return $"Act: {Action} Count: {NumberOfNodesToCreate}"; case ScheduleActionType.NoAction: default: - return String.Format(CultureInfo.CurrentCulture, "Act: {0}", Action); + return $"Act: {Action}"; } } } diff --git a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs index 0e8b74863be..6bcb954c1f3 100644 --- a/src/Build/BackEnd/Components/Scheduler/Scheduler.cs +++ b/src/Build/BackEnd/Components/Scheduler/Scheduler.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; @@ -580,7 +579,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor return Task.FromResult(0); } - Func grantCores = (int availableCores) => + Func grantCores = (availableCores) => { int grantedCores = Math.Min(requestedCores, availableCores); if (grantedCores > 0) @@ -600,7 +599,7 @@ public Task RequestCores(int requestId, int requestedCores, bool waitForCor // We have no cores to grant at the moment, queue up the request. TaskCompletionSource completionSource = new TaskCompletionSource(); _pendingRequestCoresCallbacks.Enqueue(completionSource); - return completionSource.Task.ContinueWith((Task task) => grantCores(task.Result), TaskContinuationOptions.ExecuteSynchronously); + return completionSource.Task.ContinueWith((task) => grantCores(task.Result), TaskContinuationOptions.ExecuteSynchronously); } } @@ -2431,7 +2430,7 @@ private void WriteNodeUtilizationGraphLine(ILoggingService loggingService, Build bool haveNonIdleNode = false; StringBuilder stringBuilder = new StringBuilder(64); - stringBuilder.AppendFormat("{0}: ", previousEventTime.Ticks); + stringBuilder.Append(previousEventTime.Ticks).Append(": "); for (int i = 0; i < currentWork.Length; i++) { if (currentWork[i] == invalidWorkId) @@ -2567,7 +2566,7 @@ private void TraceScheduler(string format, params object[] stuff) FileUtilities.EnsureDirectoryExists(_debugDumpPath); using StreamWriter file = FileUtilities.OpenWrite(string.Format(CultureInfo.CurrentCulture, Path.Combine(_debugDumpPath, "SchedulerTrace_{0}.txt"), EnvironmentUtilities.CurrentProcessId), append: true); - file.Write("{0}({1})-{2}: ", Thread.CurrentThread.Name, Thread.CurrentThread.ManagedThreadId, _schedulingData.EventTime.Ticks); + file.Write("{0}({1})-{2}: ", Thread.CurrentThread.Name, Environment.CurrentManagedThreadId, _schedulingData.EventTime.Ticks); file.WriteLine(format, stuff); file.Flush(); } @@ -2814,7 +2813,7 @@ private void DumpRequestSpec(StreamWriter file, SchedulableRequest request, int request.State, buildRequest.ConfigurationId, _configCache[buildRequest.ConfigurationId].ProjectFullPath, - string.Join(", ", buildRequest.Targets.ToArray())); + string.Join(", ", buildRequest.Targets)); } /// diff --git a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs index c15b5a25063..c6a7fb224d1 100644 --- a/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs +++ b/src/Build/BackEnd/Components/Scheduler/SchedulingPlan.cs @@ -111,7 +111,7 @@ public void WritePlan(int submissionId, ILoggingService loggingService, BuildEve RecursiveAccumulateConfigurationTimes(rootRequest, accumulatedTimeByConfiguration); List> configurationsInOrder = new(accumulatedTimeByConfiguration); - configurationsInOrder.Sort((KeyValuePair l, KeyValuePair r) => Comparer.Default.Compare(l.Key, r.Key)); + configurationsInOrder.Sort((l, r) => Comparer.Default.Compare(l.Key, r.Key)); foreach (KeyValuePair configuration in configurationsInOrder) { file.WriteLine(String.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", configuration.Key, configuration.Value, _configCache[configuration.Key].ProjectFullPath)); @@ -363,9 +363,8 @@ private void DetermineExpensiveConfigs() /// private void ReadHierarchy(StreamReader file) { - while (!file.EndOfStream) + while (file.ReadLine() is string line) { - string line = file.ReadLine(); if (line.Length == 0) { return; @@ -394,15 +393,14 @@ private void ReadHierarchy(StreamReader file) /// private void ReadTimes(StreamReader file) { - while (!file.EndOfStream) + while (file.ReadLine() is string line) { - string line = file.ReadLine(); if (line.Length == 0) { return; } - string[] values = line.Split(MSBuildConstants.SemicolonChar); + string[] values = line.Split(MSBuildConstants.SpaceChar); if (values.Length < 3) { throw new InvalidDataException("Too few values in build plan."); diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs index 2d5341cf215..c9876d8a9bc 100644 --- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs +++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverLoader.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Reflection; using System.Xml; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Construction; using Microsoft.Build.Eventing; using Microsoft.Build.Framework; diff --git a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs index a9ea6b37548..1d6ec92f64c 100644 --- a/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs +++ b/src/Build/BackEnd/Components/SdkResolution/SdkResolverService.cs @@ -136,7 +136,7 @@ public virtual SdkResult ResolveSdk(int submissionId, SdkReference sdk, LoggingC // // Overall, while Sdk resolvers look like a general plug-in system, there are good reasons why some of the logic is hard-coded. // It's not really meant to be modified outside of very special/internal scenarios. -#if NETCOREAPP +#if NET if (ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) { if (TryResolveSdkUsingSpecifiedResolvers( @@ -479,7 +479,7 @@ private void RegisterResolversManifests(ElementLocation location) _manifestToResolvers = new Dictionary>(); SdkResolverManifest sdkDefaultResolversManifest = null; -#if NETCOREAPP +#if NET if (!ChangeWaves.AreFeaturesEnabled(ChangeWaves.Wave17_10)) #endif { diff --git a/src/Build/BackEnd/Node/InProcNode.cs b/src/Build/BackEnd/Node/InProcNode.cs index 7b4049f8905..33d5fb6359d 100644 --- a/src/Build/BackEnd/Node/InProcNode.cs +++ b/src/Build/BackEnd/Node/InProcNode.cs @@ -222,6 +222,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr ErrorUtilities.ThrowInternalError("Unexpected call to DeserializeAndRoutePacket on the in-proc node."); } + /// + /// Not necessary for in-proc node - we don't serialize. + /// + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + // The in-proc endpoint shouldn't be serializing, just routing. + ErrorUtilities.ThrowInternalError("Unexpected call to DeserializePacket on the in-proc node."); + return null; + } + /// /// Routes the packet to the appropriate handler. /// diff --git a/src/Build/BackEnd/Node/NodeConfiguration.cs b/src/Build/BackEnd/Node/NodeConfiguration.cs index 99ab53e2e70..cd57d5b74df 100644 --- a/src/Build/BackEnd/Node/NodeConfiguration.cs +++ b/src/Build/BackEnd/Node/NodeConfiguration.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using Microsoft.Build.Execution; -using Microsoft.Build.Framework; using Microsoft.Build.Logging; #nullable disable diff --git a/src/Build/BackEnd/Node/OutOfProcNode.cs b/src/Build/BackEnd/Node/OutOfProcNode.cs index f092add506b..d56acbd0db0 100644 --- a/src/Build/BackEnd/Node/OutOfProcNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcNode.cs @@ -14,10 +14,12 @@ using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.BackEnd.SdkResolution; using Microsoft.Build.Evaluation; -using Microsoft.Build.FileAccesses; using Microsoft.Build.Framework; using Microsoft.Build.Internal; using Microsoft.Build.Shared; +#if FEATURE_REPORTFILEACCESSES +using Microsoft.Build.FileAccesses; +#endif using SdkResult = Microsoft.Build.BackEnd.SdkResolution.SdkResult; #nullable disable @@ -341,6 +343,16 @@ void INodePacketFactory.DeserializeAndRoutePacket(int nodeId, NodePacketType pac _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Deserializes a packet. + /// + /// The packet type. + /// The translator to use as a source for packet data. + INodePacket INodePacketFactory.DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes a packet to the appropriate handler. /// diff --git a/src/Build/BackEnd/Node/OutOfProcServerNode.cs b/src/Build/BackEnd/Node/OutOfProcServerNode.cs index bda79d588cd..89591cd0f4c 100644 --- a/src/Build/BackEnd/Node/OutOfProcServerNode.cs +++ b/src/Build/BackEnd/Node/OutOfProcServerNode.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Concurrent; using System.IO; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using System.Text; using System.Threading; using System.Threading.Tasks; @@ -212,6 +210,16 @@ void INodePacketFactory.DeserializeAndRoutePacket(int nodeId, NodePacketType pac _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Deserializes a packet. + /// + /// The packet type. + /// The translator to use as a source for packet data. + INodePacket INodePacketFactory.DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes a packet to the appropriate handler. /// diff --git a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs index f90f99d14d3..d2c22d49f76 100644 --- a/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs +++ b/src/Build/BackEnd/Shared/BuildRequestConfiguration.cs @@ -869,7 +869,7 @@ public override int GetHashCode() /// String representation of the object public override string ToString() { - return String.Format(CultureInfo.CurrentCulture, "{0} {1} {2} {3}", _configId, _projectFullPath, _toolsVersion, _globalProperties); + return $"{_configId} {_projectFullPath} {_toolsVersion} {_globalProperties}"; } /// diff --git a/src/Build/BackEnd/Shared/BuildResult.cs b/src/Build/BackEnd/Shared/BuildResult.cs index f433ffc25ad..1ef8455bdf1 100644 --- a/src/Build/BackEnd/Shared/BuildResult.cs +++ b/src/Build/BackEnd/Shared/BuildResult.cs @@ -5,12 +5,12 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using Microsoft.Build.Framework; -using System.Diagnostics.CodeAnalysis; namespace Microsoft.Build.Execution { diff --git a/src/Build/BackEnd/Shared/EventsCreatorHelper.cs b/src/Build/BackEnd/Shared/EventsCreatorHelper.cs index 1dad22fc1cf..6b6500c467a 100644 --- a/src/Build/BackEnd/Shared/EventsCreatorHelper.cs +++ b/src/Build/BackEnd/Shared/EventsCreatorHelper.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Framework; using Microsoft.Build.Shared; diff --git a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs index ccedf3bab7f..286e1bf9073 100644 --- a/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs +++ b/src/Build/BuildCheck/Acquisition/BuildCheckAcquisitionModule.cs @@ -40,8 +40,8 @@ public List CreateCheckFactories( assembly = Assembly.LoadFrom(checkAcquisitionData.AssemblyPath); #endif - IList availableTypes = assembly.GetExportedTypes(); - IList checkTypes = availableTypes.Where(t => typeof(Check).IsAssignableFrom(t)).ToArray(); + Type[] availableTypes = assembly.GetExportedTypes(); + Type[] checkTypes = availableTypes.Where(t => typeof(Check).IsAssignableFrom(t)).ToArray(); foreach (Type checkCandidate in checkTypes) { @@ -49,7 +49,7 @@ public List CreateCheckFactories( checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckRegistered", checkCandidate.Name, checkCandidate.Assembly); } - if (availableTypes.Count != checkTypes.Count) + if (availableTypes.Length != checkTypes.Length) { availableTypes.Except(checkTypes).ToList() .ForEach(t => checkContext.DispatchAsComment(MessageImportance.Normal, "CustomCheckBaseTypeNotAssignable", t.Name, t.Assembly)); diff --git a/src/Build/BuildCheck/Checks/EmbeddedResourceCheck.cs b/src/Build/BuildCheck/Checks/EmbeddedResourceCheck.cs index aaeda56eaa6..6feb8874470 100644 --- a/src/Build/BuildCheck/Checks/EmbeddedResourceCheck.cs +++ b/src/Build/BuildCheck/Checks/EmbeddedResourceCheck.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.IO; using System.Collections.Generic; +using System.IO; using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Framework; diff --git a/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs b/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs new file mode 100644 index 00000000000..252159162b2 --- /dev/null +++ b/src/Build/BuildCheck/Checks/ExecCliBuildCheck.cs @@ -0,0 +1,150 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.Collections.Generic; +#if !FEATURE_MSIOREDIST +using System.IO; +#endif +using Microsoft.Build.Shared; + +#if FEATURE_MSIOREDIST +using Path = Microsoft.IO.Path; +#endif + +namespace Microsoft.Build.Experimental.BuildCheck.Checks; + +internal sealed class ExecCliBuildCheck : Check +{ + public static CheckRule SupportedRule = new CheckRule( + "BC0302", + "ExecCliBuild", + ResourceUtilities.GetResourceString("BuildCheck_BC0302_Title")!, + ResourceUtilities.GetResourceString("BuildCheck_BC0302_MessageFmt")!, + new CheckConfiguration() { Severity = CheckResultSeverity.Warning }); + + private const string ExecTaskName = "Exec"; + private const string CommandParameterName = "Command"; + + private static readonly char[] s_knownCommandSeparators = ['&', ';', '|']; + + private static readonly string[] s_knownBuildCommands = + [ + "dotnet build", + "dotnet clean", + "dotnet msbuild", + "dotnet restore", + "dotnet publish", + "dotnet pack", + "dotnet vstest", + "nuget restore", + "msbuild", + "dotnet test", + "dotnet run", + ]; + + public override string FriendlyName => "MSBuild.ExecCliBuildCheck"; + + internal override bool IsBuiltIn => true; + + public override IReadOnlyList SupportedRules { get; } = [SupportedRule]; + + public override void Initialize(ConfigurationContext configurationContext) + { + /* This is it - no custom configuration */ + } + + public override void RegisterActions(IBuildCheckRegistrationContext registrationContext) + { + registrationContext.RegisterTaskInvocationAction(TaskInvocationAction); + } + + private static void TaskInvocationAction(BuildCheckDataContext context) + { + if (context.Data.TaskName == ExecTaskName + && context.Data.Parameters.TryGetValue(CommandParameterName, out TaskInvocationCheckData.TaskParameter? commandArgument)) + { + var execCommandValue = commandArgument.Value?.ToString() ?? string.Empty; + + var commandSpan = execCommandValue.AsSpan(); + int start = 0; + + while (start < commandSpan.Length) + { + var nextSeparatorIndex = commandSpan.Slice(start, commandSpan.Length - start).IndexOfAny(s_knownCommandSeparators); + + if (nextSeparatorIndex == -1) + { + if (TryGetMatchingKnownBuildCommand(commandSpan.Slice(start), out var knownBuildCommand)) + { + context.ReportResult(BuildCheckResult.CreateBuiltIn( + SupportedRule, + context.Data.TaskInvocationLocation, + context.Data.TaskName, + Path.GetFileName(context.Data.ProjectFilePath), + GetToolName(knownBuildCommand))); + } + + break; + } + else + { + var command = commandSpan.Slice(start, nextSeparatorIndex); + + if (TryGetMatchingKnownBuildCommand(command, out var knownBuildCommand)) + { + context.ReportResult(BuildCheckResult.CreateBuiltIn( + SupportedRule, + context.Data.TaskInvocationLocation, + context.Data.TaskName, + Path.GetFileName(context.Data.ProjectFilePath), + GetToolName(knownBuildCommand))); + + break; + } + + start += nextSeparatorIndex + 1; + } + } + } + } + + private static bool TryGetMatchingKnownBuildCommand(ReadOnlySpan command, out string knownBuildCommand) + { + const int maxStackLimit = 1024; + + Span normalizedBuildCommand = command.Length <= maxStackLimit ? stackalloc char[command.Length] : new char[command.Length]; + int normalizedCommandIndex = 0; + + foreach (var c in command) + { + if (char.IsWhiteSpace(c) && (normalizedCommandIndex == 0 || char.IsWhiteSpace(normalizedBuildCommand[normalizedCommandIndex - 1]))) + { + continue; + } + + normalizedBuildCommand[normalizedCommandIndex++] = c; + } + + foreach (var buildCommand in s_knownBuildCommands) + { + if (normalizedBuildCommand.StartsWith(buildCommand.AsSpan())) + { + knownBuildCommand = buildCommand; + return true; + } + } + + knownBuildCommand = string.Empty; + return false; + } + + private static string GetToolName(string knownBuildCommand) + { + int nextSpaceIndex = knownBuildCommand.IndexOf(' '); + + return nextSpaceIndex == -1 + ? knownBuildCommand + : knownBuildCommand.AsSpan().Slice(0, nextSpaceIndex).ToString(); + } +} diff --git a/src/Build/BuildCheck/Checks/SharedOutputPathCheck.cs b/src/Build/BuildCheck/Checks/SharedOutputPathCheck.cs index fe80a4ded80..635fc4ff1a6 100644 --- a/src/Build/BuildCheck/Checks/SharedOutputPathCheck.cs +++ b/src/Build/BuildCheck/Checks/SharedOutputPathCheck.cs @@ -3,9 +3,9 @@ using System.Collections.Generic; using System.IO; +using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Shared; -using Microsoft.Build.Collections; namespace Microsoft.Build.Experimental.BuildCheck.Checks; diff --git a/src/Build/BuildCheck/Checks/TargetFrameworkConfusionCheck.cs b/src/Build/BuildCheck/Checks/TargetFrameworkConfusionCheck.cs index 2c3db3512ad..7d117fe456f 100644 --- a/src/Build/BuildCheck/Checks/TargetFrameworkConfusionCheck.cs +++ b/src/Build/BuildCheck/Checks/TargetFrameworkConfusionCheck.cs @@ -46,7 +46,9 @@ private void EvaluatedPropertiesAction(BuildCheckDataContext HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) }, - { typeof(ProjectEvaluationFinishedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs)e) }, - { typeof(ProjectEvaluationStartedEventArgs), (BuildEventArgs e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs)e) }, - { typeof(EnvironmentVariableReadEventArgs), (BuildEventArgs e) => HandleEnvironmentVariableReadEvent((EnvironmentVariableReadEventArgs)e) }, - { typeof(ProjectStartedEventArgs), (BuildEventArgs e) => HandleProjectStartedRequest((ProjectStartedEventArgs)e) }, - { typeof(ProjectFinishedEventArgs), (BuildEventArgs e) => HandleProjectFinishedRequest((ProjectFinishedEventArgs)e) }, - { typeof(BuildCheckTracingEventArgs), (BuildEventArgs e) => HandleBuildCheckTracingEvent((BuildCheckTracingEventArgs)e) }, - { typeof(BuildCheckAcquisitionEventArgs), (BuildEventArgs e) => HandleBuildCheckAcquisitionEvent((BuildCheckAcquisitionEventArgs)e) }, - { typeof(TaskStartedEventArgs), (BuildEventArgs e) => HandleTaskStartedEvent((TaskStartedEventArgs)e) }, - { typeof(TaskFinishedEventArgs), (BuildEventArgs e) => HandleTaskFinishedEvent((TaskFinishedEventArgs)e) }, - { typeof(TaskParameterEventArgs), (BuildEventArgs e) => HandleTaskParameterEvent((TaskParameterEventArgs)e) }, - { typeof(BuildFinishedEventArgs), (BuildEventArgs e) => HandleBuildFinishedEvent((BuildFinishedEventArgs)e) }, - { typeof(ProjectImportedEventArgs), (BuildEventArgs e) => HandleProjectImportedEvent((ProjectImportedEventArgs)e) }, + { typeof(BuildSubmissionStartedEventArgs), (e) => HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) }, + { typeof(ProjectEvaluationFinishedEventArgs), (e) => HandleProjectEvaluationFinishedEvent((ProjectEvaluationFinishedEventArgs)e) }, + { typeof(ProjectEvaluationStartedEventArgs), (e) => HandleProjectEvaluationStartedEvent((ProjectEvaluationStartedEventArgs)e) }, + { typeof(EnvironmentVariableReadEventArgs), (e) => HandleEnvironmentVariableReadEvent((EnvironmentVariableReadEventArgs)e) }, + { typeof(ProjectStartedEventArgs), (e) => HandleProjectStartedRequest((ProjectStartedEventArgs)e) }, + { typeof(ProjectFinishedEventArgs), (e) => HandleProjectFinishedRequest((ProjectFinishedEventArgs)e) }, + { typeof(BuildCheckTracingEventArgs), (e) => HandleBuildCheckTracingEvent((BuildCheckTracingEventArgs)e) }, + { typeof(BuildCheckAcquisitionEventArgs), (e) => HandleBuildCheckAcquisitionEvent((BuildCheckAcquisitionEventArgs)e) }, + { typeof(TaskStartedEventArgs), (e) => HandleTaskStartedEvent((TaskStartedEventArgs)e) }, + { typeof(TaskFinishedEventArgs), (e) => HandleTaskFinishedEvent((TaskFinishedEventArgs)e) }, + { typeof(TaskParameterEventArgs), (e) => HandleTaskParameterEvent((TaskParameterEventArgs)e) }, + { typeof(BuildFinishedEventArgs), (e) => HandleBuildFinishedEvent((BuildFinishedEventArgs)e) }, + { typeof(ProjectImportedEventArgs), (e) => HandleProjectImportedEvent((ProjectImportedEventArgs)e) }, }; // During restore we'll wait only for restore to be done. _eventHandlersRestore = new() { - { typeof(BuildSubmissionStartedEventArgs), (BuildEventArgs e) => HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) }, + { typeof(BuildSubmissionStartedEventArgs), (e) => HandleBuildSubmissionStartedEvent((BuildSubmissionStartedEventArgs)e) }, }; _eventHandlers = _eventHandlersFull; diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs index 9e8782ae9ee..08c7ea339c4 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckConnectorLogger.cs @@ -16,15 +16,11 @@ namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure; internal sealed class BuildCheckConnectorLogger : ILogger { private readonly BuildCheckBuildEventHandler _eventHandler; - private readonly IBuildCheckManager _buildCheckManager; - private readonly ICheckContextFactory _checkContextFactory; internal BuildCheckConnectorLogger( ICheckContextFactory checkContextFactory, IBuildCheckManager buildCheckManager) { - _buildCheckManager = buildCheckManager; - _checkContextFactory = checkContextFactory; _eventHandler = new BuildCheckBuildEventHandler(checkContextFactory, buildCheckManager); } diff --git a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs index 7100095f1b4..89998bad255 100644 --- a/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/BuildCheckManagerProvider.cs @@ -149,6 +149,7 @@ internal readonly record struct BuiltInCheckFactory( new BuiltInCheckFactory([PreferProjectReferenceCheck.SupportedRule.Id], PreferProjectReferenceCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), new BuiltInCheckFactory([CopyAlwaysCheck.SupportedRule.Id], CopyAlwaysCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), new BuiltInCheckFactory([DoubleWritesCheck.SupportedRule.Id], DoubleWritesCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), + new BuiltInCheckFactory([ExecCliBuildCheck.SupportedRule.Id], ExecCliBuildCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), new BuiltInCheckFactory([NoEnvironmentVariablePropertyCheck.SupportedRule.Id], NoEnvironmentVariablePropertyCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), new BuiltInCheckFactory([EmbeddedResourceCheck.SupportedRule.Id], EmbeddedResourceCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), new BuiltInCheckFactory([TargetFrameworkConfusionCheck.SupportedRule.Id], TargetFrameworkConfusionCheck.SupportedRule.DefaultConfiguration.IsEnabled ?? false, Construct), diff --git a/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs b/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs index e0179b29c5d..0030a1574cc 100644 --- a/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs +++ b/src/Build/BuildCheck/Infrastructure/ConfigurationProvider.cs @@ -2,12 +2,12 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using Microsoft.Build.BuildCheck.Infrastructure; using Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig; -using System.Collections.Concurrent; using Microsoft.Build.Experimental.BuildCheck.Utilities; -using Microsoft.Build.BuildCheck.Infrastructure; namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure; diff --git a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs index e40d33efb34..1039bfa67cc 100644 --- a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs +++ b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigFile.cs @@ -23,23 +23,16 @@ internal partial class EditorConfigFile // Matches EditorConfig property such as "indent_style = space", see https://editorconfig.org for details private const string s_propertyMatcherPattern = @"^\s*([\w\.\-_]+)\s*[=:]\s*(.*?)\s*([#;].*)?$"; -#if NETCOREAPP - +#if NET [GeneratedRegex(s_sectionMatcherPattern)] - private static partial Regex GetSectionMatcherRegex(); + private static partial Regex SectionMatcherRegex { get; } [GeneratedRegex(s_propertyMatcherPattern)] - private static partial Regex GetPropertyMatcherRegex(); - + private static partial Regex PropertyMatcherRegex { get; } #else - private static readonly Regex s_sectionMatcher = new Regex(s_sectionMatcherPattern, RegexOptions.Compiled); - - private static readonly Regex s_propertyMatcher = new Regex(s_propertyMatcherPattern, RegexOptions.Compiled); - - private static Regex GetSectionMatcherRegex() => s_sectionMatcher; - - private static Regex GetPropertyMatcherRegex() => s_propertyMatcher; + private static Regex SectionMatcherRegex { get; } = new Regex(s_sectionMatcherPattern, RegexOptions.Compiled); + private static Regex PropertyMatcherRegex { get; } = new Regex(s_propertyMatcherPattern, RegexOptions.Compiled); #endif internal Section GlobalSection { get; } @@ -90,12 +83,12 @@ internal static EditorConfigFile Parse(string text) continue; } - var sectionMatches = GetSectionMatcherRegex().Matches(line); - if (sectionMatches.Count > 0 && sectionMatches[0].Groups.Count > 0) + var sectionMatch = SectionMatcherRegex.Match(line); + if (sectionMatch.Success && sectionMatch.Groups.Count > 0) { addNewSection(); - var sectionName = sectionMatches[0].Groups[1].Value; + var sectionName = sectionMatch.Groups[1].Value; Debug.Assert(!string.IsNullOrEmpty(sectionName)); activeSectionName = sectionName; @@ -103,11 +96,11 @@ internal static EditorConfigFile Parse(string text) continue; } - var propMatches = GetPropertyMatcherRegex().Matches(line); - if (propMatches.Count > 0 && propMatches[0].Groups.Count > 1) + var propMatch = PropertyMatcherRegex.Match(line); + if (propMatch.Success && propMatch.Groups.Count > 1) { - var key = propMatches[0].Groups[1].Value.ToLower(); - var value = propMatches[0].Groups[2].Value; + var key = propMatch.Groups[1].Value.ToLower(); + var value = propMatch.Groups[2].Value; Debug.Assert(!string.IsNullOrEmpty(key)); Debug.Assert(key == key.Trim()); diff --git a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs index 56b0842acf7..9b72644bdb8 100644 --- a/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs +++ b/src/Build/BuildCheck/Infrastructure/EditorConfig/EditorConfigParser.cs @@ -12,6 +12,10 @@ namespace Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig; internal sealed class EditorConfigParser { + // static property for embedding resolved `.editorconfig`s in binlog + private static ConcurrentBag editorConfigFilePaths = new ConcurrentBag(); + public static IEnumerable EditorConfigFilePaths => editorConfigFilePaths; + private const string EditorconfigFile = ".editorconfig"; /// @@ -25,6 +29,13 @@ internal Dictionary Parse(string filePath) return MergeEditorConfigFiles(editorConfigs, filePath); } + /// + /// Clears the editorConfigFilePaths collection after embedding in the binlog. + /// + public static void ClearEditorConfigFilePaths() + { + editorConfigFilePaths = new ConcurrentBag(); + } /// /// Fetches the list of EditorconfigFile ordered from the nearest to the filePath. /// @@ -34,21 +45,15 @@ internal List DiscoverEditorConfigFiles(string filePath) var editorConfigDataFromFilesList = new List(); var directoryOfTheProject = Path.GetDirectoryName(filePath); - // The method will look for the file in parent directory if not found in current until found or the directory is root. + // The method will look for the file in parent directory if not found in current until found or the directory is root. var editorConfigFilePath = FileUtilities.GetPathOfFileAbove(EditorconfigFile, directoryOfTheProject); - while (editorConfigFilePath != string.Empty) { var editorConfig = _editorConfigFileCache.GetOrAdd(editorConfigFilePath, (key) => { - using (FileStream stream = new FileStream(editorConfigFilePath, FileMode.Open, System.IO.FileAccess.Read, FileShare.Read)) - { - using StreamReader sr = new StreamReader(editorConfigFilePath); - var editorConfigfileContent = sr.ReadToEnd(); - return EditorConfigFile.Parse(editorConfigfileContent); - } + return EditorConfigFile.Parse(File.ReadAllText(editorConfigFilePath)); }); - + editorConfigFilePaths.Add(editorConfigFilePath); editorConfigDataFromFilesList.Add(editorConfig); if (editorConfig.IsRoot) @@ -66,7 +71,7 @@ internal List DiscoverEditorConfigFiles(string filePath) } /// - /// Retrieves the config dictionary from the sections that matched the filePath. + /// Retrieves the config dictionary from the sections that matched the filePath. /// /// /// diff --git a/src/Build/BuildCheck/OM/BuildCheckDataContext.cs b/src/Build/BuildCheck/OM/BuildCheckDataContext.cs index b4ead9bb81b..561e2f59fec 100644 --- a/src/Build/BuildCheck/OM/BuildCheckDataContext.cs +++ b/src/Build/BuildCheck/OM/BuildCheckDataContext.cs @@ -2,8 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using System.IO; +using Microsoft.Build.Experimental.BuildCheck.Infrastructure; namespace Microsoft.Build.Experimental.BuildCheck; @@ -16,9 +16,9 @@ public abstract class CheckData(string projectFilePath, int? projectConfiguratio { private string? _projectFileDirectory; // The id is going to be used in future revision -#pragma warning disable CA1823 +#pragma warning disable CA1823, IDE0052 private int? _projectConfigurationId = projectConfigurationId; -#pragma warning restore CA1823 +#pragma warning restore CA1823, IDE0052 /// /// Full path to the project file being built. diff --git a/src/Build/BuildCheck/OM/PropertyReadData.cs b/src/Build/BuildCheck/OM/PropertyReadData.cs index 531f9fdc4a4..d4198c8385f 100644 --- a/src/Build/BuildCheck/OM/PropertyReadData.cs +++ b/src/Build/BuildCheck/OM/PropertyReadData.cs @@ -1,8 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Evaluation; +using Microsoft.Build.Experimental.BuildCheck.Infrastructure; using Microsoft.Build.Shared; namespace Microsoft.Build.Experimental.BuildCheck; diff --git a/src/Build/Collections/CopyOnReadEnumerable.cs b/src/Build/Collections/CopyOnReadEnumerable.cs index 6ed48e0eed1..056f5804cfd 100644 --- a/src/Build/Collections/CopyOnReadEnumerable.cs +++ b/src/Build/Collections/CopyOnReadEnumerable.cs @@ -62,7 +62,7 @@ public IEnumerator GetEnumerator() { List list; -#if NETCOREAPP +#if NET if (_backingEnumerable.TryGetNonEnumeratedCount(out int count)) { #else diff --git a/src/Build/Collections/ICopyOnWritePropertyDictionary.cs b/src/Build/Collections/ICopyOnWritePropertyDictionary.cs index 310476fa83b..f7c0589e7f5 100644 --- a/src/Build/Collections/ICopyOnWritePropertyDictionary.cs +++ b/src/Build/Collections/ICopyOnWritePropertyDictionary.cs @@ -2,11 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Generic; -using System.Collections.Immutable; -using System.Diagnostics; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Collections/IItemDictionary.cs b/src/Build/Collections/IItemDictionary.cs index 1555c1de814..cca36bf088d 100644 --- a/src/Build/Collections/IItemDictionary.cs +++ b/src/Build/Collections/IItemDictionary.cs @@ -2,13 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Evaluation; -using Microsoft.Build.Shared; namespace Microsoft.Build.Collections { diff --git a/src/Build/Collections/IMultiDictionary.cs b/src/Build/Collections/IMultiDictionary.cs index 1fece50dc23..f78a2cf768e 100644 --- a/src/Build/Collections/IMultiDictionary.cs +++ b/src/Build/Collections/IMultiDictionary.cs @@ -1,11 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Microsoft.Build.Collections { diff --git a/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs index e3b10556772..798512d22ad 100644 --- a/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs +++ b/src/Build/Collections/RetrievableEntryHashSet/IRetrievableValuedEntryHashSet.cs @@ -1,9 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections.Generic; -using System.Runtime.Serialization; - #nullable disable namespace Microsoft.Build.Collections diff --git a/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs index dc8d96f9f7c..5db99b8ab70 100644 --- a/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs +++ b/src/Build/Collections/RetrievableEntryHashSet/RetrievableEntryHashSet.cs @@ -82,7 +82,7 @@ namespace Microsoft.Build.Collections #if FEATURE_SECURITY_PERMISSIONS [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)] #endif - internal class RetrievableEntryHashSet : IRetrievableEntryHashSet + internal class RetrievableEntryHashSet : IRetrievableEntryHashSet // CodeQL [SM02227] The dangerous method is called only in debug build. It's safe for release build. where T : class, IKeyed { // store lower 31 bits of hash code diff --git a/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs b/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs index c45da4e12b6..9b3ffdefa0f 100644 --- a/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs +++ b/src/Build/Collections/RetrievableEntryHashSet/RetrievableValuedEntryHashSet.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Diagnostics; diff --git a/src/Build/Construction/ProjectElementContainer.cs b/src/Build/Construction/ProjectElementContainer.cs index c8fabf3559e..6c6584e52c7 100644 --- a/src/Build/Construction/ProjectElementContainer.cs +++ b/src/Build/Construction/ProjectElementContainer.cs @@ -584,7 +584,7 @@ private static string GetElementIndentation(XmlElementWithLocation xmlElement) var leadingWhiteSpace = xmlElement.PreviousSibling.Value; - var lastIndexOfNewLine = leadingWhiteSpace.LastIndexOf("\n", StringComparison.Ordinal); + var lastIndexOfNewLine = leadingWhiteSpace.LastIndexOf('\n'); if (lastIndexOfNewLine == -1) { diff --git a/src/Build/Construction/ProjectRootElement.cs b/src/Build/Construction/ProjectRootElement.cs index eee48e9f667..903de887aac 100644 --- a/src/Build/Construction/ProjectRootElement.cs +++ b/src/Build/Construction/ProjectRootElement.cs @@ -43,7 +43,7 @@ namespace Microsoft.Build.Construction /// to control its lifetime and not be surprised by edits via another project collection. /// [DebuggerDisplay("{FullPath} #Children={Count} DefaultTargets={DefaultTargets} ToolsVersion={ToolsVersion} InitialTargets={InitialTargets} ExplicitlyLoaded={IsExplicitlyLoaded}")] - public class ProjectRootElement : ProjectElementContainer + public partial class ProjectRootElement : ProjectElementContainer { // Constants for default (empty) project file. private const string EmptyProjectFileContent = "{0}\r\n"; @@ -58,10 +58,18 @@ public class ProjectRootElement : ProjectElementContainer private static readonly ProjectRootElementCacheBase.OpenProjectRootElement s_openLoaderPreserveFormattingDelegate = OpenLoaderPreserveFormatting; + private const string XmlDeclarationPattern = @"\A\s*\<\?\s*xml.*\?\>\s*\Z"; + /// /// Used to determine if a file is an empty XML file if it ONLY contains an XML declaration like <?xml version="1.0" encoding="utf-8"?>. /// - private static readonly Lazy XmlDeclarationRegEx = new Lazy(() => new Regex(@"\A\s*\<\?\s*xml.*\?\>\s*\Z"), isThreadSafe: true); +#if NET + [GeneratedRegex(XmlDeclarationPattern)] + private static partial Regex XmlDeclarationRegex { get; } +#else + private static Regex XmlDeclarationRegex => s_xmlDeclarationRegex ??= new Regex(XmlDeclarationPattern); + private static Regex s_xmlDeclarationRegex; +#endif /// /// The default encoding to use / assume for a new project. @@ -1988,9 +1996,9 @@ internal static bool IsEmptyXmlFile(string path) string contents = File.ReadAllText(path); - // If the file is only whitespace or the XML declaration then it empty + // If the file is only whitespace or the XML declaration then it is empty // - return String.IsNullOrEmpty(contents) || XmlDeclarationRegEx.Value.IsMatch(contents); + return String.IsNullOrEmpty(contents) || XmlDeclarationRegex.IsMatch(contents); } catch (Exception) { diff --git a/src/Build/Construction/ProjectTargetElement.cs b/src/Build/Construction/ProjectTargetElement.cs index db079f86773..4fdf9c21495 100644 --- a/src/Build/Construction/ProjectTargetElement.cs +++ b/src/Build/Construction/ProjectTargetElement.cs @@ -106,7 +106,7 @@ public string Name string unescapedValue = EscapingUtilities.UnescapeAll(value); - int indexOfSpecialCharacter = unescapedValue.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = unescapedValue.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { ErrorUtilities.ThrowArgument("OM_NameInvalid", unescapedValue, unescapedValue[indexOfSpecialCharacter]); diff --git a/src/Build/Construction/Solution/ProjectInSolution.cs b/src/Build/Construction/Solution/ProjectInSolution.cs index 60f69b97b54..106b21221ac 100644 --- a/src/Build/Construction/Solution/ProjectInSolution.cs +++ b/src/Build/Construction/Solution/ProjectInSolution.cs @@ -2,13 +2,18 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; + using System.Collections; using System.Collections.Generic; +using System.Collections.ObjectModel; using System.IO; +using System.Linq; using System.Security; -using System.Text; using System.Xml; -#if !NETFRAMEWORK +#if NETFRAMEWORK +using System.Text; +#else +using System.Buffers; using Microsoft.Build.Shared; #endif @@ -16,8 +21,8 @@ using ProjectFileErrorUtilities = Microsoft.Build.Shared.ProjectFileErrorUtilities; using BuildEventFileInfo = Microsoft.Build.Shared.BuildEventFileInfo; using ErrorUtilities = Microsoft.Build.Shared.ErrorUtilities; -using System.Collections.ObjectModel; -using System.Linq; + + #nullable disable @@ -83,7 +88,12 @@ public sealed class ProjectInSolution /// /// Characters that need to be cleansed from a project name. /// - private static readonly char[] s_charsToCleanse = { '%', '$', '@', ';', '.', '(', ')', '\'' }; +#if NET + private static readonly SearchValues s_charsToCleanse = SearchValues.Create( +#else + private static readonly char[] s_charsToCleanse = ( +#endif + ['%', '$', '@', ';', '.', '(', ')', '\'']); /// /// Project names that need to be disambiguated when forming a target name @@ -501,12 +511,25 @@ private static string CleanseProjectName(string projectName) // If there are no special chars, just return the original string immediately. // Don't even instantiate the StringBuilder. - int indexOfChar = projectName.IndexOfAny(s_charsToCleanse); + int indexOfChar = projectName.AsSpan().IndexOfAny(s_charsToCleanse); if (indexOfChar == -1) { return projectName; } +#if NET + return string.Create(projectName.Length, (projectName, indexOfChar), static (dest, state) => + { + state.projectName.AsSpan().CopyTo(dest); + int pos = state.indexOfChar; + do + { + dest[pos] = cleanCharacter; + dest = dest.Slice(pos + 1); + } + while ((pos = dest.IndexOfAny(s_charsToCleanse)) >= 0); + }); +#else // This is where we're going to work on the final string to return to the caller. var cleanProjectName = new StringBuilder(projectName); @@ -517,6 +540,7 @@ private static string CleanseProjectName(string projectName) } return cleanProjectName.ToString(); +#endif } /// diff --git a/src/Build/Construction/Solution/SolutionFile.cs b/src/Build/Construction/Solution/SolutionFile.cs index 3a7a0ce42a7..a424caca2b6 100644 --- a/src/Build/Construction/Solution/SolutionFile.cs +++ b/src/Build/Construction/Solution/SolutionFile.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; -using System.Globalization; using System.IO; using System.Linq; using System.Runtime.InteropServices; @@ -35,38 +34,47 @@ namespace Microsoft.Build.Construction /// This class contains the functionality to parse a solution file and return a corresponding /// MSBuild project file containing the projects and dependencies defined in the solution. /// - public sealed class SolutionFile + public sealed partial class SolutionFile { #region Solution specific constants // An example of a project line looks like this: // Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ClassLibrary1", "ClassLibrary1\ClassLibrary1.csproj", "{05A5AD00-71B5-4612-AF2F-9EA9121C4111}" - private static readonly Lazy s_crackProjectLine = new Lazy( - () => new Regex( - "^" // Beginning of line - + "Project\\(\"(?.*)\"\\)" - + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace - + "\"(?.*)\"" - + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace - + "\"(?.*)\"" - + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace - + "\"(?.*)\"" - + "$", // End-of-line - RegexOptions.Compiled)); + private const string CrackProjectLinePattern = + "^" // Beginning of line + + "Project\\(\"(?.*)\"\\)" + + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace + + "\"(?.*)\"" + + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace + + "\"(?.*)\"" + + "\\s*,\\s*" // Any amount of whitespace plus "," plus any amount of whitespace + + "\"(?.*)\"" + + "$"; // End-of-line // An example of a property line looks like this: // AspNetCompiler.VirtualPath = "/webprecompile" // Because website projects now include the target framework moniker as // one of their properties, may now have '=' in it. - - private static readonly Lazy s_crackPropertyLine = new Lazy( - () => new Regex( - "^" // Beginning of line - + "(?[^=]*)" - + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace - + "(?.*)" - + "$", // End-of-line - RegexOptions.Compiled)); + private const string CrackPropertyLinePattern = + "^" // Beginning of line + + "(?[^=]*)" + + "\\s*=\\s*" // Any amount of whitespace plus "=" plus any amount of whitespace + + "(?.*)" + + "$"; // End-of-line + +#if NET + [GeneratedRegex(CrackProjectLinePattern)] + private static partial Regex CrackProjectLineRegex { get; } + + [GeneratedRegex(CrackPropertyLinePattern)] + private static partial Regex CrackPropertyLineRegex { get; } +#else + private static Regex CrackProjectLineRegex => s_crackProjectLineRegex ??= new Regex(CrackProjectLinePattern, RegexOptions.Compiled); + private static Regex CrackPropertyLineRegex => s_crackPropertyLineRegex ??= new Regex(CrackPropertyLinePattern, RegexOptions.Compiled); + + private static Regex s_crackProjectLineRegex; + private static Regex s_crackPropertyLineRegex; +#endif internal const int slnFileMinUpgradableVersion = 7; // Minimum version for MSBuild to give a nice message internal const int slnFileMinVersion = 9; // Minimum version for MSBuild to actually do anything useful @@ -89,7 +97,7 @@ public sealed class SolutionFile private const string sharedProjectGuid = "{D954291E-2A0B-460D-934E-DC6B0785DB48}"; private const char CommentStartChar = '#'; - #endregion +#endregion #region Member data private string _solutionFile; // Could be absolute or relative path to the .SLN file. private string _solutionFilterFile; // Could be absolute or relative path to the .SLNF file. @@ -121,7 +129,7 @@ public sealed class SolutionFile // TODO: Unify to NativeMethodsShared.OSUsesCaseSensitive paths // when possible. - private static StringComparer _pathComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) + private static readonly StringComparer _pathComparer = RuntimeInformation.IsOSPlatform(OSPlatform.Linux) ? StringComparer.Ordinal : StringComparer.OrdinalIgnoreCase; @@ -561,7 +569,12 @@ internal static void GetSolutionFileAndVisualStudioMajorVersions(string solution if (line.Trim().StartsWith(slnFileHeaderNoVersion, StringComparison.Ordinal)) { // Found it. Validate the version. - string fileVersionFromHeader = line.Substring(slnFileHeaderNoVersion.Length); + var fileVersionFromHeader = +#if NET + line.AsSpan(slnFileHeaderNoVersion.Length); +#else + line.Substring(slnFileHeaderNoVersion.Length); +#endif if (!System.Version.TryParse(fileVersionFromHeader, out Version version)) { @@ -1061,7 +1074,7 @@ private void ParseProject(string firstLine) { // This should be a dependency. The GUID identifying the parent project should // be both the property name and the property value. - Match match = s_crackPropertyLine.Value.Match(line); + Match match = CrackPropertyLineRegex.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectDepGuidError", proj.ProjectName); @@ -1079,7 +1092,7 @@ private void ParseProject(string firstLine) line = ReadLine(); while ((line?.StartsWith("EndProjectSection", StringComparison.Ordinal) == false)) { - Match match = s_crackPropertyLine.Value.Match(line); + Match match = CrackPropertyLineRegex.Match(line); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseWebProjectPropertiesError", proj.ProjectName); @@ -1290,7 +1303,7 @@ private void ValidateProjectRelativePath(ProjectInSolution proj) ErrorUtilities.VerifyThrow(proj.RelativePath != null, "Project relative path cannot be null."); // Verify the relative path does not contain invalid characters - ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.IndexOfAny(Path.GetInvalidPathChars()) == -1, + ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(proj.RelativePath.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseInvalidProjectFileNameCharacters", @@ -1357,7 +1370,13 @@ private static void ParseAspNetCompilerProperty( string configurationName = propertyName.Substring(0, indexOfFirstDot); // The rest of it is the actual property name. - string aspNetPropertyName = ((propertyName.Length - indexOfFirstDot) > 0) ? propertyName.Substring(indexOfFirstDot + 1, propertyName.Length - indexOfFirstDot - 1) : ""; + var aspNetPropertyName = ((propertyName.Length - indexOfFirstDot) > 0) ? +#if NET + propertyName.AsSpan(indexOfFirstDot + 1) : +#else + propertyName.Substring(indexOfFirstDot + 1) : +#endif + ""; // And the part after the sign is the property value (which was parsed out for us prior // to calling this method). @@ -1392,49 +1411,19 @@ private static void ParseAspNetCompilerProperty( } // Update the appropriate field within the parameters struct. - if (aspNetPropertyName == "AspNetCompiler.VirtualPath") - { - aspNetCompilerParameters.aspNetVirtualPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.PhysicalPath") - { - aspNetCompilerParameters.aspNetPhysicalPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.TargetPath") - { - aspNetCompilerParameters.aspNetTargetPath = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.ForceOverwrite") - { - aspNetCompilerParameters.aspNetForce = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.Updateable") - { - aspNetCompilerParameters.aspNetUpdateable = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.Debug") + switch (aspNetPropertyName) { - aspNetCompilerParameters.aspNetDebug = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.KeyFile") - { - aspNetCompilerParameters.aspNetKeyFile = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.KeyContainer") - { - aspNetCompilerParameters.aspNetKeyContainer = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.DelaySign") - { - aspNetCompilerParameters.aspNetDelaySign = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.AllowPartiallyTrustedCallers") - { - aspNetCompilerParameters.aspNetAPTCA = propertyValue; - } - else if (aspNetPropertyName == "AspNetCompiler.FixedNames") - { - aspNetCompilerParameters.aspNetFixedNames = propertyValue; + case "AspNetCompiler.VirtualPath": aspNetCompilerParameters.aspNetVirtualPath = propertyValue; break; + case "AspNetCompiler.PhysicalPath": aspNetCompilerParameters.aspNetPhysicalPath = propertyValue; break; + case "AspNetCompiler.TargetPath": aspNetCompilerParameters.aspNetTargetPath = propertyValue; break; + case "AspNetCompiler.ForceOverwrite": aspNetCompilerParameters.aspNetForce = propertyValue; break; + case "AspNetCompiler.Updateable": aspNetCompilerParameters.aspNetUpdateable = propertyValue; break; + case "AspNetCompiler.Debug": aspNetCompilerParameters.aspNetDebug = propertyValue; break; + case "AspNetCompiler.KeyFile": aspNetCompilerParameters.aspNetKeyFile = propertyValue; break; + case "AspNetCompiler.KeyContainer": aspNetCompilerParameters.aspNetKeyContainer = propertyValue; break; + case "AspNetCompiler.DelaySign": aspNetCompilerParameters.aspNetDelaySign = propertyValue; break; + case "AspNetCompiler.AllowPartiallyTrustedCallers": aspNetCompilerParameters.aspNetAPTCA = propertyValue; break; + case "AspNetCompiler.FixedNames": aspNetCompilerParameters.aspNetFixedNames = propertyValue; break; } // Store the updated parameters struct back into the hashtable by configuration name. @@ -1511,7 +1500,7 @@ internal void ParseFirstProjectLine( string firstLine, ProjectInSolution proj) { - Match match = s_crackProjectLine.Value.Match(firstLine); + Match match = CrackProjectLineRegex.Match(firstLine); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseProjectError"); @@ -1524,7 +1513,7 @@ internal void ParseFirstProjectLine( // This allows us to at least generate reasonable target names etc. instead of crashing. if (String.IsNullOrEmpty(proj.ProjectName)) { - proj.ProjectName = "EmptyProjectName." + Guid.NewGuid(); + proj.ProjectName = $"EmptyProjectName.{Guid.NewGuid()}"; } // Validate project relative path @@ -1611,7 +1600,7 @@ internal void ParseNestedProjects() continue; } - Match match = s_crackPropertyLine.Value.Match(str); + Match match = CrackPropertyLineRegex.Match(str); ProjectFileErrorUtilities.VerifyThrowInvalidProjectFile(match.Success, "SubCategoryForSolutionParsingErrors", new BuildEventFileInfo(FullPath, _currentLineNumber, 0), "SolutionParseNestedProjectError"); @@ -1770,16 +1759,14 @@ internal void ProcessProjectConfigurationSection(Dictionary rawP { // The "ActiveCfg" entry defines the active project configuration in the given solution configuration // This entry must be present for every possible solution configuration/project combination. - string entryNameActiveConfig = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.ActiveCfg", - project.ProjectGuid, solutionConfiguration.FullName); + string entryNameActiveConfig = $"{project.ProjectGuid}.{solutionConfiguration.FullName}.ActiveCfg"; // The "Build.0" entry tells us whether to build the project configuration in the given solution configuration. // Technically, it specifies a configuration name of its own which seems to be a remnant of an initial, // more flexible design of solution configurations (as well as the '.0' suffix - no higher values are ever used). // The configuration name is not used, and the whole entry means "build the project configuration" // if it's present in the solution file, and "don't build" if it's not. - string entryNameBuild = string.Format(CultureInfo.InvariantCulture, "{0}.{1}.Build.0", - project.ProjectGuid, solutionConfiguration.FullName); + string entryNameBuild = $"{project.ProjectGuid}.{solutionConfiguration.FullName}.Build.0"; if (rawProjectConfigurationsEntries.TryGetValue(entryNameActiveConfig, out string configurationPlatform)) { @@ -1905,6 +1892,6 @@ internal string GetProjectRelativePathByGuid(string projectGuid) return null; } - #endregion +#endregion } // class SolutionFile } // namespace Microsoft.Build.Construction diff --git a/src/Build/Construction/Solution/SolutionProjectGenerator.cs b/src/Build/Construction/Solution/SolutionProjectGenerator.cs index d535d1c73c0..e51789bbb38 100644 --- a/src/Build/Construction/Solution/SolutionProjectGenerator.cs +++ b/src/Build/Construction/Solution/SolutionProjectGenerator.cs @@ -860,7 +860,7 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio traversalProject.ToolsVersion = wrapperProjectToolsVersion; traversalProject.DefaultTargets = "Build"; traversalProject.InitialTargets = "ValidateSolutionConfiguration;ValidateToolsVersions;ValidateProjects"; - traversalProject.FullPath = _solutionFile.FullPath + ".metaproj"; + traversalProject.FullPath = $"{_solutionFile.FullPath}.metaproj"; // Add default solution configuration/platform names in case the user doesn't specify them on the command line AddConfigurationPlatformDefaults(traversalProject); @@ -982,7 +982,7 @@ private ProjectInstance CreateTraversalInstance(string wrapperProjectToolsVersio // For debugging purposes: some information is lost when evaluating into a project instance, // so make it possible to see what we have at this point. string path = traversalProject.FullPath; - string metaprojectPath = _solutionFile.FullPath + ".metaproj.tmp"; + string metaprojectPath = $"{_solutionFile.FullPath}.metaproj.tmp"; EmitMetaproject(traversalProject, metaprojectPath); traversalProject.FullPath = path; @@ -1349,7 +1349,7 @@ private static void AddMetaprojectTargetForManagedProject(ProjectInstance traver string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { - outputItemAsItem = "@(" + outputItem + ")"; + outputItemAsItem = $"@({outputItem})"; } ProjectTargetInstance target = metaprojectInstance.AddTarget(targetName ?? "Build", String.Empty, String.Empty, outputItemAsItem, null, String.Empty, String.Empty, String.Empty, String.Empty, false /* legacy target returns behaviour */); @@ -1396,7 +1396,7 @@ private static void AddProjectBuildTask(ProjectInstance traversalProject, Projec /// private void AddMetaprojectBuildTask(ProjectInSolution project, ProjectTargetInstance target, string targetToBuild, string outputItem) { - ProjectTaskInstance task = target.AddTask("MSBuild", Strings.WeakIntern("'%(ProjectReference.Identity)' == '" + GetMetaprojectName(project) + "'"), String.Empty); + ProjectTaskInstance task = target.AddTask("MSBuild", Strings.WeakIntern($"'%(ProjectReference.Identity)' == '{GetMetaprojectName(project)}'"), String.Empty); task.SetParameter("Projects", "@(ProjectReference)"); if (targetToBuild != null) @@ -1979,7 +1979,7 @@ private static void AddTraversalReferencesTarget(ProjectInstance traversalProjec string outputItemAsItem = null; if (!String.IsNullOrEmpty(outputItem)) { - outputItemAsItem = "@(" + outputItem + ")"; + outputItemAsItem = $"@({outputItem})"; } string correctedTargetName = targetName ?? "Build"; @@ -2052,13 +2052,13 @@ private void AddTraversalTargetForProject(ProjectInstance traversalProject, Proj if (!String.IsNullOrEmpty(outputItem)) { outputItemName = MakeIntoSafeItemName(baseProjectName) + outputItem; - outputItemAsItem = "@(" + outputItemName + ")"; + outputItemAsItem = $"@({outputItemName})"; } ProjectTargetInstance targetElement = traversalProject.AddTarget(actualTargetName, null, null, outputItemAsItem, null, null, null, null, null, false /* legacy target returns behaviour */); if (canBuildDirectly) { - AddProjectBuildTask(traversalProject, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", "'%(ProjectReference.Identity)' == '" + EscapingUtilities.Escape(project.AbsolutePath) + "'", outputItemName); + AddProjectBuildTask(traversalProject, projectConfiguration, targetElement, targetToBuild, "@(ProjectReference)", $"'%(ProjectReference.Identity)' == '{EscapingUtilities.Escape(project.AbsolutePath)}'", outputItemName); } else { diff --git a/src/Build/Definition/Project.cs b/src/Build/Definition/Project.cs index 2401d4935a1..1ac8dde6d77 100644 --- a/src/Build/Definition/Project.cs +++ b/src/Build/Definition/Project.cs @@ -2,6 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Buffers; using System.Collections.Generic; using System.Collections.Immutable; using System.Diagnostics; @@ -56,7 +57,12 @@ public class Project : ILinkableObject /// /// * and ? are invalid file name characters, but they occur in globs as wild cards. /// - private static readonly char[] s_invalidGlobChars = FileUtilities.InvalidFileNameChars.Where(c => c != '*' && c != '?' && c != '/' && c != '\\' && c != ':').ToArray(); +#if NET + private static readonly SearchValues s_invalidGlobChars = SearchValues.Create( +#else + private static readonly char[] s_invalidGlobChars = ( +#endif + FileUtilities.InvalidFileNameCharsArray.Where(c => c is not ('*' or '?' or '/' or '\\' or ':')).ToArray()); /// /// Context to log messages and events in. @@ -2618,7 +2624,7 @@ private GlobResult BuildGlobResultFromIncludeItem(ProjectItemElement itemElement { var includeItemspec = new EvaluationItemSpec(itemElement.Include, _data.Expander, itemElement.IncludeLocation, itemElement.ContainingProject.DirectoryPath); - ImmutableArray includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment && f.TextFragment.IndexOfAny(s_invalidGlobChars) == -1).ToImmutableArray(); + ItemSpecFragment[] includeGlobFragments = includeItemspec.Fragments.Where(f => f is GlobFragment && f.TextFragment.AsSpan().IndexOfAny(s_invalidGlobChars) < 0).ToArray(); if (includeGlobFragments.Length == 0) { return null; @@ -3809,7 +3815,7 @@ internal void Initialize(IDictionary globalProperties, string to // Cause the project to be actually loaded into the collection, and register for // rename notifications so we can subsequently update the collection. - _renameHandler = (string oldFullPath) => ProjectCollection.OnAfterRenameLoadedProject(oldFullPath, Owner); + _renameHandler = (oldFullPath) => ProjectCollection.OnAfterRenameLoadedProject(oldFullPath, Owner); Xml.OnAfterProjectRename += _renameHandler; Xml.OnProjectXmlChanged += ProjectRootElement_ProjectXmlChangedHandler; diff --git a/src/Build/Definition/ProjectItemDefinition.cs b/src/Build/Definition/ProjectItemDefinition.cs index 5e79cf18d51..8fa69153f7e 100644 --- a/src/Build/Definition/ProjectItemDefinition.cs +++ b/src/Build/Definition/ProjectItemDefinition.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Linq; using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.ObjectModelRemoting; diff --git a/src/Build/Definition/Toolset.cs b/src/Build/Definition/Toolset.cs index bd84fed6708..9e2444e5bc1 100644 --- a/src/Build/Definition/Toolset.cs +++ b/src/Build/Definition/Toolset.cs @@ -13,14 +13,15 @@ using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Execution; +#if NET using Microsoft.Build.Framework; +#endif using Microsoft.Build.Internal; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #if FEATURE_WIN32_REGISTRY using Microsoft.Win32; #endif -using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; using ObjectModel = System.Collections.ObjectModel; using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; diff --git a/src/Build/Definition/ToolsetConfigurationReader.cs b/src/Build/Definition/ToolsetConfigurationReader.cs index eb461785d00..204de4952f8 100644 --- a/src/Build/Definition/ToolsetConfigurationReader.cs +++ b/src/Build/Definition/ToolsetConfigurationReader.cs @@ -207,7 +207,7 @@ protected override IEnumerable GetSubToolsetPropertyD protected override Dictionary GetProjectImportSearchPathsTable(string toolsVersion, string os) { Dictionary kindToPathsCache; - var key = toolsVersion + ":" + os; + var key = $"{toolsVersion}:{os}"; if (_projectImportSearchPathsCache.TryGetValue(key, out kindToPathsCache)) { return kindToPathsCache; diff --git a/src/Build/Definition/ToolsetLocalReader.cs b/src/Build/Definition/ToolsetLocalReader.cs index dacbbb6e8b0..68fcb2a5d51 100644 --- a/src/Build/Definition/ToolsetLocalReader.cs +++ b/src/Build/Definition/ToolsetLocalReader.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Collections.Generic; -using System.Linq; using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Execution; diff --git a/src/Build/Definition/ToolsetReader.cs b/src/Build/Definition/ToolsetReader.cs index fdc817b930c..85692818736 100644 --- a/src/Build/Definition/ToolsetReader.cs +++ b/src/Build/Definition/ToolsetReader.cs @@ -27,11 +27,6 @@ namespace Microsoft.Build.Evaluation /// internal abstract class ToolsetReader { - /// - /// The global properties used to read the toolset. - /// - private PropertyDictionary _globalProperties; - /// /// The environment properties used to read the toolset. /// @@ -45,7 +40,6 @@ protected ToolsetReader( PropertyDictionary globalProperties) { _environmentProperties = environmentProperties; - _globalProperties = globalProperties; } /// @@ -686,22 +680,26 @@ private MSBuildExtensionsPathReferenceKind(string value) /// public static MSBuildExtensionsPathReferenceKind FindIn(string expression) { - if (expression.IndexOf("$(MSBuildExtensionsPath)") >= 0) - { - return MSBuildExtensionsPathReferenceKind.Default; - } - - if (expression.IndexOf("$(MSBuildExtensionsPath32)") >= 0) + const string PathBase = "$(MSBuildExtensionsPath"; + int pos = expression.IndexOf(PathBase, StringComparison.Ordinal); + if (pos >= 0) { - return MSBuildExtensionsPathReferenceKind.Path32; - } - - if (expression.IndexOf("$(MSBuildExtensionsPath64)") >= 0) - { - return MSBuildExtensionsPathReferenceKind.Path64; + ReadOnlySpan remainder = expression.AsSpan(pos + PathBase.Length); + if (remainder.StartsWith(")".AsSpan())) + { + return Default; + } + else if (remainder.StartsWith("32)".AsSpan())) + { + return Path32; + } + else if (remainder.StartsWith("64)".AsSpan())) + { + return Path64; + } } - return MSBuildExtensionsPathReferenceKind.None; + return None; } } } diff --git a/src/Build/Definition/ToolsetRegistryReader.cs b/src/Build/Definition/ToolsetRegistryReader.cs index 8e272113322..33ec20b8e26 100644 --- a/src/Build/Definition/ToolsetRegistryReader.cs +++ b/src/Build/Definition/ToolsetRegistryReader.cs @@ -3,7 +3,6 @@ #if FEATURE_WIN32_REGISTRY -using System; using System.Collections.Generic; using Microsoft.Build.Collections; using Microsoft.Build.Construction; diff --git a/src/Build/ElementLocation/ElementLocation.cs b/src/Build/ElementLocation/ElementLocation.cs index 8ca5594c946..29879ac6180 100644 --- a/src/Build/ElementLocation/ElementLocation.cs +++ b/src/Build/ElementLocation/ElementLocation.cs @@ -25,7 +25,7 @@ public abstract class ElementLocation : IElementLocation, ITranslatable, IImmuta /// /// The singleton empty element location. /// - private static ElementLocation s_emptyElementLocation = new SmallElementLocation(null, 0, 0); + private static readonly ElementLocation s_emptyElementLocation = new SmallElementLocation(null, 0, 0); /// /// The file from which this particular element originated. It may @@ -214,7 +214,7 @@ private static string GetLocationString(string file, int line, int column) } else if (line != 0) { - locationString = file + " (" + line + ")"; + locationString = $"{file} ({line})"; } else { diff --git a/src/Build/ElementLocation/XmlDocumentWithLocation.cs b/src/Build/ElementLocation/XmlDocumentWithLocation.cs index cfa910da6a8..eb8c3356873 100644 --- a/src/Build/ElementLocation/XmlDocumentWithLocation.cs +++ b/src/Build/ElementLocation/XmlDocumentWithLocation.cs @@ -27,7 +27,7 @@ internal class XmlDocumentWithLocation : XmlDocument /// /// Used to cache tag names in loaded files. /// - private static NameTable s_nameTable = new XmlNameTableThreadSafe(); + private static readonly NameTable s_nameTable = new XmlNameTableThreadSafe(); /// /// Whether we can selectively load as read-only (eg just when in program files directory) diff --git a/src/Build/Errors/InvalidToolsetDefinitionException.cs b/src/Build/Errors/InvalidToolsetDefinitionException.cs index 8dbae22aa4a..3176644c32d 100644 --- a/src/Build/Errors/InvalidToolsetDefinitionException.cs +++ b/src/Build/Errors/InvalidToolsetDefinitionException.cs @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Framework.BuildException; -using Microsoft.Build.Shared; using System; -using System.Runtime.Serialization; using System.Collections.Generic; +using System.Runtime.Serialization; +using Microsoft.Build.Framework.BuildException; +using Microsoft.Build.Shared; #if FEATURE_SECURITY_PERMISSIONS using System.Security.Permissions; #endif @@ -18,7 +18,7 @@ namespace Microsoft.Build.Exceptions /// Exception subclass that ToolsetReaders should throw. /// [Serializable] - public class InvalidToolsetDefinitionException : BuildExceptionBase + public class InvalidToolsetDefinitionException : BuildExceptionBase // CodeQL [SM02227] The dangerous method is called only in debug build. It's safe for release build. { /// /// The MSBuild error code corresponding with this exception. diff --git a/src/Build/Evaluation/ConditionEvaluator.cs b/src/Build/Evaluation/ConditionEvaluator.cs index 67455f8b4c4..cbe29768a60 100644 --- a/src/Build/Evaluation/ConditionEvaluator.cs +++ b/src/Build/Evaluation/ConditionEvaluator.cs @@ -11,7 +11,6 @@ using Microsoft.Build.Shared.FileSystem; using BuildEventContext = Microsoft.Build.Framework.BuildEventContext; using ElementLocation = Microsoft.Build.Construction.ElementLocation; -using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; namespace Microsoft.Build.Evaluation @@ -166,7 +165,7 @@ public ConcurrentStack GetOrAdd(string condition, Func s_cachedExpressionTrees = new ConcurrentDictionary(); + private static readonly ConcurrentDictionary s_cachedExpressionTrees = new ConcurrentDictionary(); /// /// For debugging leaks, a way to disable caching expression trees, to reduce noise diff --git a/src/Build/Evaluation/Conditionals/AndExpressionNode.cs b/src/Build/Evaluation/Conditionals/AndExpressionNode.cs index 1303731a2f6..f460c40142f 100644 --- a/src/Build/Evaluation/Conditionals/AndExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/AndExpressionNode.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/CharacterUtilities.cs b/src/Build/Evaluation/Conditionals/CharacterUtilities.cs index 06e7345b1b0..ed5502e91ca 100644 --- a/src/Build/Evaluation/Conditionals/CharacterUtilities.cs +++ b/src/Build/Evaluation/Conditionals/CharacterUtilities.cs @@ -22,15 +22,9 @@ internal static bool IsSimpleStringChar(char candidate) return IsSimpleStringStart(candidate) || char.IsDigit(candidate); } - internal static bool IsHexAlphabetic(char candidate) - { - return candidate == 'a' || candidate == 'b' || candidate == 'c' || candidate == 'd' || candidate == 'e' || candidate == 'f' || - candidate == 'A' || candidate == 'B' || candidate == 'C' || candidate == 'D' || candidate == 'E' || candidate == 'F'; - } - internal static bool IsHexDigit(char candidate) { - return char.IsDigit(candidate) || IsHexAlphabetic(candidate); + return char.IsDigit(candidate) || ((uint)((candidate | 0x20) - 'a') <= 'f' - 'a'); } } } diff --git a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs index 6d7b27eb243..61769eb3da9 100644 --- a/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/FunctionCallExpressionNode.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; diff --git a/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs b/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs index e8a7858d415..a701cade16d 100644 --- a/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/GenericExpressionNode.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs b/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs index f398e8d740a..57685a74ba9 100644 --- a/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/MultipleComparisonExpressionNode.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/NotExpressionNode.cs b/src/Build/Evaluation/Conditionals/NotExpressionNode.cs index 66bfc64c1c2..093599928cd 100644 --- a/src/Build/Evaluation/Conditionals/NotExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/NotExpressionNode.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs b/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs index fb6cc3b3a71..ecc9cb4995c 100644 --- a/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/NumericComparisonExpressionNode.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs b/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs index 55cf768c60b..9d3e1ee1193 100644 --- a/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/OperatorExpressionNode.cs @@ -2,8 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Collections; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/OrExpressionNode.cs b/src/Build/Evaluation/Conditionals/OrExpressionNode.cs index dae691252e2..03852261ebe 100644 --- a/src/Build/Evaluation/Conditionals/OrExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/OrExpressionNode.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System.Diagnostics; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Conditionals/Scanner.cs b/src/Build/Evaluation/Conditionals/Scanner.cs index a9d31698a28..718ae73d23d 100644 --- a/src/Build/Evaluation/Conditionals/Scanner.cs +++ b/src/Build/Evaluation/Conditionals/Scanner.cs @@ -692,11 +692,11 @@ private bool ParseRemaining() private bool ParseSimpleStringOrFunction(int start) { SkipSimpleStringChars(); - if (string.Equals(_expression.Substring(start, _parsePoint - start), "and", StringComparison.OrdinalIgnoreCase)) + if (_expression.AsSpan(start, _parsePoint - start).Equals("and".AsSpan(), StringComparison.OrdinalIgnoreCase)) { _lookahead = Token.And; } - else if (string.Equals(_expression.Substring(start, _parsePoint - start), "or", StringComparison.OrdinalIgnoreCase)) + else if (_expression.AsSpan(start, _parsePoint - start).Equals("or".AsSpan(), StringComparison.OrdinalIgnoreCase)) { _lookahead = Token.Or; } diff --git a/src/Build/Evaluation/Conditionals/StringExpressionNode.cs b/src/Build/Evaluation/Conditionals/StringExpressionNode.cs index 9e528acc5f4..9e4264af604 100644 --- a/src/Build/Evaluation/Conditionals/StringExpressionNode.cs +++ b/src/Build/Evaluation/Conditionals/StringExpressionNode.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/Evaluator.cs b/src/Build/Evaluation/Evaluator.cs index ebd4e1a7383..b061b4db88a 100644 --- a/src/Build/Evaluation/Evaluator.cs +++ b/src/Build/Evaluation/Evaluator.cs @@ -809,7 +809,7 @@ private void Evaluate() { if (!String.Equals(entry.Name, "currentsolutionconfigurationcontents", StringComparison.OrdinalIgnoreCase)) { - propertyDump += entry.Name + "=" + entry.EvaluatedValue + "\n"; + propertyDump += $"{entry.Name}={entry.EvaluatedValue}\n"; } } @@ -1886,19 +1886,17 @@ static string EvaluateProperty(string value, IElementLocation location, // Creates a project to set the properties and include the items from an SdkResult private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) { - int propertiesAndItemsHash; - -#if NETCOREAPP - HashCode hash = new HashCode(); +#if NET + HashCode hash = default; #else - propertiesAndItemsHash = -849885975; + int propertiesAndItemsHash = -849885975; #endif if (sdkResult.PropertiesToAdd != null) { foreach (var property in sdkResult.PropertiesToAdd) { -#if NETCOREAPP +#if NET hash.Add(property.Key); hash.Add(property.Value); #else @@ -1911,7 +1909,7 @@ private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) { foreach (var item in sdkResult.ItemsToAdd) { -#if NETCOREAPP +#if NET hash.Add(item.Key); hash.Add(item.Value); #else @@ -1922,8 +1920,8 @@ private ProjectRootElement CreateProjectForSdkResult(SdkResult sdkResult) } } -#if NETCOREAPP - propertiesAndItemsHash = hash.ToHashCode(); +#if NET + int propertiesAndItemsHash = hash.ToHashCode(); #endif // Generate a unique filename for the generated project for each unique set of properties and items that ends like ".SdkResolver.{propertiesAndItemsHash}.proj". @@ -2141,7 +2139,7 @@ private LoadImportsResult ExpandAndLoadImportsFromUnescapedImportExpression(stri // If neither file involved is the project itself, append its path in square brackets if (previouslyImportedAt.ContainingProject != _projectRootElement && importElement.ContainingProject != _projectRootElement) { - parenthesizedProjectLocation = "[" + _projectRootElement.FullPath + "]"; + parenthesizedProjectLocation = $"[{_projectRootElement.FullPath}]"; } // TODO: Detect if the duplicate import came from an SDK attribute _evaluationLoggingContext.LogWarning(null, new BuildEventFileInfo(importLocationInProject), "DuplicateImport", importFileUnescaped, previouslyImportedAt.Location.LocationString, parenthesizedProjectLocation); @@ -2582,7 +2580,7 @@ private void SetAllProjectsProperty() if (_lastModifiedProject != null) { P oldValue = _data.GetProperty(Constants.MSBuildAllProjectsPropertyName); - string streamImports = string.Join(";", _streamImports.ToArray()); + string streamImports = string.Join(";", _streamImports); _data.SetProperty( Constants.MSBuildAllProjectsPropertyName, oldValue == null diff --git a/src/Build/Evaluation/Expander.cs b/src/Build/Evaluation/Expander.cs index 84b34d5c67a..8e8f26a1233 100644 --- a/src/Build/Evaluation/Expander.cs +++ b/src/Build/Evaluation/Expander.cs @@ -24,8 +24,8 @@ using Microsoft.NET.StringTools; using Microsoft.Win32; using AvailableStaticMethods = Microsoft.Build.Internal.AvailableStaticMethods; -using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; using ParseArgs = Microsoft.Build.Evaluation.Expander.ArgumentParser; +using ReservedPropertyNames = Microsoft.Build.Internal.ReservedPropertyNames; using TaskItem = Microsoft.Build.Execution.ProjectItemInstance.TaskItem; using TaskItemFactory = Microsoft.Build.Execution.ProjectItemInstance.TaskItem.TaskItemFactory; @@ -279,17 +279,11 @@ private void FlushFirstValueIfNeeded() private static readonly char[] s_backtickChar = { '`' }; private static readonly char[] s_doubleQuoteChar = { '"' }; - /// - /// Those characters which indicate that an expression may contain expandable - /// expressions. - /// - private static char[] s_expandableChars = { '$', '%', '@' }; - /// /// The CultureInfo from the invariant culture. Used to avoid allocations for /// performing IndexOf etc. /// - private static CompareInfo s_invariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo; + private static readonly CompareInfo s_invariantCompareInfo = CultureInfo.InvariantCulture.CompareInfo; /// /// Properties to draw on for expansion. @@ -454,7 +448,7 @@ internal PropertiesUseTracker PropertiesUseTracker /// internal static bool ExpressionMayContainExpandableExpressions(string expression) { - return expression.IndexOfAny(s_expandableChars) > -1; + return expression.AsSpan().IndexOfAny('$', '%', '@') >= 0; } /// @@ -1110,7 +1104,12 @@ _metadata is IItemTypeDefinition itemMetadata && if (IsTruncationEnabled(_options) && metadataValue.Length > CharacterLimitPerExpansion) { - metadataValue = metadataValue.Substring(0, CharacterLimitPerExpansion - 3) + "..."; + metadataValue = +#if NET + $"{metadataValue.AsSpan(0, CharacterLimitPerExpansion - 3)}..."; +#else + $"{metadataValue.Substring(0, CharacterLimitPerExpansion - 3)}..."; +#endif } } @@ -1313,7 +1312,12 @@ internal static object ExpandPropertiesLeaveTypedAndEscaped( var value = propertyValue.ToString(); if (value.Length > CharacterLimitPerExpansion) { - propertyValue = value.Substring(0, CharacterLimitPerExpansion - 3) + "..."; + propertyValue = +#if NET + $"{value.AsSpan(0, CharacterLimitPerExpansion - 3)}..."; +#else + $"{value.Substring(0, CharacterLimitPerExpansion - 3)}..."; +#endif } } @@ -1730,7 +1734,7 @@ private static string ExpandRegistryValue(string registryExpression, IElementLoc } catch (Exception ex) when (!ExceptionHandling.NotExpectedRegistryException(ex)) { - ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidRegistryPropertyExpression", "$(" + registryExpression + ")", ex.Message); + ProjectErrorUtilities.ThrowInvalidProject(elementLocation, "InvalidRegistryPropertyExpression", $"$({registryExpression})", ex.Message); } } @@ -1858,7 +1862,7 @@ internal static ExpressionShredder.ItemExpressionCapture ExpandSingleItemVectorE } List matches; - if (expression.IndexOf('@') == -1) + if (!expression.Contains('@')) { return null; } @@ -2239,7 +2243,7 @@ internal static class IntrinsicItemFunctions /// /// A cache of previously created item function delegates. /// - private static ConcurrentDictionary s_transformFunctionDelegateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_transformFunctionDelegateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// Delegate that represents the signature of all item transformation functions @@ -2254,7 +2258,7 @@ internal static class IntrinsicItemFunctions internal static ItemTransformFunction GetItemTransformFunction(IElementLocation elementLocation, string functionName, Type itemType) { ItemTransformFunction transformFunction = null; - string qualifiedFunctionName = itemType.FullName + "::" + functionName; + string qualifiedFunctionName = $"{itemType.FullName}::{functionName}"; // We may have seen this delegate before, if so grab the one we already created if (!s_transformFunctionDelegateCache.TryGetValue(qualifiedFunctionName, out transformFunction)) @@ -2631,7 +2635,7 @@ internal static IEnumerable> Metadata(Expander exp { // It may be that the itemspec has unescaped ';'s in it so we need to split here to handle // that case. - if (metadataValue.IndexOf(';') >= 0) + if (metadataValue.Contains(';')) { var splits = ExpressionShredder.SplitSemiColonSeparatedList(metadataValue); @@ -3095,36 +3099,23 @@ private static partial class RegularExpressions * description of an item vector changes, the expressions must be updated in both places. *************************************************************************************************************************/ - - -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(ItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] - internal static partial Regex ItemMetadataPattern(); + internal static partial Regex ItemMetadataRegex { get; } #else /// /// Regular expression used to match item metadata references embedded in strings. /// For example, %(Compile.DependsOn) or %(DependsOn). /// - internal static readonly Lazy ItemMetadataPattern = new Lazy( - () => new Regex(ItemMetadataSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); -#endif + internal static Regex ItemMetadataRegex => s_itemMetadataRegex ??= + new Regex(ItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); - internal static Regex ItemMetadataRegex - { - get - { -#if NET7_0_OR_GREATER - return ItemMetadataPattern(); -#else - return ItemMetadataPattern.Value; + internal static Regex s_itemMetadataRegex; #endif - } - } - /// - /// Name of the group matching the "name" of a metadatum. - /// + /// + /// Name of the group matching the "name" of a metadatum. + /// internal const string NameGroup = "NAME"; /// @@ -3143,29 +3134,19 @@ internal static Regex ItemMetadataRegex ItemVectorWithTransformLHS + @")" + ItemMetadataSpecification + @"(?!" + ItemVectorWithTransformRHS + @"))"; -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(NonTransformItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] - internal static partial Regex NonTransformItemMetadataPattern(); + internal static partial Regex NonTransformItemMetadataRegex { get; } #else /// /// regular expression used to match item metadata references outside of item vector transforms. /// /// PERF WARNING: this Regex is complex and tends to run slowly. - internal static readonly Lazy NonTransformItemMetadataPattern = new Lazy( - () => new Regex(NonTransformItemMetadataSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); -#endif - internal static Regex NonTransformItemMetadataRegex - { - get - { -#if NET7_0_OR_GREATER - return NonTransformItemMetadataPattern(); -#else - return NonTransformItemMetadataPattern.Value; + private static Regex s_nonTransformItemMetadataPattern; + + internal static Regex NonTransformItemMetadataRegex => s_nonTransformItemMetadataPattern ??= + new Regex(NonTransformItemMetadataSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); #endif - } - } /// /// Complete description of an item metadata reference, including the optional qualifying item type. @@ -4114,8 +4095,8 @@ private static object[] CoerceArguments(object[] args, ParameterInfo[] parameter else if (parameters[n].ParameterType.GetTypeInfo().IsEnum && args[n] is string v && v.Contains(".")) { Type enumType = parameters[n].ParameterType; - string typeLeafName = enumType.Name + "."; - string typeFullName = enumType.FullName + "."; + string typeLeafName = $"{enumType.Name}."; + string typeFullName = $"{enumType.FullName}."; // Enum.parse expects commas between enum components // We'll support the C# type | syntax too @@ -4200,11 +4181,11 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn } if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { - return "[" + typeName + "]::" + name + "(" + parameters + ")"; + return $"[{typeName}]::{name}({parameters})"; } else { - return "[" + typeName + "]::" + name; + return $"[{typeName}]::{name}"; } } else @@ -4213,11 +4194,11 @@ private string GenerateStringOfMethodExecuted(string expression, object objectIn if ((_bindingFlags & BindingFlags.InvokeMethod) == BindingFlags.InvokeMethod) { - return propertyValue + "." + name + "(" + parameters + ")"; + return $"{propertyValue}.{name}({parameters})"; } else { - return propertyValue + "." + name; + return $"{propertyValue}.{name}"; } } } diff --git a/src/Build/Evaluation/Expander/ArgumentParser.cs b/src/Build/Evaluation/Expander/ArgumentParser.cs index ca4039aae5d..c2dd113d5ff 100644 --- a/src/Build/Evaluation/Expander/ArgumentParser.cs +++ b/src/Build/Evaluation/Expander/ArgumentParser.cs @@ -2,11 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Globalization; +#if NETFRAMEWORK using System.Linq; -using System.Text; -using System.Threading.Tasks; +#endif namespace Microsoft.Build.Evaluation.Expander { diff --git a/src/Build/Evaluation/Expander/WellKnownFunctions.cs b/src/Build/Evaluation/Expander/WellKnownFunctions.cs index 72f8b247933..d6bd9de96d3 100644 --- a/src/Build/Evaluation/Expander/WellKnownFunctions.cs +++ b/src/Build/Evaluation/Expander/WellKnownFunctions.cs @@ -2,16 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; using System.Linq; -using System.Reflection; using System.Runtime.CompilerServices; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; using Microsoft.Build.BackEnd.Logging; -using Microsoft.Build.Evaluation; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; @@ -236,7 +231,7 @@ internal static bool TryExecuteStringFunction(string methodName, out object? ret { if (ParseArgs.TryGetArg(args, out string? arg0) && arg0 != null) { - returnVal = text.IndexOfAny(arg0.ToCharArray()); + returnVal = text.AsSpan().IndexOfAny(arg0.AsSpan()); return true; } } @@ -262,7 +257,7 @@ internal static bool TryExecuteStringFunction(string methodName, out object? ret { if (ParseArgs.TryGetArg(args, out string? arg0) && arg0 != null) { - returnVal = text.LastIndexOfAny(arg0.ToCharArray()); + returnVal = text.AsSpan().LastIndexOfAny(arg0.AsSpan()); return true; } } diff --git a/src/Build/Evaluation/ExpressionShredder.cs b/src/Build/Evaluation/ExpressionShredder.cs index fd102dff143..a7d9e48b9a5 100644 --- a/src/Build/Evaluation/ExpressionShredder.cs +++ b/src/Build/Evaluation/ExpressionShredder.cs @@ -428,7 +428,7 @@ private static void GetReferencedItemNamesAndMetadata(string expression, int sta itemName = firstPart; metadataName = expression.Substring(startOfText, i - startOfText); - qualifiedMetadataName = itemName + "." + metadataName; + qualifiedMetadataName = $"{itemName}.{metadataName}"; } else { diff --git a/src/Build/Evaluation/IItemFactory.cs b/src/Build/Evaluation/IItemFactory.cs index 9624d90a562..d0f26ffd63b 100644 --- a/src/Build/Evaluation/IItemFactory.cs +++ b/src/Build/Evaluation/IItemFactory.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.Build.Construction; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/IntrinsicFunctions.cs b/src/Build/Evaluation/IntrinsicFunctions.cs index 032f95a251c..fc86d54e613 100644 --- a/src/Build/Evaluation/IntrinsicFunctions.cs +++ b/src/Build/Evaluation/IntrinsicFunctions.cs @@ -4,9 +4,13 @@ using System; using System.Collections.Generic; using System.IO; +#if NETFRAMEWORK +using System.Linq; +#endif using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using System.Runtime.Versioning; +using System.Security.Cryptography; using System.Text; using System.Text.RegularExpressions; using Microsoft.Build.BackEnd.Logging; @@ -18,8 +22,6 @@ using Microsoft.Build.Utilities; using Microsoft.NET.StringTools; using Microsoft.Win32; -using System.Linq; - // Needed for DoesTaskHostExistForParameters using NodeProviderOutOfProcTaskHost = Microsoft.Build.BackEnd.NodeProviderOutOfProcTaskHost; @@ -40,24 +42,13 @@ internal static partial class IntrinsicFunctions private static readonly object[] DefaultRegistryViews = [RegistryView.Default]; #pragma warning restore CA1416 -#if NET7_0_OR_GREATER +#if NET [GeneratedRegex(RegistrySdkSpecification, RegexOptions.IgnoreCase)] - private static partial Regex RegistrySdkPattern(); -#else - private static readonly Lazy RegistrySdkPattern = new Lazy(() => new Regex(RegistrySdkSpecification, RegexOptions.IgnoreCase)); -#endif - - private static Regex RegistrySdkRegex - { - get - { -#if NET7_0_OR_GREATER - return RegistrySdkPattern(); + private static partial Regex RegistrySdkRegex { get; } #else - return RegistrySdkPattern.Value; + private static Regex s_registrySdkRegex; + private static Regex RegistrySdkRegex => s_registrySdkRegex ??= new Regex(RegistrySdkSpecification, RegexOptions.IgnoreCase); #endif - } - } private static readonly Lazy NuGetFramework = new Lazy(() => NuGetFrameworkWrapper.CreateInstance()); @@ -284,8 +275,8 @@ internal static object GetRegistryValueFromView(string keyName, string valueName { if (viewObject is string viewAsString) { - string typeLeafName = typeof(RegistryView).Name + "."; - string typeFullName = typeof(RegistryView).FullName + "."; + string typeLeafName = $"{typeof(RegistryView).Name}."; + string typeFullName = $"{typeof(RegistryView).FullName}."; // We'll allow the user to specify the leaf or full type name on the RegistryView enum viewAsString = viewAsString.Replace(typeFullName, "").Replace(typeLeafName, ""); @@ -466,7 +457,12 @@ internal static object StableStringHash(string toHash, StringHashingAlgorithm al private static string CalculateSha256(string toHash) { - using var sha = System.Security.Cryptography.SHA256.Create(); +#if NET + Span hash = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(Encoding.UTF8.GetBytes(toHash), hash); + return Convert.ToHexStringLower(hash); +#else + using var sha = SHA256.Create(); var hashResult = new StringBuilder(); foreach (byte theByte in sha.ComputeHash(Encoding.UTF8.GetBytes(toHash))) { @@ -474,6 +470,7 @@ private static string CalculateSha256(string toHash) } return hashResult.ToString(); +#endif } /// @@ -651,14 +648,15 @@ internal static string SubstringByAsciiChars(string input, int start, int length { return string.Empty; } + if (start + length > input.Length) { length = input.Length - start; } + StringBuilder sb = new StringBuilder(); - for (int i = start; i < start + length; i++) + foreach (char c in input.AsSpan(start, length)) { - char c = input[i]; if (c >= 32 && c <= 126 && !FileUtilities.InvalidFileNameChars.Contains(c)) { sb.Append(c); @@ -668,6 +666,7 @@ internal static string SubstringByAsciiChars(string input, int start, int length sb.Append('_'); } } + return sb.ToString(); } @@ -804,7 +803,7 @@ private static RegistryKey GetBaseKeyFromKeyName(string keyName, RegistryView vi } else { - subKeyName = keyName.Substring(i + 1, keyName.Length - i - 1); + subKeyName = keyName.Substring(i + 1); } return basekey; diff --git a/src/Build/Evaluation/ItemSpec.cs b/src/Build/Evaluation/ItemSpec.cs index 30400c410f6..26138e50b92 100644 --- a/src/Build/Evaluation/ItemSpec.cs +++ b/src/Build/Evaluation/ItemSpec.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Linq; -using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Globbing; using Microsoft.Build.Internal; using Microsoft.Build.Shared; diff --git a/src/Build/Evaluation/LazyItemEvaluator.ItemFactoryWrapper.cs b/src/Build/Evaluation/LazyItemEvaluator.ItemFactoryWrapper.cs index 99c7166189b..f83ef9635c8 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.ItemFactoryWrapper.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.ItemFactoryWrapper.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.Build.Construction; -using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs b/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs index ff6e6d700ee..3e9a03753da 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.OrderedItemDataCollection.cs @@ -41,7 +41,6 @@ internal Builder(ImmutableList.Builder listBuilder) #region IEnumerable implementation - private ImmutableList.Enumerator GetEnumerator() => _listBuilder.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => _listBuilder.GetEnumerator(); System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _listBuilder.GetEnumerator(); diff --git a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs index 771b0e6ce40..51d91cce6d7 100644 --- a/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs +++ b/src/Build/Evaluation/LazyItemEvaluator.RemoveOperation.cs @@ -29,7 +29,7 @@ public RemoveOperation(RemoveOperationBuilder builder, LazyItemEvaluator(builder.MatchOnMetadataOptions, _matchOnMetadata, _itemSpec); } @@ -48,7 +48,7 @@ protected override void ApplyImpl(OrderedItemDataCollection.Builder listBuilder, return; } - bool matchingOnMetadata = _matchOnMetadata.Any(); + bool matchingOnMetadata = !_matchOnMetadata.IsEmpty; if (!matchingOnMetadata) { if (ItemspecContainsASingleBareItemReference(_itemSpec, _itemElement.ItemType)) diff --git a/src/Build/Evaluation/Preprocessor.cs b/src/Build/Evaluation/Preprocessor.cs index 0d38472e450..d70e8327285 100644 --- a/src/Build/Evaluation/Preprocessor.cs +++ b/src/Build/Evaluation/Preprocessor.cs @@ -26,6 +26,9 @@ namespace Microsoft.Build.Evaluation /// internal class Preprocessor { + /// 140 equal signs. + private const string Equals140 = "============================================================================================================================================"; + /// /// Project to preprocess /// @@ -99,7 +102,7 @@ private XmlDocument Preprocess() if (!String.IsNullOrEmpty(_project.FullPath)) // Ignore in-memory projects { - destinationDocument.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n" + _project.FullPath.Replace("--", "__") + "\r\n" + new String('=', 140) + "\r\n")); + destinationDocument.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n{_project.FullPath.Replace("--", "__")}\r\n{Equals140}\r\n")); } CloneChildrenResolvingImports(outerDocument, destinationDocument); @@ -310,7 +313,7 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) } destination.AppendChild(destinationDocument.CreateComment( - $"\r\n{new String('=', 140)}\r\n{importTag}\r\n\r\n{resolved.FullPath.Replace("--", "__")}\r\n{new String('=', 140)}\r\n")); + $"\r\n{Equals140}\r\n{importTag}\r\n\r\n{resolved.FullPath.Replace("--", "__")}\r\n{Equals140}\r\n")); _filePaths.Push(resolved.FullPath); CloneChildrenResolvingImports(innerDocument, destination); @@ -318,11 +321,11 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) if (i < resolvedList.Count - 1) { - destination.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n \r\n" + new String('=', 140) + "\r\n")); + destination.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n \r\n{Equals140}\r\n")); } else { - destination.AppendChild(destinationDocument.CreateComment("\r\n" + new String('=', 140) + "\r\n \r\n\r\n" + _filePaths.Peek()?.Replace("--", "__") + "\r\n" + new String('=', 140) + "\r\n")); + destination.AppendChild(destinationDocument.CreateComment($"\r\n{Equals140}\r\n \r\n\r\n{_filePaths.Peek()?.Replace("--", "__")}\r\n{Equals140}\r\n")); } } @@ -339,7 +342,7 @@ private void CloneChildrenResolvingImports(XmlNode source, XmlNode destination) CloneChildrenResolvingImports(child, destination); - destination.AppendChild(destinationDocument.CreateComment("")); + destination.AppendChild(destinationDocument.CreateComment($"")); continue; } diff --git a/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs b/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs index 065f756fede..99f4bfdf8c6 100644 --- a/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs +++ b/src/Build/Evaluation/Profiler/EvaluationLocationMarkdownPrettyPrinter.cs @@ -38,14 +38,19 @@ protected override string NormalizeExpression(string description, EvaluationLoca return null; } - text = text.Replace(Separator, "\\" + Separator); + text = text.Replace(Separator, $"\\{Separator}"); if (text.Length > 100) { - text = text.Remove(100) + "..."; + text = +#if NET + $"{text.AsSpan(0, 100)}..."; +#else + $"{text.Remove(100)}..."; +#endif } - return '`' + text + '`'; + return $"`{text}`"; } } } diff --git a/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs b/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs index a1e8dc7f30d..ea8876e874f 100644 --- a/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs +++ b/src/Build/Evaluation/Profiler/EvaluationLocationPrettyPrinterBase.cs @@ -65,7 +65,7 @@ protected static string GetElementOrConditionText(string description, Evaluation var outerXml = description; outerXml = outerXml.Replace(@"xmlns=""http://schemas.microsoft.com/developer/msbuild/2003""", ""); - var newLineIndex = outerXml.IndexOfAny(['\r', '\n']); + var newLineIndex = outerXml.AsSpan().IndexOfAny('\r', '\n'); return newLineIndex == -1 ? outerXml : outerXml.Remove(newLineIndex); } @@ -74,9 +74,19 @@ protected static string GetElementOrConditionText(string description, Evaluation /// protected void AppendDefaultHeaderWithSeparator(StringBuilder stringBuilder, string separator) { - stringBuilder.AppendLine( - string.Join(separator, "Id", "ParentId", "Pass", "File", "Line #", "Expression", "Inc (ms)", "Inc (%)", "Exc (ms)", - "Exc (%)", "#", "Kind", "Bug")); + stringBuilder.Append("Id").Append(separator) + .Append("ParentId").Append(separator) + .Append("Pass").Append(separator) + .Append("File").Append(separator) + .Append("Line #").Append(separator) + .Append("Expression").Append(separator) + .Append("Inc (ms)").Append(separator) + .Append("Inc (%)").Append(separator) + .Append("Exc (ms)").Append(separator) + .Append("Exc (%)").Append(separator) + .Append('#').Append(separator) + .Append("Kind").Append(separator) + .Append("Bug").AppendLine(); } /// @@ -92,9 +102,9 @@ protected void AppendDefaultLocationWithSeparator(StringBuilder stringBuilder, T evaluationLocation.Line?.ToString() ?? string.Empty, NormalizeExpression(evaluationLocation.ElementDescription, evaluationLocation.Kind) ?? string.Empty, GetMilliseconds(profiledLocation.InclusiveTime), - GetPercentage(totalTime, profiledLocation.InclusiveTime) + "%", + $"{GetPercentage(totalTime, profiledLocation.InclusiveTime)}%", GetMilliseconds(profiledLocation.ExclusiveTime), - GetPercentage(totalTime, profiledLocation.ExclusiveTime) + "%", + $"{GetPercentage(totalTime, profiledLocation.ExclusiveTime)}%", profiledLocation.NumberOfHits, evaluationLocation.Kind + separator)); } diff --git a/src/Build/Evaluation/ProjectParser.cs b/src/Build/Evaluation/ProjectParser.cs index 3427a49260d..a71a572677d 100644 --- a/src/Build/Evaluation/ProjectParser.cs +++ b/src/Build/Evaluation/ProjectParser.cs @@ -577,7 +577,7 @@ private ProjectTargetElement ParseProjectTargetElement(XmlElementWithLocation el // Orcas compat: all target names are automatically unescaped string targetName = EscapingUtilities.UnescapeAll(ProjectXmlUtilities.GetAttributeValue(element, XMakeAttributes.name)); - int indexOfSpecialCharacter = targetName.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = targetName.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { ProjectErrorUtilities.ThrowInvalidProject(element.GetAttributeLocation(XMakeAttributes.name), "NameInvalid", targetName, targetName[indexOfSpecialCharacter]); diff --git a/src/Build/Evaluation/ProjectRootElementCache.cs b/src/Build/Evaluation/ProjectRootElementCache.cs index d70e5648f3e..c97ebad4bff 100644 --- a/src/Build/Evaluation/ProjectRootElementCache.cs +++ b/src/Build/Evaluation/ProjectRootElementCache.cs @@ -7,7 +7,6 @@ using System.Diagnostics; using System.Globalization; using System.IO; -using System.Linq; using System.Xml; using Microsoft.Build.Collections; using Microsoft.Build.Construction; @@ -79,7 +78,7 @@ internal class ProjectRootElementCache : ProjectRootElementCacheBase /// /// Whether the cache should log activity to the Debug.Out stream /// - private static bool s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1"; + private static readonly bool s_debugLogCacheActivity = Environment.GetEnvironmentVariable("MSBUILDDEBUGXMLCACHE") == "1"; /// /// Whether the cache should check file content for cache entry invalidation. @@ -87,7 +86,7 @@ internal class ProjectRootElementCache : ProjectRootElementCacheBase /// /// Value shall be true only in case of testing. Outside QA tests it shall be false. /// - private static bool s_сheckFileContent = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT")); + private static readonly bool s_сheckFileContent = !string.IsNullOrEmpty(Environment.GetEnvironmentVariable("MSBUILDCACHECHECKFILECONTENT")); #if DEBUG /// @@ -100,7 +99,7 @@ private struct ReentrancyGuard : IDisposable /// Shall be always 0 or 1. Reentrance to the Get function (value > 1) could lead to race condition. /// [ThreadStatic] - private static int s_getEntriesNumber = 0; + private static int s_getEntriesNumber; public ReentrancyGuard() { @@ -674,7 +673,7 @@ private void DebugTraceCache(string message, string param1) if (s_debugLogCacheActivity) { string prefix = OutOfProcNode.IsOutOfProcNode ? "C" : "P"; - Trace.WriteLine(prefix + " " + EnvironmentUtilities.CurrentProcessId + " | " + message + param1); + Trace.WriteLine($"{prefix} {Process.GetCurrentProcess().Id} | {message}{param1}"); } } } diff --git a/src/Build/Evaluation/StringMetadataTable.cs b/src/Build/Evaluation/StringMetadataTable.cs index 5e920a99c23..277c5d829d9 100644 --- a/src/Build/Evaluation/StringMetadataTable.cs +++ b/src/Build/Evaluation/StringMetadataTable.cs @@ -69,7 +69,7 @@ public string GetEscapedValueIfPresent(string itemType, string name) } else { - key = itemType + "." + name; + key = $"{itemType}.{name}"; } string value; diff --git a/src/Build/FileSystem/DirectoryCacheFileSystemWrapper.cs b/src/Build/FileSystem/DirectoryCacheFileSystemWrapper.cs index 9d259bda8c6..992e89967f0 100644 --- a/src/Build/FileSystem/DirectoryCacheFileSystemWrapper.cs +++ b/src/Build/FileSystem/DirectoryCacheFileSystemWrapper.cs @@ -1,12 +1,12 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Shared; -using Microsoft.Build.Shared.FileSystem; using System; using System.Collections.Generic; using System.IO; using System.Linq; +using Microsoft.Build.Shared; +using Microsoft.Build.Shared.FileSystem; #if FEATURE_MSIOREDIST using Path = Microsoft.IO.Path; diff --git a/src/Build/Globbing/MSBuildGlob.cs b/src/Build/Globbing/MSBuildGlob.cs index e6cb8ab1ac5..38915e44cd3 100644 --- a/src/Build/Globbing/MSBuildGlob.cs +++ b/src/Build/Globbing/MSBuildGlob.cs @@ -47,7 +47,7 @@ public GlobState(string globRoot, string fileSpec, bool isLegal, string fixedDir } // Cache of Regex objects that we have created and are still alive. - private static WeakValueDictionary s_regexCache = new WeakValueDictionary(); + private static readonly WeakValueDictionary s_regexCache = new WeakValueDictionary(); private readonly Lazy _state; diff --git a/src/Build/Graph/GraphBuildRequestData.cs b/src/Build/Graph/GraphBuildRequestData.cs index 4d95ec0afe7..3d67ecf9eb2 100644 --- a/src/Build/Graph/GraphBuildRequestData.cs +++ b/src/Build/Graph/GraphBuildRequestData.cs @@ -5,8 +5,9 @@ using System.Collections.Immutable; using System.Linq; using Microsoft.Build.Execution; +#if NETFRAMEWORK using Microsoft.Build.Experimental.BuildCheck; -using Microsoft.Build.Framework; +#endif using Microsoft.Build.Shared; namespace Microsoft.Build.Graph diff --git a/src/Build/Graph/GraphBuildSubmission.cs b/src/Build/Graph/GraphBuildSubmission.cs index 164cfc8e377..a7280646e08 100644 --- a/src/Build/Graph/GraphBuildSubmission.cs +++ b/src/Build/Graph/GraphBuildSubmission.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; using System.Threading; -using Microsoft.Build.BackEnd; using Microsoft.Build.Execution; using Microsoft.Build.Shared; diff --git a/src/Build/Graph/GraphBuilder.cs b/src/Build/Graph/GraphBuilder.cs index 311392df81f..3a30af61030 100644 --- a/src/Build/Graph/GraphBuilder.cs +++ b/src/Build/Graph/GraphBuilder.cs @@ -205,9 +205,9 @@ private static void AddEdgesFromSolution(IReadOnlyDictionary value)) { - projectsByPath[projectPath].Add(project.Value.GraphNode); + value.Add(project.Value.GraphNode); } else { @@ -662,8 +662,10 @@ public void AddOrUpdateEdge((ProjectGraphNode node, ProjectGraphNode reference) { ReferenceItems.AddOrUpdate( key, +#pragma warning disable IDE0350 addValueFactory: static ((ProjectGraphNode node, ProjectGraphNode reference) key, ProjectItemInstance referenceItem) => referenceItem, updateValueFactory: static ((ProjectGraphNode node, ProjectGraphNode reference) key, ProjectItemInstance existingItem, ProjectItemInstance newItem) => +#pragma warning restore IDE0350 { string existingTargetsMetadata = existingItem.GetMetadataValue(ItemMetadataNames.ProjectReferenceTargetsMetadataName); string newTargetsMetadata = newItem.GetMetadataValue(ItemMetadataNames.ProjectReferenceTargetsMetadataName); diff --git a/src/Build/Graph/ParallelWorkSet.cs b/src/Build/Graph/ParallelWorkSet.cs index ed4ecb41537..8474968ed2c 100644 --- a/src/Build/Graph/ParallelWorkSet.cs +++ b/src/Build/Graph/ParallelWorkSet.cs @@ -145,7 +145,12 @@ internal void WaitForAllWorkAndComplete() // Release one thread that will release all the threads when all the elements are processed. _semaphore.Release(); - Task.WaitAll(_tasks.ToArray()); + Task.WaitAll( +#if NET + _tasks); +#else + _tasks.ToArray()); +#endif if (_exceptions.Count > 0) { diff --git a/src/Build/Graph/ProjectGraphNode.cs b/src/Build/Graph/ProjectGraphNode.cs index dcc2a2a24b1..4c43b1e1061 100644 --- a/src/Build/Graph/ProjectGraphNode.cs +++ b/src/Build/Graph/ProjectGraphNode.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Collections.Generic; using System.Diagnostics; using Microsoft.Build.BackEnd; diff --git a/src/Build/Graph/ProjectInterpretation.cs b/src/Build/Graph/ProjectInterpretation.cs index d927eaa7e8c..12ae373485d 100644 --- a/src/Build/Graph/ProjectInterpretation.cs +++ b/src/Build/Graph/ProjectInterpretation.cs @@ -64,7 +64,7 @@ public TargetSpecification(string target, bool skipIfNonexistent) ErrorUtilities.VerifyThrow( !skipIfNonexistent || (!target.Equals(MSBuildConstants.DefaultTargetsMarker) && !target.Equals(MSBuildConstants.ProjectReferenceTargetsOrDefaultTargetsMarker)), - target + " cannot be marked as SkipNonexistentTargets"); + $"{target} cannot be marked as SkipNonexistentTargets"); Target = target; SkipIfNonexistent = skipIfNonexistent; } diff --git a/src/Build/Instance/HostObjectException.cs b/src/Build/Instance/HostObjectException.cs index ee4613d1b14..47ba77e5d17 100644 --- a/src/Build/Instance/HostObjectException.cs +++ b/src/Build/Instance/HostObjectException.cs @@ -40,7 +40,7 @@ internal HostObjectException( Exception innerException) : base(ErrorMessagePrefix + string.Format(ErrorMessageProjectTargetTask, projectFile, targetName, taskName) - + (innerException == null ? string.Empty : ("\n=============\n" + innerException.ToString() + "\n\n")), + + (innerException == null ? string.Empty : ($"\n=============\n{innerException}\n\n")), innerException) { } diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs index b5579adf088..47e51a66e3f 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableGlobalPropertiesCollectionConverter.cs @@ -2,13 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.CodeDom; using System.Collections; using System.Collections.Generic; -using System.Linq; using System.Runtime.Serialization; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Collections; using Microsoft.Build.Execution; using Microsoft.Build.Shared; diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs index 245d42583b5..67243d66e62 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDefinitionsListConverter.cs @@ -4,9 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Collections; using Microsoft.Build.Shared; diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs index de2d06522ae..00d34f295f2 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableItemDictionary.cs @@ -5,11 +5,8 @@ using System.Collections; using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Collections; using Microsoft.Build.Evaluation; -using Microsoft.Build.Execution; using Microsoft.Build.Shared; namespace Microsoft.Build.Instance @@ -178,13 +175,11 @@ public ICollection GetItems(string itemType) private sealed class ListConverter : ICollection { - private readonly string _itemType; private readonly ICollection _list; private readonly Func _getInstance; public ListConverter(string itemType, ICollection list, Func getInstance) { - _itemType = itemType; _list = list; _getInstance = getInstance; } diff --git a/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs b/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs index 1b11db2341a..ecf078d2567 100644 --- a/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs +++ b/src/Build/Instance/ImmutableProjectCollections/ImmutableLinkedMultiDictionaryConverter.cs @@ -3,11 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Collections; -using Microsoft.Build.Execution; namespace Microsoft.Build.Instance.ImmutableProjectCollections { diff --git a/src/Build/Instance/ProjectInstance.cs b/src/Build/Instance/ProjectInstance.cs index 938fcc5cba9..f77da95817b 100644 --- a/src/Build/Instance/ProjectInstance.cs +++ b/src/Build/Instance/ProjectInstance.cs @@ -2640,7 +2640,12 @@ private static ProjectInstance[] CalculateToolsVersionAndGenerateSolutionWrapper } else /* Dev 12 and above */ { - toolsVersion = visualStudioVersion.ToString(CultureInfo.InvariantCulture) + ".0"; + toolsVersion = +#if NET + string.Create(CultureInfo.InvariantCulture, $"{visualStudioVersion}.0"); +#else + $"{visualStudioVersion.ToString(CultureInfo.InvariantCulture)}.0"; +#endif } string toolsVersionToUse = Utilities.GenerateToolsVersionToUse( @@ -3161,7 +3166,7 @@ private void Initialize( if (Traits.Instance.EscapeHatches.DebugEvaluation) { - Trace.WriteLine(String.Format(CultureInfo.InvariantCulture, "MSBUILD: Creating a ProjectInstance from an unevaluated state [{0}]", FullPath)); + Trace.WriteLine($"MSBUILD: Creating a ProjectInstance from an unevaluated state [{FullPath}]"); } ErrorUtilities.VerifyThrow(EvaluationId == BuildEventContext.InvalidEvaluationId, "Evaluation ID is invalid prior to evaluation"); diff --git a/src/Build/Instance/ProjectItemDefinitionInstance.cs b/src/Build/Instance/ProjectItemDefinitionInstance.cs index 4e886bd134a..2bfb6750f50 100644 --- a/src/Build/Instance/ProjectItemDefinitionInstance.cs +++ b/src/Build/Instance/ProjectItemDefinitionInstance.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Collections; using Microsoft.Build.Construction; using Microsoft.Build.Evaluation; -using Microsoft.Build.Instance; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Build/Instance/ProjectItemInstance.cs b/src/Build/Instance/ProjectItemInstance.cs index 8dd5127eba5..bce683abb37 100644 --- a/src/Build/Instance/ProjectItemInstance.cs +++ b/src/Build/Instance/ProjectItemInstance.cs @@ -1085,6 +1085,7 @@ public void ImportMetadata(IEnumerable> metadata) _directMetadata.ImportProperties(metadata.Select(kvp => new ProjectMetadataInstance(kvp.Key, kvp.Value, allowItemSpecModifiers: true))); } +#if FEATURE_APPDOMAIN /// /// Used to return metadata from another AppDomain. Can't use yield return because the /// generated state machine is not marked as [Serializable], so we need to allocate. @@ -1106,6 +1107,7 @@ private IEnumerable> EnumerateMetadataEager(ICopyOn // Probably better to send the raw array across the wire even if it's another allocation. return result.ToArray(); } +#endif private IEnumerable> EnumerateMetadata(ICopyOnWritePropertyDictionary list) { @@ -2112,7 +2114,7 @@ internal class TaskItemFactory : IItemFactory, IItemFacto /// /// The singleton instance. /// - private static TaskItemFactory s_instance = new TaskItemFactory(); + private static readonly TaskItemFactory s_instance = new TaskItemFactory(); /// /// Private constructor for singleton creation. diff --git a/src/Build/Instance/ProjectMetadataInstance.cs b/src/Build/Instance/ProjectMetadataInstance.cs index ec764cbbbb3..15136b19c1d 100644 --- a/src/Build/Instance/ProjectMetadataInstance.cs +++ b/src/Build/Instance/ProjectMetadataInstance.cs @@ -164,7 +164,7 @@ internal string EvaluatedValueEscaped /// public override string ToString() { - return _name + "=" + _escapedValue; + return $"{_name}={_escapedValue}"; } #region INodePacketTranslatable Members diff --git a/src/Build/Instance/ProjectPropertyInstance.cs b/src/Build/Instance/ProjectPropertyInstance.cs index dc7cb2b6624..2d0de613ea9 100644 --- a/src/Build/Instance/ProjectPropertyInstance.cs +++ b/src/Build/Instance/ProjectPropertyInstance.cs @@ -186,7 +186,7 @@ void ITranslatable.Translate(ITranslator translator) /// public override string ToString() { - return _name + "=" + _escapedValue; + return $"{_name}={_escapedValue}"; } /// diff --git a/src/Build/Instance/TaskFactories/TaskHostTask.cs b/src/Build/Instance/TaskFactories/TaskHostTask.cs index 784b67b200c..fdb9dc2373a 100644 --- a/src/Build/Instance/TaskFactories/TaskHostTask.cs +++ b/src/Build/Instance/TaskFactories/TaskHostTask.cs @@ -9,11 +9,13 @@ using System.Threading; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Exceptions; -using Microsoft.Build.FileAccesses; using Microsoft.Build.Framework; -using Microsoft.Build.Experimental.FileAccess; using Microsoft.Build.Internal; using Microsoft.Build.Shared; +#if FEATURE_REPORTFILEACCESSES +using Microsoft.Build.Experimental.FileAccess; +using Microsoft.Build.FileAccesses; +#endif #nullable disable @@ -372,6 +374,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes the specified packet /// diff --git a/src/Build/Instance/TaskRegistry.cs b/src/Build/Instance/TaskRegistry.cs index a71dad69e15..508cb7483b6 100644 --- a/src/Build/Instance/TaskRegistry.cs +++ b/src/Build/Instance/TaskRegistry.cs @@ -11,7 +11,6 @@ using System.Reflection; using System.Threading; using Microsoft.Build.BackEnd; -using Microsoft.Build.BackEnd.Components.RequestBuilder; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Collections; using Microsoft.Build.Construction; @@ -20,7 +19,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.NET.StringTools; -using ILoggingService = Microsoft.Build.BackEnd.Logging.ILoggingService; using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; using ProjectXmlUtilities = Microsoft.Build.Internal.ProjectXmlUtilities; using TargetLoggingContext = Microsoft.Build.BackEnd.Logging.TargetLoggingContext; @@ -68,64 +66,64 @@ internal sealed class TaskRegistry : ITranslatable /// callbacks; as forcing those out of proc would be just setting them up for /// known failure. /// - private static bool s_forceTaskHostLaunch = (Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"); + private static readonly bool s_forceTaskHostLaunch = (Environment.GetEnvironmentVariable("MSBUILDFORCEALLTASKSOUTOFPROC") == "1"); /// /// Simple name for the MSBuild tasks (v4), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV4SimpleName = "Microsoft.Build.Tasks.v4.0"; + private const string s_tasksV4SimpleName = "Microsoft.Build.Tasks.v4.0"; /// /// Filename for the MSBuild tasks (v4), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV4Filename = s_tasksV4SimpleName + ".dll"; + private const string s_tasksV4Filename = $"{s_tasksV4SimpleName}.dll"; /// /// Expected location that MSBuild tasks (v4) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksV4Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV4Filename); + private static readonly string s_potentialTasksV4Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV4Filename); /// /// Simple name for the MSBuild tasks (v12), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV12SimpleName = "Microsoft.Build.Tasks.v12.0"; + private const string s_tasksV12SimpleName = "Microsoft.Build.Tasks.v12.0"; /// /// Filename for the MSBuild tasks (v12), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksV12Filename = s_tasksV12SimpleName + ".dll"; + private const string s_tasksV12Filename = $"{s_tasksV12SimpleName}.dll"; /// /// Expected location that MSBuild tasks (v12) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksV12Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV12Filename); + private static readonly string s_potentialTasksV12Location = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksV12Filename); /// /// Simple name for the MSBuild tasks (v14+), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksCoreSimpleName = "Microsoft.Build.Tasks.Core"; + private const string s_tasksCoreSimpleName = "Microsoft.Build.Tasks.Core"; /// /// Filename for the MSBuild tasks (v14+), used for shimming in loading /// task factory UsingTasks /// - private static string s_tasksCoreFilename = s_tasksCoreSimpleName + ".dll"; + private const string s_tasksCoreFilename = $"{s_tasksCoreSimpleName}.dll"; /// /// Expected location that MSBuild tasks (v14+) is picked up from if the user /// references it with just a simple name, used for shimming in loading /// task factory UsingTasks /// - private static string s_potentialTasksCoreLocation = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksCoreFilename); + private static readonly string s_potentialTasksCoreLocation = Path.Combine(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, s_tasksCoreFilename); /// /// Monotonically increasing counter for registered tasks. @@ -853,13 +851,13 @@ internal class RegisteredTaskIdentityComparer : IEqualityComparer /// The singleton comparer to use when an exact match is desired /// - private static RegisteredTaskIdentityComparer s_exact = new RegisteredTaskIdentityComparer(true /* exact match */); + private static readonly RegisteredTaskIdentityComparer s_exact = new RegisteredTaskIdentityComparer(true /* exact match */); /// /// The singleton comparer to use when a fuzzy match is desired. Note that this still does an exact match on the /// name, but does a fuzzy match on the task identity parameters. /// - private static RegisteredTaskIdentityComparer s_fuzzy = new RegisteredTaskIdentityComparer(false /* fuzzy match */); + private static readonly RegisteredTaskIdentityComparer s_fuzzy = new RegisteredTaskIdentityComparer(false /* fuzzy match */); /// /// Keeps track of whether we're doing exact or fuzzy equivalency diff --git a/src/Build/Logging/BaseConsoleLogger.cs b/src/Build/Logging/BaseConsoleLogger.cs index 508223e628b..364487c1e72 100644 --- a/src/Build/Logging/BaseConsoleLogger.cs +++ b/src/Build/Logging/BaseConsoleLogger.cs @@ -518,7 +518,7 @@ internal virtual void OutputEnvironment(IDictionary environment) foreach (KeyValuePair entry in environment) { setColor(ConsoleColor.Gray); - WritePretty(String.Format(CultureInfo.CurrentCulture, "{0,-30} = ", entry.Key)); + WritePretty($"{entry.Key,-30} = "); setColor(ConsoleColor.DarkGray); WriteLinePretty(entry.Value); } @@ -536,7 +536,7 @@ internal virtual void OutputProperties(List list) foreach (DictionaryEntry prop in list) { setColor(ConsoleColor.Gray); - WritePretty(String.Format(CultureInfo.CurrentCulture, "{0,-30} = ", prop.Key)); + WritePretty($"{prop.Key,-30} = "); setColor(ConsoleColor.DarkGray); WriteLinePretty(EscapingUtilities.UnescapeAll((string)prop.Value)); } @@ -656,12 +656,12 @@ protected virtual void WriteItemType(string itemType) protected virtual void WriteItemSpec(string itemSpec) { - WriteLinePretty(" " + itemSpec); + WriteLinePretty($" {itemSpec}"); } protected virtual void WriteMetadata(string name, string value) { - WriteLinePretty(" " + name + " = " + value); + WriteLinePretty($" {name} = {value}"); } /// @@ -821,8 +821,8 @@ internal virtual void PrintCounterMessage(WriteLinePrettyFromResourceDelegate wr 2, "PerformanceLine", time, - String.Format(CultureInfo.CurrentCulture, "{0,-40}" /* pad to 40 align left */, scopeName), - String.Format(CultureInfo.CurrentCulture, "{0,3}", calls)); + $"{scopeName,-40}", // pad to 40 align left + $"{calls,3}"); } /// diff --git a/src/Build/Logging/BinaryLogger/BinaryLogger.cs b/src/Build/Logging/BinaryLogger/BinaryLogger.cs index 9c3fda8fec3..e5947b6bf33 100644 --- a/src/Build/Logging/BinaryLogger/BinaryLogger.cs +++ b/src/Build/Logging/BinaryLogger/BinaryLogger.cs @@ -4,10 +4,10 @@ using System; using System.IO; using System.IO.Compression; +using Microsoft.Build.Experimental.BuildCheck.Infrastructure.EditorConfig; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Shared; -using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -322,6 +322,12 @@ public void Shutdown() if (projectImportsCollector != null) { + // Write the build check editorconfig file paths to the log + foreach (var filePath in EditorConfigParser.EditorConfigFilePaths) + { + projectImportsCollector.AddFile(filePath); + } + EditorConfigParser.ClearEditorConfigFilePaths(); projectImportsCollector.Close(); if (CollectProjectImports == ProjectImportsCollectionMode.Embed) @@ -337,6 +343,7 @@ public void Shutdown() projectImportsCollector = null; } + if (stream != null) { // It's hard to determine whether we're at the end of decoding GZipStream diff --git a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs index 4b48d9a8592..69afeee1674 100644 --- a/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs +++ b/src/Build/Logging/BinaryLogger/BuildEventArgsReader.cs @@ -56,9 +56,9 @@ public class BuildEventArgsReader : IBuildEventArgsReaderNotifications, IDisposa // reflection is needed to set these three fields because public constructors don't provide // a way to set these from the outside - private static FieldInfo? buildEventArgsFieldThreadId = + private static readonly FieldInfo? buildEventArgsFieldThreadId = typeof(BuildEventArgs).GetField("threadId", BindingFlags.Instance | BindingFlags.NonPublic); - private static FieldInfo? buildEventArgsFieldSenderName = + private static readonly FieldInfo? buildEventArgsFieldSenderName = typeof(BuildEventArgs).GetField("senderName", BindingFlags.Instance | BindingFlags.NonPublic); /// diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/ArchiveFile.cs b/src/Build/Logging/BinaryLogger/Postprocessing/ArchiveFile.cs index ec8ba12c8b1..6239d90f453 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/ArchiveFile.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/ArchiveFile.cs @@ -4,8 +4,6 @@ using System; using System.IO; using System.IO.Compression; -using System.Text; -using Microsoft.Build.Shared; namespace Microsoft.Build.Logging { diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/StreamExtensions.cs b/src/Build/Logging/BinaryLogger/Postprocessing/StreamExtensions.cs index 8a0cc2ed489..3e85d2bb02b 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/StreamExtensions.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/StreamExtensions.cs @@ -3,9 +3,7 @@ using System; using System.Buffers; -using System.Diagnostics; using System.IO; -using System.Text; using Microsoft.Build.Shared; namespace Microsoft.Build.Logging diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs b/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs index 8c6e0c6e2b8..af92788d62a 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/SubStream.cs @@ -70,9 +70,13 @@ public override int ReadByte() public override async Task ReadAsync(byte[] buffer, int offset, int count, CancellationToken cancellationToken) { count = Math.Min((int)Math.Max(Length - _position, 0), count); -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int read = await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int read = await _stream.ReadAsync( +#if NET + buffer.AsMemory(offset, count), +#else + buffer, offset, count, +#endif + cancellationToken).ConfigureAwait(false); _position += read; return read; } diff --git a/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs b/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs index ea3fcb3c9c7..bd427fbb3bc 100644 --- a/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs +++ b/src/Build/Logging/BinaryLogger/Postprocessing/TransparentReadStream.cs @@ -116,9 +116,13 @@ public override async Task ReadAsync(byte[] buffer, int offset, int count, count = (int)(_maxAllowedPosition - _position); } -#pragma warning disable CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' - int cnt = await _stream.ReadAsync(buffer, offset, count, cancellationToken).ConfigureAwait(false); -#pragma warning restore CA1835 // Prefer the 'Memory'-based overloads for 'ReadAsync' and 'WriteAsync' + int cnt = await _stream.ReadAsync( +#if NET + buffer.AsMemory(offset, count), +#else + buffer, offset, count, +#endif + cancellationToken).ConfigureAwait(false); _position += cnt; return cnt; } diff --git a/src/Build/Logging/ConsoleLogger.cs b/src/Build/Logging/ConsoleLogger.cs index dc365d45387..41bfaf94f50 100644 --- a/src/Build/Logging/ConsoleLogger.cs +++ b/src/Build/Logging/ConsoleLogger.cs @@ -2,10 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics.CodeAnalysis; -using System.Linq; -using System.Reflection; -using System.Runtime.CompilerServices; using Microsoft.Build.BackEnd.Logging; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Logging; diff --git a/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs b/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs index 591df6df9f3..bfd1d41a51f 100644 --- a/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs +++ b/src/Build/Logging/DistributedLoggers/DistributedFileLogger.cs @@ -113,7 +113,7 @@ public void Initialize(IEventSource eventSource) // but avoids confusion by being consistent with the Engine and any error messages it may produce. fileName = _logFile.Replace(extension, _nodeId + extension); _nodeFileLogger.Verbosity = LoggerVerbosity.Detailed; - _nodeFileLogger.Parameters = "ShowEventId;ShowCommandLine;logfile=" + fileName + ";" + _parameters; + _nodeFileLogger.Parameters = $"ShowEventId;ShowCommandLine;logfile={fileName};{_parameters}"; } catch (ArgumentException e) // Catching Exception, but rethrowing unless it's a well-known exception. { diff --git a/src/Build/Logging/LogFormatter.cs b/src/Build/Logging/LogFormatter.cs index b03391a34a1..22c0ab05658 100644 --- a/src/Build/Logging/LogFormatter.cs +++ b/src/Build/Logging/LogFormatter.cs @@ -51,7 +51,7 @@ internal static string FormatTimeSpan(TimeSpan t) string rawTime = t.ToString(); // Timespan is a value type and can't be null. int rawTimeLength = rawTime.Length; int prettyLength = System.Math.Min(11, rawTimeLength); - return t.ToString().Substring(0, prettyLength); + return rawTime.Substring(0, prettyLength); } } } diff --git a/src/Build/Logging/LoggerDescription.cs b/src/Build/Logging/LoggerDescription.cs index 8f949afb9ad..9b4e34e5c34 100644 --- a/src/Build/Logging/LoggerDescription.cs +++ b/src/Build/Logging/LoggerDescription.cs @@ -96,7 +96,7 @@ public string Name if (!string.IsNullOrEmpty(_loggerClassName) && !string.IsNullOrEmpty(_loggerAssembly.AssemblyFile)) { - return _loggerClassName + ":" + _loggerAssembly.AssemblyFile; + return $"{_loggerClassName}:{_loggerAssembly.AssemblyFile}"; } else if (!string.IsNullOrEmpty(_loggerClassName)) { diff --git a/src/Build/Logging/OptimizedStringIndenter.cs b/src/Build/Logging/OptimizedStringIndenter.cs index d98f1d62094..ed28c80e8fd 100644 --- a/src/Build/Logging/OptimizedStringIndenter.cs +++ b/src/Build/Logging/OptimizedStringIndenter.cs @@ -4,7 +4,7 @@ using System; using System.Buffers; -#if NET7_0_OR_GREATER +#if NET using System.Runtime.CompilerServices; #else using System.Text; @@ -49,7 +49,7 @@ namespace Microsoft.Build.BackEnd.Logging; internal static class OptimizedStringIndenter { #nullable enable -#if NET7_0_OR_GREATER +#if NET [SkipLocalsInit] #endif internal static unsafe string IndentString(string? s, int indent, IStringBuilderProvider stringBuilderProvider) @@ -67,7 +67,7 @@ internal static unsafe string IndentString(string? s, int indent, IStringBuilder indentedStringLength += segment.Length; } -#if NET7_0_OR_GREATER +#if NET #pragma warning disable CS8500 string result = string.Create(indentedStringLength, (s, (IntPtr)(&segments), indent), static (output, state) => { diff --git a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs index d59bbb81e46..fb60ed2c23c 100644 --- a/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs +++ b/src/Build/Logging/ParallelLogger/ParallelConsoleLogger.cs @@ -760,7 +760,7 @@ internal override void OutputProperties(List list) foreach (DictionaryEntry prop in list) { setColor(ConsoleColor.Gray); - string propertyString = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", prop.Key, EscapingUtilities.UnescapeAll((string)(prop.Value))); + string propertyString = $"{prop.Key} = {EscapingUtilities.UnescapeAll((string)(prop.Value))}"; WriteMessageAligned(propertyString, false); } resetColor(); @@ -781,7 +781,7 @@ internal override void OutputEnvironment(IDictionary environment foreach (KeyValuePair entry in environment) { setColor(ConsoleColor.Gray); - string environmentMessage = String.Format(CultureInfo.CurrentCulture, "{0} = {1}", entry.Key, entry.Value); + string environmentMessage = $"{entry.Key} = {entry.Value}"; WriteMessageAligned(environmentMessage, false); } } @@ -897,7 +897,7 @@ public override void TargetFinishedHandler(object sender, TargetFinishedEventArg foreach (DictionaryEntry metadatum in metadata) { - WriteMessageAligned(new String(' ', 4 * tabWidth) + metadatum.Key + " = " + item.GetMetadata(metadatum.Key as string), false); + WriteMessageAligned($"{new String(' ', 4 * tabWidth)}{metadatum.Key} = {item.GetMetadata(metadatum.Key as string)}", false); } } } @@ -1343,7 +1343,7 @@ private void PrintTargetNamePerMessage(BuildMessageEventArgs e, bool lightenText } else { - WriteMessageAligned(targetName + ":", prefixAlreadyWritten); + WriteMessageAligned($"{targetName}:", prefixAlreadyWritten); } if (lightenText) @@ -1401,22 +1401,6 @@ private void WriteMessageAligned(string message, bool prefixAlreadyWritten, int } } - /// - /// Write message taking into account whether or not the prefix (timestamp and key) have already been written on the line - /// - private void WriteBasedOnPrefix(string nonNullMessage, bool prefixAlreadyWritten, int adjustedPrefixWidth) - { - if (prefixAlreadyWritten) - { - WriteHandler(nonNullMessage + Environment.NewLine); - } - else - { - // No prefix info has been written, indent the line to the proper location - WriteHandler(IndentString(nonNullMessage, adjustedPrefixWidth)); - } - } - /// /// Will display the target started event which was deferred until the first visible message for the target is ready to be displayed /// @@ -1618,11 +1602,11 @@ private void WriteLinePrefix(string key, DateTime eventTimeStamp, bool isMessage if (!isMessagePrefix || IsVerbosityAtLeast(LoggerVerbosity.Detailed)) { - prefixString = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, key) + ">"; + prefixString = $"{ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, key)}>"; } else { - prefixString = ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, string.Empty) + " "; + prefixString = $"{ResourceUtilities.FormatResourceStringStripCodeAndKeyword("BuildEventContext", context, string.Empty)} "; } WritePretty(prefixString); @@ -1763,8 +1747,8 @@ internal override void PrintCounterMessage(WriteLinePrettyFromResourceDelegate W MessageIndentLevel, "PerformanceLine", time, - String.Format(CultureInfo.CurrentCulture, "{0,-40}" /* pad to 40 align left */, scopeName), - String.Format(CultureInfo.CurrentCulture, "{0,3}", calls)); + $"{scopeName,-40}", // pad to 40 align left + $"{calls,3}"); if (_internalPerformanceCounters?.Count > 0) { diff --git a/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs b/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs index dd73599bec2..b066b31fea4 100644 --- a/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs +++ b/src/Build/Logging/ParallelLogger/ParallelLoggerHelpers.cs @@ -140,7 +140,6 @@ internal string[] ProjectCallStackFromProject(BuildEventContext e) ProjectStartedEventMinimumFields startedEvent = GetProjectStartedEvent(currentKey); - List stackTrace = new List(); // If there is no started event then there should be no stack trace // this is a valid situation if the event occures in the engine or outside the context of a project // or the event is raised before the project started event @@ -150,19 +149,18 @@ internal string[] ProjectCallStackFromProject(BuildEventContext e) } List projectStackTrace = GetProjectCallStack(e); - foreach (ProjectStartedEventMinimumFields projectStartedEvent in projectStackTrace) + + string[] stackTrace = new string[projectStackTrace.Count]; + for (int i = 0; i < stackTrace.Length; i++) { - if (!string.IsNullOrEmpty(projectStartedEvent.TargetNames)) - { - stackTrace.Add(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithTargetNames", projectStartedEvent.ProjectFile, projectStartedEvent.TargetNames, projectStartedEvent.FullProjectKey)); - } - else - { - stackTrace.Add(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithDefaultTargets", projectStartedEvent.ProjectFile, projectStartedEvent.FullProjectKey)); - } + ProjectStartedEventMinimumFields projectStartedEvent = projectStackTrace[i]; + + stackTrace[stackTrace.Length - i - 1] = !string.IsNullOrEmpty(projectStartedEvent.TargetNames) ? + ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithTargetNames", projectStartedEvent.ProjectFile, projectStartedEvent.TargetNames, projectStartedEvent.FullProjectKey) : + ResourceUtilities.FormatResourceStringStripCodeAndKeyword("ProjectStackWithDefaultTargets", projectStartedEvent.ProjectFile, projectStartedEvent.FullProjectKey); } - stackTrace.Reverse(); - return stackTrace.ToArray(); + + return stackTrace; } /// diff --git a/src/Build/Logging/ProfilerLogger.cs b/src/Build/Logging/ProfilerLogger.cs index b80dcf8cf0e..85f659336d8 100644 --- a/src/Build/Logging/ProfilerLogger.cs +++ b/src/Build/Logging/ProfilerLogger.cs @@ -134,7 +134,7 @@ internal ProfilerResult GetAggregatedResult(bool pruneSmallItems = true) // So keeping that map here var originalLocations = new Dictionary(EvaluationLocationIdAgnosticComparer.Singleton); - while (_profiledResults.Any()) + while (!_profiledResults.IsEmpty) { ProfilerResult profiledResult; var result = _profiledResults.TryDequeue(out profiledResult); diff --git a/src/Build/Logging/SimpleErrorLogger.cs b/src/Build/Logging/SimpleErrorLogger.cs index 5b248afd5e7..fafc01e340d 100644 --- a/src/Build/Logging/SimpleErrorLogger.cs +++ b/src/Build/Logging/SimpleErrorLogger.cs @@ -4,7 +4,6 @@ using System; using Microsoft.Build.Framework; using Microsoft.Build.Framework.Logging; -using Microsoft.Build.Logging; using Microsoft.Build.Shared; namespace Microsoft.Build.Logging.SimpleErrorLogger diff --git a/src/Build/Logging/TerminalLogger/TerminalLogger.cs b/src/Build/Logging/TerminalLogger/TerminalLogger.cs index 4e653123f12..eca3e0749a3 100644 --- a/src/Build/Logging/TerminalLogger/TerminalLogger.cs +++ b/src/Build/Logging/TerminalLogger/TerminalLogger.cs @@ -12,8 +12,8 @@ using Microsoft.Build.Framework.Logging; using Microsoft.Build.Shared; -#if NET7_0_OR_GREATER -using System.Diagnostics.CodeAnalysis; +#if NET +using System.Buffers; #endif #if NETFRAMEWORK @@ -34,15 +34,10 @@ public sealed partial class TerminalLogger : INodeLogger { private const string FilePathPattern = " -> "; -#if NET7_0_OR_GREATER - [StringSyntax(StringSyntaxAttribute.Regex)] - private const string ImmediateMessagePattern = @"\[CredentialProvider\]|--interactive"; - private const RegexOptions Options = RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.ExplicitCapture; - - [GeneratedRegex(ImmediateMessagePattern, Options)] - private static partial Regex ImmediateMessageRegex(); +#if NET + private static readonly SearchValues _immediateMessageKeywords = SearchValues.Create(["[CredentialProvider]", "--interactive"], StringComparison.OrdinalIgnoreCase); #else - private static readonly string[] _immediateMessageKeywords = { "[CredentialProvider]", "--interactive" }; + private static readonly string[] _immediateMessageKeywords = ["[CredentialProvider]", "--interactive"]; #endif private static readonly string[] newLineStrings = { "\r\n", "\n" }; @@ -165,11 +160,6 @@ public ProjectContext(BuildEventContext context) /// private bool _loggedPreviewMessage; - /// - /// The two directory separator characters to be passed to methods like . - /// - private static readonly char[] PathSeparators = { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }; - /// /// One summary per finished project test run. /// @@ -1020,8 +1010,8 @@ private void WarningRaised(object sender, BuildWarningEventArgs e) /// Raised event. /// true if marker is detected. private bool IsImmediateMessage(string message) => -#if NET7_0_OR_GREATER - ImmediateMessageRegex().IsMatch(message); +#if NET + message.AsSpan().ContainsAny(_immediateMessageKeywords); #else _immediateMessageKeywords.Any(imk => message.IndexOf(imk, StringComparison.OrdinalIgnoreCase) >= 0); #endif @@ -1060,11 +1050,22 @@ private void ErrorRaised(object sender, BuildErrorEventArgs e) private void ThreadProc() { // 1_000 / 30 is a poor approx of 30Hz + var count = 0; while (!_cts.Token.WaitHandle.WaitOne(1_000 / 30)) { + count++; lock (_lock) { - DisplayNodes(); + // Querying the terminal for it's dimensions is expensive, so we only do it every 30 frames e.g. once a second. + if (count >= 30) + { + count = 0; + DisplayNodes(); + } + else + { + DisplayNodes(false); + } } } @@ -1075,9 +1076,11 @@ private void ThreadProc() /// Render Nodes section. /// It shows what all build nodes do. /// - internal void DisplayNodes() + internal void DisplayNodes(bool updateSize = true) { - TerminalNodesFrame newFrame = new TerminalNodesFrame(_nodes, width: Terminal.Width, height: Terminal.Height); + var width = updateSize ? Terminal.Width : _currentFrame.Width; + var height = updateSize ? Terminal.Height : _currentFrame.Height; + TerminalNodesFrame newFrame = new TerminalNodesFrame(_nodes, width: width, height: height); // Do not render delta but clear everything if Terminal width or height have changed. if (newFrame.Width != _currentFrame.Width || newFrame.Height != _currentFrame.Height) @@ -1184,7 +1187,7 @@ private int NodeIndexForContext(BuildEventContext context) return null; } - int index = path.LastIndexOfAny(PathSeparators); + int index = path.AsSpan().LastIndexOfAny(Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar); return index >= 0 ? $"{path.Substring(0, index + 1)}{AnsiCodes.MakeBold(path.Substring(index + 1))}" : path; @@ -1282,7 +1285,7 @@ private string FormatEventMessage( builder.Append($"{category} {code}: "); // render multi-line message in a special way - if (message.IndexOf('\n') >= 0) + if (message.Contains('\n')) { // Place the multiline message under the project in case of minimal and higher verbosity. string[] lines = message.Split(newLineStrings, StringSplitOptions.None); diff --git a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs index 4634040bdf7..22eb0157257 100644 --- a/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs +++ b/src/Build/Logging/TerminalLogger/TerminalNodeStatus.cs @@ -1,10 +1,10 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#if DEBUG using System; -using System.Diagnostics; +#endif using Microsoft.Build.Framework.Logging; -using Microsoft.Build.Shared; namespace Microsoft.Build.Logging; @@ -68,23 +68,10 @@ obj is TerminalNodeStatus status && TargetPrefixColor == status.TargetPrefixColor && TargetPrefix == status.TargetPrefix; - public override string ToString() - { - string duration = Stopwatch.ElapsedSeconds.ToString("F1"); - - return string.IsNullOrEmpty(TargetFramework) - ? string.Format("{0}{1} {2} ({3}s)", - TerminalLogger.Indentation, - Project, - Target, - duration) - : string.Format("{0}{1} {2} {3} ({4}s)", - TerminalLogger.Indentation, - Project, - AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor), - Target, - duration); - } + public override string ToString() => + string.IsNullOrEmpty(TargetFramework) ? + $"{TerminalLogger.Indentation}{Project} {Target} ({Stopwatch.ElapsedSeconds:F1}s)" : + $"{TerminalLogger.Indentation}{Project} {AnsiCodes.Colorize(TargetFramework, TerminalLogger.TargetFrameworkColor)} {Target} ({Stopwatch.ElapsedSeconds:F1}s)"; public override int GetHashCode() { diff --git a/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs b/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs index 8e4f98fe688..8c64b0978f5 100644 --- a/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs +++ b/src/Build/Logging/TerminalLogger/TerminalProjectInfo.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; namespace Microsoft.Build.Logging; diff --git a/src/Build/Resources/Constants.cs b/src/Build/Resources/Constants.cs index f6c7a968081..68a6ab4f4bc 100644 --- a/src/Build/Resources/Constants.cs +++ b/src/Build/Resources/Constants.cs @@ -178,7 +178,7 @@ internal static class AvailableStaticMethods /// /// Locker to protect initialization /// - private static Object s_locker = new Object(); + private static readonly Object s_locker = new Object(); static AvailableStaticMethods() { @@ -365,7 +365,7 @@ private static void InitializeAvailableMethods() availableStaticMethods.TryAdd("Microsoft.Build.Utilities.ToolLocationHelper", new Tuple("Microsoft.Build.Utilities.ToolLocationHelper, Microsoft.Build.Utilities.Core, Version=" + MSBuildConstants.CurrentAssemblyVersion + ", Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", null)); availableStaticMethods.TryAdd("System.Runtime.InteropServices.RuntimeInformation", runtimeInformationType); availableStaticMethods.TryAdd("System.Runtime.InteropServices.OSPlatform", osPlatformType); -#if NET5_0_OR_GREATER +#if NET var operatingSystemType = new Tuple(null, typeof(OperatingSystem)); availableStaticMethods.TryAdd("System.OperatingSystem", operatingSystemType); #else diff --git a/src/Build/Resources/Strings.resx b/src/Build/Resources/Strings.resx index 128cf7283c0..f6b9b047fea 100644 --- a/src/Build/Resources/Strings.resx +++ b/src/Build/Resources/Strings.resx @@ -2206,6 +2206,12 @@ Utilization: {0} Average Utilization: {1:###.0} Project {0} specifies 'TargetFramework(s)' property '{1}', which does not use the .NET SDK. Those properties are not understood by projects that import C# targets directly. Terms in quotes are not to be translated. + + The 'Exec' task should not be used to build a project. + + + Task {0} from project {1} builds a project using the {2} CLI. The MSBuild task should be used instead. + A property that is accessed should be declared first. @@ -2386,6 +2392,9 @@ Utilization: {0} Average Utilization: {1:###.0} succeeded: {0} {0} whole number + + Loading telemetry libraries failed with exception: {0}. + @@ -194,6 +193,7 @@ + @@ -274,9 +274,6 @@ <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualBasic.CrossTargeting.targets" TargetFramework="%(_TargetFrameworks.Identity)" /> <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualBasic.CurrentVersion.targets" TargetFramework="%(_TargetFrameworks.Identity)" /> <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualBasic.targets" TargetFramework="%(_TargetFrameworks.Identity)" /> - <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualStudioVersion.v11.Common.props" TargetFramework="%(_TargetFrameworks.Identity)" /> - <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualStudioVersion.v12.Common.props" TargetFramework="%(_TargetFrameworks.Identity)" /> - <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\Microsoft.VisualStudioVersion.v14.Common.props" TargetFramework="%(_TargetFrameworks.Identity)" /> <_OurFiles Include="$(OutputPath)%(_TargetFrameworks.Identity)\ref\**" TargetFramework="%(_TargetFrameworks.Identity)" Subdirectory="ref\" /> diff --git a/src/MSBuild/MSBuildClientApp.cs b/src/MSBuild/MSBuildClientApp.cs index be768b58b8d..3eeb975bc40 100644 --- a/src/MSBuild/MSBuildClientApp.cs +++ b/src/MSBuild/MSBuildClientApp.cs @@ -97,28 +97,5 @@ public static MSBuildApp.ExitType Execute( return MSBuildApp.ExitType.MSBuildClientFailure; } - - // Copied from NodeProviderOutOfProcBase.cs -#if RUNTIME_TYPE_NETCORE - private static string? CurrentHost; - private static string GetCurrentHost() - { - if (CurrentHost == null) - { - string dotnetExe = Path.Combine(FileUtilities.GetFolderAbove(BuildEnvironmentHelper.Instance.CurrentMSBuildToolsDirectory, 2), - NativeMethodsShared.IsWindows ? "dotnet.exe" : "dotnet"); - if (File.Exists(dotnetExe)) - { - CurrentHost = dotnetExe; - } - else - { - CurrentHost = EnvironmentUtilities.ProcessPath ?? throw new InvalidOperationException("Failed to retrieve process executable."); - } - } - - return CurrentHost; - } -#endif } } diff --git a/src/MSBuild/OutOfProcTaskHostNode.cs b/src/MSBuild/OutOfProcTaskHostNode.cs index f862ae2adca..9b670a086d4 100644 --- a/src/MSBuild/OutOfProcTaskHostNode.cs +++ b/src/MSBuild/OutOfProcTaskHostNode.cs @@ -4,7 +4,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; using System.IO; using System.Reflection; @@ -592,6 +591,16 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr _packetFactory.DeserializeAndRoutePacket(nodeId, packetType, translator); } + /// + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + return _packetFactory.DeserializePacket(packetType, translator); + } + /// /// Routes the specified packet /// diff --git a/src/MSBuild/PerformanceLogEventListener.cs b/src/MSBuild/PerformanceLogEventListener.cs index 6772a6aeefc..a42f05194bc 100644 --- a/src/MSBuild/PerformanceLogEventListener.cs +++ b/src/MSBuild/PerformanceLogEventListener.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Diagnostics; using System.Diagnostics.Tracing; using System.IO; using System.Text; @@ -22,7 +21,7 @@ internal struct ProviderConfiguration internal EventLevel Level { get; set; } } - private static ProviderConfiguration[] s_config = + private static readonly ProviderConfiguration[] s_config = [ new ProviderConfiguration() { @@ -82,7 +81,7 @@ internal void Initialize(string logDirectory) _processIDStr = EnvironmentUtilities.CurrentProcessId.ToString(); // Use a GUID disambiguator to make sure that we have a unique file name. - string logFilePath = Path.Combine(logDirectory, $"perf-{_processIDStr}-{Guid.NewGuid().ToString("N")}.log"); + string logFilePath = Path.Combine(logDirectory, $"perf-{_processIDStr}-{Guid.NewGuid():N}.log"); Stream outputStream = new FileStream( logFilePath, @@ -143,7 +142,7 @@ protected override void OnEventWritten(EventWrittenEventArgs eventData) s_builder.Clear(); } - s_builder.Append($"[{DateTime.UtcNow.ToString("o")}] Event={eventData.EventSource.Name}/{eventData.EventName} ProcessID={_processIDStr} ThreadID={System.Threading.Thread.CurrentThread.ManagedThreadId}\t "); + s_builder.Append($"[{DateTime.UtcNow:o}] Event={eventData.EventSource.Name}/{eventData.EventName} ProcessID={_processIDStr} ThreadID={Environment.CurrentManagedThreadId}\t "); for (int i = 0; i < eventData.PayloadNames.Count; i++) { s_builder.Append($"{eventData.PayloadNames[i]}=\"{eventData.Payload[i]}\" "); diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf index 24c3f4dcb45..0968a8a75fb 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hans.xlf @@ -841,9 +841,9 @@ WarningsOnly -- 仅显示警告。 NoItemAndPropertyList -- 在开始生成每个项目时不显示 项和属性的列表。 - ShowCommandLine -- 显示 TaskCommandLineEvent 消息 + ShowCommandLine -- 显示 TaskCommandLineEvent 消息 ShowTimestamp -- 将时间戳作为所有消息的前缀 - 显示。 + 显示。 ShowEventId -- 显示已开始事件、已完成事件和消息 的事件 ID。 ForceNoAlign -- 不将文本与控制台缓冲区的大小 @@ -900,10 +900,10 @@ Example: -validate:MyExtendedBuildSchema.xsd - -validate 依据默认架构验证项目。(缩写: + -validate 依据默认架构验证项目。(缩写: -val) - -validate:<schema> 依据指定的架构验证项目。(缩写: + -validate:<schema> 依据指定的架构验证项目。(缩写: -val) 示例: -validate:MyExtendedBuildSchema.xsd @@ -1081,7 +1081,7 @@ -toolsversion:<version> 要在生成过程中使用的 MSBuild 工具集 (任务、目标等)的版本。此版本将重写 - 各个项目指定的版本。(缩写: + 各个项目指定的版本。(缩写: -tv) 示例: -toolsversion:3.5 @@ -1137,17 +1137,17 @@ template and append the node id to this fileName to create a log file for each node. - -distributedFileLogger + -distributedFileLogger 将生成输出记录到多个日志文件,每个 MSBuild 节点 一个日志文件。这些文件的初始位置为 当前目录。默认情况下,这些文件名为 “MSBuild<nodeid>.log”。可通过添加 - “-fileLoggerParameters”开关来指定 + “-fileLoggerParameters”开关来指定 这些文件的位置和 fileLogger 的其他参数。 如果日志文件名是通过 fileLoggerParameters 开关设置的,分布式记录器将使用 fileName 作为 - 模板并将节点 ID 附加到此 fileName + 模板并将节点 ID 附加到此 fileName 以便为每个节点创建一个日志文件。 @@ -1189,12 +1189,12 @@ -flp1:warningsonly;logfile=msbuild.wrn -flp2:errorsonly;logfile=msbuild.err - -fileloggerparameters[n]:<parameters> + -fileloggerparameters[n]:<parameters> 为文件记录器提供任何额外的参数。 存在此开关意味着 存在对应的 -filelogger[n] 开关。 “n”(如果存在)可以为 1-9 的数字。 - 任何分布式文件记录器也可以使用 + 任何分布式文件记录器也可以使用 -fileloggerparameters,具体可参阅 -distributedFileLogger 的说明。 (缩写: -flp[n]) 为控制台记录器列出的相同参数 @@ -1214,8 +1214,8 @@ -fileLoggerParameters:LogFile=MyLog.log;Append; Verbosity=diagnostic;Encoding=UTF-8 - -flp:Summary;Verbosity=minimal;LogFile=msbuild.sum - -flp1:warningsonly;logfile=msbuild.wrn + -flp:Summary;Verbosity=minimal;LogFile=msbuild.sum + -flp1:warningsonly;logfile=msbuild.wrn -flp2:errorsonly;logfile=msbuild.err @@ -2200,4 +2200,4 @@ - + \ No newline at end of file diff --git a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf index 58b406ea531..b2b8fb45067 100644 --- a/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf +++ b/src/MSBuild/Resources/xlf/Strings.zh-Hant.xlf @@ -214,7 +214,7 @@ 終端機記錄器的參數。(簡短形式: -tlp) 可用的參數。 default -- 指定終端機記錄器的預設值。 - 其需要下列其中一值: + 其需要下列其中一值: 。 - 'on'、'true' 會強制使用 TerminalLogger,即使 其之後可能會停用。 @@ -227,7 +227,7 @@ -verbosity showCommandLine -- 顯示 TaskCommandLineEvent 訊息 - 範例: + 範例: -tlp:default=auto;verbosity=diag;shownCommandLine @@ -246,7 +246,7 @@ -getResultOutputFile:file 將輸出從 get* 重新導向至檔案。 - 範例: + 範例: -getProperty:Bar -getResultOutputFile:Biz.txt 這會將屬性列的值寫入 Biz.txt。 @@ -263,7 +263,7 @@ -check 在建置期間啟用 BuildChecks。 - BuildCheck 會啟用評估規則以確保組建的 + BuildCheck 會啟用評估規則以確保組建的 屬性。如需詳細資訊,請參閱 aka.ms/buildcheck @@ -446,8 +446,8 @@ -isolateProjects[:True|MessageUponIsolationViolation|False] 導致 MSBuild 在隔離中建置每個專案。 - 設定為 "MessageUponIsolationViolation" - (或其簡短形式 "Message") 時,如果提供 + 設定為 "MessageUponIsolationViolation" + (或其簡短形式 "Message") 時,如果提供 -outputResultsCache 切換,則只會序列化來自 頂層目標的結果。這是為了降低相依性專案上, 由於其相依性位於快取目標上 (其副作用 @@ -1081,8 +1081,8 @@ -toolsversion:<版本> 建置期間所使用的 MSBuild 工具組 (工作、目標等) - 版本。此版本將會覆寫 - 個別專案所指定的版本。(簡短形式: + 版本。此版本將會覆寫 + 個別專案所指定的版本。(簡短形式: -tv) 範例: -toolsVersion:3.5 @@ -2201,4 +2201,4 @@ - + \ No newline at end of file diff --git a/src/MSBuild/XMake.cs b/src/MSBuild/XMake.cs index 8bf202edc41..276ee52c214 100644 --- a/src/MSBuild/XMake.cs +++ b/src/MSBuild/XMake.cs @@ -1777,7 +1777,7 @@ private static bool PrintTargets(string projectFile, string toolsVersion, Dictio ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "LongPaths", ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( - "LongPaths_" + longPaths.ToString())), + $"LongPaths_{longPaths}")), MessageImportance.Low)); } @@ -1789,7 +1789,7 @@ private static bool PrintTargets(string projectFile, string toolsVersion, Dictio ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( "SAC", ResourceUtilities.FormatResourceStringIgnoreCodeAndKeyword( - "SAC_" + SAC_State.ToString())), + $"SAC_{SAC_State}")), MessageImportance.Low)); } @@ -3094,7 +3094,7 @@ private static bool WarningsAsErrorsSwitchIsEmpty(CommandLineSwitches commandLin return false; } - int indexOfColon = val.IndexOf(":"); + int indexOfColon = val.IndexOf(':'); return indexOfColon < 0 || indexOfColon == val.Length - 1; } @@ -3663,13 +3663,13 @@ private static void ValidateExtensions(string[] projectExtensionsToIgnore) InitializationException.VerifyThrow(extension?.Length >= 2, "InvalidExtensionToIgnore", extension); // There is an invalid char in the extensionToIgnore. - InitializationException.VerifyThrow(extension.IndexOfAny(Path.GetInvalidPathChars()) == -1, "InvalidExtensionToIgnore", extension, null, false); + InitializationException.VerifyThrow(extension.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) < 0, "InvalidExtensionToIgnore", extension, null, false); // There were characters before the extension. InitializationException.VerifyThrow(string.Equals(extension, Path.GetExtension(extension), StringComparison.OrdinalIgnoreCase), "InvalidExtensionToIgnore", extension, null, false); // Make sure that no wild cards are in the string because for now we don't allow wild card extensions. - InitializationException.VerifyThrow(extension.IndexOfAny(s_wildcards) == -1, "InvalidExtensionToIgnore", extension, null, false); + InitializationException.VerifyThrow(extension.IndexOfAny(MSBuildConstants.WildcardChars) == -1, "InvalidExtensionToIgnore", extension, null, false); } } } @@ -3723,7 +3723,7 @@ private static string[] ProcessTargetSwitch(string[] parameters) { foreach (string parameter in parameters) { - int indexOfSpecialCharacter = parameter.IndexOfAny(XMakeElements.InvalidTargetNameCharacters); + int indexOfSpecialCharacter = parameter.AsSpan().IndexOfAny(XMakeElements.InvalidTargetNameCharacters); if (indexOfSpecialCharacter >= 0) { CommandLineSwitchException.Throw("NameInvalid", nameof(XMakeElements.target), parameter, parameter[indexOfSpecialCharacter].ToString()); @@ -3737,11 +3737,6 @@ private static string[] ProcessTargetSwitch(string[] parameters) /// private static readonly char[] s_propertyValueSeparator = MSBuildConstants.EqualsChar; - /// - /// This is a set of wildcard chars which can cause a file extension to be invalid - /// - private static readonly char[] s_wildcards = MSBuildConstants.WildcardChars; - /// /// Determines which ToolsVersion was specified on the command line. If more than /// one ToolsVersion was specified, we honor only the final ToolsVersion. @@ -4531,6 +4526,7 @@ private static void ReplayBinaryLog( } } +#if FEATURE_XML_SCHEMA_VALIDATION /// /// Figures out if the project needs to be validated against a schema. /// @@ -4551,6 +4547,7 @@ private static string ProcessValidateSwitch(string[] parameters) return schemaFile; } +#endif /// /// Given an invalid ToolsVersion string and the collection of valid toolsets, diff --git a/src/Package/MSBuild.VSSetup/files.swr b/src/Package/MSBuild.VSSetup/files.swr index ee8123a3994..a9e28eea646 100644 --- a/src/Package/MSBuild.VSSetup/files.swr +++ b/src/Package/MSBuild.VSSetup/files.swr @@ -22,7 +22,6 @@ vs.relatedProcessFiles folder InstallDir:\MSBuild\Current file source=$(X86BinPath)Microsoft.Common.props - file source=$(X86BinPath)Microsoft.VisualStudioVersion.v17.Common.props file source=$(ThirdPartyNotice) folder InstallDir:\MSBuild\Current\Bin diff --git a/src/Package/Microsoft.Build.UnGAC/Microsoft.Build.UnGAC.csproj b/src/Package/Microsoft.Build.UnGAC/Microsoft.Build.UnGAC.csproj index 6282c3a2134..0d02925d4e9 100644 --- a/src/Package/Microsoft.Build.UnGAC/Microsoft.Build.UnGAC.csproj +++ b/src/Package/Microsoft.Build.UnGAC/Microsoft.Build.UnGAC.csproj @@ -24,6 +24,6 @@ - + \ No newline at end of file diff --git a/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj b/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj index 684e80213ae..ec425f3f969 100644 --- a/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj +++ b/src/Samples/ProjectCachePlugin/ProjectCachePlugin.csproj @@ -1,4 +1,5 @@ + true false @@ -7,17 +8,20 @@ $(LatestDotNetCoreForMSBuild) $(FullFrameworkTFM);$(LatestDotNetCoreForMSBuild) + + - - - - + + + + + diff --git a/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs b/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs index 6820a134454..a15f6e62113 100644 --- a/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs +++ b/src/Shared/AssemblyFolders/AssemblyFoldersEx.cs @@ -82,7 +82,7 @@ internal AssemblyFoldersEx( return; } - bool is64bitOS = EnvironmentUtilities.Is64BitOperatingSystem; + bool is64bitOS = Environment.Is64BitOperatingSystem; bool targeting64bit = targetProcessorArchitecture == ProcessorArchitecture.Amd64 || targetProcessorArchitecture == ProcessorArchitecture.IA64; // The registry lookup should be as follows: @@ -367,7 +367,7 @@ internal static List GatherVersionStrings(string ta // Loop over versions from registry. foreach (string version in versions) { - if ((version.Length > 0) && (String.Equals(version.Substring(0, 1), "v", StringComparison.OrdinalIgnoreCase))) + if ((version.Length > 0) && version[0] is 'v' or 'V') { Version candidateVersion = VersionUtilities.ConvertToVersion(version); diff --git a/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs b/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs index b546b28ccb9..4f79794879d 100644 --- a/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs +++ b/src/Shared/AssemblyFolders/AssemblyFoldersFromConfig.cs @@ -41,7 +41,7 @@ internal AssemblyFoldersFromConfig(string configFile, string targetRuntimeVersio // Platform-agnostic folders first. FindDirectories(assemblyTargets, target => string.IsNullOrEmpty(target.Platform)); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { if (targeting64Bit) { diff --git a/src/Shared/AssemblyNameExtension.cs b/src/Shared/AssemblyNameExtension.cs index f29b9e8e443..9f4b12b918e 100644 --- a/src/Shared/AssemblyNameExtension.cs +++ b/src/Shared/AssemblyNameExtension.cs @@ -577,6 +577,11 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 baseLenThat = asString2.Length; } +#if NET + ReadOnlySpan nameThis = asString1.AsSpan(0, baseLenThis); + ReadOnlySpan nameThat = asString2.AsSpan(0, baseLenThat); + return nameThis.CompareTo(nameThat, StringComparison.OrdinalIgnoreCase); +#else // If the lengths are the same then we can compare without copying. if (baseLenThis == baseLenThat) { @@ -587,6 +592,7 @@ private static int CompareBaseNamesStringWise(string asString1, string asString2 string nameThis = asString1.Substring(0, baseLenThis); string nameThat = asString2.Substring(0, baseLenThat); return string.Compare(nameThis, nameThat, StringComparison.OrdinalIgnoreCase); +#endif } /// @@ -778,24 +784,18 @@ internal bool ComparePublicKeyToken(AssemblyNameExtension that) /// internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) { +#if NET + return aPKT.AsSpan().SequenceEqual(bPKT.AsSpan()); +#else // Some assemblies (real case was interop assembly) may have null PKTs. - if (aPKT == null) - { -#pragma warning disable CA1825 // Avoid zero-length array allocations - aPKT = new byte[0]; -#pragma warning restore CA1825 // Avoid zero-length array allocations - } - if (bPKT == null) - { -#pragma warning disable CA1825 // Avoid zero-length array allocations - bPKT = new byte[0]; -#pragma warning restore CA1825 // Avoid zero-length array allocations - } + aPKT ??= []; + bPKT ??= []; if (aPKT.Length != bPKT.Length) { return false; } + for (int i = 0; i < aPKT.Length; ++i) { if (aPKT[i] != bPKT[i]) @@ -803,7 +803,9 @@ internal static bool ComparePublicKeyTokens(byte[] aPKT, byte[] bPKT) return false; } } + return true; +#endif } /// @@ -987,8 +989,8 @@ public void Translate(ITranslator translator) // TODO: consider some kind of protection against infinite loop during serialization, hint: pre serialize check for cycle in graph translator.TranslateHashSet(ref remappedFrom, - (ITranslator t) => new AssemblyNameExtension(t), - (int capacity) => CreateRemappedFrom()); + (t) => new AssemblyNameExtension(t), + (capacity) => CreateRemappedFrom()); } } } diff --git a/src/Shared/AwaitExtensions.cs b/src/Shared/AwaitExtensions.cs index 711d2dde947..f864c62bb23 100644 --- a/src/Shared/AwaitExtensions.cs +++ b/src/Shared/AwaitExtensions.cs @@ -19,7 +19,7 @@ internal static class AwaitExtensions /// /// Synchronizes access to the staScheduler field. /// - private static Object s_staSchedulerSync = new Object(); + private static readonly Object s_staSchedulerSync = new Object(); /// /// The singleton STA scheduler object. @@ -155,7 +155,7 @@ private class OneSTAThreadPerTaskScheduler : TaskScheduler /// /// The current queue of tasks. /// - private ConcurrentQueue _queuedTasks = new ConcurrentQueue(); + private readonly ConcurrentQueue _queuedTasks = new ConcurrentQueue(); /// /// Returns the list of queued tasks. diff --git a/src/Shared/BuildEnvironmentHelper.cs b/src/Shared/BuildEnvironmentHelper.cs index c3615e4acf6..f93696c6aba 100644 --- a/src/Shared/BuildEnvironmentHelper.cs +++ b/src/Shared/BuildEnvironmentHelper.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Diagnostics; using System.IO; using System.Linq; using System.Text.RegularExpressions; diff --git a/src/Shared/CanonicalError.cs b/src/Shared/CanonicalError.cs index 011818c3f1c..4da40e7054f 100644 --- a/src/Shared/CanonicalError.cs +++ b/src/Shared/CanonicalError.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +#if NET +using System.Buffers; +#endif using System.Globalization; using System.Text.RegularExpressions; @@ -49,99 +52,141 @@ namespace Microsoft.Build.Shared /// /// <text> : warning [num]: <msg> /// - internal static class CanonicalError + internal static partial class CanonicalError { // Defines the main pattern for matching messages. - private static readonly Lazy s_originCategoryCodeTextExpression = new Lazy( - () => new Regex( - // Beginning of line and any amount of whitespace. - @"^\s*" - // Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or - // string with no colon followed by a colon. - + @"(((?(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" - // Origin may also be empty. In this case there's no trailing colon. - + "|())" - // Match the empty string or a string without a colon that ends with a space - + "(?(()|([^:]*? )))" - // Match 'error' or 'warning'. - + @"(?(error|warning))" - // Match anything starting with a space that's not a colon/space, followed by a colon. - // Error code is optional in which case "error"/"warning" can be followed immediately by a colon. - + @"( \s*(?[^: ]*))?\s*:" - // Whatever's left on this line, including colons. - + "(?.*)$", - RegexOptions.IgnoreCase | RegexOptions.Compiled)); - - private static readonly Lazy s_originCategoryCodeTextExpression2 = new Lazy( - () => new Regex( - @"^\s*(?(?.*):(?(?[0-9]*):(?[0-9]*))):(? error| warning):(?.*)", - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string OriginCategoryCodeTextExpressionPattern = + // Beginning of line and any amount of whitespace. + @"^\s*" + // Match a [optional project number prefix 'ddd>'], single letter + colon + remaining filename, or + // string with no colon followed by a colon. + + @"(((?(((\d+>)?[a-zA-Z]?:[^:]*)|([^:]*))):)" + // Origin may also be empty. In this case there's no trailing colon. + + "|())" + // Match the empty string or a string without a colon that ends with a space + + "(?(()|([^:]*? )))" + // Match 'error' or 'warning'. + + @"(?(error|warning))" + // Match anything starting with a space that's not a colon/space, followed by a colon. + // Error code is optional in which case "error"/"warning" can be followed immediately by a colon. + + @"( \s*(?[^: ]*))?\s*:" + // Whatever's left on this line, including colons. + + "(?.*)$"; + + private const string OriginCategoryCodeTextExpression2Pattern = + @"^\s*(?(?.*):(?(?[0-9]*):(?[0-9]*))):(? error| warning):(?.*)"; // Matches and extracts filename and location from an 'origin' element. - private static readonly Lazy s_filenameLocationFromOrigin = new Lazy( - () => new Regex( - "^" // Beginning of line - + @"(\d+>)?" // Optional ddd> project number prefix - + "(?.*)" // Match anything. - + @"\(" // Find a parenthesis. - + @"(?[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-' - + @"\)\s*" // Find the closing paren then any amount of spaces. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string FilenameLocationFromOriginPattern = + "^" // Beginning of line + + @"(\d+>)?" // Optional ddd> project number prefix + + "(?.*)" // Match anything. + + @"\(" // Find a parenthesis. + + @"(?[\,,0-9,-]*)" // Match any combination of numbers and ',' and '-' + + @"\)\s*" // Find the closing paren then any amount of spaces. + + "$"; // End-of-line // Matches location that is a simple number. - private static readonly Lazy s_lineFromLocation = new Lazy( - () => new Regex( // Example: line - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineFromLocationPattern = // Example: line + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a range of lines. - private static readonly Lazy s_lineLineFromLocation = new Lazy( - () => new Regex( // Example: line-line - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "-" // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineLineFromLocationPattern = // Example: line-line + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "-" // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a line and column - private static readonly Lazy s_lineColFromLocation = new Lazy( - () => new Regex( // Example: line,col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColFromLocationPattern = // Example: line,col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is a line and column-range - private static readonly Lazy s_lineColColFromLocation = new Lazy( - () => new Regex( // Example: line,col-col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "-" // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColColFromLocationPattern = // Example: line,col-col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "-" // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line // Matches location that is line,col,line,col - private static readonly Lazy s_lineColLineColFromLocation = new Lazy( - () => new Regex( // Example: line,col,line,col - "^" // Beginning of line - + "(?[0-9]*)" // Match any number. - + "," // Comma - + "(?[0-9]*)" // Match any number. - + "," // Dash - + "(?[0-9]*)" // Match any number. - + "," // Dash - + "(?[0-9]*)" // Match any number. - + "$", // End-of-line - RegexOptions.IgnoreCase | RegexOptions.Compiled)); + private const string LineColLineColFromLocationPattern = // Example: line,col,line,col + "^" // Beginning of line + + "(?[0-9]*)" // Match any number. + + "," // Comma + + "(?[0-9]*)" // Match any number. + + "," // Dash + + "(?[0-9]*)" // Match any number. + + "," // Dash + + "(?[0-9]*)" // Match any number. + + "$"; // End-of-line + +#if NET + [GeneratedRegex(OriginCategoryCodeTextExpressionPattern, RegexOptions.IgnoreCase)] + private static partial Regex OriginCategoryCodeTextExpression { get; } + + [GeneratedRegex(OriginCategoryCodeTextExpression2Pattern, RegexOptions.IgnoreCase)] + private static partial Regex OriginCategoryCodeTextExpression2 { get; } + + [GeneratedRegex(FilenameLocationFromOriginPattern, RegexOptions.IgnoreCase)] + private static partial Regex FilenameLocationFromOrigin { get; } + + [GeneratedRegex(LineFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineFromLocation { get; } + + [GeneratedRegex(LineLineFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineLineFromLocation { get; } + + [GeneratedRegex(LineColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColFromLocation { get; } + + [GeneratedRegex(LineColColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColColFromLocation { get; } + + [GeneratedRegex(LineColLineColFromLocationPattern, RegexOptions.IgnoreCase)] + private static partial Regex LineColLineColFromLocation { get; } +#else + private static Regex s_originCategoryCodeTextExpression; + private static Regex OriginCategoryCodeTextExpression => s_originCategoryCodeTextExpression ??= + new Regex(OriginCategoryCodeTextExpressionPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_originCategoryCodeTextExpression2; + private static Regex OriginCategoryCodeTextExpression2 => s_originCategoryCodeTextExpression2 ??= + new Regex(OriginCategoryCodeTextExpression2Pattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_filenameLocationFromOrigin; + private static Regex FilenameLocationFromOrigin => s_filenameLocationFromOrigin ??= + new Regex(FilenameLocationFromOriginPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineFromLocation; + private static Regex LineFromLocation => s_lineFromLocation ??= + new Regex(LineFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineLineFromLocation; + private static Regex LineLineFromLocation => s_lineLineFromLocation ??= + new Regex(LineLineFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColFromLocation; + private static Regex LineColFromLocation => s_lineColFromLocation ??= + new Regex(LineColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColColFromLocation; + private static Regex LineColColFromLocation => s_lineColColFromLocation ??= + new Regex(LineColColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); + + private static Regex s_lineColLineColFromLocation; + private static Regex LineColLineColFromLocation => s_lineColLineColFromLocation ??= + new Regex(LineColLineColFromLocationPattern, RegexOptions.IgnoreCase | RegexOptions.Compiled); +#endif /// /// Represents the parts of a decomposed canonical message. @@ -234,6 +279,10 @@ private static int ConvertToIntWithDefault(string value) return result; } +#if NET + private static readonly SearchValues s_warningOrError = SearchValues.Create(["warning", "error"], StringComparison.OrdinalIgnoreCase); +#endif + /// /// Decompose an error or warning message into constituent parts. If the message isn't in the canonical form, return null. /// @@ -256,8 +305,12 @@ internal static Parts Parse(string message) // If a tool has a large amount of output that isn't an error or warning (eg., "dir /s %hugetree%") // the regex below is slow. It's faster to pre-scan for "warning" and "error" // and bail out if neither are present. - if (message.IndexOf("warning", StringComparison.OrdinalIgnoreCase) == -1 && - message.IndexOf("error", StringComparison.OrdinalIgnoreCase) == -1) +#if NET + if (message.AsSpan().IndexOfAny(s_warningOrError) < 0) +#else + if (message.IndexOf("warning", StringComparison.OrdinalIgnoreCase) < 0 && + message.IndexOf("error", StringComparison.OrdinalIgnoreCase) < 0) +#endif { return null; } @@ -283,7 +336,7 @@ internal static Parts Parse(string message) // Here's an example from the Japanese version of LINK.EXE: // AssemblyInfo.cpp : fatal error LNK1106: ???????????? ??????????????: 0x6580 ?????????? // - Match match = s_originCategoryCodeTextExpression.Value.Match(message); + Match match = OriginCategoryCodeTextExpression.Match(message); string category; if (!match.Success) { @@ -292,7 +345,7 @@ internal static Parts Parse(string message) // err.cpp:6:3: error: use of undeclared identifier 'force_an_error' // ----------- ----- --------------------------------------------- // Origin Cat. Text - match = s_originCategoryCodeTextExpression2.Value.Match(message); + match = OriginCategoryCodeTextExpression2.Match(message); if (!match.Success) { return null; @@ -320,7 +373,7 @@ internal static Parts Parse(string message) string[] explodedText = parsedMessage.text.Split(MSBuildConstants.SingleQuoteChar, StringSplitOptions.RemoveEmptyEntries); if (explodedText.Length > 0) { - parsedMessage.code = "G" + explodedText[0].GetHashCode().ToString("X8"); + parsedMessage.code = $"G{explodedText[0].GetHashCode():X8}"; } else { @@ -353,7 +406,7 @@ internal static Parts Parse(string message) // Origin is not a simple file, but it still could be of the form, // foo.cpp(location) - match = s_filenameLocationFromOrigin.Value.Match(origin); + match = FilenameLocationFromOrigin.Match(origin); if (match.Success) { @@ -373,14 +426,14 @@ internal static Parts Parse(string message) // (line,col,line,col) if (location.Length > 0) { - match = s_lineFromLocation.Value.Match(location); + match = LineFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); } else { - match = s_lineLineFromLocation.Value.Match(location); + match = LineLineFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -388,7 +441,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColFromLocation.Value.Match(location); + match = LineColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -396,7 +449,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColColFromLocation.Value.Match(location); + match = LineColColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); @@ -405,7 +458,7 @@ internal static Parts Parse(string message) } else { - match = s_lineColLineColFromLocation.Value.Match(location); + match = LineColLineColFromLocation.Match(location); if (match.Success) { parsedMessage.line = ConvertToIntWithDefault(match.Groups["LINE"].Value.Trim()); diff --git a/src/Shared/CommunicationsUtilities.cs b/src/Shared/CommunicationsUtilities.cs index 12061206c4d..fe93672dee4 100644 --- a/src/Shared/CommunicationsUtilities.cs +++ b/src/Shared/CommunicationsUtilities.cs @@ -7,7 +7,9 @@ using System.Globalization; using System.IO; using System.IO.Pipes; +#if NETFRAMEWORK using System.Runtime.InteropServices; +#endif #if FEATURE_SECURITY_PRINCIPAL_WINDOWS using System.Security.Principal; #endif @@ -99,10 +101,10 @@ protected internal Handshake(HandshakeOptions nodeType) CommunicationsUtilities.Trace("Building handshake for node type {0}, (version {1}): options {2}.", nodeType, handshakeVersion, options); string handshakeSalt = Environment.GetEnvironmentVariable("MSBUILDNODEHANDSHAKESALT"); - CommunicationsUtilities.Trace("Handshake salt is " + handshakeSalt); + CommunicationsUtilities.Trace("Handshake salt is {0}", handshakeSalt); string toolsDirectory = BuildEnvironmentHelper.Instance.MSBuildToolsDirectoryRoot; - CommunicationsUtilities.Trace("Tools directory root is " + toolsDirectory); - salt = CommunicationsUtilities.GetHashCode(handshakeSalt + toolsDirectory); + CommunicationsUtilities.Trace("Tools directory root is {0}", toolsDirectory); + salt = CommunicationsUtilities.GetHashCode($"{handshakeSalt}{toolsDirectory}"); Version fileVersion = new Version(FileVersionInfo.GetVersionInfo(Assembly.GetExecutingAssembly().Location).FileVersion); fileVersionMajor = fileVersion.Major; fileVersionMinor = fileVersion.Minor; @@ -115,7 +117,7 @@ protected internal Handshake(HandshakeOptions nodeType) // This is used as a key, so it does not need to be human readable. public override string ToString() { - return String.Format("{0} {1} {2} {3} {4} {5} {6}", options, salt, fileVersionMajor, fileVersionMinor, fileVersionBuild, fileVersionPrivate, sessionId); + return $"{options} {salt} {fileVersionMajor} {fileVersionMinor} {fileVersionBuild} {fileVersionPrivate} {sessionId}"; } public virtual int[] RetrieveHandshakeComponents() @@ -178,8 +180,14 @@ public string ComputeHash() if (_computedHash == null) { var input = GetKey(); + byte[] utf8 = Encoding.UTF8.GetBytes(input); +#if NET + Span bytes = stackalloc byte[SHA256.HashSizeInBytes]; + SHA256.HashData(utf8, bytes); +#else using var sha = SHA256.Create(); - var bytes = sha.ComputeHash(Encoding.UTF8.GetBytes(input)); + var bytes = sha.ComputeHash(utf8); +#endif _computedHash = Convert.ToBase64String(bytes) .Replace("/", "_") .Replace("=", string.Empty); @@ -211,7 +219,7 @@ internal static class CommunicationsUtilities /// /// Whether to trace communications /// - private static bool s_trace = Traits.Instance.DebugNodeCommunication; + private static readonly bool s_trace = Traits.Instance.DebugNodeCommunication; /// /// Lock trace to ensure we are logging in serial fashion. @@ -581,7 +589,7 @@ internal static int ReadIntForHandshake(this PipeStream stream, byte? byteToAcce #nullable disable #if !FEATURE_APM - internal static async Task ReadAsync(Stream stream, byte[] buffer, int bytesToRead) + internal static async ValueTask ReadAsync(Stream stream, byte[] buffer, int bytesToRead) { int totalBytesRead = 0; while (totalBytesRead < bytesToRead) diff --git a/src/Shared/Constants.cs b/src/Shared/Constants.cs index e435d354935..4aa800ef2d2 100644 --- a/src/Shared/Constants.cs +++ b/src/Shared/Constants.cs @@ -2,6 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +#if NET +using System.Buffers; +#endif using System.IO; #nullable disable @@ -112,25 +115,31 @@ internal static class MSBuildConstants internal const string ProjectReferenceTargetsOrDefaultTargetsMarker = ".projectReferenceTargetsOrDefaultTargets"; // One-time allocations to avoid implicit allocations for Split(), Trim(). - internal static readonly char[] SemicolonChar = { ';' }; - internal static readonly char[] SpaceChar = { ' ' }; - internal static readonly char[] SingleQuoteChar = { '\'' }; - internal static readonly char[] EqualsChar = { '=' }; - internal static readonly char[] ColonChar = { ':' }; - internal static readonly char[] BackslashChar = { '\\' }; - internal static readonly char[] NewlineChar = { '\n' }; - internal static readonly char[] CrLf = { '\r', '\n' }; - internal static readonly char[] ForwardSlash = { '/' }; - internal static readonly char[] ForwardSlashBackslash = { '/', '\\' }; - internal static readonly char[] WildcardChars = { '*', '?' }; - internal static readonly string[] CharactersForExpansion = { "*", "?", "$(", "@(", "%" }; - internal static readonly char[] CommaChar = { ',' }; - internal static readonly char[] HyphenChar = { '-' }; - internal static readonly char[] DirectorySeparatorChar = { Path.DirectorySeparatorChar }; - internal static readonly char[] DotChar = { '.' }; - internal static readonly string[] EnvironmentNewLine = { Environment.NewLine }; - internal static readonly char[] PipeChar = { '|' }; - internal static readonly char[] PathSeparatorChar = { Path.PathSeparator }; + internal static readonly char[] SemicolonChar = [';']; + internal static readonly char[] SpaceChar = [' ']; + internal static readonly char[] SingleQuoteChar = ['\'']; + internal static readonly char[] EqualsChar = ['=']; + internal static readonly char[] ColonChar = [':']; + internal static readonly char[] BackslashChar = ['\\']; + internal static readonly char[] NewlineChar = ['\n']; + internal static readonly char[] CrLf = ['\r', '\n']; + internal static readonly char[] ForwardSlash = ['/']; + internal static readonly char[] ForwardSlashBackslash = ['/', '\\']; + internal static readonly char[] WildcardChars = ['*', '?']; + internal static readonly string[] CharactersForExpansion = ["*", "?", "$(", "@(", "%"]; + internal static readonly char[] CommaChar = [',']; + internal static readonly char[] HyphenChar = ['-']; + internal static readonly char[] DirectorySeparatorChar = [Path.DirectorySeparatorChar]; + internal static readonly char[] DotChar = ['.']; + internal static readonly string[] EnvironmentNewLine = [Environment.NewLine]; + internal static readonly char[] PipeChar = ['|']; + internal static readonly char[] PathSeparatorChar = [Path.PathSeparator]; + +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars()); +#else + internal static readonly char[] InvalidPathChars = Path.GetInvalidPathChars(); +#endif } internal static class PropertyNames diff --git a/src/Shared/ConversionUtilities.cs b/src/Shared/ConversionUtilities.cs index 10bdc82790e..d04a52db675 100644 --- a/src/Shared/ConversionUtilities.cs +++ b/src/Shared/ConversionUtilities.cs @@ -3,7 +3,9 @@ using System; using System.Globalization; +#if !NET using System.Text; +#endif using Error = Microsoft.Build.Shared.ErrorUtilities; #nullable disable @@ -57,6 +59,9 @@ internal static bool ConvertStringToBool(string parameterValue, bool nullOrWhite /// A string byte types formated as X2. internal static string ConvertByteArrayToHex(byte[] bytes) { +#if NET + return Convert.ToHexString(bytes); +#else var sb = new StringBuilder(); foreach (var b in bytes) { @@ -64,6 +69,7 @@ internal static string ConvertByteArrayToHex(byte[] bytes) } return sb.ToString(); +#endif } internal static bool TryConvertStringToBool(string parameterValue, out bool boolValue) @@ -131,7 +137,13 @@ internal static double ConvertDecimalToDouble(string number) /// internal static double ConvertHexToDouble(string number) { - return (double)Int32.Parse(number.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); + return (double)Int32.Parse( +#if NET + number.AsSpan(2), +#else + number.Substring(2), +#endif + NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat); } /// @@ -172,9 +184,15 @@ private static bool ValidHexNumber(string number, out int value) { bool canConvert = false; value = 0; - if (number.Length >= 3 && number[0] == '0' && (number[1] == 'x' || number[1] == 'X')) + if (number.Length >= 3 && number[0] is '0' && number[1] is 'x' or 'X') { - canConvert = Int32.TryParse(number.Substring(2), NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out value); + canConvert = Int32.TryParse( +#if NET + number.AsSpan(2), +#else + number.Substring(2), +#endif + NumberStyles.AllowHexSpecifier, CultureInfo.InvariantCulture.NumberFormat, out value); } return canConvert; } diff --git a/src/Shared/Debugging/PrintLineDebugger.cs b/src/Shared/Debugging/PrintLineDebugger.cs index 0702770278a..11618bc3cc6 100644 --- a/src/Shared/Debugging/PrintLineDebugger.cs +++ b/src/Shared/Debugging/PrintLineDebugger.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +#if DEBUG using System.IO; +#endif using System.Reflection; using System.Runtime.CompilerServices; using Microsoft.Build.Framework; @@ -38,13 +40,17 @@ internal sealed class PrintLineDebugger : IDisposable public static Lazy DefaultWithProcessInfo = new Lazy(() => Create(null, null, true)); +#if DEBUG private readonly string _id; +#endif private readonly CommonWriterType _writerSetByThisInstance; public PrintLineDebugger(string id, CommonWriterType writer) { +#if DEBUG _id = id ?? string.Empty; +#endif if (writer != null) { @@ -145,10 +151,12 @@ public void Log( #endif } +#if DEBUG private static string CallsiteString(string sourceFilePath, string memberName, int sourceLineNumber) { return $"@{Path.GetFileNameWithoutExtension(sourceFilePath)}.{memberName}({sourceLineNumber})"; } +#endif private void ReleaseUnmanagedResources() { diff --git a/src/Shared/Debugging/PrintLineDebuggerWriters.cs b/src/Shared/Debugging/PrintLineDebuggerWriters.cs index e0dc425c320..bcdadc22a24 100644 --- a/src/Shared/Debugging/PrintLineDebuggerWriters.cs +++ b/src/Shared/Debugging/PrintLineDebuggerWriters.cs @@ -68,7 +68,7 @@ public CompositeWriter(IEnumerable writers) public static CommonWriterType StdOutWriter = (id, callsite, args) => Console.WriteLine(SimpleFormat(id, callsite, args)); - private static Lazy _artifactsLogs = new Lazy( + private static readonly Lazy _artifactsLogs = new Lazy( () => { var executingAssembly = FileUtilities.ExecutingAssemblyPath; diff --git a/src/Shared/EscapingUtilities.cs b/src/Shared/EscapingUtilities.cs index f84ddf86632..8bde0027840 100644 --- a/src/Shared/EscapingUtilities.cs +++ b/src/Shared/EscapingUtilities.cs @@ -26,7 +26,7 @@ internal static class EscapingUtilities /// Optional cache of escaped strings for use when needing to escape in performance-critical scenarios with significant /// expected string reuse. /// - private static Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); + private static readonly Dictionary s_unescapedToEscapedStrings = new Dictionary(StringComparer.Ordinal); private static bool TryDecodeHexDigit(char character, out int value) { diff --git a/src/Shared/ExceptionHandling.cs b/src/Shared/ExceptionHandling.cs index 16a19dcadc2..a802379a8e4 100644 --- a/src/Shared/ExceptionHandling.cs +++ b/src/Shared/ExceptionHandling.cs @@ -8,7 +8,6 @@ namespace Microsoft.Build.AppxPackage.Shared #else using System; using System.Collections.Generic; -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; diff --git a/src/Shared/FileMatcher.cs b/src/Shared/FileMatcher.cs index d6cd177e8ad..9d97c12de8d 100644 --- a/src/Shared/FileMatcher.cs +++ b/src/Shared/FileMatcher.cs @@ -26,14 +26,19 @@ internal class FileMatcher private readonly IFileSystem _fileSystem; private const string recursiveDirectoryMatch = "**"; - private static readonly string s_directorySeparator = new string(Path.DirectorySeparatorChar, 1); + private static readonly string s_directorySeparatorString = Path.DirectorySeparatorChar.ToString(); + private static readonly string s_twoDirectorySeparators = s_directorySeparatorString + s_directorySeparatorString; - private static readonly string s_thisDirectory = "." + s_directorySeparator; + private static readonly string s_thisDirectory = $".{s_directorySeparatorString}"; private static readonly char[] s_wildcardCharacters = { '*', '?' }; private static readonly char[] s_wildcardAndSemicolonCharacters = { '*', '?', ';' }; - private static readonly string[] s_propertyAndItemReferences = { "$(", "@(" }; +#if NET + private static readonly SearchValues s_propertyAndItemReferences = SearchValues.Create(["$(", "@("], StringComparison.Ordinal); +#else + private static readonly string[] s_propertyAndItemReferences = ["$(", "@("]; +#endif // on OSX both System.IO.Path separators are '/', so we have to use the literals internal static readonly char[] directorySeparatorCharacters = FileUtilities.Slashes; @@ -45,12 +50,6 @@ internal class FileMatcher private readonly ConcurrentDictionary> _cachedGlobExpansions; private readonly Lazy> _cachedGlobExpansionsLock = new Lazy>(() => new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase)); - /// - /// Cache of the list of invalid path characters, because this method returns a clone (for security reasons) - /// which can cause significant transient allocations - /// - private static readonly char[] s_invalidPathChars = Path.GetInvalidPathChars(); - public const RegexOptions DefaultRegexOptions = RegexOptions.IgnoreCase; private readonly GetFileSystemEntries _getFileSystemEntries; @@ -186,7 +185,7 @@ internal static bool HasWildcards(string filespec) // Choose LastIndexOfAny instead of IndexOfAny because it seems more likely // that wildcards will tend to be towards the right side. - return -1 != filespec.LastIndexOfAny(s_wildcardCharacters); + return filespec.LastIndexOfAny(s_wildcardCharacters) >= 0; } /// @@ -195,10 +194,8 @@ internal static bool HasWildcards(string filespec) internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filespec) { return - - (-1 != filespec.IndexOfAny(s_wildcardAndSemicolonCharacters)) || - HasPropertyOrItemReferences(filespec) - ; + (filespec.IndexOfAny(s_wildcardAndSemicolonCharacters) >= 0) || + HasPropertyOrItemReferences(filespec); } /// @@ -206,7 +203,12 @@ internal static bool HasWildcardsSemicolonItemOrPropertyReferences(string filesp /// internal static bool HasPropertyOrItemReferences(string filespec) { - return s_propertyAndItemReferences.Any(filespec.Contains); + return +#if NET + filespec.AsSpan().ContainsAny(s_propertyAndItemReferences); +#else + s_propertyAndItemReferences.Any(filespec.Contains); +#endif } /// @@ -288,10 +290,10 @@ private static bool ShouldEnforceMatching(string searchPattern) // extensions that start with the same three characters e.g. "*.htm" would match both "file.htm" and "file.html" // 3) if the ? wildcard is to the left of a period, it matches files with shorter name e.g. ???.txt would match // foo.txt, fo.txt and also f.txt - return searchPattern.IndexOf("?.", StringComparison.Ordinal) != -1 || + return searchPattern.Contains("?.") || ( Path.GetExtension(searchPattern).Length == (3 + 1 /* +1 for the period */) && - searchPattern.IndexOf('*') != -1) || + searchPattern.Contains('*')) || searchPattern.EndsWith("?", StringComparison.Ordinal); } @@ -440,7 +442,7 @@ internal static string GetLongPathName( string path, GetFileSystemEntries getFileSystemEntries) { - if (path.IndexOf("~", StringComparison.Ordinal) == -1) + if (!path.Contains('~')) { // A path with no '~' must not be a short name. return path; @@ -451,15 +453,11 @@ internal static string GetLongPathName( string[] parts = path.Split(directorySeparatorCharacters); string pathRoot; - bool isUnc = path.StartsWith(s_directorySeparator + s_directorySeparator, StringComparison.Ordinal); + bool isUnc = path.StartsWith(s_twoDirectorySeparators, StringComparison.Ordinal); int startingElement; if (isUnc) { - pathRoot = s_directorySeparator + s_directorySeparator; - pathRoot += parts[2]; - pathRoot += s_directorySeparator; - pathRoot += parts[3]; - pathRoot += s_directorySeparator; + pathRoot = $"{s_twoDirectorySeparators}{parts[2]}{s_directorySeparatorString}{parts[3]}{s_directorySeparatorString}"; startingElement = 4; } else @@ -468,7 +466,7 @@ internal static string GetLongPathName( if (path.Length > 2 && path[1] == ':') { // Not relative - pathRoot = parts[0] + s_directorySeparator; + pathRoot = parts[0] + s_directorySeparatorString; startingElement = 1; } else @@ -493,7 +491,7 @@ internal static string GetLongPathName( } else { - if (parts[i].IndexOf("~", StringComparison.Ordinal) == -1) + if (!parts[i].Contains('~')) { // If there's no ~, don't hit the disk. longParts[i - startingElement] = parts[i]; @@ -529,7 +527,7 @@ internal static string GetLongPathName( } } - return pathRoot + string.Join(s_directorySeparator, longParts); + return pathRoot + string.Join(s_directorySeparatorString, longParts); } /// @@ -562,8 +560,7 @@ internal void SplitFileSpec( */ if (recursiveDirectoryMatch == filenamePart) { - wildcardDirectoryPart += recursiveDirectoryMatch; - wildcardDirectoryPart += s_directorySeparator; + wildcardDirectoryPart = $"{wildcardDirectoryPart}{recursiveDirectoryMatch}{s_directorySeparatorString}"; filenamePart = "*.*"; } @@ -1107,7 +1104,7 @@ private static RecursiveStepResult GetFilesRecursiveStep( // or we've reached the end of the wildcard directory elements, considerFiles = true; } - else if (recursionState.RemainingWildcardDirectory.IndexOf(recursiveDirectoryMatch, StringComparison.Ordinal) == 0) + else if (recursionState.RemainingWildcardDirectory.StartsWith(recursiveDirectoryMatch, StringComparison.Ordinal)) { // or, we've reached a "**" so everything else is matched recursively. considerFiles = true; @@ -1211,22 +1208,10 @@ internal static string RegularExpressionFromFileSpec( /// /// True if both parts meet all conditions for a legal filespec. private static bool IsLegalFileSpec(string wildcardDirectoryPart, string filenamePart) => - !HasDotDot(wildcardDirectoryPart) + !wildcardDirectoryPart.Contains("..") && !HasMisplacedRecursiveOperator(wildcardDirectoryPart) && !HasMisplacedRecursiveOperator(filenamePart); - private static bool HasDotDot(string str) - { - for (int i = 0; i < str.Length - 1; i++) - { - if (str[i] == '.' && str[i + 1] == '.') - { - return true; - } - } - return false; - } - private static bool HasMisplacedRecursiveOperator(string str) { for (int i = 0; i < str.Length - 1; i++) @@ -1585,7 +1570,7 @@ internal void GetFileSpecInfo( internal static bool RawFileSpecIsValid(string filespec) { // filespec cannot contain illegal characters - if (-1 != filespec.IndexOfAny(s_invalidPathChars)) + if (filespec.AsSpan().IndexOfAny(MSBuildConstants.InvalidPathChars) >= 0) { return false; } @@ -1595,7 +1580,7 @@ internal static bool RawFileSpecIsValid(string filespec) * * Any path with "..." in it is illegal. */ - if (-1 != filespec.IndexOf("...", StringComparison.Ordinal)) + if (filespec.Contains("...")) { return false; } @@ -1607,12 +1592,12 @@ internal static bool RawFileSpecIsValid(string filespec) * http://www.website.com * */ - int rightmostColon = filespec.LastIndexOf(":", StringComparison.Ordinal); + int rightmostColon = filespec.LastIndexOf(':'); if ( - -1 != rightmostColon - && 1 != rightmostColon) + rightmostColon >= 0 + && rightmostColon != 1) { return false; } @@ -2229,7 +2214,7 @@ internal static string Normalize(string aString) // replace multiple slashes with the OS separator else if (afterSlashesIndex > index) { - sb.Append(s_directorySeparator); + sb.Append(s_directorySeparatorString); } // skip non-slashes @@ -2526,7 +2511,7 @@ private static string[] CreateArrayWithSingleItemIfNotExcluded(string filespecUn Debug.Assert(excludeState.SearchData.RegexFileMatch != null || excludeState.SearchData.DirectoryPattern != null, "Expected Regex or directory pattern to be used for exclude file matching"); excludeState.BaseDirectory = state.BaseDirectory; - excludeState.RemainingWildcardDirectory = recursiveDirectoryMatch + s_directorySeparator; + excludeState.RemainingWildcardDirectory = recursiveDirectoryMatch + s_directorySeparatorString; searchesToExclude.Add(excludeState); } } diff --git a/src/Shared/FileSystem/IFileSystem.cs b/src/Shared/FileSystem/IFileSystem.cs index 191e348ee2d..8bfcb130067 100644 --- a/src/Shared/FileSystem/IFileSystem.cs +++ b/src/Shared/FileSystem/IFileSystem.cs @@ -38,7 +38,7 @@ internal interface IFileSystem FileAttributes GetAttributes(string path); - public DateTime GetLastWriteTimeUtc(string path); + DateTime GetLastWriteTimeUtc(string path); bool DirectoryExists(string path); diff --git a/src/Shared/FileSystem/ManagedFileSystem.cs b/src/Shared/FileSystem/ManagedFileSystem.cs index f2223635f80..53f3c7c4bc7 100644 --- a/src/Shared/FileSystem/ManagedFileSystem.cs +++ b/src/Shared/FileSystem/ManagedFileSystem.cs @@ -18,6 +18,7 @@ internal class ManagedFileSystem : IFileSystem public static ManagedFileSystem Singleton() => ManagedFileSystem.Instance; +#if FEATURE_MSIOREDIST private static bool ShouldUseMicrosoftIO { get @@ -31,6 +32,7 @@ private static bool ShouldUseMicrosoftIO #endif } } +#endif protected ManagedFileSystem() { } diff --git a/src/Shared/FileUtilities.cs b/src/Shared/FileUtilities.cs index 82d4f55b354..cb67ca26bec 100644 --- a/src/Shared/FileUtilities.cs +++ b/src/Shared/FileUtilities.cs @@ -7,6 +7,9 @@ #else using Microsoft.Build.Shared.Concurrent; #endif +#if NET +using System.Buffers; +#endif using System.Collections.Generic; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; @@ -71,7 +74,7 @@ public static bool GetIsFileSystemCaseSensitive() { try { - string pathWithUpperCase = Path.Combine(Path.GetTempPath(), "CASESENSITIVETEST" + Guid.NewGuid().ToString("N")); + string pathWithUpperCase = Path.Combine(Path.GetTempPath(), $"CASESENSITIVETEST{Guid.NewGuid():N}"); using (new FileStream(pathWithUpperCase, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, 0x1000, FileOptions.DeleteOnClose)) { string lowerCased = pathWithUpperCase.ToLowerInvariant(); @@ -91,20 +94,24 @@ public static bool GetIsFileSystemCaseSensitive() /// Copied from https://github.com/dotnet/corefx/blob/056715ff70e14712419d82d51c8c50c54b9ea795/src/Common/src/System/IO/PathInternal.Windows.cs#L61 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// - internal static readonly char[] InvalidPathChars = +#if NET + internal static readonly SearchValues InvalidPathChars = SearchValues.Create( +#else + internal static readonly char[] InvalidPathChars = ( +#endif [ '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, (char)11, (char)12, (char)13, (char)14, (char)15, (char)16, (char)17, (char)18, (char)19, (char)20, (char)21, (char)22, (char)23, (char)24, (char)25, (char)26, (char)27, (char)28, (char)29, (char)30, (char)31 - ]; + ]); /// /// Copied from https://github.com/dotnet/corefx/blob/387cf98c410bdca8fd195b28cbe53af578698f94/src/System.Runtime.Extensions/src/System/IO/Path.Windows.cs#L18 /// MSBuild should support the union of invalid path chars across the supported OSes, so builds can have the same behaviour crossplatform: https://github.com/dotnet/msbuild/issues/781#issuecomment-243942514 /// - internal static readonly char[] InvalidFileNameChars = + internal static readonly char[] InvalidFileNameCharsArray = [ '\"', '<', '>', '|', '\0', (char)1, (char)2, (char)3, (char)4, (char)5, (char)6, (char)7, (char)8, (char)9, (char)10, @@ -113,6 +120,12 @@ public static bool GetIsFileSystemCaseSensitive() (char)31, ':', '*', '?', '\\', '/' ]; +#if NET + internal static readonly SearchValues InvalidFileNameChars = SearchValues.Create(InvalidFileNameCharsArray); +#else + internal static char[] InvalidFileNameChars => InvalidFileNameCharsArray; +#endif + internal static readonly char[] Slashes = { '/', '\\' }; internal static readonly string DirectorySeparatorString = Path.DirectorySeparatorChar.ToString(); @@ -179,7 +192,7 @@ internal static bool CanWriteToDirectory(string directory) { try { - string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid().ToString("N")}_testFile.txt"); + string testFilePath = Path.Combine(directory, $"MSBuild_{Guid.NewGuid():N}_testFile.txt"); FileInfo file = new(testFilePath); file.Directory.Create(); // If the directory already exists, this method does nothing. File.WriteAllText(testFilePath, $"MSBuild process {EnvironmentUtilities.CurrentProcessId} successfully wrote to file."); @@ -258,7 +271,11 @@ internal static string EnsureTrailingNoLeadingSlash(string path, int start) return FixFilePath(start < stop && IsSlash(path[stop - 1]) ? path.Substring(start) : +#if NET + string.Concat(path.AsSpan(start), new(in Path.DirectorySeparatorChar))); +#else path.Substring(start) + Path.DirectorySeparatorChar); +#endif } /// @@ -315,7 +332,11 @@ internal static string EnsureQuotes(string path, bool isSingleQuote = true) // Special case: convert the quotes. if (path.Length > 1 && path[0] == convertQuote && path[path.Length - 1] == convertQuote) { +#if NET + path = $"{targetQuote}{path.AsSpan(1, path.Length - 2)}{targetQuote}"; +#else path = $"{targetQuote}{path.Substring(1, path.Length - 2)}{targetQuote}"; +#endif } // Enclose the path in a set of the 'target' quote unless the string is already quoted with the 'target' quotes. else if (path.Length == 1 || path[0] != targetQuote || path[path.Length - 1] != targetQuote) @@ -844,17 +865,23 @@ internal static string NormalizePathForComparisonNoThrow(string path, string cur internal static bool PathIsInvalid(string path) { - if (path.IndexOfAny(InvalidPathChars) >= 0) - { - return true; - } - // Path.GetFileName does not react well to malformed filenames. // For example, Path.GetFileName("a/b/foo:bar") returns bar instead of foo:bar // It also throws exceptions on illegal path characters - var lastDirectorySeparator = path.LastIndexOfAny(Slashes); - - return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; +#if NET + if (!path.AsSpan().ContainsAny(InvalidPathChars)) + { + int lastDirectorySeparator = path.LastIndexOfAny(Slashes); + return path.AsSpan(lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0).ContainsAny(InvalidFileNameChars); + } +#else + if (path.IndexOfAny(InvalidPathChars) < 0) + { + int lastDirectorySeparator = path.LastIndexOfAny(Slashes); + return path.IndexOfAny(InvalidFileNameChars, lastDirectorySeparator >= 0 ? lastDirectorySeparator + 1 : 0) >= 0; + } +#endif + return true; } /// @@ -1538,13 +1565,42 @@ internal static void ClearFileExistenceCache() internal static void ReadFromStream(this Stream stream, byte[] content, int startIndex, int length) { -#if NET7_0_OR_GREATER stream.ReadExactly(content, startIndex, length); -#else -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' - stream.Read(content, 0, length); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' -#endif } } } + +#if !NET +namespace System.IO +{ + internal static class StreamExtensions + { + internal static void ReadExactly(this Stream stream, byte[] buffer, int offset, int count) + { + if (buffer == null) + { + throw new ArgumentNullException(nameof(buffer)); + } + if (offset < 0) + { + throw new ArgumentOutOfRangeException(nameof(offset)); + } + if ((uint)count > buffer.Length - offset) + { + throw new ArgumentOutOfRangeException(nameof(count)); + } + + while (count > 0) + { + int read = stream.Read(buffer, offset, count); + if (read <= 0) + { + throw new EndOfStreamException(); + } + offset +=read; + count -= read; + } + } + } +} +#endif \ No newline at end of file diff --git a/src/Shared/FileUtilitiesRegex.cs b/src/Shared/FileUtilitiesRegex.cs index 76e283a1a2a..c35f1f9ed5f 100644 --- a/src/Shared/FileUtilitiesRegex.cs +++ b/src/Shared/FileUtilitiesRegex.cs @@ -52,7 +52,11 @@ internal static bool StartsWithDrivePattern(string pattern) // first character must be a letter, // second character must be a ":" return pattern.Length >= 2 && +#if NET + char.IsAsciiLetter(pattern[0]) && +#else ((pattern[0] >= 'A' && pattern[0] <= 'Z') || (pattern[0] >= 'a' && pattern[0] <= 'z')) && +#endif pattern[1] == ':'; } diff --git a/src/Shared/FrameworkLocationHelper.cs b/src/Shared/FrameworkLocationHelper.cs index 2bc28819c6e..506ba750ab3 100644 --- a/src/Shared/FrameworkLocationHelper.cs +++ b/src/Shared/FrameworkLocationHelper.cs @@ -97,7 +97,7 @@ internal static class FrameworkLocationHelper private const string dotNetFrameworkRegistryKeyV20 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionV20; internal static string dotNetFrameworkVersionFolderPrefixV30 = NativeMethodsShared.IsWindows ? "v3.0" : "3.0"; // v3.0 is for WinFx. - private static string s_dotNetFrameworkRegistryKeyV30 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV30 + "\\Setup"; + private static readonly string s_dotNetFrameworkRegistryKeyV30 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV30 + "\\Setup"; #if FEATURE_WIN32_REGISTRY private const string fallbackDotNetFrameworkSdkRegistryInstallPath = "SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows"; @@ -111,7 +111,7 @@ internal static class FrameworkLocationHelper private const string fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A = "HKEY_LOCAL_MACHINE\\" + dotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A; internal static string dotNetFrameworkVersionFolderPrefixV35 = NativeMethodsShared.IsWindows ? "v3.5" : "3.5"; // v3.5 is for Orcas. - private static string s_dotNetFrameworkRegistryKeyV35 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV35; + private static readonly string s_dotNetFrameworkRegistryKeyV35 = dotNetFrameworkSetupRegistryPath + "\\" + dotNetFrameworkVersionFolderPrefixV35; internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS10 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnWinSDK70A; internal const string fullDotNetFrameworkSdkRegistryKeyV35OnVS11 = fullDotNetFrameworkSdkRegistryPathForV35ToolsOnManagedToolsSDK80A; @@ -512,7 +512,7 @@ private static string FallbackDotNetFrameworkSdkInstallPath fallbackDotNetFrameworkSdkRegistryInstallPath, fallbackDotNetFrameworkSdkInstallKeyValue); - if (EnvironmentUtilities.Is64BitProcess && s_fallbackDotNetFrameworkSdkInstallPath == null) + if (Environment.Is64BitProcess && s_fallbackDotNetFrameworkSdkInstallPath == null) { // Since we're 64-bit, what we just checked was the 64-bit fallback key -- so now let's // check the 32-bit one too, just in case. @@ -773,8 +773,7 @@ internal static string FindDotNetFrameworkPath( { if (!NativeMethodsShared.IsWindows) { - if (!string.IsNullOrEmpty(prefix) - && prefix.Substring(0, 1).Equals("v", StringComparison.OrdinalIgnoreCase)) + if (!string.IsNullOrEmpty(prefix) && prefix[0] is 'v' or 'V') { prefix = prefix.Substring(1); } @@ -813,8 +812,12 @@ internal static string FindDotNetFrameworkPath( // the path is something like 'C:\MyPath\64\Framework64'. 9 = length of 'Framework', to make the index match // the location of the '64'. int indexOf64 = indexOfFramework64 + 9; - string tempLocation = baseLocation; - baseLocation = tempLocation.Substring(0, indexOf64) + tempLocation.Substring(indexOf64 + 2, tempLocation.Length - indexOf64 - 2); + baseLocation = +#if NET + string.Concat(baseLocation.AsSpan(0, indexOf64), baseLocation.AsSpan(indexOf64 + 2)); +#else + baseLocation.Substring(0, indexOf64) + baseLocation.Substring(indexOf64 + 2); +#endif } else if (indexOfFramework64 == -1 && architecture == DotNetFrameworkArchitecture.Bitness64) { @@ -1258,26 +1261,11 @@ private class DotNetFrameworkSpec private const string HKLM = "HKEY_LOCAL_MACHINE"; private const string MicrosoftSDKsRegistryKey = @"SOFTWARE\Microsoft\Microsoft SDKs"; - /// - /// The registry key of this .net framework, i.e. "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" for .net v4.5. - /// - private readonly string _dotNetFrameworkRegistryKey; - - /// - /// The name in registry to indicate that this .net framework is installed, i.e. "Install" for .net v4.5. - /// - private readonly string _dotNetFrameworkSetupRegistryInstalledName; - /// /// The key in registry to indicate the sdk tools folder, i.e. "WinSDK-NetFx40Tools-x86" for .net v4.5. /// private readonly string _dotNetFrameworkSdkRegistryToolsKey; - /// - /// The version of visual studio that shipped with this .net framework. - /// - private readonly Version _visualStudioVersion; - /// /// Does this .net framework include MSBuild? /// @@ -1295,9 +1283,24 @@ private class DotNetFrameworkSpec #if FEATURE_WIN32_REGISTRY /// - /// Cached path of the corresponding windows sdk. + /// The registry key of this .net framework, i.e. "SOFTWARE\Microsoft\NET Framework Setup\NDP\v4\Full" for .net v4.5. + /// + private readonly string _dotNetFrameworkRegistryKey; + + /// + /// The name in registry to indicate that this .net framework is installed, i.e. "Install" for .net v4.5. + /// + private readonly string _dotNetFrameworkSetupRegistryInstalledName; + + /// + /// /// Cached path of the corresponding windows sdk. /// private string _pathToWindowsSdk; + + /// + /// The version of visual studio that shipped with this .net framework. + /// + private readonly Version _visualStudioVersion; #endif /// @@ -1316,15 +1319,18 @@ public DotNetFrameworkSpec( Version visualStudioVersion = null) { this.Version = version; - this._visualStudioVersion = visualStudioVersion; - this._dotNetFrameworkRegistryKey = dotNetFrameworkRegistryKey; - this._dotNetFrameworkSetupRegistryInstalledName = dotNetFrameworkSetupRegistryInstalledName; this.DotNetFrameworkFolderPrefix = dotNetFrameworkVersionFolderPrefix; this._dotNetFrameworkSdkRegistryToolsKey = dotNetFrameworkSdkRegistryToolsKey; this.DotNetFrameworkSdkRegistryInstallationFolderName = dotNetFrameworkSdkRegistryInstallationFolderName; this._hasMsBuild = hasMSBuild; this._pathsToDotNetFramework = new ConcurrentDictionary(); this._pathsToDotNetFrameworkSdkTools = new ConcurrentDictionary(); + +#if FEATURE_WIN32_REGISTRY + this._dotNetFrameworkRegistryKey = dotNetFrameworkRegistryKey; + this._dotNetFrameworkSetupRegistryInstalledName = dotNetFrameworkSetupRegistryInstalledName; + this._visualStudioVersion = visualStudioVersion; +#endif } /// diff --git a/src/Shared/IElementLocation.cs b/src/Shared/IElementLocation.cs index 4824e758d86..57fb254271c 100644 --- a/src/Shared/IElementLocation.cs +++ b/src/Shared/IElementLocation.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using Microsoft.Build.BackEnd; -using Microsoft.Build.Framework; #nullable disable diff --git a/src/Shared/INodePacketFactory.cs b/src/Shared/INodePacketFactory.cs index c972e0408b5..63d469eb021 100644 --- a/src/Shared/INodePacketFactory.cs +++ b/src/Shared/INodePacketFactory.cs @@ -43,7 +43,14 @@ internal interface INodePacketFactory void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITranslator translator); /// - /// Routes the specified packet + /// Takes a serializer and deserializes the packet. + /// + /// The packet type. + /// The translator containing the data from which the packet should be reconstructed. + INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator); + + /// + /// Routes the specified packet. /// /// The node from which the packet was received. /// The packet to route. diff --git a/src/Shared/LoadedType.cs b/src/Shared/LoadedType.cs index 6b2f8aed5c8..6be0b228a75 100644 --- a/src/Shared/LoadedType.cs +++ b/src/Shared/LoadedType.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Execution; using Microsoft.Build.Framework; -#nullable disable namespace Microsoft.Build.Shared { @@ -47,7 +46,7 @@ internal LoadedType(Type type, AssemblyLoadInfo assemblyLoadInfo, Assembly loade // properties and reflect over them without needing them to be fully loaded, so it also isn't need for TaskHosts. // MetadataLoadContext-loaded Type objects don't support testing for inherited attributes, so we manually walk the BaseType chain. - Type t = type; + Type? t = type; while (t is not null) { if (CustomAttributeData.GetCustomAttributes(t).Any(attr => attr.AttributeType.Name.Equals(nameof(LoadInSeparateAppDomainAttribute)))) @@ -92,7 +91,7 @@ internal LoadedType(Type type, AssemblyLoadInfo assemblyLoadInfo, Assembly loade } // Check whether it's assignable to ITaskItem or ITaskItem[]. Simplify to just checking for ITaskItem. - Type pt = props[i].PropertyType; + Type? pt = props[i].PropertyType; if (pt.IsArray) { pt = pt.GetElementType(); @@ -101,9 +100,9 @@ internal LoadedType(Type type, AssemblyLoadInfo assemblyLoadInfo, Assembly loade bool isAssignableToITask = iTaskItemType.IsAssignableFrom(pt); Properties[i] = new ReflectableTaskPropertyInfo(props[i], outputAttribute, requiredAttribute, isAssignableToITask); - if (loadedViaMetadataLoadContext) + if (loadedViaMetadataLoadContext && PropertyAssemblyQualifiedNames != null) { - PropertyAssemblyQualifiedNames[i] = Properties[i].PropertyType.AssemblyQualifiedName; + PropertyAssemblyQualifiedNames[i] = Properties[i]?.PropertyType?.AssemblyQualifiedName?? string.Empty; } } #else @@ -143,7 +142,7 @@ private bool CheckForHardcodedSTARequirement() { AssemblyName assemblyName = Type.GetTypeInfo().Assembly.GetName(); Version lastVersionToForce = new Version(3, 5); - if (assemblyName.Version.CompareTo(lastVersionToForce) > 0) + if (assemblyName.Version?.CompareTo(lastVersionToForce) > 0) { if (String.Equals(assemblyName.Name, "PresentationBuildTasks", StringComparison.OrdinalIgnoreCase)) { @@ -180,7 +179,7 @@ private bool CheckForHardcodedSTARequirement() /// /// Assembly-qualified names for properties. Only has a value if this type was loaded using MetadataLoadContext. /// - internal string[] PropertyAssemblyQualifiedNames { get; private set; } + internal string[]? PropertyAssemblyQualifiedNames { get; private set; } /// /// Gets the assembly the type was loaded from. diff --git a/src/Shared/LogMessagePacketBase.cs b/src/Shared/LogMessagePacketBase.cs index 36e8e9db0df..cb7123038f0 100644 --- a/src/Shared/LogMessagePacketBase.cs +++ b/src/Shared/LogMessagePacketBase.cs @@ -1,4 +1,4 @@ -// Licensed to the .NET Foundation under one or more agreements. +// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. using System; @@ -10,6 +10,7 @@ using Microsoft.Build.Framework; #if !TASKHOST +using Microsoft.Build.Framework.Telemetry; using Microsoft.Build.Experimental.BuildCheck; #endif @@ -17,11 +18,6 @@ using Microsoft.Build.Collections; using Microsoft.Build.Framework.Profiler; using System.Collections; -using System.Linq; -#endif - -#if FEATURE_APPDOMAIN -using TaskEngineAssemblyResolver = Microsoft.Build.BackEnd.Logging.TaskEngineAssemblyResolver; #endif #nullable disable @@ -270,15 +266,17 @@ internal abstract class LogMessagePacketBase : INodePacket /// private static readonly int s_defaultPacketVersion = (Environment.Version.Major * 10) + Environment.Version.Minor; +#if TASKHOST /// /// Dictionary of methods used to read BuildEventArgs. /// - private static Dictionary s_readMethodCache = new Dictionary(); + private static readonly Dictionary s_readMethodCache = new Dictionary(); +#endif /// /// Dictionary of methods used to write BuildEventArgs. /// - private static Dictionary s_writeMethodCache = new Dictionary(); + private static readonly Dictionary s_writeMethodCache = new Dictionary(); /// /// Delegate for translating targetfinished events. @@ -430,14 +428,14 @@ internal void WriteToStream(ITranslator translator) bool eventCanSerializeItself = methodInfo != null; #if !TASKHOST && !MSBUILDENTRYPOINTEXE - if (_buildEvent is ProjectEvaluationStartedEventArgs - or ProjectEvaluationFinishedEventArgs - or ResponseFileUsedEventArgs) - { - // switch to serialization methods that we provide in this file - // and don't use the WriteToStream inherited from LazyFormattedBuildEventArgs - eventCanSerializeItself = false; - } + if (_buildEvent is ProjectEvaluationStartedEventArgs + or ProjectEvaluationFinishedEventArgs + or ResponseFileUsedEventArgs) + { + // switch to serialization methods that we provide in this file + // and don't use the WriteToStream inherited from LazyFormattedBuildEventArgs + eventCanSerializeItself = false; + } #endif translator.Translate(ref eventCanSerializeItself); @@ -478,6 +476,8 @@ internal void ReadFromStream(ITranslator translator) if (eventCanSerializeItself) { + +#if TASKHOST MethodInfo methodInfo = null; lock (s_readMethodCache) { @@ -492,6 +492,11 @@ internal void ReadFromStream(ITranslator translator) ArgsReaderDelegate readerMethod = (ArgsReaderDelegate)CreateDelegateRobust(typeof(ArgsReaderDelegate), _buildEvent, methodInfo); readerMethod(translator.Reader, packetVersion); + +#else + _buildEvent.CreateFromStream(translator.Reader, packetVersion); +#endif + if (_eventType == LoggingEventType.TargetFinishedEvent && _targetFinishedTranslator != null) { _targetFinishedTranslator(translator, (TargetFinishedEventArgs)_buildEvent); diff --git a/src/Shared/MSBuildLoadContext.cs b/src/Shared/MSBuildLoadContext.cs index 7427c5ed735..b5396133c08 100644 --- a/src/Shared/MSBuildLoadContext.cs +++ b/src/Shared/MSBuildLoadContext.cs @@ -6,7 +6,6 @@ using System.IO; using System.Reflection; using System.Runtime.Loader; -using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; namespace Microsoft.Build.Shared diff --git a/src/Shared/MSBuildNameIgnoreCaseComparer.cs b/src/Shared/MSBuildNameIgnoreCaseComparer.cs index 9517b5f2646..e8e9a65b9eb 100644 --- a/src/Shared/MSBuildNameIgnoreCaseComparer.cs +++ b/src/Shared/MSBuildNameIgnoreCaseComparer.cs @@ -64,6 +64,9 @@ public bool Equals(string compareToString, string constrainedString, int start, return false; } +#if NET + return compareToString.AsSpan().Equals(constrainedString.AsSpan(start, lengthToCompare), StringComparison.OrdinalIgnoreCase); +#else if (lengthToCompare != compareToString.Length) { return false; @@ -104,6 +107,7 @@ public bool Equals(string compareToString, string constrainedString, int start, } return true; +#endif } /// diff --git a/src/Shared/Modifiers.cs b/src/Shared/Modifiers.cs index a6c203525d2..d7e77955644 100644 --- a/src/Shared/Modifiers.cs +++ b/src/Shared/Modifiers.cs @@ -69,7 +69,7 @@ internal static class ItemSpecModifiers DefiningProjectExtension }; - private static HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); + private static readonly HashSet s_tableOfItemSpecModifiers = new HashSet(All, StringComparer.OrdinalIgnoreCase); /// /// Indicates if the given name is reserved for an item-spec modifier. diff --git a/src/Shared/NamedPipeUtil.cs b/src/Shared/NamedPipeUtil.cs index bf31c0193f6..25094e59035 100644 --- a/src/Shared/NamedPipeUtil.cs +++ b/src/Shared/NamedPipeUtil.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Diagnostics; using System.IO; namespace Microsoft.Build.Shared diff --git a/src/Shared/NodeEndpointOutOfProcBase.cs b/src/Shared/NodeEndpointOutOfProcBase.cs index 70629ecf2d8..fbaeb4dbde2 100644 --- a/src/Shared/NodeEndpointOutOfProcBase.cs +++ b/src/Shared/NodeEndpointOutOfProcBase.cs @@ -8,16 +8,18 @@ #else using System.Collections.Concurrent; #endif -using System.IO; -using System.IO.Pipes; using System.Threading; using Microsoft.Build.Internal; using Microsoft.Build.Shared; +using System.IO.Pipes; +using System.IO; + #if FEATURE_SECURITY_PERMISSIONS || FEATURE_PIPE_SECURITY using System.Security.AccessControl; #endif #if FEATURE_PIPE_SECURITY && FEATURE_NAMED_PIPE_SECURITY_CONSTRUCTOR using System.Security.Principal; + #endif #if NET451_OR_GREATER || NETCOREAPP using System.Threading.Tasks; @@ -494,7 +496,7 @@ private void PacketPumpProc() { if (localPipeServer.IsConnected) { -#if NETCOREAPP // OperatingSystem.IsWindows() is new in .NET 5.0 +#if NET // OperatingSystem.IsWindows() is new in .NET 5.0 if (OperatingSystem.IsWindows()) #endif { @@ -522,7 +524,7 @@ private void RunReadLoop(BufferedReadStream localReadPipe, NamedPipeServerStream #if NET451_OR_GREATER Task readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); #elif NETCOREAPP - Task readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); + Task readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length).AsTask(); #else IAsyncResult result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #endif @@ -614,7 +616,7 @@ private void RunReadLoop(BufferedReadStream localReadPipe, NamedPipeServerStream #if NET451_OR_GREATER readTask = localReadPipe.ReadAsync(headerByte, 0, headerByte.Length, CancellationToken.None); #elif NETCOREAPP - readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length); + readTask = CommunicationsUtilities.ReadAsync(localReadPipe, headerByte, headerByte.Length).AsTask(); #else result = localReadPipe.BeginRead(headerByte, 0, headerByte.Length, null, null); #endif diff --git a/src/Shared/NodePacketFactory.cs b/src/Shared/NodePacketFactory.cs index 214ddfa20f9..478c88310eb 100644 --- a/src/Shared/NodePacketFactory.cs +++ b/src/Shared/NodePacketFactory.cs @@ -55,7 +55,22 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); } - record.DeserializeAndRoutePacket(nodeId, translator); + INodePacket packet = record.DeserializePacket(translator); + record.RoutePacket(nodeId, packet); + } + + /// + /// Creates a packet with data from a binary stream. + /// + public INodePacket DeserializePacket(NodePacketType packetType, ITranslator translator) + { + // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case + if (!_packetFactories.TryGetValue(packetType, out PacketFactoryRecord record)) + { + ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packetType); + } + + return record.DeserializePacket(translator); } /// @@ -63,7 +78,12 @@ public void DeserializeAndRoutePacket(int nodeId, NodePacketType packetType, ITr /// public void RoutePacket(int nodeId, INodePacket packet) { - PacketFactoryRecord record = _packetFactories[packet.Type]; + // PERF: Not using VerifyThrow to avoid boxing of packetType in the non-error case + if (!_packetFactories.TryGetValue(packet.Type, out PacketFactoryRecord record)) + { + ErrorUtilities.ThrowInternalError("No packet handler for type {0}", packet.Type); + } + record.RoutePacket(nodeId, packet); } @@ -77,12 +97,12 @@ private class PacketFactoryRecord /// /// The handler to invoke when the packet is deserialized. /// - private INodePacketHandler _handler; + private readonly INodePacketHandler _handler; /// /// The method used to construct a packet from a translator stream. /// - private NodePacketFactoryMethod _factoryMethod; + private readonly NodePacketFactoryMethod _factoryMethod; /// /// Constructor. @@ -94,21 +114,14 @@ public PacketFactoryRecord(INodePacketHandler handler, NodePacketFactoryMethod f } /// - /// Creates a packet from a binary stream and sends it to the registered handler. + /// Creates a packet from a binary stream. /// - public void DeserializeAndRoutePacket(int nodeId, ITranslator translator) - { - INodePacket packet = _factoryMethod(translator); - RoutePacket(nodeId, packet); - } + public INodePacket DeserializePacket(ITranslator translator) => _factoryMethod(translator); /// /// Routes the packet to the correct destination. /// - public void RoutePacket(int nodeId, INodePacket packet) - { - _handler.PacketReceived(nodeId, packet); - } + public void RoutePacket(int nodeId, INodePacket packet) => _handler.PacketReceived(nodeId, packet); } } } diff --git a/src/Shared/NodePipeBase.cs b/src/Shared/NodePipeBase.cs new file mode 100644 index 00000000000..fd1d08efe9c --- /dev/null +++ b/src/Shared/NodePipeBase.cs @@ -0,0 +1,271 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.IO.Pipes; +using System.Threading; +using Microsoft.Build.BackEnd; +using Microsoft.Build.Framework; + +#if !TASKHOST +using System.Buffers.Binary; +using System.Threading.Tasks; +using Microsoft.Build.Eventing; +#endif + +namespace Microsoft.Build.Internal +{ + internal abstract class NodePipeBase : IDisposable + { + /// + /// A packet header consists of 1 byte (enum) for the packet type + 4 bytes (int32) for the packet length. + /// + private const int HeaderLength = 5; + + /// + /// The size of the intermediate in-memory buffers. + /// + private const int InitialBufferSize = 131_072; + + /// + /// The maximum number of bytes to write in a single operation. + /// + private const int MaxPacketWriteSize = 104_8576; + + /// + /// A reusable buffer for reading the packet header. + /// + private readonly byte[] _headerData = new byte[HeaderLength]; + + /// + /// A buffer typically big enough to handle a packet body. + /// We use this as a convenient way to manage and cache a byte[] that's resized + /// automatically to fit our payload. + /// + private readonly MemoryStream _readBuffer = new(InitialBufferSize); + + /// + /// A buffer typically big enough to handle a packet body. + /// We use this as a convenient way to manage and cache a byte[] that's resized + /// automatically to fit our payload. + /// + private readonly MemoryStream _writeBuffer = new(InitialBufferSize); + + private readonly ITranslator _readTranslator; + + private readonly ITranslator _writeTranslator; + + /// + /// The packet factory to be used for deserialization, as packet types may have custom factory logic. + /// + private INodePacketFactory? _packetFactory; + + protected NodePipeBase(string pipeName, Handshake handshake) + { + PipeName = pipeName; + HandshakeComponents = handshake.RetrieveHandshakeComponents(); + _readTranslator = BinaryTranslator.GetReadTranslator(_readBuffer, InterningBinaryReader.CreateSharedBuffer()); + _writeTranslator = BinaryTranslator.GetWriteTranslator(_writeBuffer); + } + + protected abstract PipeStream NodeStream { get; } + + protected string PipeName { get; } + + protected int[] HandshakeComponents { get; } + + public void Dispose() + { + _readBuffer.Dispose(); + _writeBuffer.Dispose(); + _readTranslator.Dispose(); + _writeTranslator.Dispose(); + NodeStream.Dispose(); + } + + internal void RegisterPacketFactory(INodePacketFactory packetFactory) => _packetFactory = packetFactory; + + internal void WritePacket(INodePacket packet) + { + int messageLength = WritePacketToBuffer(packet); + byte[] buffer = _writeBuffer.GetBuffer(); + + for (int i = 0; i < messageLength; i += MaxPacketWriteSize) + { + int lengthToWrite = Math.Min(messageLength - i, MaxPacketWriteSize); + NodeStream.Write(buffer, i, lengthToWrite); + } + } + + internal INodePacket ReadPacket() + { + // Read the header. + int headerBytesRead = Read(_headerData, HeaderLength); + + // When an active connection is broken, any pending read will return 0 bytes before the pipe transitions to + // the broken state. As this is expected behavior, don't throw an exception if no packet is pending, A node + // may disconnect without waiting on the other end to gracefully cancel, and the caller can decide whether + // this was intentional. + if (headerBytesRead == 0) + { + return new NodeShutdown(NodeShutdownReason.ConnectionFailed); + } + else if (headerBytesRead != HeaderLength) + { + throw new IOException($"Incomplete header read. {headerBytesRead} of {HeaderLength} bytes read."); + } + +#if TASKHOST + int packetLength = BitConverter.ToInt32(_headerData, 1); +#else + int packetLength = BinaryPrimitives.ReadInt32LittleEndian(new Span(_headerData, 1, 4)); + MSBuildEventSource.Log.PacketReadSize(packetLength); +#endif + + // Read the packet. Set the buffer length now to avoid additional resizing during the read. + _readBuffer.Position = 0; + _readBuffer.SetLength(packetLength); + int packetBytesRead = Read(_readBuffer.GetBuffer(), packetLength); + + if (packetBytesRead < packetLength) + { + throw new IOException($"Incomplete packet read. {packetBytesRead} of {packetLength} bytes read."); + } + + return DeserializePacket(); + } + +#if !TASKHOST + internal async Task WritePacketAsync(INodePacket packet, CancellationToken cancellationToken = default) + { + int messageLength = WritePacketToBuffer(packet); + byte[] buffer = _writeBuffer.GetBuffer(); + + for (int i = 0; i < messageLength; i += MaxPacketWriteSize) + { + int lengthToWrite = Math.Min(messageLength - i, MaxPacketWriteSize); +#if NET + await NodeStream.WriteAsync(buffer.AsMemory(i, lengthToWrite), cancellationToken).ConfigureAwait(false); +#else + await NodeStream.WriteAsync(buffer, i, lengthToWrite, cancellationToken).ConfigureAwait(false); +#endif + } + } + + internal async Task ReadPacketAsync(CancellationToken cancellationToken = default) + { + // Read the header. + int headerBytesRead = await ReadAsync(_headerData, HeaderLength, cancellationToken).ConfigureAwait(false); + + // When an active connection is broken, any pending read will return 0 bytes before the pipe transitions to + // the broken state. As this is expected behavior, don't throw an exception if no packet is pending, A node + // may disconnect without waiting on the other end to gracefully cancel, and the caller can decide whether + // this was intentional. + if (headerBytesRead == 0) + { + return new NodeShutdown(NodeShutdownReason.ConnectionFailed); + } + else if (headerBytesRead != HeaderLength) + { + throw new IOException($"Incomplete header read. {headerBytesRead} of {HeaderLength} bytes read."); + } + + int packetLength = BinaryPrimitives.ReadInt32LittleEndian(new Span(_headerData, 1, 4)); + MSBuildEventSource.Log.PacketReadSize(packetLength); + + // Read the packet. Set the buffer length now to avoid additional resizing during the read. + _readBuffer.Position = 0; + _readBuffer.SetLength(packetLength); + int packetBytesRead = await ReadAsync(_readBuffer.GetBuffer(), packetLength, cancellationToken).ConfigureAwait(false); + + if (packetBytesRead < packetLength) + { + throw new IOException($"Incomplete packet read. {packetBytesRead} of {packetLength} bytes read."); + } + + return DeserializePacket(); + } +#endif + + private int WritePacketToBuffer(INodePacket packet) + { + // Clear the buffer but keep the underlying capacity to avoid reallocations. + _writeBuffer.SetLength(HeaderLength); + _writeBuffer.Position = HeaderLength; + + // Serialize and write the packet to the buffer. + packet.Translate(_writeTranslator); + + // Write the header to the buffer. + _writeBuffer.Position = 0; + _writeBuffer.WriteByte((byte)packet.Type); + int messageLength = (int)_writeBuffer.Length; + _writeTranslator.Writer.Write(messageLength - HeaderLength); + + return messageLength; + } + + private int Read(byte[] buffer, int bytesToRead) + { + int totalBytesRead = 0; + while (totalBytesRead < bytesToRead) + { + int bytesRead = NodeStream.Read(buffer, totalBytesRead, bytesToRead - totalBytesRead); + + // 0 byte read will occur if the pipe disconnects. + if (bytesRead == 0) + { + break; + } + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } + +#if !TASKHOST + private async ValueTask ReadAsync(byte[] buffer, int bytesToRead, CancellationToken cancellationToken) + { + int totalBytesRead = 0; + while (totalBytesRead < bytesToRead) + { +#if NET + int bytesRead = await NodeStream.ReadAsync(buffer.AsMemory(totalBytesRead, bytesToRead - totalBytesRead), cancellationToken).ConfigureAwait(false); +#else + int bytesRead = await NodeStream.ReadAsync(buffer, totalBytesRead, bytesToRead - totalBytesRead, cancellationToken).ConfigureAwait(false); +#endif + + // 0 byte read will occur if the pipe disconnects. + if (bytesRead == 0) + { + break; + } + + totalBytesRead += bytesRead; + } + + return totalBytesRead; + } +#endif + + private INodePacket DeserializePacket() + { + if (_packetFactory == null) + { + throw new InternalErrorException("No packet factory is registered for deserialization."); + } + + NodePacketType packetType = (NodePacketType)_headerData[0]; + try + { + return _packetFactory.DeserializePacket(packetType, _readTranslator); + } + catch (Exception e) when (e is not InternalErrorException) + { + throw new InternalErrorException($"Exception while deserializing packet {packetType}: {e}"); + } + } + } +} diff --git a/src/Shared/NodePipeClient.cs b/src/Shared/NodePipeClient.cs new file mode 100644 index 00000000000..a521f4f34d2 --- /dev/null +++ b/src/Shared/NodePipeClient.cs @@ -0,0 +1,94 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System.IO.Pipes; +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY +using System; +using System.Security.Principal; +#endif + +namespace Microsoft.Build.Internal +{ + internal sealed class NodePipeClient : NodePipeBase + { + /// + /// If true, sets a timeout for the handshake. This is only used on Unix-like socket implementations, because the + /// timeout on the PipeStream connection is ignore. + /// + private static readonly bool s_useHandhakeTimeout = !NativeMethodsShared.IsWindows; + + private readonly NamedPipeClientStream _pipeClient; + + internal NodePipeClient(string pipeName, Handshake handshake) + : base(pipeName, handshake) => +#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter + _pipeClient = new( + serverName: ".", + pipeName, + PipeDirection.InOut, + PipeOptions.Asynchronous +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + | PipeOptions.CurrentUserOnly +#endif + ); +#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + + protected override PipeStream NodeStream => _pipeClient; + + internal void ConnectToServer(int timeout) + { + CommunicationsUtilities.Trace("Attempting connect to pipe {0} with timeout {1} ms", PipeName, timeout); + _pipeClient.Connect(timeout); +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + // Verify that the owner of the pipe is us. This prevents a security hole where a remote node has + // been faked up with ACLs that would let us attach to it. It could then issue fake build requests back to + // us, potentially causing us to execute builds that do harmful or unexpected things. The pipe owner can + // only be set to the user's own SID by a normal, unprivileged process. The conditions where a faked up + // remote node could set the owner to something else would also let it change owners on other objects, so + // this would be a security flaw upstream of us. + ValidateRemotePipeOwner(); +#endif + PerformHandshake(s_useHandhakeTimeout ? timeout : 0); + CommunicationsUtilities.Trace("Successfully connected to pipe {0}...!", PipeName); + } + +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + // This code needs to be in a separate method so that we don't try (and fail) to load the Windows-only APIs when JIT-ing the code + // on non-Windows operating systems + private void ValidateRemotePipeOwner() + { + SecurityIdentifier identifier = WindowsIdentity.GetCurrent().Owner; + PipeSecurity remoteSecurity = _pipeClient.GetAccessControl(); + IdentityReference remoteOwner = remoteSecurity.GetOwner(typeof(SecurityIdentifier)); + + if (remoteOwner != identifier) + { + CommunicationsUtilities.Trace("The remote pipe owner {0} does not match {1}", remoteOwner.Value, identifier.Value); + throw new UnauthorizedAccessException(); + } + } +#endif + + /// + /// Connect to named pipe stream and ensure validate handshake and security. + /// + private void PerformHandshake(int timeout) + { + for (int i = 0; i < HandshakeComponents.Length; i++) + { + CommunicationsUtilities.Trace("Writing handshake part {0} ({1}) to pipe {2}", i, HandshakeComponents[i], PipeName); + _pipeClient.WriteIntForHandshake(HandshakeComponents[i]); + } + + // This indicates that we have finished all the parts of our handshake; hopefully the endpoint has as well. + _pipeClient.WriteEndOfHandshakeSignal(); + + CommunicationsUtilities.Trace("Reading handshake from pipe {0}", PipeName); +#if NET + _pipeClient.ReadEndOfHandshakeSignal(true, timeout); +#else + _pipeClient.ReadEndOfHandshakeSignal(true); +#endif + } + } +} diff --git a/src/Shared/NodePipeServer.cs b/src/Shared/NodePipeServer.cs new file mode 100644 index 00000000000..eb932d973aa --- /dev/null +++ b/src/Shared/NodePipeServer.cs @@ -0,0 +1,232 @@ +// Licensed to the .NET Foundation under one or more agreements. +// The .NET Foundation licenses this file to you under the MIT license. + +using System; +using System.IO; +using System.IO.Pipes; +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY +using System.Security.AccessControl; +using System.Security.Principal; +#endif +using Microsoft.Build.BackEnd; +using Microsoft.Build.Shared; + +#if !TASKHOST +using System.Threading.Tasks; +#endif + +namespace Microsoft.Build.Internal +{ + internal sealed class NodePipeServer : NodePipeBase + { + /// + /// The size of kernel-level buffers used by the named pipe. If the total size of pending reads or write requests exceed + /// this amount (known as the quota), IO will block until either pending operations complete, or the OS increases the quota. + /// + private const int PipeBufferSize = 131_072; + +#if NET + /// + /// A timeout for the handshake. This is only used on Unix-like socket implementations, because the + /// timeout on the PipeStream connection is ignore. + /// + private static readonly int s_handshakeTimeout = NativeMethodsShared.IsWindows ? 0 : 60_000; +#endif + + private readonly NamedPipeServerStream _pipeServer; + + internal NodePipeServer(string pipeName, Handshake handshake, int maxNumberOfServerInstances = 1) + : base(pipeName, handshake) + { + PipeOptions pipeOptions = PipeOptions.Asynchronous; +#if FEATURE_PIPEOPTIONS_CURRENTUSERONLY + pipeOptions |= PipeOptions.CurrentUserOnly; +#else + // Restrict access to just this account. We set the owner specifically here, and on the + // pipe client side they will check the owner against this one - they must have identical + // SIDs or the client will reject this server. This is used to avoid attacks where a + // hacked server creates a less restricted pipe in an attempt to lure us into using it and + // then sending build requests to the real pipe client (which is the MSBuild Build Manager.) + PipeAccessRule rule = new(WindowsIdentity.GetCurrent().Owner, PipeAccessRights.ReadWrite, AccessControlType.Allow); + PipeSecurity security = new(); + security.AddAccessRule(rule); + security.SetOwner(rule.IdentityReference); +#endif + + _pipeServer = new NamedPipeServerStream( + pipeName, + PipeDirection.InOut, + maxNumberOfServerInstances, + PipeTransmissionMode.Byte, + pipeOptions, + inBufferSize: PipeBufferSize, + outBufferSize: PipeBufferSize +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + , security, + HandleInheritability.None +#endif +#pragma warning disable SA1111 // Closing parenthesis should be on line of last parameter + ); +#pragma warning restore SA1111 // Closing parenthesis should be on line of last parameter + } + + protected override PipeStream NodeStream => _pipeServer; + + internal LinkStatus WaitForConnection() + { + DateTime originalWaitStartTime = DateTime.UtcNow; + bool gotValidConnection = false; + + while (!gotValidConnection) + { + gotValidConnection = true; + DateTime restartWaitTime = DateTime.UtcNow; + + // We only wait to wait the difference between now and the last original start time, in case we have multiple hosts attempting + // to attach. This prevents each attempt from resetting the timer. + TimeSpan usedWaitTime = restartWaitTime - originalWaitStartTime; + int waitTimeRemaining = Math.Max(0, CommunicationsUtilities.NodeConnectionTimeout - (int)usedWaitTime.TotalMilliseconds); + + try + { + // Wait for a connection +#if TASKHOST + IAsyncResult resultForConnection = _pipeServer.BeginWaitForConnection(null, null); + CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + bool connected = resultForConnection.AsyncWaitHandle.WaitOne(waitTimeRemaining, false); + _pipeServer.EndWaitForConnection(resultForConnection); +#else + Task connectionTask = _pipeServer.WaitForConnectionAsync(); + CommunicationsUtilities.Trace("Waiting for connection {0} ms...", waitTimeRemaining); + bool connected = connectionTask.Wait(waitTimeRemaining); +#endif + if (!connected) + { + CommunicationsUtilities.Trace("Connection timed out waiting a host to contact us. Exiting comm thread."); + return LinkStatus.ConnectionFailed; + } + + CommunicationsUtilities.Trace("Parent started connecting. Reading handshake from parent"); + + // The handshake protocol is a series of int exchanges. The host sends us a each component, and we + // verify it. Afterwards, the host sends an "End of Handshake" signal, to which we respond in kind. + // Once the handshake is complete, both sides can be assured the other is ready to accept data. + try + { + gotValidConnection = ValidateHandshake(); +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + gotValidConnection &= ValidateClientIdentity(); +#endif + } + catch (IOException e) + { + // We will get here when: + // 1. The host (OOP main node) connects to us, it immediately checks for user privileges + // and if they don't match it disconnects immediately leaving us still trying to read the blank handshake + // 2. The host is too old sending us bits we automatically reject in the handshake + // 3. We expected to read the EndOfHandshake signal, but we received something else + CommunicationsUtilities.Trace("Client connection failed but we will wait for another connection. Exception: {0}", e.Message); + gotValidConnection = false; + } + catch (InvalidOperationException) + { + gotValidConnection = false; + } + + if (!gotValidConnection && _pipeServer.IsConnected) + { + _pipeServer.Disconnect(); + } + } + catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) + { + CommunicationsUtilities.Trace("Client connection failed. Exiting comm thread. {0}", e); + if (_pipeServer.IsConnected) + { + _pipeServer.Disconnect(); + } + + ExceptionHandling.DumpExceptionToFile(e); + return LinkStatus.Failed; + } + } + + return LinkStatus.Active; + } + + internal void Disconnect() + { + try + { + if (_pipeServer.IsConnected) + { +#if NET // OperatingSystem.IsWindows() is new in .NET 5.0 + if (OperatingSystem.IsWindows()) +#endif + { + _pipeServer.WaitForPipeDrain(); + } + + _pipeServer.Disconnect(); + } + } + catch (Exception) + { + // We don't really care if Disconnect somehow fails, but it gives us a chance to do the right thing. + } + } + + private bool ValidateHandshake() + { + for (int i = 0; i < HandshakeComponents.Length; i++) + { + // This will disconnect a < 16.8 host; it expects leading 00 or F5 or 06. 0x00 is a wildcard. +#if NET + int handshakePart = _pipeServer.ReadIntForHandshake(byteToAccept: i == 0 ? CommunicationsUtilities.handshakeVersion : null, s_handshakeTimeout); +#else + int handshakePart = _pipeServer.ReadIntForHandshake(byteToAccept: i == 0 ? CommunicationsUtilities.handshakeVersion : null); +#endif + + if (handshakePart != HandshakeComponents[i]) + { + CommunicationsUtilities.Trace("Handshake failed. Received {0} from host not {1}. Probably the host is a different MSBuild build.", handshakePart, HandshakeComponents[i]); + _pipeServer.WriteIntForHandshake(i + 1); + return false; + } + } + + // To ensure that our handshake and theirs have the same number of bytes, receive and send a magic number indicating EOS. +#if NET + _pipeServer.ReadEndOfHandshakeSignal(false, s_handshakeTimeout); +#else + _pipeServer.ReadEndOfHandshakeSignal(false); +#endif + + CommunicationsUtilities.Trace("Successfully connected to parent."); + _pipeServer.WriteEndOfHandshakeSignal(); + + return true; + } + +#if !FEATURE_PIPEOPTIONS_CURRENTUSERONLY + private bool ValidateClientIdentity() + { + // We will only talk to a host that was started by the same user as us. Even though the pipe access is set to only allow this user, we want to ensure they + // haven't attempted to change those permissions out from under us. This ensures that the only way they can truly gain access is to be impersonating the + // user we were started by. + WindowsIdentity currentIdentity = WindowsIdentity.GetCurrent(); + WindowsIdentity? clientIdentity = null; + _pipeServer.RunAsClient(() => { clientIdentity = WindowsIdentity.GetCurrent(true); }); + + if (clientIdentity == null || !string.Equals(clientIdentity.Name, currentIdentity.Name, StringComparison.OrdinalIgnoreCase)) + { + CommunicationsUtilities.Trace("Handshake failed. Host user is {0} but we were created by {1}.", (clientIdentity == null) ? "" : clientIdentity.Name, currentIdentity.Name); + return false; + } + + return true; + } +#endif + + } +} diff --git a/src/Shared/PlatformNegotiation.cs b/src/Shared/PlatformNegotiation.cs index 18b9b361e3b..c21db16aa0e 100644 --- a/src/Shared/PlatformNegotiation.cs +++ b/src/Shared/PlatformNegotiation.cs @@ -61,18 +61,18 @@ internal static string GetNearestPlatform(string overridePlatformValue, string r // Prioritize platformLookupTable **metadata** attached to the ProjectReference item // before the current project's table. We do this to allow per-ProjectReference fine tuning. else if (projectReferenceLookupTable != null && - projectReferenceLookupTable.ContainsKey(currentProjectPlatform) && - projectReferencePlatforms.Contains(projectReferenceLookupTable[currentProjectPlatform])) + projectReferenceLookupTable.TryGetValue(currentProjectPlatform, out string? value) && + projectReferencePlatforms.Contains(value)) { - buildProjectReferenceAs = projectReferenceLookupTable[currentProjectPlatform]; + buildProjectReferenceAs = value; log?.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.FoundMappingInTable", currentProjectPlatform, buildProjectReferenceAs, projectReferenceLookupTableMetadata); } // Current project's translation table follows else if (currentProjectLookupTable != null && - currentProjectLookupTable.ContainsKey(currentProjectPlatform) && - projectReferencePlatforms.Contains(currentProjectLookupTable[currentProjectPlatform])) + currentProjectLookupTable.TryGetValue(currentProjectPlatform, out value) && + projectReferencePlatforms.Contains(value)) { - buildProjectReferenceAs = currentProjectLookupTable[currentProjectPlatform]; + buildProjectReferenceAs = value; log?.LogMessageFromResources(MessageImportance.Low, "GetCompatiblePlatform.FoundMappingInTable", currentProjectPlatform, buildProjectReferenceAs, platformLookupTable); } // AnyCPU if possible diff --git a/src/Shared/ProcessExtensions.cs b/src/Shared/ProcessExtensions.cs index 362a8b0a8c1..d13ebbac5fe 100644 --- a/src/Shared/ProcessExtensions.cs +++ b/src/Shared/ProcessExtensions.cs @@ -11,7 +11,7 @@ internal static class ProcessExtensions { public static void KillTree(this Process process, int timeoutMilliseconds) { -#if NETCOREAPP +#if NET process.Kill(entireProcessTree: true); #else if (NativeMethodsShared.IsWindows) diff --git a/src/Shared/ProjectWriter.cs b/src/Shared/ProjectWriter.cs index fa2e6f76a1c..d0b57944440 100644 --- a/src/Shared/ProjectWriter.cs +++ b/src/Shared/ProjectWriter.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.IO; using System.Text; using System.Text.RegularExpressions; @@ -15,7 +14,7 @@ namespace Microsoft.Build.Shared /// This class is used to save MSBuild project files. It contains special handling for MSBuild notations that are not saved /// correctly by the XML DOM's default save mechanism. /// - internal sealed class ProjectWriter : XmlTextWriter + internal sealed partial class ProjectWriter : XmlTextWriter { #region Regular expressions for item vector transforms @@ -28,9 +27,7 @@ internal sealed class ProjectWriter : XmlTextWriter // Note that the pattern is more strict than the rules for valid XML element names. internal const string itemTypeOrMetadataNameSpecification = @"[A-Za-z_][A-Za-z_0-9\-]*"; - // the portion of an item transform that is the function that we wish to execute on the item - internal const string itemFunctionNameSpecification = @"[A-Za-z]*"; - + // regular expression used to match item vector transforms // description of an item vector transform, including the optional separator specification private const string itemVectorTransformSpecification = @"(?@\(\s*) @@ -40,15 +37,9 @@ internal sealed class ProjectWriter : XmlTextWriter (?\s*\))"; // ) - // regular expression used to match item vector transforms - // internal for unit testing only - internal static readonly Lazy itemVectorTransformPattern = new Lazy( - () => - new Regex(itemVectorTransformSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); - // description of an item vector transform, including the optional separator specification, but with no (named) capturing // groups -- see the WriteString() method for details + // regular expression used to match item vector transforms, with no (named) capturing groups private const string itemVectorTransformRawSpecification = @"@\(\s* (" + itemTypeOrMetadataNameSpecification + @") @@ -56,12 +47,21 @@ internal sealed class ProjectWriter : XmlTextWriter (\s*,\s*'[^']*')? \s*\)"; - // regular expression used to match item vector transforms, with no (named) capturing groups - // internal for unit testing only - internal static readonly Lazy itemVectorTransformRawPattern = new Lazy( - () => - new Regex(itemVectorTransformRawSpecification, - RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled)); +#if NET + [GeneratedRegex(itemVectorTransformSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] + private static partial Regex ItemVectorTransformRegex { get; } + + [GeneratedRegex(itemVectorTransformRawSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture)] + private static partial Regex ItemVectorTransformRawRegex { get; } +#else + private static Regex ItemVectorTransformRegex => itemVectorTransformPattern ??= + new Regex(itemVectorTransformSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + private static Regex itemVectorTransformPattern; + + private static Regex ItemVectorTransformRawRegex => itemVectorTransformRawPattern ??= + new Regex(itemVectorTransformRawSpecification, RegexOptions.IgnorePatternWhitespace | RegexOptions.ExplicitCapture | RegexOptions.Compiled); + private static Regex itemVectorTransformRawPattern; +#endif /************************************************************************************************************************** * WARNING: The regular expressions above MUST be kept in sync with the expressions in the ItemExpander class. @@ -130,14 +130,14 @@ internal void Initialize(XmlDocument project, XmlDeclaration projectRootElementD /// public override void WriteString(string text) { - MatchCollection itemVectorTransforms = itemVectorTransformRawPattern.Value.Matches(text); + MatchCollection itemVectorTransforms = ItemVectorTransformRawRegex.Matches(text); // if the string contains any item vector transforms if (itemVectorTransforms.Count > 0) { // separate out the text that surrounds the transforms // NOTE: use the Regex with no (named) capturing groups, otherwise Regex.Split() will split on them - string[] surroundingTextPieces = itemVectorTransformRawPattern.Value.Split(text); + string[] surroundingTextPieces = ItemVectorTransformRawRegex.Split(text); ErrorUtilities.VerifyThrow(itemVectorTransforms.Count == (surroundingTextPieces.Length - 1), "We must have two pieces of surrounding text for every item vector transform found."); @@ -149,7 +149,7 @@ public override void WriteString(string text) base.WriteString(surroundingTextPieces[i]); // break up the transform into its constituent pieces - Match itemVectorTransform = itemVectorTransformPattern.Value.Match(itemVectorTransforms[i].Value); + Match itemVectorTransform = ItemVectorTransformRegex.Match(itemVectorTransforms[i].Value); ErrorUtilities.VerifyThrow(itemVectorTransform.Success, "Item vector transform must be matched by both the raw and decorated regular expressions."); diff --git a/src/Shared/PropertyParser.cs b/src/Shared/PropertyParser.cs index a9e1c29d72c..48110e0ca4c 100644 --- a/src/Shared/PropertyParser.cs +++ b/src/Shared/PropertyParser.cs @@ -47,10 +47,10 @@ internal static bool GetTable(TaskLoggingHelper log, string parameterName, strin // whitespace from beginning and end of both name and value. (When authoring a // project/targets file, people like to use whitespace and newlines to pretty up // the file format.) - if (indexOfEqualsSign != -1) + if (indexOfEqualsSign >= 0) { - propertyName = propertyNameValuePair.Substring(0, indexOfEqualsSign).Trim(); - propertyValue = propertyNameValuePair.Substring(indexOfEqualsSign + 1).Trim(); + propertyName = propertyNameValuePair.AsSpan(0, indexOfEqualsSign).Trim().ToString(); + propertyValue = propertyNameValuePair.AsSpan(indexOfEqualsSign + 1).Trim().ToString(); } // Make sure we have a property name and property value (though the value is allowed to be blank). @@ -103,8 +103,8 @@ internal static bool GetTableWithEscaping(TaskLoggingHelper log, string paramete // whitespace from beginning and end of both name and value. (When authoring a // project/targets file, people like to use whitespace and newlines to pretty up // the file format.) - string propertyName = propertyNameValueString.Substring(0, indexOfEqualsSign).Trim(); - string propertyValue = EscapingUtilities.Escape(propertyNameValueString.Substring(indexOfEqualsSign + 1).Trim()); + string propertyName = propertyNameValueString.AsSpan(0, indexOfEqualsSign).Trim().ToString(); + string propertyValue = EscapingUtilities.Escape(propertyNameValueString.AsSpan(indexOfEqualsSign + 1).Trim().ToString()); // Make sure we have a property name and property value (though the value is allowed to be blank). if (propertyName.Length == 0) diff --git a/src/Shared/QuotingUtilities.cs b/src/Shared/QuotingUtilities.cs index 17ca8a419db..5d42939b505 100644 --- a/src/Shared/QuotingUtilities.cs +++ b/src/Shared/QuotingUtilities.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Text; diff --git a/src/Shared/RegisteredTaskObjectCacheBase.cs b/src/Shared/RegisteredTaskObjectCacheBase.cs index d7d25b7d962..10391f5d336 100644 --- a/src/Shared/RegisteredTaskObjectCacheBase.cs +++ b/src/Shared/RegisteredTaskObjectCacheBase.cs @@ -22,7 +22,7 @@ internal class RegisteredTaskObjectCacheBase /// /// The cache for AppDomain lifetime objects. /// - private static Lazy> s_appDomainLifetimeObjects = new Lazy>(); + private static readonly Lazy> s_appDomainLifetimeObjects = new Lazy>(); /// /// The cache for Build lifetime objects. diff --git a/src/Shared/ResourceUtilities.cs b/src/Shared/ResourceUtilities.cs index e046c7d4c5a..7ff74c83f19 100644 --- a/src/Shared/ResourceUtilities.cs +++ b/src/Shared/ResourceUtilities.cs @@ -122,7 +122,7 @@ internal static string ExtractMessageCode(bool msbuildCodeOnly, string message, if (i < message.Length) { - message = message.Substring(i, message.Length - i); + message = message.Substring(i); } return message; diff --git a/src/Shared/StringUtils.cs b/src/Shared/StringUtils.cs index 10152956f27..cb109d5f9d6 100644 --- a/src/Shared/StringUtils.cs +++ b/src/Shared/StringUtils.cs @@ -16,6 +16,10 @@ internal static class StringUtils /// Random generated string of the specified length. public static string GenerateRandomString(int length) { +#if NET + return string.Create(length, 0, static (dest, _) => + Random.Shared.GetItems("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+_", dest)); +#else // Base64, 2^6 = 64 const int eachStringCharEncodesBites = 6; const int eachByteHasBits = 8; @@ -32,6 +36,7 @@ public static string GenerateRandomString(int length) string randomBase64String = Convert.ToBase64String(randomBytes).Replace('/', '_'); return randomBase64String.Substring(0, length); +#endif } /// @@ -45,10 +50,14 @@ public static string RemoveLastInstanceOf(this string fromString, string substri { int lastOccurrenceIndex = fromString.LastIndexOf(substring, comparison); - if (lastOccurrenceIndex != -1) + if (lastOccurrenceIndex >= 0) { - fromString = fromString.Substring(0, lastOccurrenceIndex) + - fromString.Substring(lastOccurrenceIndex + substring.Length); + fromString = +#if NET + $"{fromString.AsSpan(0, lastOccurrenceIndex)}{fromString.AsSpan(lastOccurrenceIndex + substring.Length)}"; +#else + $"{fromString.Substring(0, lastOccurrenceIndex)}{fromString.Substring(lastOccurrenceIndex + substring.Length)}"; +#endif } return fromString; diff --git a/src/Shared/TaskHostConfiguration.cs b/src/Shared/TaskHostConfiguration.cs index 723a4ba240b..df56c4efc53 100644 --- a/src/Shared/TaskHostConfiguration.cs +++ b/src/Shared/TaskHostConfiguration.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Diagnostics; using System.Globalization; -using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Shared/TaskHostTaskComplete.cs b/src/Shared/TaskHostTaskComplete.cs index 862341eaa8f..6bded722522 100644 --- a/src/Shared/TaskHostTaskComplete.cs +++ b/src/Shared/TaskHostTaskComplete.cs @@ -4,7 +4,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; -#if !CLR2COMPATIBILITY +#if !CLR2COMPATIBILITY && FEATURE_REPORTFILEACCESSES using Microsoft.Build.Experimental.FileAccess; #endif using Microsoft.Build.Shared; diff --git a/src/Shared/TaskLoader.cs b/src/Shared/TaskLoader.cs index ea170a16a08..602a36871ed 100644 --- a/src/Shared/TaskLoader.cs +++ b/src/Shared/TaskLoader.cs @@ -5,8 +5,6 @@ using System.Reflection; using Microsoft.Build.Framework; -#nullable disable - namespace Microsoft.Build.Shared { /// @@ -19,7 +17,7 @@ internal static class TaskLoader /// For saving the assembly that was loaded by the TypeLoader /// We only use this when the assembly failed to load properly into the appdomain /// - private static LoadedType s_resolverLoadedType; + private static LoadedType? s_resolverLoadedType; #endif /// @@ -42,7 +40,7 @@ internal static bool IsTaskClass(Type type, object unused) /// Creates an ITask instance and returns it. /// #pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - internal static ITask CreateTask( + internal static ITask? CreateTask( LoadedType loadedType, string taskName, string taskLocation, @@ -55,7 +53,7 @@ internal static ITask CreateTask( #endif bool isOutOfProc #if FEATURE_APPDOMAIN - , out AppDomain taskAppDomain + , out AppDomain? taskAppDomain #endif ) #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter @@ -64,7 +62,7 @@ bool isOutOfProc bool separateAppDomain = loadedType.HasLoadInSeparateAppDomainAttribute; s_resolverLoadedType = null; taskAppDomain = null; - ITask taskInstanceInOtherAppDomain = null; + ITask? taskInstanceInOtherAppDomain = null; #endif try @@ -126,7 +124,7 @@ bool isOutOfProc { // perf improvement for the same appdomain case - we already have the type object // and don't want to go through reflection to recreate it from the name. - return (ITask)Activator.CreateInstance(loadedType.Type); + return (ITask?)Activator.CreateInstance(loadedType.Type); } #if FEATURE_APPDOMAIN @@ -158,7 +156,7 @@ bool isOutOfProc taskInstanceInOtherAppDomain = (ITask)taskAppDomain.CreateInstanceAndUnwrap(loadedType.Type.GetTypeInfo().Assembly.FullName, loadedType.Type.FullName); } - return taskInstanceInOtherAppDomain; + return taskInstanceInOtherAppDomain; #endif } finally @@ -179,10 +177,14 @@ bool isOutOfProc /// This is a resolver to help created AppDomains when they are unable to load an assembly into their domain we will help /// them succeed by providing the already loaded one in the currentdomain so that they can derive AssemblyName info from it /// - internal static Assembly AssemblyResolver(object sender, ResolveEventArgs args) + internal static Assembly? AssemblyResolver(object sender, ResolveEventArgs args) { - if (args.Name.Equals(s_resolverLoadedType.LoadedAssemblyName.FullName, StringComparison.OrdinalIgnoreCase)) + if (args.Name.Equals(s_resolverLoadedType?.LoadedAssemblyName?.FullName, StringComparison.OrdinalIgnoreCase)) { + if (s_resolverLoadedType == null || s_resolverLoadedType.Path == null) + { + return null; + } return s_resolverLoadedType.LoadedAssembly ?? Assembly.Load(s_resolverLoadedType.Path); } diff --git a/src/Shared/TaskLoggingHelper.cs b/src/Shared/TaskLoggingHelper.cs index 979319e5d36..b79bca33d16 100644 --- a/src/Shared/TaskLoggingHelper.cs +++ b/src/Shared/TaskLoggingHelper.cs @@ -4,7 +4,9 @@ using System; using System.Collections.Generic; using System.Diagnostics; +#if NET using System.Diagnostics.CodeAnalysis; +#endif using System.Globalization; using System.IO; using System.Resources; diff --git a/src/Shared/TaskParameter.cs b/src/Shared/TaskParameter.cs index 3e436da8c38..2e397ebab7b 100644 --- a/src/Shared/TaskParameter.cs +++ b/src/Shared/TaskParameter.cs @@ -4,9 +4,7 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Globalization; -using System.Linq; using System.Reflection; #if FEATURE_APPDOMAIN using System.Security; @@ -999,6 +997,7 @@ public IEnumerable> EnumerateMetadata() return EnumerateMetadataLazy(); } +#if FEATURE_APPDOMAIN private IEnumerable> EnumerateMetadataEager() { if (_customEscapedMetadata == null || _customEscapedMetadata.Count == 0) @@ -1016,6 +1015,7 @@ private IEnumerable> EnumerateMetadataEager() return result; } +#endif private IEnumerable> EnumerateMetadataLazy() { diff --git a/src/Shared/TempFileUtilities.cs b/src/Shared/TempFileUtilities.cs index 190f0dddf2b..4c607dfd2b4 100644 --- a/src/Shared/TempFileUtilities.cs +++ b/src/Shared/TempFileUtilities.cs @@ -82,7 +82,7 @@ private static string CreateFolderUnderTemp() /// internal static string GetTemporaryDirectory(bool createDirectory = true, string subfolder = null) { - string temporaryDirectory = Path.Combine(TempFileDirectory, "Temporary" + Guid.NewGuid().ToString("N"), subfolder ?? string.Empty); + string temporaryDirectory = Path.Combine(TempFileDirectory, $"Temporary{Guid.NewGuid():N}", subfolder ?? string.Empty); if (createDirectory) { diff --git a/src/Shared/ToolsetElement.cs b/src/Shared/ToolsetElement.cs index 0e2f7591b55..70002db51f0 100644 --- a/src/Shared/ToolsetElement.cs +++ b/src/Shared/ToolsetElement.cs @@ -279,7 +279,7 @@ private void UpdateOSMap(ConfigurationElement element) { if (element.ElementInformation.LineNumber != 0) { - locationString = String.Format("{0} ({1})", element.ElementInformation.Source, element.ElementInformation.LineNumber); + locationString = $"{element.ElementInformation.Source} ({element.ElementInformation.LineNumber})"; } else { diff --git a/src/Shared/Tracing.cs b/src/Shared/Tracing.cs index 87b3a3b7e67..70b8d4e0792 100644 --- a/src/Shared/Tracing.cs +++ b/src/Shared/Tracing.cs @@ -4,8 +4,8 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Diagnostics.CodeAnalysis; #if DEBUG +using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.Reflection; #endif @@ -25,7 +25,7 @@ internal static class Tracing /// /// A dictionary of named counters /// - private static Dictionary s_counts; + private static readonly Dictionary s_counts; /// /// Last time logging happened @@ -35,7 +35,7 @@ internal static class Tracing /// /// How often to log /// - private static TimeSpan s_interval; + private static readonly TimeSpan s_interval; /// /// A place callers can put something worth logging later @@ -45,7 +45,7 @@ internal static class Tracing /// /// Short name of the current assembly - to distinguish statics when this type is shared into different assemblies /// - private static string s_currentAssemblyName; + private static readonly string s_currentAssemblyName; #pragma warning restore 649 #if DEBUG @@ -95,7 +95,7 @@ internal static void Slot(string tag, string value) [Conditional("DEBUG")] internal static void Slot(string tag, KeyValuePair value) { - Slot(tag, value.Key.ToString() + "=" + value.Key.ToString()); + Slot(tag, $"{value.Key}={value.Key}"); } /// diff --git a/src/Shared/TypeLoader.cs b/src/Shared/TypeLoader.cs index 3a3013b36aa..7c5efa8ce3d 100644 --- a/src/Shared/TypeLoader.cs +++ b/src/Shared/TypeLoader.cs @@ -31,12 +31,12 @@ internal class TypeLoader /// /// Cache to keep track of the assemblyLoadInfos based on a given type filter. /// - private static ConcurrentDictionary, ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); + private static readonly ConcurrentDictionary, ConcurrentDictionary> s_cacheOfLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); /// /// Cache to keep track of the assemblyLoadInfos based on a given type filter for assemblies which are to be loaded for reflectionOnlyLoads. /// - private static ConcurrentDictionary, ConcurrentDictionary> s_cacheOfReflectionOnlyLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); + private static readonly ConcurrentDictionary, ConcurrentDictionary> s_cacheOfReflectionOnlyLoadedTypesByFilter = new ConcurrentDictionary, ConcurrentDictionary>(); /// /// Type filter for this typeloader @@ -45,7 +45,7 @@ internal class TypeLoader private static MetadataLoadContext _context; - private static string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework(); + private static readonly string[] runtimeAssemblies = findRuntimeAssembliesWithMicrosoftBuildFramework(); private static string microsoftBuildFrameworkPath; // We need to append Microsoft.Build.Framework from next to the executing assembly first to make sure it's loaded before the runtime variant. @@ -56,10 +56,7 @@ private static string[] findRuntimeAssembliesWithMicrosoftBuildFramework() string[] msbuildAssemblies = Directory.GetFiles(msbuildDirectory, "*.dll"); string[] runtimeAssemblies = Directory.GetFiles(RuntimeEnvironment.GetRuntimeDirectory(), "*.dll"); - List runtimeAssembliesList = new(runtimeAssemblies); - runtimeAssembliesList.AddRange(msbuildAssemblies); - - return runtimeAssembliesList.ToArray(); + return [.. runtimeAssemblies, .. msbuildAssemblies]; } /// diff --git a/src/Shared/UnitTests/AssemblyNameEx_Tests.cs b/src/Shared/UnitTests/AssemblyNameEx_Tests.cs index ec407e54d29..fbeba59cf92 100644 --- a/src/Shared/UnitTests/AssemblyNameEx_Tests.cs +++ b/src/Shared/UnitTests/AssemblyNameEx_Tests.cs @@ -7,7 +7,6 @@ using System.IO; using System.Linq; using System.Reflection; -using System.Runtime.Serialization.Formatters.Binary; using Microsoft.Build.BackEnd; using Microsoft.Build.Shared; using Shouldly; diff --git a/src/Shared/UnitTests/CommunicationUtilities_Tests.cs b/src/Shared/UnitTests/CommunicationUtilities_Tests.cs index 84cfcd034b8..63e1e2a7197 100644 --- a/src/Shared/UnitTests/CommunicationUtilities_Tests.cs +++ b/src/Shared/UnitTests/CommunicationUtilities_Tests.cs @@ -6,7 +6,6 @@ using System.Collections.Generic; using Shouldly; using Xunit; -using Xunit.Abstractions; using CommunicationsUtilities = Microsoft.Build.Internal.CommunicationsUtilities; namespace Microsoft.Build.UnitTests diff --git a/src/Shared/UnitTests/CopyOnWriteDictionary_Tests.cs b/src/Shared/UnitTests/CopyOnWriteDictionary_Tests.cs index 32101d5d60e..689edb1e36b 100644 --- a/src/Shared/UnitTests/CopyOnWriteDictionary_Tests.cs +++ b/src/Shared/UnitTests/CopyOnWriteDictionary_Tests.cs @@ -3,8 +3,6 @@ using System; using System.Collections.Generic; -using System.IO; -using System.Runtime.Serialization.Formatters.Binary; using Microsoft.Build.Collections; using Shouldly; using Xunit; diff --git a/src/Shared/UnitTests/ErrorUtilities_Tests.cs b/src/Shared/UnitTests/ErrorUtilities_Tests.cs index 6adbc583bd9..4bd7a10e35d 100644 --- a/src/Shared/UnitTests/ErrorUtilities_Tests.cs +++ b/src/Shared/UnitTests/ErrorUtilities_Tests.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; - using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Xunit; diff --git a/src/Shared/UnitTests/FileMatcher_Tests.cs b/src/Shared/UnitTests/FileMatcher_Tests.cs index 6d43904420f..35235a7d0f6 100644 --- a/src/Shared/UnitTests/FileMatcher_Tests.cs +++ b/src/Shared/UnitTests/FileMatcher_Tests.cs @@ -14,7 +14,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Shared/UnitTests/FileUtilities_Tests.cs b/src/Shared/UnitTests/FileUtilities_Tests.cs index a99d79be223..8f2a750f675 100644 --- a/src/Shared/UnitTests/FileUtilities_Tests.cs +++ b/src/Shared/UnitTests/FileUtilities_Tests.cs @@ -9,7 +9,6 @@ using Microsoft.Build.Shared; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs index efb5d7297ab..fd5e9a82ed8 100644 --- a/src/Shared/UnitTests/NativeMethodsShared_Tests.cs +++ b/src/Shared/UnitTests/NativeMethodsShared_Tests.cs @@ -8,7 +8,6 @@ using System.Runtime.Versioning; using Microsoft.Build.Shared; using Xunit; -using Xunit.NetCore.Extensions; @@ -123,7 +122,7 @@ public void SetCurrentDirectoryDoesNotSetNonexistentFolder() { for (int i = 0; i < 10; i++) { - nonexistentDirectory = Path.Combine(currentDirectory, "foo", "bar", "baz") + Guid.NewGuid(); + nonexistentDirectory = $"{Path.Combine(currentDirectory, "foo", "bar", "baz")}{Guid.NewGuid()}"; if (!Directory.Exists(nonexistentDirectory)) { diff --git a/src/Shared/UnitTests/XmakeAttributes_Tests.cs b/src/Shared/UnitTests/XmakeAttributes_Tests.cs index 25e7cb3a50d..da7375f123a 100644 --- a/src/Shared/UnitTests/XmakeAttributes_Tests.cs +++ b/src/Shared/UnitTests/XmakeAttributes_Tests.cs @@ -1,11 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +using System; using Microsoft.Build.Shared; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable @@ -130,7 +130,7 @@ public void TestMergeRuntimeValuesCurrentToCore() public void TestArchitectureValuesMatch() { string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture(); - string notCurrentArchitecture = EnvironmentUtilities.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; + string notCurrentArchitecture = Environment.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; Assert.True(XMakeAttributes.ArchitectureValuesMatch(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture)); Assert.True(XMakeAttributes.ArchitectureValuesMatch(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.x64)); @@ -145,7 +145,7 @@ public void TestArchitectureValuesMatch() public void TestMergeArchitectureValues() { string currentArchitecture = XMakeAttributes.GetCurrentMSBuildArchitecture(); - string notCurrentArchitecture = EnvironmentUtilities.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; + string notCurrentArchitecture = Environment.Is64BitProcess ? XMakeAttributes.MSBuildArchitectureValues.x86 : XMakeAttributes.MSBuildArchitectureValues.x64; string mergedArchitecture; Assert.True(XMakeAttributes.TryMergeArchitectureValues(XMakeAttributes.MSBuildArchitectureValues.any, XMakeAttributes.MSBuildArchitectureValues.currentArchitecture, out mergedArchitecture)); diff --git a/src/Shared/VersionUtilities.cs b/src/Shared/VersionUtilities.cs index 91c4721f00c..99e1e36774d 100644 --- a/src/Shared/VersionUtilities.cs +++ b/src/Shared/VersionUtilities.cs @@ -74,7 +74,7 @@ internal static Version ConvertToVersion(string version, bool throwException) // Versions must have at least a Major and a Minor (e.g. 10.0), so if it's // just one number without a decimal, add a decimal and a 0. Random strings // like "tmp" will be filtered out in the Parse() or TryParse() steps - if (version.IndexOf(".") == -1) + if (version.IndexOf('.') == -1) { version += ".0"; } diff --git a/src/Shared/XMakeElements.cs b/src/Shared/XMakeElements.cs index 991feb5796c..69528ab540a 100644 --- a/src/Shared/XMakeElements.cs +++ b/src/Shared/XMakeElements.cs @@ -1,6 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. - +#if NET +using System.Buffers; +#endif using System.Collections.Generic; #nullable disable @@ -35,7 +37,12 @@ internal static class XMakeElements internal const string usingTaskBody = "Task"; internal const string sdk = "Sdk"; - internal static readonly char[] InvalidTargetNameCharacters = [ '$', '@', '(', ')', '%', '*', '?', '.' ]; +#if NET + internal static readonly SearchValues InvalidTargetNameCharacters = SearchValues.Create( +#else + internal static readonly char[] InvalidTargetNameCharacters = ( +#endif + ['$', '@', '(', ')', '%', '*', '?', '.']); // Names that cannot be used as property or item names because they are reserved internal static readonly HashSet ReservedItemNames = diff --git a/src/StringTools.UnitTests/StringTools.UnitTests.net35.csproj b/src/StringTools.UnitTests/StringTools.UnitTests.net35.csproj index b63fa1e4e9e..d45c242ed70 100644 --- a/src/StringTools.UnitTests/StringTools.UnitTests.net35.csproj +++ b/src/StringTools.UnitTests/StringTools.UnitTests.net35.csproj @@ -1,9 +1,5 @@ - - - $(FullFrameworkTFM) $(RuntimeOutputPlatformTarget) diff --git a/src/StringTools/FowlerNollVo1aHash.cs b/src/StringTools/FowlerNollVo1aHash.cs index 5a9a876e4c0..56c2ca80891 100644 --- a/src/StringTools/FowlerNollVo1aHash.cs +++ b/src/StringTools/FowlerNollVo1aHash.cs @@ -1,8 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Runtime.InteropServices; -using System; namespace Microsoft.NET.StringTools { diff --git a/src/StringTools/InternableString.cs b/src/StringTools/InternableString.cs index 7e657d56cdb..d19afe2a662 100644 --- a/src/StringTools/InternableString.cs +++ b/src/StringTools/InternableString.cs @@ -387,7 +387,7 @@ private static unsafe uint GetHashCodeHelper(char* charPtr, int length, uint has [MethodImpl(MethodImplOptions.AggressiveInlining)] private static uint RotateLeft(uint value, int offset) { -#if NETCOREAPP +#if NET return System.Numerics.BitOperations.RotateLeft(value, offset); #else // Copied from System\Numerics\BitOperations.cs in dotnet/runtime as the routine is not available on .NET Framework. diff --git a/src/StringTools/StringTools.csproj b/src/StringTools/StringTools.csproj index 4809373c67c..5015904acba 100644 --- a/src/StringTools/StringTools.csproj +++ b/src/StringTools/StringTools.csproj @@ -1,7 +1,6 @@ - $(LibraryTargetFrameworks) - $(LibraryTargetFrameworks);net35 + $(LibraryTargetFrameworks);net35 AnyCPU true true diff --git a/src/StringTools/WeakStringCacheInterner.cs b/src/StringTools/WeakStringCacheInterner.cs index 2eb3fd23231..cc53c501ce3 100644 --- a/src/StringTools/WeakStringCacheInterner.cs +++ b/src/StringTools/WeakStringCacheInterner.cs @@ -85,7 +85,7 @@ public string InternableToString(ref InternableString candidate) string expectedString = candidate.ExpensiveConvertToString(); if (!String.Equals(internedString, expectedString)) { - throw new InvalidOperationException(String.Format("Interned string {0} should have been {1}", internedString, expectedString)); + throw new InvalidOperationException($"Interned string {internedString} should have been {expectedString}"); } #endif @@ -137,12 +137,12 @@ public string FormatStatistics() if (_internCallCountsByString != null) { - result.AppendLine(string.Format("\n{0}{1}{0}", new string('=', 41 - (title.Length / 2)), title)); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Hits", _regularInternHits, "hits")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "WeakStringCache Misses", _regularInternMisses, "misses")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Strings*", _internEliminatedStrings, "strings")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Eliminated Chars", _internEliminatedChars, "chars")); - result.AppendLine(string.Format("||{0,50}|{1,20:N0}|{2,8}|", "Estimated Eliminated Bytes", _internEliminatedChars * 2, "bytes")); + result.AppendLine($"\n{new string('=', 41 - (title.Length / 2))}{title}{new string('=', 41 - (title.Length / 2))}"); + result.AppendLine($"||{"WeakStringCache Hits",50}|{_regularInternHits,20:N0}|{"hits",8}|"); + result.AppendLine($"||{"WeakStringCache Misses",50}|{_regularInternMisses,20:N0}|{"misses",8}|"); + result.AppendLine($"||{"Eliminated Strings*",50}|{_internEliminatedStrings,20:N0}|{"strings",8}|"); + result.AppendLine($"||{"Eliminated Chars",50}|{_internEliminatedChars,20:N0}|{"chars",8}|"); + result.AppendLine($"||{"Estimated Eliminated Bytes",50}|{_internEliminatedChars * 2,20:N0}|{"bytes",8}|"); result.AppendLine("Elimination assumes that strings provided were unique objects."); result.AppendLine("|---------------------------------------------------------------------------------|"); @@ -158,7 +158,7 @@ public string FormatStatistics() WeakStringCache.DebugInfo debugInfo = _weakStringCache.GetDebugInfo(); result.AppendLine("WeakStringCache statistics:"); - result.AppendLine(string.Format("String count live/collected/total = {0}/{1}/{2}", debugInfo.LiveStringCount, debugInfo.CollectedStringCount, debugInfo.LiveStringCount + debugInfo.CollectedStringCount)); + result.AppendLine($"String count live/collected/total = {debugInfo.LiveStringCount}/{debugInfo.CollectedStringCount}/{debugInfo.LiveStringCount + debugInfo.CollectedStringCount}"); } else { diff --git a/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs b/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs index 76123167442..cdfb19a59bf 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/Miscellaneous.cs @@ -18,7 +18,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; using FrameworkNameVersioning = System.Runtime.Versioning.FrameworkName; using SystemProcessorArchitecture = System.Reflection.ProcessorArchitecture; diff --git a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceTestFixture.cs b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceTestFixture.cs index 774d8f3d735..ddfb1bd9c4f 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceTestFixture.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/ResolveAssemblyReferenceTestFixture.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; using System.IO; using System.Runtime.Versioning; using Microsoft.Build.Framework; diff --git a/src/Tasks.UnitTests/AssemblyDependency/SpecificVersionPrimary.cs b/src/Tasks.UnitTests/AssemblyDependency/SpecificVersionPrimary.cs index a27434df16c..c11fc53cfcc 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/SpecificVersionPrimary.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/SpecificVersionPrimary.cs @@ -9,7 +9,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/AssemblyDependency/SuggestedRedirects.cs b/src/Tasks.UnitTests/AssemblyDependency/SuggestedRedirects.cs index 9ab178efbb5..2dc93f2fe56 100644 --- a/src/Tasks.UnitTests/AssemblyDependency/SuggestedRedirects.cs +++ b/src/Tasks.UnitTests/AssemblyDependency/SuggestedRedirects.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.IO; using System.Reflection; -using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; diff --git a/src/Tasks.UnitTests/AssignCulture_Tests.cs b/src/Tasks.UnitTests/AssignCulture_Tests.cs index 47ceeb5d7b0..fd2d3e0995b 100644 --- a/src/Tasks.UnitTests/AssignCulture_Tests.cs +++ b/src/Tasks.UnitTests/AssignCulture_Tests.cs @@ -2,12 +2,10 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/AssignLinkMetadata_Tests.cs b/src/Tasks.UnitTests/AssignLinkMetadata_Tests.cs index 9e70ea4d20a..a0eb459595a 100644 --- a/src/Tasks.UnitTests/AssignLinkMetadata_Tests.cs +++ b/src/Tasks.UnitTests/AssignLinkMetadata_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; diff --git a/src/Tasks.UnitTests/AssignTargetPath_Tests.cs b/src/Tasks.UnitTests/AssignTargetPath_Tests.cs index 2289f896eaf..f2c08c59c6d 100644 --- a/src/Tasks.UnitTests/AssignTargetPath_Tests.cs +++ b/src/Tasks.UnitTests/AssignTargetPath_Tests.cs @@ -3,7 +3,6 @@ using System.Collections.Generic; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Shouldly; diff --git a/src/Tasks.UnitTests/AxImp_Tests.cs b/src/Tasks.UnitTests/AxImp_Tests.cs index 52d433c32f9..6525c1656ea 100644 --- a/src/Tasks.UnitTests/AxImp_Tests.cs +++ b/src/Tasks.UnitTests/AxImp_Tests.cs @@ -1,11 +1,9 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs b/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs index ce9d7feadcb..99df3fbf783 100644 --- a/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs +++ b/src/Tasks.UnitTests/AxTlbBaseTask_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Utilities; using Microsoft.Runtime.Hosting; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/CodeTaskFactoryEmbeddedFileInBinlogTestHelper.cs b/src/Tasks.UnitTests/CodeTaskFactoryEmbeddedFileInBinlogTestHelper.cs index 20542cd8ce8..20c5c443a17 100644 --- a/src/Tasks.UnitTests/CodeTaskFactoryEmbeddedFileInBinlogTestHelper.cs +++ b/src/Tasks.UnitTests/CodeTaskFactoryEmbeddedFileInBinlogTestHelper.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.IO; using System.IO.Compression; using Microsoft.Build.Logging; diff --git a/src/Tasks.UnitTests/ComReference_Tests.cs b/src/Tasks.UnitTests/ComReference_Tests.cs index 741cd74479e..5f2b31c1010 100644 --- a/src/Tasks.UnitTests/ComReference_Tests.cs +++ b/src/Tasks.UnitTests/ComReference_Tests.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/CombinePath_Tests.cs b/src/Tasks.UnitTests/CombinePath_Tests.cs index a719717e07e..080cf10f288 100644 --- a/src/Tasks.UnitTests/CombinePath_Tests.cs +++ b/src/Tasks.UnitTests/CombinePath_Tests.cs @@ -4,11 +4,9 @@ using System.IO; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/CombineTargetFrameworkInfoProperties_Tests.cs b/src/Tasks.UnitTests/CombineTargetFrameworkInfoProperties_Tests.cs index e22a715f8a4..4bf3eba2529 100644 --- a/src/Tasks.UnitTests/CombineTargetFrameworkInfoProperties_Tests.cs +++ b/src/Tasks.UnitTests/CombineTargetFrameworkInfoProperties_Tests.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.Build.Framework; using Microsoft.Build.Tasks; using Shouldly; diff --git a/src/Tasks.UnitTests/CreateItem_Tests.cs b/src/Tasks.UnitTests/CreateItem_Tests.cs index de09dcbc85e..90a57722f30 100644 --- a/src/Tasks.UnitTests/CreateItem_Tests.cs +++ b/src/Tasks.UnitTests/CreateItem_Tests.cs @@ -4,18 +4,14 @@ using System; using System.Collections.Generic; using System.IO; -using Microsoft.Build.Definition; -using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Microsoft.Build.UnitTests.Shared; using Microsoft.Build.Utilities; using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/Delete_Tests.cs b/src/Tasks.UnitTests/Delete_Tests.cs index d404c63246f..e523c13816c 100644 --- a/src/Tasks.UnitTests/Delete_Tests.cs +++ b/src/Tasks.UnitTests/Delete_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Utilities; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/Exec_Tests.cs b/src/Tasks.UnitTests/Exec_Tests.cs index 6997613405b..2f0f9e1ace3 100644 --- a/src/Tasks.UnitTests/Exec_Tests.cs +++ b/src/Tasks.UnitTests/Exec_Tests.cs @@ -16,7 +16,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable @@ -171,9 +170,20 @@ public void Timeout() Assert.Equal(expectedExitCode, exec.ExitCode); ((MockEngine)exec.BuildEngine).AssertLogContains("MSB5002"); int warningsCount = ((MockEngine)exec.BuildEngine).Warnings; - warningsCount.ShouldBe(1, + if (warningsCount == 1) + { + warningsCount.ShouldBe(1, $"Expected 1 warning, encountered {warningsCount}: " + string.Join(",", ((MockEngine)exec.BuildEngine).WarningEvents.Select(w => w.Message))); + } + else + { + // Occasionally temp files fail to delete because of virus checkers, so generate MSB5018 warning + ((MockEngine)exec.BuildEngine).AssertLogContains("MSB5018"); + warningsCount.ShouldBe(2, + $"Expected 2 warnings, encountered {warningsCount}: " + string.Join(",", + ((MockEngine)exec.BuildEngine).WarningEvents.Select(w => w.Message))); + } // ToolTask does not log an error on timeout. Assert.Equal(0, ((MockEngine)exec.BuildEngine).Errors); diff --git a/src/Tasks.UnitTests/FileStateTests.cs b/src/Tasks.UnitTests/FileStateTests.cs index 1048fc95358..1b23ffaba7d 100644 --- a/src/Tasks.UnitTests/FileStateTests.cs +++ b/src/Tasks.UnitTests/FileStateTests.cs @@ -6,7 +6,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/FindUnderPath_Tests.cs b/src/Tasks.UnitTests/FindUnderPath_Tests.cs index ce0be31a183..5b38b678b58 100644 --- a/src/Tasks.UnitTests/FindUnderPath_Tests.cs +++ b/src/Tasks.UnitTests/FindUnderPath_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; diff --git a/src/Tasks.UnitTests/FormatUrl_Tests.cs b/src/Tasks.UnitTests/FormatUrl_Tests.cs index 26fabaf9db5..8cc77f6358a 100644 --- a/src/Tasks.UnitTests/FormatUrl_Tests.cs +++ b/src/Tasks.UnitTests/FormatUrl_Tests.cs @@ -7,7 +7,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; namespace Microsoft.Build.UnitTests { diff --git a/src/Tasks.UnitTests/GetAssembliesMetadata_Tests.cs b/src/Tasks.UnitTests/GetAssembliesMetadata_Tests.cs index 8d50571656d..1c8bcd0ccf3 100644 --- a/src/Tasks.UnitTests/GetAssembliesMetadata_Tests.cs +++ b/src/Tasks.UnitTests/GetAssembliesMetadata_Tests.cs @@ -10,11 +10,11 @@ using System.Text; using System.Threading.Tasks; using Microsoft.Build.UnitTests; +using Microsoft.Build.UnitTests.Shared; using Shouldly; using Xunit; -using Xunit.Sdk; -using Microsoft.Build.UnitTests.Shared; using Xunit.Abstractions; +using Xunit.Sdk; namespace Microsoft.Build.Tasks.UnitTests { diff --git a/src/Tasks.UnitTests/GetInstalledSDKLocations_Tests.cs b/src/Tasks.UnitTests/GetInstalledSDKLocations_Tests.cs index 040199f9fb9..e5ad7426db7 100644 --- a/src/Tasks.UnitTests/GetInstalledSDKLocations_Tests.cs +++ b/src/Tasks.UnitTests/GetInstalledSDKLocations_Tests.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/GetSDKReference_Tests.cs b/src/Tasks.UnitTests/GetSDKReference_Tests.cs index db7e1f155f1..f5ff062f87b 100644 --- a/src/Tasks.UnitTests/GetSDKReference_Tests.cs +++ b/src/Tasks.UnitTests/GetSDKReference_Tests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Linq; using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -13,7 +12,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/HintPathResolver_Tests.cs b/src/Tasks.UnitTests/HintPathResolver_Tests.cs index 599a5750961..9548e32f421 100644 --- a/src/Tasks.UnitTests/HintPathResolver_Tests.cs +++ b/src/Tasks.UnitTests/HintPathResolver_Tests.cs @@ -8,7 +8,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/MakeDir_Tests.cs b/src/Tasks.UnitTests/MakeDir_Tests.cs index 8a50acbd4c3..7b29328f258 100644 --- a/src/Tasks.UnitTests/MakeDir_Tests.cs +++ b/src/Tasks.UnitTests/MakeDir_Tests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/Move_Tests.cs b/src/Tasks.UnitTests/Move_Tests.cs index ca8bee21b50..a951d75d216 100644 --- a/src/Tasks.UnitTests/Move_Tests.cs +++ b/src/Tasks.UnitTests/Move_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/OutputPathTests.cs b/src/Tasks.UnitTests/OutputPathTests.cs index af1d3902804..1bf85cccef8 100644 --- a/src/Tasks.UnitTests/OutputPathTests.cs +++ b/src/Tasks.UnitTests/OutputPathTests.cs @@ -12,7 +12,6 @@ using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/PortableTasks_Tests.cs b/src/Tasks.UnitTests/PortableTasks_Tests.cs index e409d1f61bd..cd051c5a90e 100644 --- a/src/Tasks.UnitTests/PortableTasks_Tests.cs +++ b/src/Tasks.UnitTests/PortableTasks_Tests.cs @@ -9,7 +9,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/RegressionTests.cs b/src/Tasks.UnitTests/RegressionTests.cs index ef1b944e160..924a0adb84b 100644 --- a/src/Tasks.UnitTests/RegressionTests.cs +++ b/src/Tasks.UnitTests/RegressionTests.cs @@ -8,7 +8,6 @@ using Microsoft.Build.UnitTests; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/ResolveComReference_Tests.cs b/src/Tasks.UnitTests/ResolveComReference_Tests.cs index 47fb7e3c853..9dc7b95f2c2 100644 --- a/src/Tasks.UnitTests/ResolveComReference_Tests.cs +++ b/src/Tasks.UnitTests/ResolveComReference_Tests.cs @@ -5,8 +5,6 @@ using System; using System.Collections.Generic; -using System.IO; -using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; diff --git a/src/Tasks.UnitTests/ResolveSDKReference_Tests.cs b/src/Tasks.UnitTests/ResolveSDKReference_Tests.cs index 775098727b3..26ee71150e0 100644 --- a/src/Tasks.UnitTests/ResolveSDKReference_Tests.cs +++ b/src/Tasks.UnitTests/ResolveSDKReference_Tests.cs @@ -12,7 +12,6 @@ using Microsoft.Build.Utilities; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; using SDKReference = Microsoft.Build.Tasks.ResolveSDKReference.SDKReference; #nullable disable diff --git a/src/Tasks.UnitTests/ResourceHandling/GenerateResourceOutOfProc_Tests.cs b/src/Tasks.UnitTests/ResourceHandling/GenerateResourceOutOfProc_Tests.cs index 472d732a973..b9d1c4eae42 100644 --- a/src/Tasks.UnitTests/ResourceHandling/GenerateResourceOutOfProc_Tests.cs +++ b/src/Tasks.UnitTests/ResourceHandling/GenerateResourceOutOfProc_Tests.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.IO; -using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; @@ -12,7 +11,6 @@ using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/ResourceHandling/GenerateResource_Tests.cs b/src/Tasks.UnitTests/ResourceHandling/GenerateResource_Tests.cs index 458fc147f78..772dfd5c743 100644 --- a/src/Tasks.UnitTests/ResourceHandling/GenerateResource_Tests.cs +++ b/src/Tasks.UnitTests/ResourceHandling/GenerateResource_Tests.cs @@ -11,12 +11,10 @@ using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Tasks; -using Microsoft.Build.UnitTests.Shared; using Microsoft.Build.Utilities; using Shouldly; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs index a77f9d5759e..e1867c0129a 100644 --- a/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs +++ b/src/Tasks.UnitTests/RoslynCodeTaskFactory_Tests.cs @@ -5,7 +5,6 @@ using System.Collections.Generic; using System.Linq; using System.Reflection; -using System.Text.RegularExpressions; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; diff --git a/src/Tasks.UnitTests/SGen_Tests.cs b/src/Tasks.UnitTests/SGen_Tests.cs index 75b712a53a7..2755cc179aa 100644 --- a/src/Tasks.UnitTests/SGen_Tests.cs +++ b/src/Tasks.UnitTests/SGen_Tests.cs @@ -4,7 +4,6 @@ using System; using System.IO; using System.Linq; -using Microsoft.Build.Shared; using Microsoft.Build.Tasks; using Shouldly; using Xunit; diff --git a/src/Tasks.UnitTests/SdkToolsPathUtility_Tests.cs b/src/Tasks.UnitTests/SdkToolsPathUtility_Tests.cs index f0cd952108b..a784e141ede 100644 --- a/src/Tasks.UnitTests/SdkToolsPathUtility_Tests.cs +++ b/src/Tasks.UnitTests/SdkToolsPathUtility_Tests.cs @@ -7,7 +7,6 @@ using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/SecurityUtil_Tests.cs b/src/Tasks.UnitTests/SecurityUtil_Tests.cs index 696e5ef1470..7ae12543968 100644 --- a/src/Tasks.UnitTests/SecurityUtil_Tests.cs +++ b/src/Tasks.UnitTests/SecurityUtil_Tests.cs @@ -2,15 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Runtime.ConstrainedExecution; using System.Runtime.Versioning; -using System.Security.Cryptography; using System.Security.Cryptography.X509Certificates; -using System.Text; -using System.Threading.Tasks; using Microsoft.Build.Tasks.Deployment.ManifestUtilities; using Shouldly; using Xunit; diff --git a/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Class1.cs b/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Class1.cs index 891e68b690d..d36d6c2c0d4 100644 --- a/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Class1.cs +++ b/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Class1.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Reflection; using System.Runtime.InteropServices; namespace Custom_COM diff --git a/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Properties/AssemblyInfo.cs b/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Properties/AssemblyInfo.cs index 233af2b505c..71939e001bb 100644 --- a/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Properties/AssemblyInfo.cs +++ b/src/Tasks.UnitTests/TestResources/Projects/Custom_COM/Custom_COM/Properties/AssemblyInfo.cs @@ -1,9 +1,8 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System.Resources; using System.Reflection; -using System.Runtime.CompilerServices; +using System.Resources; using System.Runtime.InteropServices; // General Information about an assembly is controlled through the following diff --git a/src/Tasks.UnitTests/Unzip_Tests.cs b/src/Tasks.UnitTests/Unzip_Tests.cs index bcfe0b54460..e2e1494ee52 100644 --- a/src/Tasks.UnitTests/Unzip_Tests.cs +++ b/src/Tasks.UnitTests/Unzip_Tests.cs @@ -5,13 +5,11 @@ using System.Diagnostics; using System.IO; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.UnitTests; using Microsoft.Build.UnitTests.Shared; using Microsoft.Build.Utilities; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/WriteCodeFragment_Tests.cs b/src/Tasks.UnitTests/WriteCodeFragment_Tests.cs index 1c8dffcff37..d077c4f8ec7 100644 --- a/src/Tasks.UnitTests/WriteCodeFragment_Tests.cs +++ b/src/Tasks.UnitTests/WriteCodeFragment_Tests.cs @@ -10,7 +10,6 @@ using Microsoft.Build.Utilities; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #nullable disable diff --git a/src/Tasks.UnitTests/XmlPeek_Tests.cs b/src/Tasks.UnitTests/XmlPeek_Tests.cs index 63ce7c1be53..81b0a7ddd89 100644 --- a/src/Tasks.UnitTests/XmlPeek_Tests.cs +++ b/src/Tasks.UnitTests/XmlPeek_Tests.cs @@ -6,7 +6,6 @@ using System.Linq; using Microsoft.Build.Evaluation; -using Microsoft.Build.Framework; using Microsoft.Build.Tasks; using Microsoft.Build.Utilities; diff --git a/src/Tasks/AppConfig/BindingRedirect.cs b/src/Tasks/AppConfig/BindingRedirect.cs index 8be7d2413c7..d29808b9b59 100644 --- a/src/Tasks/AppConfig/BindingRedirect.cs +++ b/src/Tasks/AppConfig/BindingRedirect.cs @@ -43,17 +43,21 @@ internal void Read(XmlReader reader) try { - if (dashPosition != -1) + if (dashPosition >= 0) { // This is a version range. - OldVersionLow = new Version(oldVersion.Substring(0, dashPosition)); - OldVersionHigh = new Version(oldVersion.Substring(dashPosition + 1)); +#if NET + OldVersionLow = Version.Parse(oldVersion.AsSpan(0, dashPosition)); + OldVersionHigh = Version.Parse(oldVersion.AsSpan(dashPosition + 1)); +#else + OldVersionLow = Version.Parse(oldVersion.Substring(0, dashPosition)); + OldVersionHigh = Version.Parse(oldVersion.Substring(dashPosition + 1)); +#endif } else { // This is a single version. - OldVersionLow = new Version(oldVersion); - OldVersionHigh = new Version(oldVersion); + OldVersionLow = OldVersionHigh = new Version(oldVersion); } } catch (Exception e) when (!ExceptionHandling.IsCriticalException(e)) diff --git a/src/Tasks/AssemblyDependency/AssemblyAttributes.cs b/src/Tasks/AssemblyDependency/AssemblyAttributes.cs index 4e6fd111a27..56d51302e29 100644 --- a/src/Tasks/AssemblyDependency/AssemblyAttributes.cs +++ b/src/Tasks/AssemblyDependency/AssemblyAttributes.cs @@ -1,12 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - namespace Microsoft.Build.Tasks.AssemblyDependency { /// diff --git a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs index 1453e8ab6d8..ded0c8b46bf 100644 --- a/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs +++ b/src/Tasks/AssemblyDependency/AssemblyFoldersFromConfig/AssemblyFoldersFromConfigCache.cs @@ -2,7 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Immutable; +using System.Collections.Generic; using System.IO; using System.Linq; using Microsoft.Build.Shared; @@ -19,7 +19,7 @@ internal class AssemblyFoldersFromConfigCache /// /// Set of files in ALL AssemblyFolderFromConfig directories /// - private readonly ImmutableHashSet _filesInDirectories = ImmutableHashSet.Empty; + private readonly HashSet _filesInDirectories; /// /// File exists delegate we are replacing @@ -45,12 +45,12 @@ internal AssemblyFoldersFromConfigCache(AssemblyFoldersFromConfig assemblyFolder } else { - _filesInDirectories = assemblyFoldersFromConfig.AsParallel() + _filesInDirectories = new(assemblyFoldersFromConfig.AsParallel() .Where(assemblyFolder => FileUtilities.DirectoryExistsNoThrow(assemblyFolder.DirectoryPath)) .SelectMany( assemblyFolder => - Directory.GetFiles(assemblyFolder.DirectoryPath, "*.*", SearchOption.TopDirectoryOnly)) - .ToImmutableHashSet(StringComparer.OrdinalIgnoreCase); + Directory.GetFiles(assemblyFolder.DirectoryPath, "*.*", SearchOption.TopDirectoryOnly)), + StringComparer.OrdinalIgnoreCase); } } diff --git a/src/Tasks/AssemblyDependency/AssemblyInformation.cs b/src/Tasks/AssemblyDependency/AssemblyInformation.cs index 531d95a8fc5..2a3c9c6aef3 100644 --- a/src/Tasks/AssemblyDependency/AssemblyInformation.cs +++ b/src/Tasks/AssemblyDependency/AssemblyInformation.cs @@ -12,12 +12,9 @@ #endif using System.Reflection; using System.Runtime.Versioning; -using System.Security.Cryptography; -using System.Security.Cryptography.X509Certificates; using System.Text; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using static Microsoft.Build.Shared.FileSystem.WindowsNative; #if FEATURE_ASSEMBLYLOADCONTEXT using System.Reflection.PortableExecutable; using System.Reflection.Metadata; @@ -50,7 +47,7 @@ internal class AssemblyInformation : DisposableBase #endif #if !FEATURE_ASSEMBLYLOADCONTEXT - private static string s_targetFrameworkAttribute = "System.Runtime.Versioning.TargetFrameworkAttribute"; + private const string s_targetFrameworkAttribute = "System.Runtime.Versioning.TargetFrameworkAttribute"; #endif #if !FEATURE_ASSEMBLYLOADCONTEXT // Borrowed from genman. diff --git a/src/Tasks/AssemblyDependency/GenerateBindingRedirects.cs b/src/Tasks/AssemblyDependency/GenerateBindingRedirects.cs index 99307761f8f..ad9503b4121 100644 --- a/src/Tasks/AssemblyDependency/GenerateBindingRedirects.cs +++ b/src/Tasks/AssemblyDependency/GenerateBindingRedirects.cs @@ -3,14 +3,14 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; +using System.Xml; using System.Xml.Linq; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using System.IO; -using System.Xml; #nullable disable diff --git a/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs b/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs index daeaae94b3b..1a6656c607d 100644 --- a/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs +++ b/src/Tasks/AssemblyDependency/GlobalAssemblyCache.cs @@ -283,7 +283,7 @@ internal static string GetLocation( bool useGacRarCache = Environment.GetEnvironmentVariable("MSBUILDDISABLEGACRARCACHE") == null; if (buildEngine != null && useGacRarCache) { - string key = "44d78b60-3bbe-48fe-9493-04119ebf515f" + "|" + targetProcessorArchitecture.ToString() + "|" + targetedRuntimeVersion.ToString() + "|" + fullFusionName.ToString() + "|" + specificVersion.ToString(); + string key = $"44d78b60-3bbe-48fe-9493-04119ebf515f|{targetProcessorArchitecture}|{targetedRuntimeVersion}|{fullFusionName}|{specificVersion}"; fusionNameToResolvedPath = buildEngine.GetRegisteredTaskObject(key, RegisteredTaskObjectLifetime.Build) as ConcurrentDictionary; if (fusionNameToResolvedPath == null) { diff --git a/src/Tasks/AssemblyDependency/InstalledAssemblies.cs b/src/Tasks/AssemblyDependency/InstalledAssemblies.cs index b7352e6b255..3c6fbf3ba98 100644 --- a/src/Tasks/AssemblyDependency/InstalledAssemblies.cs +++ b/src/Tasks/AssemblyDependency/InstalledAssemblies.cs @@ -3,7 +3,6 @@ using System; using System.Collections.Generic; -using System.Linq; using Microsoft.Build.Shared; #nullable disable diff --git a/src/Tasks/AssemblyDependency/Reference.cs b/src/Tasks/AssemblyDependency/Reference.cs index d9ba3671e32..78648beda64 100644 --- a/src/Tasks/AssemblyDependency/Reference.cs +++ b/src/Tasks/AssemblyDependency/Reference.cs @@ -725,7 +725,7 @@ internal HashSet RemappedAssemblyNames() /// internal void AddPreUnificationVersion(String referencePath, Version version, UnificationReason reason) { - string key = referencePath + version.ToString() + reason.ToString(); + string key = $"{referencePath}{version}{reason}"; // Only add a reference, version, and reason once. UnificationVersion unificationVersion; diff --git a/src/Tasks/AssemblyDependency/ReferenceTable.cs b/src/Tasks/AssemblyDependency/ReferenceTable.cs index f82b1b43eb0..2b57c7287f8 100644 --- a/src/Tasks/AssemblyDependency/ReferenceTable.cs +++ b/src/Tasks/AssemblyDependency/ReferenceTable.cs @@ -92,10 +92,6 @@ internal sealed class ReferenceTable private readonly GetAssemblyMetadata _getAssemblyMetadata; /// Delegate used to get the image runtime version of a file private readonly GetAssemblyRuntimeVersion _getRuntimeVersion; -#if FEATURE_WIN32_REGISTRY - /// Delegate to get the base registry key for AssemblyFoldersEx - private OpenBaseKey _openBaseKey; -#endif /// Version of the runtime we are targeting private readonly Version _targetedRuntimeVersion; @@ -320,9 +316,6 @@ internal ReferenceTable( _getRuntimeVersion = getRuntimeVersion; _projectTargetFramework = projectTargetFramework; _targetedRuntimeVersion = targetedRuntimeVersion; -#if FEATURE_WIN32_REGISTRY - _openBaseKey = openBaseKey; -#endif _targetFrameworkMoniker = targetFrameworkMoniker; _latestTargetFrameworkDirectories = latestTargetFrameworkDirectories; _copyLocalDependenciesWhenParentReferenceInGac = copyLocalDependenciesWhenParentReferenceInGac; @@ -807,9 +800,7 @@ private static void TryGatherAssemblyNameEssentials(string fusionName, ref Assem return; } - string newFusionName = String.Format(CultureInfo.InvariantCulture, - "{0}, Version={1}, Culture={2}, PublicKeyToken={3}", - name, version, culture, publicKeyToken); + string newFusionName = $"{name}, Version={version}, Culture={culture}, PublicKeyToken={publicKeyToken}"; // Now try to convert to an AssemblyName. try @@ -830,19 +821,20 @@ private static void TryGatherAssemblyNameEssentials(string fusionName, ref Assem private static void TryGetAssemblyNameComponent(string fusionName, string component, ref string value) { int position = fusionName.IndexOf(component + "=", StringComparison.Ordinal); - if (position == -1) + if (position < 0) { return; } + position += component.Length + 1; - int nextDelimiter = fusionName.IndexOfAny([',', ' '], position); - if (nextDelimiter == -1) + int nextDelimiter = fusionName.AsSpan(position).IndexOfAny(',', ' '); + if (nextDelimiter < 0) { value = fusionName.Substring(position); } else { - value = fusionName.Substring(position, nextDelimiter - position); + value = fusionName.Substring(position, nextDelimiter); } } @@ -2320,20 +2312,11 @@ private static bool CompareRefToDef(AssemblyName @ref, AssemblyName def) byte[] rpkt = @ref.GetPublicKeyToken(); byte[] dpkt = def.GetPublicKeyToken(); - - if (rpkt.Length != dpkt.Length) + if (!rpkt.AsSpan().SequenceEqual(dpkt.AsSpan())) { return false; } - for (int i = 0; i < rpkt.Length; i++) - { - if (rpkt[i] != dpkt[i]) - { - return false; - } - } - if (@ref.Version != def.Version) { return false; diff --git a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs index 8dd35fcdcbc..8a42269ce14 100644 --- a/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs +++ b/src/Tasks/AssemblyDependency/ResolveAssemblyReference.cs @@ -5,7 +5,9 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +#if !NET using System.Globalization; +#endif using System.IO; using System.Linq; using System.Reflection; @@ -1331,6 +1333,9 @@ internal static string ByteArrayToString(byte[] a) return null; } +#if NET + return Convert.ToHexStringLower(a); +#else var buffer = new StringBuilder(a.Length * 2); for (int i = 0; i < a.Length; ++i) { @@ -1338,6 +1343,7 @@ internal static string ByteArrayToString(byte[] a) } return buffer.ToString(); +#endif } /// @@ -1559,25 +1565,28 @@ private void LogInputs() } Log.LogMessage(importance, property, "AppConfigFile"); - Log.LogMessage(importance, indent + AppConfigFile); + Log.LogMessage(importance, $"{indent}{AppConfigFile}"); Log.LogMessage(importance, property, "AutoUnify"); - Log.LogMessage(importance, indent + AutoUnify.ToString()); + Log.LogMessage(importance, $"{indent}{AutoUnify}"); + + Log.LogMessage(importance, property, "EnableCustomCulture"); + Log.LogMessage(importance, $"{indent}{EnableCustomCulture}"); Log.LogMessage(importance, property, "EnableCustomCulture"); Log.LogMessage(importance, $"{indent}{EnableCustomCulture}"); Log.LogMessage(importance, property, "CopyLocalDependenciesWhenParentReferenceInGac"); - Log.LogMessage(importance, indent + _copyLocalDependenciesWhenParentReferenceInGac); + Log.LogMessage(importance, $"{indent}{_copyLocalDependenciesWhenParentReferenceInGac}"); Log.LogMessage(importance, property, "FindDependencies"); - Log.LogMessage(importance, indent + _findDependencies); + Log.LogMessage(importance, $"{indent}{_findDependencies}"); Log.LogMessage(importance, property, "TargetProcessorArchitecture"); - Log.LogMessage(importance, indent + TargetProcessorArchitecture); + Log.LogMessage(importance, $"{indent}{TargetProcessorArchitecture}"); Log.LogMessage(importance, property, "StateFile"); - Log.LogMessage(importance, indent + StateFile); + Log.LogMessage(importance, $"{indent}{StateFile}"); Log.LogMessage(importance, property, "InstalledAssemblySubsetTables"); foreach (ITaskItem installedAssemblySubsetTable in InstalledAssemblySubsetTables) @@ -1587,33 +1596,33 @@ private void LogInputs() } Log.LogMessage(importance, property, "IgnoreInstalledAssemblySubsetTable"); - Log.LogMessage(importance, indent + _ignoreDefaultInstalledAssemblySubsetTables); + Log.LogMessage(importance, $"{indent}{_ignoreDefaultInstalledAssemblySubsetTables}"); Log.LogMessage(importance, property, "TargetFrameworkSubsets"); foreach (string subset in _targetFrameworkSubsets) { - Log.LogMessage(importance, indent + subset); + Log.LogMessage(importance, $"{indent}{subset}"); } Log.LogMessage(importance, property, "FullTargetFrameworkSubsetNames"); foreach (string subset in FullTargetFrameworkSubsetNames) { - Log.LogMessage(importance, indent + subset); + Log.LogMessage(importance, $"{indent}{subset}"); } Log.LogMessage(importance, property, "ProfileName"); - Log.LogMessage(importance, indent + ProfileName); + Log.LogMessage(importance, $"{indent}{ProfileName}"); Log.LogMessage(importance, property, "FullFrameworkFolders"); foreach (string fullFolder in FullFrameworkFolders) { - Log.LogMessage(importance, indent + fullFolder); + Log.LogMessage(importance, $"{indent}{fullFolder}"); } Log.LogMessage(importance, property, "LatestTargetFrameworkDirectories"); foreach (string latestFolder in _latestTargetFrameworkDirectories) { - Log.LogMessage(importance, indent + latestFolder); + Log.LogMessage(importance, $"{indent}{latestFolder}"); } Log.LogMessage(importance, property, "ProfileTablesLocation"); @@ -2871,7 +2880,7 @@ internal static string GenerateSubSetName(string[] frameworkSubSetNames, ITaskIt } } - return String.Join(", ", subsetNames.ToArray()); + return String.Join(", ", subsetNames); } /// diff --git a/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs b/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs index c22ab73bc9f..8e7c85a73b3 100644 --- a/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs +++ b/src/Tasks/BootstrapperUtil/BootstrapperBuilder.cs @@ -492,9 +492,9 @@ internal string[] Cultures Refresh(); } - List list = _cultures.Values.Select(v => v.ToString()).ToList(); - list.Sort(); - return list.ToArray(); + string[] array = _cultures.Values.Select(v => v.ToString()).ToArray(); + Array.Sort(array); + return array; } } @@ -603,7 +603,7 @@ private void RefreshProducts() foreach (string strSubDirectory in Directory.GetDirectories(packagePath)) { int nStartIndex = packagePath.Length; - if ((strSubDirectory.ToCharArray())[nStartIndex] == System.IO.Path.DirectorySeparatorChar) + if (strSubDirectory[nStartIndex] == System.IO.Path.DirectorySeparatorChar) { nStartIndex++; } @@ -948,7 +948,7 @@ private void ExploreDirectory(string strSubDirectory, XmlElement rootElement, st } XmlNode langNode = langDoc.SelectSingleNode(BOOTSTRAPPER_PREFIX + ":Package", _xmlNamespaceManager); - Debug.Assert(langNode != null, string.Format(CultureInfo.CurrentCulture, "Unable to find a package node in {0}", strLangManifestFilename)); + Debug.Assert(langNode != null, $"Unable to find a package node in {strLangManifestFilename}"); if (langNode != null) { XmlElement langElement = (XmlElement)(_document.ImportNode(langNode, true)); @@ -1040,7 +1040,7 @@ private void ExploreDirectory(string strSubDirectory, XmlElement rootElement, st } else { - Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "Validation results already added for Product Code '{0}'", productCodeAttribute)); + Debug.WriteLine($"Validation results already added for Product Code '{productCodeAttribute}'"); } } } @@ -1512,7 +1512,7 @@ private bool BuildPackages(BuildSettings settings, XmlElement configElement, Res // Add the file size to the PackageFileNode XmlAttribute sizeAttribute = packageFileNode.OwnerDocument.CreateAttribute("Size"); var fi = new FileInfo(packageFileSource.Value); - sizeAttribute.Value = "" + (fi.Length.ToString(CultureInfo.InvariantCulture)); + sizeAttribute.Value = fi.Length.ToString(CultureInfo.InvariantCulture); MergeAttribute(packageFileNode, sizeAttribute); } } @@ -1547,7 +1547,7 @@ private bool BuildPackages(BuildSettings settings, XmlElement configElement, Res if (configElement != null) { configElement.AppendChild(configElement.OwnerDocument.ImportNode(node, true)); - DumpXmlToFile(node, string.Format(CultureInfo.CurrentCulture, "{0}.{1}.xml", package.Product.ProductCode, package.Culture)); + DumpXmlToFile(node, $"{package.Product.ProductCode}.{package.Culture}.xml"); } } @@ -1602,13 +1602,17 @@ private static string ByteArrayToString(byte[] byteArray) return null; } - var output = new StringBuilder(byteArray.Length); +#if NET + return Convert.ToHexString(byteArray); +#else + var output = new StringBuilder(byteArray.Length * 2); foreach (byte byteValue in byteArray) { output.Append(byteValue.ToString("X02", CultureInfo.InvariantCulture)); } return output.ToString(); +#endif } private static string GetFileHash(string filePath) @@ -1983,7 +1987,7 @@ private static Stream GetEmbeddedResourceStream(string name) { Assembly a = Assembly.GetExecutingAssembly(); Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(BootstrapperBuilder).Namespace, name)); - Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name)); + Debug.Assert(s != null, $"EmbeddedResource '{name}' not found"); return s; } diff --git a/src/Tasks/BootstrapperUtil/BuildMessage.cs b/src/Tasks/BootstrapperUtil/BuildMessage.cs index bc4272ed8d8..db219a6a7d0 100644 --- a/src/Tasks/BootstrapperUtil/BuildMessage.cs +++ b/src/Tasks/BootstrapperUtil/BuildMessage.cs @@ -13,9 +13,14 @@ namespace Microsoft.Build.Tasks.Deployment.Bootstrapper /// /// Represents messages that occur during the BootstrapperBuilder's Build operation. /// - public class BuildMessage : IBuildMessage + public partial class BuildMessage : IBuildMessage { - private static readonly Regex s_msbuildMessageCodePattern = new Regex(@"(\d+)$"); +#if NET + [GeneratedRegex(@"\d+$")] + private static partial Regex MsbuildMessageCodePattern { get; } +#else + private static Regex MsbuildMessageCodePattern { get; } = new Regex(@"\d+$"); +#endif private BuildMessage(BuildMessageSeverity severity, string message, string helpKeyword, string helpCode) { @@ -25,7 +30,7 @@ private BuildMessage(BuildMessageSeverity severity, string message, string helpK HelpCode = helpCode; if (!String.IsNullOrEmpty(HelpCode)) { - Match match = s_msbuildMessageCodePattern.Match(HelpCode); + Match match = MsbuildMessageCodePattern.Match(HelpCode); if (match.Success) { HelpId = int.Parse(match.Value, CultureInfo.InvariantCulture); diff --git a/src/Tasks/BootstrapperUtil/Product.cs b/src/Tasks/BootstrapperUtil/Product.cs index 6e9d0e1ceca..bf788c343b5 100644 --- a/src/Tasks/BootstrapperUtil/Product.cs +++ b/src/Tasks/BootstrapperUtil/Product.cs @@ -168,7 +168,7 @@ internal void AddPackage(Package package) } else { - Debug.WriteLine(String.Format(CultureInfo.CurrentCulture, "A package with culture '{0}' has already been added to product '{1}'", package.Culture.ToLowerInvariant(), ProductCode)); + Debug.WriteLine($"A package with culture '{package.Culture.ToLowerInvariant()}' has already been added to product '{ProductCode}'"); } } diff --git a/src/Tasks/BootstrapperUtil/Util.cs b/src/Tasks/BootstrapperUtil/Util.cs index 973ca74a61f..bfd845b297f 100644 --- a/src/Tasks/BootstrapperUtil/Util.cs +++ b/src/Tasks/BootstrapperUtil/Util.cs @@ -124,7 +124,12 @@ public static string GetDefaultPath(string visualStudioVersion) { dotIndex = visualStudioVersion.Length; } + +#if NET + if (Int32.TryParse(visualStudioVersion.AsSpan(0, dotIndex), out int majorVersion) && (majorVersion < 11)) +#else if (Int32.TryParse(visualStudioVersion.Substring(0, dotIndex), out int majorVersion) && (majorVersion < 11)) +#endif { visualStudioVersion = BOOTSTRAPPER_REGISTRY_PATH_VERSION_VS2010; } diff --git a/src/Tasks/ComReference.cs b/src/Tasks/ComReference.cs index 340be0e84b1..67e50ba29d6 100644 --- a/src/Tasks/ComReference.cs +++ b/src/Tasks/ComReference.cs @@ -263,7 +263,7 @@ internal static bool GetTypeLibNameForITypeLib(TaskLoggingHelper log, bool silen if (typeLibName.Length >= 4) { - if (string.Equals(typeLibName.Substring(typeLibName.Length - 4), ".dll", StringComparison.OrdinalIgnoreCase)) + if (typeLibName.AsSpan().EndsWith(".dll".AsSpan(), StringComparison.OrdinalIgnoreCase)) { typeLibName = typeLibName.Substring(0, typeLibName.Length - 4); } diff --git a/src/Tasks/CompatibilitySuppressions.xml b/src/Tasks/CompatibilitySuppressions.xml index 6e6f63a5eb6..fafd80dcb79 100644 --- a/src/Tasks/CompatibilitySuppressions.xml +++ b/src/Tasks/CompatibilitySuppressions.xml @@ -1,10 +1,6 @@  - CP0007 T:Microsoft.Build.Tasks.AL @@ -71,11 +67,6 @@ ref/netstandard2.0/Microsoft.Build.Tasks.Core.dll ref/net472/Microsoft.Build.Tasks.Core.dll - PKV004 .NETCoreApp,Version=v2.0 diff --git a/src/Tasks/Copy.cs b/src/Tasks/Copy.cs index 3cf26dc3eb4..96a0e17089b 100644 --- a/src/Tasks/Copy.cs +++ b/src/Tasks/Copy.cs @@ -7,7 +7,6 @@ using System.IO; using System.Runtime.InteropServices; using System.Threading; -using System.Threading.Tasks.Dataflow; using Microsoft.Build.Eventing; using Microsoft.Build.Framework; @@ -15,8 +14,6 @@ using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Utilities; -using TPLTask = System.Threading.Tasks.Task; - #nullable disable namespace Microsoft.Build.Tasks diff --git a/src/Tasks/CreateProperty.cs b/src/Tasks/CreateProperty.cs index d077411d58c..7d7d131b149 100644 --- a/src/Tasks/CreateProperty.cs +++ b/src/Tasks/CreateProperty.cs @@ -1,7 +1,6 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using Microsoft.Build.Framework; #nullable disable diff --git a/src/Tasks/CultureInfoCache.cs b/src/Tasks/CultureInfoCache.cs index aed8b824d4f..66baa32d31f 100644 --- a/src/Tasks/CultureInfoCache.cs +++ b/src/Tasks/CultureInfoCache.cs @@ -2,13 +2,13 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.Globalization; -#if NET5_0_OR_GREATER +#if NET using System.Linq; -using Microsoft.Build.Framework; -#endif +#else +using System.Collections.Generic; using Microsoft.Build.Shared; +#endif #nullable disable @@ -23,7 +23,7 @@ namespace Microsoft.Build.Tasks /// internal static class CultureInfoCache { -#if !NET5_0_OR_GREATER +#if !NET private static readonly Lazy> ValidCultureNames = new Lazy>(() => InitializeValidCultureNames()); #endif @@ -33,6 +33,7 @@ internal static class CultureInfoCache // installed cultures, even if the registry keys are set. Therefore, add them to the list manually. private static readonly string[] pseudoLocales = ["qps-ploc", "qps-ploca", "qps-plocm", "qps-Latn-x-sh"]; +#if !NET private static HashSet InitializeValidCultureNames() { #if !FEATURE_CULTUREINFO_GETCULTURES @@ -55,6 +56,7 @@ private static HashSet InitializeValidCultureNames() return validCultureNames; } +#endif /// /// Determine if a culture string represents a valid instance. @@ -63,7 +65,7 @@ private static HashSet InitializeValidCultureNames() /// True if the culture is determined to be valid. internal static bool IsValidCultureString(string name) { -#if NET5_0_OR_GREATER +#if NET try { // GetCultureInfo throws if the culture doesn't exist @@ -80,7 +82,7 @@ internal static bool IsValidCultureString(string name) #endif } -#if !FEATURE_CULTUREINFO_GETCULTURES +#if !NET && !FEATURE_CULTUREINFO_GETCULTURES // Copied from https://github.com/aspnet/Localization/blob/5e1fb16071affd15f15b9c732833f3ae2ac46e10/src/Microsoft.Framework.Globalization.CultureInfoCache/CultureInfoList.cs // Regenerated using the tool (removed by https://github.com/aspnet/Localization/pull/130) // * Removed the empty string from the list diff --git a/src/Tasks/DownloadFile.cs b/src/Tasks/DownloadFile.cs index 71dc72e4c91..9028bd1f386 100644 --- a/src/Tasks/DownloadFile.cs +++ b/src/Tasks/DownloadFile.cs @@ -156,7 +156,7 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken) { response.EnsureSuccessStatusCode(); } -#if NET6_0_OR_GREATER +#if NET catch (HttpRequestException) { throw; @@ -203,7 +203,7 @@ private async Task DownloadAsync(Uri uri, CancellationToken cancellationToken) Log.LogMessageFromResources(MessageImportance.High, "DownloadFile.Downloading", SourceUrl, destinationFile.FullName, response.Content.Headers.ContentLength); #pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter using (Stream responseStream = await response.Content.ReadAsStreamAsync( -#if NET6_0_OR_GREATER +#if NET cancellationToken #endif ).ConfigureAwait(false)) @@ -260,7 +260,7 @@ private static bool IsRetriable(Exception exception, out Exception actualExcepti } } -#if NET6_0_OR_GREATER +#if NET // net5.0 included StatusCode in the HttpRequestException. switch (httpRequestException.StatusCode) { @@ -329,7 +329,7 @@ private bool TryGetFileName(HttpResponseMessage response, out string filename) return !String.IsNullOrWhiteSpace(filename); } -#if !NET6_0_OR_GREATER +#if !NET /// /// Represents a wrapper around the that also contains the . /// DEPRECATED as of net5.0, which included the StatusCode in the HttpRequestException class. diff --git a/src/Tasks/Error.cs b/src/Tasks/Error.cs index 9d5ad2ab386..b507c65e066 100644 --- a/src/Tasks/Error.cs +++ b/src/Tasks/Error.cs @@ -3,7 +3,9 @@ #nullable disable +#if NET using System.Diagnostics.CodeAnalysis; +#endif namespace Microsoft.Build.Tasks { diff --git a/src/Tasks/Exec.cs b/src/Tasks/Exec.cs index cf87ebedec1..4daa47cf647 100644 --- a/src/Tasks/Exec.cs +++ b/src/Tasks/Exec.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +#if NET using System.Diagnostics.CodeAnalysis; +#endif using System.IO; using System.Text; using System.Text.RegularExpressions; diff --git a/src/Tasks/FileIO/GetFileHash.cs b/src/Tasks/FileIO/GetFileHash.cs index 2d1ec4f5308..b975be5fd5e 100644 --- a/src/Tasks/FileIO/GetFileHash.cs +++ b/src/Tasks/FileIO/GetFileHash.cs @@ -143,7 +143,7 @@ internal static byte[] ComputeHash(Func algorithmFactory, string using (var stream = File.OpenRead(filePath)) using (var algorithm = algorithmFactory()) { -#if NET5_0_OR_GREATER +#if NET return algorithm.ComputeHashAsync(stream, ct).Result; #else return algorithm.ComputeHash(stream); diff --git a/src/Tasks/FileIO/VerifyFileHash.cs b/src/Tasks/FileIO/VerifyFileHash.cs index 9c3eca7556b..58bd4ce7aa8 100644 --- a/src/Tasks/FileIO/VerifyFileHash.cs +++ b/src/Tasks/FileIO/VerifyFileHash.cs @@ -2,9 +2,9 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; +using System.Threading; using Microsoft.Build.Framework; using Microsoft.Build.Shared.FileSystem; -using System.Threading; #nullable disable diff --git a/src/Tasks/FindInvalidProjectReferences.cs b/src/Tasks/FindInvalidProjectReferences.cs index 791ac147af6..607df8b83cd 100644 --- a/src/Tasks/FindInvalidProjectReferences.cs +++ b/src/Tasks/FindInvalidProjectReferences.cs @@ -13,17 +13,22 @@ namespace Microsoft.Build.Tasks /// /// Returns the reference assembly paths to the various frameworks /// - public class FindInvalidProjectReferences : TaskExtension + public partial class FindInvalidProjectReferences : TaskExtension { #region Fields + private const string PlatformMonikerFormatPattern = @"(?^[^,]*),\s*Version=(?.*)"; + /// /// Regex for breaking up the platform moniker /// Example: XNA, Version=8.0 /// - private static readonly Regex s_platformMonikerFormat = new Regex( - @"(?^[^,]*),\s*Version=(?.*)", - RegexOptions.IgnoreCase); +#if NET + [GeneratedRegex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase)] + private static partial Regex PlatformMonikerRegex { get; } +#else + private static Regex PlatformMonikerRegex { get; } = new Regex(PlatformMonikerFormatPattern, RegexOptions.IgnoreCase); +#endif /// /// Reference moniker metadata @@ -111,7 +116,7 @@ public override bool Execute() /// private static bool ParseMoniker(string reference, out string platformIdentity, out Version platformVersion) { - Match match = s_platformMonikerFormat.Match(reference); + Match match = PlatformMonikerRegex.Match(reference); platformIdentity = String.Empty; bool parsedVersion = false; diff --git a/src/Tasks/FormatVersion.cs b/src/Tasks/FormatVersion.cs index 88cfc595cb5..86182dca239 100644 --- a/src/Tasks/FormatVersion.cs +++ b/src/Tasks/FormatVersion.cs @@ -47,9 +47,14 @@ public override bool Execute() { OutputVersion = "1.0.0.0"; } - else if (Version.EndsWith("*", StringComparison.Ordinal)) + else if (Version[Version.Length - 1] == '*') { - OutputVersion = Version.Substring(0, Version.Length - 1) + Revision.ToString("G", CultureInfo.InvariantCulture); + OutputVersion = +#if NET + string.Create(CultureInfo.InvariantCulture, $"{Version.AsSpan(0, Version.Length - 1)}{Revision:G}"); +#else + Version.Substring(0, Version.Length - 1) + Revision.ToString("G", CultureInfo.InvariantCulture); +#endif } else { diff --git a/src/Tasks/GenerateApplicationManifest.cs b/src/Tasks/GenerateApplicationManifest.cs index 1c32b6feb36..cbd90c75ed4 100644 --- a/src/Tasks/GenerateApplicationManifest.cs +++ b/src/Tasks/GenerateApplicationManifest.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Runtime.Versioning; using System.Xml; @@ -241,7 +240,7 @@ private bool AddIsolatedComReferences(ApplicationManifest manifest) } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateApplicationManifest.AddIsolatedComReferences t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateApplicationManifest.AddIsolatedComReferences t={Environment.TickCount - t1}"); return success; } @@ -326,7 +325,7 @@ private bool AddClickOnceFiles(ApplicationManifest manifest) } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateApplicationManifest.AddClickOnceFiles t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateApplicationManifest.AddClickOnceFiles t={Environment.TickCount - t1}"); return true; } diff --git a/src/Tasks/GenerateManifestBase.cs b/src/Tasks/GenerateManifestBase.cs index a58209b9053..54362377d72 100644 --- a/src/Tasks/GenerateManifestBase.cs +++ b/src/Tasks/GenerateManifestBase.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; using System.IO; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -519,7 +518,7 @@ private bool ResolveFiles() return false; } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifestBase.ResolveFiles t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifestBase.ResolveFiles t={Environment.TickCount - t1}"); return true; } @@ -625,8 +624,8 @@ private bool WriteManifest() return false; } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifestBase.WriteManifest t={0}", Environment.TickCount - t1)); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "Total time to generate manifest '{1}': t={0}", Environment.TickCount - _startTime, Path.GetFileName(OutputManifest.ItemSpec))); + Util.WriteLog($"GenerateManifestBase.WriteManifest t={Environment.TickCount - t1}"); + Util.WriteLog($"Total time to generate manifest '{Path.GetFileName(OutputManifest.ItemSpec)}': t={Environment.TickCount - _startTime}"); return true; } } diff --git a/src/Tasks/GenerateResource.cs b/src/Tasks/GenerateResource.cs index b888da26a59..43678c19db1 100644 --- a/src/Tasks/GenerateResource.cs +++ b/src/Tasks/GenerateResource.cs @@ -16,7 +16,6 @@ using System.Diagnostics.CodeAnalysis; using System.Globalization; using System.IO; -using System.Linq; using System.Resources; using System.Resources.Extensions; using System.Reflection; @@ -42,6 +41,7 @@ using Microsoft.Build.Utilities; #if FEATURE_RESXREADER_LIVEDESERIALIZATION using Microsoft.Win32; +using System.Linq; #endif #nullable disable @@ -145,7 +145,7 @@ public sealed partial class GenerateResource : TaskExtension, IIncrementalTask #if FEATURE_RESGEN // Our calculation is not quite correct. Using a number substantially less than 32768 in order to // be sure we don't exceed it. - private static int s_maximumCommandLength = 28000; + private const int s_maximumCommandLength = 28000; #endif // FEATURE_RESGEN // Contains the list of paths from which inputs will not be taken into account during up-to-date check. @@ -1705,7 +1705,7 @@ private void UpdateNewestUncorrelatedInputWriteTime() // Check the timestamp of each of the passed-in references to find the newest; // and then the additional inputs - ITaskItem[] inputs = this.References ?? [.. (this.AdditionalInputs ?? [])]; + var inputs = (this.References ?? []).Concat(this.AdditionalInputs ?? []); foreach (ITaskItem input in inputs) { @@ -1793,7 +1793,7 @@ private bool NeedSeparateAppDomain() string resolvedTypeName = typeName; // This type name might be an alias, so first resolve that if any. - int indexOfSeperator = typeName.IndexOf(",", StringComparison.Ordinal); + int indexOfSeperator = typeName.IndexOf(','); if (indexOfSeperator != -1) { @@ -2015,12 +2015,6 @@ private bool DetermineWhetherSerializedObjectLoads(string data) return result != null; } } -#endif - - /// - /// Chars that should be ignored in the nicely justified block of base64 - /// - private static readonly char[] s_specialChars = [' ', '\r', '\n']; /// /// Turns the nicely justified block of base64 found in a resx into a byte array. @@ -2028,7 +2022,7 @@ private bool DetermineWhetherSerializedObjectLoads(string data) /// private static byte[] ByteArrayFromBase64WrappedString(string text) { - if (text.IndexOfAny(s_specialChars) != -1) + if (text.AsSpan().IndexOfAny(' ', '\r', '\n') != -1) // Chars that should be ignored in the nicely justified block of base64 { StringBuilder sb = new StringBuilder(text.Length); for (int i = 0; i < text.Length; i++) @@ -2051,6 +2045,7 @@ private static byte[] ByteArrayFromBase64WrappedString(string text) return Convert.FromBase64String(text); } } +#endif /// /// Make sure that OutputResources has 1 file name for each name in Sources. @@ -2300,10 +2295,12 @@ internal string StronglyTypedClassName /// private List _inFiles; +#if !FEATURE_ASSEMBLYLOADCONTEXT /// /// List of satellite input files to process. /// private List _satelliteInFiles; +#endif /// /// List of output files to process. @@ -2315,11 +2312,6 @@ internal string StronglyTypedClassName /// private bool _extractResWFiles; - /// - /// Where to write extracted ResW files. - /// - private string _resWOutputDirectory; - private bool _usePreserializedResources; internal List ExtractedResWFiles @@ -2407,7 +2399,9 @@ internal void Run( _logger = log; _assemblyFiles = assemblyFilesList; _inFiles = inputs; +#if !FEATURE_ASSEMBLYLOADCONTEXT _satelliteInFiles = satelliteInputs; +#endif _outFiles = outputs; _useSourcePath = sourcePath; _stronglyTypedLanguage = language; @@ -2418,7 +2412,6 @@ internal void Run( _stronglyTypedClassIsPublic = publicClass; _readers = new List(); _extractResWFiles = extractingResWFiles; - _resWOutputDirectory = resWOutputDirectory; _portableLibraryCacheInfo = new List(); _usePreserializedResources = usePreserializedResources; _logWarningForBinaryFormatter = logWarningForBinaryFormatter; @@ -3044,7 +3037,7 @@ private void ReadResources(String filename, bool shouldUseSourcePath, String out default: // We should never get here, we've already checked the format - Debug.Fail("Unknown format " + format.ToString()); + Debug.Fail($"Unknown format {format}"); return; } _logger.LogMessageFromResources(MessageImportance.Low, "GenerateResource.ReadResourceMessage", reader.resources.Count, filename); @@ -3325,7 +3318,7 @@ private void WriteResources(ReaderInfo reader, String filename) #if FEATURE_RESXREADER_LIVEDESERIALIZATION WriteResources(reader, new ResXResourceWriter(filename)); // closes writer for us #else - _logger.LogError(format.ToString() + " not supported on .NET Core MSBuild"); + _logger.LogError($"{format} not supported on .NET Core MSBuild"); #endif break; @@ -3339,7 +3332,7 @@ private void WriteResources(ReaderInfo reader, String filename) default: // We should never get here, we've already checked the format - Debug.Fail("Unknown format " + format.ToString()); + Debug.Fail($"Unknown format {format}"); break; } } @@ -3695,7 +3688,13 @@ private void ReadTextResources(ReaderInfo reader, String fileName) } try { - ch = (char)UInt16.Parse(new String(hex), NumberStyles.HexNumber, CultureInfo.CurrentCulture); + ch = (char)UInt16.Parse( +#if NET + hex, +#else + new String(hex), +#endif + NumberStyles.HexNumber, CultureInfo.CurrentCulture); } catch (FormatException) { @@ -4150,7 +4149,7 @@ public Type GetType(string name, bool throwOnError, bool ignoreCase) result = a.GetType(name, false, ignoreCase); if (result == null) { - int indexOfComma = name.IndexOf(",", StringComparison.Ordinal); + int indexOfComma = name.IndexOf(','); if (indexOfComma != -1) { string shortName = name.Substring(0, indexOfComma); diff --git a/src/Tasks/GetAssembliesMetadata.cs b/src/Tasks/GetAssembliesMetadata.cs index 73a3877001f..c3186034f57 100644 --- a/src/Tasks/GetAssembliesMetadata.cs +++ b/src/Tasks/GetAssembliesMetadata.cs @@ -2,20 +2,11 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Concurrent; using System.Collections.Generic; -using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; -using System.Linq; -using System.Reflection; using System.Runtime.Versioning; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Build.BackEnd; using Microsoft.Build.Framework; using Microsoft.Build.Shared; -using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.AssemblyDependency; using Microsoft.Build.Utilities; diff --git a/src/Tasks/GetAssemblyIdentity.cs b/src/Tasks/GetAssemblyIdentity.cs index 1d5c78c929e..375e0d2d430 100644 --- a/src/Tasks/GetAssemblyIdentity.cs +++ b/src/Tasks/GetAssemblyIdentity.cs @@ -3,12 +3,14 @@ using System; using System.Collections.Generic; -using System.Globalization; using System.Reflection; -using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; +#if !NET +using System.Globalization; +using System.Text; +#endif #nullable disable @@ -45,12 +47,17 @@ private static string ByteArrayToHex(Byte[] a) { return null; } + +#if NET + return Convert.ToHexString(a); +#else var s = new StringBuilder(a.Length * 2); foreach (Byte b in a) { s.Append(b.ToString("X02", CultureInfo.InvariantCulture)); } return s.ToString(); +#endif } public override bool Execute() diff --git a/src/Tasks/GetSDKReferenceFiles.cs b/src/Tasks/GetSDKReferenceFiles.cs index b1a24a00d6a..34f84047a3f 100644 --- a/src/Tasks/GetSDKReferenceFiles.cs +++ b/src/Tasks/GetSDKReferenceFiles.cs @@ -1090,7 +1090,7 @@ internal bool IsAssemblyListCacheFileUpToDate(string sdkIdentity, string sdkRoot string currentAssembly = String.Empty; try { -#if NETCOREAPP +#if NET currentAssembly = Assembly.GetExecutingAssembly().Location; #else currentAssembly = Assembly.GetExecutingAssembly().CodeBase; diff --git a/src/Tasks/Hash.cs b/src/Tasks/Hash.cs index 0bc42f56bfe..828012a2add 100644 --- a/src/Tasks/Hash.cs +++ b/src/Tasks/Hash.cs @@ -94,6 +94,9 @@ public override bool Execute() sha.TransformFinalBlock(shaBuffer, 0, shaBufferPosition); +#if NET + HashResult = Convert.ToHexStringLower(sha.Hash); +#else using (var stringBuilder = new ReuseableStringBuilder(sha.HashSize)) { foreach (var b in sha.Hash) @@ -102,6 +105,7 @@ public override bool Execute() } HashResult = stringBuilder.ToString(); } +#endif } finally { diff --git a/src/Tasks/MSBuild.cs b/src/Tasks/MSBuild.cs index 3169eaec219..12426e0c9aa 100644 --- a/src/Tasks/MSBuild.cs +++ b/src/Tasks/MSBuild.cs @@ -261,10 +261,7 @@ public override bool Execute() if (BuildInParallel) { skipProjects = new bool[Projects.Length]; - for (int i = 0; i < skipProjects.Length; i++) - { - skipProjects[i] = true; - } + skipProjects.AsSpan().Fill(true); } else { diff --git a/src/Tasks/ManifestUtil/ApplicationManifest.cs b/src/Tasks/ManifestUtil/ApplicationManifest.cs index 081762e8b84..22564f94bc6 100644 --- a/src/Tasks/ManifestUtil/ApplicationManifest.cs +++ b/src/Tasks/ManifestUtil/ApplicationManifest.cs @@ -9,7 +9,6 @@ using System.Runtime.InteropServices; using System.Xml; using System.Xml.Serialization; -using Microsoft.Build.Shared; #nullable disable @@ -508,7 +507,7 @@ private void ValidateCom() } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifest.CheckForComDuplicates t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifest.CheckForComDuplicates t={Environment.TickCount - t1}"); } private void ValidateConfig() @@ -661,11 +660,11 @@ private void ValidateReferencesForClickOnceApplication() // Check for two or more items with the same TargetPath... string key = assembly.TargetPath.ToLowerInvariant(); - if (!targetPathList.ContainsKey(key)) + if (!targetPathList.TryGetValue(key, out bool value)) { targetPathList.Add(key, false); } - else if (!targetPathList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateTargetPath", assembly.ToString()); targetPathList[key] = true; // only warn once per path @@ -707,18 +706,18 @@ private void ValidateReferencesForClickOnceApplication() // Check for two or more items with the same TargetPath... string key = file.TargetPath.ToLowerInvariant(); - if (!targetPathList.ContainsKey(key)) + if (!targetPathList.TryGetValue(key, out bool value)) { targetPathList.Add(key, false); } - else if (!targetPathList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateTargetPath", file.TargetPath); targetPathList[key] = true; // only warn once per path } } } - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "GenerateManifest.CheckManifestReferences t={0}", Environment.TickCount - t1)); + Util.WriteLog($"GenerateManifest.CheckManifestReferences t={Environment.TickCount - t1}"); } private void ValidateReferenceForPartialTrust(AssemblyReference assembly, TrustInfo trustInfo) diff --git a/src/Tasks/ManifestUtil/AssemblyIdentity.cs b/src/Tasks/ManifestUtil/AssemblyIdentity.cs index 6328476020f..28dbbfef17f 100644 --- a/src/Tasks/ManifestUtil/AssemblyIdentity.cs +++ b/src/Tasks/ManifestUtil/AssemblyIdentity.cs @@ -26,7 +26,7 @@ namespace Microsoft.Build.Tasks.Deployment.ManifestUtilities /// This is a serialization format, do not remove or change the private fields. [ComVisible(false)] [XmlRoot("AssemblyIdentity")] - public sealed class AssemblyIdentity + public sealed partial class AssemblyIdentity { /// /// Specifies which attributes are to be returned by the GetFullName function. @@ -59,6 +59,17 @@ public enum FullNameFlags private string _processorArchitecture; private string _type; + private const string AssemblyNamePattern = + "^(?[^,]*)(, Version=(?[^,]*))?(, Culture=(?[^,]*))?(, PublicKeyToken=(?[^,]*))?(, ProcessorArchitecture=(?[^,]*))?(, Type=(?[^,]*))?"; + +#if NET + [GeneratedRegex(AssemblyNamePattern)] + private static partial Regex AssemblyNameRegex { get; } +#else + private static Regex AssemblyNameRegex => _assemblyNameRegex ??= new Regex(AssemblyNamePattern); + private static Regex _assemblyNameRegex; +#endif + /// /// Initializes a new instance of the AssemblyIdentity class. /// @@ -165,7 +176,7 @@ public AssemblyIdentity(AssemblyIdentity identity) public static AssemblyIdentity FromAssemblyName(string assemblyName) { // NOTE: We're not using System.Reflection.AssemblyName class here because we need ProcessorArchitecture and Type attributes. - Regex re = new Regex("^(?[^,]*)(, Version=(?[^,]*))?(, Culture=(?[^,]*))?(, PublicKeyToken=(?[^,]*))?(, ProcessorArchitecture=(?[^,]*))?(, Type=(?[^,]*))?"); + Regex re = AssemblyNameRegex; Match m = re.Match(assemblyName); string name = m.Result("${name}"); string version = m.Result("${version}"); @@ -367,10 +378,15 @@ public bool IsInFramework(string frameworkIdentifier, string frameworkVersion) Version version = null; if (!string.IsNullOrEmpty(frameworkVersion)) { - // CA1307:Specify StringComparison. Suppressed since a valid string representation of a version would be parsed correctly even if the the first character is not "v". - if (frameworkVersion.StartsWith("v")) + if (frameworkVersion[0] == 'v') { - System.Version.TryParse(frameworkVersion.Substring(1), out version); + System.Version.TryParse( +#if NET + frameworkVersion.AsSpan(1), +#else + frameworkVersion.Substring(1), +#endif + out version); } else { @@ -514,14 +530,14 @@ internal string Resolve(string[] searchPaths, bool specificVersion) foreach (string searchPath in searchPaths) { - string file = String.Format(CultureInfo.InvariantCulture, "{0}.dll", _name); + string file = $"{_name}.dll"; string path = Path.Combine(searchPath, file); if (FileSystems.Default.FileExists(path) && IsEqual(this, FromFile(path), specificVersion)) { return path; } - file = String.Format(CultureInfo.InvariantCulture, "{0}.manifest", _name); + file = $"{_name}.manifest"; path = Path.Combine(searchPath, file); if (FileSystems.Default.FileExists(path) && IsEqual(this, FromManifest(path), specificVersion)) { diff --git a/src/Tasks/ManifestUtil/ConvertUtil.cs b/src/Tasks/ManifestUtil/ConvertUtil.cs index 7945fed64e5..88be5e23f8a 100644 --- a/src/Tasks/ManifestUtil/ConvertUtil.cs +++ b/src/Tasks/ManifestUtil/ConvertUtil.cs @@ -26,11 +26,11 @@ public static bool ToBoolean(string value, bool defaultValue) } catch (FormatException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}, returning {2}", value, typeof(bool).Name, defaultValue.ToString())); + Debug.Fail($"Invalid value '{value}' for {typeof(bool).Name}, returning {defaultValue}"); } catch (ArgumentException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}, returning {2}", value, typeof(bool).Name, defaultValue.ToString())); + Debug.Fail($"Invalid value '{value}' for {typeof(bool).Name}, returning {defaultValue}"); } } return defaultValue; diff --git a/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs b/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs index 01cb5f8f5d3..dd5d26ca355 100644 --- a/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs +++ b/src/Tasks/ManifestUtil/EmbeddedManifestReader.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; using System.IO; using System.Runtime.InteropServices; @@ -74,7 +73,7 @@ public static Stream Read(string path) int t1 = Environment.TickCount; EmbeddedManifestReader r = new EmbeddedManifestReader(path); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "EmbeddedManifestReader.Read t={0}", Environment.TickCount - t1)); + Util.WriteLog($"EmbeddedManifestReader.Read t={Environment.TickCount - t1}"); return r._manifest; } } diff --git a/src/Tasks/ManifestUtil/Manifest.cs b/src/Tasks/ManifestUtil/Manifest.cs index d8e5b2a8fdd..60b55b7cb1e 100644 --- a/src/Tasks/ManifestUtil/Manifest.cs +++ b/src/Tasks/ManifestUtil/Manifest.cs @@ -10,7 +10,6 @@ using System.Runtime.InteropServices; using System.Xml; using System.Xml.Serialization; -using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; #nullable disable @@ -630,11 +629,11 @@ private void ValidateReferences() // Check for two or more assemblies with the same identity... string identity = assembly.AssemblyIdentity.GetFullName(AssemblyIdentity.FullNameFlags.All); string key = identity.ToLowerInvariant(); - if (!identityList.ContainsKey(key)) + if (!identityList.TryGetValue(key, out bool value)) { identityList.Add(key, false); } - else if (!identityList[key]) + else if (!value) { OutputMessages.AddWarningMessage("GenerateManifest.DuplicateAssemblyIdentity", identity); identityList[key] = true; // only warn once per identity diff --git a/src/Tasks/ManifestUtil/ManifestFormatter.cs b/src/Tasks/ManifestUtil/ManifestFormatter.cs index d7d7ee01eb1..6300dba4d0b 100644 --- a/src/Tasks/ManifestUtil/ManifestFormatter.cs +++ b/src/Tasks/ManifestUtil/ManifestFormatter.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Globalization; using System.IO; using System.Text; using System.Xml; @@ -97,8 +96,8 @@ public static Stream Format(Stream input) w.WriteEndDocument(); w.Flush(); m.Position = 0; - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.Format t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.Format t={Environment.TickCount - t1}"); return m; } } -} \ No newline at end of file +} diff --git a/src/Tasks/ManifestUtil/ManifestReader.cs b/src/Tasks/ManifestUtil/ManifestReader.cs index 131de566c33..87e7e3d452d 100644 --- a/src/Tasks/ManifestUtil/ManifestReader.cs +++ b/src/Tasks/ManifestUtil/ManifestReader.cs @@ -54,9 +54,7 @@ private static XmlDocument GetXmlDocument(string path) using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { byte[] buffer = new byte[2]; -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' The check of bytes happens later in the code. In case of invalid documents the code will throw an exception during xml loading. - s.Read(buffer, 0, 2); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + s.ReadExactly(buffer, 0, 2); s.Position = 0; var document = new XmlDocument(); var xrSettings = new XmlReaderSettings { DtdProcessing = DtdProcessing.Ignore }; @@ -140,9 +138,7 @@ public static Manifest ReadManifest(string manifestType, string path, bool prese using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { byte[] buffer = new byte[2]; -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' The check of bytes happens later in the code. In case of invalid document the exception is expected later - s.Read(buffer, 0, 2); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + s.ReadExactly(buffer, 0, 2); s.Position = 0; // if first two bytes are "MZ" then we're looking at an .exe or a .dll not a .manifest if ((buffer[0] == 0x4D) && (buffer[1] == 0x5A)) @@ -246,7 +242,7 @@ private static Manifest Deserialize(Stream s) using (XmlReader xr = XmlReader.Create(s, xrSettings)) { var m = (Manifest)xs.Deserialize(xr); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestReader.Deserialize t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestReader.Deserialize t={Environment.TickCount - t1}"); return m; } } diff --git a/src/Tasks/ManifestUtil/ManifestWriter.cs b/src/Tasks/ManifestUtil/ManifestWriter.cs index 8da08fbacde..71d3a1478b6 100644 --- a/src/Tasks/ManifestUtil/ManifestWriter.cs +++ b/src/Tasks/ManifestUtil/ManifestWriter.cs @@ -3,7 +3,6 @@ using System; using System.Collections; -using System.Globalization; using System.IO; using System.Runtime.InteropServices; using System.Xml.Serialization; @@ -28,7 +27,7 @@ private static Stream Serialize(Manifest manifest) int t1 = Environment.TickCount; s.Serialize(w, manifest); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.Serialize t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.Serialize t={Environment.TickCount - t1}"); w.Flush(); m.Position = 0; @@ -188,7 +187,7 @@ private static void WriteManifest(Manifest manifest, Stream output, string targe Util.WriteLogFile(n + ".write.3-formatted.xml", s4); Util.CopyStream(s4, output); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.WriteManifest t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.WriteManifest t={Environment.TickCount - t1}"); } } } diff --git a/src/Tasks/ManifestUtil/MetadataReader.cs b/src/Tasks/ManifestUtil/MetadataReader.cs index fe8269ecdca..efd6d271087 100644 --- a/src/Tasks/ManifestUtil/MetadataReader.cs +++ b/src/Tasks/ManifestUtil/MetadataReader.cs @@ -173,7 +173,12 @@ private string GetPublicKeyToken() an.SetPublicKey(pk); byte[] pkt = an.GetPublicKeyToken(); - publicKeyToken = BitConverter.ToString(pkt).Replace("-", ""); + publicKeyToken = +#if NET + Convert.ToHexString(pkt); +#else + BitConverter.ToString(pkt).Replace("-", ""); +#endif } if (!String.IsNullOrEmpty(publicKeyToken)) diff --git a/src/Tasks/ManifestUtil/PathUtil.cs b/src/Tasks/ManifestUtil/PathUtil.cs index 81c678e17cf..83184f8f618 100644 --- a/src/Tasks/ManifestUtil/PathUtil.cs +++ b/src/Tasks/ManifestUtil/PathUtil.cs @@ -3,7 +3,11 @@ using System; using System.IO; +#if !NET using System.Linq; +#else +using System.Text; +#endif using Microsoft.Build.Shared; #nullable disable @@ -162,9 +166,7 @@ public static bool IsPEFile(string path) byte[] buffer = new byte[2]; using (Stream s = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' - s.Read(buffer, 0, 2); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + s.ReadExactly(buffer, 0, 2); } // if first two bytes are "MZ" then we're looking at an .exe or a .dll not a .manifest @@ -222,7 +224,13 @@ public static string Resolve(string path) { // Unfortunately Uri.Host is read-only, so we need to reconstruct it manually... int i = path.IndexOf(localHost, StringComparison.OrdinalIgnoreCase); - return i >= 0 ? path.Substring(0, i) + Environment.MachineName.ToLowerInvariant() + path.Substring(i + localHost.Length) : path; + return i >= 0 ? +#if NET + $"{path.AsSpan(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.AsSpan(i + localHost.Length)}" : +#else + $"{path.Substring(0, i)}{Environment.MachineName.ToLowerInvariant()}{path.Substring(i + localHost.Length)}" : +#endif + path; } return path; } @@ -231,7 +239,11 @@ public static string Resolve(string path) return Path.GetFullPath(path); // make sure it's a full path } - private static bool IsAsciiString(string str) - => str.All(c => c <= 127); + private static bool IsAsciiString(string str) => +#if NET + Ascii.IsValid(str); +#else + str.All(c => c <= 127); +#endif } } diff --git a/src/Tasks/ManifestUtil/SecurityUtil.cs b/src/Tasks/ManifestUtil/SecurityUtil.cs index a737ed59012..0013fbde3e2 100644 --- a/src/Tasks/ManifestUtil/SecurityUtil.cs +++ b/src/Tasks/ManifestUtil/SecurityUtil.cs @@ -502,7 +502,7 @@ public static PermissionSet XmlToPermissionSet(XmlElement element) [SupportedOSPlatform("windows")] public static void SignFile(string certThumbprint, Uri timestampUrl, string path) { - SignFile(certThumbprint, timestampUrl, path, null, null); + SignFile(certThumbprint, timestampUrl, path, targetFrameworkVersion: null, targetFrameworkIdentifier: null); } /// @@ -518,7 +518,7 @@ public static void SignFile(string certThumbprint, string path, string targetFrameworkVersion) { - SignFile(certThumbprint, timestampUrl, path, targetFrameworkVersion, null); + SignFile(certThumbprint, timestampUrl, path, targetFrameworkVersion, targetFrameworkIdentifier: null); } /// @@ -536,7 +536,7 @@ public static void SignFile(string certThumbprint, string targetFrameworkVersion, string targetFrameworkIdentifier) { - SignFile(certThumbprint, timestampUrl, path, targetFrameworkVersion, targetFrameworkIdentifier, false); + SignFile(certThumbprint, timestampUrl, path, targetFrameworkVersion, targetFrameworkIdentifier, disallowMansignTimestampFallback: false); } /// @@ -637,7 +637,7 @@ public static void SignFile(X509Certificate2 cert, Uri timestampUrl, string path { // setup resources System.Resources.ResourceManager resources = new System.Resources.ResourceManager("Microsoft.Build.Tasks.Core.Strings.ManifestUtilities", typeof(SecurityUtilities).Module.Assembly); - SignFileInternal(cert, timestampUrl, path, true, resources); + SignFileInternal(cert, timestampUrl, path, targetFrameworkSupportsSha256: true, resources); } [SupportedOSPlatform("windows")] @@ -701,6 +701,7 @@ private static void SignFileInternal(X509Certificate2 cert, { doc.Load(xr); } + var manifest = new SignedCmiManifest2(doc, useSha256); CmiManifestSigner2 signer; if (useSha256 && rsa is RSACryptoServiceProvider rsacsp) diff --git a/src/Tasks/ManifestUtil/TrustInfo.cs b/src/Tasks/ManifestUtil/TrustInfo.cs index bc10cb1d02c..5583c56c349 100644 --- a/src/Tasks/ManifestUtil/TrustInfo.cs +++ b/src/Tasks/ManifestUtil/TrustInfo.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Runtime.InteropServices; #if !RUNTIME_TYPE_NETCORE @@ -790,7 +789,7 @@ public void WriteManifest(Stream input, Stream output) output.Flush(); } document.Save(output); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "ManifestWriter.WriteTrustInfo t={0}", Environment.TickCount - t1)); + Util.WriteLog($"ManifestWriter.WriteTrustInfo t={Environment.TickCount - t1}"); } } } diff --git a/src/Tasks/ManifestUtil/Util.cs b/src/Tasks/ManifestUtil/Util.cs index a6b50bd028f..a2d89b437c7 100644 --- a/src/Tasks/ManifestUtil/Util.cs +++ b/src/Tasks/ManifestUtil/Util.cs @@ -67,47 +67,25 @@ public static string ByteArrayToHex(Byte[] a) return null; } - StringBuilder s = new StringBuilder(a.Length); +#if NET + return Convert.ToHexString(a); +#else + StringBuilder s = new StringBuilder(a.Length * 2); foreach (Byte b in a) { s.Append(b.ToString("X02", CultureInfo.InvariantCulture)); } return s.ToString(); +#endif } - public static string ByteArrayToString(Byte[] a) - { - if (a == null) - { - return null; - } - - StringBuilder s = new StringBuilder(a.Length); - foreach (Byte b in a) - { - s.Append(Convert.ToChar(b)); - } - - return s.ToString(); - } - - public static int CopyStream(Stream input, Stream output) + public static void CopyStream(Stream input, Stream output) { const int bufferSize = 0x4000; - byte[] buffer = new byte[bufferSize]; - int bytesCopied = 0; - int bytesRead; - do - { - bytesRead = input.Read(buffer, 0, bufferSize); - output.Write(buffer, 0, bytesRead); - bytesCopied += bytesRead; - } while (bytesRead > 0); - output.Flush(); + input.CopyTo(output, bufferSize); input.Position = 0; output.Position = 0; - return bytesCopied; } public static string FilterNonprintableChars(string value) @@ -194,9 +172,15 @@ public static Version GetTargetFrameworkVersion(string targetFramework) Version frameworkVersion = null; if (!String.IsNullOrEmpty(targetFramework)) { - if (targetFramework.StartsWith("v", StringComparison.OrdinalIgnoreCase)) + if (targetFramework[0] is 'v' or 'V') { - Version.TryParse(targetFramework.Substring(1), out frameworkVersion); + Version.TryParse( +#if NET + targetFramework.AsSpan(1), +#else + targetFramework.Substring(1), +#endif + out frameworkVersion); } else { @@ -216,8 +200,8 @@ public static string GetEmbeddedResourceString(string name) public static Stream GetEmbeddedResourceStream(string name) { Assembly a = Assembly.GetExecutingAssembly(); - Stream s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, name)); - Debug.Assert(s != null, String.Format(CultureInfo.CurrentCulture, "EmbeddedResource '{0}' not found", name)); + Stream s = a.GetManifestResourceStream($"{typeof(Util).Namespace}.{name}"); + Debug.Assert(s != null, $"EmbeddedResource '{name}' not found"); return s; } @@ -634,8 +618,14 @@ public static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } + return new Version(version); } diff --git a/src/Tasks/ManifestUtil/XmlUtil.cs b/src/Tasks/ManifestUtil/XmlUtil.cs index 32f985124aa..524ee02ff3d 100644 --- a/src/Tasks/ManifestUtil/XmlUtil.cs +++ b/src/Tasks/ManifestUtil/XmlUtil.cs @@ -5,7 +5,6 @@ using System.Collections; using System.Diagnostics; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Reflection; using System.Text; @@ -82,14 +81,14 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t2 = Environment.TickCount; XPathDocument d = new XPathDocument(s); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XPathDocument(1) t={0}", Environment.TickCount - t2)); + Util.WriteLog($"new XPathDocument(1) t={Environment.TickCount - t2}"); int t3 = Environment.TickCount; var xslc = new XslCompiledTransform(); // Using the Trusted Xslt is fine as the style sheet comes from our own assemblies. // This is similar to the prior this.GetType().Assembly/Evidence method that was used in the now depricated XslTransform. xslc.Load(d, XsltSettings.TrustedXslt, s_resolver); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Load t={0}", Environment.TickCount - t3)); + Util.WriteLog($"XslCompiledTransform.Load t={Environment.TickCount - t3}"); // Need to copy input stream because XmlReader will close it, // causing errors for later callers that access the same stream @@ -99,7 +98,7 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t4 = Environment.TickCount; using (XmlReader reader = XmlReader.Create(clonedInput)) { - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "new XmlReader(2) t={0}", Environment.TickCount - t4)); + Util.WriteLog($"new XmlReader(2) t={Environment.TickCount - t4}"); XsltArgumentList args = null; if (entries.Length > 0) @@ -110,7 +109,7 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona string key = entry.Key.ToString(); object val = entry.Value.ToString(); args.AddParam(key, "", val); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "arg: key='{0}' value='{1}'", key, val.ToString())); + Util.WriteLog($"arg: key='{key}' value='{val}'"); } } @@ -122,13 +121,13 @@ public static Stream XslTransform(string resource, Stream input, params Dictiona int t5 = Environment.TickCount; xslc.Transform(reader, args, w, s_resolver); - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform.Transform t={0}", Environment.TickCount - t4)); + Util.WriteLog($"XslCompiledTransform.Transform t={Environment.TickCount - t4}"); w.WriteEndDocument(); w.Flush(); m.Position = 0; - Util.WriteLog(String.Format(CultureInfo.CurrentCulture, "XslCompiledTransform(\"{0}\") t={1}", resource, Environment.TickCount - t1)); + Util.WriteLog($"XslCompiledTransform(\"{resource}\") t={Environment.TickCount - t1}"); return m; } @@ -153,7 +152,7 @@ public override Object GetEntity(Uri uri, string role, Type t) { // First look in assembly resources... Assembly a = Assembly.GetExecutingAssembly(); - s = a.GetManifestResourceStream(String.Format(CultureInfo.InvariantCulture, "{0}.{1}", typeof(Util).Namespace, filename)); + s = a.GetManifestResourceStream($"{typeof(Util).Namespace}.{filename}"); if (s != null) { @@ -191,7 +190,7 @@ public override Object GetEntity(Uri uri, string role, Type t) } // Didn't find the resource... - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "ResourceResolver could not find file '{0}'", filename)); + Debug.Fail($"ResourceResolver could not find file '{filename}'"); return null; } } diff --git a/src/Tasks/ManifestUtil/mansign2.cs b/src/Tasks/ManifestUtil/mansign2.cs index 862bfb6b88c..522a2173045 100644 --- a/src/Tasks/ManifestUtil/mansign2.cs +++ b/src/Tasks/ManifestUtil/mansign2.cs @@ -550,126 +550,68 @@ private static void ReplacePublicKeyToken(XmlDocument manifestDom, AsymmetricAlg } } + [SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "SHA1 is retained for compatibility reasons as an option in VisualStudio signing page and consequently in the trust manager, default is SHA2.")] private static byte[] ComputeHashFromManifest(XmlDocument manifestDom, bool useSha256) { -#if (true) // BUGBUG: Remove before RTM when old format support is no longer needed. - return ComputeHashFromManifest(manifestDom, false, useSha256); - } + // Since the DOM given to us is not guaranteed to be normalized, + // we need to normalize it ourselves. Also, we always preserve + // white space as Fusion XML engine always preserve white space. + XmlDocument normalizedDom = new XmlDocument(); + normalizedDom.PreserveWhitespace = true; + + // Normalize the document + using (TextReader stringReader = new StringReader(manifestDom.OuterXml)) + { + XmlReaderSettings settings = new XmlReaderSettings(); + settings.DtdProcessing = DtdProcessing.Parse; + using (XmlReader reader = XmlReader.Create(stringReader, settings, manifestDom.BaseURI)) + { + normalizedDom.Load(reader); + } + } - [System.Diagnostics.CodeAnalysis.SuppressMessage("Security", "CA5350:Do Not Use Weak Cryptographic Algorithms", Justification = "SHA1 is retained for compatibility reasons as an option in VisualStudio signing page and consequently in the trust manager, default is SHA2.")] - private static byte[] ComputeHashFromManifest(XmlDocument manifestDom, bool oldFormat, bool useSha256) - { - if (oldFormat) - { - XmlDsigExcC14NTransform exc = new XmlDsigExcC14NTransform(); - exc.LoadInput(manifestDom); + XmlDsigExcC14NTransform exc = new XmlDsigExcC14NTransform(); + exc.LoadInput(normalizedDom); - if (useSha256) - { + if (useSha256) + { #pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - using (SHA256 sha2 = SHA256.Create( + using (SHA256 sha2 = SHA256.Create( #if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - "System.Security.Cryptography.SHA256CryptoServiceProvider" + "System.Security.Cryptography.SHA256CryptoServiceProvider" #endif - )) + )) #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter - { - byte[] hash = sha2.ComputeHash(exc.GetOutput() as MemoryStream); - if (hash == null) - { - throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); - } - - return hash; - } - } - else { -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - // codeql[cs/weak-crypto] SHA1 is retained for compatibility reasons as an option in VisualStudio signing page and consequently in the trust manager, default is SHA2. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/139025 - using (SHA1 sha1 = SHA1.Create( -#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - "System.Security.Cryptography.SHA1CryptoServiceProvider" -#endif - )) -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + byte[] hash = sha2.ComputeHash(exc.GetOutput() as MemoryStream); + if (hash == null) { - byte[] hash = sha1.ComputeHash(exc.GetOutput() as MemoryStream); - if (hash == null) - { - throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); - } - - return hash; + throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); } + + return hash; } } else { -#endif - // Since the DOM given to us is not guaranteed to be normalized, - // we need to normalize it ourselves. Also, we always preserve - // white space as Fusion XML engine always preserve white space. - XmlDocument normalizedDom = new XmlDocument(); - normalizedDom.PreserveWhitespace = true; - - // Normalize the document - using (TextReader stringReader = new StringReader(manifestDom.OuterXml)) - { - XmlReaderSettings settings = new XmlReaderSettings(); - settings.DtdProcessing = DtdProcessing.Parse; - using (XmlReader reader = XmlReader.Create(stringReader, settings, manifestDom.BaseURI)) - { - normalizedDom.Load(reader); - } - } - - XmlDsigExcC14NTransform exc = new XmlDsigExcC14NTransform(); - exc.LoadInput(normalizedDom); - - if (useSha256) - { #pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - using (SHA256 sha2 = SHA256.Create( + // codeql[cs/weak-crypto] SHA1 is retained for compatibility reasons as an option in VisualStudio signing page and consequently in the trust manager, default is SHA2. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/139025 + using (SHA1 sha1 = SHA1.Create( #if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - "System.Security.Cryptography.SHA256CryptoServiceProvider" + "System.Security.Cryptography.SHA1CryptoServiceProvider" #endif - )) + )) #pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter - { - byte[] hash = sha2.ComputeHash(exc.GetOutput() as MemoryStream); - if (hash == null) - { - throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); - } - - return hash; - } - } - else { -#pragma warning disable SA1111, SA1009 // Closing parenthesis should be on line of last parameter - // codeql[cs/weak-crypto] SHA1 is retained for compatibility reasons as an option in VisualStudio signing page and consequently in the trust manager, default is SHA2. https://devdiv.visualstudio.com/DevDiv/_workitems/edit/139025 - using (SHA1 sha1 = SHA1.Create( -#if FEATURE_CRYPTOGRAPHIC_FACTORY_ALGORITHM_NAMES - "System.Security.Cryptography.SHA1CryptoServiceProvider" -#endif - )) -#pragma warning restore SA1111, SA1009 // Closing parenthesis should be on line of last parameter + byte[] hash = sha1.ComputeHash(exc.GetOutput() as MemoryStream); + if (hash == null) { - byte[] hash = sha1.ComputeHash(exc.GetOutput() as MemoryStream); - if (hash == null) - { - throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); - } - - return hash; + throw new CryptographicException(Win32.TRUST_E_BAD_DIGEST); } - } -#if (true) // BUGBUG: Remove before RTM when old format support is no longer needed. + return hash; + } } -#endif } private const string AssemblyNamespaceUri = "urn:schemas-microsoft-com:asm.v1"; @@ -739,8 +681,8 @@ private static void AuthenticodeSignLicenseDom(XmlDocument licenseDom, CmiManife signedXml.SignedInfo.CanonicalizationMethod = SignedXml.XmlDsigExcC14NTransformUrl; if (signer.UseSha256) { - signedXml.SignedInfo.SignatureMethod = Sha256SignatureMethodUri; - } + signedXml.SignedInfo.SignatureMethod = Sha256SignatureMethodUri; + } else { signedXml.SignedInfo.SignatureMethod = Sha1SignatureMethodUri; @@ -817,12 +759,17 @@ private static string ObtainRFC3161Timestamp(string timeStampUrl, string signatu try { +#if NET + Span nonce = stackalloc byte[32]; + RandomNumberGenerator.Fill(nonce); +#else byte[] nonce = new byte[32]; using (RandomNumberGenerator rng = RandomNumberGenerator.Create()) { rng.GetBytes(nonce); } +#endif // Eventually, CryptEncodeObjectEx(...) is called on a CRYPT_TIMESTAMP_REQUEST with this nonce, // and CryptEncodeObjectEx(...) interprets the nonce as a little endian, DER-encoded integer value @@ -1044,13 +991,19 @@ private static void StrongNameSignManifestDom(XmlDocument manifestDom, XmlDocume // Insert the signature now. signatureParent.AppendChild(xmlDigitalSignature); } + +#if !NET private static readonly char[] s_hexValues = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; +#endif private static string BytesToHexString(byte[] array, int start, int end) { string result = null; if (array != null) { +#if NET + return Convert.ToHexStringLower(array.AsSpan(start, end - start)); +#else char[] hexOrder = new char[(end - start) * 2]; int i = end; int digit, j = 0; @@ -1062,6 +1015,7 @@ private static string BytesToHexString(byte[] array, int start, int end) hexOrder[j++] = s_hexValues[digit]; } result = new String(hexOrder); +#endif } return result; } @@ -1096,12 +1050,12 @@ internal class CmiManifestSigner2 private X509Certificate2Collection _certificates; private X509IncludeOption _includeOption; private CmiManifestSignerFlag _signerFlag; - private bool _useSha256; + private readonly bool _useSha256; private CmiManifestSigner2() { } internal CmiManifestSigner2(AsymmetricAlgorithm strongNameKey) : - this(strongNameKey, null, false) + this(strongNameKey, certificate: null, useSha256: false) { } internal CmiManifestSigner2(AsymmetricAlgorithm strongNameKey, X509Certificate2 certificate, bool useSha256) @@ -1299,7 +1253,7 @@ internal CmiAuthenticodeSignerInfo(int errorCode) } internal CmiAuthenticodeSignerInfo(Win32.AXL_SIGNER_INFO signerInfo, - Win32.AXL_TIMESTAMPER_INFO timestamperInfo) + Win32.AXL_TIMESTAMPER_INFO timestamperInfo) { _error = (int)signerInfo.dwError; if (signerInfo.pChainContext != IntPtr.Zero) diff --git a/src/Tasks/Microsoft.Build.Tasks.csproj b/src/Tasks/Microsoft.Build.Tasks.csproj index 37d7cece260..a518f22fe8b 100644 --- a/src/Tasks/Microsoft.Build.Tasks.csproj +++ b/src/Tasks/Microsoft.Build.Tasks.csproj @@ -447,24 +447,6 @@ PreserveNewest - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - - - PreserveNewest - $(AssemblyName).Strings.resources @@ -663,7 +645,6 @@ - @@ -682,6 +663,7 @@ + diff --git a/src/Tasks/Microsoft.Common.props b/src/Tasks/Microsoft.Common.props index e322c4a2f1e..be186f628bd 100644 --- a/src/Tasks/Microsoft.Common.props +++ b/src/Tasks/Microsoft.Common.props @@ -108,7 +108,7 @@ Copyright (C) Microsoft Corporation. All rights reserved. JavaScript - + diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v11.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v11.Common.props deleted file mode 100644 index 9ef50e92c14..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v11.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 11.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v12.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v12.Common.props deleted file mode 100644 index 428a5c1d571..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v12.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 12.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v14.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v14.Common.props deleted file mode 100644 index c2e2af3ad8e..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v14.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 14.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v15.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v15.Common.props deleted file mode 100644 index 9d9e0f62d71..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v15.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 15.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v16.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v16.Common.props deleted file mode 100644 index 1843227e760..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v16.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 16.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/Microsoft.VisualStudioVersion.v17.Common.props b/src/Tasks/Microsoft.VisualStudioVersion.v17.Common.props deleted file mode 100644 index 94031c33888..00000000000 --- a/src/Tasks/Microsoft.VisualStudioVersion.v17.Common.props +++ /dev/null @@ -1,19 +0,0 @@ - - - - - 17.0 - $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) - - - \ No newline at end of file diff --git a/src/Tasks/NativeMethods.cs b/src/Tasks/NativeMethods.cs index 111880e1d30..5f321996db9 100644 --- a/src/Tasks/NativeMethods.cs +++ b/src/Tasks/NativeMethods.cs @@ -12,7 +12,9 @@ using System.Collections.Generic; using System.Collections; using System.Globalization; +#if !NET using System.Linq; +#endif #if FEATURE_HANDLEPROCESSCORRUPTEDSTATEEXCEPTIONS using System.Runtime.ExceptionServices; #endif @@ -520,7 +522,7 @@ internal struct PROCESS_INFORMATION /// /// Interop methods. /// - internal static class NativeMethods + internal static partial class NativeMethods { #region Constants @@ -1160,7 +1162,7 @@ internal static unsafe bool TryReadMetadataString(string fullPath, IntPtr attrDa } // And convert it to the output string. - strValue = new String(Encoding.UTF8.GetChars(bytes)); + strValue = Encoding.UTF8.GetString(bytes); } else { @@ -1235,19 +1237,24 @@ internal static unsafe int CorSigUncompressData(IntPtr data, out int uncompresse /// This class is a wrapper over the native GAC enumeration API. /// [ComVisible(false)] - internal class AssemblyCacheEnum : IEnumerable + internal partial class AssemblyCacheEnum : IEnumerable { /// /// Path to the gac /// private static readonly string s_gacPath = Path.Combine(NativeMethodsShared.FrameworkBasePath, "gac"); + private const string AssemblyVersionPattern = @"^([.\d]+)_([^_]*)_([a-fA-F\d]{16})$"; + /// /// Regex for directory version parsing /// - private static readonly Regex s_assemblyVersionRegex = new Regex( - @"^([.\d]+)_([^_]*)_([a-fA-F\d]{16})$", - RegexOptions.CultureInvariant | RegexOptions.Compiled); +#if NET + [GeneratedRegex(AssemblyVersionPattern, RegexOptions.CultureInvariant)] + private static partial Regex AssemblyVersionRegex { get; } +#else + private static Regex AssemblyVersionRegex { get; } = new Regex(AssemblyVersionPattern, RegexOptions.CultureInvariant | RegexOptions.Compiled); +#endif /// /// The IAssemblyEnum interface which allows us to ask for the next assembly from the GAC enumeration. @@ -1387,7 +1394,7 @@ public IEnumerator GetEnumerator() var versionString = Path.GetFileName(version); if (!string.IsNullOrWhiteSpace(versionString)) { - var match = s_assemblyVersionRegex.Match(versionString); + var match = AssemblyVersionRegex.Match(versionString); if (match.Success) { var name = new AssemblyName @@ -1407,10 +1414,16 @@ public IEnumerator GetEnumerator() if (!string.IsNullOrWhiteSpace(match.Groups[3].Value)) { var value = match.Groups[3].Value; - name.SetPublicKeyToken( + byte[] key = +#if NET + Convert.FromHexString(value.AsSpan(0, 16)); +#else Enumerable.Range(0, 16) - .Where(x => x % 2 == 0) - .Select(x => Convert.ToByte(value.Substring(x, 2), 16)).ToArray()); + .Where(x => x % 2 == 0) + .Select(x => Convert.ToByte(value.Substring(x, 2), 16)) + .ToArray(); +#endif + name.SetPublicKeyToken(key); } yield return new AssemblyNameExtension(name); @@ -1455,8 +1468,12 @@ public static string AssemblyPathFromStrongName(string strongName) "{0}_{1}_{2}", assemblyNameVersion.Version.ToString(4), assemblyNameVersion.CultureName != "neutral" ? assemblyNameVersion.CultureName : string.Empty, +#if NET + Convert.ToHexStringLower(assemblyNameVersion.GetPublicKeyToken())), +#else assemblyNameVersion.GetPublicKeyToken() .Aggregate(new StringBuilder(), (builder, v) => builder.Append(v.ToString("x2")))), +#endif assemblyNameVersion.Name + ".dll"); if (FileSystems.Default.FileExists(path)) diff --git a/src/Tasks/RedistList.cs b/src/Tasks/RedistList.cs index f5476e2f9fc..7d8e6acd7ce 100644 --- a/src/Tasks/RedistList.cs +++ b/src/Tasks/RedistList.cs @@ -372,7 +372,7 @@ private static string GetSimpleName(string assemblyName) throw new ArgumentNullException(nameof(assemblyName)); } - int i = assemblyName.IndexOf(",", StringComparison.Ordinal); + int i = assemblyName.IndexOf(','); return i > 0 ? assemblyName.Substring(0, i) : assemblyName; } @@ -794,7 +794,7 @@ private static void ParseFileListSection(AssemblyTableInfo assemblyTableInfo, st { // When comparing the assembly entries we want to compare the FullName which is a formatted as name, version, publicKeyToken and culture and whether the entry is a redistroot flag // We do not need to add the redistName and the framework directory because this will be the same for all entries in the current redist list being read. - string hashIndex = String.Format(CultureInfo.InvariantCulture, "{0},{1}", newEntry.FullName, newEntry.IsRedistRoot == null ? "null" : newEntry.IsRedistRoot.ToString()); + string hashIndex = $"{newEntry.FullName},{(newEntry.IsRedistRoot == null ? "null" : newEntry.IsRedistRoot.ToString())}"; assemblyEntries.TryGetValue(hashIndex, out AssemblyEntry dictionaryEntry); // If the entry is not in the dictionary or the entry is in the dictionary but the new entry has the ingac flag true, make sure the dictionary contains the entry with the ingac true. diff --git a/src/Tasks/RequiresFramework35SP1Assembly.cs b/src/Tasks/RequiresFramework35SP1Assembly.cs index 4714f818e73..741dcbe59fc 100644 --- a/src/Tasks/RequiresFramework35SP1Assembly.cs +++ b/src/Tasks/RequiresFramework35SP1Assembly.cs @@ -79,7 +79,12 @@ private static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } return new Version(version); } diff --git a/src/Tasks/ResolveComReference.cs b/src/Tasks/ResolveComReference.cs index c8ba6d686f4..c339ea9c5be 100644 --- a/src/Tasks/ResolveComReference.cs +++ b/src/Tasks/ResolveComReference.cs @@ -5,7 +5,7 @@ #if !RUNTIME_TYPE_NETCORE using System.Collections.Generic; #endif -#if !NET7_0_OR_GREATER +#if !NET using System.Diagnostics; using System.Globalization; using System.IO; diff --git a/src/Tasks/ResolveKeySource.cs b/src/Tasks/ResolveKeySource.cs index 6a7ae609617..366f0badba0 100644 --- a/src/Tasks/ResolveKeySource.cs +++ b/src/Tasks/ResolveKeySource.cs @@ -62,6 +62,7 @@ public override bool Execute() return ResolveAssemblyKey() && ResolveManifestKey(); } +#if FEATURE_PFX_SIGNING // We we use hash the contens of .pfx file so we can establish relationship file <-> container name, whithout // need to prompt for password. Note this is not used for any security reasons. With the departure from standard MD5 algoritm // we need as simple hash function for replacement. The data blobs we use (.pfx files) are @@ -87,6 +88,7 @@ private static UInt64 HashFromBlob(byte[] data) result |= dw2; return result; } +#endif private bool ResolveAssemblyKey() { diff --git a/src/Tasks/ResolveManifestFiles.cs b/src/Tasks/ResolveManifestFiles.cs index 9a78f010f16..d8a61ab1579 100644 --- a/src/Tasks/ResolveManifestFiles.cs +++ b/src/Tasks/ResolveManifestFiles.cs @@ -215,7 +215,12 @@ private static Version ConvertFrameworkVersionToString(string version) { if (version.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - return new Version(version.Substring(1)); + return Version.Parse( +#if NET + version.AsSpan(1)); +#else + version.Substring(1)); +#endif } return new Version(version); } @@ -346,7 +351,7 @@ private static CultureInfo GetItemCulture(ITaskItem item) // Infer culture from path (i.e. "obj\debug\fr\WindowsApplication1.resources.dll" -> "fr") string[] pathSegments = PathUtil.GetPathSegments(item.ItemSpec); itemCulture = pathSegments.Length > 1 ? pathSegments[pathSegments.Length - 2] : null; - Debug.Assert(!String.IsNullOrEmpty(itemCulture), String.Format(CultureInfo.CurrentCulture, "Satellite item '{0}' is missing expected attribute '{1}'", item.ItemSpec, "Culture")); + Debug.Assert(!String.IsNullOrEmpty(itemCulture), $"Satellite item '{item.ItemSpec}' is missing expected attribute 'Culture'"); item.SetMetadata("Culture", itemCulture); } return new CultureInfo(itemCulture); @@ -862,7 +867,7 @@ public void Add(ITaskItem item) // Add to map with full name, for SpecificVersion=true case string key = fusionName.ToLowerInvariant(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same key '{0}' detected", key)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same key '{key}' detected"); if (!_dictionary.ContainsKey(key)) { _dictionary.Add(key, entry); @@ -921,7 +926,7 @@ public void Add(ITaskItem item) { // Use satellite assembly strong name signature as key string key = identity.ToString(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same key '{0}' detected", key)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same key '{key}' detected"); if (!_dictionary.ContainsKey(key)) { _dictionary.Add(key, entry); @@ -962,7 +967,7 @@ public void Add(ITaskItem item, bool includedByDefault) } string key = targetPath.ToLowerInvariant(); - Debug.Assert(!_dictionary.ContainsKey(key), String.Format(CultureInfo.CurrentCulture, "Two or more items with same '{0}' attribute detected", ItemMetadataNames.targetPath)); + Debug.Assert(!_dictionary.ContainsKey(key), $"Two or more items with same '{(object)ItemMetadataNames.targetPath}' attribute detected"); var entry = new MapEntry(item, includedByDefault); if (!_dictionary.ContainsKey(key)) { @@ -997,11 +1002,11 @@ private static PublishState StringToPublishState(string value) } catch (FormatException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); + Debug.Fail($"Invalid value '{value}' for PublishState"); } catch (ArgumentException) { - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Invalid value '{0}' for {1}", value, "PublishState")); + Debug.Fail($"Invalid value '{value}' for PublishState"); } } return PublishState.Auto; @@ -1036,14 +1041,14 @@ public static PublishFlags GetAssemblyFlags(PublishState state, bool copyLocal) isPublished = false; break; case PublishState.DataFile: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); + Debug.Fail("PublishState.DataFile is invalid for an assembly"); break; case PublishState.Prerequisite: isPrerequisite = true; isPublished = false; break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); @@ -1073,10 +1078,10 @@ public static PublishFlags GetFileFlags(PublishState state, string fileExtension isPublished = true; break; case PublishState.Prerequisite: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.Prerequisite is invalid for a file")); + Debug.Fail("PublishState.Prerequisite is invalid for a file"); break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); @@ -1103,14 +1108,14 @@ public static PublishFlags GetSatelliteFlags(PublishState state, CultureInfo sat isPublished = false; break; case PublishState.DataFile: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "PublishState.DataFile is invalid for an assembly")); + Debug.Fail("PublishState.DataFile is invalid for an assembly"); break; case PublishState.Prerequisite: isPrerequisite = true; isPublished = false; break; default: - Debug.Fail(String.Format(CultureInfo.CurrentCulture, "Unhandled value PublishFlags.{0}", state.ToString())); + Debug.Fail($"Unhandled value PublishFlags.{state}"); break; } return new PublishFlags(isDataFile, isPrerequisite, isPublished); diff --git a/src/Tasks/ResolveSDKReference.cs b/src/Tasks/ResolveSDKReference.cs index 4cf06aa29e8..9d2c288a93e 100644 --- a/src/Tasks/ResolveSDKReference.cs +++ b/src/Tasks/ResolveSDKReference.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using System.IO; using System.Linq; using System.Text.RegularExpressions; @@ -20,7 +19,7 @@ namespace Microsoft.Build.Tasks /// Resolves an SDKReference to a full path on disk /// #pragma warning disable RS0022 // Constructor make noninheritable base class inheritable: Longstanding API design that we shouldn't change now - public class ResolveSDKReference : TaskExtension + public partial class ResolveSDKReference : TaskExtension #pragma warning restore RS0022 // Constructor make noninheritable base class inheritable { #region fields @@ -33,13 +32,18 @@ public class ResolveSDKReference : TaskExtension { "UAP", "Windows" } }; + private const string SdkReferenceFormatPattern = @"(?^[^,]*),\s*Version=(?.*)"; + /// /// Regex for breaking up the sdk reference include into pieces. /// Example: XNA, Version=8.0 /// - private static readonly Regex s_sdkReferenceFormat = new Regex( - @"(?^[^,]*),\s*Version=(?.*)", - RegexOptions.IgnoreCase); +#if NET + [GeneratedRegex(SdkReferenceFormatPattern, RegexOptions.IgnoreCase)] + private static partial Regex SdkReferenceFormatRegex { get; } +#else + private static Regex SdkReferenceFormatRegex { get; } = new Regex(SdkReferenceFormatPattern, RegexOptions.IgnoreCase); +#endif /// /// SimpleName group @@ -409,7 +413,7 @@ public override bool Execute() { if (!sdksAlreadyErrorOrWarnedFor.Contains(incompatibleReference) && incompatibleReference != notCompatibleReference /*cannot be incompatible with self*/) { - listOfIncompatibleReferences.Add(String.Format(CultureInfo.CurrentCulture, "\"{0}\"", incompatibleReference.SDKName)); + listOfIncompatibleReferences.Add($"\"{incompatibleReference.SDKName}\""); sdksAlreadyErrorOrWarnedFor.Add(incompatibleReference); } } @@ -438,7 +442,7 @@ public override bool Execute() { if (!sdksAlreadyErrorOrWarnedFor.Contains(incompatibleReference) && incompatibleReference != notCompatibleReference /*cannot be incompatible with self*/) { - listOfIncompatibleReferences.Add(String.Format(CultureInfo.CurrentCulture, "\"{0}\"", incompatibleReference.SDKName)); + listOfIncompatibleReferences.Add($"\"{incompatibleReference.SDKName}\""); sdksAlreadyErrorOrWarnedFor.Add(incompatibleReference); } } @@ -481,7 +485,7 @@ internal static void AddMetadataToReferences(TaskLoggingHelper log, HashSet sdkRef // Return true if no reference could be found return resolvedReference == null; }) - .Select(y => String.Format(CultureInfo.CurrentCulture, "\"{0}\"", y)) + .Select(y => $"\"{y}\"") .ToArray(); return unresolvedDependencyIdentities; @@ -574,7 +578,7 @@ internal SDKReference ParseSDKReference(ITaskItem referenceItem) /// private static bool ParseSDKReference(string reference, out string sdkSimpleName, out string rawSdkVersion) { - Match match = s_sdkReferenceFormat.Match(reference); + Match match = SdkReferenceFormatRegex.Match(reference); sdkSimpleName = String.Empty; bool parsedVersion = false; @@ -733,7 +737,7 @@ public SDKReference(ITaskItem taskItem, string sdkName, string sdkVersion) ReferenceItem = taskItem; SimpleName = sdkName; Version = sdkVersion; - SDKName = String.Format(CultureInfo.InvariantCulture, "{0}, Version={1}", SimpleName, Version); + SDKName = $"{SimpleName}, Version={Version}"; FrameworkIdentitiesFromManifest = new Dictionary(StringComparer.OrdinalIgnoreCase); AppxLocationsFromManifest = new Dictionary(StringComparer.OrdinalIgnoreCase); ResolutionErrors = new List>(); @@ -1289,13 +1293,13 @@ private void CreateResolvedReferenceItem(string targetConfiguration, string targ { // Try and find a framework identity that matches on both the configuration and architecture "FrameworkIdentity--" FrameworkIdentity = null; - string frameworkIdentityKey = String.Format(CultureInfo.InvariantCulture, "{0}-{1}-{2}", SDKManifest.Attributes.FrameworkIdentity, sdkConfiguration, sdkArchitecture); + string frameworkIdentityKey = $"{SDKManifest.Attributes.FrameworkIdentity}-{sdkConfiguration}-{sdkArchitecture}"; FrameworkIdentity = FindFrameworkIdentity(frameworkIdentityKey); // Try and find a framework identity that matches on the configuration , Element must be named "FrameworkIdentity-" only. if (FrameworkIdentity == null) { - frameworkIdentityKey = String.Format(CultureInfo.InvariantCulture, "{0}-{1}", SDKManifest.Attributes.FrameworkIdentity, sdkConfiguration); + frameworkIdentityKey = $"{SDKManifest.Attributes.FrameworkIdentity}-{sdkConfiguration}"; FrameworkIdentity = FindFrameworkIdentity(frameworkIdentityKey); } diff --git a/src/Tasks/ResourceHandling/MSBuildResXReader.cs b/src/Tasks/ResourceHandling/MSBuildResXReader.cs index ea1ffae0211..1f6b95b7067 100644 --- a/src/Tasks/ResourceHandling/MSBuildResXReader.cs +++ b/src/Tasks/ResourceHandling/MSBuildResXReader.cs @@ -89,7 +89,11 @@ private static string GetFullTypeNameFromAlias(string aliasedTypeName, Dictionar int indexStart = aliasedTypeName.IndexOf(','); if (aliases.TryGetValue(aliasedTypeName.Substring(indexStart + 2), out string fullAssemblyIdentity)) { +#if NET + return string.Concat(aliasedTypeName.AsSpan(0, indexStart + 2), fullAssemblyIdentity); +#else return aliasedTypeName.Substring(0, indexStart + 2) + fullAssemblyIdentity; +#endif } // Allow "System.String" bare @@ -290,7 +294,7 @@ private static void AddLinkedResource(string resxFilename, bool pathsRelativeToB /// private static bool IsByteArray(string fileRefType) { - return fileRefType.IndexOf("System.Byte[]") != -1 && fileRefType.IndexOf("mscorlib") != -1; + return fileRefType.Contains("System.Byte[]") && fileRefType.Contains("mscorlib"); } internal static bool IsString(string fileRefType) @@ -333,7 +337,7 @@ internal static string[] ParseResxFileRefString(string stringValue) string remainingString; if (stringValue.StartsWith("\"")) { - int lastIndexOfQuote = stringValue.LastIndexOf("\""); + int lastIndexOfQuote = stringValue.LastIndexOf('"'); if (lastIndexOfQuote - 1 < 0) { throw new ArgumentException(nameof(stringValue)); @@ -349,7 +353,7 @@ internal static string[] ParseResxFileRefString(string stringValue) } else { - int nextSemiColumn = stringValue.IndexOf(";"); + int nextSemiColumn = stringValue.IndexOf(';'); if (nextSemiColumn == -1) { throw new ArgumentException(nameof(stringValue)); diff --git a/src/Tasks/Resources/xlf/Strings.it.xlf b/src/Tasks/Resources/xlf/Strings.it.xlf index 82941a83d6b..5614177333f 100644 --- a/src/Tasks/Resources/xlf/Strings.it.xlf +++ b/src/Tasks/Resources/xlf/Strings.it.xlf @@ -2839,7 +2839,7 @@ Could not infer the type of parameter "{0}" because the attribute type is unknown. The value will be treated as a string. - Non è stato possibile inferire il tipo di parametro "{0}" perché il tipo di attributo è sconosciuto. Il valore sarà trattto come una stringa. + Non è stato possibile inferire il tipo di parametro "{0}" perché il tipo di attributo è sconosciuto. Il valore sarà tratto come una stringa. diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs index 535156bc1fd..d5d9ebda785 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactory.cs @@ -536,10 +536,10 @@ internal bool TryResolveAssemblyReferences(TaskLoggingHelper log, RoslynCodeTask // Start with the user specified references and include all of the default references that are language agnostic IEnumerable references = taskInfo.References.Union(DefaultReferences[String.Empty]); - if (DefaultReferences.ContainsKey(taskInfo.CodeLanguage)) + if (DefaultReferences.TryGetValue(taskInfo.CodeLanguage, out IEnumerable value)) { // Append default references for the specific language - references = references.Union(DefaultReferences[taskInfo.CodeLanguage]); + references = references.Union(value); } List directoriesToAddToAppDomain = new(); diff --git a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs index 50a26f7f5f2..21021e60adf 100644 --- a/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs +++ b/src/Tasks/RoslynCodeTaskFactory/RoslynCodeTaskFactoryCompilers.cs @@ -4,10 +4,12 @@ using System; using System.IO; using System.Linq; -using System.Runtime.InteropServices; using Microsoft.Build.Framework; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; +#if RUNTIME_TYPE_NETCORE +using System.Runtime.InteropServices; +using Microsoft.Build.Shared; +#endif #nullable disable diff --git a/src/Tasks/StrongNameUtils.cs b/src/Tasks/StrongNameUtils.cs index 26fce852479..f1aef67e8c0 100644 --- a/src/Tasks/StrongNameUtils.cs +++ b/src/Tasks/StrongNameUtils.cs @@ -49,10 +49,8 @@ internal static void ReadKeyFile(TaskLoggingHelper log, string keyFile, out Stro int fileLength = (int)fs.Length; keyFileContents = new byte[fileLength]; -#pragma warning disable CA2022 // Avoid inexact read with 'Stream.Read' // TODO: Read the count of read bytes and check if it matches the expected length, if not raise an exception - fs.Read(keyFileContents, 0, fileLength); -#pragma warning restore CA2022 // Avoid inexact read with 'Stream.Read' + fs.ReadExactly(keyFileContents, 0, fileLength); } } catch (ArgumentException e) diff --git a/src/Tasks/SystemState.cs b/src/Tasks/SystemState.cs index f85dc93eb7e..0d1dac8f86c 100644 --- a/src/Tasks/SystemState.cs +++ b/src/Tasks/SystemState.cs @@ -70,7 +70,7 @@ internal sealed class SystemState : StateFileBase, ITranslatable /// /// Additional level of caching kept at the process level. /// - private static ConcurrentDictionary s_processWideFileStateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + private static readonly ConcurrentDictionary s_processWideFileStateCache = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); /// /// XML tables of installed assemblies. @@ -171,10 +171,8 @@ public void Translate(ITranslator translator) ErrorUtilities.VerifyThrowArgumentNull(translator); translator.Translate(ref lastModified); - translator.Translate(ref assemblyName, - (ITranslator t) => new AssemblyNameExtension(t)); - translator.TranslateArray(ref dependencies, - (ITranslator t) => new AssemblyNameExtension(t)); + translator.Translate(ref assemblyName, (t) => new AssemblyNameExtension(t)); + translator.TranslateArray(ref dependencies, (t) => new AssemblyNameExtension(t)); translator.Translate(ref scatterFiles); translator.Translate(ref runtimeVersion); translator.Translate(ref frameworkName); @@ -268,7 +266,7 @@ public override void Translate(ITranslator translator) translator.TranslateDictionary( ref (translator.Mode == TranslationDirection.WriteToStream) ? ref instanceLocalOutgoingFileStateCache : ref instanceLocalFileStateCache, StringComparer.OrdinalIgnoreCase, - (ITranslator t) => new FileState(t)); + (t) => new FileState(t)); // IsDirty should be false for either direction. Either this cache was brought // up-to-date with the on-disk cache or vice versa. Either way, they agree. diff --git a/src/Tasks/TaskRequiresFramework.cs b/src/Tasks/TaskRequiresFramework.cs index e681eb903b5..f29331729e6 100644 --- a/src/Tasks/TaskRequiresFramework.cs +++ b/src/Tasks/TaskRequiresFramework.cs @@ -1,8 +1,11 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. +#pragma warning disable IDE0052 +#if NETFRAMEWORK using System; +#endif namespace Microsoft.Build.Tasks { diff --git a/src/Tasks/Unzip.cs b/src/Tasks/Unzip.cs index 690308135f0..01026579232 100644 --- a/src/Tasks/Unzip.cs +++ b/src/Tasks/Unzip.cs @@ -327,7 +327,7 @@ private void ParsePattern(string pattern, out string[] patterns) // Supporting property references would require access to Expander which is unavailable in Microsoft.Build.Tasks Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternPropertyReferences", pattern); } - else if (pattern.IndexOfAny(FileUtilities.InvalidPathChars) != -1) + else if (pattern.AsSpan().IndexOfAny(FileUtilities.InvalidPathChars) >= 0) { Log.LogErrorWithCodeFromResources("Unzip.ErrorParsingPatternInvalidPath", pattern); } diff --git a/src/Tasks/Warning.cs b/src/Tasks/Warning.cs index 2f46b591c2a..b1db0edd418 100644 --- a/src/Tasks/Warning.cs +++ b/src/Tasks/Warning.cs @@ -3,7 +3,9 @@ #nullable disable +#if NET using System.Diagnostics.CodeAnalysis; +#endif namespace Microsoft.Build.Tasks { diff --git a/src/Tasks/WriteCodeFragment.cs b/src/Tasks/WriteCodeFragment.cs index ecfe9bad0d6..9e08cea56bc 100644 --- a/src/Tasks/WriteCodeFragment.cs +++ b/src/Tasks/WriteCodeFragment.cs @@ -210,7 +210,13 @@ private string GenerateCode(out string extension) if (name.StartsWith("_Parameter", StringComparison.OrdinalIgnoreCase)) { - if (!Int32.TryParse(name.Substring("_Parameter".Length), out int index)) + if (!Int32.TryParse( +#if NET + name.AsSpan("_Parameter".Length), +#else + name.Substring("_Parameter".Length), +#endif + out int index)) { Log.LogErrorWithCodeFromResources("General.InvalidValue", name, "WriteCodeFragment"); return null; diff --git a/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs b/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs index 1ef6c1ec726..f2fbe04a6b2 100644 --- a/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs +++ b/src/Tasks/XamlTaskFactory/CommandLineGenerator.cs @@ -413,22 +413,21 @@ private static void EmitStringArraySwitch(CommandLineBuilder clb, CommandLineToo /// private static bool PerformSwitchValueSubstition(CommandLineBuilder clb, CommandLineToolSwitch commandLineToolSwitch, string switchValue) { - Regex regex = new Regex(@"\[value]", RegexOptions.IgnoreCase); - Match match = regex.Match(commandLineToolSwitch.SwitchValue); - if (match.Success) + const string Value = "[value]"; + int valuePos = commandLineToolSwitch.SwitchValue.IndexOf(Value, StringComparison.OrdinalIgnoreCase); + if (valuePos >= 0) { - string prefixToAppend = commandLineToolSwitch.SwitchValue.Substring(match.Index + match.Length, commandLineToolSwitch.SwitchValue.Length - (match.Index + match.Length)); - string valueToAppend; - if (!switchValue.EndsWith("\\\\", StringComparison.OrdinalIgnoreCase) && switchValue.EndsWith("\\", StringComparison.OrdinalIgnoreCase) && prefixToAppend.Length > 0 && prefixToAppend[0] == '\"') - { - // If the combined string would create \" then we need to escape it - // if the combined string would create \\" then we ignore it as as assume it is already escaped. - valueToAppend = commandLineToolSwitch.SwitchValue.Substring(0, match.Index) + switchValue + "\\" + prefixToAppend; - } - else - { - valueToAppend = commandLineToolSwitch.SwitchValue.Substring(0, match.Index) + switchValue + prefixToAppend; - } + string prefixToAppend = commandLineToolSwitch.SwitchValue.Substring(valuePos + Value.Length); + + // If the combined string would create \" then we need to escape it + // if the combined string would create \\" then we ignore it as as assume it is already escaped. + bool needsEscaping = + !switchValue.EndsWith("\\\\", StringComparison.OrdinalIgnoreCase) && + switchValue.EndsWith("\\", StringComparison.OrdinalIgnoreCase) && + prefixToAppend.Length > 0 && + prefixToAppend[0] == '\"'; + + string valueToAppend = $"{commandLineToolSwitch.SwitchValue.Substring(0, valuePos)}{switchValue}{(needsEscaping ? "\\" : "")}{prefixToAppend}"; clb.AppendSwitch(valueToAppend); return true; @@ -645,8 +644,7 @@ private void GenerateTemplatedCommandLine(CommandLineBuilder builder) // Match all instances of [asdf], where "asdf" can be any combination of any // characters *except* a [ or an ]. i.e., if "[ [ sdf ]" is passed, then we will // match "[ sdf ]" - string matchString = @"\[[^\[\]]+\]"; - Regex regex = new Regex(matchString, RegexOptions.ECMAScript); + Regex regex = new Regex(@"\[[^\[\]]+\]", RegexOptions.ECMAScript); MatchCollection matches = regex.Matches(CommandLineTemplate); int indexOfEndOfLastSubstitution = 0; @@ -735,7 +733,7 @@ private void GenerateTemplatedCommandLine(CommandLineBuilder builder) indexOfEndOfLastSubstitution = match.Index + match.Length; } - builder.AppendTextUnquoted(CommandLineTemplate.Substring(indexOfEndOfLastSubstitution, CommandLineTemplate.Length - indexOfEndOfLastSubstitution)); + builder.AppendTextUnquoted(CommandLineTemplate.Substring(indexOfEndOfLastSubstitution)); } } } diff --git a/src/Tasks/XamlTaskFactory/RelationsParser.cs b/src/Tasks/XamlTaskFactory/RelationsParser.cs index 6a119d563f2..415c3174a9f 100644 --- a/src/Tasks/XamlTaskFactory/RelationsParser.cs +++ b/src/Tasks/XamlTaskFactory/RelationsParser.cs @@ -6,8 +6,6 @@ using System.IO; using System.Xml; using Microsoft.Build.Shared; -using Microsoft.Build.Tasks.Deployment.ManifestUtilities; -using Microsoft.IO; using File = System.IO.File; #nullable disable diff --git a/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs b/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs index e74251b8bba..18e0ce17d1d 100644 --- a/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs +++ b/src/Tasks/XamlTaskFactory/XamlTaskFactory.cs @@ -71,7 +71,7 @@ public Type TaskType { if (_taskType == null) { - _taskType = _taskAssembly.GetType(String.Concat(XamlTaskNamespace, ".", TaskName), true); + _taskType = _taskAssembly.GetType($"{XamlTaskNamespace}.{TaskName}", true); } return _taskType; @@ -195,7 +195,7 @@ public bool Initialize(string taskName, IDictionary ta /// The task factory logging host will log messages in the context of the task. public ITask CreateTask(IBuildEngine taskFactoryLoggingHost) { - string fullTaskName = String.Concat(TaskNamespace, ".", TaskName); + string fullTaskName = $"{TaskNamespace}.{TaskName}"; return (ITask)_taskAssembly.CreateInstance(fullTaskName); } diff --git a/src/Tasks/XslTransformation.cs b/src/Tasks/XslTransformation.cs index 90b49a9780b..6130771ccd4 100644 --- a/src/Tasks/XslTransformation.cs +++ b/src/Tasks/XslTransformation.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.Collections.Generic; using System.IO; -using System.Reflection; using System.Xml; using System.Xml.XPath; using System.Xml.Xsl; @@ -12,6 +10,11 @@ using Microsoft.Build.Shared; using Microsoft.Build.Utilities; +#if FEATURE_COMPILED_XSL +using System.Collections.Generic; +using System.Reflection; +#endif + #nullable disable namespace Microsoft.Build.Tasks @@ -495,6 +498,7 @@ public XslCompiledTransform LoadXslt(bool useTrustedSettings) return xslct; } +#if FEATURE_COMPILED_XSL /// /// Find the type from an assembly and loads it. /// @@ -528,6 +532,7 @@ private static Type FindType(string assemblyPath, string typeName) throw new ArgumentException(ResourceUtilities.FormatResourceStringStripCodeAndKeyword("XslTransform.MustSpecifyType", assemblyPath)); } } +#endif } #endregion } diff --git a/src/UnitTests.Shared/DriveMapping.cs b/src/UnitTests.Shared/DriveMapping.cs index 81324086548..c9034b6902a 100644 --- a/src/UnitTests.Shared/DriveMapping.cs +++ b/src/UnitTests.Shared/DriveMapping.cs @@ -2,10 +2,8 @@ // The .NET Foundation licenses this file to you under the MIT license. #nullable enable -using System; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using System.Text; namespace Microsoft.Build.UnitTests.Shared; diff --git a/src/UnitTests.Shared/DummyMappedDrive.cs b/src/UnitTests.Shared/DummyMappedDrive.cs index ec7df37a00d..064ec22d4b6 100644 --- a/src/UnitTests.Shared/DummyMappedDrive.cs +++ b/src/UnitTests.Shared/DummyMappedDrive.cs @@ -5,7 +5,6 @@ using System; using System.Diagnostics; using System.IO; -using System.Runtime.Versioning; namespace Microsoft.Build.UnitTests.Shared; diff --git a/src/UnitTests.Shared/DummyMappedDriveUtils.cs b/src/UnitTests.Shared/DummyMappedDriveUtils.cs index 90f596858d3..5e8412c956d 100644 --- a/src/UnitTests.Shared/DummyMappedDriveUtils.cs +++ b/src/UnitTests.Shared/DummyMappedDriveUtils.cs @@ -4,7 +4,6 @@ #nullable enable using System; using Microsoft.Build.Framework; -using Microsoft.Build.UnitTests.Shared; namespace Microsoft.Build.UnitTests.Shared; diff --git a/src/UnitTests.Shared/EngineTestEnvironment.cs b/src/UnitTests.Shared/EngineTestEnvironment.cs index 9f543926223..6464653bcf9 100644 --- a/src/UnitTests.Shared/EngineTestEnvironment.cs +++ b/src/UnitTests.Shared/EngineTestEnvironment.cs @@ -25,9 +25,9 @@ namespace Microsoft.Build.UnitTests public partial class TestEnvironment { // reset the default build manager and the state it might have accumulated from other tests -#pragma warning disable CA1823 // Avoid unused private fields +#pragma warning disable CA1823, IDE0052 // Avoid unused private fields private object _resetBuildManager = new ResetDefaultBuildManager(); -#pragma warning restore CA1823 // Avoid unused private fields +#pragma warning restore CA1823, IDE0052 // Avoid unused private fields private sealed class ResetDefaultBuildManager { diff --git a/src/UnitTests.Shared/ObjectModelHelpers.cs b/src/UnitTests.Shared/ObjectModelHelpers.cs index 375c84d6779..a075aaad7e4 100644 --- a/src/UnitTests.Shared/ObjectModelHelpers.cs +++ b/src/UnitTests.Shared/ObjectModelHelpers.cs @@ -23,11 +23,9 @@ using Microsoft.Build.Logging; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; -using Microsoft.Build.Utilities; using Shouldly; using Xunit; using Xunit.Abstractions; -using InvalidProjectFileException = Microsoft.Build.Exceptions.InvalidProjectFileException; #nullable disable diff --git a/src/UnitTests.Shared/RunnerUtilities.cs b/src/UnitTests.Shared/RunnerUtilities.cs index e0879c00028..038c1fc2b9b 100644 --- a/src/UnitTests.Shared/RunnerUtilities.cs +++ b/src/UnitTests.Shared/RunnerUtilities.cs @@ -3,12 +3,11 @@ using System; using System.Diagnostics; -using Microsoft.Build.Shared; using System.IO; using System.Reflection; using Microsoft.Build.Framework; +using Microsoft.Build.Shared; using Xunit.Abstractions; -using System.Linq; #nullable disable diff --git a/src/UnitTests.Shared/TestEnvironment.cs b/src/UnitTests.Shared/TestEnvironment.cs index 1df70aad6af..39374abd308 100644 --- a/src/UnitTests.Shared/TestEnvironment.cs +++ b/src/UnitTests.Shared/TestEnvironment.cs @@ -677,19 +677,16 @@ public override void Revert() public class TransientTestFile : TransientTestState { - private readonly bool _createFile; private readonly bool _expectedAsOutput; public TransientTestFile(string extension, bool createFile, bool expectedAsOutput) { - _createFile = createFile; _expectedAsOutput = expectedAsOutput; Path = FileUtilities.GetTemporaryFile(null, null, extension, createFile); } public TransientTestFile(string rootPath, string extension, bool createFile, bool expectedAsOutput) { - _createFile = createFile; _expectedAsOutput = expectedAsOutput; Path = FileUtilities.GetTemporaryFile(rootPath, null, extension, createFile); } diff --git a/src/Utilities.UnitTests/EncodingUtilities_Tests.cs b/src/Utilities.UnitTests/EncodingUtilities_Tests.cs index a56ee25c4db..591ba25fd9c 100644 --- a/src/Utilities.UnitTests/EncodingUtilities_Tests.cs +++ b/src/Utilities.UnitTests/EncodingUtilities_Tests.cs @@ -1,11 +1,7 @@ // Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. -using System; using System.Globalization; -using System.Runtime.InteropServices; -using System.Threading; -using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Shouldly; using Xunit; diff --git a/src/Utilities.UnitTests/MuxLogger_Tests.cs b/src/Utilities.UnitTests/MuxLogger_Tests.cs index db440177756..b4228b11909 100644 --- a/src/Utilities.UnitTests/MuxLogger_Tests.cs +++ b/src/Utilities.UnitTests/MuxLogger_Tests.cs @@ -2,9 +2,7 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using System.IO; using System.Threading; -using System.Xml; using Microsoft.Build.Evaluation; using Microsoft.Build.Execution; using Microsoft.Build.Framework; diff --git a/src/Utilities.UnitTests/ProcessorArchitecture_Tests.cs b/src/Utilities.UnitTests/ProcessorArchitecture_Tests.cs index ff9c8243fea..d4d52e05d0f 100644 --- a/src/Utilities.UnitTests/ProcessorArchitecture_Tests.cs +++ b/src/Utilities.UnitTests/ProcessorArchitecture_Tests.cs @@ -2,7 +2,6 @@ // The .NET Foundation licenses this file to you under the MIT license. using System; -using Microsoft.Build.Shared; using Microsoft.Build.Utilities; using Shouldly; using Xunit; diff --git a/src/Utilities.UnitTests/TaskItem_Tests.cs b/src/Utilities.UnitTests/TaskItem_Tests.cs index 04a54e3e511..a7db155e2ce 100644 --- a/src/Utilities.UnitTests/TaskItem_Tests.cs +++ b/src/Utilities.UnitTests/TaskItem_Tests.cs @@ -11,7 +11,6 @@ using Microsoft.Build.Utilities; using Shouldly; using Xunit; -using Xunit.NetCore.Extensions; #pragma warning disable 0219 diff --git a/src/Utilities.UnitTests/TaskLoggingHelper_Tests.cs b/src/Utilities.UnitTests/TaskLoggingHelper_Tests.cs index 9a3a96ef330..04e54d27c00 100644 --- a/src/Utilities.UnitTests/TaskLoggingHelper_Tests.cs +++ b/src/Utilities.UnitTests/TaskLoggingHelper_Tests.cs @@ -3,7 +3,6 @@ using System; using System.IO; -using Microsoft.Build.Exceptions; using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Utilities; diff --git a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs index a34a45ea3ec..a06401e8191 100644 --- a/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs +++ b/src/Utilities.UnitTests/ToolLocationHelper_Tests.cs @@ -23,7 +23,6 @@ using SharedDotNetFrameworkArchitecture = Microsoft.Build.Shared.DotNetFrameworkArchitecture; using Xunit; using Xunit.Abstractions; -using Xunit.NetCore.Extensions; #nullable disable @@ -2142,7 +2141,7 @@ public void GetPathToStandardLibraries64Bit35() string frameworkDirectory2064bit = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Bitness64); string frameworkDirectory20Current = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Current); - if (!EnvironmentUtilities.Is64BitOperatingSystem) + if (!Environment.Is64BitOperatingSystem) { // "Not 64 bit OS " return; @@ -2163,7 +2162,7 @@ public void GetPathToStandardLibraries64Bit35() pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v3.5", string.Empty, "itanium"); pathToFramework.ShouldBe(frameworkDirectory2064bit, StringCompareShould.IgnoreCase); - if (!EnvironmentUtilities.Is64BitProcess) + if (!Environment.Is64BitProcess) { pathToFramework = ToolLocationHelper.GetPathToStandardLibraries(".NetFramework", "v3.5", string.Empty, "RandomPlatform"); pathToFramework.ShouldBe(frameworkDirectory2032bit, StringCompareShould.IgnoreCase); @@ -2188,7 +2187,7 @@ public void GetPathToStandardLibraries64Bit40() { IList referencePaths = ToolLocationHelper.GetPathToReferenceAssemblies(new FrameworkNameVersioning(".NETFramework", new Version("4.0"))); - if (!EnvironmentUtilities.Is64BitOperatingSystem) + if (!Environment.Is64BitOperatingSystem) { // "Not 64 bit OS " return; @@ -2233,7 +2232,7 @@ public void GetPathToStandardLibraries32Bit35() string frameworkDirectory2032bit = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Bitness32); string frameworkDirectory20Current = FrameworkLocationHelper.GetPathToDotNetFrameworkV20(SharedDotNetFrameworkArchitecture.Current); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { // "Is a 64 bit OS " return; @@ -2271,7 +2270,7 @@ public void GetPathToStandardLibraries32Bit40() { IList referencePaths = ToolLocationHelper.GetPathToReferenceAssemblies(new FrameworkNameVersioning(".NETFramework", new Version("4.0"))); - if (EnvironmentUtilities.Is64BitOperatingSystem) + if (Environment.Is64BitOperatingSystem) { // "Is 64 bit OS " return; diff --git a/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs b/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs index 1bcad909332..f99b7af9a60 100644 --- a/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs +++ b/src/Utilities.UnitTests/TrackedDependencies/FileTrackerTests.cs @@ -1735,9 +1735,7 @@ public void CreateFileDoesntRecordWriteIfNotWrittenTo() var buffer = new byte[10]; using (FileStream fs = File.Open(readFile, FileMode.Open, FileAccess.ReadWrite, FileShare.None)) { -#pragma warning disable CA2022 - fs.Read(buffer, 0, 10); -#pragma warning restore CA2022 + fs.ReadExactly(buffer, 0, 10); } FileTracker.WriteContextTLogs(testDir, tlogRootName); diff --git a/src/Utilities/CommandLineBuilder.cs b/src/Utilities/CommandLineBuilder.cs index c41b18d74c0..07ac12544e9 100644 --- a/src/Utilities/CommandLineBuilder.cs +++ b/src/Utilities/CommandLineBuilder.cs @@ -154,14 +154,14 @@ public CommandLineBuilder(bool quoteHyphensOnCommandLine, bool useNewLineSeparat /// /// Use a private property so that we can lazy initialize the regex /// - private Regex DefinitelyNeedQuotes => _definitelyNeedQuotes - ?? (_definitelyNeedQuotes = new Regex(_quoteHyphens ? s_definitelyNeedQuotesRegexWithHyphen : s_definitelyNeedQuotesRegexNoHyphen, RegexOptions.CultureInvariant)); + private Regex DefinitelyNeedQuotes => _definitelyNeedQuotes ??= + new Regex(_quoteHyphens ? s_definitelyNeedQuotesRegexWithHyphen : s_definitelyNeedQuotesRegexNoHyphen, RegexOptions.CultureInvariant); /// /// Use a private getter property to we can lazy initialize the regex /// - private Regex AllowedUnquoted => _allowedUnquoted - ?? (_allowedUnquoted = new Regex(_quoteHyphens ? s_allowedUnquotedRegexNoHyphen : s_allowedUnquotedRegexWithHyphen, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant)); + private Regex AllowedUnquoted => _allowedUnquoted ??= + new Regex(_quoteHyphens ? s_allowedUnquotedRegexNoHyphen : s_allowedUnquotedRegexWithHyphen, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); /// /// Checks the given switch parameter to see if it must/can be quoted. @@ -259,6 +259,9 @@ protected void AppendQuotedTextToBuffer(StringBuilder buffer, string unquotedTex } // Count the number of quotes +#if NET + int literalQuotes = unquotedTextToAppend.AsSpan().Count('"'); +#else int literalQuotes = 0; for (int i = 0; i < unquotedTextToAppend.Length; i++) { @@ -267,6 +270,8 @@ protected void AppendQuotedTextToBuffer(StringBuilder buffer, string unquotedTex literalQuotes++; } } +#endif + if (literalQuotes > 0) { // Replace any \" sequences with \\" diff --git a/src/Utilities/CompatibilitySuppressions.xml b/src/Utilities/CompatibilitySuppressions.xml index eeee8bb3f8a..d61bb3c6b1f 100644 --- a/src/Utilities/CompatibilitySuppressions.xml +++ b/src/Utilities/CompatibilitySuppressions.xml @@ -1,11 +1,6 @@  - PKV004 .NETCoreApp,Version=v2.0 diff --git a/src/Utilities/LockCheck.cs b/src/Utilities/LockCheck.cs index 7b191851e70..187d3d59f8e 100644 --- a/src/Utilities/LockCheck.cs +++ b/src/Utilities/LockCheck.cs @@ -7,7 +7,6 @@ using System.Linq; using System.Runtime.InteropServices; using System.Runtime.Versioning; -using Microsoft.Build.Framework; using Microsoft.Build.Shared; #nullable disable @@ -241,7 +240,7 @@ public override bool Equals(object obj) public override string ToString() { - return ProcessId + "@" + StartTime.ToString("s"); + return $"{ProcessId}@{StartTime:s}"; } } diff --git a/src/Utilities/Microsoft.Build.Utilities.csproj b/src/Utilities/Microsoft.Build.Utilities.csproj index e5a0a89b595..86e032a484b 100644 --- a/src/Utilities/Microsoft.Build.Utilities.csproj +++ b/src/Utilities/Microsoft.Build.Utilities.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/Utilities/SDKManifest.cs b/src/Utilities/SDKManifest.cs index 86e0be400ce..d8c06a2c234 100644 --- a/src/Utilities/SDKManifest.cs +++ b/src/Utilities/SDKManifest.cs @@ -45,22 +45,22 @@ public class SDKManifest /// /// Pattern in path to extension SDK used to help determine if manifest is from a framework SDK /// - private static string s_extensionSDKPathPattern = @"\MICROSOFT SDKS\WINDOWS\V8.0\EXTENSIONSDKS"; + private const string s_extensionSDKPathPattern = @"\MICROSOFT SDKS\WINDOWS\V8.0\EXTENSIONSDKS"; /// /// Default version of MaxPlatformVersion in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMaxPlatformVersion = "8.0"; + private const string s_defaultMaxPlatformVersion = "8.0"; /// /// Default version of MinOSVersion in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMinOSVersion = "6.2.1"; + private const string s_defaultMinOSVersion = "6.2.1"; /// /// Default version of MaxOSVersionTested in framework extension SDKs with manifest not containing such a property /// - private static string s_defaultMaxOSVersionTested = "6.2.1"; + private const string s_defaultMaxOSVersionTested = "6.2.1"; /// /// What should happen if this sdk is resolved with other sdks of the same productfamily or same sdk name. diff --git a/src/Utilities/TargetPlatformSDK.cs b/src/Utilities/TargetPlatformSDK.cs index 8740fe39e10..64ef39b0ed5 100644 --- a/src/Utilities/TargetPlatformSDK.cs +++ b/src/Utilities/TargetPlatformSDK.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; -using System.Globalization; using Microsoft.Build.Shared; #nullable disable @@ -189,6 +188,6 @@ public bool ContainsPlatform(string targetPlatformIdentifier, string targetPlatf /// /// Given an identifier and version, construct a string to use as a key for that combination. /// - internal static string GetSdkKey(string sdkIdentifier, string sdkVersion) => string.Format(CultureInfo.InvariantCulture, "{0}, Version={1}", sdkIdentifier, sdkVersion); + internal static string GetSdkKey(string sdkIdentifier, string sdkVersion) => $"{sdkIdentifier}, Version={sdkVersion}"; } } diff --git a/src/Utilities/TaskItem.cs b/src/Utilities/TaskItem.cs index d43175b77aa..c1820c060aa 100644 --- a/src/Utilities/TaskItem.cs +++ b/src/Utilities/TaskItem.cs @@ -508,6 +508,7 @@ void IMetadataContainer.ImportMetadata(IEnumerable> _metadata.SetItems(metadata.Select(kvp => new KeyValuePair(kvp.Key, kvp.Value ?? string.Empty))); } +#if FEATURE_APPDOMAIN private IEnumerable> EnumerateMetadataEager() { if (_metadata == null) @@ -526,6 +527,7 @@ private IEnumerable> EnumerateMetadataEager() return result; } +#endif private IEnumerable> EnumerateMetadataLazy() { diff --git a/src/Utilities/ToolLocationHelper.cs b/src/Utilities/ToolLocationHelper.cs index 3f13658e0dd..25ae27e63d0 100644 --- a/src/Utilities/ToolLocationHelper.cs +++ b/src/Utilities/ToolLocationHelper.cs @@ -9,7 +9,6 @@ using System.Runtime.Versioning; using System.Text; using System.Xml; -using Microsoft.Build.Framework; using Microsoft.Build.Shared; using Microsoft.Build.Shared.FileSystem; using Microsoft.Build.Tasks.AssemblyFoldersFromConfig; @@ -1821,7 +1820,7 @@ public static string GetPathToStandardLibraries(string targetFrameworkIdentifier if (NativeMethodsShared.IsWindows && platformTarget != null) { // If we are a 32 bit operating system the we should always return the 32 bit directory, or we are targeting x86, arm is also 32 bit - if (!EnvironmentUtilities.Is64BitOperatingSystem || platformTarget.Equals("x86", StringComparison.OrdinalIgnoreCase) || platformTarget.Equals("arm", StringComparison.OrdinalIgnoreCase)) + if (!Environment.Is64BitOperatingSystem || platformTarget.Equals("x86", StringComparison.OrdinalIgnoreCase) || platformTarget.Equals("arm", StringComparison.OrdinalIgnoreCase)) { targetedArchitecture = SharedDotNetFrameworkArchitecture.Bitness32; } @@ -2863,7 +2862,7 @@ private static void GatherSDKListFromRegistry(string registryRoot, Dictionary GetFrameworkVersions(string frameworkReferenceRoot, // only add if the version folder name is of the right format if (folder.Name.Length >= 4 && folder.Name.StartsWith("v", StringComparison.OrdinalIgnoreCase)) { - Version ver; - if (Version.TryParse(folder.Name.Substring(1), out ver)) + if (Version.TryParse( +#if NET + folder.Name.AsSpan(1), +#else + folder.Name.Substring(1), +#endif + out _)) { frameworkVersions.Add(folder.Name); } @@ -3982,10 +3990,14 @@ private VersionComparer() public int Compare(string versionX, string versionY) { +#if NET + return Version.Parse(versionX.AsSpan(1)).CompareTo(Version.Parse(versionY.AsSpan(1))); +#else return new Version(versionX.Substring(1)).CompareTo(new Version(versionY.Substring(1))); +#endif } } - #endregion +#endregion } } diff --git a/src/Utilities/TrackedDependencies/FileTracker.cs b/src/Utilities/TrackedDependencies/FileTracker.cs index ef175ad916b..c3ac96f1f15 100644 --- a/src/Utilities/TrackedDependencies/FileTracker.cs +++ b/src/Utilities/TrackedDependencies/FileTracker.cs @@ -9,7 +9,6 @@ using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using System.IO; -using System.Linq; using System.Text; using Microsoft.Build.Framework; using Microsoft.Build.Shared; @@ -80,7 +79,7 @@ public static class FileTracker /// This must be the base system-wide temp path because we use it to filter out I/O of tools outside of our control. /// Tools running under the tracker may put temp files in the temp base or in a sub-directory of their choosing. /// - private static readonly string s_tempPath = Path.GetTempPath(); + private static readonly string s_tempPath = FileUtilities.EnsureTrailingSlash(Path.GetTempPath()); // The short path to temp private static readonly string s_tempShortPath = FileUtilities.EnsureTrailingSlash(NativeMethodsShared.GetShortFilePath(s_tempPath).ToUpperInvariant()); @@ -247,12 +246,25 @@ public static bool FileIsExcludedFromDependencies(string fileName) // 5. Files under the common ("All Users") Application Data location -- C:\Documents and Settings\All Users\Application Data // on XP and either C:\Users\All Users\Application Data or C:\ProgramData on Vista+ - return FileIsUnderPath(fileName, s_applicationDataPath) || - FileIsUnderPath(fileName, s_localApplicationDataPath) || - FileIsUnderPath(fileName, s_localLowApplicationDataPath) || - FileIsUnderPath(fileName, s_tempShortPath) || - FileIsUnderPath(fileName, s_tempLongPath) || - s_commonApplicationDataPaths.Any(p => FileIsUnderPath(fileName, p)); + if (FileIsUnderNormalizedPath(fileName, s_applicationDataPath) || + FileIsUnderNormalizedPath(fileName, s_localApplicationDataPath) || + FileIsUnderNormalizedPath(fileName, s_localLowApplicationDataPath) || + FileIsUnderNormalizedPath(fileName, s_tempShortPath) || + FileIsUnderNormalizedPath(fileName, s_tempLongPath)) + { + return true; + } + + // PERF: Avoid LINQ in this path. + foreach (string p in s_commonApplicationDataPaths) + { + if (FileIsUnderNormalizedPath(fileName, p)) + { + return true; + } + } + + return false; } /// @@ -266,15 +278,31 @@ public static bool FileIsExcludedFromDependencies(string fileName) /// public static bool FileIsUnderPath(string fileName, string path) { + // Ensure that the path has a trailing slash that we are checking under + // By default the paths that we check for most often will have, so this will + // return fast and not allocate memory in the process + return FileIsUnderNormalizedPath(fileName, FileUtilities.EnsureTrailingSlash(path)); + } + + internal static bool FileIsUnderNormalizedPath(string fileName, string path) + { + int pathLength = path.Length; + + Debug.Assert(path[pathLength - 1] == Path.DirectorySeparatorChar); + // UNDONE: Get the long file path for the entry // This is an incredibly expensive operation. The tracking log // as written by CL etc. does not contain short paths // fileDirectory = NativeMethods.GetFullLongFilePath(fileDirectory); - // Ensure that the path has a trailing slash that we are checking under - // By default the paths that we check for most often will have, so this will - // return fast and not allocate memory in the process - path = FileUtilities.EnsureTrailingSlash(path); + // quick checks to return early. If our given filename is less than the length of the path, it can't be under it. + // Similarly, if the file name doesn't have a path separator + // at the index of the last separator in the path, it doesn't match. + // Because we have a normalized path below we can check against normal-slash directly. + if (fileName.Length < pathLength || fileName[pathLength - 1] != Path.DirectorySeparatorChar) + { + return false; + } // Is the fileName under the filePath? return string.Compare(fileName, 0, path, 0, path.Length, StringComparison.OrdinalIgnoreCase) == 0; diff --git a/template_feed/content/Microsoft.CheckTemplate/.template.config/template.json b/template_feed/content/Microsoft.CheckTemplate/.template.config/template.json index c3b83f0c8e8..5753a79bada 100644 --- a/template_feed/content/Microsoft.CheckTemplate/.template.config/template.json +++ b/template_feed/content/Microsoft.CheckTemplate/.template.config/template.json @@ -27,7 +27,7 @@ "type": "parameter", "description": "Overrides the default Microsoft.Build version where check's interfaces are placed", "datatype": "text", - "defaultValue": "17.13.0", + "defaultValue": "17.15.0", "replaces": "1.0.0-MicrosoftBuildPackageVersion", "displayName": "Microsoft.Build default package version override" }