diff --git a/.ado/build-template.yml b/.ado/build-template.yml deleted file mode 100644 index 5beec9b9629..00000000000 --- a/.ado/build-template.yml +++ /dev/null @@ -1,528 +0,0 @@ -# -# Shared build and pack template used by CI and PR pipelines. -# CI sets buildEnvironment: Continuous for signed Official builds. -# PR sets buildEnvironment: PullRequest for Unofficial validation builds. -# - -parameters: -- name: buildEnvironment - type: string - default: PullRequest - values: [PullRequest, Continuous] - -- name: isReleaseBuild - type: string - default: auto - values: [auto, 'true', 'false'] - -- name: AgentPool - type: object - default: - Medium: - name: rnw-pool-4-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8-microsoft - demands: ImageOverride -equals rnw-img-vs2022-node22 - -- name: desktopBuildMatrix - type: object - default: - - Name: X64Debug - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X64Release - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86Debug - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: X86Release - BuildConfiguration: Release - BuildPlatform: x86 - - Name: ARM64ECDebug - BuildConfiguration: Debug - BuildPlatform: ARM64EC - - Name: ARM64ECRelease - BuildConfiguration: Release - BuildPlatform: ARM64EC - -- name: universalBuildMatrix - type: object - default: - - Name: X64DebugFabric - BuildConfiguration: Debug - BuildPlatform: x64 - - Name: X64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x64 - - Name: X86DebugFabric - BuildConfiguration: Debug - BuildPlatform: x86 - - Name: X86ReleaseFabric - BuildConfiguration: Release - BuildPlatform: x86 - - Name: Arm64DebugFabric - BuildConfiguration: Debug - BuildPlatform: ARM64 - - Name: Arm64ReleaseFabric - BuildConfiguration: Release - BuildPlatform: ARM64 - -resources: - repositories: - - repository: 1ESPipelineTemplates - type: git - name: 1ESPipelineTemplates/1ESPipelineTemplates - ref: refs/tags/release - -extends: - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates - ${{ else }}: - template: v1/1ES.Unofficial.PipelineTemplate.yml@1ESPipelineTemplates - parameters: - pool: ${{ parameters.AgentPool.Medium }} - ${{ if ne(parameters.buildEnvironment, 'Continuous') }}: - settings: - skipBuildTagsForGitHubPullRequests: true - featureFlags: - autoEnablePREfastWithNewRuleset: false # PREfast produces 0 actionable findings; auto-enable injects /analyze into every C++ TU, generating ~2656 SARIF files that Guardian uploads for ~19 min per native build - sdl: - credscan: - suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json - spotBugs: - enabled: false # We don't have any java, but random packages in node_modules do - prefast: - enabled: false - stages: - #================================================================= - # Stage: Setup - # Detects build scenario, bumps versions (developer builds), - # and publishes version variables for downstream stages. - #================================================================= - - stage: Setup - jobs: - - job: Setup - displayName: Setup - variables: [template: .ado/variables/shared.yml@self] - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 15 - steps: - # 1. Detect whether this is a release build or a developer build - - pwsh: | - $param = '${{ parameters.isReleaseBuild }}' - $buildReason = '$(Build.Reason)' - $sourceMessage = '$(Build.SourceVersionMessage)' - $prSourceBranch = '$(System.PullRequest.SourceBranch)' - - if ($param -eq 'true') { - $isRelease = $true - Write-Host "Release build forced by parameter" - } - elseif ($param -eq 'false') { - $isRelease = $false - Write-Host "Developer build forced by parameter" - } - else { - # Auto-detect - if ($buildReason -eq 'PullRequest') { - # PR: check source branch - $isRelease = $prSourceBranch.StartsWith('prepare-release/') - Write-Host "PR source branch: $prSourceBranch -> isRelease=$isRelease" - } - else { - # CI/Manual: check commit message - $firstLine = ($sourceMessage -split "`n")[0].Trim() - $isRelease = $firstLine.StartsWith("RELEASE:") - Write-Host "Commit message first line: $firstLine -> isRelease=$isRelease" - } - } - - Write-Host "RESULT: isReleaseBuild=$isRelease" - Write-Host "##vso[task.setvariable variable=isReleaseBuild;isOutput=true]$isRelease" - Write-Host "##vso[task.setvariable variable=isReleaseBuild]$isRelease" - name: detectScenario - displayName: Detect build scenario - - # 2. Full checkout (needed for beachball bump on developer builds; - # harmless ~1 min overhead on release builds where bump is skipped) - - template: .ado/templates/checkout-full.yml@self - parameters: - persistCredentials: true - - # 3. Node.js (always needed — setVersionEnvVars.js requires it) - - task: UseNode@1 - inputs: - version: '24.x' - displayName: Use Node.js 24.x - - - template: .ado/templates/compute-beachball-branch-name.yml@self - - # 4. Beachball tooling (developer builds only) - # Inlined from strict-yarn-install.yml because template references - # do not support the condition: keyword. - - pwsh: | - $packageJson = Get-Content ./package.json | ConvertFrom-Json - $packageJson.scripts = $packageJson.scripts | Select-Object * -ExcludeProperty postinstall - $packageJson | ConvertTo-Json | Out-File ./package.json - displayName: Remove postinstall - condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) - - - script: npx --yes midgard-yarn-strict@1.2.4 @rnw-scripts/beachball-config - displayName: Strict yarn install @rnw-scripts/beachball-config - condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) - - - script: npx lage build --scope @rnw-scripts/prepare-release --scope @rnw-scripts/beachball-config - displayName: Build prepare-release and beachball-config - condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) - - # 5. Beachball check (Developer PR only) - - script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run 'yarn change' from root of repo to generate a change file." - displayName: Check for change files - condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False'), eq(variables['Build.Reason'], 'PullRequest')) - - # 6. Bump versions via prepare-release (Developer builds only) - - script: npx prepare-release --bump-only --branch $(BeachBallBranchName) --no-color - displayName: Bump versions (prepare-release --bump-only) - condition: and(succeeded(), eq(variables['detectScenario.isReleaseBuild'], 'False')) - - # 7. Read version and set pipeline variables (always) - - template: .ado/templates/set-version-vars.yml@self - parameters: - buildEnvironment: Continuous - - # 8. Include npmPack.js for Release pipeline (CI only) - - script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js" - displayName: Include npmPack.js in VersionEnvVars artifact - condition: and(succeeded(), eq('${{ parameters.buildEnvironment }}', 'Continuous')) - - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Publish version variables' - targetPath: $(Build.StagingDirectory)/versionEnvVars - artifactName: VersionEnvVars - - #================================================================= - # Stage: Build - # Shipping builds (Desktop + Universal), ESRP signing, NPM pack, - # linting, and NuGet packing. - #================================================================= - - stage: Build - dependsOn: Setup - jobs: - # Create NPM packages - - job: RnwNpmPack - displayName: Create NPM packages - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 60 - cancelTimeoutInMinutes: 5 - steps: - - template: .ado/templates/checkout-shallow.yml@self - - - template: .ado/templates/prepare-js-env.yml@self - parameters: - agentImage: HostedImage - - - script: node .ado/scripts/npmPack.js --clean --no-color "$(Pipeline.Workspace)\published-packages" - displayName: Pack npm packages - - - script: dir /s "$(Pipeline.Workspace)\published-packages" - displayName: Show created npm packages - - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Publish npm pack artifacts' - condition: succeededOrFailed() - targetPath: $(Pipeline.Workspace)/published-packages - artifactName: NpmPackedTarballs - - # Run linting - - template: .ado/jobs/linting.yml@self - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - # Build, test, and sign Desktop DLLs - - ${{ each matrix in parameters.desktopBuildMatrix }}: - - job: Desktop${{ matrix.Name }} - displayName: Desktop ${{ matrix.Name }} - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout - variables: - - template: .ado/variables/windows.yml@self - # Enable if any issues RNTesterIntegrationTests::* become unstable. - - name: Desktop.IntegrationTests.SkipRNTester - value: false - #5059 - Disable failing or intermittent tests (IntegrationTestHarness,WebSocket,Logging). - #10732 - WebSocketIntegrationTest::SendReceiveSsl fails on Windows Server 2022. - #12714 - Disable for first deployment of test website. - #14217 - Reneable RNTesterIntegrationTests - - name: Desktop.IntegrationTests.Filter - value: > - (FullyQualifiedName!=RNTesterIntegrationTests::IntegrationTestHarness)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocket)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketBlob)& - (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketMultipleSend)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendConsecutive)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveLargeMessage)& - (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveSsl)& - (FullyQualifiedName!=Microsoft::React::Test::HttpOriginPolicyIntegrationTest)& - (FullyQualifiedName!=RNTesterIntegrationTests::Dummy)& - (FullyQualifiedName!=RNTesterIntegrationTests::Fetch)& - (FullyQualifiedName!=RNTesterIntegrationTests::XHRSample)& - (FullyQualifiedName!=RNTesterIntegrationTests::Blob)& - (FullyQualifiedName!=RNTesterIntegrationTests::Logging) - #6799 - HostFunctionTest, HostObjectProtoTest crash under JSI/V8; - # PreparedJavaScriptSourceTest asserts/fails under JSI/ChakraCore - - name: Desktop.UnitTests.Filter - value: > - (FullyQualifiedName!~HostFunctionTest)& - (FullyQualifiedName!~HostObjectProtoTest)& - (FullyQualifiedName!~PreparedJavaScriptSourceTest) - steps: - # Build and test - - template: .ado/jobs/desktop-single.yml@self - parameters: - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - buildEnvironment: ${{ parameters.buildEnvironment }} - - # Stage shipping artifacts (copies to staging dir, does not publish) - - template: .ado/templates/publish-build-artifacts.yml@self - parameters: - oneESMode: true - artifactName: Desktop - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - React.Windows.Desktop\** - React.Windows.Desktop.DLL\** - React.Windows.Desktop.Test.DLL\** - - # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - template: .ado/templates/esrp-codesign-binaries.yml@self - parameters: - displayName: 'CodeSign Desktop Binaries' - folderPath: $(Build.StagingDirectory)/NuGet/Desktop/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - pattern: | - **/react-native-win32.dll - - templateContext: - sdl: - prefast: - enabled: false - binskim: - analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\React.Windows.Desktop.DLL\react-native-win32.dll' - outputs: - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: Desktop.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' - artifactName: Desktop.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} - targetPath: $(Build.StagingDirectory)/NuGet/Desktop/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - - # Build, test, and sign Universal DLLs - - ${{ each matrix in parameters.universalBuildMatrix }}: - - job: Universal${{ matrix.Name }} - displayName: Universal ${{ matrix.Name }} - pool: ${{ parameters.AgentPool.Large }} - timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout - variables: - - template: .ado/variables/windows.yml@self - # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). - # Keep NuGet cache independent from the user directory. - - name: NUGET_PACKAGES - value: $(Agent.TempDirectory)/NuGetPackages - steps: - # Build and test - - template: .ado/jobs/universal-single.yml@self - parameters: - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - buildEnvironment: ${{ parameters.buildEnvironment }} - - # Stage shipping artifacts (copies to staging dir, does not publish) - - template: .ado/templates/publish-build-artifacts.yml@self - parameters: - oneESMode: true - artifactName: ReactWindows - buildPlatform: ${{ matrix.BuildPlatform }} - buildConfiguration: ${{ matrix.BuildConfiguration }} - contents: | - Microsoft.ReactNative\Microsoft.ReactNative.* - Microsoft.ReactNative.CsWinRT\Microsoft.ReactNative.Projection.* - - # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - template: .ado/templates/esrp-codesign-binaries.yml@self - parameters: - displayName: 'CodeSign Microsoft.ReactNative Binaries' - folderPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - pattern: | - **/Microsoft.ReactNative.dll - **/Microsoft.ReactNative.winmd - **/Microsoft.ReactNative.Projection.dll - - templateContext: - sdl: - prefast: - enabled: false - binskim: - analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.dll' - outputs: - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' - artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} - targetPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} - - # Create NuGet packages - - job: RNWNuget - displayName: Pack NuGet - dependsOn: - - RnwNpmPack - - Linting - - ${{ each matrix in parameters.desktopBuildMatrix }}: - - Desktop${{ matrix.Name }} - - ${{ each matrix in parameters.universalBuildMatrix }}: - - Universal${{ matrix.Name }} - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 60 # Protect against the long CodeSign task - - steps: - - template: .ado/templates/checkout-shallow.yml@self - - - template: .ado/templates/prepare-js-env.yml@self - - - template: .ado/templates/apply-published-version-vars.yml@self - - # The commit tag in the nuspec requires that we use at least nuget 5.8 (because things break with nuget versions before and VS 16.8 or later) - - task: NuGetToolInstaller@1 - inputs: - versionSpec: ">=5.8.0" - - - template: .ado/templates/prep-and-pack-nuget.yml@self - parameters: - artifactName: ReactWindows - publishCommitId: $(publishCommitId) - npmVersion: $(npmVersion) - packMicrosoftReactNative: true - packMicrosoftReactNativeCxx: true - slices: - - platform: x64 - configuration: Release - - platform: x86 - configuration: Release - - platform: ARM64 - configuration: Release - - - template: .ado/templates/prep-and-pack-nuget.yml@self - parameters: - artifactName: Desktop - publishCommitId: $(publishCommitId) - npmVersion: $(npmVersion) - packDesktop: true - slices: - - platform: x64 - configuration: Release - - platform: x86 - configuration: Release - - platform: ARM64EC - configuration: Release - - platform: x64 - configuration: Debug - - platform: x86 - configuration: Debug - - platform: ARM64EC - configuration: Debug - - # ESRP sign (CI only — service connection unavailable in PR/Unofficial mode) - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - template: .ado/templates/esrp-codesign-nuget.yml@self - parameters: - displayName: 'CodeSign all NuGet packages' - folderPath: $(System.DefaultWorkingDirectory)/NugetRootFinal - pattern: '**/*.nupkg' - - # Tag the source commit when a RELEASE build succeeds (CI only) - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - script: | - TAG_NAME="react-native-windows_v$(npmVersion)" - echo "Creating tag $TAG_NAME on $(Build.SourceVersion)" - git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" tag "$TAG_NAME" "$(Build.SourceVersion)" - git -c http.extraheader="AUTHORIZATION: bearer $(System.AccessToken)" push origin "$TAG_NAME" - displayName: Tag release sources - condition: and(succeeded(), startsWith(variables['Build.SourceVersionMessage'], 'RELEASE:')) - - templateContext: - sdl: - binskim: - analyzeTargetGlob: '$(System.DefaultWorkingDirectory)\NugetRoot\**\Microsoft.ReactNative\Microsoft.ReactNative.dll;$(System.DefaultWorkingDirectory)\NugetRoot\**\React.Windows.Desktop.DLL\react-native-win32.dll' - outputs: - - output: pipelineArtifact - displayName: 'Publish final nuget artifacts' - targetPath: $(System.DefaultWorkingDirectory)\NugetRootFinal - artifactName: "ReactWindows-final-nuget" - - #================================================================= - # Stage: Tests - # Independent test jobs that don't require native build artifacts. - # Runs in parallel with Build stage (both depend only on Setup). - #================================================================= - - stage: Tests - displayName: Tests - dependsOn: Setup - jobs: - - template: .ado/jobs/node-tests.yml@self - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: .ado/jobs/playground.yml@self - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - - template: .ado/jobs/e2e-test.yml@self - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - - #================================================================= - # Stage: CLI - # CLI init verification — tests the final NuGet + npm packages. - # Runs after Build stage completes (needs RNWNuget artifacts). - #================================================================= - - stage: CLI - displayName: CLI - dependsOn: Build - jobs: - - template: .ado/jobs/cli-init-windows.yml@self - parameters: - buildEnvironment: ${{ parameters.buildEnvironment }} - AgentPool: ${{ parameters.AgentPool }} - diff --git a/.ado/ci-pipeline.yml b/.ado/ci-pipeline.yml deleted file mode 100644 index f7b84b8ff3f..00000000000 --- a/.ado/ci-pipeline.yml +++ /dev/null @@ -1,29 +0,0 @@ -# -# The CI pipeline entry point. -# Extends build-template.yml with buildEnvironment: Continuous for signed Official builds. -# - -name: 0.0.$(Date:yyMM.d)$(Rev:rrr) - -trigger: none -pr: none - -parameters: - - name: isReleaseBuild - displayName: 'Treat as Release build (skip beachball, use committed versions)' - type: string - default: auto - values: - - auto - - 'true' - - 'false' - -variables: - - template: variables/windows.yml - - group: RNW Secrets - -extends: - template: build-template.yml@self - parameters: - buildEnvironment: Continuous - isReleaseBuild: ${{ parameters.isReleaseBuild }} diff --git a/.ado/compliance.yml b/.ado/compliance.yml new file mode 100644 index 00000000000..659bb734967 --- /dev/null +++ b/.ado/compliance.yml @@ -0,0 +1,140 @@ +name: 0.0.$(Date:yyMM.d)$(Rev:rrr) + +parameters: +- name: AgentPool + type: object + default: + Medium: + name: rnw-pool-4-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 +- name: forceCodeQL + displayName: Force CodeQL to rebuild databases + type: boolean + default: false +- name: complianceWarnOnly + displayName: Convert compliance errors to warnings + type: boolean + default: true # Let's get all results in this pipeline + +variables: + - template: variables/windows.yml + - group: RNW Secrets + - name: Codeql.Enabled + value: true + - ${{ if eq(parameters.forceCodeQL, true) }}: + - name: Codeql.Cadence + value: 0 + - ${{ if eq(parameters.forceCodeQL, false) }}: + - name: Codeql.Cadence + value: 120 # In hours, default to only run every 5 days + +trigger: none +pr: none + +jobs: + - job: RnwUniversalCompliance + displayName: RNW Universal Compliance + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 360 # Compliance tasks recommend to 3x usual build timeout + + steps: + - template: templates/checkout-shallow.yml + + - template: templates/prepare-js-env.yml + + - template: templates/set-version-vars.yml + parameters: + buildEnvironment: Continuous + + - template: templates/publish-version-vars.yml + + - template: templates/prepare-build-env.yml + parameters: + platform: x64 + configuration: Release + buildEnvironment: Continuous + + - template: templates/apply-published-version-vars.yml + + # Pre-build compliance tasks + + - template: templates/run-compliance-prebuild.yml + parameters: + complianceWarnOnly: ${{ parameters.complianceWarnOnly }} + + - task: NuGetAuthenticate@1 + + # AgentES Task (https://aka.ms/UES) + # Installs and runs the "Agent ES" tool, which scans the source code for banned file types. + - powershell: | + & nuget.exe install AgentES -FallbackSource https://microsoft.pkgs.visualstudio.com/_packaging/Undocked.Shell.Services/nuget/v3/index.json + $AgentESPath = (Get-ChildItem -Path AgentES* -Filter AgentES.exe -Recurse | %{$_.FullName}) + & $AgentESPath $env:BUILD_SOURCESDIRECTORY -e:$env:BUILD_SOURCESDIRECTORY\.ado\config\AgentES.Exemptions.json -b + displayName: "⚖️ AgentES - Scan of Repository for UES Policy Violations" + workingDirectory: $(Agent.BuildDirectory) + continueOnError: ${{ parameters.complianceWarnOnly }} + + # Initialize CodeQL 3000 Task (https://aka.ms/codeql3000) + # Performs static code analysis. + - task: CodeQL3000Init@0 + displayName: "🛡️ Initialize CodeQL" + continueOnError: ${{ parameters.complianceWarnOnly }} + + # Build RNW + + - template: templates/msbuild-sln.yml + parameters: + solutionDir: vnext + solutionName: Microsoft.ReactNative.NewArch.sln + buildPlatform: x64 + buildConfiguration: Release + + # Post-build compliance tasks + + - template: templates/run-compliance-postbuild.yml + parameters: + complianceWarnOnly: ${{ parameters.complianceWarnOnly }} + + # Attack Surface Analyzer (ASA) for SDL compliance + # This is integrated into the compliance pipeline but runs independently + # Note: ASA requires before/after snapshots, so we run a separate analysis + - task: PowerShell@2 + displayName: '🛡️ Attack Surface Analyzer - Note' + inputs: + targetType: inline + script: | + Write-Host "==========================================" + Write-Host "Attack Surface Analyzer (ASA) Information" + Write-Host "==========================================" + Write-Host "" + Write-Host "ASA runs as a separate job in the PR pipeline (see stages.yml)." + Write-Host "It performs before/after snapshot analysis of the build process." + Write-Host "" + Write-Host "For manual ASA runs or to view results:" + Write-Host "1. Check PR pipeline artifacts for ASA_Results" + Write-Host "2. Review docs/attack-surface-analyzer.md for guidance" + Write-Host "3. Run ASA locally: dotnet tool install -g Microsoft.CST.AttackSurfaceAnalyzer.CLI" + Write-Host "" + Write-Host "✅ ASA integration is active in PR builds" + + # Finalize CodeQL 3000 Task (https://aka.ms/codeql3000) + # Performs static code analysis. + - task: CodeQL3000Finalize@0 + displayName: "🛡️ Finalize CodeQL" + inputs: + # Enable TSA for automatic bug filing from CodeQL + TSAEnabled: true + TSAOptions: | + { + "areaPath": "OS\\Windows Client and Services\\WinPD\\SPICE\\ReactNative", + "iterationPath": "OS\\Future", + "notificationAliases": ["$(TSANotificationAliases)"], + "codebaseAdmins": ["$(TSACodebaseAdmins)"], + "bugTags": ["SDL", "Security"], + "instanceUrl": "https://dev.azure.com/microsoft", + "projectName": "OS" + } + continueOnError: ${{ parameters.complianceWarnOnly }} diff --git a/.ado/continuous.yml b/.ado/continuous.yml new file mode 100644 index 00000000000..f20e680ad11 --- /dev/null +++ b/.ado/continuous.yml @@ -0,0 +1,28 @@ +name: RNW CI $(Date:yyyyMMdd).$(Rev:r) + +trigger: none # will disable CI builds entirely +pr: none + +variables: + - group: RNW Secrets + - group: platform-override-zero-permission-token + +parameters: + - name: AgentPool + type: object + default: + Small: + name: rnw-pool-2 + demands: ImageOverride -equals rnw-img-vs2022-node22 + Medium: + name: rnw-pool-4 + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8 + demands: ImageOverride -equals rnw-img-vs2022-node22 + +stages: + - template: stages.yml + parameters: + buildEnvironment: Continuous + AgentPool: ${{ parameters.AgentPool }} diff --git a/.ado/integrate-rn.yaml b/.ado/integrate-rn.yaml index b9ec5384ada..092d0424f76 100644 --- a/.ado/integrate-rn.yaml +++ b/.ado/integrate-rn.yaml @@ -33,7 +33,7 @@ jobs: displayName: yarn integrate-rn continueOnError: true - - pwsh: | + - powershell: | if (!(Test-Path "$(Agent.TempDirectory)\integration-report.md")) { dir "$(Agent.TempDirectory)" throw "No integration report generated" @@ -43,7 +43,7 @@ jobs: } displayName: Test for changes - - pwsh: | + - powershell: | $reportWithHeader = "${{ parameters.commitTitle}}`n`n" + (Get-Content -Raw $(Agent.TempDirectory)\integration-report.md) # Trim to max GitHub PR length $trimmedReport = $reportWithHeader.substring(0, [math]::min(65536, $reportWithHeader.length)) @@ -55,12 +55,12 @@ jobs: git commit -F $(Agent.TempDirectory)\commit-message.md displayName: Commit changes - - pwsh: | + - powershell: | $commitHash = git log --format=%H -n 1 Write-Output "##vso[task.setvariable variable=FirstCommit]$commitHash" displayName: Save base commit - - pwsh: | + - powershell: | $patchScope = node -e " const path = require('path'); const {enumerateRepoPackages} = require('@react-native-windows/package-utils'); @@ -72,7 +72,7 @@ jobs: yarn change --scope @($patchScope.split(',')) --message "${{ parameters.commitTitle}}" --type patch displayName: Create patch changefiles - - pwsh: | + - powershell: | $prereleaseScope = node -e " const path = require('path'); const {enumerateRepoPackages} = require('@react-native-windows/package-utils'); @@ -93,7 +93,7 @@ jobs: - script: git push origin integrate-${{ parameters.reactNativeVersion }} displayName: Push changes - - pwsh: | + - powershell: | $headers = @{'Accept'='application/vnd.github.v3+json'; 'Authorization'='Token $(githubAuthToken)'} $body = '{"head": "integrate-${{ parameters.reactNativeVersion }}", "base": "main", "title": "${{ parameters.commitTitle}}"}' Invoke-WebRequest -Method Post -Headers $headers -Body $body https://api.github.com/repos/microsoft/react-native-windows/pulls diff --git a/.ado/jobs/attack-surface-analyzer.yml b/.ado/jobs/attack-surface-analyzer.yml new file mode 100644 index 00000000000..5b8e7f11a7e --- /dev/null +++ b/.ado/jobs/attack-surface-analyzer.yml @@ -0,0 +1,406 @@ +# Job to run Attack Surface Analyzer (ASA) for SDL compliance +# Validates that installers or high-privilege programs do not weaken OS security +parameters: + - name: buildEnvironment + type: string + default: PullRequest + values: + - PullRequest + - SecurePullRequest + - Continuous + - name: AgentPool + type: object + - name: complianceWarnOnly + displayName: Convert compliance errors to warnings + type: boolean + default: true + +jobs: + # Only run ASA for SecurePullRequest builds + - ${{if eq(parameters.buildEnvironment, 'SecurePullRequest')}}: + - job: AttackSurfaceAnalyzer + displayName: Attack Surface Analyzer (ASA) 🛡️ + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 5 + + variables: + - template: ../variables/windows.yml + + steps: + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/set-version-vars.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + + - template: ../templates/prepare-build-env.yml + parameters: + platform: x64 + configuration: Release + buildEnvironment: ${{ parameters.buildEnvironment }} + + # Authenticate to NuGet feeds + - task: NuGetAuthenticate@1 + displayName: '🛡️ Authenticate NuGet' + + # Install Attack Surface Analyzer CLI tool + - task: PowerShell@2 + displayName: '🛡️ Install Attack Surface Analyzer' + inputs: + errorActionPreference: 'continue' + targetType: inline + script: | + Write-Host "Installing Attack Surface Analyzer (ASA) CLI tool..." + # Install from public NuGet.org feed + dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI --add-source https://api.nuget.org/v3/index.json + + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to install ASA tool" + exit 1 + } + + # Take "before" snapshot of the system + - task: PowerShell@2 + displayName: '🛡️ ASA - Collect Before Snapshot' + inputs: + targetType: inline + script: | + # Refresh environment PATH to include .NET global tools + $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") + + Write-Host "Taking 'before' snapshot of system state..." + # Collect files (-f), registry (-r), services (-s), ports (-p), certificates (-c) + asa collect -f -r -s -p -c --directories "$(Build.SourcesDirectory)" --verbose + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to collect 'before' snapshot" + exit 1 + } + Write-Host "✅ Before snapshot collected successfully" + + # Build the React Native Windows solution + # This simulates the "installation" that ASA will analyze + - template: ../templates/msbuild-sln.yml + parameters: + solutionDir: vnext + solutionName: Microsoft.ReactNative.NewArch.sln + buildPlatform: x64 + buildConfiguration: Release + + # Optional: Build NuGet packages if needed + # This step simulates package creation which could modify system state + - task: PowerShell@2 + displayName: '🛡️ ASA - Simulate Package Installation' + inputs: + targetType: inline + script: | + Write-Host "Simulating package installation for ASA analysis..." + Write-Host "Build artifacts are in place for analysis" + # Note: Actual NuGet package installation would go here if needed + # For now, we're analyzing the build process itself + + # Take "after" snapshot of the system + - task: PowerShell@2 + displayName: '🛡️ ASA - Collect After Snapshot' + inputs: + targetType: inline + script: | + # Refresh environment PATH to include .NET global tools + $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") + + Write-Host "Taking 'after' snapshot of system state..." + # Collect files (-f), registry (-r), services (-s), ports (-p), certificates (-c) + asa collect -f -r -s -p -c --directories "$(Build.SourcesDirectory)" --verbose + if ($LASTEXITCODE -ne 0) { + Write-Error "Failed to collect 'after' snapshot" + exit 1 + } + Write-Host "✅ After snapshot collected successfully" + + # Export comparison results + - task: PowerShell@2 + displayName: '🛡️ ASA - Export Comparison Results' + inputs: + targetType: inline + errorActionPreference: 'continue' + script: | + # Refresh environment PATH to include .NET global tools + $env:PATH = [System.Environment]::GetEnvironmentVariable("PATH", "Machine") + ";" + [System.Environment]::GetEnvironmentVariable("PATH", "User") + + Write-Host "Comparing before and after snapshots..." + + # Create output directory for results + $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" + New-Item -ItemType Directory -Force -Path $resultsDir | Out-Null + + Write-Host "Generating comparison results (SARIF and JSON)..." + + # Export as SARIF format for security analysis + $sarifOutput = "$resultsDir\asa-comparison.sarif" + Write-Host "Attempting SARIF export to: $sarifOutput" + try { + asa export-collect --outputsarif "$sarifOutput" --verbose + if ($LASTEXITCODE -eq 0) { + Write-Host "✅ SARIF export successful" + } else { + Write-Warning "SARIF export command returned exit code: $LASTEXITCODE" + } + } catch { + Write-Warning "SARIF export failed with exception: $($_.Exception.Message)" + } + + # Export as JSON format for additional analysis + $jsonOutput = "$resultsDir\asa-comparison.json" + Write-Host "Attempting JSON export to: $jsonOutput" + try { + asa export-collect "$jsonOutput" --verbose + if ($LASTEXITCODE -eq 0) { + Write-Host "✅ JSON export successful" + } else { + Write-Warning "JSON export command returned exit code: $LASTEXITCODE" + } + } catch { + Write-Warning "JSON export failed with exception: $($_.Exception.Message)" + } + + # ENHANCED: Check for actual generated files and copy them if needed + Write-Host "Checking for generated ASA files..." + + # Initialize arrays to collect files + $generatedFiles = @() + + # Check multiple possible locations where ASA might write files + $searchLocations = @( + "$(Build.SourcesDirectory)", # Source directory + (Get-Location).Path, # Current working directory + $env:TEMP, # Temp directory + $env:USERPROFILE # User profile directory + ) + + Write-Host "Searching for ASA output files in multiple locations..." + + foreach ($location in $searchLocations) { + if (Test-Path $location) { + Write-Host "Checking: $location" + + # Look for timestamp-based output files that ASA actually generates + $locationFiles = @() + $locationFiles += Get-ChildItem -Path $location -Filter "*summary*.json*" -ErrorAction SilentlyContinue + $locationFiles += Get-ChildItem -Path $location -Filter "*vs_*.json*" -ErrorAction SilentlyContinue + $locationFiles += Get-ChildItem -Path $location -Filter "*.sarif" -ErrorAction SilentlyContinue + + if ($locationFiles.Count -gt 0) { + Write-Host " Found $($locationFiles.Count) files in $location" + $generatedFiles += $locationFiles + } + } + } + + if ($generatedFiles.Count -gt 0) { + Write-Host "Found $($generatedFiles.Count) ASA output files total:" + foreach ($file in $generatedFiles) { + Write-Host " - $($file.FullName)" + + # Copy to results directory with descriptive names + $destinationName = $file.Name + if ($file.Name -like "*summary*" -or $file.Name -like "*vs_*") { + $destinationName = "asa-comparison-$($file.Name)" + } + + try { + Copy-Item $file.FullName "$resultsDir\$destinationName" -Force + Write-Host " ✅ Copied to: $destinationName" + } catch { + Write-Warning " ❌ Failed to copy $($file.Name): $($_.Exception.Message)" + } + } + } else { + Write-Warning "No ASA output files found in any search locations" + Write-Host "Performing recursive search for ASA-related files..." + + # Try a broader recursive search from current directory + try { + $allFiles = Get-ChildItem -Recurse -File -ErrorAction SilentlyContinue | + Where-Object { + $_.Name -like "*asa*" -or + $_.Name -like "*summary*" -or + $_.Name -like "*vs_*" -or + $_.Name -like "*attack*" -or + $_.Extension -eq ".sarif" + } | + Select-Object -First 20 + + if ($allFiles -and $allFiles.Count -gt 0) { + Write-Host "Found potential ASA files through recursive search:" + foreach ($file in $allFiles) { + Write-Host " - $($file.FullName)" + } + } else { + Write-Host "No ASA-related files found through recursive search" + } + } catch { + Write-Warning "Recursive search failed: $($_.Exception.Message)" + } + } + + Write-Host "✅ Export process completed" + Write-Host "Results location: $resultsDir" + + # Ensure we don't exit with error code even if file discovery had issues + exit 0 + + # Analyze results and check for security issues + - task: PowerShell@2 + displayName: '🛡️ ASA - Analyze Results' + inputs: + targetType: inline + script: | + Write-Host "Analyzing ASA results for security regressions..." + + $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" + + Write-Host "📊 ASA Analysis Summary:" + Write-Host "========================" + + # Look for any ASA result files in the results directory + $allResultFiles = Get-ChildItem -Path $resultsDir -File -ErrorAction SilentlyContinue + + if ($allResultFiles.Count -eq 0) { + Write-Warning "No result files found in $resultsDir" + return + } + + Write-Host "Found $($allResultFiles.Count) result files:" + foreach ($file in $allResultFiles) { + Write-Host " - $($file.Name) ($([math]::Round($file.Length/1KB, 2)) KB)" + } + + # Analyze SARIF files if present + $sarifFiles = $allResultFiles | Where-Object { $_.Extension -eq ".sarif" } + foreach ($sarifFile in $sarifFiles) { + Write-Host "" + Write-Host "📋 SARIF Analysis: $($sarifFile.Name)" + try { + $sarifContent = Get-Content $sarifFile.FullName | ConvertFrom-Json + $findingCount = 0 + + if ($sarifContent.runs -and $sarifContent.runs.Count -gt 0) { + foreach ($run in $sarifContent.runs) { + if ($run.results) { + $findingCount += $run.results.Count + Write-Host " Tool: $($run.tool.driver.name)" + Write-Host " Findings: $($run.results.Count)" + } + } + } + + if ($findingCount -gt 0) { + Write-Host "⚠️ $findingCount security findings detected in SARIF report" + } else { + Write-Host "✅ No security findings in SARIF report" + } + } catch { + Write-Host "⚠️ Could not parse SARIF file: $($_.Exception.Message)" + } + } + + # Analyze JSON files + $jsonFiles = $allResultFiles | Where-Object { $_.Extension -eq ".json" -or $_.Name -like "*.json*" } + foreach ($jsonFile in $jsonFiles) { + Write-Host "" + Write-Host "📋 JSON Analysis: $($jsonFile.Name)" + try { + $content = Get-Content $jsonFile.FullName -Raw + if ($content.Length -gt 0) { + Write-Host " File size: $([math]::Round($jsonFile.Length/1KB, 2)) KB" + Write-Host " Content preview:" + # Show first few lines of content + $lines = $content -split "`n" | Select-Object -First 5 + foreach ($line in $lines) { + if ($line.Trim()) { + Write-Host " $($line.Trim())" + } + } + } + } catch { + Write-Host "⚠️ Could not analyze JSON file: $($_.Exception.Message)" + } + } + + Write-Host "" + Write-Host "🛡️ SDL Compliance Notes:" + Write-Host "- Review all result files for security vulnerabilities" + Write-Host "- Pay attention to privilege escalations" + Write-Host "- Check for unsigned binaries or weak permissions" + Write-Host "- Verify certificate and firewall changes" + Write-Host "" + Write-Host "Results available in build artifacts: ASA_Results" + Write-Host "ASA analysis completed" + continueOnError: ${{ parameters.complianceWarnOnly }} + + # Publish ASA results as build artifact + - task: PublishBuildArtifacts@1 + displayName: '🛡️ Publish ASA Results' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)\ASA_Results' + ArtifactName: 'ASA_Results' + publishLocation: 'Container' + condition: always() + + # Generate summary for PR + - task: PowerShell@2 + displayName: '🛡️ ASA - Generate PR Summary' + inputs: + targetType: inline + script: | + Write-Host "Generating ASA summary for PR..." + + $resultsDir = "$(Build.ArtifactStagingDirectory)\ASA_Results" + + # Check if results directory exists and has files + if (Test-Path $resultsDir) { + $resultFiles = Get-ChildItem -Path $resultsDir -File -ErrorAction SilentlyContinue + + if ($resultFiles.Count -gt 0) { + Write-Host "Found $($resultFiles.Count) ASA result files:" + + # Look for SARIF files to upload as summary + $sarifFiles = $resultFiles | Where-Object { $_.Extension -eq ".sarif" } + foreach ($sarifFile in $sarifFiles) { + Write-Host "Uploading SARIF summary: $($sarifFile.Name)" + Write-Host "##vso[task.uploadsummary]$($sarifFile.FullName)" + } + + # List all result files + foreach ($file in $resultFiles) { + Write-Host " - $($file.Name) ($([math]::Round($file.Length/1KB, 2)) KB)" + } + + Write-Host "✅ ASA results will be available in PR artifacts" + } else { + Write-Host "⚠️ Results directory exists but contains no files" + } + } else { + Write-Host "⚠️ Results directory not found: $resultsDir" + } + + # Additional logging for troubleshooting + Write-Host "" + Write-Host "🔍 Troubleshooting Info:" + Write-Host "Build.SourcesDirectory: $(Build.SourcesDirectory)" + Write-Host "Build.ArtifactStagingDirectory: $(Build.ArtifactStagingDirectory)" + + # Look for any ASA files in the source directory + $sourceFiles = Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*asa*" -ErrorAction SilentlyContinue + $sourceFiles += Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*summary*" -ErrorAction SilentlyContinue + $sourceFiles += Get-ChildItem -Path "$(Build.SourcesDirectory)" -Filter "*vs_*" -ErrorAction SilentlyContinue + + if ($sourceFiles.Count -gt 0) { + Write-Host "ASA files found in source directory:" + foreach ($file in $sourceFiles) { + Write-Host " - $($file.FullName)" + } + } + condition: always() diff --git a/.ado/jobs/cli-init-windows.yml b/.ado/jobs/cli-init-windows.yml index c32021c8ab8..f124b154578 100644 --- a/.ado/jobs/cli-init-windows.yml +++ b/.ado/jobs/cli-init-windows.yml @@ -4,9 +4,13 @@ parameters: default: PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: AgentPool type: object + - name: buildNuGetOnly + type: boolean + default: false - name: buildMatrix type: object default: @@ -17,31 +21,48 @@ parameters: configuration: Release platform: x64 additionalRunArguments: --no-autolink + useNuGet: true + publishNuGet: true - Name: FabricX86Debug template: cpp-app configuration: Debug platform: x86 additionalRunArguments: --no-autolink + useNuGet: true - Name: FabricArm64Release template: cpp-app configuration: Release platform: ARM64 additionalRunArguments: --no-autolink --no-deploy + useNuGet: true - Name: FabricLibX64Release template: cpp-lib configuration: Release platform: x64 additionalRunArguments: + useNuGet: true - Name: FabricLibX86Debug template: cpp-lib configuration: Debug platform: x86 additionalRunArguments: + useNuGet: true - Name: FabricLibArm64Release template: cpp-lib configuration: Release platform: ARM64 additionalRunArguments: --no-deploy + useNuGet: true + - BuildEnvironment: SecurePullRequest + Matrix: + - Name: FabricX64Release + template: cpp-app + configuration: Release + platform: x64 + additionalRunArguments: --no-autolink + useNuGet: true + publishNuGet: true + useExperimentalWinUI3: true - BuildEnvironment: Continuous Matrix: - Name: FabricX64Debug @@ -49,108 +70,117 @@ parameters: configuration: Debug platform: x64 additionalRunArguments: --no-autolink + useNuGet: true - Name: FabricX64Release template: cpp-app configuration: Release platform: x64 additionalRunArguments: --no-autolink + useNuGet: true + publishNuGet: true - Name: FabricX86Debug template: cpp-app configuration: Debug platform: x86 additionalRunArguments: --no-autolink + useNuGet: true - Name: FabricX86Release template: cpp-app configuration: Release platform: x86 + useNuGet: true + publishNuGet: true - Name: FabricArm64Debug template: cpp-app configuration: Debug platform: ARM64 additionalRunArguments: --no-autolink --no-deploy + useNuGet: true - Name: FabricArm64Release template: cpp-app configuration: Release platform: ARM64 additionalRunArguments: --no-autolink --no-deploy + useNuGet: true + publishNuGet: true - Name: FabricLibX64Debug template: cpp-lib configuration: Debug platform: x64 additionalRunArguments: + useNuGet: true - Name: FabricLibX64Release template: cpp-lib configuration: Release platform: x64 additionalRunArguments: + useNuGet: true - Name: FabricLibX86Debug template: cpp-lib configuration: Debug platform: x86 additionalRunArguments: + useNuGet: true - Name: FabricLibX86Release template: cpp-lib configuration: Release platform: x86 additionalRunArguments: + useNuGet: true - Name: FabricLibArm64Debug template: cpp-lib configuration: Debug platform: ARM64 additionalRunArguments: --no-deploy + useNuGet: true - Name: FabricLibArm64Release template: cpp-lib configuration: Release platform: ARM64 additionalRunArguments: --no-deploy + useNuGet: true jobs: - ${{ each config in parameters.buildMatrix }}: - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: - ${{ each matrix in config.Matrix }}: - - job: CliInitWindows${{ matrix.Name }} - displayName: Verify CliInitWindows ${{ matrix.Name }} - - # No job-level dependsOn needed — the CLI stage depends on the Build stage, - # which guarantees all build artifacts are available before CLI jobs start. - - variables: [template: ../variables/windows.yml] + - ${{ if eq(coalesce(matrix.useNuGet, false), parameters.buildNuGetOnly) }}: + - job: CliInitWindows${{ matrix.Name }} + displayName: Verify CliInitWindows ${{ matrix.Name }} - pool: ${{ parameters.AgentPool.Medium }} - timeoutInMinutes: 40 - cancelTimeoutInMinutes: 5 + ${{ if eq(matrix.useNuGet, true) }}: + dependsOn: + - UniversalBuild${{ matrix.platform }}ReleaseFabric - steps: - - template: ../templates/checkout-full.yml - parameters: - persistCredentials: false # We don't need git creds in this job + variables: [template: ../variables/windows.yml] - - template: ../templates/prepare-js-env.yml + ${{ if eq(matrix.lowResource, true) }}: + pool: ${{ parameters.AgentPool.Small }} + ${{ else }}: + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 40 + cancelTimeoutInMinutes: 5 - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ parameters.platform }} - configuration: ${{ parameters.configuration }} - buildEnvironment: ${{ parameters.buildEnvironment }} + steps: + - template: ../templates/checkout-full.yml + parameters: + persistCredentials: false # We don't need git creds in this job - - template: ../templates/react-native-init-windows.yml - parameters: - template: ${{ matrix.template }} - configuration: ${{ matrix.configuration }} - platform: ${{ matrix.platform }} - additionalInitArguments: ${{ matrix.additionalInitArguments }} - additionalRunArguments: ${{ matrix.additionalRunArguments }} - buildEnvironment: ${{ parameters.buildEnvironment }} - useExperimentalWinUI3: ${{ coalesce(matrix.useExperimentalWinUI3, false) }} + - template: ../templates/prepare-js-env.yml - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Upload traces' - condition: succeededOrFailed() - targetPath: $(Build.StagingDirectory)/Tracing - artifactName: Traces - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload Verdaccio.log (on failure)' - condition: failed() - targetPath: verdaccio.log - artifactName: $(Agent.JobName).Verdaccio.log-$(System.JobAttempt) + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ parameters.platform }} + configuration: ${{ parameters.configuration }} + buildEnvironment: ${{ parameters.buildEnvironment }} + + - template: ../templates/react-native-init-windows.yml + parameters: + template: ${{ matrix.template }} + configuration: ${{ matrix.configuration }} + platform: ${{ matrix.platform }} + additionalInitArguments: ${{ matrix.additionalInitArguments }} + additionalRunArguments: ${{ matrix.additionalRunArguments }} + buildEnvironment: ${{ parameters.buildEnvironment }} + useNuGet: ${{ coalesce(matrix.useNuGet, false) }} + useExperimentalWinUI3: ${{ coalesce(matrix.useExperimentalWinUI3, false) }} + publishNuGet: ${{ coalesce(matrix.publishNuGet, false) }} diff --git a/.ado/jobs/desktop-single.yml b/.ado/jobs/desktop-single.yml deleted file mode 100644 index b9cdcb19fab..00000000000 --- a/.ado/jobs/desktop-single.yml +++ /dev/null @@ -1,216 +0,0 @@ -# -# Step template: Build and test one Desktop platform/config combination. -# Called from build-template.yml which owns the job definition, artifact -# publishing, ESRP signing, and templateContext. -# -# Note: Desktop test filter variables (Desktop.IntegrationTests.Filter, -# Desktop.UnitTests.Filter, Desktop.IntegrationTests.SkipRNTester) must -# be defined at the job level in build-template.yml. -# - -parameters: - - name: buildPlatform - type: string - - name: buildConfiguration - type: string - - name: buildEnvironment - type: string - default: PullRequest - values: - - PullRequest - - Continuous - -steps: - # Set up IIS for integration tests - - pwsh: | - Install-WindowsFeature -Name Web-Server, Web-Scripting-Tools - displayName: Install IIS - - - pwsh: | - Invoke-WebRequest ` - -Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' ` - -OutFile dotnet-hosting-8.0.3-win.exe - - Write-Host 'Installing .NET hosting bundle' - Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' - Write-Host 'Installed .NET hosting bundle' - - Invoke-WebRequest ` - -Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' ` - -OutFile dotnet-sdk-7.0.407-win-x64.exe - - Write-Host 'Installing .NET 7 SDK' - Start-Process -Wait -FilePath .\dotnet-sdk-7.0.407-win-x64.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' - Write-Host 'Installed .NET 7 SDK' - displayName: Install the .NET Core Hosting Bundle - - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ parameters.buildPlatform }} - configuration: ${{ parameters.buildConfiguration }} - buildEnvironment: ${{ parameters.buildEnvironment }} - - - template: ../templates/apply-published-version-vars.yml - - - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: - - pwsh: | - $newValue = '(FullyQualifiedName!~RNTesterIntegrationTests::)&' + "$(Desktop.IntegrationTests.Filter)" - Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" - displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests - - - template: ../templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: ReactWindows-Desktop.sln - buildPlatform: ${{ parameters.buildPlatform }} - buildConfiguration: ${{ parameters.buildConfiguration }} - oneESMode: true - msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props - - - ${{ if and(eq(parameters.buildConfiguration, 'Debug'), eq(parameters.buildPlatform, 'x64')) }}: - - script: yarn bundle - displayName: Build react-native-win32 RNTester bundle - workingDirectory: packages/@office-iss/react-native-win32 - - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - template: ../templates/component-governance.yml - - # --- Tests --- - - template: ../templates/discover-google-test-adapter.yml - - - ${{ if ne(parameters.buildPlatform, 'ARM64EC') }}: - - task: VSTest@2 - displayName: Run Desktop Unit Tests - timeoutInMinutes: 5 # Set smaller timeout, due to hangs - inputs: - testSelector: testAssemblies - testAssemblyVer2: | - React.Windows.Desktop.UnitTests/React.Windows.Desktop.UnitTests.dll - # Bug #8000: Tracks fixing the tests - # ReactCommon.UnitTests/ReactCommon.UnitTests.exe - pathtoCustomTestAdapters: $(GoogleTestAdapterPath) - searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ parameters.buildPlatform }}/${{ parameters.buildConfiguration }} - testFiltercriteria: $(Desktop.UnitTests.Filter) - runTestsInIsolation: true - platform: ${{ parameters.buildPlatform }} - configuration: ${{ parameters.buildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - - # Suspected debug assert in TestRunner hanging tests randomly. Run only on Release for now. - - ${{ if and(eq(parameters.buildConfiguration, 'Release'), ne(variables['Desktop.IntegrationTests.SkipRNTester'], true), ne(parameters.buildPlatform, 'ARM64EC')) }}: - - task: PowerShell@2 - displayName: Set up test servers - inputs: - targetType: filePath - filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tfs\Start-TestServers.ps1 - arguments: -SourcesDirectory $(Build.SourcesDirectory)\vnext -Preload -SleepSeconds 120 - pwsh: true - - - task: DotNetCoreCLI@2 - displayName: Publish Test Website - inputs: - command: publish - publishWebProjects: false - zipAfterPublish: false - projects: $(Build.SourcesDirectory)\vnext\TestWebsite\Microsoft.ReactNative.Test.Website.csproj - arguments: --configuration ${{ parameters.buildConfiguration }} - - - pwsh: | - # Create and make available to IIS - $cert = New-SelfSignedCertificate ` - -Type SSLServerAuthentication ` - -KeyExportPolicy Exportable ` - -Subject 'CN=localhost' ` - -NotAfter ([DateTime]::Now).AddHours(2) ` - -CertStoreLocation Cert:\LocalMachine\My\ - - $certPath = "${env:TEMP}\localhost.pfx" - $certPass = -join ('a'..'z' | Get-Random -Count 32) | ConvertTo-SecureString -AsPlainText -Force - $certHash = $cert.Thumbprint - Write-Host "##vso[task.setvariable variable=TestWebsiteCertificateThumbprint]$certHash" - - # Export PFX - $cert | Export-PfxCertificate -FilePath $certPath -Password $certPass - - # Trust globally - Import-PfxCertificate ` - -Exportable ` - -FilePath $certPath ` - -Password $certPass ` - -CertStoreLocation Cert:\LocalMachine\Root\ - displayName: Install SSL Certificate - - - task: IISWebAppManagementOnMachineGroup@0 - displayName: Create Test Website - inputs: - EnableIIS: false - IISDeploymentType: IISWebsite - ActionIISWebsite: CreateOrUpdateWebsite - SSLCertThumbPrint: $(TestWebsiteCertificateThumbprint) - WebsiteName: RNW Test Website - # Hard-coding x64 for publish path. - # Our MSBuild customizations cause dotnet commands to assume the default PlatformTarget - WebsitePhysicalPath: $(Build.SourcesDirectory)\vnext\target\x64\${{ parameters.buildConfiguration }}\Microsoft.ReactNative.Test.Website\Publish - WebsitePhysicalPathAuth: WebsiteUserPassThrough - CreateOrUpdateAppPoolForWebsite: false - ConfigureAuthenticationForWebsite: false - AppPoolNameForWebsite: DefaultAppPool - AddBinding: true - Bindings: | - { - bindings: [ - { - "protocol": "http", - "ipAddress": "*", - "port": "5555", - "sslThumbprint": "", - "sniFlag": false - }, - { - "protocol": "https", - "ipAddress": "*", - "port": "5543", - "sslThumbprint": "$(TestWebsiteCertificateThumbprint)", - "sniFlag": false - } - ] - } - - - task: PowerShell@2 - displayName: Ensure servers readiness - inputs: - targetType: 'inline' - pwsh: true - script: | - # Test website - Invoke-WebRequest -UseBasicParsing -Uri 'http://localhost:5555' - Invoke-WebRequest -UseBasicParsing -Uri 'https://localhost:5543' - - # Bundler - Invoke-WebRequest -UseBasicParsing -Uri "http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=windows&dev=true" - continueOnError: true - - - task: VSTest@2 - displayName: Run Desktop Integration Tests - inputs: - testSelector: testAssemblies - testAssemblyVer2: React.Windows.Desktop.IntegrationTests\React.Windows.Desktop.IntegrationTests.dll - searchFolder: $(Build.SourcesDirectory)\vnext\target\${{ parameters.buildPlatform }}\${{ parameters.buildConfiguration }} - testFiltercriteria: $(Desktop.IntegrationTests.Filter) - runTestsInIsolation: true - platform: ${{ parameters.buildPlatform }} - configuration: ${{ parameters.buildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - otherConsoleOptions: '/blame -- RunConfiguration.TestSessionTimeout=300000' - - - template: ../templates/stop-packagers.yml diff --git a/.ado/jobs/desktop.yml b/.ado/jobs/desktop.yml new file mode 100644 index 00000000000..f671faeb269 --- /dev/null +++ b/.ado/jobs/desktop.yml @@ -0,0 +1,340 @@ +parameters: + - name: buildEnvironment + type: string + default : PullRequest + values: + - PullRequest + - SecurePullRequest + - Continuous + + - name: AgentPool + type: object + + - name: buildMatrix + type: object + default: + - BuildEnvironment: PullRequest + Matrix: + - Name: X64Debug + BuildConfiguration: Debug + BuildPlatform: x64 + - Name: X64Release + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86Debug + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: ARM64ECDebug + BuildConfiguration: Debug + BuildPlatform: ARM64EC + - Name: ARM64ECRelease + BuildConfiguration: Release + BuildPlatform: ARM64EC + - BuildEnvironment: SecurePullRequest + Matrix: + - Name: X64Debug + BuildConfiguration: Debug + BuildPlatform: x64 + UseExperimentalWinUI3: true + - BuildEnvironment: Continuous + Matrix: + - Name: X64Debug + BuildConfiguration: Debug + BuildPlatform: x64 + - Name: X64Release + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86Debug + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: X86Release + BuildConfiguration: Release + BuildPlatform: x86 + - Name: ARM64ECDebug + BuildConfiguration: Debug + BuildPlatform: ARM64EC + - Name: ARM64ECRelease + BuildConfiguration: Release + BuildPlatform: ARM64EC + +jobs: + - ${{ each config in parameters.buildMatrix }}: + - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: + - ${{ each matrix in config.Matrix }}: + - job: Desktop${{ matrix.Name }} + displayName: Desktop ${{ matrix.Name }} + + variables: + - template: ../variables/windows.yml + + # Enable if any issues RNTesterIntegrationTests::* become unstable. + - name: Desktop.IntegrationTests.SkipRNTester + value: false + + #5059 - Disable failing or intermittent tests (IntegrationTestHarness,WebSocket,Logging). + #10732 - WebSocketIntegrationTest::SendReceiveSsl fails on Windows Server 2022. + #12714 - Disable for first deployment of test website. + # RNTesterIntegrationTests::WebSocket + # RNTesterIntegrationTests::WebSocketBlob + # RNTesterIntegrationTests::WebSocketMultipleSend + #14217 - Reneable RNTesterIntegrationTests + # RNTesterIntegrationTests::Dummy + # RNTesterIntegrationTests::Fetch + # RNTesterIntegrationTests::XHRSample + # RNTesterIntegrationTests::Blob + # RNTesterIntegrationTests::Logging + # - CI agents show the following server-side errors (local runs succeed): + # - [0x801901f4] Internal server error (500). + # - [0x800710dd] The operation identifier is not valid. + # WebSocketIntegrationTest::ConnectClose)& + # WebSocketIntegrationTest::ConnectNoClose)& + # WebSocketIntegrationTest::SendReceiveClose)& + # WebSocketIntegrationTest::SendConsecutive)& + # WebSocketIntegrationTest::SendReceiveLargeMessage)& + # WebSocketIntegrationTest::SendReceiveSsl)& + - name: Desktop.IntegrationTests.Filter + value: > + (FullyQualifiedName!=RNTesterIntegrationTests::IntegrationTestHarness)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocket)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketBlob)& + (FullyQualifiedName!=RNTesterIntegrationTests::WebSocketMultipleSend)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::ConnectNoClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveClose)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendConsecutive)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveLargeMessage)& + (FullyQualifiedName!=Microsoft::React::Test::WebSocketIntegrationTest::SendReceiveSsl)& + (FullyQualifiedName!=Microsoft::React::Test::HttpOriginPolicyIntegrationTest)& + (FullyQualifiedName!=RNTesterIntegrationTests::Dummy)& + (FullyQualifiedName!=RNTesterIntegrationTests::Fetch)& + (FullyQualifiedName!=RNTesterIntegrationTests::XHRSample)& + (FullyQualifiedName!=RNTesterIntegrationTests::Blob)& + (FullyQualifiedName!=RNTesterIntegrationTests::Logging) + #6799 - + # HostFunctionTest - Crashes under JSI/V8 + # HostObjectProtoTest - Crashes under JSI/V8 + # PreparedJavaScriptSourceTest - Asserts/Fails under JSI/ChakraCore + - name: Desktop.UnitTests.Filter + value: > + (FullyQualifiedName!~HostFunctionTest)& + (FullyQualifiedName!~HostObjectProtoTest)& + (FullyQualifiedName!~PreparedJavaScriptSourceTest) + + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 80 # how long to run the job before automatically cancelling - Issue 13442 + cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them + + steps: + # Set up IIS tests { + - pwsh: | + Install-WindowsFeature -Name Web-Server, Web-Scripting-Tools + displayName: Install IIS + + - pwsh: | + Invoke-WebRequest ` + -Uri 'https://download.visualstudio.microsoft.com/download/pr/20598243-c38f-4538-b2aa-af33bc232f80/ea9b2ca232f59a6fdc84b7a31da88464/dotnet-hosting-8.0.3-win.exe' ` + -OutFile dotnet-hosting-8.0.3-win.exe + + Write-Host 'Installing .NET hosting bundle' + Start-Process -Wait -FilePath .\dotnet-hosting-8.0.3-win.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' + Write-Host 'Installed .NET hosting bundle' + + Invoke-WebRequest ` + -Uri 'https://download.visualstudio.microsoft.com/download/pr/f2ec926e-0d98-4a8b-8c70-722ccc2ca0e5/b59941b0c60f16421679baafdb7e9338/dotnet-sdk-7.0.407-win-x64.exe' ` + -OutFile dotnet-sdk-7.0.407-win-x64.exe + + Write-Host 'Installing .NET 7 SDK' + Start-Process -Wait -FilePath .\dotnet-sdk-7.0.407-win-x64.exe -ArgumentList '/INSTALL', '/QUIET', '/NORESTART' + Write-Host 'Installed .NET 7 SDK' + displayName: Install the .NET Core Hosting Bundle + + # } Set up IIS tests + + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + buildEnvironment: ${{ config.BuildEnvironment }} + + - template: ../templates/apply-published-version-vars.yml + + - ${{ if eq(variables['Desktop.IntegrationTests.SkipRNTester'], true) }}: + - powershell: | + $newValue = '(FullyQualifiedName!~RNTesterIntegrationTests::)&' + "$(Desktop.IntegrationTests.Filter)" + Write-Host "##vso[task.setvariable variable=Desktop.IntegrationTests.Filter]$newValue" + displayName: Update Desktop.IntegrationTests.Filter to exclude RNTester integration tests + + - ${{ if eq(matrix.UseExperimentalWinUI3, true) }}: + - template: ../templates/enable-experimental-winui3.yml + parameters: + workingDir: vnext + + - template: ../templates/msbuild-sln.yml + parameters: + solutionDir: vnext + solutionName: ReactWindows-Desktop.sln + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + + - ${{ if and(eq(matrix.BuildConfiguration, 'Debug'), eq(matrix.BuildPlatform, 'x64')) }}: + - script: yarn bundle + displayName: Build react-native-win32 RNTester bundle + workingDirectory: packages/@office-iss/react-native-win32 + + - ${{ if eq(config.BuildEnvironment, 'Continuous') }}: + - template: ../templates/component-governance.yml + + - template: ../templates/discover-google-test-adapter.yml + + - ${{ if ne(matrix.BuildPlatform, 'ARM64EC') }}: + - task: VSTest@2 + displayName: Run Desktop Unit Tests + timeoutInMinutes: 5 # Set smaller timeout , due to hangs + inputs: + testSelector: testAssemblies + testAssemblyVer2: | + React.Windows.Desktop.UnitTests/React.Windows.Desktop.UnitTests.dll + # Bug #8000: Tracks fixing the tests + # ReactCommon.UnitTests/ReactCommon.UnitTests.exe + pathtoCustomTestAdapters: $(GoogleTestAdapterPath) + searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + testFiltercriteria: $(Desktop.UnitTests.Filter) + runTestsInIsolation: true + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + + # Suspected debug assert in TestRunner hanging tests randomly. Run only on Release for now. + - ${{ if and(eq(matrix.BuildConfiguration, 'Release'), ne(variables['Desktop.IntegrationTests.SkipRNTester'], true), ne(matrix.BuildPlatform, 'ARM64EC')) }}: + - task: PowerShell@2 + displayName: Set up test servers + inputs: + targetType: filePath # filePath | inline + filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tfs\Start-TestServers.ps1 + arguments: -SourcesDirectory $(Build.SourcesDirectory)\vnext -Preload -SleepSeconds 120 + + - task: DotNetCoreCLI@2 + displayName: Publish Test Website + inputs: + command: publish + publishWebProjects: false + zipAfterPublish: false + projects: $(Build.SourcesDirectory)\vnext\TestWebsite\Microsoft.ReactNative.Test.Website.csproj + arguments: --configuration ${{ matrix.BuildConfiguration }} + + - pwsh: | + # Create and make available to IIS + $cert = New-SelfSignedCertificate ` + -Type SSLServerAuthentication ` + -KeyExportPolicy Exportable ` + -Subject 'CN=localhost' ` + -NotAfter ([DateTime]::Now).AddHours(2) ` + -CertStoreLocation Cert:\LocalMachine\My\ + + $certPath = "${env:TEMP}\localhost.pfx" + $certPass = -join ('a'..'z' | Get-Random -Count 32) | ConvertTo-SecureString -AsPlainText -Force + $certHash = $cert.Thumbprint + Write-Host "##vso[task.setvariable variable=TestWebsiteCertificateThumbprint]$certHash" + + # Export PFX + $cert | Export-PfxCertificate -FilePath $certPath -Password $certPass + + # Trust globally + Import-PfxCertificate ` + -Exportable ` + -FilePath $certPath ` + -Password $certPass ` + -CertStoreLocation Cert:\LocalMachine\Root\ + displayName: Install SSL Certificate + + - task: IISWebAppManagementOnMachineGroup@0 + displayName: Create Test Website + inputs: + EnableIIS: false + IISDeploymentType: IISWebsite + ActionIISWebsite: CreateOrUpdateWebsite + SSLCertThumbPrint: $(TestWebsiteCertificateThumbprint) + # IIS Website + WebsiteName: RNW Test Website + # Hard-coding x64 for publish path. + # Our MSBuild customizations cause dotnet commans to assume the default PlatformTarget + WebsitePhysicalPath: $(Build.SourcesDirectory)\vnext\target\x64\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative.Test.Website\Publish + WebsitePhysicalPathAuth: WebsiteUserPassThrough + CreateOrUpdateAppPoolForWebsite: false + ConfigureAuthenticationForWebsite: false + # IIS Application pool + AppPoolNameForWebsite: DefaultAppPool + # IIS Bindings + # See https://stackoverflow.com/questions/60089756 + AddBinding: true + Bindings: | + { + bindings: [ + { + "protocol": "http", + "ipAddress": "*", + "port": "5555", + "sslThumbprint": "", + "sniFlag": false + }, + { + "protocol": "https", + "ipAddress": "*", + "port": "5543", + "sslThumbprint": "$(TestWebsiteCertificateThumbprint)", + "sniFlag": false + } + ] + } + + - task: PowerShell@2 + displayName: Ensure servers readiness + inputs: + targetType: 'inline' + script: | + # Test website + Invoke-WebRequest -Uri 'http://localhost:5555' + Invoke-WebRequest -Uri 'https://localhost:5543' + + # Bundler + Invoke-WebRequest -UseBasicParsing -Uri "http://localhost:8081/IntegrationTests/IntegrationTestsApp.bundle?platform=windows&dev=true" + continueOnError: true + + - task: VSTest@2 + displayName: Run Desktop Integration Tests + inputs: + testSelector: testAssemblies + testAssemblyVer2: React.Windows.Desktop.IntegrationTests\React.Windows.Desktop.IntegrationTests.dll + searchFolder: $(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }} + testFiltercriteria: $(Desktop.IntegrationTests.Filter) + runTestsInIsolation: true + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + otherConsoleOptions: '/blame -- RunConfiguration.TestSessionTimeout=300000' + + - template: ../templates/stop-packagers.yml + + - script: node .ado/scripts/build.js --binskim --platform ${{ matrix.BuildPlatform }} --configuration ${{ matrix.BuildConfiguration }} --target desktop + displayName: Run BinSkim Analysis + condition: eq('${{ matrix.BuildConfiguration }}', 'Release') + + - template: ../templates/publish-build-artifacts.yml + parameters: + artifactName: Desktop + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + React.Windows.Desktop\** + React.Windows.Desktop.DLL\** + React.Windows.Desktop.Test.DLL\** diff --git a/.ado/jobs/e2e-test.yml b/.ado/jobs/e2e-test.yml index 244f8911c44..6ea13a358fc 100644 --- a/.ado/jobs/e2e-test.yml +++ b/.ado/jobs/e2e-test.yml @@ -5,6 +5,7 @@ parameters: default : PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: AgentPool type: object @@ -45,7 +46,7 @@ jobs: configuration: Release buildEnvironment: ${{ config.buildEnvironment }} - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=BuildLogDirectory]$(Build.BinariesDirectory)\${{ matrix.BuildPlatform }}\BuildLogs" displayName: Set BuildLogDirectory @@ -54,7 +55,6 @@ jobs: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Start-Tracing.ps1 - pwsh: true - template: ../templates/run-windows-with-certificates.yml parameters: @@ -91,10 +91,15 @@ jobs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Stop-Tracing.ps1 arguments: -NoAnalysis -outputFolder $(Build.StagingDirectory)/Tracing - pwsh: true condition: true - # Publish tasks moved to templateContext.outputs (1ES compliance) + - task: PublishBuildArtifacts@1 + displayName: Upload traces + inputs: + pathtoPublish: '$(Build.StagingDirectory)/Tracing' + artifactName: 'Traces - $(Agent.JobName)-$(System.JobAttempt)' + condition: true + - task: CopyFiles@2 displayName: Copy Fabric snapshots inputs: @@ -111,35 +116,20 @@ jobs: contents: "**" condition: failed() + - task: PublishPipelineArtifact@1 + displayName: "Publish Artifact: RNTesterApp Fabric" + inputs: + artifactName: RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) + targetPath: $(Build.StagingDirectory)/RNTesterApp-Fabric + condition: failed() + + - task: PublishPipelineArtifact@1 + displayName: "Publish Artifact: Fabric Snapshots" + inputs: + artifactName: Snapshots - RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) + targetPath: $(Build.StagingDirectory)/snapshots-fabric + condition: failed() + - template: ../templates/upload-build-logs.yml parameters: - oneESMode: true buildLogDirectory: '$(BuildLogDirectory)' - - templateContext: - outputs: - - output: pipelineArtifact - displayName: 'Upload traces' - condition: true - targetPath: $(Build.StagingDirectory)/Tracing - artifactName: Traces - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: RNTesterApp Fabric' - condition: failed() - targetPath: $(Build.StagingDirectory)/RNTesterApp-Fabric - artifactName: RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Publish Artifact: Fabric Snapshots' - condition: failed() - targetPath: $(Build.StagingDirectory)/snapshots-fabric - artifactName: Snapshots - RNTesterApp-Fabric-${{ matrix.Name }}-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) diff --git a/.ado/jobs/linting.yml b/.ado/jobs/linting.yml index 7e4c85d8d67..612a0fe6738 100644 --- a/.ado/jobs/linting.yml +++ b/.ado/jobs/linting.yml @@ -4,6 +4,7 @@ parameters: default : PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: AgentPool @@ -20,6 +21,8 @@ jobs: - template: ../templates/prepare-js-env.yml + - template: ../templates/run-compliance-prebuild.yml + - script: yarn format:verify displayName: yarn format:verify diff --git a/.ado/jobs/node-tests.yml b/.ado/jobs/node-tests.yml index 8824f5504ba..ef1167cefa2 100644 --- a/.ado/jobs/node-tests.yml +++ b/.ado/jobs/node-tests.yml @@ -4,6 +4,7 @@ parameters: default : PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: AgentPool diff --git a/.ado/jobs/nuget-desktop.yml b/.ado/jobs/nuget-desktop.yml new file mode 100644 index 00000000000..e93822c4bfd --- /dev/null +++ b/.ado/jobs/nuget-desktop.yml @@ -0,0 +1,44 @@ +parameters: + - name: buildEnvironment + type: string + default : PullRequest + values: + - PullRequest + - SecurePullRequest + - Continuous + - name: AgentPool + type: object + +jobs: + - ${{if ne(parameters.buildEnvironment, 'SecurePullRequest')}}: + - job: PackDesktop + displayName: Pack Desktop NuGet Package + variables: [template: ../variables/windows.yml] + dependsOn: + - DesktopX64Release + - DesktopX86Debug + - DesktopARM64ECRelease + + pool: ${{ parameters.AgentPool.Small }} + timeoutInMinutes: 30 # how long to run the job before automatically cancelling + cancelTimeoutInMinutes: 5 # how much time to give 'run always even if cancelled tasks' before killing them + steps: + - template: ../templates/checkout-shallow.yml + + - template: ../templates/yarn-install.yml + + - task: NuGetToolInstaller@1 + inputs: + versionSpec: ">=5.8.0" + + - template: ../templates/prep-and-pack-nuget.yml + parameters: + artifactName: Desktop + packDesktop: true + slices: + - platform: x64 + configuration: Release + - platform: x86 + configuration: Debug + - platform: ARM64EC + configuration: Release diff --git a/.ado/jobs/playground.yml b/.ado/jobs/playground.yml index 188288f3366..7da2ba11f6f 100644 --- a/.ado/jobs/playground.yml +++ b/.ado/jobs/playground.yml @@ -4,6 +4,7 @@ parameters: default : PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: AgentPool type: object @@ -27,6 +28,23 @@ parameters: BuildConfiguration: Release BuildPlatform: x64 SolutionFile: Playground-Composition.sln + - BuildEnvironment: SecurePullRequest + Matrix: + - Name: X86DebugCompositionExperimentalWinUI3 + BuildConfiguration: Debug + BuildPlatform: x86 + SolutionFile: Playground-Composition.sln + UseExperimentalWinUI3: true + - Name: X64DebugCompositionExperimentalWinUI3 + BuildConfiguration: Debug + BuildPlatform: x64 + SolutionFile: Playground-Composition.sln + UseExperimentalWinUI3: true + - Name: X64ReleaseCompositionExperimentalWinUI3 + BuildConfiguration: Release + BuildPlatform: x64 + SolutionFile: Playground-Composition.sln + UseExperimentalWinUI3: true - BuildEnvironment: Continuous Matrix: - Name: X86DebugComposition @@ -99,19 +117,19 @@ jobs: # Execute debug feature tests (skip this step for the Win32 Playground app and for release builds) # Need to re-setup ProcDump a 2nd time for this to work correctly - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=CrashDumpRootPath]$(Build.StagingDirectory)\DebugTestCrashDumps" New-Item -Path '$(Build.StagingDirectory)\DebugTestCrashDumps' -ItemType Directory displayName: Set CrashDumpRootPath - - pwsh: | + - powershell: | & $(Build.SourcesDirectory)\.ado\scripts\RunProcDump.ps1 -ProcDumpArgs @("-mm", "-i", "$(CrashDumpRootPath)") -ProcDumpInstallPath "$(ProcDumpPath)" -Verbose displayName: Setup ProcDump as AeDebug # This step loose-file-deploys the UWP Playground app and uses it as an RNW host for a series # of debug feature tests. In the future, these tests should be performed against a host app # better suited for automated tests (probably the E2E test app). - - pwsh: | + - powershell: | $appRecipeToolPath = Join-Path (& "C:\Program Files (x86)\Microsoft Visual Studio\Installer\vswhere.exe" -latest -property installationPath) "Common7\IDE\DeployAppRecipe.exe" if (!(Test-Path $appRecipeToolPath)) { throw "can't find '$appRecipeToolPath'" } $platDirs = @{ "x64" = "x64\"; "x86" = ""} # map from ADO BuildPlatform arg to VS build output dir part @@ -125,7 +143,7 @@ jobs: displayName: Run Debug Feature Tests timeoutInMinutes: 5 - - pwsh: | + - powershell: | foreach ($logFile in (ls "$(Build.StagingDirectory)\DebugTestLogs\*.log")) { Write-Host "logFile: '$logFile'" Get-Content $logFile | ForEach-Object {Write-Host "##[debug]$_"} @@ -135,30 +153,16 @@ jobs: - template: ../templates/upload-build-logs.yml parameters: - oneESMode: true artifactName: 'DebugTest $(Agent.JobName)-$(System.JobAttempt)' buildLogDirectory: '$(Build.StagingDirectory)\DebugTestLogs' - ${{if eq(config.BuildEnvironment, 'Continuous')}}: - template: ../templates/cleanup-certificate.yml - # App Package publish moved to templateContext.outputs (1ES compliance) - - templateContext: - outputs: - ${{ if eq(matrix.UploadAppx, true) }}: - - output: pipelineArtifact - displayName: 'Upload App Package' + - task: PublishBuildArtifacts@1 + displayName: Upload App Package + inputs: + pathtoPublish: 'packages/playground/windows/AppPackages/playground' + artifactName: 'Playground-${{ matrix.Name }}-$(System.JobAttempt)' condition: succeededOrFailed() - targetPath: packages/playground/windows/AppPackages/playground - artifactName: Playground-${{ matrix.Name }}-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload build logs' - condition: succeededOrFailed() - targetPath: $(BuildLogDirectory) - artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) - - output: pipelineArtifact - displayName: 'Upload crash dumps' - condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) - targetPath: '$(CrashDumpRootPath)' - artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) diff --git a/.ado/jobs/setup.yml b/.ado/jobs/setup.yml new file mode 100644 index 00000000000..68afe1600e8 --- /dev/null +++ b/.ado/jobs/setup.yml @@ -0,0 +1,63 @@ +parameters: + - name: buildEnvironment + type: string + values: + - PullRequest + - SecurePullRequest + - Continuous + +jobs: + - job: Setup + displayName: Setup + timeoutInMinutes: 4 # Kill the job early to catch Beachball hangs + variables: [template: ../variables/shared.yml] + pool: {vmImage: ubuntu-latest} + + steps: + - task: UseNode@1 + inputs: + version: '22.14.0' + displayName: 'Use Node.js 22.14.0' + + - template: ../templates/checkout-full.yml + parameters: + persistCredentials: true # Git creds needed for beachball + + - powershell: gci env:/BUILD_* + displayName: Show build information + + - template: ../templates/compute-beachball-branch-name.yml + + - template: ../templates/strict-yarn-install.yml + parameters: + workspace: '@rnw-scripts/beachball-config' + + - script: npx lage build --scope @rnw-scripts/beachball-config --no-deps + displayName: Build @rnw-scripts/beachball-config + + - script: | + echo "System.PullRequest.SourceBranch = $(System.PullRequest.SourceBranch)" + echo "Build.SourceBranch = $(Build.SourceBranch)" + echo "Build.SourceBranchName = $(Build.SourceBranchName)" + displayName: Print branch variables + + - pwsh: | + npx beachball check --verbose 2>&1 | Tee-Object -Variable beachballOutput + $beachballErrors = $beachballOutput | Where-Object { $_ -match "ERROR: *"} + $beachballErrors | ForEach { Write-Host "##vso[task.logissue type=warning]POSSIBLE $_" } + displayName: Warn for possible invalid change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) + + - ${{ if endsWith(parameters.buildEnvironment, 'PullRequest') }}: + - script: npx beachball check --branch origin/$(BeachBallBranchName) --verbose --changehint "##vso[task.logissue type=error]Run \"yarn change\" from root of repo to generate a change file." + displayName: Check for change files + condition: not(startsWith(variables['System.PullRequest.SourceBranch'], 'prepare-release/')) + + - script: npx beachball bump --branch origin/$(BeachBallBranchName) --yes --verbose + displayName: beachball bump + + - template: ../templates/set-version-vars.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + + - template: ../templates/publish-version-vars.yml diff --git a/.ado/jobs/universal-single.yml b/.ado/jobs/universal-single.yml deleted file mode 100644 index 556f932a424..00000000000 --- a/.ado/jobs/universal-single.yml +++ /dev/null @@ -1,71 +0,0 @@ -# -# Step template: Build and test one Universal platform/config combination. -# Called from build-template.yml which owns the job definition, artifact -# publishing, ESRP signing, and templateContext. -# - -parameters: - - name: buildPlatform - type: string - - name: buildConfiguration - type: string - - name: buildEnvironment - type: string - default: PullRequest - values: - - PullRequest - - Continuous -steps: - - template: ../templates/checkout-shallow.yml - - - template: ../templates/prepare-js-env.yml - - - template: ../templates/prepare-build-env.yml - parameters: - platform: ${{ parameters.buildPlatform }} - configuration: ${{ parameters.buildConfiguration }} - buildEnvironment: ${{ parameters.buildEnvironment }} - - - template: ../templates/apply-published-version-vars.yml - - - template: ../templates/msbuild-sln.yml - parameters: - solutionDir: vnext - solutionName: Microsoft.ReactNative.NewArch.sln - buildPlatform: ${{ parameters.buildPlatform }} - buildConfiguration: ${{ parameters.buildConfiguration }} - oneESMode: true - msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props - - - ${{ if eq(parameters.buildEnvironment, 'Continuous') }}: - - template: ../templates/component-governance.yml - - - task: PowerShell@2 - displayName: Make AnyCPU Reference Assemblies - inputs: - filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 - arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target - pwsh: true - - # --- Tests (run against the just-built binaries, no download/restore needed) --- - - template: ../templates/discover-google-test-adapter.yml - - - task: VSTest@2 - displayName: Run Universal Unit Tests (Native) - timeoutInMinutes: 5 # Set smaller timeout, due to hangs - inputs: - testSelector: testAssemblies - testAssemblyVer2: | - Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.exe - Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.exe - Mso.UnitTests/Mso.UnitTests.exe - pathtoCustomTestAdapters: $(GoogleTestAdapterPath) - searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ parameters.buildPlatform }}/${{ parameters.buildConfiguration }} - runTestsInIsolation: true - platform: ${{ parameters.buildPlatform }} - configuration: ${{ parameters.buildConfiguration }} - publishRunAttachments: true - collectDumpOn: onAbortOnly - vsTestVersion: latest - failOnMinTestsNotRun: true - condition: and(succeeded(), not(eq('${{ parameters.buildPlatform }}', 'ARM64'))) diff --git a/.ado/jobs/universal.yml b/.ado/jobs/universal.yml new file mode 100644 index 00000000000..ad01ed13c67 --- /dev/null +++ b/.ado/jobs/universal.yml @@ -0,0 +1,217 @@ + parameters: + - name: buildEnvironment + type: string + default : PullRequest + values: + - PullRequest + - SecurePullRequest + - Continuous + - name: AgentPool + type: object + - name: buildMatrix + type: object + default: + - BuildEnvironment: PullRequest + Matrix: + - Name: X64DebugFabric + BuildConfiguration: Debug + BuildPlatform: x64 + CreateApiDocs: true + - Name: X64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric + BuildConfiguration: Release + BuildPlatform: x86 + - Name: Arm64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: ARM64 + - BuildEnvironment: SecurePullRequest + Matrix: + - Name: X64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x64 + UseExperimentalWinUI3: true + - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric + BuildConfiguration: Release + BuildPlatform: x86 + UseExperimentalWinUI3: true + - BuildEnvironment: Continuous + Matrix: + - Name: X64DebugFabric + BuildConfiguration: Debug + BuildPlatform: x64 + CreateApiDocs: true + - Name: X64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86DebugFabric + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: X86ReleaseFabric # Specifically built so binskim / tests get run on fabric + BuildConfiguration: Release + BuildPlatform: x86 + - Name: Arm64DebugFabric + BuildConfiguration: Debug + BuildPlatform: ARM64 + - Name: Arm64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: ARM64 + + jobs: + - ${{ each config in parameters.buildMatrix }}: + - ${{ if eq(config.BuildEnvironment, parameters.buildEnvironment) }}: + - ${{ each matrix in config.Matrix }}: + - job: UniversalBuild${{ matrix.Name }} + variables: + - template: ../variables/windows.yml + # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). + # Keep NuGet cache independent from the user directory. + - name: NUGET_PACKAGES + value: $(Agent.TempDirectory)/NuGetPackages + displayName: Universal Build ${{ matrix.Name }} + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 5 + + steps: + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + buildEnvironment: ${{ config.BuildEnvironment }} + + - template: ../templates/apply-published-version-vars.yml + + - ${{ if eq(matrix.UseExperimentalWinUI3, true) }}: + - template: ../templates/enable-experimental-winui3.yml + parameters: + workingDir: vnext + + - template: ../templates/msbuild-sln.yml + parameters: + solutionDir: vnext + solutionName: Microsoft.ReactNative.NewArch.sln + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + + - ${{ if eq(config.BuildEnvironment, 'Continuous') }}: + - template: ../templates/component-governance.yml + + - ${{ if eq(matrix.CreateApiDocs, true) }}: + - powershell: | + $winmd2md_url = "https://github.com/asklar/winmd2md/releases/download/v0.1.16/winmd2md.exe" + Invoke-WebRequest -UseBasicParsing $winmd2md_url -OutFile $env:TEMP\winmd2md.exe + & $env:TEMP\winmd2md.exe /experimental /outputDirectory vnext\target\winmd2md vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.winmd + displayName: "Generate WinRT API docs" + continueOnError: true + + - task: PublishBuildArtifacts@1 + displayName: Upload WinRT API docs + inputs: + pathtoPublish: 'vnext\target\winmd2md' + artifactName: 'WinRT API docs - $(Agent.JobName)-$(System.JobAttempt)' + + - task: PowerShell@2 + displayName: Make AnyCPU Reference Assemblies + inputs: + filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 + arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target + + - template: ../templates/publish-build-artifacts.yml + parameters: + artifactName: ReactWindows + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + Microsoft.ReactNative\** + Microsoft.ReactNative.Cxx.UnitTests\** + Microsoft.ReactNative.IntegrationTests\** + Microsoft.ReactNative.CsWinRT\** + Microsoft.ReactNative.Managed\** + Microsoft.ReactNative.Managed.CodeGen\** + Microsoft.ReactNative.Managed.CodeGen.UnitTests\** + Microsoft.ReactNative.Managed.UnitTests\** + Mso.UnitTests\** + + - job: UniversalTest${{ matrix.Name }} + variables: + - template: ../variables/windows.yml + # Some tasks run on a different user (VssAdministrator) instead of the default user (AzDevOps). + # Keep NuGet cache independent from the user directory. + - name: NUGET_PACKAGES + value: $(Agent.TempDirectory)/NuGetPackages + displayName: Universal Test ${{ matrix.Name }} + dependsOn: + - UniversalBuild${{ matrix.Name }} + + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 5 + + steps: + - template: ../templates/checkout-shallow.yml + + - template: ../templates/prepare-js-env.yml + + - template: ../templates/prepare-build-env.yml + parameters: + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + buildEnvironment: ${{ config.BuildEnvironment }} + + - task: PowerShell@2 + displayName: Check if this environment meets the development dependencies + inputs: + targetType: filePath + filePath: $(Build.SourcesDirectory)\vnext\Scripts\rnw-dependencies.ps1 + arguments: -NoPrompt -Tags buildLab + + - task: DownloadPipelineArtifact@1 + displayName: Download "ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}" + inputs: + targetPath: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} + + - script: node .ado/scripts/build.js --binskim --platform ${{ matrix.BuildPlatform }} --configuration ${{ matrix.BuildConfiguration }} --target universal + displayName: Run BinSkim Analysis + condition: and(succeeded(), eq('${{ matrix.BuildConfiguration }}', 'Release'), ne('${{ matrix.BuildPlatform }}', 'ARM64')) + + - template: ../templates/discover-google-test-adapter.yml + + - task: MSBuild@1 + displayName: Restore NuGet packages + # Should be kept in sync with UniversalBuild - template: ../templates/msbuild-sln.yml + inputs: + solution: vnext/Microsoft.ReactNative.NewArch.sln + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + msbuildArchitecture: x64 + msbuildArguments: + /restore + /p:RestoreLockedMode=true + /p:RestoreForceEvaluate=true + + - task: VSTest@2 + displayName: Run Universal Unit Tests (Native) + timeoutInMinutes: 5 # Set smaller timeout , due to hangs + inputs: + testSelector: testAssemblies + testAssemblyVer2: | + Microsoft.ReactNative.Cxx.UnitTests/Microsoft.ReactNative.Cxx.UnitTests.exe + Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.exe + Mso.UnitTests/Mso.UnitTests.exe + pathtoCustomTestAdapters: $(GoogleTestAdapterPath) + searchFolder: $(Build.SourcesDirectory)/vnext/target/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + runTestsInIsolation: true + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + publishRunAttachments: true + collectDumpOn: onAbortOnly + vsTestVersion: latest + failOnMinTestsNotRun: true + condition: and(succeeded(), not(eq('${{ matrix.BuildPlatform }}', 'ARM64'))) diff --git a/.ado/pr-pipeline.yml b/.ado/pr-pipeline.yml deleted file mode 100644 index dc744e0b64b..00000000000 --- a/.ado/pr-pipeline.yml +++ /dev/null @@ -1,28 +0,0 @@ -# -# The PR pipeline entry point. -# Extends build-template.yml with buildEnvironment: PullRequest for Unofficial validation builds. -# - -name: $(Date:yyyyMMdd).$(Rev:r) - -trigger: none - -pr: - - main - - master - - "*-stable" - -variables: - - group: platform-override-zero-permission-token - -extends: - template: build-template.yml@self - parameters: - buildEnvironment: PullRequest - AgentPool: - Medium: - name: rnw-pool-4 - demands: ImageOverride -equals rnw-img-vs2022-node22 - Large: - name: rnw-pool-8 - demands: ImageOverride -equals rnw-img-vs2022-node22 diff --git a/.ado/publish.yml b/.ado/publish.yml index c78eb7e3bf1..1cf16e3b6ed 100644 --- a/.ado/publish.yml +++ b/.ado/publish.yml @@ -1,31 +1,352 @@ -# -# The Publish pipeline entry point. -# This file is a copy of ci-pipeline.yml kept for backward compatibility -# with *-stable branches that haven't been updated yet. -# Once all branches use ci-pipeline.yml, this file can be deleted. -# - name: 0.0.$(Date:yyMM.d)$(Rev:rrr) -trigger: none -pr: none - parameters: - - name: isReleaseBuild - displayName: 'Treat as Release build (skip beachball, use committed versions)' - type: string - default: auto - values: - - auto - - 'true' - - 'false' +- name: AgentPool + type: object + default: + Medium: + name: rnw-pool-4-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8-microsoft + demands: ImageOverride -equals rnw-img-vs2022-node22 + +- name: desktopBuildMatrix + type: object + default: + - Name: X64Debug + BuildConfiguration: Debug + BuildPlatform: x64 + - Name: X64Release + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86Debug + BuildConfiguration: Debug + BuildPlatform: x86 + - Name: X86Release + BuildConfiguration: Release + BuildPlatform: x86 + - Name: ARM64ECDebug + BuildConfiguration: Debug + BuildPlatform: ARM64EC + - Name: ARM64ECRelease + BuildConfiguration: Release + BuildPlatform: ARM64EC + +- name: universalBuildMatrix + type: object + default: + - Name: X64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x64 + - Name: X86ReleaseFabric + BuildConfiguration: Release + BuildPlatform: x86 + - Name: Arm64ReleaseFabric + BuildConfiguration: Release + BuildPlatform: ARM64 variables: - template: variables/windows.yml - group: RNW Secrets +trigger: none +pr: none + +resources: + repositories: + - repository: 1ESPipelineTemplates + type: git + name: 1ESPipelineTemplates/1ESPipelineTemplates + ref: refs/tags/release extends: - template: build-template.yml@self + template: v1/1ES.Official.PipelineTemplate.yml@1ESPipelineTemplates parameters: - buildEnvironment: Continuous - isReleaseBuild: ${{ parameters.isReleaseBuild }} + pool: ${{ parameters.AgentPool.Medium }} + featureFlags: + autoEnablePREfastWithNewRuleset: false # PREfast produces 0 actionable findings; auto-enable injects /analyze into every C++ TU, generating ~2656 SARIF files that Guardian uploads for ~19 min per native build + sdl: + credscan: + suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json + spotBugs: + enabled: false # We don't have any java, but random packages in node_modules do + prefast: + enabled: false + stages: + - stage: RNWPublish + jobs: + # Set version variables + - job: SetVersionVars + displayName: Set Version Variables + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 15 + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/set-version-vars.yml@self + parameters: + buildEnvironment: Continuous + + # We new npmPack.js in Release pipeline to detect already published NPM packages and avoid publishing them again + - script: copy ".ado\scripts\npmPack.js" "$(Build.StagingDirectory)\versionEnvVars\npmPack.js" + displayName: Include npmPack.js in VersionEnvVars artifact + + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish version variables' + targetPath: $(Build.StagingDirectory)/versionEnvVars + artifactName: VersionEnvVars + + # Create NPM packages + - job: RnwNpmPack + displayName: Create NPM packages + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 + cancelTimeoutInMinutes: 5 + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + parameters: + agentImage: HostedImage + + - script: node .ado/scripts/npmPack.js --clean --no-color "$(Pipeline.Workspace)\published-packages" + displayName: Pack npm packages + + - script: dir /s "$(Pipeline.Workspace)\published-packages" + displayName: Show created npm packages + + templateContext: + outputs: + - output: pipelineArtifact + displayName: 'Publish npm pack artifacts' + condition: succeededOrFailed() + targetPath: $(Pipeline.Workspace)/published-packages + artifactName: NpmPackedTarballs + + # Run linting + - template: .ado/jobs/linting.yml@self + parameters: + buildEnvironment: Continuous + AgentPool: ${{ parameters.AgentPool }} + + # Create and sign Destop DLLs + - ${{ each matrix in parameters.desktopBuildMatrix }}: + - job: RnwNativeBuildDesktop${{ matrix.Name }} + displayName: Build Desktop ${{ matrix.Name }} + dependsOn: SetVersionVars + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + + - template: .ado/templates/prepare-build-env.yml@self + parameters: + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + buildEnvironment: Publish + + - template: .ado/templates/apply-published-version-vars.yml@self + + - template: .ado/templates/msbuild-sln.yml@self + parameters: + solutionDir: vnext + solutionName: ReactWindows-Desktop.Publish.slnf + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + oneESMode: true ## Files are only copied to staging, not published + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props + + - template: .ado/templates/publish-build-artifacts.yml@self + parameters: + oneESMode: true ## Files are only copied to staging, not published + artifactName: Desktop + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + React.Windows.Desktop\Microsoft.ReactNative.winmd + React.Windows.Desktop.DLL\react-native-win32.* + + - template: .ado/templates/esrp-codesign-binaries.yml@self + parameters: + displayName: 'CodeSign Desktop Binaries' + folderPath: $(Build.StagingDirectory)/NuGet/Desktop/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + pattern: | + **/react-native-win32.dll + + templateContext: + sdl: + prefast: + enabled: false + binskim: + analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\React.Windows.Desktop.DLL\react-native-win32.dll' + outputs: + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: Desktop.${{matrix.buildPlatform}}.${{matrix.buildConfiguration}}' + artifactName: Desktop.${{matrix.buildPlatform}}.${{matrix.buildConfiguration}} + targetPath: $(Build.StagingDirectory)/NuGet/Desktop/${{matrix.buildPlatform}}/${{matrix.buildConfiguration}} + + # Create and sign Universal DLLs + - ${{ each matrix in parameters.universalBuildMatrix }}: + - job: RnwNativeBuildUniversal${{ matrix.Name }} + displayName: Build Universal ${{ matrix.Name }} + dependsOn: SetVersionVars + pool: ${{ parameters.AgentPool.Large }} + timeoutInMinutes: 360 # CodeQL requires 3x usual build timeout + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + + - template: .ado/templates/prepare-build-env.yml@self + parameters: + platform: ${{ matrix.BuildPlatform }} + configuration: ${{ matrix.BuildConfiguration }} + buildEnvironment: Publish + + - template: .ado/templates/apply-published-version-vars.yml@self + + - template: .ado/templates/msbuild-sln.yml@self + parameters: + solutionDir: vnext + solutionName: Microsoft.ReactNative.NewArch.Publish.slnf + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + oneESMode: true ## Files are only copied to staging, not published + msbuildArguments: /p:ForceImportAfterCppTargets=$(Build.SourcesDirectory)\vnext\PropertySheets\CIBuildOptimizations.props + + - task: PowerShell@2 + displayName: Make AnyCPU Reference Assemblies + inputs: + filePath: vnext/Scripts/Tfs/Make-AnyCPU-RefAssemblies.ps1 + arguments: -TargetRoot $(Build.SourcesDirectory)\vnext\target -BuildRoot $(Build.SourcesDirectory)\vnext\target + + - template: .ado/templates/publish-build-artifacts.yml@self + parameters: + oneESMode: true ## Files are only copied to staging, not published + artifactName: ReactWindows + buildPlatform: ${{ matrix.BuildPlatform }} + buildConfiguration: ${{ matrix.BuildConfiguration }} + contents: | + Microsoft.ReactNative\Microsoft.ReactNative.* + Microsoft.ReactNative.CsWinRT\Microsoft.ReactNative.Projection.* + + - template: .ado/templates/esrp-codesign-binaries.yml@self + parameters: + displayName: 'CodeSign Microsoft.ReactNative Binaries' + folderPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + pattern: | + **/Microsoft.ReactNative.dll + **/Microsoft.ReactNative.winmd + **/Microsoft.ReactNative.Projection.dll + + templateContext: + sdl: + prefast: + enabled: false + binskim: + analyzeTargetGlob: '$(Build.SourcesDirectory)\vnext\target\${{ matrix.BuildPlatform }}\${{ matrix.BuildConfiguration }}\Microsoft.ReactNative\Microsoft.ReactNative.dll' + outputs: + - output: pipelineArtifact + displayName: 'Upload build logs' + condition: succeededOrFailed() + targetPath: $(BuildLogDirectory) + artifactName: Build logs - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Upload crash dumps' + condition: and(succeededOrFailed(), eq(variables.HasCrashDumps, 'True')) + targetPath: '$(CrashDumpRootPath)' + artifactName: Crash dumps - $(Agent.JobName)-$(System.JobAttempt) + - output: pipelineArtifact + displayName: 'Publish Artifact: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }}' + artifactName: ReactWindows.${{ matrix.BuildPlatform }}.${{ matrix.BuildConfiguration }} + targetPath: $(Build.StagingDirectory)/NuGet/ReactWindows/${{ matrix.BuildPlatform }}/${{ matrix.BuildConfiguration }} + + # Create Nuget packages + - job: RNWNuget + displayName: Pack NuGet + dependsOn: + - RnwNpmPack + - Linting + - ${{ each matrix in parameters.desktopBuildMatrix }}: + - RnwNativeBuildDesktop${{ matrix.Name }} + - ${{ each matrix in parameters.universalBuildMatrix }}: + - RnwNativeBuildUniversal${{ matrix.Name }} + pool: ${{ parameters.AgentPool.Medium }} + timeoutInMinutes: 60 # Protect against the long CodeSign task + + steps: + - template: .ado/templates/checkout-shallow.yml@self + + - template: .ado/templates/prepare-js-env.yml@self + + - template: .ado/templates/apply-published-version-vars.yml@self + + # The commit tag in the nuspec requires that we use at least nuget 5.8 (because things break with nuget versions before and VS 16.8 or later) + - task: NuGetToolInstaller@1 + inputs: + versionSpec: ">=5.8.0" + + - template: .ado/templates/prep-and-pack-nuget.yml@self + parameters: + artifactName: ReactWindows + publishCommitId: $(publishCommitId) + npmVersion: $(npmVersion) + packMicrosoftReactNative: true + packMicrosoftReactNativeCxx: true + slices: + - platform: x64 + configuration: Release + - platform: x86 + configuration: Release + - platform: ARM64 + configuration: Release + + - template: .ado/templates/prep-and-pack-nuget.yml@self + parameters: + artifactName: Desktop + publishCommitId: $(publishCommitId) + npmVersion: $(npmVersion) + packDesktop: true + slices: + - platform: x64 + configuration: Release + - platform: x86 + configuration: Release + - platform: ARM64EC + configuration: Release + - platform: x64 + configuration: Debug + - platform: x86 + configuration: Debug + - platform: ARM64EC + configuration: Debug + + - template: .ado/templates/esrp-codesign-nuget.yml@self + parameters: + displayName: 'CodeSign all NuGet packages' + folderPath: $(System.DefaultWorkingDirectory)/NugetRootFinal + pattern: '**/*.nupkg' + + templateContext: + sdl: + binskim: + analyzeTargetGlob: '$(System.DefaultWorkingDirectory)\NugetRoot\**\Microsoft.ReactNative\Microsoft.ReactNative.dll;$(System.DefaultWorkingDirectory)\NugetRoot\**\React.Windows.Desktop.DLL\react-native-win32.dll' + outputs: + - output: pipelineArtifact + displayName: 'Publish final nuget artifacts' + targetPath: $(System.DefaultWorkingDirectory)\NugetRootFinal + artifactName: "ReactWindows-final-nuget" diff --git a/.ado/release.yml b/.ado/release.yml index 89381cba6b9..4bd8ffe7f58 100644 --- a/.ado/release.yml +++ b/.ado/release.yml @@ -77,7 +77,7 @@ extends: echo Publish.requestedForID: $(resources.pipeline.Publish.requestedForID) displayName: Log all pipeline variables - - pwsh: | + - powershell: | $buildReason = $env:BUILD_REASON # Use only the first line of the commit message $sourceMessage = ($env:SOURCE_MESSAGE -split "`n")[0].Trim() @@ -174,7 +174,7 @@ extends: displayName: Remove already published packages - script: dir /s "$(Pipeline.Workspace)\published-packages" displayName: Show npm packages after cleanup - - pwsh: | + - powershell: | $tgzFiles = Get-ChildItem -Path "$(Pipeline.Workspace)\published-packages" -Filter "*.tgz" -Recurse $tgzCount = $tgzFiles.Count Write-Host "Found $tgzCount .tgz files" @@ -278,7 +278,7 @@ extends: artifactName: 'ReactWindows-final-nuget' targetPath: '$(Pipeline.Workspace)/ReactWindows-final-nuget' steps: - - pwsh: | + - powershell: | # Extract PDB files from all NuGet packages (.nupkg are ZIP archives) $nugetDir = "$(Pipeline.Workspace)/ReactWindows-final-nuget" $symbolsDir = "$(Pipeline.Workspace)/symbols" diff --git a/.ado/stages.yml b/.ado/stages.yml new file mode 100644 index 00000000000..172876377dd --- /dev/null +++ b/.ado/stages.yml @@ -0,0 +1,85 @@ +parameters: + - name: buildEnvironment + type: string + default: PullRequest + values: + - PullRequest + - SecurePullRequest + - Continuous + - name: AgentPool + type: object + +stages: + - stage: Setup + jobs: + - template: jobs/setup.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + + - stage: Build + displayName: Build 🔨 + dependsOn: Setup + jobs: + - template: jobs/universal.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/desktop.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/nuget-desktop.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/cli-init-windows.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + buildNuGetOnly: true + + # Attack Surface Analyzer for SDL compliance + - template: jobs/attack-surface-analyzer.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + complianceWarnOnly: true + + - stage: IntegrationTests + displayName: Tests 🧪 + dependsOn: Setup + jobs: + - template: jobs/linting.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/node-tests.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/playground.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - template: jobs/e2e-test.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + + - stage: CLI + displayName: Cli 🖥 + dependsOn: Setup + jobs: + - template: jobs/cli-init-windows.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + buildNuGetOnly: false + + - template: jobs/macos-tests.yml diff --git a/.ado/templates/cleanup-certificate.yml b/.ado/templates/cleanup-certificate.yml index 70c92c7f5d3..525276acb9c 100644 --- a/.ado/templates/cleanup-certificate.yml +++ b/.ado/templates/cleanup-certificate.yml @@ -1,5 +1,5 @@ steps: - - pwsh: | + - powershell: | $PfxPath = Join-Path -Path $(Build.SourcesDirectory) -ChildPath EncodedKey.pfx Write-Host $PfxPath Remove-Item -path $PfxPath diff --git a/.ado/templates/compute-beachball-branch-name.yml b/.ado/templates/compute-beachball-branch-name.yml index 41cd156fa6b..a9fb43462f1 100644 --- a/.ado/templates/compute-beachball-branch-name.yml +++ b/.ado/templates/compute-beachball-branch-name.yml @@ -1,11 +1,11 @@ steps: - - pwsh: | + - powershell: | Write-Host "Setting BeachBallBranchName to $(System.PullRequest.TargetBranch)" Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$(System.PullRequest.TargetBranch)" displayName: Set BeachBallBranchName for Pull Request condition: ${{ eq(variables['Build.Reason'], 'PullRequest') }} - - pwsh: | + - powershell: | Write-Host "Setting BeachBallBranchName to $(Build.SourceBranchName)" Write-Host "##vso[task.setvariable variable=BeachBallBranchName]$(Build.SourceBranchName)" displayName: Set BeachBallBranchName for CI diff --git a/.ado/templates/detect-nuget-lockfile-changes.yml b/.ado/templates/detect-nuget-lockfile-changes.yml index 068f625b219..daee55476d1 100644 --- a/.ado/templates/detect-nuget-lockfile-changes.yml +++ b/.ado/templates/detect-nuget-lockfile-changes.yml @@ -4,7 +4,7 @@ parameters: default: true steps: - - pwsh: | + - powershell: | & git add */packages*.lock.json $changed = git status --porcelain=v1 */packages*.lock.json if ($changed -ne $null) { diff --git a/.ado/templates/discover-google-test-adapter.yml b/.ado/templates/discover-google-test-adapter.yml index 2a0c05a100e..d2e63281ac1 100644 --- a/.ado/templates/discover-google-test-adapter.yml +++ b/.ado/templates/discover-google-test-adapter.yml @@ -1,6 +1,6 @@ steps: - - pwsh: | + - powershell: | $vsExtensionPath="${env:ProgramFiles}\Microsoft Visual Studio\2022\Enterprise\Common7\IDE\Extensions\"; $GoogleTestAdapterPath=(Get-ChildItem $vsExtensionPath -Directory | Where-Object -FilterScript {Test-Path (Join-Path -Path $_.FullName -ChildPath "GoogleTestAdapter.Core.dll")}).FullName diff --git a/.ado/templates/enable-experimental-winui3.yml b/.ado/templates/enable-experimental-winui3.yml index a5cba36dae1..f65dd14bed3 100644 --- a/.ado/templates/enable-experimental-winui3.yml +++ b/.ado/templates/enable-experimental-winui3.yml @@ -18,7 +18,6 @@ steps: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\EnableInternalWinAppSDKFeed.ps1 - pwsh: true - task: NuGetAuthenticate@1 displayName: 'NuGet Authenticate Internal WinAppSDK Feed' diff --git a/.ado/templates/msbuild-sln.yml b/.ado/templates/msbuild-sln.yml index 5b52eca5aaf..56d7bc62222 100644 --- a/.ado/templates/msbuild-sln.yml +++ b/.ado/templates/msbuild-sln.yml @@ -18,12 +18,12 @@ parameters: parallelBuild: true steps: - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=BuildLogDirectory]$(Build.BinariesDirectory)\${{ parameters.buildPlatform }}\${{ parameters.buildConfiguration }}\BuildLogs" Write-Host "##vso[task.setvariable variable=MsBuildWarnAsErrorArgument]/warnaserror" displayName: Set Log directory and warn as error - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=MsBuildWarnAsErrorArgument]" condition: not(eq('${{parameters.warnAsError}}', 'true')) displayName: Disable WarnAsError diff --git a/.ado/templates/prep-and-pack-nuget.yml b/.ado/templates/prep-and-pack-nuget.yml index 112a2b1ac6b..6913307076e 100644 --- a/.ado/templates/prep-and-pack-nuget.yml +++ b/.ado/templates/prep-and-pack-nuget.yml @@ -1,6 +1,9 @@ parameters: - name: artifactName type: string + - name: artifactName2 + type: string + default: '' - name: slices type: object @@ -50,6 +53,12 @@ steps: inputs: artifact: ${{ parameters.artifactName }}.${{ slice.platform }}.${{ slice.configuration }} path: ${{parameters.nugetroot}}/${{ parameters.artifactName }}/${{ slice.platform }}/${{ slice.configuration }} + - ${{ if ne(parameters.artifactName2, '') }}: + - task: DownloadPipelineArtifact@2 + displayName: 'Download ${{ parameters.artifactName2 }}.${{ slice.platform }}.${{ slice.configuration }}' + inputs: + artifact: ${{ parameters.artifactName2 }}.${{ slice.platform }}.${{ slice.configuration }} + path: ${{parameters.nugetroot}}/${{ parameters.artifactName2 }}/${{ slice.platform }}/${{ slice.configuration }} - pwsh: yarn build displayName: Run yarn build @@ -59,7 +68,6 @@ steps: inputs: filePath: vnext/Scripts/Tfs/Layout-MSRN-Headers.ps1 arguments: -TargetRoot ${{parameters.nugetroot}} - pwsh: true - ${{ if eq(parameters.packDesktop, true) }}: - task: PowerShell@2 @@ -67,10 +75,9 @@ steps: inputs: filePath: vnext/Scripts/Tfs/Layout-Desktop-Headers.ps1 arguments: -TargetRoot ${{parameters.nugetroot}} - pwsh: true - ${{ if or(eq(parameters.packMicrosoftReactNative, true), eq(parameters.packMicrosoftReactNativeCxx, true)) }}: - - pwsh: | + - powershell: | (Get-Content -Path ${{parameters.nugetroot}}\Microsoft.ReactNative.VersionCheck.targets) -replace '\$\$nuGetPackageVersion\$\$', '${{parameters.npmVersion}}' | Set-Content -Path ${{parameters.nugetroot}}\Microsoft.ReactNative.VersionCheck.targets displayName: Patch version check file with version ${{parameters.npmVersion}} diff --git a/.ado/templates/prep-and-pack-single.yml b/.ado/templates/prep-and-pack-single.yml index 9bc1246e211..cdc7d7f4081 100644 --- a/.ado/templates/prep-and-pack-single.yml +++ b/.ado/templates/prep-and-pack-single.yml @@ -25,11 +25,11 @@ parameters: steps: - - pwsh: gci $(System.DefaultWorkingDirectory)/NugetRoot + - powershell: gci $(System.DefaultWorkingDirectory)/NugetRoot displayName: List files in NugetRoot - ${{ if ne(parameters.slices, '') }}: - - pwsh: > + - powershell: > .\StripAdditionalPlatformsFromNuspec.ps1 -nuspec ${{ coalesce(parameters.nuspec, parameters.outputPackage) }}.nuspec -outfile ${{ parameters.outputPackage }}.nuspec @@ -51,7 +51,7 @@ steps: packDestination: $(System.DefaultWorkingDirectory)/NugetRootFinal buildProperties: version=${{ parameters.packageVersion }};id=${{ parameters.outputPackage }};${{ parameters.buildProperties }} - - pwsh: gci $(System.DefaultWorkingDirectory)/NugetRootFinal + - powershell: gci $(System.DefaultWorkingDirectory)/NugetRootFinal displayName: List files in NugetRootFinal - script: | diff --git a/.ado/templates/prepare-build-env.yml b/.ado/templates/prepare-build-env.yml index 2e81a4bad40..ab5dc7e6bc8 100644 --- a/.ado/templates/prepare-build-env.yml +++ b/.ado/templates/prepare-build-env.yml @@ -36,27 +36,27 @@ steps: configuration: ${{ parameters.configuration }} buildEnvironment: ${{ parameters.buildEnvironment }} - - pwsh: | + - powershell: | & reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /s & reg query "HKEY_CURRENT_USER\SOFTWARE\Microsoft\Windows\Windows Error Reporting" /s & reg query "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug" /s Enable-WindowsErrorReporting displayName: Check and enable Windows Error Reporting - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=CrashDumpRootPath]$(Build.StagingDirectory)\CrashDumps" New-Item -Path '$(Build.StagingDirectory)\CrashDumps' -ItemType Directory displayName: Set CrashDumpRootPath - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=MSBUILDDEBUGPATH]$(CrashDumpRootPath)" displayName: Set MSBUILDDEBUGPATH - - pwsh: | + - powershell: | Write-Host "##vso[task.setvariable variable=ProcDumpPath]$(Build.StagingDirectory)\Procdump" displayName: Set ProcDumpPath - - pwsh: | + - powershell: | & $(Build.SourcesDirectory)\.ado\scripts\RunProcDump.ps1 -ProcDumpArgs @("-mm", "-i", "$(CrashDumpRootPath)") -ProcDumpInstallPath "$(ProcDumpPath)" -Verbose displayName: Setup ProcDump as AeDebug \ No newline at end of file diff --git a/.ado/templates/prepare-js-env.yml b/.ado/templates/prepare-js-env.yml index eefe6581503..89c8724575f 100644 --- a/.ado/templates/prepare-js-env.yml +++ b/.ado/templates/prepare-js-env.yml @@ -11,7 +11,7 @@ steps: - task: NodeTool@0 displayName: Set Node Version inputs: - versionSpec: '24.x' + versionSpec: '22.x' - script: if not exist %APPDATA%\npm (mkdir %APPDATA%\npm) displayName: Ensure npm directory for npx commands diff --git a/.ado/templates/publish-nuget-to-ado-feed.yml b/.ado/templates/publish-nuget-to-ado-feed.yml index c716639638d..48c38c92364 100644 --- a/.ado/templates/publish-nuget-to-ado-feed.yml +++ b/.ado/templates/publish-nuget-to-ado-feed.yml @@ -35,7 +35,7 @@ steps: # Also expose the token for the pre-push duplicate check Write-Host "##vso[task.setvariable variable=NuGetAccessToken;issecret=true]$accessToken" -- pwsh: | +- powershell: | Add-Type -AssemblyName System.IO.Compression.FileSystem $feedUrl = "${{ parameters.nugetFeedUrl }}" diff --git a/.ado/templates/react-native-init-windows.yml b/.ado/templates/react-native-init-windows.yml index 14419878b0f..878df865054 100644 --- a/.ado/templates/react-native-init-windows.yml +++ b/.ado/templates/react-native-init-windows.yml @@ -19,14 +19,21 @@ parameters: - name: additionalInitArguments type: string default: '' + - name: useNuGet + type: boolean + default: false - name: useExperimentalWinUI3 type: boolean default: false + - name: publishNuGet + type: boolean + default: false - name: buildEnvironment type: string default: PullRequest - values: + values: - PullRequest + - SecurePullRequest - Continuous steps: @@ -37,12 +44,25 @@ steps: parameters: buildEnvironment: ${{ parameters.buildEnvironment }} - # Download the final NuGet packages produced by the RNWNuget job - - task: DownloadPipelineArtifact@2 - displayName: 'Download final NuGet packages' - inputs: - artifact: ReactWindows-final-nuget - path: $(System.DefaultWorkingDirectory)\NugetTestFeed + - ${{ if eq(parameters.useNuGet, true) }}: + - template: prep-and-pack-nuget.yml + parameters: + artifactName: ReactWindows + npmVersion: $(npmVersion) + packMicrosoftReactNative: true + packMicrosoftReactNativeCxx: true + slices: + - platform: ${{ parameters.platform }} + configuration: Release + + - ${{ if eq(parameters.publishNuGet, true) }}: + - task: PublishPipelineArtifact@1 + displayName: "Publish Artifact: CliInitNuGet.${{parameters.platform}}.${{parameters.configuration}}" + # Do nothing if the artifact was already published. E.g. after rerunning a past successful job attempt + continueOnError: true + inputs: + artifactName: CliInitNuGet.${{parameters.platform}}.${{parameters.configuration}} + targetPath: $(System.DefaultWorkingDirectory)/NugetRootFinal - ${{ if endsWith(parameters.template, '-app') }}: - script: | @@ -68,7 +88,7 @@ steps: ${{ else }}: workingDir: $(Agent.BuildDirectory)\testcli\windows - - pwsh: | + - powershell: | $path = (Get-ChildItem -Filter "Package.appxmanifest" -File -Recurse).FullName; [xml] $manifest = Get-Content $path $manifest.Package.Identity.Name = 'ReactNative.InitTest' @@ -87,7 +107,6 @@ steps: inputs: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Start-Tracing.ps1 - pwsh: true - template: react-native-debug-info.yml parameters: @@ -96,13 +115,14 @@ steps: ${{ else }}: workingDirectory: $(Agent.BuildDirectory)\testcli - - pwsh: | - nuget.exe sources add -name TestFeed -source $(System.DefaultWorkingDirectory)\NugetTestFeed - nuget.exe sources remove -name react-native - nuget.exe sources remove -name Nuget.org - nuget.exe sources add -name Nuget.org -source https://api.nuget.org/v3/index.json - displayName: Add local NuGet test feed - workingDirectory: $(Agent.BuildDirectory)\testcli + - ${{ if eq(parameters.useNuGet, true) }}: + - powershell: | + nuget.exe sources add -name TestFeed -source $(System.DefaultWorkingDirectory)\NugetTestFeed + nuget.exe sources remove -name react-native + nuget.exe sources remove -name Nuget.org + nuget.exe sources add -name Nuget.org -source https://api.nuget.org/v3/index.json + displayName: Add local NuGet test feed + workingDirectory: $(Agent.BuildDirectory)\testcli - template: ../templates/run-windows-with-certificates.yml parameters: @@ -120,7 +140,6 @@ steps: - template: upload-build-logs.yml parameters: - oneESMode: true buildLogDirectory: '$(Build.BinariesDirectory)\${{ parameters.platform }}\${{ parameters.configuration }}\BuildLogs' # Only run the following on fabric apps @@ -141,7 +160,11 @@ steps: targetType: filePath # filePath | inline filePath: $(Build.SourcesDirectory)\vnext\Scripts\Tracing\Stop-Tracing.ps1 arguments: -NoAnalysis -outputFolder $(Build.StagingDirectory)/Tracing - pwsh: true condition: succeededOrFailed() - # Traces artifact publishing moved to templateContext.outputs in cli-init-windows.yml (1ES compliance) + - task: PublishBuildArtifacts@1 + displayName: Upload traces + inputs: + pathtoPublish: '$(Build.StagingDirectory)/Tracing' + artifactName: 'Traces - $(Agent.JobName)-$(System.JobAttempt)' + condition: succeededOrFailed() diff --git a/.ado/templates/run-compliance-postbuild.yml b/.ado/templates/run-compliance-postbuild.yml new file mode 100644 index 00000000000..4b6931be493 --- /dev/null +++ b/.ado/templates/run-compliance-postbuild.yml @@ -0,0 +1,14 @@ +# Compliance tasks to be run post build +parameters: +- name: complianceWarnOnly + displayName: Convert compliance errors to warnings + type: boolean + default: false + +steps: + # Component Governance Detection Task (https://docs.opensource.microsoft.com/tools/cg/) + # Detects open source you use and alerts you to whether it has security vulnerabilities or legal issues. + # TODO: Reconcile with existing component-governance.yml template + - task: ms.vss-governance-buildtask.governance-build-task-component-detection.ComponentGovernanceComponentDetection@0 + displayName: '⚖️ Component Governance Detection' + continueOnError: ${{ parameters.complianceWarnOnly }} diff --git a/.ado/templates/run-compliance-prebuild.yml b/.ado/templates/run-compliance-prebuild.yml new file mode 100644 index 00000000000..09cd4a19874 --- /dev/null +++ b/.ado/templates/run-compliance-prebuild.yml @@ -0,0 +1,89 @@ +# Compliance tasks to be run pre build +parameters: +- name: complianceWarnOnly + displayName: Convert compliance errors to warnings + type: boolean + default: false + +steps: + # Anti-Malware Scan Task (https://aka.ms/gdn-azdo-antimalware) + # Runs an Anti-Malware Scan on Windows agents, using Windows Defender. + - task: securedevelopmentteam.vss-secure-development-tools.build-task-antimalware.AntiMalware@4 + displayName: '⚖️ Run Anti-Malware Scan' + inputs: + InputType: 'Basic' + ScanType: 'CustomScan' + FileDirPath: $(Build.SourcesDirectory) + EnableServices: true + SupportLogOnError: false + TreatSignatureUpdateFailureAs: 'Warning' + SignatureFreshness: 'OneDay' + TreatStaleSignatureAs: 'Error' + continueOnError: ${{ parameters.complianceWarnOnly }} + + # PoliCheck Build Task (https://aka.ms/gdn-azdo-policheck) + # Scans the text of source code, comments, and content for terminology that could be sensitive for legal, cultural, or geopolitical reasons. + - task: securedevelopmentteam.vss-secure-development-tools.build-task-policheck.PoliCheck@2 + displayName: '⚖️ Run PoliCheck' + inputs: + targetType: F + targetArgument: $(Build.SourcesDirectory) + result: PoliCheck.xml + optionsFC: 1 + optionsXS: 1 + optionsHMENABLE: 0 + optionsPE: 1|2|3|4 + optionsSEV: 1|2|3|4 + optionsUEPath: $(Build.SourcesDirectory)\.ado\config\PoliCheckExclusions.xml + optionsRulesDBPath: $(Build.SourcesDirectory)\.ado\config\PoliCheckRules.mdb + continueOnError: ${{ parameters.complianceWarnOnly }} + + # CredScan Task (https://aka.ms/gdn-azdo-credscan) + # Searches through source code and build outputs for credentials left behind in the open. + - task: securedevelopmentteam.vss-secure-development-tools.build-task-credscan.CredScan@3 + displayName: '⚖️ Run CredScan' + inputs: + outputFormat: pre + suppressionsFile: $(Build.SourcesDirectory)\.ado\config\CredScanSuppressions.json + batchSize: 20 + debugMode: false + continueOnError: ${{ parameters.complianceWarnOnly }} + + - task: PublishSecurityAnalysisLogs@3 + displayName: 'Publish Guardian Artifacts' + inputs: + ArtifactName: CodeAnalysisLogs + ArtifactType: Container + PublishProcessedResults: false + AllTools: true + + # PostAnalysis Task (https://docs.microsoft.com/en-us/azure/security/develop/yaml-configuration#post-analysis-task) + # Breaks the build if any of the tasks failed. + - task: PostAnalysis@2 + displayName: "⚖️ Compliance Pre-Build Analysis" + inputs: + AllTools: false + AntiMalware: true + CredScan: true + PoliCheck: true + PoliCheckBreakOn: Severity4Above + ToolLogsNotFoundAction: "Error" + # TSA Configuration for automatic bug filing + TSAEnabled: true + TSAOptions: | + { + "areaPath": "OS\\Windows Client and Services\\WinPD\\SPICE\\ReactNative", + "iterationPath": "OS\\Future", + "notificationAliases": ["$(TSANotificationAliases)"], + "codebaseAdmins": ["$(TSACodebaseAdmins)"], + "bugTags": ["SDL", "Security"], + "instanceUrl": "https://dev.azure.com/microsoft", + "projectName": "OS", + "allTools": true + } + continueOnError: ${{ parameters.complianceWarnOnly }} + + # Restore unnecessary changes that were made by the compliance tasks + - script: | + git restore $(Build.SourcesDirectory)\.ado\config\PoliCheckRules.mdb + displayName: "⚖️ Compliance Pre-Build Cleanup" diff --git a/.ado/templates/run-wack.yml b/.ado/templates/run-wack.yml index c697385e97f..fa692a3fd71 100644 --- a/.ado/templates/run-wack.yml +++ b/.ado/templates/run-wack.yml @@ -5,7 +5,7 @@ parameters: type: string steps: - - pwsh: .ado/scripts/TestWACK.ps1 -PackageName ${{ parameters.packageName }} -OutputDir $(Build.StagingDirectory)\WACK + - powershell: .ado/scripts/TestWACK.ps1 -PackageName ${{ parameters.packageName }} -OutputDir $(Build.StagingDirectory)\WACK displayName: Run WACK - task: PublishBuildArtifacts@1 diff --git a/.ado/templates/run-windows-with-certificates.yml b/.ado/templates/run-windows-with-certificates.yml index c05eef784e8..154d4d65764 100644 --- a/.ado/templates/run-windows-with-certificates.yml +++ b/.ado/templates/run-windows-with-certificates.yml @@ -4,6 +4,7 @@ parameters: default: PullRequest values: - PullRequest + - SecurePullRequest - Continuous - name: buildConfiguration type: string diff --git a/.ado/templates/set-appx-platforms.yml b/.ado/templates/set-appx-platforms.yml index 4a949c64f51..47830ade907 100644 --- a/.ado/templates/set-appx-platforms.yml +++ b/.ado/templates/set-appx-platforms.yml @@ -26,5 +26,5 @@ parameters: # un-overriden path in official tests. steps: - ${{ if and(endsWith(parameters.buildEnvironment, 'PullRequest'), eq(parameters.configuration, 'Release')) }}: - - pwsh: $env:AppxBundlePlatforms = "${{ parameters.platform }}" + - powershell: $env:AppxBundlePlatforms = "${{ parameters.platform }}" displayName: Set AppxBundlePlatforms to "${{ parameters.platform }}" diff --git a/.ado/templates/set-experimental-feature.yml b/.ado/templates/set-experimental-feature.yml index c3c4a7fab04..1080b44af6c 100644 --- a/.ado/templates/set-experimental-feature.yml +++ b/.ado/templates/set-experimental-feature.yml @@ -7,7 +7,7 @@ parameters: type: string steps: - - pwsh: | + - powershell: | [xml] $xmlDoc = Get-Content .\ExperimentalFeatures.props # Add to new property group at the end of the file to ensure it overrides any other setting $propertyGroup = $xmlDoc.CreateElement("PropertyGroup", $xmlDoc.DocumentElement.NamespaceURI); diff --git a/.ado/templates/set-version-vars.yml b/.ado/templates/set-version-vars.yml index 6253aef064c..b0bfc5387d4 100644 --- a/.ado/templates/set-version-vars.yml +++ b/.ado/templates/set-version-vars.yml @@ -5,6 +5,7 @@ parameters: default: PullRequest values: - PullRequest + - SecurePullRequest - Continuous steps: diff --git a/.ado/templates/stop-packagers.yml b/.ado/templates/stop-packagers.yml index d83421fc9bc..c121c3945ac 100644 --- a/.ado/templates/stop-packagers.yml +++ b/.ado/templates/stop-packagers.yml @@ -3,7 +3,6 @@ steps: displayName: Cleanup any packagers left over from other builds inputs: targetType: inline # filePath | inline - pwsh: true script: | echo "ports:{" echo ${env:RNTester.PackagerPort} diff --git a/.ado/templates/upload-build-logs.yml b/.ado/templates/upload-build-logs.yml index 0c4f041f3da..a22b33bd207 100644 --- a/.ado/templates/upload-build-logs.yml +++ b/.ado/templates/upload-build-logs.yml @@ -19,7 +19,6 @@ steps: condition: succeededOrFailed() inputs: targetType: inline # filePath | inline - pwsh: true script: | Write-Host "Looking for procdump64 processes..."; Get-Process | ForEach-Object { @@ -40,7 +39,6 @@ steps: condition: and(succeededOrFailed(), ${{ parameters.uploadCrashDumps }}) inputs: targetType: inline # filePath | inline - pwsh: true script: | Write-Host "Looking for crash dumps in $(CrashDumpRootPath)..."; Get-ChildItem -Path $(CrashDumpRootPath) -File -Recurse; diff --git a/.ado/templates/verdaccio-start.yml b/.ado/templates/verdaccio-start.yml index b0066877e8d..54a91c75890 100644 --- a/.ado/templates/verdaccio-start.yml +++ b/.ado/templates/verdaccio-start.yml @@ -5,7 +5,7 @@ parameters: default: true steps: - - pwsh: start-process verdaccio.cmd -ArgumentList @('--config', './.ado/verdaccio/config.yaml') + - powershell: start-process verdaccio.cmd -ArgumentList @('--config', './.ado/verdaccio/config.yaml') displayName: Launch test npm server (verdaccio) - script: node .ado/scripts/waitForVerdaccio.js diff --git a/.ado/templates/verdaccio-stop.yml b/.ado/templates/verdaccio-stop.yml index 17e4107396a..7113dd12ea2 100644 --- a/.ado/templates/verdaccio-stop.yml +++ b/.ado/templates/verdaccio-stop.yml @@ -10,7 +10,15 @@ steps: displayName: Kill Verdaccio condition: succeededOrFailed() - # Verdaccio log artifact publishing moved to templateContext.outputs in cli-init-windows.yml (1ES compliance) + - ${{ if eq(parameters.uploadLogs, true) }}: + # We are experiencing random package restore failures. + # We want to uploading the vedaccio logs to aid in diagnosing if it is verdaccio or npmjs.org + - task: PublishPipelineArtifact@1 + displayName: Upload Verdaccio.log (on failure) + inputs: + targetPath: 'verdaccio.log' + artifact: '$(Agent.JobName).Verdaccio.log-$(System.JobAttempt)' + condition: failed() - script: | call npm config delete registry diff --git a/.ado/templates/write-certificate.yml b/.ado/templates/write-certificate.yml index 88d770b0569..271272f1ffd 100644 --- a/.ado/templates/write-certificate.yml +++ b/.ado/templates/write-certificate.yml @@ -4,7 +4,7 @@ parameters: default: 'pwd' steps: - - pwsh: | + - powershell: | $certStoreRoot="cert:\CurrentUser\My" $rootFolder="$(Build.SourcesDirectory)" diff --git a/.ado/windows-vs-pr-secure.yml b/.ado/windows-vs-pr-secure.yml new file mode 100644 index 00000000000..3601bce96c8 --- /dev/null +++ b/.ado/windows-vs-pr-secure.yml @@ -0,0 +1,31 @@ +name: $(Date:yyyyMMdd).$(Rev:r) + +trigger: none # will disable CI builds entirely + +pr: + - main + - master + - "*-stable" + +variables: + - group: platform-override-zero-permission-token + +parameters: + - name: AgentPool + type: object + default: + Small: + name: rnw-pool-2 + demands: ImageOverride -equals rnw-img-vs2022-node22 + Medium: + name: rnw-pool-4 + demands: ImageOverride -equals rnw-img-vs2022-node22 + Large: + name: rnw-pool-8 + demands: ImageOverride -equals rnw-img-vs2022-node22 + +stages: + - template: stages.yml + parameters: + buildEnvironment: SecurePullRequest + AgentPool: ${{ parameters.AgentPool }} diff --git a/.ado/windows-vs-pr.yml b/.ado/windows-vs-pr.yml index c97e1695768..ae79db81131 100644 --- a/.ado/windows-vs-pr.yml +++ b/.ado/windows-vs-pr.yml @@ -1,13 +1,6 @@ -# -# The PR pipeline entry point. -# This file is a copy of pr-pipeline.yml kept for backward compatibility -# with *-stable branches that haven't been updated yet. -# Once all branches use pr-pipeline.yml, this file can be deleted. -# - name: $(Date:yyyyMMdd).$(Rev:r) -trigger: none +trigger: none # will disable CI builds entirely pr: - main @@ -17,14 +10,22 @@ pr: variables: - group: platform-override-zero-permission-token -extends: - template: build-template.yml@self - parameters: - buildEnvironment: PullRequest - AgentPool: +parameters: + - name: AgentPool + type: object + default: + Small: + name: rnw-pool-2 + demands: ImageOverride -equals rnw-img-vs2022-node22 Medium: name: rnw-pool-4 demands: ImageOverride -equals rnw-img-vs2022-node22 Large: name: rnw-pool-8 demands: ImageOverride -equals rnw-img-vs2022-node22 + +stages: + - template: stages.yml + parameters: + buildEnvironment: PullRequest + AgentPool: ${{ parameters.AgentPool }} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d55544888b9..4cb460fd6a3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,6 +123,16 @@ Build and see if your solution works. Consult [Building React-Native-Windows](ht Testing is a key component in the development workflow. If your changes affect existing test cases, or you're working on brand new features and also the accompanying test cases, see [End-to-End Testing](https://github.com/microsoft/react-native-windows/blob/main/docs/e2e-testing.md) for more information about how to validate your work locally. +### 🛡️ Security & Compliance + +React Native Windows follows Microsoft's Secure Development Lifecycle (SDL) requirements. As part of this commitment: + +- **Attack Surface Analyzer (ASA)** runs automatically on every PR to detect security regressions during the build process. This ensures that builds don't inadvertently weaken the OS security configuration. +- If ASA detects changes in your PR, review the artifacts in the PR build to understand the security implications. +- For more information about ASA, see [Attack Surface Analyzer documentation](https://github.com/microsoft/react-native-windows/blob/main/docs/attack-surface-analyzer.md). + +Most changes won't trigger ASA findings, but if yours does, the documentation explains how to review and address the findings. + ### ✅ Code Review When you'd like the team to review your PR, (even if the work is not yet fully-complete), open a PR so that the team can review your work and provide comments, suggestions, and request changes. It may take several cycles, but the end result will be solid, testable, conformant code that is safe for us to merge. diff --git a/docs/attack-surface-analyzer.md b/docs/attack-surface-analyzer.md new file mode 100644 index 00000000000..a677f243dcd --- /dev/null +++ b/docs/attack-surface-analyzer.md @@ -0,0 +1,206 @@ +# Attack Surface Analyzer (ASA) Integration + +## Overview + +Attack Surface Analyzer (ASA) is integrated into the React Native Windows CI pipeline to meet **Secure Development Lifecycle (SDL)** compliance requirements. ASA validates that installers or other high-privilege programs do not weaken the security configuration of the operating system. + +## What is ASA? + +When installing software on an operating system, elevated privileges are often required. Since installers typically run with 'Administrator' or 'root' privileges, they can easily change the security configuration of the operating system, potentially leaving it in a weakened state after installation is complete. + +Attack Surface Analyzer is a Microsoft tool that helps determine the changes made to an operating system during software installation by: +1. Taking a "before" snapshot of the system state +2. Performing the installation or build process +3. Taking an "after" snapshot of the system state +4. Comparing the two snapshots to identify security-relevant changes + +## How ASA Runs in CI + +ASA is integrated into the Build stage of the PR pipeline and runs automatically on secure pull requests (SecurePullRequest builds). The job performs the following steps: + +### 1. Installation +```yaml +dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI +``` + +### 2. Before Snapshot +Captures the initial system state before any build operations: +```bash +asa collect -r before --verbose +``` + +### 3. Build Process +Builds the React Native Windows solution (simulating package installation): +- Compiles Microsoft.ReactNative.sln (x64, Release) +- This step represents the "installation" that ASA analyzes + +### 4. After Snapshot +Captures the system state after build operations: +```bash +asa collect -r after --verbose +``` + +### 5. Comparison & Analysis +Exports and analyzes the differences: +```bash +asa export-collect -f before after -o asa-comparison.json +asa export-collect -f before after -o asa-comparison.html +``` + +### 6. Results Publication +Results are published as build artifacts for review: +- JSON format: `asa-comparison.json` (machine-readable) +- HTML format: `asa-comparison.html` (human-readable) + +## What ASA Checks For + +ASA monitors for security-relevant changes including: + +- **Registry Changes**: Permission modifications, new keys, or value changes +- **File System Changes**: Permission modifications, new files, or attribute changes +- **Service Installations**: New services or service configuration changes +- **Firewall Rules**: New rules or modifications to existing rules +- **Certificate Store**: New certificates or modifications to certificate stores +- **User/Group Changes**: New users, groups, or permission changes +- **Port/Network Changes**: New listening ports or network configuration changes + +## Reviewing ASA Results + +### In Azure DevOps + +1. Go to the failed/completed build +2. Navigate to the "Artifacts" section +3. Download the `ASA_Results` artifact +4. Open `asa-comparison.html` in a browser for a visual overview +5. Review `asa-comparison.json` for detailed, programmatic analysis + +### Understanding Results + +**Expected Changes:** +- Build artifacts in the build directory +- Temporary files in system temp directories +- MSBuild cache updates +- Visual Studio temporary files + +**Unexpected Changes (Require Review):** +- Registry permission changes in system areas +- Service installations +- Firewall rule additions +- Certificate store modifications +- Changes to Program Files or System32 directories + +## SDL Compliance + +ASA integration satisfies the SDL requirement: + +> **Microsoft.Security.AccessControl.10011**: Use Attack Surface Analyzer (ASA) to validate that installers or other high-privilege programs do not weaken the security configuration of the operating system. + +All issues identified by ASA must be **fixed** or **justified** before merging. If ASA detects security regressions: + +1. Review the specific changes in the ASA report +2. Determine if the changes are intentional and necessary +3. If unintentional, fix the installation/build process to avoid the change +4. If intentional, document the justification in the PR +5. Security team may need to approve certain changes + +## Pipeline Configuration + +ASA runs as part of the Build stage in `.ado/stages.yml`: + +```yaml +- template: jobs/attack-surface-analyzer.yml + parameters: + buildEnvironment: ${{ parameters.buildEnvironment }} + AgentPool: ${{ parameters.AgentPool }} + complianceWarnOnly: true +``` + +### Parameters + +- **buildEnvironment**: Currently configured to run only for SecurePullRequest builds +- **AgentPool**: Defines the build agent pool (Medium tier recommended) +- **complianceWarnOnly**: When `true`, ASA failures won't block PR (default: `true`) + +ASA is configured to run only for SecurePullRequest builds to ensure thorough security scanning in controlled environments. It uses `complianceWarnOnly: true` to allow gradual adoption. This can be changed to `false` to enforce blocking on security regressions. + +## Local Testing + +Developers can run ASA locally to test before submitting a PR: + +### Prerequisites +```powershell +# Install ASA CLI +dotnet tool install --global Microsoft.CST.AttackSurfaceAnalyzer.CLI + +# Verify installation +asa --version +``` + +### Running ASA Locally + +```powershell +# 1. Take before snapshot +asa collect -r before + +# 2. Perform your build/installation +cd vnext +msbuild Microsoft.ReactNative.sln /p:Configuration=Release /p:Platform=x64 + +# 3. Take after snapshot +asa collect -r after + +# 4. Export comparison +asa export-collect -f before after -o .\asa-comparison.html + +# 5. Review results +start .\asa-comparison.html +``` + +### GUI Mode (Alternative) + +ASA also provides a browser-based GUI: + +```powershell +asa gui +``` + +Then navigate to `http://localhost:5000` in your browser. + +## Troubleshooting + +### ASA Installation Fails + +**Issue**: `dotnet tool install` fails +**Solution**: Ensure .NET SDK 8.0 or later is installed: +```powershell +dotnet --version +``` + +### Large Number of Changes Detected + +**Issue**: ASA reports many changes +**Solution**: +- Filter out expected build artifacts +- Check if antivirus or other background processes are making changes +- Ensure a clean build environment + +### ASA Job Times Out + +**Issue**: ASA job exceeds 60-minute timeout +**Solution**: +- Check for hung processes during build +- Review snapshot collection performance +- Consider increasing timeout in `.ado/jobs/attack-surface-analyzer.yml` + +## References + +- [Microsoft Attack Surface Analyzer GitHub](https://github.com/microsoft/AttackSurfaceAnalyzer) +- [ASA Documentation](https://github.com/microsoft/AttackSurfaceAnalyzer/wiki) +- [ASA CLI Usage](https://github.com/microsoft/AttackSurfaceAnalyzer/wiki/Command-Line-Usage) + +## Support + +For issues or questions about ASA integration: +- Check the [ASA GitHub Issues](https://github.com/microsoft/AttackSurfaceAnalyzer/issues) +- Contact the React Native Windows security team +- Review the SDL compliance documentation in Service Tree diff --git a/docs/build-pipelines.md b/docs/build-pipelines.md deleted file mode 100644 index 35327b6782b..00000000000 --- a/docs/build-pipelines.md +++ /dev/null @@ -1,105 +0,0 @@ -# Build Pipelines - -React Native Windows uses three Azure DevOps pipelines, all built on the 1ES Pipeline Templates. - -## Pipeline Overview - -| Pipeline | File | Template | Trigger | -|----------|------|----------|---------| -| **CI** | `ci-pipeline.yml` | 1ES Official | Push to `main`, `*-stable` | -| **PR** | `pr-pipeline.yml` | 1ES Unofficial | PRs to `main`, `*-stable` | -| **Release** | `release.yml` | 1ES Official | Triggered by successful CI run | - -CI and PR share a single `build-template.yml` that contains all build, test, and packaging logic. The `buildEnvironment` parameter (`Continuous` or `PullRequest`) controls what runs: - -- **Both**: build, test, lint, NuGet pack, CLI init verification -- **CI only**: ESRP code signing, release tagging -- **PR only**: beachball change file check - -## Stages - -``` -Setup ──┬── Build ──── CLI - │ - └── Tests -``` - -### Setup - -Detects whether this is a **release build** (commit message starts with `RELEASE:`) or a **developer build**. For developer builds, runs beachball to bump versions. Publishes version variables for all downstream stages. - -### Build - -Runs in parallel for each platform/configuration in the build matrix: - -- **Desktop** (x64/x86/ARM64EC, Debug/Release) — builds, runs unit and integration tests, signs shipping binaries (CI) -- **Universal** (x64/x86/ARM64, Debug/Release) — builds, runs unit tests, signs shipping binaries (CI) -- **Linting** — JS/TS lint, format check, override validation -- **NPM Pack** — creates npm tarballs -- **NuGet Pack** — downloads all build artifacts, packs NuGet packages, ESRP signs them (CI) - -Each build job uses `*-single.yml` step templates that build the full `.sln`, run tests, and stage shipping artifacts — we test what we ship. - -### Tests - -Runs in parallel with Build (both depend only on Setup): - -- **Node Tests** — Jest-based JavaScript tests -- **Playground** — builds playground apps across configs -- **E2E Tests** — Fabric end-to-end tests - -### CLI - -Depends on Build (needs the NuGet packages). Runs `react-native init` + `react-native-windows init` against the just-built packages to verify the developer experience works. - -## Agent Pools - -| Pool | Used By | -|------|---------| -| `rnw-pool-4-microsoft` | CI: most jobs (default) | -| `rnw-pool-8-microsoft` | CI: native builds (Desktop, Universal) | -| `rnw-pool-4` | PR: most jobs (default) | -| `rnw-pool-8` | PR: native builds | - -PR uses public pools (no `-microsoft` suffix). The PR entry point overrides the pool defaults. - -## SDL & Compliance - -The 1ES Official template (CI) handles SDL automatically: - -- **CredScan** — credential scanning -- **BinSkim** — binary security analysis (per-job targets via `templateContext.sdl`) -- **Component Governance** — open-source license compliance -- **CodeQL** — static analysis -- **ESLint** — JS/TS analysis (with exclusions) - -PREfast, SpotBugs, and Bandit are disabled (no actionable findings for this repo). - -## Release Flow - -1. Developer merges a PR to `main` -2. **CI pipeline** runs: build, test, sign, pack -3. On success, a separate **prepare-release-bot** pipeline creates/updates a release PR (e.g., `prepare-release/main`) -4. When the release PR is merged (commit message: `RELEASE: ...`), CI runs again as a **release build**: - - Skips beachball bump (versions already committed) - - Builds, tests, signs, packs - - Tags the source commit (`react-native-windows_v`) -5. **Release pipeline** triggers on successful CI, publishing packages to npm and NuGet feeds - -## Key Files - -| File | Purpose | -|------|---------| -| `.ado/ci-pipeline.yml` | CI entry point | -| `.ado/pr-pipeline.yml` | PR entry point | -| `.ado/build-template.yml` | Shared build/test/pack logic | -| `.ado/release.yml` | Release pipeline (publishes to feeds) | -| `.ado/jobs/desktop-single.yml` | Desktop build + test steps | -| `.ado/jobs/universal-single.yml` | Universal build + test steps | -| `.ado/jobs/cli-init-windows.yml` | CLI init verification | -| `.ado/jobs/e2e-test.yml` | E2E test job | -| `.ado/jobs/playground.yml` | Playground build job | -| `.ado/jobs/linting.yml` | Linting job | -| `.ado/jobs/node-tests.yml` | Node/Jest test job | -| `.ado/templates/prep-and-pack-nuget.yml` | NuGet packing steps | -| `.ado/templates/esrp-codesign-*.yml` | ESRP signing steps | diff --git a/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts index 3056f061de0..0b247386978 100644 --- a/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts +++ b/packages/@rnw-scripts/prepare-release/src/prepareRelease.ts @@ -7,7 +7,7 @@ * "Version Packages" pull request. * * Usage: - * npx prepare-release --branch [--bump-only] [--dry-run] [--no-color] [--help] + * npx prepare-release --branch [--dry-run] [--no-color] [--help] * * @format */ @@ -64,15 +64,13 @@ Usage: npx prepare-release --branch [options] Options: - --branch Target branch for version baseline (required) - --bump-only Only run beachball bump in current working tree, skip git/PR operations + --branch Target branch to prepare release for (required) --dry-run Do everything except push and PR create/update --no-color Disable colored output --help, -h Show this help message Examples: npx prepare-release --branch main - npx prepare-release --branch main --bump-only npx prepare-release --branch 0.76-stable --dry-run `); } @@ -163,7 +161,6 @@ async function detectRemote(git: GitRepo, repoUrl: string): Promise { const {values} = parseArgs({ options: { branch: {type: 'string'}, - 'bump-only': {type: 'boolean', default: false}, 'dry-run': {type: 'boolean', default: false}, help: {type: 'boolean', short: 'h', default: false}, 'no-color': {type: 'boolean', default: false}, @@ -184,12 +181,9 @@ async function detectRemote(git: GitRepo, repoUrl: string): Promise { process.exit(1); } - const bumpOnly = values['bump-only']; const dryRun = values['dry-run']; - if (bumpOnly) { - console.log(colorize('[BUMP-ONLY MODE]', ansi.yellow)); - } else if (dryRun) { + if (dryRun) { console.log(colorize('[DRY RUN MODE]', ansi.yellow)); } @@ -231,20 +225,6 @@ async function detectRemote(git: GitRepo, repoUrl: string): Promise { } console.log(colorize('Found pending change files.', ansi.green)); - // 6b. Bump-only mode: run beachball bump in-place and exit - if (bumpOnly) { - console.log(colorize('Running beachball bump...', ansi.bright)); - await bumpVersions({ - targetBranch, - remote: remoteName, - cwd: repoRoot, - }); - console.log( - colorize('Bump complete (--bump-only mode).', ansi.green + ansi.bright), - ); - process.exit(0); - } - // 7. Check for existing PR const prBranch = `prepare-release/${targetBranch}`; console.log(