From ca77fa022dec43ec0989c9a30ea2dd7b40eaec5e Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sat, 28 Mar 2026 01:46:47 +0000 Subject: [PATCH 01/59] Add initial Azure validation pipeline --- .github/workflows/test.yml | 4 +- azure-pipelines.yml | 281 +++++++++++++++++++++++++++++++++++++ 2 files changed, 283 insertions(+), 2 deletions(-) create mode 100644 azure-pipelines.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e5c015e..7f21868 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,9 +2,9 @@ name: Run Tests on: push: - branches: [ main, develop ] + branches: [ main ] pull_request: - branches: [ main, develop ] + branches: [ main ] jobs: test: diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..4cb063e --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,281 @@ +# Hybrid validation pipeline for the GitHub-hosted UE customization repo. +# +# This pipeline is intended to be connected to GitHub through the Azure Pipelines GitHub App +# so its checks surface back into GitHub pull requests targeting main. +# +# The hosted test stage can run immediately. +# The security stage is disabled by default until the Technology project has: +# - a self-hosted Windows pool with UE installed and MSBuild available +# - service connections named `Coverity on Polaris` and `BlackDuckScanner` +# - a variable group named `BlackDuck on Polaris` exposing BLACKDUCK_ACCESS_TOKEN + +resources: + repositories: + - repository: Templates + type: git + name: Dabacon Products/Templates + ref: refs/heads/main + +variables: + - template: variables.yml@Templates + - name: HostedVmImage + value: windows-latest + - name: SecurityPoolName + value: Technology UE Validation + - name: EnableSecurityScans + value: 'false' + - name: CoverityServiceConnection + value: Coverity on Polaris + - name: BlackDuckServiceConnection + value: BlackDuckScanner + - name: BlackDuckGroup + value: DabaconProductsGroup + - name: CoverityProjectTeam + value: .RnD.Polaris.Dabacon-Products.SecurityAdvisors + - name: CoverityCliVersion + value: 2025.9.0 + +trigger: + branches: + include: + - main + +pr: + autoCancel: 'true' + branches: + include: + - main + +stages: + - stage: HostedTests + displayName: Hosted Tests + jobs: + - job: Pester + displayName: Pester + pool: + vmImage: $(HostedVmImage) + steps: + - checkout: self + fetchDepth: '0' + + - pwsh: | + Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" + displayName: Show PowerShell version + + - pwsh: | + if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { + Write-Host "Installing NuGet provider..." + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null + } + displayName: Install NuGet provider + + - pwsh: | + $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue + if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') { + Write-Host "Trusting PSGallery..." + Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted + } + displayName: Trust PSGallery + + - pwsh: | + if (-not (Get-Module -ListAvailable -Name Pester)) { + Write-Host "Installing Pester module..." + Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null + } + displayName: Install Pester + + - pwsh: | + ./scripts/Tests/Run-Tests.ps1 -Quick + displayName: Run tests + + - stage: SecurityValidation + displayName: Security Validation + dependsOn: HostedTests + condition: and(succeeded(), eq(variables['EnableSecurityScans'], 'true')) + jobs: + - job: CoverityAndBlackDuck + displayName: Coverity And Black Duck + timeoutInMinutes: '180' + pool: + name: $(SecurityPoolName) + variables: + - group: BlackDuck on Polaris + steps: + - checkout: self + fetchDepth: '0' + + - checkout: Templates + fetchDepth: '1' + + - pwsh: | + $ueCommand = Get-Command UE -ErrorAction SilentlyContinue + if (-not $ueCommand) { + throw 'UE command is not available on this agent. Use a self-hosted Windows agent with AVEVA Unified Engineering installed.' + } + + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + if (-not (Test-Path -Path $vswhere)) { + throw 'vswhere.exe is not available on this agent. Install Visual Studio Build Tools or Visual Studio.' + } + displayName: Validate agent prerequisites + + - pwsh: | + ./scripts/Setup-UECustomizationEnvironment.ps1 + displayName: Prepare UE customization environment + + - pwsh: | + ./scripts/Build-AddIns.ps1 -Directory templates -Configuration Release + ./scripts/Build-AddIns.ps1 -Directory Examples -Configuration Release + displayName: Build add-in projects + + - pwsh: | + $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" + $msbuildPath = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 + + if (-not $msbuildPath) { + throw 'MSBuild.exe could not be located with vswhere.' + } + + $solutions = Get-ChildItem -Path $env:BUILD_SOURCESDIRECTORY -Recurse -Filter *.sln -File | + Where-Object { + $_.FullName -like "*\\Examples\\*" -or $_.FullName -like "*\\templates\\*" + } | + Sort-Object -Property FullName + + if (-not $solutions) { + throw 'No solution files were found under Examples or templates.' + } + + $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' + $commitDate = ([DateTime](git -C $env:BUILD_SOURCESDIRECTORY log -1 --format=%ci)).ToString('o') + + $cleanCommands = foreach ($solution in $solutions) { + " - shell: ['$msbuildPath', '$($solution.FullName)', '/t:Clean', '/p:Configuration=Release']" + } + + $buildCommands = foreach ($solution in $solutions) { + " - shell: ['$msbuildPath', '$($solution.FullName)', '/t:Build', '/p:Configuration=Release']" + } + + $polarisContent = @( + 'version: 1' + 'project:' + " name: $env:BUILD_REPOSITORY_NAME" + " branch: $branchName" + " projectDir: $env:BUILD_SOURCESDIRECTORY" + ' revision:' + " name: $env:BUILD_SOURCEVERSION" + " date: $commitDate" + 'capture:' + ' build:' + ' cleanCommands:' + $cleanCommands + ' buildCommands:' + $buildCommands + 'analyze:' + ' mode: central' + 'install:' + ' coverity:' + " version: $(CoverityCliVersion)" + 'serverUrl: https://osisoft.cop.blackduck.com' + ) -join "`n" + + $polarisPath = Join-Path $env:PIPELINE_WORKSPACE 'polaris.yml' + $polarisContent | Out-File -FilePath $polarisPath -Encoding ascii -Force + Get-Content -Path $polarisPath + displayName: Generate polaris.yml + + - task: Cache@2 + displayName: Restore Coverity CLI cache + inputs: + key: CoverityCLI | $(CoverityCliVersion) | v1 + path: $(Pipeline.Workspace)\Coverity-CLI + cacheHitVar: COVERITY_CLI_RESTORED + + - pwsh: | + $toolPath = "$(Pipeline.Workspace)\Coverity-CLI" + if (-not (Test-Path -Path $toolPath)) { + New-Item -ItemType Directory -Path $toolPath | Out-Null + } + displayName: Ensure Coverity CLI folder + + - task: BlackduckCoverityOnPolaris@2 + displayName: Coverity scan + inputs: + polarisService: $(CoverityServiceConnection) + polarisCommand: > + -c $(Pipeline.Workspace)\polaris.yml + analyze + -w + env: + AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) + POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI + + - publish: $(Build.SourcesDirectory)\.blackduck + artifact: Coverity + displayName: Publish Coverity artifacts + condition: always() + + - pwsh: | + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $versionName = "$branchName-$($env:BUILD_BUILDID)" + } + + Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" + Write-Host "Black Duck project version: $versionName" + displayName: Calculate Black Duck version + + - task: ArchiveFiles@2 + displayName: Archive sources and build outputs + inputs: + rootFolderOrFile: $(Build.SourcesDirectory) + includeRootFolder: true + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + replaceExistingArchive: true + + - task: BlackDuckDetectTask@10 + displayName: Black Duck scan + inputs: + BlackDuckScaService: $(BlackDuckServiceConnection) + DetectVersion: latest + DetectFolder: $(Pipeline.Workspace)\detect + DetectArguments: | + --detect.project.name=$(Build.Repository.Name) + --detect.project.version.name=$(BlackDuckProjectVersion) + --detect.project.group.name=$(BlackDuckGroup) + --detect.source.path=$(Build.SourcesDirectory) + --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip + --detect.cleanup=true + --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs + --detect.detector.search.depth=10 + --detect.timeout=1800 + --detect.wait.for.results=true + --detect.wait.for.results.timeout=6000000 + --logging.level.detect=DEBUG + --detect.diagnostic=true + + - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs + artifact: BlackDuckLogs + displayName: Publish Black Duck logs + condition: always() + + - pwsh: | + $outputPath = Join-Path $env:PIPELINE_WORKSPACE 'BlackDuckComponentNotices.txt' + & "$(Pipeline.Workspace)\Templates\scripts\BlackDuck\Generate-BlackDuckNotice.ps1" ` + -ProjectName "$(Build.Repository.Name)" ` + -ProjectVersion "$(BlackDuckProjectVersion)" ` + -OutputPath $outputPath + displayName: Generate Black Duck NOTICE + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + env: + BlackduckToken: $(BLACKDUCK_ACCESS_TOKEN) + + - publish: $(Pipeline.Workspace)\BlackDuckComponentNotices.txt + artifact: BlackDuckNotice + displayName: Publish Black Duck NOTICE + condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) \ No newline at end of file From 5aa8f79a1ae1e39e354e15c0725780204b99195c Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sat, 28 Mar 2026 04:53:05 +0000 Subject: [PATCH 02/59] take out blackduck until we get service connection shared --- .github/workflows/test.yml | 80 +++++++-------- azure-pipelines.yml | 198 ++++++++++++++++++++----------------- 2 files changed, 148 insertions(+), 130 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 7f21868..a2bdd2e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,50 +2,50 @@ name: Run Tests on: push: - branches: [ main ] + branches: [main] pull_request: - branches: [ main ] + branches: [main] jobs: test: runs-on: windows-latest steps: - - uses: actions/checkout@v4 - - - name: Set up PowerShell - shell: pwsh - run: | - Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" - - - name: Install NuGet provider - shell: pwsh - run: | - if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { - Write-Host "Installing NuGet provider..." - Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null - } - - - name: Trust PSGallery - shell: pwsh - run: | - $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue - if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') { - Write-Host "Trusting PSGallery..." - Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted - } - - - name: Install Pester - shell: pwsh - run: | - if (-not (Get-Module -ListAvailable -Name Pester)) { - Write-Host "Installing Pester module..." - Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null - } - - - name: Run tests - shell: pwsh - run: | - cd ${{ github.workspace }} - & .\scripts\Tests\Run-Tests.ps1 -Quick - continue-on-error: false + - uses: actions/checkout@v4 + + - name: Set up PowerShell + shell: pwsh + run: | + Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" + + - name: Install NuGet provider + shell: pwsh + run: | + if (-not (Get-PackageProvider -Name NuGet -ListAvailable -ErrorAction SilentlyContinue)) { + Write-Host "Installing NuGet provider..." + Install-PackageProvider -Name NuGet -MinimumVersion 2.8.5.201 -Force | Out-Null + } + + - name: Trust PSGallery + shell: pwsh + run: | + $psGallery = Get-PSRepository -Name 'PSGallery' -ErrorAction SilentlyContinue + if ($psGallery -and $psGallery.InstallationPolicy -ne 'Trusted') { + Write-Host "Trusting PSGallery..." + Set-PSRepository -Name 'PSGallery' -InstallationPolicy Trusted + } + + - name: Install Pester + shell: pwsh + run: | + if (-not (Get-Module -ListAvailable -Name Pester)) { + Write-Host "Installing Pester module..." + Install-Module Pester -Scope CurrentUser -Force -AllowClobber -SkipPublisherCheck | Out-Null + } + + - name: Run tests + shell: pwsh + run: | + cd ${{ github.workspace }} + & .\scripts\Tests\Run-Tests.ps1 -Quick + continue-on-error: false diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4cb063e..4c6dca6 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,7 +4,8 @@ # so its checks surface back into GitHub pull requests targeting main. # # The hosted test stage can run immediately. -# The security stage is disabled by default until the Technology project has: +# The security stage runs on CI builds from main or when doBuildAnalysis=true. +# The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available # - service connections named `Coverity on Polaris` and `BlackDuckScanner` # - a variable group named `BlackDuck on Polaris` exposing BLACKDUCK_ACCESS_TOKEN @@ -22,8 +23,6 @@ variables: value: windows-latest - name: SecurityPoolName value: Technology UE Validation - - name: EnableSecurityScans - value: 'false' - name: CoverityServiceConnection value: Coverity on Polaris - name: BlackDuckServiceConnection @@ -41,7 +40,7 @@ trigger: - main pr: - autoCancel: 'true' + autoCancel: "true" branches: include: - main @@ -56,7 +55,7 @@ stages: vmImage: $(HostedVmImage) steps: - checkout: self - fetchDepth: '0' + fetchDepth: "0" - pwsh: | Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" @@ -91,21 +90,28 @@ stages: - stage: SecurityValidation displayName: Security Validation dependsOn: HostedTests - condition: and(succeeded(), eq(variables['EnableSecurityScans'], 'true')) + condition: | + and( + succeeded(), + or( + eq(variables['Build.SourceBranch'], 'refs/heads/main'), + eq(variables['doBuildAnalysis'], 'true') + ) + ) jobs: - job: CoverityAndBlackDuck displayName: Coverity And Black Duck - timeoutInMinutes: '180' + timeoutInMinutes: "180" pool: name: $(SecurityPoolName) variables: - group: BlackDuck on Polaris steps: - checkout: self - fetchDepth: '0' + fetchDepth: "0" - checkout: Templates - fetchDepth: '1' + fetchDepth: "1" - pwsh: | $ueCommand = Get-Command UE -ErrorAction SilentlyContinue @@ -147,6 +153,11 @@ stages: } $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + $polarisBranch = "$branchName$templatesPostfix" $commitDate = ([DateTime](git -C $env:BUILD_SOURCESDIRECTORY log -1 --format=%ci)).ToString('o') $cleanCommands = foreach ($solution in $solutions) { @@ -161,7 +172,7 @@ stages: 'version: 1' 'project:' " name: $env:BUILD_REPOSITORY_NAME" - " branch: $branchName" + " branch: $polarisBranch" " projectDir: $env:BUILD_SOURCESDIRECTORY" ' revision:' " name: $env:BUILD_SOURCEVERSION" @@ -199,83 +210,90 @@ stages: } displayName: Ensure Coverity CLI folder - - task: BlackduckCoverityOnPolaris@2 - displayName: Coverity scan - inputs: - polarisService: $(CoverityServiceConnection) - polarisCommand: > - -c $(Pipeline.Workspace)\polaris.yml - analyze - -w - env: - AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) - POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI - - - publish: $(Build.SourcesDirectory)\.blackduck - artifact: Coverity - displayName: Publish Coverity artifacts - condition: always() - - - pwsh: | - if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" - } - else { - $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - $versionName = "$branchName-$($env:BUILD_BUILDID)" - } - - Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" - Write-Host "Black Duck project version: $versionName" - displayName: Calculate Black Duck version - - - task: ArchiveFiles@2 - displayName: Archive sources and build outputs - inputs: - rootFolderOrFile: $(Build.SourcesDirectory) - includeRootFolder: true - archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip - replaceExistingArchive: true - - - task: BlackDuckDetectTask@10 - displayName: Black Duck scan - inputs: - BlackDuckScaService: $(BlackDuckServiceConnection) - DetectVersion: latest - DetectFolder: $(Pipeline.Workspace)\detect - DetectArguments: | - --detect.project.name=$(Build.Repository.Name) - --detect.project.version.name=$(BlackDuckProjectVersion) - --detect.project.group.name=$(BlackDuckGroup) - --detect.source.path=$(Build.SourcesDirectory) - --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip - --detect.cleanup=true - --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs - --detect.detector.search.depth=10 - --detect.timeout=1800 - --detect.wait.for.results=true - --detect.wait.for.results.timeout=6000000 - --logging.level.detect=DEBUG - --detect.diagnostic=true - - - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs - artifact: BlackDuckLogs - displayName: Publish Black Duck logs - condition: always() - - - pwsh: | - $outputPath = Join-Path $env:PIPELINE_WORKSPACE 'BlackDuckComponentNotices.txt' - & "$(Pipeline.Workspace)\Templates\scripts\BlackDuck\Generate-BlackDuckNotice.ps1" ` - -ProjectName "$(Build.Repository.Name)" ` - -ProjectVersion "$(BlackDuckProjectVersion)" ` - -OutputPath $outputPath - displayName: Generate Black Duck NOTICE - condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - env: - BlackduckToken: $(BLACKDUCK_ACCESS_TOKEN) - - - publish: $(Pipeline.Workspace)\BlackDuckComponentNotices.txt - artifact: BlackDuckNotice - displayName: Publish Black Duck NOTICE - condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) \ No newline at end of file + # - task: BlackduckCoverityOnPolaris@2 + # displayName: Coverity scan + # inputs: + # polarisService: $(CoverityServiceConnection) + # polarisCommand: > + # -c $(Pipeline.Workspace)\polaris.yml + # analyze + # -w + # env: + # AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) + # POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI + + # - publish: $(Build.SourcesDirectory)\.blackduck + # artifact: Coverity + # displayName: Publish Coverity artifacts + # condition: always() + + # - pwsh: | + # $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + # $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + # ? "@$($templatesBranch)" ` + # : '' + + # if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + # $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" + # } + # else { + # $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + # $versionName = "$branchName-$($env:BUILD_BUILDID)" + # } + + # $versionName = "$versionName$templatesPostfix" + + # Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" + # Write-Host "Black Duck project version: $versionName" + # displayName: Calculate Black Duck version + + # - task: ArchiveFiles@2 + # displayName: Archive sources and build outputs + # inputs: + # rootFolderOrFile: $(Build.SourcesDirectory) + # includeRootFolder: true + # archiveType: zip + # archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + # replaceExistingArchive: true + + # - task: BlackDuckDetectTask@10 + # displayName: Black Duck scan + # inputs: + # BlackDuckScaService: $(BlackDuckServiceConnection) + # DetectVersion: latest + # DetectFolder: $(Pipeline.Workspace)\detect + # DetectArguments: | + # --detect.project.name=$(Build.Repository.Name) + # --detect.project.version.name=$(BlackDuckProjectVersion) + # --detect.project.group.name=$(BlackDuckGroup) + # --detect.source.path=$(Build.SourcesDirectory) + # --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip + # --detect.cleanup=true + # --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs + # --detect.detector.search.depth=10 + # --detect.timeout=1800 + # --detect.wait.for.results=true + # --detect.wait.for.results.timeout=6000000 + # --logging.level.detect=DEBUG + # --detect.diagnostic=true + + # - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs + # artifact: BlackDuckLogs + # displayName: Publish Black Duck logs + # condition: always() + + # - pwsh: | + # $outputPath = Join-Path $env:PIPELINE_WORKSPACE 'BlackDuckComponentNotices.txt' + # & "$(Pipeline.Workspace)\Templates\scripts\BlackDuck\Generate-BlackDuckNotice.ps1" ` + # -ProjectName "$(Build.Repository.Name)" ` + # -ProjectVersion "$(BlackDuckProjectVersion)" ` + # -OutputPath $outputPath + # displayName: Generate Black Duck NOTICE + # condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + # env: + # BlackduckToken: $(BLACKDUCK_ACCESS_TOKEN) + + # - publish: $(Pipeline.Workspace)\BlackDuckComponentNotices.txt + # artifact: BlackDuckNotice + # displayName: Publish Black Duck NOTICE + # condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) From 96a668f59ca23e42be79267d933478b17fa6135e Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sat, 28 Mar 2026 05:06:47 +0000 Subject: [PATCH 03/59] refactor: update Azure pipeline to use specific Ubuntu pool and remove unused variables --- azure-pipelines.yml | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4c6dca6..6dbcb6e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,10 +19,10 @@ resources: variables: - template: variables.yml@Templates - - name: HostedVmImage - value: windows-latest - - name: SecurityPoolName - value: Technology UE Validation + # - name: HostedVmImage + # value: windows-latest + # - name: SecurityPoolName + # value: Technology UE Validation - name: CoverityServiceConnection value: Coverity on Polaris - name: BlackDuckServiceConnection @@ -51,8 +51,14 @@ stages: jobs: - job: Pester displayName: Pester + # pool: + # vmImage: $(HostedVmImage) + container: mcr.microsoft.com/dotnet/sdk:9.0 pool: - vmImage: $(HostedVmImage) + name: Dabacon-Products-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage steps: - checkout: self fetchDepth: "0" @@ -102,8 +108,14 @@ stages: - job: CoverityAndBlackDuck displayName: Coverity And Black Duck timeoutInMinutes: "180" + # pool: + # name: $(SecurityPoolName) + container: mcr.microsoft.com/dotnet/sdk:9.0 pool: - name: $(SecurityPoolName) + name: Dabacon-Products-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage variables: - group: BlackDuck on Polaris steps: From f8882d61d999249aa3c9f8a5bc109c9f55ea7e65 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sat, 28 Mar 2026 05:11:12 +0000 Subject: [PATCH 04/59] HostedVmImage --- azure-pipelines.yml | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6dbcb6e..e0280e5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,8 +19,8 @@ resources: variables: - template: variables.yml@Templates - # - name: HostedVmImage - # value: windows-latest + - name: HostedVmImage + value: windows-latest # - name: SecurityPoolName # value: Technology UE Validation - name: CoverityServiceConnection @@ -51,14 +51,14 @@ stages: jobs: - job: Pester displayName: Pester - # pool: - # vmImage: $(HostedVmImage) - container: mcr.microsoft.com/dotnet/sdk:9.0 pool: - name: Dabacon-Products-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage + vmImage: $(HostedVmImage) + # container: mcr.microsoft.com/dotnet/sdk:9.0 + # pool: + # name: Dabacon-Products-Microsoft-Hosted-Ubuntu + # demands: + # - ImageOverride -equals ubuntu-latest + # - WorkFolder -equals /mnt/storage/sdc/storage steps: - checkout: self fetchDepth: "0" @@ -108,14 +108,16 @@ stages: - job: CoverityAndBlackDuck displayName: Coverity And Black Duck timeoutInMinutes: "180" + pool: + vmImage: $(HostedVmImage) # pool: # name: $(SecurityPoolName) - container: mcr.microsoft.com/dotnet/sdk:9.0 - pool: - name: Dabacon-Products-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage + # container: mcr.microsoft.com/dotnet/sdk:9.0 + # pool: + # name: Dabacon-Products-Microsoft-Hosted-Ubuntu + # demands: + # - ImageOverride -equals ubuntu-latest + # - WorkFolder -equals /mnt/storage/sdc/storage variables: - group: BlackDuck on Polaris steps: From 532ffaa5e28840cfa902317637f247b3bc7b5612 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sun, 29 Mar 2026 02:08:40 +0100 Subject: [PATCH 05/59] Use Technology Templates fork for pipeline checkout --- azure-pipelines.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e0280e5..da9c572 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -5,6 +5,7 @@ # # The hosted test stage can run immediately. # The security stage runs on CI builds from main or when doBuildAnalysis=true. +# Templates is consumed from a Technology fork to avoid cross-project checkout permissions. # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available # - service connections named `Coverity on Polaris` and `BlackDuckScanner` @@ -14,7 +15,7 @@ resources: repositories: - repository: Templates type: git - name: Dabacon Products/Templates + name: Technology/Templates ref: refs/heads/main variables: From f50167b44e6bc2930da2a40600e1c2cf440ddec6 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sun, 29 Mar 2026 16:52:45 +0100 Subject: [PATCH 06/59] Gate UE security validation until infrastructure exists --- azure-pipelines.yml | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index da9c572..e18d06e 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -4,7 +4,8 @@ # so its checks surface back into GitHub pull requests targeting main. # # The hosted test stage can run immediately. -# The security stage runs on CI builds from main or when doBuildAnalysis=true. +# The security stage remains opt-in until the Technology project has a UE-capable +# self-hosted Windows pool or the lane is rewritten into a hosted-compatible flow. # Templates is consumed from a Technology fork to avoid cross-project checkout permissions. # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available @@ -22,6 +23,8 @@ variables: - template: variables.yml@Templates - name: HostedVmImage value: windows-latest + - name: EnableSecurityValidation + value: 'false' # - name: SecurityPoolName # value: Technology UE Validation - name: CoverityServiceConnection @@ -97,14 +100,7 @@ stages: - stage: SecurityValidation displayName: Security Validation dependsOn: HostedTests - condition: | - and( - succeeded(), - or( - eq(variables['Build.SourceBranch'], 'refs/heads/main'), - eq(variables['doBuildAnalysis'], 'true') - ) - ) + condition: and(succeeded(), eq(variables['EnableSecurityValidation'], 'true')) jobs: - job: CoverityAndBlackDuck displayName: Coverity And Black Duck From af9c5de4ca7dae7d0d4d7034db0a5b4c591940a7 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sun, 29 Mar 2026 22:37:26 +0100 Subject: [PATCH 07/59] Enable hosted fake compile for Coverity --- azure-pipelines.yml | 88 +-- scripts/Invoke-FakeUECompile.ps1 | 76 +++ ...a.ApplicationFramework.Presentation.csproj | 6 + shims/Aveva.ApplicationFramework.csproj | 6 + shims/Aveva.Core.Database.Filters.csproj | 6 + shims/Aveva.Core.Database.csproj | 15 + shims/Aveva.Core.Geometry.csproj | 6 + shims/Aveva.Core.Implementation.csproj | 6 + shims/Aveva.Core.Presentation.csproj | 6 + shims/Aveva.Core.Utilities.csproj | 6 + shims/Aveva.Core3D.Clasher.csproj | 6 + shims/Directory.Build.props | 14 + shims/StartUp.csproj | 6 + shims/src/ApplicationFrameworkPresentation.cs | 17 + shims/src/Clasher.cs | 82 +++ shims/src/Database.cs | 614 ++++++++++++++++++ shims/src/Filters.cs | 86 +++ shims/src/Geometry.cs | 45 ++ shims/src/Presentation.cs | 69 ++ shims/src/Utilities.cs | 88 +++ 20 files changed, 1188 insertions(+), 60 deletions(-) create mode 100644 scripts/Invoke-FakeUECompile.ps1 create mode 100644 shims/Aveva.ApplicationFramework.Presentation.csproj create mode 100644 shims/Aveva.ApplicationFramework.csproj create mode 100644 shims/Aveva.Core.Database.Filters.csproj create mode 100644 shims/Aveva.Core.Database.csproj create mode 100644 shims/Aveva.Core.Geometry.csproj create mode 100644 shims/Aveva.Core.Implementation.csproj create mode 100644 shims/Aveva.Core.Presentation.csproj create mode 100644 shims/Aveva.Core.Utilities.csproj create mode 100644 shims/Aveva.Core3D.Clasher.csproj create mode 100644 shims/Directory.Build.props create mode 100644 shims/StartUp.csproj create mode 100644 shims/src/ApplicationFrameworkPresentation.cs create mode 100644 shims/src/Clasher.cs create mode 100644 shims/src/Database.cs create mode 100644 shims/src/Filters.cs create mode 100644 shims/src/Geometry.cs create mode 100644 shims/src/Presentation.cs create mode 100644 shims/src/Utilities.cs diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e18d06e..13db6b8 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -115,8 +115,6 @@ stages: # demands: # - ImageOverride -equals ubuntu-latest # - WorkFolder -equals /mnt/storage/sdc/storage - variables: - - group: BlackDuck on Polaris steps: - checkout: self fetchDepth: "0" @@ -125,43 +123,12 @@ stages: fetchDepth: "1" - pwsh: | - $ueCommand = Get-Command UE -ErrorAction SilentlyContinue - if (-not $ueCommand) { - throw 'UE command is not available on this agent. Use a self-hosted Windows agent with AVEVA Unified Engineering installed.' - } - - $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - if (-not (Test-Path -Path $vswhere)) { - throw 'vswhere.exe is not available on this agent. Install Visual Studio Build Tools or Visual Studio.' - } - displayName: Validate agent prerequisites - - - pwsh: | - ./scripts/Setup-UECustomizationEnvironment.ps1 - displayName: Prepare UE customization environment + ./scripts/Invoke-FakeUECompile.ps1 -Directory Examples -Configuration Release + displayName: Build example projects with fake UE references - pwsh: | - ./scripts/Build-AddIns.ps1 -Directory templates -Configuration Release - ./scripts/Build-AddIns.ps1 -Directory Examples -Configuration Release - displayName: Build add-in projects - - - pwsh: | - $vswhere = "${env:ProgramFiles(x86)}\Microsoft Visual Studio\Installer\vswhere.exe" - $msbuildPath = & $vswhere -latest -products * -requires Microsoft.Component.MSBuild -find MSBuild\**\Bin\MSBuild.exe | Select-Object -First 1 - - if (-not $msbuildPath) { - throw 'MSBuild.exe could not be located with vswhere.' - } - - $solutions = Get-ChildItem -Path $env:BUILD_SOURCESDIRECTORY -Recurse -Filter *.sln -File | - Where-Object { - $_.FullName -like "*\\Examples\\*" -or $_.FullName -like "*\\templates\\*" - } | - Sort-Object -Property FullName - - if (-not $solutions) { - throw 'No solution files were found under Examples or templates.' - } + $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source + $fakeCompileScriptPath = Join-Path $env:BUILD_SOURCESDIRECTORY 'scripts\Invoke-FakeUECompile.ps1' $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') @@ -171,13 +138,13 @@ stages: $polarisBranch = "$branchName$templatesPostfix" $commitDate = ([DateTime](git -C $env:BUILD_SOURCESDIRECTORY log -1 --format=%ci)).ToString('o') - $cleanCommands = foreach ($solution in $solutions) { - " - shell: ['$msbuildPath', '$($solution.FullName)', '/t:Clean', '/p:Configuration=Release']" - } + $cleanCommands = @( + " - shell: ['$pwshPath', '-NoProfile', '-Command', 'Write-Host fake-clean-step']" + ) - $buildCommands = foreach ($solution in $solutions) { - " - shell: ['$msbuildPath', '$($solution.FullName)', '/t:Build', '/p:Configuration=Release']" - } + $buildCommands = @( + " - shell: ['$pwshPath', '-NoProfile', '-File', '$fakeCompileScriptPath', '-Directory', 'Examples', '-Configuration', 'Release']" + ) $polarisContent = @( 'version: 1' @@ -209,8 +176,9 @@ stages: - task: Cache@2 displayName: Restore Coverity CLI cache + retryCountOnTaskFailure: 3 inputs: - key: CoverityCLI | $(CoverityCliVersion) | v1 + key: '"CoverityCLI" | "$(CoverityCliVersion)" | "v1"' path: $(Pipeline.Workspace)\Coverity-CLI cacheHitVar: COVERITY_CLI_RESTORED @@ -221,22 +189,22 @@ stages: } displayName: Ensure Coverity CLI folder - # - task: BlackduckCoverityOnPolaris@2 - # displayName: Coverity scan - # inputs: - # polarisService: $(CoverityServiceConnection) - # polarisCommand: > - # -c $(Pipeline.Workspace)\polaris.yml - # analyze - # -w - # env: - # AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) - # POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI - - # - publish: $(Build.SourcesDirectory)\.blackduck - # artifact: Coverity - # displayName: Publish Coverity artifacts - # condition: always() + - task: BlackduckCoverityOnPolaris@2 + displayName: Coverity scan + inputs: + polarisService: $(CoverityServiceConnection) + polarisCommand: > + -c $(Pipeline.Workspace)\polaris.yml + analyze + -w + env: + AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) + POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI + + - publish: $(Build.SourcesDirectory)\.blackduck + artifact: Coverity + displayName: Publish Coverity artifacts + condition: always() # - pwsh: | # $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 new file mode 100644 index 0000000..9f3e94f --- /dev/null +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -0,0 +1,76 @@ +#Requires -Version 7.0 + +[CmdletBinding()] +param( + [string[]]$Directory = @('Examples'), + [string]$Configuration = 'Release', + [string]$ShimProjectsPath, + [string]$ShimOutputPath +) + +$ErrorActionPreference = 'Stop' + +$repoRoot = (Resolve-Path (Join-Path $PSScriptRoot '..')).Path + +if (-not $ShimProjectsPath) { + $ShimProjectsPath = Join-Path $repoRoot 'shims' +} + +if (-not $ShimOutputPath) { + $ShimOutputPath = Join-Path $ShimProjectsPath "artifacts\$Configuration" +} + +$resolvedShimProjectsPath = (Resolve-Path $ShimProjectsPath).Path +$buildScriptPath = Join-Path $PSScriptRoot 'Build-AddIns.ps1' + +$projects = Get-ChildItem -Path $resolvedShimProjectsPath -Filter '*.csproj' -File | Sort-Object Name +if (-not $projects) { + throw "No shim projects found under $resolvedShimProjectsPath" +} + +foreach ($project in $projects) { + Write-Host ("Building shim assembly: {0}" -f $project.Name) -ForegroundColor Cyan + dotnet build $project.FullName -c $Configuration -nologo + if ($LASTEXITCODE -ne 0) { + throw ("Shim build failed: {0}" -f $project.FullName) + } +} + +$resolvedShimOutputPath = (Resolve-Path $ShimOutputPath).Path +$previousReferencePath = $env:UNIFIED_ENGINEERING_REFERENCE_PATH +$targetDirectories = foreach ($entry in $Directory) { + foreach ($candidate in ($entry -split ',')) { + $trimmed = $candidate.Trim().Trim([char[]]@("'", '"')) + if (-not [string]::IsNullOrWhiteSpace($trimmed)) { + $trimmed + } + } +} + +try { + $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $resolvedShimOutputPath + + if ($env:TF_BUILD) { + Write-Host ("##vso[task.setvariable variable=UNIFIED_ENGINEERING_REFERENCE_PATH]{0}" -f $resolvedShimOutputPath) + } + + Write-Host ("Using fake UE references from: {0}" -f $resolvedShimOutputPath) -ForegroundColor Yellow + + foreach ($targetDirectory in $targetDirectories) { + Write-Host ("Compiling with fake UE references: {0}" -f $targetDirectory) -ForegroundColor Yellow + & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration + if ($LASTEXITCODE -ne 0) { + exit $LASTEXITCODE + } + } + + exit 0 +} +finally { + if ([string]::IsNullOrWhiteSpace($previousReferencePath)) { + Remove-Item Env:UNIFIED_ENGINEERING_REFERENCE_PATH -ErrorAction SilentlyContinue + } + else { + $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $previousReferencePath + } +} \ No newline at end of file diff --git a/shims/Aveva.ApplicationFramework.Presentation.csproj b/shims/Aveva.ApplicationFramework.Presentation.csproj new file mode 100644 index 0000000..9e8e054 --- /dev/null +++ b/shims/Aveva.ApplicationFramework.Presentation.csproj @@ -0,0 +1,6 @@ + + + Aveva.ApplicationFramework.Presentation + Aveva.ApplicationFramework.Presentation + + \ No newline at end of file diff --git a/shims/Aveva.ApplicationFramework.csproj b/shims/Aveva.ApplicationFramework.csproj new file mode 100644 index 0000000..a2e5b91 --- /dev/null +++ b/shims/Aveva.ApplicationFramework.csproj @@ -0,0 +1,6 @@ + + + Aveva.ApplicationFramework + Aveva.ApplicationFramework + + \ No newline at end of file diff --git a/shims/Aveva.Core.Database.Filters.csproj b/shims/Aveva.Core.Database.Filters.csproj new file mode 100644 index 0000000..dc9aa9a --- /dev/null +++ b/shims/Aveva.Core.Database.Filters.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.Database.Filters + Aveva.Core.Database.Filters + + \ No newline at end of file diff --git a/shims/Aveva.Core.Database.csproj b/shims/Aveva.Core.Database.csproj new file mode 100644 index 0000000..2e48bcf --- /dev/null +++ b/shims/Aveva.Core.Database.csproj @@ -0,0 +1,15 @@ + + + Aveva.Core.Database + Aveva.Core.Database + + + + + + + + + + + \ No newline at end of file diff --git a/shims/Aveva.Core.Geometry.csproj b/shims/Aveva.Core.Geometry.csproj new file mode 100644 index 0000000..5e9fd28 --- /dev/null +++ b/shims/Aveva.Core.Geometry.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.Geometry + Aveva.Core.Geometry + + \ No newline at end of file diff --git a/shims/Aveva.Core.Implementation.csproj b/shims/Aveva.Core.Implementation.csproj new file mode 100644 index 0000000..d4f4ed5 --- /dev/null +++ b/shims/Aveva.Core.Implementation.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.Implementation + Aveva.Core.Implementation + + \ No newline at end of file diff --git a/shims/Aveva.Core.Presentation.csproj b/shims/Aveva.Core.Presentation.csproj new file mode 100644 index 0000000..9033125 --- /dev/null +++ b/shims/Aveva.Core.Presentation.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.Presentation + Aveva.Core.Presentation + + \ No newline at end of file diff --git a/shims/Aveva.Core.Utilities.csproj b/shims/Aveva.Core.Utilities.csproj new file mode 100644 index 0000000..f3972c3 --- /dev/null +++ b/shims/Aveva.Core.Utilities.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.Utilities + Aveva.Core.Utilities + + \ No newline at end of file diff --git a/shims/Aveva.Core3D.Clasher.csproj b/shims/Aveva.Core3D.Clasher.csproj new file mode 100644 index 0000000..e32e24a --- /dev/null +++ b/shims/Aveva.Core3D.Clasher.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core3D.Clasher + Aveva.Core3D.Clasher + + \ No newline at end of file diff --git a/shims/Directory.Build.props b/shims/Directory.Build.props new file mode 100644 index 0000000..45bc4f9 --- /dev/null +++ b/shims/Directory.Build.props @@ -0,0 +1,14 @@ + + + netstandard2.0 + latest + disable + disable + false + false + 1591 + $(MSBuildThisFileDirectory)artifacts\$(Configuration)\ + false + false + + \ No newline at end of file diff --git a/shims/StartUp.csproj b/shims/StartUp.csproj new file mode 100644 index 0000000..fc28b78 --- /dev/null +++ b/shims/StartUp.csproj @@ -0,0 +1,6 @@ + + + StartUp + StartUp + + \ No newline at end of file diff --git a/shims/src/ApplicationFrameworkPresentation.cs b/shims/src/ApplicationFrameworkPresentation.cs new file mode 100644 index 0000000..3beee45 --- /dev/null +++ b/shims/src/ApplicationFrameworkPresentation.cs @@ -0,0 +1,17 @@ +using System.Collections.Generic; + +namespace Aveva.ApplicationFramework.Presentation +{ + public class Command + { + public string Key { get; set; } + + public IList List { get; } = new List(); + + public object Value { get; set; } + + public virtual void Execute() + { + } + } +} \ No newline at end of file diff --git a/shims/src/Clasher.cs b/shims/src/Clasher.cs new file mode 100644 index 0000000..61101e0 --- /dev/null +++ b/shims/src/Clasher.cs @@ -0,0 +1,82 @@ +using System; +using Aveva.Core.Database; +using Aveva.Core.Geometry; + +namespace Aveva.Core3D.Clasher +{ + public enum SpatialMapStatus + { + Unknown, + Complete, + } + + public class SpatialMap + { + public static SpatialMap Instance { get; } = new SpatialMap(); + + public SpatialMapStatus CheckSpatialMap(Db db) + { + return SpatialMapStatus.Complete; + } + + public void BuildSpatialMap() + { + } + } + + public class ClashOptions + { + public bool IncludeTouches { get; set; } + + public double Clearance { get; set; } + + public static ClashOptions Create() + { + return new ClashOptions(); + } + } + + public class ObstructionList + { + public static ObstructionList Create() + { + return new ObstructionList(); + } + } + + public class ClashSet + { + public Clash[] Clashes { get; set; } = Array.Empty(); + + public static ClashSet Create() + { + return new ClashSet(); + } + } + + public class Clasher + { + public static Clasher Instance { get; } = new Clasher(); + + public bool CheckAll(ClashOptions options, ObstructionList obstructions, ClashSet clashSet) + { + return false; + } + } + + public class Clash + { + public DbElement First { get; set; } = new DbElement(); + + public DbElement Second { get; set; } = new DbElement(); + + public ClashType Type { get; set; } + + public Position ClashPosition { get; set; } = Position.Create(0d, 0d, 0d); + } + + public enum ClashType + { + None, + } +} \ No newline at end of file diff --git a/shims/src/Database.cs b/shims/src/Database.cs new file mode 100644 index 0000000..cf0147a --- /dev/null +++ b/shims/src/Database.cs @@ -0,0 +1,614 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using Aveva.Core.Geometry; +using Aveva.Core.Utilities.Messaging; + +namespace Aveva.Core.Database +{ + public class DbAttribute + { + public DbAttribute() + { + } + + public DbAttribute(string name) + { + Name = name; + } + + public string Name { get; set; } + + public static DbAttribute GetDbAttribute(string name) + { + return new DbAttribute(name); + } + } + + public static class DbAttributeInstance + { + public static readonly DbAttribute NAME = new DbAttribute(nameof(NAME)); + public static readonly DbAttribute DESC = new DbAttribute(nameof(DESC)); + public static readonly DbAttribute BUIL = new DbAttribute(nameof(BUIL)); + public static readonly DbAttribute REV = new DbAttribute(nameof(REV)); + public static readonly DbAttribute PRES = new DbAttribute(nameof(PRES)); + public static readonly DbAttribute HREF = new DbAttribute(nameof(HREF)); + public static readonly DbAttribute LNTP = new DbAttribute(nameof(LNTP)); + public static readonly DbAttribute HDIR = new DbAttribute(nameof(HDIR)); + public static readonly DbAttribute POS = new DbAttribute(nameof(POS)); + public static readonly DbAttribute ORI = new DbAttribute(nameof(ORI)); + public static readonly DbAttribute FLNN = new DbAttribute(nameof(FLNN)); + public static readonly DbAttribute AREA = new DbAttribute(nameof(AREA)); + public static readonly DbAttribute CREF = new DbAttribute(nameof(CREF)); + public static readonly DbAttribute ISNAME = new DbAttribute(nameof(ISNAME)); + public static readonly DbAttribute MEMB = new DbAttribute(nameof(MEMB)); + public static readonly DbAttribute DIAM = new DbAttribute(nameof(DIAM)); + public static readonly DbAttribute LCLM = new DbAttribute(nameof(LCLM)); + public static readonly DbAttribute LCLMH = new DbAttribute(nameof(LCLMH)); + public static readonly DbAttribute XLEN = new DbAttribute(nameof(XLEN)); + public static readonly DbAttribute YLEN = new DbAttribute(nameof(YLEN)); + public static readonly DbAttribute ZLEN = new DbAttribute(nameof(ZLEN)); + } + + public class DbElementType + { + public DbElementType() + { + } + + public DbElementType(string name) + { + Name = name; + } + + public string Name { get; set; } + + public static DbElementType GetElementType(int hash) + { + return new DbElementType(hash.ToString()); + } + + public static DbElementType GetElementType(string name) + { + return new DbElementType(name); + } + + public DbAttribute[] SystemAttributes() + { + return Array.Empty(); + } + } + + public static class DbElementTypeInstance + { + public static readonly DbElementType SITE = new DbElementType(nameof(SITE)); + public static readonly DbElementType ZONE = new DbElementType(nameof(ZONE)); + public static readonly DbElementType EQUIPMENT = new DbElementType(nameof(EQUIPMENT)); + public static readonly DbElementType CYLINDER = new DbElementType(nameof(CYLINDER)); + public static readonly DbElementType BOX = new DbElementType(nameof(BOX)); + public static readonly DbElementType NOZZLE = new DbElementType(nameof(NOZZLE)); + public static readonly DbElementType PIPE = new DbElementType(nameof(PIPE)); + public static readonly DbElementType BRANCH = new DbElementType(nameof(BRANCH)); + public static readonly DbElementType GASKET = new DbElementType(nameof(GASKET)); + public static readonly DbElementType ELBOW = new DbElementType(nameof(ELBOW)); + public static readonly DbElementType TEE = new DbElementType(nameof(TEE)); + } + + public class DbElement + { + public static DbElement GetElement(string name) + { + return new DbElement + { + Name = name, + IsValid = true, + }; + } + + public string Name { get; set; } + + public bool IsValid { get; set; } = true; + + public DbElement Previous { get; set; } = new DbElement(false); + + public DbElement() + { + } + + private DbElement(bool valid) + { + IsValid = valid; + } + + public void Delete() + { + } + + public DbElement Create(int position, DbElementType elementType) + { + return new DbElement(); + } + + public DbElement CreateAfter(DbElementType elementType) + { + return new DbElement(); + } + + public DbElement CreateFirst(DbElementType elementType) + { + return new DbElement(); + } + + public DbElement CreateLast(DbElementType elementType) + { + return new DbElement(); + } + + public void SetAttribute(DbAttribute attribute, object value) + { + } + + public string GetString(DbAttribute attribute) + { + return string.Empty; + } + + public bool GetBool(DbAttribute attribute) + { + return false; + } + + public int GetInteger(DbAttribute attribute) + { + return 0; + } + + public double GetDouble(DbAttribute attribute) + { + return 0d; + } + + public DbElement GetElement(DbAttribute attribute) + { + return new DbElement(); + } + + public Direction GetDirection(DbAttribute attribute) + { + return new Direction(); + } + + public Position GetPosition(DbAttribute attribute) + { + return Position.Create(0d, 0d, 0d); + } + + public Orientation GetOrientation(DbAttribute attribute) + { + return new Orientation(); + } + + public string GetAsString(DbAttribute attribute) + { + return string.Empty; + } + + public void Copy(DbElement element) + { + } + + public void CopyHierarchy(DbElement element, DbCopyOption options) + { + } + + public DbElement FirstMember() + { + return new DbElement(); + } + + public DbElement FirstMember(DbElementType type) + { + return new DbElement(); + } + + public void InsertAfterLast(DbElement element) + { + } + + public void InsertAfter(DbElement element) + { + } + + public DbElement Next() + { + return new DbElement(); + } + + public DbElement Next(DbElementType type) + { + return new DbElement(); + } + + public DbElement[] Members() + { + return Array.Empty(); + } + + public DbElement[] Members(DbElementType type) + { + return Array.Empty(); + } + + public DbElement Member(int index) + { + return new DbElement(); + } + + public DbElement[] GetElementArray(DbAttribute attribute, DbElementType type) + { + return Array.Empty(); + } + + public double EvaluateDouble(DbExpression expression, DbAttributeUnit unit) + { + return 0d; + } + + public void SetRule(DbAttribute attribute, DbRule rule) + { + } + + public DbRule GetRule(DbAttribute attribute) + { + return new DbRule(); + } + + public void DeleteRule(DbAttribute attribute) + { + } + + public bool ExistRule(DbAttribute attribute) + { + return false; + } + + public bool VerifyRule(DbAttribute attribute) + { + return false; + } + + public void ExecuteRule(DbAttribute attribute) + { + } + + public void ExecuteAllRules() + { + } + + public void PropagateRules(DbAttribute attribute) + { + } + + public void Claim() + { + } + + public void Release() + { + } + + public void ClaimHierarchy() + { + } + + public void ReleaseHierarchy() + { + } + + public DbElementType GetElementType() + { + return new DbElementType(); + } + + public void ChangeType(DbElementType type) + { + } + + public bool AtDefault(DbAttribute attribute) + { + return true; + } + + public bool IsDescendant(DbElement element) + { + return false; + } + + public void SetAttributeDefault(DbAttribute attribute) + { + } + } + + public class DBElementCollection : IEnumerable + { + public DBElementCollection(DbElement root) + { + } + + public DBElementCollection(DbElement root, object filter) + { + } + + public bool IncludeRoot { get; set; } + + public object Filter { get; set; } + + public DBElementEnumerator GetEnumerator() + { + return new DBElementEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public class DBElementEnumerator : IEnumerator + { + public DbElement Current => new DbElement(); + + object IEnumerator.Current => Current; + + public bool MoveNext() + { + return false; + } + + public void Reset() + { + } + + public void Dispose() + { + } + } + + public class DbCopyOption + { + public string FromName { get; set; } + + public string ToName { get; set; } + + public bool Rename { get; set; } + } + + public class DbExpression + { + public static DbExpression Parse(string expression) + { + return new DbExpression(); + } + } + + public enum DbAttributeUnit + { + DIST, + } + + public enum DbRuleStatus + { + DYNAMIC, + } + + public enum DbExpressionType + { + REAL, + } + + public class DbRule + { + public static DbRule CreateDbRule(DbExpression expression, DbRuleStatus status, DbExpressionType expressionType) + { + return new DbRule(); + } + } + + public class Project + { + public static Project CurrentProject { get; } = new Project(); + + public string Name { get; set; } = string.Empty; + + public string UserName { get; set; } = string.Empty; + + public bool IsOpen() + { + return false; + } + + public void Open(string project, string username, string password) + { + Name = project; + UserName = username; + } + + public void Close() + { + } + } + + public class MDB + { + public static MDB CurrentMDB { get; } = new MDB(); + + public string Name { get; set; } = string.Empty; + + public void SaveWork(string description) + { + } + + public void CloseMDB() + { + } + + public void Open(MDBSetup setup, out PdmsMessage error) + { + error = new PdmsMessage(); + } + + public Db[] GetDBArray(DbType type) + { + return Array.Empty(); + } + + public DbElement GetFirstWorld(DbType type) + { + return new DbElement(); + } + + public void QuitWork() + { + } + } + + public class MDBSetup + { + public static MDBSetup CreateMDBSetup(string name, out PdmsMessage error) + { + error = new PdmsMessage(); + return new MDBSetup(); + } + } + + public class Db + { + public string Name { get; set; } = string.Empty; + } + + public enum DbType + { + Design, + } + + public class NameTable : IEnumerable + { + public static NameTable GetNameTable(Db db, DbAttribute attribute, string prefix, string suffix) + { + return new NameTable(); + } + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)Array.Empty()).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public class ElementTreeNavigator + { + public ElementTreeNavigator(DbElement root, object filter) + { + } + + public DbElement FirstMemberInScan(DbElement element) + { + return new DbElement(); + } + + public DbElement NextInScan(DbElement element) + { + return new DbElement(); + } + + public DbElement Parent(DbElement element) + { + return new DbElement(); + } + + public DbElement[] MembersInScan(DbElement element) + { + return Array.Empty(); + } + } + + public class DbQualifier + { + } + + public static class DbPseudoAttribute + { + public delegate double GetDoubleDelegate(DbElement element, DbAttribute attribute, DbQualifier qualifier); + + public static void AddGetDoubleAttribute(DbAttribute attribute, DbElementType type, GetDoubleDelegate callback) + { + } + } + + public static class DbPostElementChange + { + public delegate void PostCreateDelegate(DbElement element); + + public delegate void PreDeleteDelegate(DbElement element); + + public delegate void PostMoveDelegate(DbElement element, DbElement oldOwner, int oldPosition); + + public delegate void PostAttributeChangeDelegate(DbElement element, DbAttribute attribute); + + public delegate void PostRefAttributeChangeDelegate(DbElement element, DbAttribute attribute, DbElement oldReference); + + public static void AddPostCreateElement(DbElementType type, PostCreateDelegate callback) + { + } + + public static void AddPreDeleteElement(DbElementType type, PreDeleteDelegate callback) + { + } + + public static void AddPostMoveElement(DbElementType type, PostMoveDelegate callback) + { + } + + public static void AddPostAttributeChange(DbAttribute attribute, PostAttributeChangeDelegate callback) + { + } + + public static void AddPostRefAttributeChange(DbAttribute attribute, PostRefAttributeChangeDelegate callback) + { + } + } + + public class Spatial + { + public static Spatial Instance { get; } = new Spatial(); + + public DbElement[] ElementsInBox(LimitsBox limitsBox, bool fullyWithin) + { + return Array.Empty(); + } + + public DbElement[] ElementsInElementBox(DbElement element, bool fullyWithin) + { + return Array.Empty(); + } + + public bool ElementInBox(DbElement element, LimitsBox limitsBox) + { + return false; + } + + public bool ElementInElementBox(DbElement first, DbElement second) + { + return false; + } + } +} \ No newline at end of file diff --git a/shims/src/Filters.cs b/shims/src/Filters.cs new file mode 100644 index 0000000..7186cd6 --- /dev/null +++ b/shims/src/Filters.cs @@ -0,0 +1,86 @@ +using Aveva.Core.Database; + +namespace Aveva.Core.Database.Filters +{ + public class TypeFilter + { + public TypeFilter() + { + } + + public TypeFilter(DbElementType type) + { + } + + public void Add(DbElementType type) + { + } + + public DbElement FirstMember(DbElement element) + { + return new DbElement(); + } + + public DbElement Next(DbElement element) + { + return new DbElement(); + } + + public DbElement[] Members(DbElement element) + { + return System.Array.Empty(); + } + + public DbElement Parent(DbElement element) + { + return new DbElement(); + } + } + + public class CompoundFilter + { + public void AddShow(object filter) + { + } + } + + public class AndFilter : CompoundFilter + { + public void Add(object filter) + { + } + + public bool Valid(DbElement element) + { + return true; + } + } + + public class TrueFilter + { + } + + public class AttributeTrueFilter + { + public AttributeTrueFilter(DbAttribute attribute) + { + } + + public bool Valid(DbElement element) + { + return true; + } + } + + public class BelowOrAtType + { + public BelowOrAtType(DbElementType type) + { + } + + public bool ScanBelow(DbElement element) + { + return true; + } + } +} \ No newline at end of file diff --git a/shims/src/Geometry.cs b/shims/src/Geometry.cs new file mode 100644 index 0000000..6d241e6 --- /dev/null +++ b/shims/src/Geometry.cs @@ -0,0 +1,45 @@ +namespace Aveva.Core.Geometry +{ + public class Direction + { + public static Direction Create(double x, double y, double z) + { + return new Direction(); + } + } + + public class Position + { + public double X { get; set; } + + public double Y { get; set; } + + public double Z { get; set; } + + public static Position Create(double x, double y, double z) + { + return new Position + { + X = x, + Y = y, + Z = z, + }; + } + } + + public class Orientation + { + public static Orientation Create(Direction xAxis, Direction yAxis) + { + return new Orientation(); + } + } + + public class LimitsBox + { + public static LimitsBox Create(Position first, Position second) + { + return new LimitsBox(); + } + } +} \ No newline at end of file diff --git a/shims/src/Presentation.cs b/shims/src/Presentation.cs new file mode 100644 index 0000000..678c97f --- /dev/null +++ b/shims/src/Presentation.cs @@ -0,0 +1,69 @@ +using System; +using Aveva.Core.Database; + +namespace Aveva.Core.Presentation +{ + public delegate void CurrentElementChangedEventHandler(object sender, CurrentElementChangedEventArgs e); + + public class CurrentElementChangedEventArgs : EventArgs + { + public DbElement Element { get; set; } = new DbElement(); + } + + public static class CurrentElement + { + private static DbElement s_element = new DbElement(); + + public static event CurrentElementChangedEventHandler CurrentElementChanged; + + public static DbElement Element + { + get => s_element; + set + { + s_element = value; + CurrentElementChanged?.Invoke(null, new CurrentElementChangedEventArgs { Element = value }); + } + } + } + + public delegate void DbChangesEventHandler(object sender, DbChangesEventArgs e); + + public class DbChangesEventArgs : EventArgs + { + public ChangeList ChangeList { get; } = new ChangeList(); + } + + public class ChangeList + { + public DbElement[] GetCreations() + { + return Array.Empty(); + } + + public DbElement[] GetDeletions() + { + return Array.Empty(); + } + + public DbElement[] GetMemberChanges() + { + return Array.Empty(); + } + + public DbElement[] GetModified() + { + return Array.Empty(); + } + } + + public static class DatabaseService + { + public static event DbChangesEventHandler Changes; + + public static void RaiseChanges() + { + Changes?.Invoke(null, new DbChangesEventArgs()); + } + } +} \ No newline at end of file diff --git a/shims/src/Utilities.cs b/shims/src/Utilities.cs new file mode 100644 index 0000000..f22c493 --- /dev/null +++ b/shims/src/Utilities.cs @@ -0,0 +1,88 @@ +using System; + +namespace Aveva.Core.Utilities.Messaging +{ + public class PdmsMessage + { + } + + public class PdmsException : Exception + { + public PdmsException() + { + } + + public PdmsException(string message) : base(message) + { + } + } +} + +namespace Aveva.Core.Utilities.Undo +{ + public class UndoTransaction + { + public static UndoTransaction GetUndoTransaction() + { + return new UndoTransaction(); + } + + public void StartTransaction(string description) + { + } + + public void EndTransaction() + { + } + + public static void PerformUndo() + { + } + + public static void PerformRedo() + { + } + } + + public abstract class UndoSubscriber + { + public virtual object GetBeginState() + { + return null; + } + + public virtual object GetEndState() + { + return null; + } + + public virtual void RestoreBeginState(object state) + { + } + + public virtual void RestoreEndState(object state) + { + } + } + + public static class UndoCaretaker + { + public static void RegisterUndoSubscriber(UndoSubscriber subscriber) + { + } + + public static void RemoveUndoSubscriber(UndoSubscriber subscriber) + { + } + } +} + +namespace Aveva.Core.Utilities.CommandLine +{ + public static class Command + { + public static void Update() + { + } + } +} \ No newline at end of file From ed12affdc7a87e7b4dc3fbcadbb27ca059558aca Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sun, 29 Mar 2026 23:15:07 +0100 Subject: [PATCH 08/59] Expand fake compile shims for templates --- azure-pipelines.yml | 6 +- scripts/Build-AddIns.ps1 | 19 +- scripts/Invoke-FakeUECompile.ps1 | 2 +- ...ApplicationFramework.Implementation.csproj | 6 + shims/Aveva.Core.Database.csproj | 8 +- ...Aveva.Core.FlexibleExplorer.Control.csproj | 6 + shims/Aveva.Core.FlexibleExplorer.Core.csproj | 6 + shims/Directory.Build.props | 3 +- shims/GridControl.csproj | 6 + shims/Microsoft.Web.WebView2.Core.csproj | 6 + shims/Microsoft.Web.WebView2.WinForms.csproj | 6 + shims/Newtonsoft.Json.csproj | 6 + shims/PMLNet.csproj | 6 + shims/src/ApplicationFrameworkPresentation.cs | 282 ++++++++++++++++++ shims/src/Database.cs | 6 + shims/src/FlexibleExplorer.cs | 73 +++++ shims/src/Json.cs | 80 +++++ shims/src/PmlNet.cs | 15 + shims/src/Presentation.cs | 72 +++++ shims/src/Utilities.cs | 10 + shims/src/WebView2.cs | 65 ++++ .../FlexibleExplorerAddInTemplate.cs | 1 + .../MyFlexibleExplorerControl.cs | 6 +- 23 files changed, 679 insertions(+), 17 deletions(-) create mode 100644 shims/Aveva.ApplicationFramework.Implementation.csproj create mode 100644 shims/Aveva.Core.FlexibleExplorer.Control.csproj create mode 100644 shims/Aveva.Core.FlexibleExplorer.Core.csproj create mode 100644 shims/GridControl.csproj create mode 100644 shims/Microsoft.Web.WebView2.Core.csproj create mode 100644 shims/Microsoft.Web.WebView2.WinForms.csproj create mode 100644 shims/Newtonsoft.Json.csproj create mode 100644 shims/PMLNet.csproj create mode 100644 shims/src/FlexibleExplorer.cs create mode 100644 shims/src/Json.cs create mode 100644 shims/src/PmlNet.cs create mode 100644 shims/src/WebView2.cs diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 13db6b8..4bb3922 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -123,8 +123,8 @@ stages: fetchDepth: "1" - pwsh: | - ./scripts/Invoke-FakeUECompile.ps1 -Directory Examples -Configuration Release - displayName: Build example projects with fake UE references + ./scripts/Invoke-FakeUECompile.ps1 -Directory templates,Examples -Configuration Release + displayName: Build template and example projects with fake UE references - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source @@ -143,7 +143,7 @@ stages: ) $buildCommands = @( - " - shell: ['$pwshPath', '-NoProfile', '-File', '$fakeCompileScriptPath', '-Directory', 'Examples', '-Configuration', 'Release']" + " - shell: ['$pwshPath', '-NoProfile', '-File', '$fakeCompileScriptPath', '-Directory', 'templates,Examples', '-Configuration', 'Release']" ) $polarisContent = @( diff --git a/scripts/Build-AddIns.ps1 b/scripts/Build-AddIns.ps1 index 932ddde..4bc8019 100644 --- a/scripts/Build-AddIns.ps1 +++ b/scripts/Build-AddIns.ps1 @@ -1,7 +1,8 @@ param( [Parameter(Mandatory = $false)] [string]$Directory = "source", - [string]$Configuration = "Debug" + [string]$Configuration = "Debug", + [switch]$SkipPostBuildEvent ) $ErrorActionPreference = "Stop" @@ -33,7 +34,21 @@ if (-not $projects) { $failed = @() foreach ($proj in $projects) { Write-Host "--- Building $($proj.FullName) ---" -ForegroundColor Cyan - dotnet build $proj.FullName -c $Configuration -clp:Summary -nologo + $buildArgs = @( + 'build' + $proj.FullName + '-c' + $Configuration + '-clp:Summary' + '-nologo' + ) + + if ($SkipPostBuildEvent) { + $buildArgs += '-p:RunPostBuildEvent=Never' + $buildArgs += '-p:PostBuildEvent=' + } + + dotnet @buildArgs if ($LASTEXITCODE -ne 0) { $failed += $proj.FullName } diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 index 9f3e94f..9cf4aee 100644 --- a/scripts/Invoke-FakeUECompile.ps1 +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -58,7 +58,7 @@ try { foreach ($targetDirectory in $targetDirectories) { Write-Host ("Compiling with fake UE references: {0}" -f $targetDirectory) -ForegroundColor Yellow - & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration + & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration -SkipPostBuildEvent if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } diff --git a/shims/Aveva.ApplicationFramework.Implementation.csproj b/shims/Aveva.ApplicationFramework.Implementation.csproj new file mode 100644 index 0000000..2ea7f56 --- /dev/null +++ b/shims/Aveva.ApplicationFramework.Implementation.csproj @@ -0,0 +1,6 @@ + + + Aveva.ApplicationFramework.Implementation + Aveva.ApplicationFramework.Implementation + + \ No newline at end of file diff --git a/shims/Aveva.Core.Database.csproj b/shims/Aveva.Core.Database.csproj index 2e48bcf..925e3b9 100644 --- a/shims/Aveva.Core.Database.csproj +++ b/shims/Aveva.Core.Database.csproj @@ -4,12 +4,6 @@ Aveva.Core.Database - - - - - - - + \ No newline at end of file diff --git a/shims/Aveva.Core.FlexibleExplorer.Control.csproj b/shims/Aveva.Core.FlexibleExplorer.Control.csproj new file mode 100644 index 0000000..df56982 --- /dev/null +++ b/shims/Aveva.Core.FlexibleExplorer.Control.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.FlexibleExplorer.Control + Aveva.Core.FlexibleExplorer.Control + + \ No newline at end of file diff --git a/shims/Aveva.Core.FlexibleExplorer.Core.csproj b/shims/Aveva.Core.FlexibleExplorer.Core.csproj new file mode 100644 index 0000000..4aaf960 --- /dev/null +++ b/shims/Aveva.Core.FlexibleExplorer.Core.csproj @@ -0,0 +1,6 @@ + + + Aveva.Core.FlexibleExplorer.Core + Aveva.Core.FlexibleExplorer.Core + + \ No newline at end of file diff --git a/shims/Directory.Build.props b/shims/Directory.Build.props index 45bc4f9..8adb7f3 100644 --- a/shims/Directory.Build.props +++ b/shims/Directory.Build.props @@ -1,9 +1,10 @@ - netstandard2.0 + net48 latest disable disable + true false false 1591 diff --git a/shims/GridControl.csproj b/shims/GridControl.csproj new file mode 100644 index 0000000..414d294 --- /dev/null +++ b/shims/GridControl.csproj @@ -0,0 +1,6 @@ + + + GridControl + GridControl + + \ No newline at end of file diff --git a/shims/Microsoft.Web.WebView2.Core.csproj b/shims/Microsoft.Web.WebView2.Core.csproj new file mode 100644 index 0000000..4fc1c1b --- /dev/null +++ b/shims/Microsoft.Web.WebView2.Core.csproj @@ -0,0 +1,6 @@ + + + Microsoft.Web.WebView2.Core + Microsoft.Web.WebView2.Core + + \ No newline at end of file diff --git a/shims/Microsoft.Web.WebView2.WinForms.csproj b/shims/Microsoft.Web.WebView2.WinForms.csproj new file mode 100644 index 0000000..d422918 --- /dev/null +++ b/shims/Microsoft.Web.WebView2.WinForms.csproj @@ -0,0 +1,6 @@ + + + Microsoft.Web.WebView2.WinForms + Microsoft.Web.WebView2.WinForms + + \ No newline at end of file diff --git a/shims/Newtonsoft.Json.csproj b/shims/Newtonsoft.Json.csproj new file mode 100644 index 0000000..ccdbc9f --- /dev/null +++ b/shims/Newtonsoft.Json.csproj @@ -0,0 +1,6 @@ + + + Newtonsoft.Json + Newtonsoft.Json + + \ No newline at end of file diff --git a/shims/PMLNet.csproj b/shims/PMLNet.csproj new file mode 100644 index 0000000..cfef247 --- /dev/null +++ b/shims/PMLNet.csproj @@ -0,0 +1,6 @@ + + + PMLNet + PMLNet + + \ No newline at end of file diff --git a/shims/src/ApplicationFrameworkPresentation.cs b/shims/src/ApplicationFrameworkPresentation.cs index 3beee45..cf16152 100644 --- a/shims/src/ApplicationFrameworkPresentation.cs +++ b/shims/src/ApplicationFrameworkPresentation.cs @@ -1,4 +1,70 @@ +using System; using System.Collections.Generic; +using System.ComponentModel; +using System.Windows.Forms; + +namespace Aveva.ApplicationFramework +{ + public interface IAddinInjected + { + string Name { get; } + + string Description { get; } + + void Start(IDependencyResolver resolver); + + void Start(ServiceManager serviceManager); + + void Stop(); + } + + public interface IDependencyResolver + { + } + + public sealed class ServiceManager + { + } + + public static class DependencyResolver + { + private static readonly Aveva.ApplicationFramework.Presentation.WindowManager s_windowManager = new Aveva.ApplicationFramework.Presentation.WindowManager(); + private static readonly Aveva.ApplicationFramework.Presentation.CommandManager s_commandManager = new Aveva.ApplicationFramework.Presentation.CommandManager(); + private static readonly Dictionary s_services = new Dictionary + { + { typeof(Aveva.ApplicationFramework.Presentation.IWindowManager), s_windowManager }, + { typeof(Aveva.ApplicationFramework.Presentation.ICommandManager), s_commandManager }, + { typeof(Aveva.ApplicationFramework.Presentation.ICommandBarManager), s_windowManager.CommandBarManager }, + }; + + public static T GetImplementationOf() where T : class + { + if (s_services.TryGetValue(typeof(T), out object service)) + { + return service as T; + } + + return null; + } + } + + public static class Log + { + private static readonly LoggerProxy s_logger = new LoggerProxy(); + + public static LoggerProxy Logger() + { + return s_logger; + } + } + + public sealed class LoggerProxy + { + public void MessageFormat(string format, params object[] args) + { + } + } +} namespace Aveva.ApplicationFramework.Presentation { @@ -10,8 +76,224 @@ public class Command public object Value { get; set; } + public bool Checked { get; set; } + public virtual void Execute() { } } + + public enum DockedPosition + { + Left, + Right, + Top, + Bottom, + } + + public class DockedWindow + { + public event CancelEventHandler Closing; + + public event EventHandler Closed; + + public string Key { get; set; } + + public string Title { get; set; } + + public Control Content { get; set; } + + public DockedPosition Position { get; set; } + + public int Width { get; set; } + + public bool SaveLayout { get; set; } + + public bool Visible { get; private set; } + + public void Show() + { + Visible = true; + } + + public void Hide() + { + Visible = false; + } + + public void Close() + { + CancelEventArgs args = new CancelEventArgs(); + Closing?.Invoke(this, args); + if (args.Cancel) + { + return; + } + + Visible = false; + Closed?.Invoke(this, EventArgs.Empty); + } + } + + public interface IWindowManager + { + event EventHandler WindowLayoutLoaded; + + ICommandBarManager CommandBarManager { get; } + + DockedWindow CreateDockedWindow(string key, string title, Control control, DockedPosition position); + } + + public interface ICommandManager + { + CommandCollection Commands { get; } + } + + public interface ICommandBarManager + { + event EventHandler UILoaded; + + ToolCollection RootTools { get; } + } + + public interface ITool + { + string Key { get; set; } + + bool Checked { get; set; } + + event EventHandler ToolClick; + } + + public sealed class CommandCollection : List + { + } + + public class CommandManager : ICommandManager + { + public CommandCollection Commands { get; } = new CommandCollection(); + } + + public class WindowManager : IWindowManager + { + public event EventHandler WindowLayoutLoaded; + + public ICommandBarManager CommandBarManager { get; } = new CommandBarManager(); + + public DockedWindow CreateDockedWindow(string key, string title, Control control, DockedPosition position) + { + WindowLayoutLoaded?.Invoke(this, EventArgs.Empty); + return new DockedWindow + { + Key = key, + Title = title, + Content = control, + Position = position, + }; + } + } + + public class CommandBarManager : ICommandBarManager + { + public event EventHandler UILoaded; + + public ToolCollection RootTools { get; } = new ToolCollection(); + + public void RaiseUiLoaded() + { + UILoaded?.Invoke(this, EventArgs.Empty); + } + } + + public class ToolEventArgs : EventArgs + { + public string Key { get; set; } + } + + public delegate void ToolEventHandler(object sender, ToolEventArgs args); + + public class ToolBase : ITool + { + public string Key { get; set; } + + public bool Checked { get; set; } + + public event EventHandler ToolClick; + + protected void RaiseToolClick() + { + ToolClick?.Invoke(this, EventArgs.Empty); + } + } + + public class ButtonTool : ToolBase + { + } + + public class StateButtonTool : ToolBase + { + } + + public class MenuTool : ToolBase + { + public bool IsContextMenu { get; set; } + + public ToolCollection Tools { get; } = new ToolCollection(); + } + + public class ToolCollection + { + private readonly Dictionary _items = new Dictionary(StringComparer.OrdinalIgnoreCase); + + public event ToolEventHandler ToolAdded; + + public ITool this[string key] => _items.TryGetValue(key, out ITool tool) ? tool : null; + + public bool Contains(string key) + { + return _items.ContainsKey(key); + } + + public MenuTool AddMenuTool(string key, string caption, object image, string category) + { + MenuTool tool = new MenuTool { Key = key }; + _items[key] = tool; + ToolAdded?.Invoke(this, new ToolEventArgs { Key = key }); + return tool; + } + + public ButtonTool AddButtonTool(string key, string caption, object image, string category) + { + ButtonTool tool = new ButtonTool { Key = key }; + _items[key] = tool; + ToolAdded?.Invoke(this, new ToolEventArgs { Key = key }); + return tool; + } + + public ITool AddTool(string key) + { + if (_items.TryGetValue(key, out ITool existing)) + { + return existing; + } + + ButtonTool tool = new ButtonTool { Key = key }; + _items[key] = tool; + ToolAdded?.Invoke(this, new ToolEventArgs { Key = key }); + return tool; + } + } + + public static class MessageBoxEx + { + public static DialogResult Show(string text) + { + return DialogResult.OK; + } + + public static DialogResult Show(string text, string caption, MessageBoxButtons buttons, MessageBoxIcon icon) + { + return DialogResult.OK; + } + } } \ No newline at end of file diff --git a/shims/src/Database.cs b/shims/src/Database.cs index cf0147a..1f48955 100644 --- a/shims/src/Database.cs +++ b/shims/src/Database.cs @@ -38,6 +38,7 @@ public static class DbAttributeInstance public static readonly DbAttribute POS = new DbAttribute(nameof(POS)); public static readonly DbAttribute ORI = new DbAttribute(nameof(ORI)); public static readonly DbAttribute FLNN = new DbAttribute(nameof(FLNN)); + public static readonly DbAttribute FLNM = new DbAttribute(nameof(FLNM)); public static readonly DbAttribute AREA = new DbAttribute(nameof(AREA)); public static readonly DbAttribute CREF = new DbAttribute(nameof(CREF)); public static readonly DbAttribute ISNAME = new DbAttribute(nameof(ISNAME)); @@ -193,6 +194,11 @@ public string GetAsString(DbAttribute attribute) return string.Empty; } + public DbAttribute[] GetAttributes() + { + return Array.Empty(); + } + public void Copy(DbElement element) { } diff --git a/shims/src/FlexibleExplorer.cs b/shims/src/FlexibleExplorer.cs new file mode 100644 index 0000000..b8ea1ca --- /dev/null +++ b/shims/src/FlexibleExplorer.cs @@ -0,0 +1,73 @@ +using System.IO; +using System.Windows.Forms; +using Aveva.ApplicationFramework; +using Aveva.Core.Presentation; + +namespace Aveva.Core.FlexibleExplorer +{ + public enum RefreshOperation + { + None, + NodesOnly, + NodesWithCollections, + WholeTree, + } + + public class RefreshArguments + { + public DbChangesEventArgs DbChangesEventArgs { get; set; } + + public object DbRawChanges { get; set; } + + public RefreshOperation RefreshType { get; set; } + } + + public interface IFlexibleExplorerRefreshManager + { + RefreshArguments GetRefreshType(RefreshArguments refreshArguments); + } + + public class XmlDefinitionProvider + { + public XmlDefinitionProvider(string xmlContent, string fileName) + { + } + + public object RootDefinitions { get; } = new object[0]; + + public static string GetLocalFile(string path) + { + return File.Exists(path) ? path : null; + } + } +} + +namespace Aveva.Core.FlexibleExplorer +{ + public class FlexibleExplorer : Control + { + public FlexibleExplorer(IDependencyResolver dependencyResolver, object viewContext, bool useDefaultImages) + { + } + + public BorderStyle BorderStyle { get; set; } + + public bool DisplayCheckBox { get; set; } + + public bool DisplayLockIndicator { get; set; } + + public bool DisplayClaimIndicator { get; set; } + + public bool DisplayReadOnlyIndicator { get; set; } + + public Aveva.Core.FlexibleExplorer.IFlexibleExplorerRefreshManager CustomRefreshManager { get; set; } + + public void MultiSelect(bool enabled) + { + } + + public void CreateRoots(object rootDefinitions, bool preserveExisting) + { + } + } +} \ No newline at end of file diff --git a/shims/src/Json.cs b/shims/src/Json.cs new file mode 100644 index 0000000..853fb74 --- /dev/null +++ b/shims/src/Json.cs @@ -0,0 +1,80 @@ +using System.Collections; +using System.Collections.Generic; + +namespace Newtonsoft.Json.Linq +{ + public enum JTokenType + { + Object, + Array, + String, + Integer, + Float, + Boolean, + Null, + } + + public abstract class JToken + { + public abstract JTokenType Type { get; } + + public static JToken Parse(string json) + { + return new JObject(); + } + + public virtual T Value() + { + return default(T); + } + + public override string ToString() + { + return string.Empty; + } + } + + public sealed class JObject : JToken + { + public override JTokenType Type => JTokenType.Object; + + public IEnumerable Properties() + { + return new JProperty[0]; + } + } + + public sealed class JArray : JToken, IEnumerable + { + public override JTokenType Type => JTokenType.Array; + + public int Count => 0; + + public IEnumerator GetEnumerator() + { + return ((IEnumerable)new JToken[0]).GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + } + + public sealed class JProperty + { + public string Name { get; set; } = string.Empty; + + public JToken Value { get; set; } = new JValue(); + } + + public sealed class JValue : JToken + { + public override JTokenType Type => JTokenType.String; + + public override T Value() + { + return default(T); + } + } +} \ No newline at end of file diff --git a/shims/src/PmlNet.cs b/shims/src/PmlNet.cs new file mode 100644 index 0000000..3606942 --- /dev/null +++ b/shims/src/PmlNet.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections; + +namespace Aveva.Core.PMLNet +{ + [AttributeUsage(AttributeTargets.All, AllowMultiple = false)] + public sealed class PMLNetCallableAttribute : Attribute + { + } + + public static class PMLNetDelegate + { + public delegate void PMLNetEventHandler(ArrayList args); + } +} \ No newline at end of file diff --git a/shims/src/Presentation.cs b/shims/src/Presentation.cs index 678c97f..5116337 100644 --- a/shims/src/Presentation.cs +++ b/shims/src/Presentation.cs @@ -1,5 +1,9 @@ using System; +using System.Collections; +using System.Windows.Forms; +using Aveva.ApplicationFramework.Presentation; using Aveva.Core.Database; +using Aveva.Core.PMLNet; namespace Aveva.Core.Presentation { @@ -66,4 +70,72 @@ public static void RaiseChanges() Changes?.Invoke(null, new DbChangesEventArgs()); } } + + public class NetDataSource + { + public NetDataSource(string tableName, Hashtable attributes, Hashtable titles, Hashtable items) + { + } + } + + public class NetGridControl : Control + { + public bool allGridEvents { get; set; } + + public bool ColumnExcelFilter { get; set; } + + public bool ColumnSummaries { get; set; } + + public bool EditableGrid { get; set; } + + public bool ErrorIcon { get; set; } + + public bool ExtendLastColumn { get; set; } + + public bool FixedHeaders { get; set; } + + public bool FixedRows { get; set; } + + public int GridHeight { get; set; } + + public bool HeaderSort { get; set; } + + public bool HideGroupByBox { get; set; } + + public bool OutlookGroupStyle { get; set; } + + public bool RowAddDeleteGrid { get; set; } + + public bool ScrollSelectedRowToView { get; set; } + + public bool SingleRowSelection { get; set; } + + public bool Updates { get; set; } + + public MenuTool PopupMenu { get; set; } + + public MenuTool PopupMenuHeader { get; set; } + + public event PMLNetDelegate.PMLNetEventHandler AfterSelectChange; + + public void BindToDataSource(NetDataSource dataSource) + { + } + + public void setNameColumnImage() + { + } + + public void saveGridToExcel(string path) + { + } + + public void printPreview() + { + } + + public void setRowColor(double row, string colour) + { + } + } } \ No newline at end of file diff --git a/shims/src/Utilities.cs b/shims/src/Utilities.cs index f22c493..4671bda 100644 --- a/shims/src/Utilities.cs +++ b/shims/src/Utilities.cs @@ -6,8 +6,18 @@ public class PdmsMessage { } + public class PdmsError + { + public string MessageText() + { + return string.Empty; + } + } + public class PdmsException : Exception { + public PdmsError Error { get; } = new PdmsError(); + public PdmsException() { } diff --git a/shims/src/WebView2.cs b/shims/src/WebView2.cs new file mode 100644 index 0000000..f946c15 --- /dev/null +++ b/shims/src/WebView2.cs @@ -0,0 +1,65 @@ +using System; +using System.ComponentModel; +using System.Threading.Tasks; +using System.Windows.Forms; + +namespace Microsoft.Web.WebView2.Core +{ + public class CoreWebView2Settings + { + public bool AreDefaultContextMenusEnabled { get; set; } + + public bool IsStatusBarEnabled { get; set; } + + public bool IsZoomControlEnabled { get; set; } + + public bool AreDevToolsEnabled { get; set; } + } + + public class CoreWebView2NavigationStartingEventArgs : EventArgs + { + } + + public class CoreWebView2NavigationCompletedEventArgs : EventArgs + { + } + + public class CoreWebView2 + { + public CoreWebView2Settings Settings { get; } = new CoreWebView2Settings(); + + public event EventHandler NavigationStarting; + + public event EventHandler NavigationCompleted; + } +} + +namespace Microsoft.Web.WebView2.WinForms +{ + public class WebView2 : Control, ISupportInitialize + { + public Microsoft.Web.WebView2.Core.CoreWebView2 CoreWebView2 { get; private set; } = new Microsoft.Web.WebView2.Core.CoreWebView2(); + + public double ZoomFactor { get; set; } + + public Uri Source { get; set; } + + public Task EnsureCoreWebView2Async(object environment) + { + if (CoreWebView2 == null) + { + CoreWebView2 = new Microsoft.Web.WebView2.Core.CoreWebView2(); + } + + return Task.CompletedTask; + } + + public void BeginInit() + { + } + + public void EndInit() + { + } + } +} \ No newline at end of file diff --git a/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs b/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs index a4f350a..b100b17 100644 --- a/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs +++ b/templates/FlexibleExplorerAddInTemplate/FlexibleExplorerAddInTemplate.cs @@ -2,6 +2,7 @@ using Aveva.ApplicationFramework; using Aveva.ApplicationFramework.Presentation; using Aveva.Core.Database; +using Aveva.Core.Presentation; namespace Aveva.Core.Samples { diff --git a/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs b/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs index cc26b87..7a2a2b4 100644 --- a/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs +++ b/templates/FlexibleExplorerAddInTemplate/MyFlexibleExplorerControl.cs @@ -15,7 +15,7 @@ public partial class MyFlexibleExplorerControl : UserControl /// /// The flexible explorer. /// - private FlexibleExplorer.FlexibleExplorer _flexibleExplorer; + private global::Aveva.Core.FlexibleExplorer.FlexibleExplorer _flexibleExplorer; /// /// The dependency resolver. @@ -87,7 +87,7 @@ private string LoadXmlSecurely(string xmlFilePath) /// /// A FlexibleExplorer. /// - private FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlFileName) + private global::Aveva.Core.FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlFileName) { if (_flexibleExplorer != null) { @@ -95,7 +95,7 @@ private FlexibleExplorer.FlexibleExplorer InitialiseFlexibleExplorer(string xmlF } else { - _flexibleExplorer = new FlexibleExplorer.FlexibleExplorer(_dependencyResolver, null, true); + _flexibleExplorer = new global::Aveva.Core.FlexibleExplorer.FlexibleExplorer(_dependencyResolver, null, true); _flexibleExplorer.Dock = DockStyle.Fill; _flexibleExplorer.BorderStyle = BorderStyle.FixedSingle; From 0d0c694545380480d1ac369108c3b595823622dd Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Sun, 29 Mar 2026 23:17:25 +0100 Subject: [PATCH 09/59] Make security validation queueable --- azure-pipelines.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4bb3922..5a21234 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -19,12 +19,15 @@ resources: name: Technology/Templates ref: refs/heads/main +parameters: + - name: EnableSecurityValidation + type: string + default: 'false' + variables: - template: variables.yml@Templates - name: HostedVmImage value: windows-latest - - name: EnableSecurityValidation - value: 'false' # - name: SecurityPoolName # value: Technology UE Validation - name: CoverityServiceConnection @@ -100,7 +103,7 @@ stages: - stage: SecurityValidation displayName: Security Validation dependsOn: HostedTests - condition: and(succeeded(), eq(variables['EnableSecurityValidation'], 'true')) + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - job: CoverityAndBlackDuck displayName: Coverity And Black Duck From d8cf945b9e6e114cbeb790d80d54892b58ef4d00 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 00:09:51 +0100 Subject: [PATCH 10/59] Fix fake compile path for multi-checkout --- azure-pipelines.yml | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5a21234..4059945 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -128,10 +128,12 @@ stages: - pwsh: | ./scripts/Invoke-FakeUECompile.ps1 -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references + workingDirectory: $(Build.Repository.LocalPath) - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source - $fakeCompileScriptPath = Join-Path $env:BUILD_SOURCESDIRECTORY 'scripts\Invoke-FakeUECompile.ps1' + $repoRoot = $env:BUILD_REPOSITORY_LOCALPATH + $fakeCompileScriptPath = Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1' $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') @@ -139,7 +141,7 @@ stages: ? "@$($templatesBranch)" ` : '' $polarisBranch = "$branchName$templatesPostfix" - $commitDate = ([DateTime](git -C $env:BUILD_SOURCESDIRECTORY log -1 --format=%ci)).ToString('o') + $commitDate = ([DateTime](git -C $repoRoot log -1 --format=%ci)).ToString('o') $cleanCommands = @( " - shell: ['$pwshPath', '-NoProfile', '-Command', 'Write-Host fake-clean-step']" @@ -154,7 +156,7 @@ stages: 'project:' " name: $env:BUILD_REPOSITORY_NAME" " branch: $polarisBranch" - " projectDir: $env:BUILD_SOURCESDIRECTORY" + " projectDir: $repoRoot" ' revision:' " name: $env:BUILD_SOURCEVERSION" " date: $commitDate" From fd91b6ad938ddde0e2bfaeba4b1c1f2e80ee9471 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 00:13:30 +0100 Subject: [PATCH 11/59] Use explicit self checkout path in security job --- azure-pipelines.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 4059945..c61450d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -126,13 +126,13 @@ stages: fetchDepth: "1" - pwsh: | - ./scripts/Invoke-FakeUECompile.ps1 -Directory templates,Examples -Configuration Release + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + & (Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references - workingDirectory: $(Build.Repository.LocalPath) - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source - $repoRoot = $env:BUILD_REPOSITORY_LOCALPATH + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' $fakeCompileScriptPath = Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1' $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' From cc63e14913117e93d9809878918da8fb0cd040e6 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 00:29:25 +0100 Subject: [PATCH 12/59] Force rebuild during Coverity capture --- azure-pipelines.yml | 8 ++++---- scripts/Build-AddIns.ps1 | 7 ++++++- scripts/Invoke-FakeUECompile.ps1 | 19 ++++++++++++++++--- 3 files changed, 26 insertions(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c61450d..d041ccb 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,7 +22,7 @@ resources: parameters: - name: EnableSecurityValidation type: string - default: 'false' + default: "false" variables: - template: variables.yml@Templates @@ -65,7 +65,7 @@ stages: # name: Dabacon-Products-Microsoft-Hosted-Ubuntu # demands: # - ImageOverride -equals ubuntu-latest - # - WorkFolder -equals /mnt/storage/sdc/storage + # - WorkFolder -equals /mnt/storage/sdc/storage steps: - checkout: self fetchDepth: "0" @@ -117,7 +117,7 @@ stages: # name: Dabacon-Products-Microsoft-Hosted-Ubuntu # demands: # - ImageOverride -equals ubuntu-latest - # - WorkFolder -equals /mnt/storage/sdc/storage + # - WorkFolder -equals /mnt/storage/sdc/storage steps: - checkout: self fetchDepth: "0" @@ -148,7 +148,7 @@ stages: ) $buildCommands = @( - " - shell: ['$pwshPath', '-NoProfile', '-File', '$fakeCompileScriptPath', '-Directory', 'templates,Examples', '-Configuration', 'Release']" + " - shell: ['$pwshPath', '-NoProfile', '-File', '$fakeCompileScriptPath', '-Directory', 'templates,Examples', '-Configuration', 'Release', '-Rebuild']" ) $polarisContent = @( diff --git a/scripts/Build-AddIns.ps1 b/scripts/Build-AddIns.ps1 index 4bc8019..6031bf2 100644 --- a/scripts/Build-AddIns.ps1 +++ b/scripts/Build-AddIns.ps1 @@ -2,7 +2,8 @@ param( [Parameter(Mandatory = $false)] [string]$Directory = "source", [string]$Configuration = "Debug", - [switch]$SkipPostBuildEvent + [switch]$SkipPostBuildEvent, + [switch]$Rebuild ) $ErrorActionPreference = "Stop" @@ -43,6 +44,10 @@ foreach ($proj in $projects) { '-nologo' ) + if ($Rebuild) { + $buildArgs += '-t:Rebuild' + } + if ($SkipPostBuildEvent) { $buildArgs += '-p:RunPostBuildEvent=Never' $buildArgs += '-p:PostBuildEvent=' diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 index 9cf4aee..d558c98 100644 --- a/scripts/Invoke-FakeUECompile.ps1 +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -5,7 +5,8 @@ param( [string[]]$Directory = @('Examples'), [string]$Configuration = 'Release', [string]$ShimProjectsPath, - [string]$ShimOutputPath + [string]$ShimOutputPath, + [switch]$Rebuild ) $ErrorActionPreference = 'Stop' @@ -30,7 +31,19 @@ if (-not $projects) { foreach ($project in $projects) { Write-Host ("Building shim assembly: {0}" -f $project.Name) -ForegroundColor Cyan - dotnet build $project.FullName -c $Configuration -nologo + $shimBuildArgs = @( + 'build' + $project.FullName + '-c' + $Configuration + '-nologo' + ) + + if ($Rebuild) { + $shimBuildArgs += '-t:Rebuild' + } + + dotnet @shimBuildArgs if ($LASTEXITCODE -ne 0) { throw ("Shim build failed: {0}" -f $project.FullName) } @@ -58,7 +71,7 @@ try { foreach ($targetDirectory in $targetDirectories) { Write-Host ("Compiling with fake UE references: {0}" -f $targetDirectory) -ForegroundColor Yellow - & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration -SkipPostBuildEvent + & $buildScriptPath -Directory $targetDirectory -Configuration $Configuration -SkipPostBuildEvent -Rebuild:$Rebuild if ($LASTEXITCODE -ne 0) { exit $LASTEXITCODE } From 65f7fabe81037328f4ba5504ce329166ac652c76 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 00:46:35 +0100 Subject: [PATCH 13/59] Fix Coverity artifact publish path --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d041ccb..91721d2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -206,7 +206,7 @@ stages: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI - - publish: $(Build.SourcesDirectory)\.blackduck + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.blackduck artifact: Coverity displayName: Publish Coverity artifacts condition: always() From 990d0da84e4d09d71a6314d10030c3236b1edf7b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 01:36:02 +0100 Subject: [PATCH 14/59] Ignore generated pmllib output --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 18a9596..4aa022d 100644 --- a/.gitignore +++ b/.gitignore @@ -49,6 +49,8 @@ bld/ # Build results on 'Bin' directories **/[Bb]in/* +# Local UE customization runtime output +pmllib/ # Uncomment if you have tasks that rely on *.refresh files to move binaries # (https://github.com/github/gitignore/pull/3736) #!**/[Bb]in/*.refresh From e83e34ec3bc5e6ec1a9abcd5939d2cd7eec0dfac Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 02:01:34 +0100 Subject: [PATCH 15/59] Document hosted validation agent guidance --- .../agents/ue-customization-manager.agent.md | 27 +++++++++++++++++++ README.md | 7 +++++ 2 files changed, 34 insertions(+) diff --git a/.github/agents/ue-customization-manager.agent.md b/.github/agents/ue-customization-manager.agent.md index 2cadceb..d419423 100644 --- a/.github/agents/ue-customization-manager.agent.md +++ b/.github/agents/ue-customization-manager.agent.md @@ -45,6 +45,13 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - **CRITICAL**: After invoking a script with `run_in_terminal`, remain absolutely silent. Do not send any messages, status updates, or comments about the script running or the terminal awaiting input. - **IMPORTANT**: The scripts do NOT wait for user input after they start running. All input is gathered upfront via `vscode/askQuestions`. Never say the script is "waiting for input" because it is not - it runs autonomously after the initial parameter gathering. +7. **Understand Hosted Validation**: + - Recognize that [`azure-pipelines.yml`](../../azure-pipelines.yml) contains a hosted security-validation lane that uses fake UE references to validate both `templates/` and `Examples/`. + - Use [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1) when reasoning about hosted compile-only validation. It builds shim assemblies under `shims/`, sets `UNIFIED_ENGINEERING_REFERENCE_PATH`, and compiles selected directories against the fake UE surface. + - Treat `shims/` as compile-only stand-ins for hosted validation and static analysis, not runtime replacements for a real UE installation. + - Remember that `pmllib/` is generated local output for PML template builds and is intentionally ignored in git. + - When investigating hosted Coverity capture problems, verify that the Polaris-wrapped fake compile is using `-Rebuild` so compiler activity is emitted during capture. + ## Key Script Understanding ### Setup-UECustomizationEnvironment.ps1 @@ -219,6 +226,26 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - "For security, I'll collect your password via PowerShell credential prompt instead of asking in chat" - "The password will be cleared from memory immediately after launching UE" +### Hosted Validation and Fake Compile +**Purpose**: Help users understand and troubleshoot the hosted validation path that cannot depend on a real UE installation. + +**Key components**: +- [`azure-pipelines.yml`](../../azure-pipelines.yml): Contains the hosted test lane and the optional hosted security-validation lane. +- [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Builds compile-only shim assemblies and compiles `templates/` and `Examples/` against them. +- [`scripts/Build-AddIns.ps1`](../../scripts/Build-AddIns.ps1): Supports `-SkipPostBuildEvent` and `-Rebuild` for deterministic compile-only builds. +- [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture. + +**Important notes**: +- Hosted fake compile is for validation only. It proves the code compiles against the expected API surface but does not replace testing with a real UE runtime. +- The security lane uses explicit self-checkout paths in the multi-checkout job. Do not assume relative paths like `./scripts/...` will be correct in that job. +- The Polaris-wrapped fake compile must force rebuild behavior; otherwise Coverity can report zero captured files because the build becomes an incremental no-op. +- `pmllib/` is generated by PML template build steps and should be treated as ignored local output, not authored source. + +**Example guidance**: +- "If hosted validation fails, I can inspect the fake-compile path and the multi-checkout pipeline job before assuming a UE runtime issue." +- "If Coverity reports zero captured files, I will verify that the Polaris build path is using `Invoke-FakeUECompile.ps1 -Rebuild`." +- "If you see a local `pmllib/` folder after building a PML template, that is generated output rather than source to commit." + ## Typical Workflows ### Build Projects diff --git a/README.md b/README.md index 604efbd..e76eb9b 100644 --- a/README.md +++ b/README.md @@ -49,6 +49,7 @@ The UE Customization Manager agent: - **Explains workflows** - Helps you understand the proper sequence of operations - **Validates operations** - Warns about destructive operations and validates parameters - **Troubleshoots issues** - Provides context-aware assistance based on README documentation +- **Explains hosted validation** - Understands the fake-compile, shim, and hosted security-validation path used in CI - **Launches UE** - Can start AVEVA Unified Engineering when requested - **Manages the lifecycle** - From initial setup through add-in creation to final cleanup @@ -111,6 +112,12 @@ You can interact with the agent using natural language in the GitHub Copilot cha @ue-customization-manager The script says the environment doesn't exist @ue-customization-manager What files will be deleted if I run Clear? + +@ue-customization-manager Why did hosted security validation fail? + +@ue-customization-manager How does fake compile work for templates and Examples? + +@ue-customization-manager Why did a local pmllib folder appear after building a PML template? ``` ### Benefits of Using the Agent From 28af40a0b8bacacd9eece945a4a51c42e701bf91 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Mon, 30 Mar 2026 02:10:42 +0100 Subject: [PATCH 16/59] Enhance documentation for Setup-UECustomizationEnvironment.ps1 and New-UEAddin.ps1 scripts --- .../agents/ue-customization-manager.agent.md | 81 +++++++++++++++++-- 1 file changed, 74 insertions(+), 7 deletions(-) diff --git a/.github/agents/ue-customization-manager.agent.md b/.github/agents/ue-customization-manager.agent.md index d419423..aa64ade 100644 --- a/.github/agents/ue-customization-manager.agent.md +++ b/.github/agents/ue-customization-manager.agent.md @@ -55,25 +55,30 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz ## Key Script Understanding ### Setup-UECustomizationEnvironment.ps1 + **Purpose**: Creates the initial customization environment structure and unpacks AVEVA binaries. **When to use**: + - First time setting up a UE customization environment - Need to reinitialize environment components - Setting up on a new machine - Reusing an existing environment without unpacking again **Key parameters**: + - -ReuseEnvironment (optional): If specified, skips unpacking and reuses the existing configuration (script validates environment exists). - CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable) **Parameter Gathering Workflow**: + 1. Use `vscode/askQuestions` to ask the user if they want to reuse an existing environment 2. If yes, pass -ReuseEnvironment to skip unpacking and reuse configuration 3. If no, perform full setup (script will validate and handle all checks) 4. **After invoking the script, remain completely silent** - do not send any messages about progress, terminal status, or awaiting input **Important notes**: + - Requires UE command in PATH - Sets environment variables: CAF_ADDINS_PATH, CAF_UIC_PATH at User level - Creates subdirectories: addins/, samples/ @@ -84,76 +89,91 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - To create add-ins after setup, use New-UEAddin.ps1 in a separate step **Example guidance**: + - "This script creates the foundation. Run it first to initialize your environment." - "After setup completes, you can create add-ins using New-UEAddin.ps1" - "If you want to skip unpacking and reuse your existing environment, I can pass -ReuseEnvironment." ### New-UEAddin.ps1 + **Purpose**: Creates a new add-in configuration and source code by copying, renaming, and customizing a template folder. **When to use**: + - After Setup-UECustomizationEnvironment.ps1 has been run - Adding a new add-in to the environment - Creating multiple add-ins in the same environment **Key parameters**: + - -AddinName (mandatory): Name of the add-in to create. Use `vscode/askQuestions` to gather this if not provided by user. - -Template (mandatory): Name of the template to use. Available templates are in the `templates/` folder. Use `vscode/askQuestions` to present available template options and gather the selection. - -Module (mandatory): UE module to target. Valid values: Model, Engineering, EngineeringConfiguration, Isodraft, Draw, Paragon, Spooler, Monitor. Use `vscode/askQuestions` to present these as options and gather the selection. - CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable) **Parameter Gathering Workflow**: + 1. List available templates from the `templates/` directory using: `Get-ChildItem -Path templates -Directory | Select-Object -ExpandProperty Name | Out-String` (the `| Out-String` is critical to ensure all templates are captured in terminal output) 2. Use `vscode/askQuestions` to gather AddinName, Module, and Template in a single call 3. Pass all parameters to the script - never let the script prompt interactively **Validation**: + - -AddinName must contain only A-Za-z characters (no numbers, spaces, or special characters) **Important notes**: + - Requires existing customization environment (will fail if Setup hasn't been run) - Always gather template, module, and add-in name via `vscode/askQuestions` before calling the script - Creates XML entries in the module-specific Addins and Customization files (e.g., Engineering → TagsAddins.xml and TagsCustomization.xml) when the selected template contains a `.uic` file - **Copies templates/[TemplateName]/ to source/[AddinName]/** using robocopy - Excludes build artifacts (.vs, bin, obj, logs, log directories) -- Excludes compiler/cache files (*.user, *.suo, *.pdb, etc.) +- Excludes compiler/cache files (_.user, _.suo, \*.pdb, etc.) - Renames all files/folders/references containing the template name to the new add-in name - Replaces all text occurrences of the template name in copied files with the new add-in name - Can be called multiple times for different add-ins, with same or different templates **Example guidance**: + - "I'll gather the required information for creating your add-in" - "I need to know the add-in name, which template to use, and which UE module to target" - "Each add-in needs a unique name that will be referenced in XML configuration files" ### Remove-UEAddin.ps1 + **Purpose**: Removes an existing add-in configuration and source code from the customization environment. **When to use**: + - Need to remove an add-in that's no longer needed - Cleaning up specific add-ins while keeping others - Removing configurations before environment cleanup **⚠️ SAFETY REQUIREMENT**: + - **ALWAYS use `vscode/askQuestions` to confirm with the user before running this script** - Ask: "Are you sure you want to remove the add-in '[AddinName]'? This will delete the source code folder and all configuration files." - Only proceed if the user confirms with "yes" or similar affirmative response - If the user declines, do not run the script **Key parameters**: + - -AddinName (mandatory): Name of the add-in to remove. Use `vscode/askQuestions` to gather this if not provided. - CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable) **Parameter Gathering Workflow**: + 1. Use `vscode/askQuestions` to gather AddinName 2. Use `vscode/askQuestions` to confirm the deletion with the user 3. Only proceed if user confirms 4. Pass parameters to the script - never let the script prompt interactively **Validation**: + - -AddinName must contain only A-Za-z characters (no numbers, spaces, or special characters) **Important notes**: + - Scans and removes XML entries across all module-specific `*Addins.xml` and `*Customization.xml` files under the `addins/` folder - Deletes the corresponding `.uic` file and the add-in DLL (`addins/[AddinName].dll`) if present - **Deletes the entire `source/[AddinName]/` folder and all its contents** @@ -162,27 +182,33 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - Does not affect environment structure or other add-ins **Example guidance**: + - "I'll gather the add-in name and confirmation before removing" - "This operation is destructive and will delete the add-in configuration and source code" ### Clear-UECustomizationEnvironment.ps1 + **Purpose**: Removes customization environment folders (samples and addins) while preserving custom add-in source code. **When to use**: + - Cleaning up after development - Removing unpacked samples and configuration files - Starting fresh with a new environment without losing custom add-in projects **Key parameters**: + - CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable) **⚠️ SAFETY REQUIREMENT**: + - **ALWAYS use `vscode/askQuestions` to confirm with the user before running this script** - Ask: "Are you sure you want to clear the customization environment? This will delete the samples/ and addins/ folders (but preserve your source/ code)." - Only proceed if the user confirms with "yes" or similar affirmative response - If the user declines, do not run the script **Important notes**: + - **Deletes samples/ and addins/ folders** (unpacked binaries and configurations) - **Preserves the source/ folder** with all custom add-in projects - To remove individual add-ins, use Remove-UEAddin.ps1 instead @@ -191,28 +217,34 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - Use this when you want to clean the environment but keep your custom add-in code **Example guidance**: + - "This cleans up the unpacked files but keeps your custom add-in source code intact" - "To remove specific add-ins, use Remove-UEAddin.ps1 first" - Runs non-interactively without confirmation prompts - Environment variables are NOT unset (use manual cleanup if needed) **Example guidance**: + - "This removes the source, addins, and binaries folders but keeps your root directory intact" - "Safe to run after you're done with an environment setup" ### Start-UE.ps1 + **Purpose**: Launches AVEVA Unified Engineering with properly restored environment variables. **When to use**: + - Starting UE for testing or development - Ensuring environment variables are correctly set before launching - After building add-ins and ready to test **Key parameters**: + - Optional: `-Module`, `-Project`, `-Username`, `-Mdb` (forwarded to UE as `--module`, `--project`, `--username`, `--mdb`) - CustomizationRootDir is automatically set to the parent of the scripts folder (non-configurable) **Important notes**: + - Restores environment variables from `environment.json` using `Restore-EnvironmentVariables` - Requires that `Setup-UECustomizationEnvironment.ps1` has been run previously - If `environment.json` is not found, warns the user but attempts to launch UE anyway @@ -221,27 +253,32 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz - When asked to launch UE, prompt the user to confirm whether to include any of the optional UE arguments and pass those values into `Start-UE.ps1`. **Example guidance**: + - "Use this script instead of running 'ue' directly to ensure your environment is properly configured" - "After building your add-ins, run Start-UE.ps1 to test them in AVEVA Unified Engineering" - "For security, I'll collect your password via PowerShell credential prompt instead of asking in chat" - "The password will be cleared from memory immediately after launching UE" ### Hosted Validation and Fake Compile + **Purpose**: Help users understand and troubleshoot the hosted validation path that cannot depend on a real UE installation. **Key components**: + - [`azure-pipelines.yml`](../../azure-pipelines.yml): Contains the hosted test lane and the optional hosted security-validation lane. - [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Builds compile-only shim assemblies and compiles `templates/` and `Examples/` against them. - [`scripts/Build-AddIns.ps1`](../../scripts/Build-AddIns.ps1): Supports `-SkipPostBuildEvent` and `-Rebuild` for deterministic compile-only builds. - [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture. **Important notes**: + - Hosted fake compile is for validation only. It proves the code compiles against the expected API surface but does not replace testing with a real UE runtime. - The security lane uses explicit self-checkout paths in the multi-checkout job. Do not assume relative paths like `./scripts/...` will be correct in that job. - The Polaris-wrapped fake compile must force rebuild behavior; otherwise Coverity can report zero captured files because the build becomes an incremental no-op. - `pmllib/` is generated by PML template build steps and should be treated as ignored local output, not authored source. **Example guidance**: + - "If hosted validation fails, I can inspect the fake-compile path and the multi-checkout pipeline job before assuming a UE runtime issue." - "If Coverity reports zero captured files, I will verify that the Polaris build path is using `Invoke-FakeUECompile.ps1 -Rebuild`." - "If you see a local `pmllib/` folder after building a PML template, that is generated output rather than source to commit." @@ -249,77 +286,105 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz ## Typical Workflows ### Build Projects + `powershell + # Build all sample projects + pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory samples # Build all source projects + pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory source # Build any custom path + pwsh -NoProfile -File scripts/Build-AddIns.ps1 -Directory C:\Path\To\Projects -Configuration Release ` ### Initial Setup with Add-ins + `powershell + # 1. Setup the environment + .\scripts\Setup-UECustomizationEnvironment.ps1 # 2. Create the first add-in + .\scripts\New-UEAddin.ps1 -AddinName "MyFirstAddin" # 3. Create more add-ins as needed + .\scripts\New-UEAddin.ps1 -AddinName "MySecondAddin" # 4. Build your add-ins + .\scripts\Build-AddIns.ps1 -Directory source # 5. Launch UE to test + .\scripts\Start-UE.ps1 # 6. When done, clean up + .\scripts\Clear-UECustomizationEnvironment.ps1 ` ### Multiple Add-in Development + `powershell + # 1. Setup environment + .\scripts\Setup-UECustomizationEnvironment.ps1 # 2. Create multiple add-ins over time + .\scripts\New-UEAddin.ps1 -AddinName "Addin1" .\scripts\New-UEAddin.ps1 -AddinName "Addin2" .\scripts\New-UEAddin.ps1 -AddinName "Addin3" # 3. Build and test + .\scripts\Build-AddIns.ps1 -Directory source .\scripts\Start-UE.ps1 # 4. Remove one that's no longer needed + .\scripts\Remove-UEAddin.ps1 -AddinName "Addin2" # 5. Clean up when done + .\scripts\Clear-UECustomizationEnvironment.ps1 ` ### Iterative Development + `powershell + # 1. Initial setup + .\scripts\Setup-UECustomizationEnvironment.ps1 # 2. Create add-in + .\scripts\New-UEAddin.ps1 -AddinName "MyAddin" # 3. Build and test cycle + .\scripts\Build-AddIns.ps1 -Directory source .\scripts\Start-UE.ps1 + # Modify code, rebuild, relaunch as needed... # 4. If you need to remove and recreate + .\scripts\Remove-UEAddin.ps1 -AddinName "MyAddin" .\scripts\New-UEAddin.ps1 -AddinName "MyAddin" -Template "TemplateAddIn" -Module "Model" # 5. Final cleanup + .\scripts\Clear-UECustomizationEnvironment.ps1 ` @@ -335,8 +400,8 @@ These variables are available in the current session immediately and in future P ## Important Considerations ### Directory Structure -` -CustomizationRootDir/ + +`CustomizationRootDir/ source/ # Add-in source code directory TemplateAddIn/ # Template (preserved by Clear script) [AddinName]/ # Auto-generated copies with substitutions @@ -346,10 +411,10 @@ CustomizationRootDir/ DesignCustomization.xml, TagsCustomization.xml, ConfigurationCustomization.xml, ... [AddinName].uic [AddinName].dll - binaries/ # Supporting binaries from MSIX unpack -` + binaries/ # Supporting binaries from MSIX unpack` ### File Operations + - All scripts run non-interactively without prompting - Scripts validate paths and parameters before operations - AddinName validation enforces A-Za-z characters only @@ -357,6 +422,7 @@ CustomizationRootDir/ - Errors are thrown with clear messages about what went wrong ### Error Handling + - Scripts check for required files before operations - Missing files in remove operations generate warnings, not errors - Invalid paths or missing prerequisites generate clear error messages @@ -375,7 +441,7 @@ CustomizationRootDir/ 3. **Sequential Guidance**: Help users understand the order of operations. Setup must run before New-UEAddin. -4. **Patience with Long-Running Operations**: +4. **Patience with Long-Running Operations**: - The MSIX unpack operation (during Setup-UECustomizationEnvironment.ps1) can take several minutes to complete - Use appropriate timeouts (300000ms or 5 minutes minimum) when running setup scripts - **The scripts do NOT require any user input after they start** - all parameters are gathered upfront via `vscode/askQuestions` @@ -407,6 +473,7 @@ CustomizationRootDir/ ## When to Ask Clarifying Questions Ask for clarification when: + - User intent is ambiguous (is this first setup or modification?) - Required parameters are missing - Script prerequisites might not be met @@ -417,9 +484,9 @@ Example: "Have you already run Setup-UECustomizationEnvironment.ps1? The New-UEA ## Available Documentation The repository includes: + - **README.md**: Complete documentation of all scripts with detailed examples - **Instructions.md**: Original instructions and setup guidance - Each script is self-documented with inline comments Always leverage the README documentation when helping users understand requirements or troubleshoot issues. - From dd9cbf10a595d956377135f6b29f2f234a5b9be3 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 10:57:57 +0100 Subject: [PATCH 17/59] Refactor Azure pipeline for hosted security validation and testing stages --- azure-pipelines.yml | 40 +++++++++++++++++----------------------- 1 file changed, 17 insertions(+), 23 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 91721d2..594e687 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -3,9 +3,9 @@ # This pipeline is intended to be connected to GitHub through the Azure Pipelines GitHub App # so its checks surface back into GitHub pull requests targeting main. # -# The hosted test stage can run immediately. -# The security stage remains opt-in until the Technology project has a UE-capable -# self-hosted Windows pool or the lane is rewritten into a hosted-compatible flow. +# The hosted test stage can run immediately on the Technology stateful hosted pools. +# The security stage remains opt-in, but now uses the hosted-compatible fake-compile flow +# on the Technology stateful hosted Ubuntu pool instead of generic vmImage selection. # Templates is consumed from a Technology fork to avoid cross-project checkout permissions. # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available @@ -26,10 +26,6 @@ parameters: variables: - template: variables.yml@Templates - - name: HostedVmImage - value: windows-latest - # - name: SecurityPoolName - # value: Technology UE Validation - name: CoverityServiceConnection value: Coverity on Polaris - name: BlackDuckServiceConnection @@ -59,13 +55,13 @@ stages: - job: Pester displayName: Pester pool: - vmImage: $(HostedVmImage) - # container: mcr.microsoft.com/dotnet/sdk:9.0 - # pool: - # name: Dabacon-Products-Microsoft-Hosted-Ubuntu - # demands: - # - ImageOverride -equals ubuntu-latest - # - WorkFolder -equals /mnt/storage/sdc/storage + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 + workspace: + clean: all steps: - checkout: self fetchDepth: "0" @@ -109,15 +105,13 @@ stages: displayName: Coverity And Black Duck timeoutInMinutes: "180" pool: - vmImage: $(HostedVmImage) - # pool: - # name: $(SecurityPoolName) - # container: mcr.microsoft.com/dotnet/sdk:9.0 - # pool: - # name: Dabacon-Products-Microsoft-Hosted-Ubuntu - # demands: - # - ImageOverride -equals ubuntu-latest - # - WorkFolder -equals /mnt/storage/sdc/storage + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 + workspace: + clean: all steps: - checkout: self fetchDepth: "0" From 0dceea107f1ba06dcf9d6ca803cc022987566fdb Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 11:10:12 +0100 Subject: [PATCH 18/59] Refactor Azure pipeline for Black Duck integration and cleanup --- azure-pipelines.yml | 155 ++++++++++++++++++++++---------------------- 1 file changed, 76 insertions(+), 79 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 594e687..e0634e2 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -10,7 +10,6 @@ # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available # - service connections named `Coverity on Polaris` and `BlackDuckScanner` -# - a variable group named `BlackDuck on Polaris` exposing BLACKDUCK_ACCESS_TOKEN resources: repositories: @@ -26,12 +25,9 @@ parameters: variables: - template: variables.yml@Templates - - name: CoverityServiceConnection - value: Coverity on Polaris - - name: BlackDuckServiceConnection - value: BlackDuckScanner - - name: BlackDuckGroup - value: DabaconProductsGroup + + + - name: CoverityProjectTeam value: .RnD.Polaris.Dabacon-Products.SecurityAdvisors - name: CoverityCliVersion @@ -177,7 +173,7 @@ stages: displayName: Restore Coverity CLI cache retryCountOnTaskFailure: 3 inputs: - key: '"CoverityCLI" | "$(CoverityCliVersion)" | "v1"' + key: "\"CoverityCLI\" | \"$(CoverityCliVersion)\" | \"v1\"" path: $(Pipeline.Workspace)\Coverity-CLI cacheHitVar: COVERITY_CLI_RESTORED @@ -191,7 +187,7 @@ stages: - task: BlackduckCoverityOnPolaris@2 displayName: Coverity scan inputs: - polarisService: $(CoverityServiceConnection) + polarisService: Coverity on Polaris polarisCommand: > -c $(Pipeline.Workspace)\polaris.yml analyze @@ -205,73 +201,74 @@ stages: displayName: Publish Coverity artifacts condition: always() - # - pwsh: | - # $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') - # $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` - # ? "@$($templatesBranch)" ` - # : '' - - # if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - # $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" - # } - # else { - # $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - # $versionName = "$branchName-$($env:BUILD_BUILDID)" - # } - - # $versionName = "$versionName$templatesPostfix" - - # Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" - # Write-Host "Black Duck project version: $versionName" - # displayName: Calculate Black Duck version - - # - task: ArchiveFiles@2 - # displayName: Archive sources and build outputs - # inputs: - # rootFolderOrFile: $(Build.SourcesDirectory) - # includeRootFolder: true - # archiveType: zip - # archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip - # replaceExistingArchive: true - - # - task: BlackDuckDetectTask@10 - # displayName: Black Duck scan - # inputs: - # BlackDuckScaService: $(BlackDuckServiceConnection) - # DetectVersion: latest - # DetectFolder: $(Pipeline.Workspace)\detect - # DetectArguments: | - # --detect.project.name=$(Build.Repository.Name) - # --detect.project.version.name=$(BlackDuckProjectVersion) - # --detect.project.group.name=$(BlackDuckGroup) - # --detect.source.path=$(Build.SourcesDirectory) - # --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip - # --detect.cleanup=true - # --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs - # --detect.detector.search.depth=10 - # --detect.timeout=1800 - # --detect.wait.for.results=true - # --detect.wait.for.results.timeout=6000000 - # --logging.level.detect=DEBUG - # --detect.diagnostic=true - - # - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs - # artifact: BlackDuckLogs - # displayName: Publish Black Duck logs - # condition: always() - - # - pwsh: | - # $outputPath = Join-Path $env:PIPELINE_WORKSPACE 'BlackDuckComponentNotices.txt' - # & "$(Pipeline.Workspace)\Templates\scripts\BlackDuck\Generate-BlackDuckNotice.ps1" ` - # -ProjectName "$(Build.Repository.Name)" ` - # -ProjectVersion "$(BlackDuckProjectVersion)" ` - # -OutputPath $outputPath - # displayName: Generate Black Duck NOTICE - # condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) - # env: - # BlackduckToken: $(BLACKDUCK_ACCESS_TOKEN) - - # - publish: $(Pipeline.Workspace)\BlackDuckComponentNotices.txt - # artifact: BlackDuckNotice - # displayName: Publish Black Duck NOTICE - # condition: and(succeeded(), ne(variables['Build.Reason'], 'PullRequest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + - pwsh: | + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $versionName = "$branchName-$($env:BUILD_BUILDID)" + } + + $versionName = "$versionName$templatesPostfix" + + Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" + Write-Host "Black Duck project version: $versionName" + displayName: Calculate Black Duck version + + - template: steps/install-python.yml + parameters: + pythonVersion: 3.11 + condition: not(canceled()) + + - task: ArchiveFiles@2 + displayName: Archive sources and build outputs + inputs: + rootFolderOrFile: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + includeRootFolder: true + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + replaceExistingArchive: true + verbose: true + + - task: BlackDuckDetectTask@10 + displayName: Black Duck scan + retryCountOnTaskFailure: 0 + inputs: + BlackDuckScaService: BlackDuckScanner + DetectVersion: latest + DetectFolder: $(Pipeline.Workspace)\detect + DetectArguments: | + --detect.project.name=$(Build.Repository.Name) + --detect.project.version.name=$(BlackDuckProjectVersion) + --detect.project.group.name=DabaconProductsGroup + --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip + --detect.cleanup=true + --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs + --detect.detector.search.depth=10 + --detect.detector.search.continue=true + --detect.blackduck.signature.scanner.upload.source.mode=true + --detect.blackduck.signature.scanner.copyright.search=true + --detect.blackduck.signature.scanner.license.search=true + --logging.level.detect=DEBUG + --detect.diagnostic=true + --detect.timeout=1800 + --detect.blackduck.signature.scanner.arguments= --fs-wait-time=90 + --detect.wait.for.results=true + --detect.wait.for.results.timeout=6000000 + + - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs + artifact: BlackDuckLogs + displayName: Publish Black Duck logs + condition: always() + + - publish: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + artifact: BlackDuckInput + displayName: Publish Black Duck input archive + condition: always() From 35629c509a07e0e6ebcf49a1e470dbf7abb753a8 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 11:25:03 +0100 Subject: [PATCH 19/59] Enhance Azure pipeline and scripts for shim project generation and publishing --- .../agents/ue-customization-manager.agent.md | 4 +- .gitignore | 1 + azure-pipelines.yml | 13 +++- scripts/Invoke-FakeUECompile.ps1 | 63 ++++++++++++++++++- ...ApplicationFramework.Implementation.csproj | 6 -- ...a.ApplicationFramework.Presentation.csproj | 6 -- shims/Aveva.ApplicationFramework.csproj | 6 -- shims/Aveva.Core.Database.Filters.csproj | 6 -- shims/Aveva.Core.Database.csproj | 9 --- ...Aveva.Core.FlexibleExplorer.Control.csproj | 6 -- shims/Aveva.Core.FlexibleExplorer.Core.csproj | 6 -- shims/Aveva.Core.Geometry.csproj | 6 -- shims/Aveva.Core.Implementation.csproj | 6 -- shims/Aveva.Core.Presentation.csproj | 6 -- shims/Aveva.Core.Utilities.csproj | 6 -- shims/Aveva.Core3D.Clasher.csproj | 6 -- shims/GridControl.csproj | 6 -- shims/Microsoft.Web.WebView2.Core.csproj | 6 -- shims/Microsoft.Web.WebView2.WinForms.csproj | 6 -- shims/Newtonsoft.Json.csproj | 6 -- shims/PMLNet.csproj | 6 -- shims/StartUp.csproj | 6 -- shims/shim-projects.psd1 | 24 +++++++ 23 files changed, 98 insertions(+), 118 deletions(-) delete mode 100644 shims/Aveva.ApplicationFramework.Implementation.csproj delete mode 100644 shims/Aveva.ApplicationFramework.Presentation.csproj delete mode 100644 shims/Aveva.ApplicationFramework.csproj delete mode 100644 shims/Aveva.Core.Database.Filters.csproj delete mode 100644 shims/Aveva.Core.Database.csproj delete mode 100644 shims/Aveva.Core.FlexibleExplorer.Control.csproj delete mode 100644 shims/Aveva.Core.FlexibleExplorer.Core.csproj delete mode 100644 shims/Aveva.Core.Geometry.csproj delete mode 100644 shims/Aveva.Core.Implementation.csproj delete mode 100644 shims/Aveva.Core.Presentation.csproj delete mode 100644 shims/Aveva.Core.Utilities.csproj delete mode 100644 shims/Aveva.Core3D.Clasher.csproj delete mode 100644 shims/GridControl.csproj delete mode 100644 shims/Microsoft.Web.WebView2.Core.csproj delete mode 100644 shims/Microsoft.Web.WebView2.WinForms.csproj delete mode 100644 shims/Newtonsoft.Json.csproj delete mode 100644 shims/PMLNet.csproj delete mode 100644 shims/StartUp.csproj create mode 100644 shims/shim-projects.psd1 diff --git a/.github/agents/ue-customization-manager.agent.md b/.github/agents/ue-customization-manager.agent.md index aa64ade..cf1c66a 100644 --- a/.github/agents/ue-customization-manager.agent.md +++ b/.github/agents/ue-customization-manager.agent.md @@ -266,9 +266,9 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz **Key components**: - [`azure-pipelines.yml`](../../azure-pipelines.yml): Contains the hosted test lane and the optional hosted security-validation lane. -- [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Builds compile-only shim assemblies and compiles `templates/` and `Examples/` against them. +- [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Generates shim project files from `shims/shim-projects.psd1`, builds compile-only shim assemblies, and compiles `templates/` and `Examples/` against them. - [`scripts/Build-AddIns.ps1`](../../scripts/Build-AddIns.ps1): Supports `-SkipPostBuildEvent` and `-Rebuild` for deterministic compile-only builds. -- [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture. +- [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture, plus the `shim-projects.psd1` manifest used to generate shim project files at build time. **Important notes**: diff --git a/.gitignore b/.gitignore index 4aa022d..8b4abf6 100644 --- a/.gitignore +++ b/.gitignore @@ -51,6 +51,7 @@ bld/ **/[Bb]in/* # Local UE customization runtime output pmllib/ +shims/generated/ # Uncomment if you have tasks that rely on *.refresh files to move binaries # (https://github.com/github/gitignore/pull/3736) #!**/[Bb]in/*.refresh diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e0634e2..e662db4 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,9 +25,6 @@ parameters: variables: - template: variables.yml@Templates - - - - name: CoverityProjectTeam value: .RnD.Polaris.Dabacon-Products.SecurityAdvisors - name: CoverityCliVersion @@ -120,6 +117,16 @@ stages: & (Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims + artifact: ShimSources + displayName: Publish shim sources + condition: always() + + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\artifacts\Release + artifact: ShimAssemblies + displayName: Publish compiled shim assemblies + condition: always() + - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 index d558c98..b152539 100644 --- a/scripts/Invoke-FakeUECompile.ps1 +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -5,6 +5,8 @@ param( [string[]]$Directory = @('Examples'), [string]$Configuration = 'Release', [string]$ShimProjectsPath, + [string]$ShimManifestPath, + [string]$GeneratedShimProjectsPath, [string]$ShimOutputPath, [switch]$Rebuild ) @@ -17,16 +19,73 @@ if (-not $ShimProjectsPath) { $ShimProjectsPath = Join-Path $repoRoot 'shims' } +if (-not $ShimManifestPath) { + $ShimManifestPath = Join-Path $ShimProjectsPath 'shim-projects.psd1' +} + +if (-not $GeneratedShimProjectsPath) { + $GeneratedShimProjectsPath = Join-Path $ShimProjectsPath 'generated' +} + if (-not $ShimOutputPath) { $ShimOutputPath = Join-Path $ShimProjectsPath "artifacts\$Configuration" } $resolvedShimProjectsPath = (Resolve-Path $ShimProjectsPath).Path +$resolvedShimManifestPath = (Resolve-Path $ShimManifestPath).Path +$resolvedGeneratedShimProjectsPath = [System.IO.Path]::GetFullPath($GeneratedShimProjectsPath) $buildScriptPath = Join-Path $PSScriptRoot 'Build-AddIns.ps1' -$projects = Get-ChildItem -Path $resolvedShimProjectsPath -Filter '*.csproj' -File | Sort-Object Name +$shimManifest = Import-PowerShellDataFile -Path $resolvedShimManifestPath +$shimProjects = if ($shimManifest.ContainsKey('Projects')) { + $shimManifest.Projects +} +else { + $null +} + +if (-not $shimProjects -or $shimProjects.Count -eq 0) { + throw "No shim projects were defined in $resolvedShimManifestPath" +} + +if (Test-Path -LiteralPath $resolvedGeneratedShimProjectsPath) { + Remove-Item -LiteralPath $resolvedGeneratedShimProjectsPath -Recurse -Force +} + +$null = New-Item -ItemType Directory -Path $resolvedGeneratedShimProjectsPath -Force + +foreach ($projectEntry in $shimProjects.GetEnumerator()) { + $projectName = [string]$projectEntry.Key + $projectConfig = $projectEntry.Value + $rootNamespace = if ($projectConfig.RootNamespace) { + [string]$projectConfig.RootNamespace + } + else { + $projectName + } + + $projectLines = [System.Collections.Generic.List[string]]::new() + $projectLines.Add('') + $projectLines.Add(' ') + $projectLines.Add(" $projectName") + $projectLines.Add(" $rootNamespace") + $projectLines.Add(' ') + + if ($projectConfig.ContainsKey('CompileInclude')) { + $projectLines.Add(' ') + $projectLines.Add((' ' -f $projectConfig.CompileInclude)) + $projectLines.Add(' ') + } + + $projectLines.Add('') + + $generatedProjectPath = Join-Path $resolvedGeneratedShimProjectsPath "$projectName.csproj" + $projectLines -join [Environment]::NewLine | Set-Content -LiteralPath $generatedProjectPath -Encoding utf8 +} + +$projects = Get-ChildItem -Path $resolvedGeneratedShimProjectsPath -Filter '*.csproj' -File | Sort-Object Name if (-not $projects) { - throw "No shim projects found under $resolvedShimProjectsPath" + throw "No generated shim projects found under $resolvedGeneratedShimProjectsPath" } foreach ($project in $projects) { diff --git a/shims/Aveva.ApplicationFramework.Implementation.csproj b/shims/Aveva.ApplicationFramework.Implementation.csproj deleted file mode 100644 index 2ea7f56..0000000 --- a/shims/Aveva.ApplicationFramework.Implementation.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.ApplicationFramework.Implementation - Aveva.ApplicationFramework.Implementation - - \ No newline at end of file diff --git a/shims/Aveva.ApplicationFramework.Presentation.csproj b/shims/Aveva.ApplicationFramework.Presentation.csproj deleted file mode 100644 index 9e8e054..0000000 --- a/shims/Aveva.ApplicationFramework.Presentation.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.ApplicationFramework.Presentation - Aveva.ApplicationFramework.Presentation - - \ No newline at end of file diff --git a/shims/Aveva.ApplicationFramework.csproj b/shims/Aveva.ApplicationFramework.csproj deleted file mode 100644 index a2e5b91..0000000 --- a/shims/Aveva.ApplicationFramework.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.ApplicationFramework - Aveva.ApplicationFramework - - \ No newline at end of file diff --git a/shims/Aveva.Core.Database.Filters.csproj b/shims/Aveva.Core.Database.Filters.csproj deleted file mode 100644 index dc9aa9a..0000000 --- a/shims/Aveva.Core.Database.Filters.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.Database.Filters - Aveva.Core.Database.Filters - - \ No newline at end of file diff --git a/shims/Aveva.Core.Database.csproj b/shims/Aveva.Core.Database.csproj deleted file mode 100644 index 925e3b9..0000000 --- a/shims/Aveva.Core.Database.csproj +++ /dev/null @@ -1,9 +0,0 @@ - - - Aveva.Core.Database - Aveva.Core.Database - - - - - \ No newline at end of file diff --git a/shims/Aveva.Core.FlexibleExplorer.Control.csproj b/shims/Aveva.Core.FlexibleExplorer.Control.csproj deleted file mode 100644 index df56982..0000000 --- a/shims/Aveva.Core.FlexibleExplorer.Control.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.FlexibleExplorer.Control - Aveva.Core.FlexibleExplorer.Control - - \ No newline at end of file diff --git a/shims/Aveva.Core.FlexibleExplorer.Core.csproj b/shims/Aveva.Core.FlexibleExplorer.Core.csproj deleted file mode 100644 index 4aaf960..0000000 --- a/shims/Aveva.Core.FlexibleExplorer.Core.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.FlexibleExplorer.Core - Aveva.Core.FlexibleExplorer.Core - - \ No newline at end of file diff --git a/shims/Aveva.Core.Geometry.csproj b/shims/Aveva.Core.Geometry.csproj deleted file mode 100644 index 5e9fd28..0000000 --- a/shims/Aveva.Core.Geometry.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.Geometry - Aveva.Core.Geometry - - \ No newline at end of file diff --git a/shims/Aveva.Core.Implementation.csproj b/shims/Aveva.Core.Implementation.csproj deleted file mode 100644 index d4f4ed5..0000000 --- a/shims/Aveva.Core.Implementation.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.Implementation - Aveva.Core.Implementation - - \ No newline at end of file diff --git a/shims/Aveva.Core.Presentation.csproj b/shims/Aveva.Core.Presentation.csproj deleted file mode 100644 index 9033125..0000000 --- a/shims/Aveva.Core.Presentation.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.Presentation - Aveva.Core.Presentation - - \ No newline at end of file diff --git a/shims/Aveva.Core.Utilities.csproj b/shims/Aveva.Core.Utilities.csproj deleted file mode 100644 index f3972c3..0000000 --- a/shims/Aveva.Core.Utilities.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core.Utilities - Aveva.Core.Utilities - - \ No newline at end of file diff --git a/shims/Aveva.Core3D.Clasher.csproj b/shims/Aveva.Core3D.Clasher.csproj deleted file mode 100644 index e32e24a..0000000 --- a/shims/Aveva.Core3D.Clasher.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Aveva.Core3D.Clasher - Aveva.Core3D.Clasher - - \ No newline at end of file diff --git a/shims/GridControl.csproj b/shims/GridControl.csproj deleted file mode 100644 index 414d294..0000000 --- a/shims/GridControl.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - GridControl - GridControl - - \ No newline at end of file diff --git a/shims/Microsoft.Web.WebView2.Core.csproj b/shims/Microsoft.Web.WebView2.Core.csproj deleted file mode 100644 index 4fc1c1b..0000000 --- a/shims/Microsoft.Web.WebView2.Core.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Microsoft.Web.WebView2.Core - Microsoft.Web.WebView2.Core - - \ No newline at end of file diff --git a/shims/Microsoft.Web.WebView2.WinForms.csproj b/shims/Microsoft.Web.WebView2.WinForms.csproj deleted file mode 100644 index d422918..0000000 --- a/shims/Microsoft.Web.WebView2.WinForms.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Microsoft.Web.WebView2.WinForms - Microsoft.Web.WebView2.WinForms - - \ No newline at end of file diff --git a/shims/Newtonsoft.Json.csproj b/shims/Newtonsoft.Json.csproj deleted file mode 100644 index ccdbc9f..0000000 --- a/shims/Newtonsoft.Json.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - Newtonsoft.Json - Newtonsoft.Json - - \ No newline at end of file diff --git a/shims/PMLNet.csproj b/shims/PMLNet.csproj deleted file mode 100644 index cfef247..0000000 --- a/shims/PMLNet.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - PMLNet - PMLNet - - \ No newline at end of file diff --git a/shims/StartUp.csproj b/shims/StartUp.csproj deleted file mode 100644 index fc28b78..0000000 --- a/shims/StartUp.csproj +++ /dev/null @@ -1,6 +0,0 @@ - - - StartUp - StartUp - - \ No newline at end of file diff --git a/shims/shim-projects.psd1 b/shims/shim-projects.psd1 new file mode 100644 index 0000000..91779f5 --- /dev/null +++ b/shims/shim-projects.psd1 @@ -0,0 +1,24 @@ +@{ + Projects = @{ + 'Aveva.ApplicationFramework' = @{} + 'Aveva.ApplicationFramework.Implementation' = @{} + 'Aveva.ApplicationFramework.Presentation' = @{} + 'Aveva.Core.Database' = @{ + CompileInclude = '..\src\**\*.cs' + } + 'Aveva.Core.Database.Filters' = @{} + 'Aveva.Core.FlexibleExplorer.Control' = @{} + 'Aveva.Core.FlexibleExplorer.Core' = @{} + 'Aveva.Core.Geometry' = @{} + 'Aveva.Core.Implementation' = @{} + 'Aveva.Core.Presentation' = @{} + 'Aveva.Core.Utilities' = @{} + 'Aveva.Core3D.Clasher' = @{} + 'GridControl' = @{} + 'Microsoft.Web.WebView2.Core' = @{} + 'Microsoft.Web.WebView2.WinForms' = @{} + 'Newtonsoft.Json' = @{} + 'PMLNet' = @{} + 'StartUp' = @{} + } +} \ No newline at end of file From bf09db89f24270ecd2d76da7ad9765d4cb93705b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 11:36:25 +0100 Subject: [PATCH 20/59] Update shim project generation and Azure pipeline for CI validation --- .github/agents/ue-customization-manager.agent.md | 4 ++-- azure-pipelines.yml | 10 ++++++++++ scripts/Invoke-FakeUECompile.ps1 | 14 ++++++++++++-- shims/shim-projects.psd1 | 2 +- 4 files changed, 25 insertions(+), 5 deletions(-) diff --git a/.github/agents/ue-customization-manager.agent.md b/.github/agents/ue-customization-manager.agent.md index cf1c66a..0746217 100644 --- a/.github/agents/ue-customization-manager.agent.md +++ b/.github/agents/ue-customization-manager.agent.md @@ -266,13 +266,13 @@ You are an expert assistant for managing AVEVA Unified Engineering (UE) customiz **Key components**: - [`azure-pipelines.yml`](../../azure-pipelines.yml): Contains the hosted test lane and the optional hosted security-validation lane. -- [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Generates shim project files from `shims/shim-projects.psd1`, builds compile-only shim assemblies, and compiles `templates/` and `Examples/` against them. +- [`scripts/Invoke-FakeUECompile.ps1`](../../scripts/Invoke-FakeUECompile.ps1): Generates CI-only shim project files from `shims/shim-projects.psd1` into `.tmp/generated-shims`, builds compile-only shim assemblies, and compiles `templates/` and `Examples/` against them. - [`scripts/Build-AddIns.ps1`](../../scripts/Build-AddIns.ps1): Supports `-SkipPostBuildEvent` and `-Rebuild` for deterministic compile-only builds. - [`shims/`](../../shims): Contains compile-only UE API stand-ins used for hosted validation and Coverity capture, plus the `shim-projects.psd1` manifest used to generate shim project files at build time. **Important notes**: -- Hosted fake compile is for validation only. It proves the code compiles against the expected API surface but does not replace testing with a real UE runtime. +- Hosted fake compile is for validation only. It proves the code compiles against the expected API surface for CI and Coverity capture, but does not replace development or testing with a real UE runtime. - The security lane uses explicit self-checkout paths in the multi-checkout job. Do not assume relative paths like `./scripts/...` will be correct in that job. - The Polaris-wrapped fake compile must force rebuild behavior; otherwise Coverity can report zero captured files because the build becomes an incremental no-op. - `pmllib/` is generated by PML template build steps and should be treated as ignored local output, not authored source. diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e662db4..b5018c0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -122,6 +122,16 @@ stages: displayName: Publish shim sources condition: always() + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\shim-projects.psd1 + artifact: ShimManifest + displayName: Publish shim manifest + condition: always() + + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.tmp\generated-shims + artifact: GeneratedShimProjects + displayName: Publish generated shim project files + condition: always() + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\artifacts\Release artifact: ShimAssemblies displayName: Publish compiled shim assemblies diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 index b152539..3885ac4 100644 --- a/scripts/Invoke-FakeUECompile.ps1 +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -1,5 +1,8 @@ #Requires -Version 7.0 +# Builds compile-only shim assemblies for hosted validation and Coverity capture. +# This is a CI-focused path; developers with a real UE installation should use the normal UE references. + [CmdletBinding()] param( [string[]]$Directory = @('Examples'), @@ -24,7 +27,7 @@ if (-not $ShimManifestPath) { } if (-not $GeneratedShimProjectsPath) { - $GeneratedShimProjectsPath = Join-Path $ShimProjectsPath 'generated' + $GeneratedShimProjectsPath = Join-Path $repoRoot '.tmp\generated-shims' } if (-not $ShimOutputPath) { @@ -34,6 +37,7 @@ if (-not $ShimOutputPath) { $resolvedShimProjectsPath = (Resolve-Path $ShimProjectsPath).Path $resolvedShimManifestPath = (Resolve-Path $ShimManifestPath).Path $resolvedGeneratedShimProjectsPath = [System.IO.Path]::GetFullPath($GeneratedShimProjectsPath) +$shimDirectoryBuildPropsPath = Join-Path $resolvedShimProjectsPath 'Directory.Build.props' $buildScriptPath = Join-Path $PSScriptRoot 'Build-AddIns.ps1' $shimManifest = Import-PowerShellDataFile -Path $resolvedShimManifestPath @@ -66,14 +70,20 @@ foreach ($projectEntry in $shimProjects.GetEnumerator()) { $projectLines = [System.Collections.Generic.List[string]]::new() $projectLines.Add('') + $projectLines.Add((' ' -f $shimDirectoryBuildPropsPath.Replace('&', '&'))) $projectLines.Add(' ') $projectLines.Add(" $projectName") $projectLines.Add(" $rootNamespace") $projectLines.Add(' ') if ($projectConfig.ContainsKey('CompileInclude')) { + $compileIncludePath = [string]$projectConfig.CompileInclude + if (-not [System.IO.Path]::IsPathRooted($compileIncludePath)) { + $compileIncludePath = [System.IO.Path]::GetFullPath((Join-Path $resolvedShimProjectsPath $compileIncludePath)) + } + $projectLines.Add(' ') - $projectLines.Add((' ' -f $projectConfig.CompileInclude)) + $projectLines.Add((' ' -f $compileIncludePath.Replace('&', '&'))) $projectLines.Add(' ') } diff --git a/shims/shim-projects.psd1 b/shims/shim-projects.psd1 index 91779f5..207fef3 100644 --- a/shims/shim-projects.psd1 +++ b/shims/shim-projects.psd1 @@ -4,7 +4,7 @@ 'Aveva.ApplicationFramework.Implementation' = @{} 'Aveva.ApplicationFramework.Presentation' = @{} 'Aveva.Core.Database' = @{ - CompileInclude = '..\src\**\*.cs' + CompileInclude = 'src\**\*.cs' } 'Aveva.Core.Database.Filters' = @{} 'Aveva.Core.FlexibleExplorer.Control' = @{} From c99779b37647aae68a7e263750b648ceca22fb4c Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 11:41:25 +0100 Subject: [PATCH 21/59] Update Azure pipeline to reference specific template for Python installation --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b5018c0..e0e8362 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -238,7 +238,7 @@ stages: Write-Host "Black Duck project version: $versionName" displayName: Calculate Black Duck version - - template: steps/install-python.yml + - template: steps/install-python.yml@Templates parameters: pythonVersion: 3.11 condition: not(canceled()) From 88946a067e172244d7d978fab0f9200fc78d7dcd Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 12:27:18 +0100 Subject: [PATCH 22/59] Fix hosted test temp path handling --- .../Tests/UECustomizationFunctions.Tests.ps1 | 28 ++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/scripts/Tests/UECustomizationFunctions.Tests.ps1 b/scripts/Tests/UECustomizationFunctions.Tests.ps1 index 64fcef1..bb8a9d8 100644 --- a/scripts/Tests/UECustomizationFunctions.Tests.ps1 +++ b/scripts/Tests/UECustomizationFunctions.Tests.ps1 @@ -5,6 +5,8 @@ Import-Module -Name $modulePath -Force -ErrorAction Stop Describe 'UECustomizationFunctions Module' { BeforeAll { + $script:TempRoot = [System.IO.Path]::GetTempPath() + Mock -CommandName Set-EnvironmentVariableUserAndSession -ModuleName UECustomizationFunctions -MockWith { Set-Item -Path ("Env:{0}" -f $Name) -Value $Value } @@ -45,7 +47,7 @@ Describe 'UECustomizationFunctions Module' { Context 'Find-RequiredFile' { It 'throws if file not found' { - $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { { Find-RequiredFile -RootPath $temp.FullName -FileName 'nope.txt' } | Should -Throw } @@ -55,7 +57,7 @@ Describe 'UECustomizationFunctions Module' { } It 'returns full path when file exists' { - $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $file = Join-Path $temp.FullName 'found.txt' Set-Content -Path $file -Value 'test' -Encoding UTF8 @@ -68,7 +70,7 @@ Describe 'UECustomizationFunctions Module' { } It 'finds files in subdirectories' { - $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $subdir = New-Item -Path (Join-Path $temp.FullName "subdir") -ItemType Directory $file = Join-Path $subdir.FullName 'nested.txt' @@ -84,7 +86,7 @@ Describe 'UECustomizationFunctions Module' { Context 'Initialize-DirectoryStructure' { It 'creates addins and pmllib directories' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $addins = Join-Path $tempRoot.FullName 'addins' $pmllib = Join-Path $tempRoot.FullName 'pmllib' @@ -98,7 +100,7 @@ Describe 'UECustomizationFunctions Module' { } It 'creates nested directory paths' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $addins = Join-Path $tempRoot.FullName 'nested\path\addins' $pmllib = Join-Path $tempRoot.FullName 'nested\path\pmllib' @@ -114,7 +116,7 @@ Describe 'UECustomizationFunctions Module' { Context 'Set-EnvironmentConfiguration' { It 'creates configuration file with correct content' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $configPath = Join-Path $tempRoot.FullName 'environment.json' $addins = Join-Path $tempRoot.FullName 'addins' @@ -134,7 +136,7 @@ Describe 'UECustomizationFunctions Module' { } It 'sets environment variables in session' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $configPath = Join-Path $tempRoot.FullName 'environment.json' $addins = Join-Path $tempRoot.FullName 'addins' @@ -159,7 +161,7 @@ Describe 'UECustomizationFunctions Module' { } It 'restores environment variables from config file' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $configPath = Join-Path $tempRoot.FullName 'environment.json' $addins = Join-Path $tempRoot.FullName 'addins' @@ -193,7 +195,7 @@ Describe 'UECustomizationFunctions Module' { } It 'throws if destination is invalid' { - $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { $zipPath = Join-Path $temp.FullName 'test.zip' Set-Content -Path $zipPath -Value 'dummy' @@ -209,10 +211,10 @@ Describe 'UECustomizationFunctions Module' { Context 'Install-UEFiles' { It 'does not throw if source directory has no config files' { - $temp = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $temp = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { # Should warn but not throw when no config files are found - { Install-UEFiles -UnpackPath $temp.FullName -AddinsFolder (Join-Path $env:TEMP 'addins') } | Should -Not -Throw + { Install-UEFiles -UnpackPath $temp.FullName -AddinsFolder (Join-Path $script:TempRoot 'addins') } | Should -Not -Throw } finally { Remove-Item -Path $temp.FullName -Recurse -Force @@ -228,7 +230,7 @@ Describe 'UECustomizationFunctions Module' { Context 'Clear-UECustomizationEnvironment.ps1 Script' { It 'removes environment.json during cleanup' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { # Create a mock environment.json $configPath = Join-Path $tempRoot.FullName 'environment.json' @@ -267,7 +269,7 @@ Describe 'UECustomizationFunctions Module' { } It 'updates DesignCustomization.xml only when template contains .uic' { - $tempRoot = New-Item -ItemType Directory -Path (Join-Path $env:TEMP ("UE-Test-" + [Guid]::NewGuid())) + $tempRoot = New-Item -ItemType Directory -Path (Join-Path $script:TempRoot ("UE-Test-" + [Guid]::NewGuid())) try { # Arrange environment structure $addins = Join-Path $tempRoot.FullName 'addins' From 5587bec339954cb441e18b561ff36155d4af02cf Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 12:29:49 +0100 Subject: [PATCH 23/59] Make addin template copy cross-platform --- scripts/UECustomizationFunctions.psm1 | 46 +++++++++++++++++++-------- 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/scripts/UECustomizationFunctions.psm1 b/scripts/UECustomizationFunctions.psm1 index 3c5c361..5c8ae82 100644 --- a/scripts/UECustomizationFunctions.psm1 +++ b/scripts/UECustomizationFunctions.psm1 @@ -808,20 +808,38 @@ function Invoke-EnvironmentCleanup { $excludeDirs = @('bin', 'obj', '.vs', 'logs', 'log') $excludeFiles = @('*.user', '*.suo', '*.rsuser', '*.userprefs', '*.pdb', '*.ipdb', '*.iobj', '*.obj', '*.pch', '*.log', '*.tlog', '*.tmp', '*.cache', '*.ilk') - $xdArgs = $excludeDirs | ForEach-Object { '/XD', $_ } - $xfArgs = $excludeFiles | ForEach-Object { '/XF', $_ } - $robocopyArgs = @($sourceTemplateDir, $targetAddinDir, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/R:2', '/W:1') + $xdArgs + $xfArgs - & robocopy @robocopyArgs | Out-Null - $rc = $LASTEXITCODE - if ($rc -ge 8) { - $exception = [System.InvalidOperationException]::new("Robocopy failed with exit code $rc while copying template.") - $errorRecord = [System.Management.Automation.ErrorRecord]::new( - $exception, - 'RobocopyFailed', - [System.Management.Automation.ErrorCategory]::InvalidOperation, - $rc - ) - $PSCmdlet.ThrowTerminatingError($errorRecord) + $robocopyCommand = Get-Command -Name 'robocopy' -ErrorAction SilentlyContinue + if ($robocopyCommand) { + $xdArgs = $excludeDirs | ForEach-Object { '/XD', $_ } + $xfArgs = $excludeFiles | ForEach-Object { '/XF', $_ } + $robocopyArgs = @($sourceTemplateDir, $targetAddinDir, '/E', '/NFL', '/NDL', '/NJH', '/NJS', '/NC', '/NS', '/R:2', '/W:1') + $xdArgs + $xfArgs + & $robocopyCommand.Source @robocopyArgs | Out-Null + $rc = $LASTEXITCODE + if ($rc -ge 8) { + $exception = [System.InvalidOperationException]::new("Robocopy failed with exit code $rc while copying template.") + $errorRecord = [System.Management.Automation.ErrorRecord]::new( + $exception, + 'RobocopyFailed', + [System.Management.Automation.ErrorCategory]::InvalidOperation, + $rc + ) + $PSCmdlet.ThrowTerminatingError($errorRecord) + } + } + else { + Copy-Item -Path $sourceTemplateDir -Destination $targetAddinDir -Recurse -Force + + foreach ($excludedDir in $excludeDirs) { + Get-ChildItem -Path $targetAddinDir -Directory -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { $_.Name -eq $excludedDir } | + Remove-Item -Recurse -Force -ErrorAction SilentlyContinue + } + + foreach ($excludedPattern in $excludeFiles) { + Get-ChildItem -Path $targetAddinDir -File -Recurse -Force -ErrorAction SilentlyContinue | + Where-Object { $_.Name -like $excludedPattern } | + Remove-Item -Force -ErrorAction SilentlyContinue + } } $itemsToRename = Get-ChildItem -Path $targetAddinDir -Recurse -Force | Where-Object { $_.Name -like "*$Template*" } From 58977081c6ad40090e7635e360fcb2008b9bc0b7 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 13:10:11 +0100 Subject: [PATCH 24/59] Install dotnet inside security container --- azure-pipelines.yml | 104 +++++++++++++++++++++++++++++++++++++------- 1 file changed, 88 insertions(+), 16 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index e0e8362..bf47385 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -112,30 +112,90 @@ stages: - checkout: Templates fetchDepth: "1" + - bash: | + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y curl ca-certificates + + curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh + chmod +x /tmp/dotnet-install.sh + /tmp/dotnet-install.sh --channel 9.0 --install-dir /usr/share/dotnet + ln -sf /usr/share/dotnet/dotnet /usr/local/bin/dotnet + dotnet --info + displayName: Install .NET SDK + retryCountOnTaskFailure: 3 + + - bash: | + # Install Python 3 and pip in the Linux container + set -euo pipefail + export DEBIAN_FRONTEND=noninteractive + apt-get update + apt-get install -y python3 python3-pip + + if ! command -v python >/dev/null 2>&1; then + ln -sf /usr/bin/python3 /usr/local/bin/python + fi + displayName: Install Python 3 and pip + retryCountOnTaskFailure: 3 + + - pwsh: | + Write-Host "Verifying Python installation..." + $pythonVersion = python --version 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "Python verification failed: $pythonVersion" + } + + $pipVersion = python -m pip --version 2>&1 + if ($LASTEXITCODE -ne 0) { + throw "Pip verification failed: $pipVersion" + } + + Write-Host "Python version: $pythonVersion" + Write-Host "Pip version: $pipVersion" + displayName: Verify Python and pip + - pwsh: | $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' & (Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + $paths = @{ + HasShimSources = Test-Path -LiteralPath (Join-Path $repoRoot 'shims') + HasShimManifest = Test-Path -LiteralPath (Join-Path $repoRoot 'shims\shim-projects.psd1') + HasGeneratedShimProjects = Test-Path -LiteralPath (Join-Path $repoRoot '.tmp\generated-shims') + HasShimAssemblies = Test-Path -LiteralPath (Join-Path $repoRoot 'shims\artifacts\Release') + } + + foreach ($entry in $paths.GetEnumerator()) { + $value = if ($entry.Value) { 'true' } else { 'false' } + Write-Host "##vso[task.setvariable variable=$($entry.Key)]$value" + Write-Host "$($entry.Key): $value" + } + displayName: Detect shim artifacts + condition: always() + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims artifact: ShimSources displayName: Publish shim sources - condition: always() + condition: and(always(), eq(variables.HasShimSources, 'true')) - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\shim-projects.psd1 artifact: ShimManifest displayName: Publish shim manifest - condition: always() + condition: and(always(), eq(variables.HasShimManifest, 'true')) - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.tmp\generated-shims artifact: GeneratedShimProjects displayName: Publish generated shim project files - condition: always() + condition: and(always(), eq(variables.HasGeneratedShimProjects, 'true')) - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\artifacts\Release artifact: ShimAssemblies displayName: Publish compiled shim assemblies - condition: always() + condition: and(always(), eq(variables.HasShimAssemblies, 'true')) - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source @@ -213,11 +273,6 @@ stages: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.blackduck - artifact: Coverity - displayName: Publish Coverity artifacts - condition: always() - - pwsh: | $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` @@ -238,11 +293,6 @@ stages: Write-Host "Black Duck project version: $versionName" displayName: Calculate Black Duck version - - template: steps/install-python.yml@Templates - parameters: - pythonVersion: 3.11 - condition: not(canceled()) - - task: ArchiveFiles@2 displayName: Archive sources and build outputs inputs: @@ -280,12 +330,34 @@ stages: --detect.wait.for.results=true --detect.wait.for.results.timeout=6000000 + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY + $paths = @{ + HasCoverityArtifacts = Test-Path -LiteralPath (Join-Path $repoRoot '.blackduck') + HasBlackDuckLogs = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-logs') + HasBlackDuckInput = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-input.zip') + } + + foreach ($entry in $paths.GetEnumerator()) { + $value = if ($entry.Value) { 'true' } else { 'false' } + Write-Host "##vso[task.setvariable variable=$($entry.Key)]$value" + Write-Host "$($entry.Key): $value" + } + displayName: Detect security artifacts + condition: always() + + - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.blackduck + artifact: Coverity + displayName: Publish Coverity artifacts + condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) + - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs artifact: BlackDuckLogs displayName: Publish Black Duck logs - condition: always() + condition: and(always(), eq(variables.HasBlackDuckLogs, 'true')) - publish: $(Build.ArtifactStagingDirectory)\blackduck-input.zip artifact: BlackDuckInput displayName: Publish Black Duck input archive - condition: always() + condition: and(always(), eq(variables.HasBlackDuckInput, 'true')) From 503254efc01e4bfff306281766cbd9a943996102 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 13:40:06 +0100 Subject: [PATCH 25/59] Use non-root dotnet install in security job --- azure-pipelines.yml | 49 +++++++++------------------------------------ 1 file changed, 10 insertions(+), 39 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf47385..2d44705 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -112,48 +112,19 @@ stages: - checkout: Templates fetchDepth: "1" - - bash: | - set -euo pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install -y curl ca-certificates - - curl -fsSL https://dot.net/v1/dotnet-install.sh -o /tmp/dotnet-install.sh - chmod +x /tmp/dotnet-install.sh - /tmp/dotnet-install.sh --channel 9.0 --install-dir /usr/share/dotnet - ln -sf /usr/share/dotnet/dotnet /usr/local/bin/dotnet - dotnet --info - displayName: Install .NET SDK - retryCountOnTaskFailure: 3 - - - bash: | - # Install Python 3 and pip in the Linux container - set -euo pipefail - export DEBIAN_FRONTEND=noninteractive - apt-get update - apt-get install -y python3 python3-pip - - if ! command -v python >/dev/null 2>&1; then - ln -sf /usr/bin/python3 /usr/local/bin/python - fi - displayName: Install Python 3 and pip - retryCountOnTaskFailure: 3 - - pwsh: | - Write-Host "Verifying Python installation..." - $pythonVersion = python --version 2>&1 - if ($LASTEXITCODE -ne 0) { - throw "Python verification failed: $pythonVersion" - } + $dotnetRoot = Join-Path $env:PIPELINE_WORKSPACE 'dotnet' + $installScript = Join-Path $env:PIPELINE_WORKSPACE 'dotnet-install.ps1' - $pipVersion = python -m pip --version 2>&1 - if ($LASTEXITCODE -ne 0) { - throw "Pip verification failed: $pipVersion" - } + Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $installScript + & $installScript -Channel '9.0' -InstallDir $dotnetRoot + + Write-Host "##vso[task.prependpath]$dotnetRoot" + Write-Host "##vso[task.setvariable variable=DOTNET_ROOT]$dotnetRoot" - Write-Host "Python version: $pythonVersion" - Write-Host "Pip version: $pipVersion" - displayName: Verify Python and pip + & (Join-Path $dotnetRoot 'dotnet') --info + displayName: Install .NET SDK + retryCountOnTaskFailure: 3 - pwsh: | $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' From 70c0290e719dc5284af5fc8c50e13729df69d4ff Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 13:50:02 +0100 Subject: [PATCH 26/59] Specify dotnet install architecture --- azure-pipelines.yml | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2d44705..ef6634d 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -115,9 +115,15 @@ stages: - pwsh: | $dotnetRoot = Join-Path $env:PIPELINE_WORKSPACE 'dotnet' $installScript = Join-Path $env:PIPELINE_WORKSPACE 'dotnet-install.ps1' + $processArchitecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant() + $dotnetArchitecture = switch ($processArchitecture) { + 'x64' { 'x64' } + 'arm64' { 'arm64' } + default { throw "Unsupported .NET install architecture: $processArchitecture" } + } Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $installScript - & $installScript -Channel '9.0' -InstallDir $dotnetRoot + & $installScript -Channel '9.0' -Architecture $dotnetArchitecture -InstallDir $dotnetRoot Write-Host "##vso[task.prependpath]$dotnetRoot" Write-Host "##vso[task.setvariable variable=DOTNET_ROOT]$dotnetRoot" From c6266e6f447be46fc3799712dc50535807e4c9e3 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 15:27:34 +0100 Subject: [PATCH 27/59] Align security job with Templates dotnet container --- azure-pipelines.yml | 29 ++++++++++++----------------- 1 file changed, 12 insertions(+), 17 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ef6634d..8023eee 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -102,7 +102,7 @@ stages: demands: - ImageOverride -equals ubuntu-latest - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 + container: mcr.microsoft.com/dotnet/sdk:9.0 workspace: clean: all steps: @@ -113,24 +113,19 @@ stages: fetchDepth: "1" - pwsh: | - $dotnetRoot = Join-Path $env:PIPELINE_WORKSPACE 'dotnet' - $installScript = Join-Path $env:PIPELINE_WORKSPACE 'dotnet-install.ps1' - $processArchitecture = [System.Runtime.InteropServices.RuntimeInformation]::ProcessArchitecture.ToString().ToLowerInvariant() - $dotnetArchitecture = switch ($processArchitecture) { - 'x64' { 'x64' } - 'arm64' { 'arm64' } - default { throw "Unsupported .NET install architecture: $processArchitecture" } - } - - Invoke-WebRequest -Uri 'https://dot.net/v1/dotnet-install.ps1' -OutFile $installScript - & $installScript -Channel '9.0' -Architecture $dotnetArchitecture -InstallDir $dotnetRoot - - Write-Host "##vso[task.prependpath]$dotnetRoot" - Write-Host "##vso[task.setvariable variable=DOTNET_ROOT]$dotnetRoot" + dotnet --list-sdks + displayName: List globally installed .NET SDKs - & (Join-Path $dotnetRoot 'dotnet') --info - displayName: Install .NET SDK + - task: UseDotNet@2 + displayName: Ensure .NET 9 SDK for Linux retryCountOnTaskFailure: 3 + inputs: + packageType: sdk + version: 9.0.x + + - pwsh: | + dotnet --info + displayName: Show .NET SDK info - pwsh: | $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' From c9b46e605aca99016efccb44342f35a360af9aa9 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:04:57 +0100 Subject: [PATCH 28/59] Bootstrap net481 reference assemblies for fake compile --- scripts/Build-AddIns.ps1 | 8 ++++ scripts/Invoke-FakeUECompile.ps1 | 63 ++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+) diff --git a/scripts/Build-AddIns.ps1 b/scripts/Build-AddIns.ps1 index 6031bf2..a329f78 100644 --- a/scripts/Build-AddIns.ps1 +++ b/scripts/Build-AddIns.ps1 @@ -44,6 +44,14 @@ foreach ($proj in $projects) { '-nologo' ) + if ($env:FrameworkPathOverride) { + $buildArgs += "-p:FrameworkPathOverride=$($env:FrameworkPathOverride)" + } + + if ($env:TargetFrameworkRootPath) { + $buildArgs += "-p:TargetFrameworkRootPath=$($env:TargetFrameworkRootPath)" + } + if ($Rebuild) { $buildArgs += '-t:Rebuild' } diff --git a/scripts/Invoke-FakeUECompile.ps1 b/scripts/Invoke-FakeUECompile.ps1 index 3885ac4..c1111fe 100644 --- a/scripts/Invoke-FakeUECompile.ps1 +++ b/scripts/Invoke-FakeUECompile.ps1 @@ -40,6 +40,45 @@ $resolvedGeneratedShimProjectsPath = [System.IO.Path]::GetFullPath($GeneratedShi $shimDirectoryBuildPropsPath = Join-Path $resolvedShimProjectsPath 'Directory.Build.props' $buildScriptPath = Join-Path $PSScriptRoot 'Build-AddIns.ps1' +function Ensure-NetFrameworkReferenceAssemblies { + [CmdletBinding()] + param( + [Parameter(Mandatory = $true)] + [string]$WorkspaceRoot + ) + + $packageId = 'Microsoft.NETFramework.ReferenceAssemblies.net481' + $packageVersion = '1.0.3' + $packageRoot = Join-Path $WorkspaceRoot '.tmp\netfx-reference-assemblies' + $packageDir = Join-Path $packageRoot "$packageId.$packageVersion" + $packageArchive = Join-Path $packageRoot "$packageId.$packageVersion.nupkg" + $referenceDir = Join-Path $packageDir 'build\.NETFramework\v4.8.1' + $frameworkRoot = Join-Path $packageDir 'build\' + + if (-not (Test-Path -LiteralPath $referenceDir)) { + $null = New-Item -ItemType Directory -Path $packageRoot -Force + + $packageUrl = "https://www.nuget.org/api/v2/package/$packageId/$packageVersion" + Write-Host ("Downloading .NET Framework reference assemblies from {0}" -f $packageUrl) -ForegroundColor Cyan + Invoke-WebRequest -Uri $packageUrl -OutFile $packageArchive + + if (Test-Path -LiteralPath $packageDir) { + Remove-Item -LiteralPath $packageDir -Recurse -Force + } + + Expand-Archive -Path $packageArchive -DestinationPath $packageDir -Force + } + + if (-not (Test-Path -LiteralPath $referenceDir)) { + throw "Reference assembly directory not found after package extraction: $referenceDir" + } + + return [PSCustomObject]@{ + FrameworkPathOverride = $referenceDir + TargetFrameworkRootPath = $frameworkRoot + } +} + $shimManifest = Import-PowerShellDataFile -Path $resolvedShimManifestPath $shimProjects = if ($shimManifest.ContainsKey('Projects')) { $shimManifest.Projects @@ -120,6 +159,8 @@ foreach ($project in $projects) { $resolvedShimOutputPath = (Resolve-Path $ShimOutputPath).Path $previousReferencePath = $env:UNIFIED_ENGINEERING_REFERENCE_PATH +$previousFrameworkPathOverride = $env:FrameworkPathOverride +$previousTargetFrameworkRootPath = $env:TargetFrameworkRootPath $targetDirectories = foreach ($entry in $Directory) { foreach ($candidate in ($entry -split ',')) { $trimmed = $candidate.Trim().Trim([char[]]@("'", '"')) @@ -132,6 +173,14 @@ $targetDirectories = foreach ($entry in $Directory) { try { $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $resolvedShimOutputPath + if (-not $IsWindows) { + $netFrameworkReferences = Ensure-NetFrameworkReferenceAssemblies -WorkspaceRoot $repoRoot + $env:FrameworkPathOverride = $netFrameworkReferences.FrameworkPathOverride + $env:TargetFrameworkRootPath = $netFrameworkReferences.TargetFrameworkRootPath + + Write-Host ("Using .NET Framework reference assemblies from: {0}" -f $env:FrameworkPathOverride) -ForegroundColor Yellow + } + if ($env:TF_BUILD) { Write-Host ("##vso[task.setvariable variable=UNIFIED_ENGINEERING_REFERENCE_PATH]{0}" -f $resolvedShimOutputPath) } @@ -155,4 +204,18 @@ finally { else { $env:UNIFIED_ENGINEERING_REFERENCE_PATH = $previousReferencePath } + + if ([string]::IsNullOrWhiteSpace($previousFrameworkPathOverride)) { + Remove-Item Env:FrameworkPathOverride -ErrorAction SilentlyContinue + } + else { + $env:FrameworkPathOverride = $previousFrameworkPathOverride + } + + if ([string]::IsNullOrWhiteSpace($previousTargetFrameworkRootPath)) { + Remove-Item Env:TargetFrameworkRootPath -ErrorAction SilentlyContinue + } + else { + $env:TargetFrameworkRootPath = $previousTargetFrameworkRootPath + } } \ No newline at end of file From febf60b95db2d5784cbacaa8766105d810d7b4f8 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:12:40 +0100 Subject: [PATCH 29/59] Normalize Linux security stage paths --- azure-pipelines.yml | 50 ++++++++++++++++++++++----------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 8023eee..5b99626 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -128,17 +128,17 @@ stages: displayName: Show .NET SDK info - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' & (Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' $paths = @{ HasShimSources = Test-Path -LiteralPath (Join-Path $repoRoot 'shims') - HasShimManifest = Test-Path -LiteralPath (Join-Path $repoRoot 'shims\shim-projects.psd1') - HasGeneratedShimProjects = Test-Path -LiteralPath (Join-Path $repoRoot '.tmp\generated-shims') - HasShimAssemblies = Test-Path -LiteralPath (Join-Path $repoRoot 'shims\artifacts\Release') + HasShimManifest = Test-Path -LiteralPath (Join-Path $repoRoot 'shims/shim-projects.psd1') + HasGeneratedShimProjects = Test-Path -LiteralPath (Join-Path $repoRoot '.tmp/generated-shims') + HasShimAssemblies = Test-Path -LiteralPath (Join-Path $repoRoot 'shims/artifacts/Release') } foreach ($entry in $paths.GetEnumerator()) { @@ -149,30 +149,30 @@ stages: displayName: Detect shim artifacts condition: always() - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims artifact: ShimSources displayName: Publish shim sources condition: and(always(), eq(variables.HasShimSources, 'true')) - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\shim-projects.psd1 + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/shim-projects.psd1 artifact: ShimManifest displayName: Publish shim manifest condition: and(always(), eq(variables.HasShimManifest, 'true')) - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.tmp\generated-shims + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.tmp/generated-shims artifact: GeneratedShimProjects displayName: Publish generated shim project files condition: and(always(), eq(variables.HasGeneratedShimProjects, 'true')) - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\shims\artifacts\Release + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release artifact: ShimAssemblies displayName: Publish compiled shim assemblies condition: and(always(), eq(variables.HasShimAssemblies, 'true')) - pwsh: | $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' - $fakeCompileScriptPath = Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $fakeCompileScriptPath = Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1' $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') @@ -223,11 +223,11 @@ stages: retryCountOnTaskFailure: 3 inputs: key: "\"CoverityCLI\" | \"$(CoverityCliVersion)\" | \"v1\"" - path: $(Pipeline.Workspace)\Coverity-CLI + path: $(Pipeline.Workspace)/Coverity-CLI cacheHitVar: COVERITY_CLI_RESTORED - pwsh: | - $toolPath = "$(Pipeline.Workspace)\Coverity-CLI" + $toolPath = "$(Pipeline.Workspace)/Coverity-CLI" if (-not (Test-Path -Path $toolPath)) { New-Item -ItemType Directory -Path $toolPath | Out-Null } @@ -238,12 +238,12 @@ stages: inputs: polarisService: Coverity on Polaris polarisCommand: > - -c $(Pipeline.Workspace)\polaris.yml + -c $(Pipeline.Workspace)/polaris.yml analyze -w env: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) - POLARIS_HOME: $(Pipeline.Workspace)\Coverity-CLI + POLARIS_HOME: $(Pipeline.Workspace)/Coverity-CLI - pwsh: | $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') @@ -268,10 +268,10 @@ stages: - task: ArchiveFiles@2 displayName: Archive sources and build outputs inputs: - rootFolderOrFile: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + rootFolderOrFile: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework includeRootFolder: true archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + archiveFile: $(Build.ArtifactStagingDirectory)/blackduck-input.zip replaceExistingArchive: true verbose: true @@ -281,15 +281,15 @@ stages: inputs: BlackDuckScaService: BlackDuckScanner DetectVersion: latest - DetectFolder: $(Pipeline.Workspace)\detect + DetectFolder: $(Pipeline.Workspace)/detect DetectArguments: | --detect.project.name=$(Build.Repository.Name) --detect.project.version.name=$(BlackDuckProjectVersion) --detect.project.group.name=DabaconProductsGroup - --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework - --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip + --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework + --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input.zip --detect.cleanup=true - --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs + --detect.output.path=$(Build.ArtifactStagingDirectory)/blackduck-logs --detect.detector.search.depth=10 --detect.detector.search.continue=true --detect.blackduck.signature.scanner.upload.source.mode=true @@ -303,7 +303,7 @@ stages: --detect.wait.for.results.timeout=6000000 - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's\UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY $paths = @{ HasCoverityArtifacts = Test-Path -LiteralPath (Join-Path $repoRoot '.blackduck') @@ -319,17 +319,17 @@ stages: displayName: Detect security artifacts condition: always() - - publish: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework\.blackduck + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.blackduck artifact: Coverity displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) - - publish: $(Build.ArtifactStagingDirectory)\blackduck-logs + - publish: $(Build.ArtifactStagingDirectory)/blackduck-logs artifact: BlackDuckLogs displayName: Publish Black Duck logs condition: and(always(), eq(variables.HasBlackDuckLogs, 'true')) - - publish: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + - publish: $(Build.ArtifactStagingDirectory)/blackduck-input.zip artifact: BlackDuckInput displayName: Publish Black Duck input archive condition: and(always(), eq(variables.HasBlackDuckInput, 'true')) From 799cfb1d666aa7ca011cd7ac8c2520e2343bd475 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:21:55 +0100 Subject: [PATCH 30/59] Use stable pwsh path for Polaris capture --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5b99626..0d444ac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -170,7 +170,7 @@ stages: condition: and(always(), eq(variables.HasShimAssemblies, 'true')) - pwsh: | - $pwshPath = (Get-Command pwsh -ErrorAction Stop).Source + $pwshPath = if ($IsLinux) { '/usr/bin/pwsh' } else { (Get-Command pwsh -ErrorAction Stop).Source } $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' $fakeCompileScriptPath = Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1' From 9c80a80db56320d63d5d543af9c52c9cbffba46b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:30:37 +0100 Subject: [PATCH 31/59] Use PowerShell zip for Black Duck input --- azure-pipelines.yml | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 0d444ac..f5f7d05 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -265,15 +265,17 @@ stages: Write-Host "Black Duck project version: $versionName" displayName: Calculate Black Duck version - - task: ArchiveFiles@2 + - pwsh: | + $sourceRoot = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" + $archivePath = "$(Build.ArtifactStagingDirectory)/blackduck-input.zip" + + if (Test-Path -LiteralPath $archivePath) { + Remove-Item -LiteralPath $archivePath -Force + } + + Add-Type -AssemblyName System.IO.Compression.FileSystem + [System.IO.Compression.ZipFile]::CreateFromDirectory($sourceRoot, $archivePath) displayName: Archive sources and build outputs - inputs: - rootFolderOrFile: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework - includeRootFolder: true - archiveType: zip - archiveFile: $(Build.ArtifactStagingDirectory)/blackduck-input.zip - replaceExistingArchive: true - verbose: true - task: BlackDuckDetectTask@10 displayName: Black Duck scan From 9c5af2e574a768e25f13a1193c02f114023c156d Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:44:24 +0100 Subject: [PATCH 32/59] Split Coverity and Black Duck jobs --- azure-pipelines.yml | 78 +++++++++++++++++++++++++++++++-------------- 1 file changed, 54 insertions(+), 24 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index f5f7d05..ed31eab 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -94,8 +94,8 @@ stages: dependsOn: HostedTests condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - - job: CoverityAndBlackDuck - displayName: Coverity And Black Duck + - job: CoverityScan + displayName: Coverity Scan timeoutInMinutes: "180" pool: name: Technology-Microsoft-Hosted-Ubuntu @@ -245,6 +245,45 @@ stages: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) POLARIS_HOME: $(Pipeline.Workspace)/Coverity-CLI + - pwsh: | + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $paths = @{ + HasCoverityArtifacts = Test-Path -LiteralPath (Join-Path $repoRoot '.blackduck') + } + + foreach ($entry in $paths.GetEnumerator()) { + $value = if ($entry.Value) { 'true' } else { 'false' } + Write-Host "##vso[task.setvariable variable=$($entry.Key)]$value" + Write-Host "$($entry.Key): $value" + } + displayName: Detect security artifacts + condition: always() + + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.blackduck + artifact: Coverity + displayName: Publish Coverity artifacts + condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) + + - job: BlackDuckScan + displayName: Black Duck Scan + dependsOn: CoverityScan + condition: succeededOrFailed() + timeoutInMinutes: "120" + pool: + name: Technology-Microsoft-Hosted-Windows + demands: + - ImageOverride -equals windows-latest + container: mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022 + workspace: + clean: all + steps: + - checkout: self + fetchDepth: "0" + + - checkout: Templates + fetchDepth: "1" + - pwsh: | $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` @@ -265,17 +304,15 @@ stages: Write-Host "Black Duck project version: $versionName" displayName: Calculate Black Duck version - - pwsh: | - $sourceRoot = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" - $archivePath = "$(Build.ArtifactStagingDirectory)/blackduck-input.zip" - - if (Test-Path -LiteralPath $archivePath) { - Remove-Item -LiteralPath $archivePath -Force - } - - Add-Type -AssemblyName System.IO.Compression.FileSystem - [System.IO.Compression.ZipFile]::CreateFromDirectory($sourceRoot, $archivePath) + - task: ArchiveFiles@2 displayName: Archive sources and build outputs + inputs: + rootFolderOrFile: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + includeRootFolder: true + archiveType: zip + archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip + replaceExistingArchive: true + verbose: true - task: BlackDuckDetectTask@10 displayName: Black Duck scan @@ -283,15 +320,15 @@ stages: inputs: BlackDuckScaService: BlackDuckScanner DetectVersion: latest - DetectFolder: $(Pipeline.Workspace)/detect + DetectFolder: $(Pipeline.Workspace)\detect DetectArguments: | --detect.project.name=$(Build.Repository.Name) --detect.project.version.name=$(BlackDuckProjectVersion) --detect.project.group.name=DabaconProductsGroup - --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework - --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input.zip + --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip --detect.cleanup=true - --detect.output.path=$(Build.ArtifactStagingDirectory)/blackduck-logs + --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs --detect.detector.search.depth=10 --detect.detector.search.continue=true --detect.blackduck.signature.scanner.upload.source.mode=true @@ -305,10 +342,8 @@ stages: --detect.wait.for.results.timeout=6000000 - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY $paths = @{ - HasCoverityArtifacts = Test-Path -LiteralPath (Join-Path $repoRoot '.blackduck') HasBlackDuckLogs = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-logs') HasBlackDuckInput = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-input.zip') } @@ -318,14 +353,9 @@ stages: Write-Host "##vso[task.setvariable variable=$($entry.Key)]$value" Write-Host "$($entry.Key): $value" } - displayName: Detect security artifacts + displayName: Detect Black Duck artifacts condition: always() - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.blackduck - artifact: Coverity - displayName: Publish Coverity artifacts - condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) - - publish: $(Build.ArtifactStagingDirectory)/blackduck-logs artifact: BlackDuckLogs displayName: Publish Black Duck logs From de3dfa13b272c4e039f8b1d8bb51a00c2d7f2a8a Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 16:56:39 +0100 Subject: [PATCH 33/59] Add Ubuntu Black Duck comparison job --- azure-pipelines.yml | 105 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 104 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index ed31eab..80cfb21 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -245,7 +245,6 @@ stages: AZURE_DEVOPS_EXT_PAT: $(System.AccessToken) POLARIS_HOME: $(Pipeline.Workspace)/Coverity-CLI - - pwsh: | - pwsh: | $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' $paths = @{ @@ -365,3 +364,107 @@ stages: artifact: BlackDuckInput displayName: Publish Black Duck input archive condition: and(always(), eq(variables.HasBlackDuckInput, 'true')) + + - job: BlackDuckScanUbuntu + displayName: Black Duck Scan Ubuntu + dependsOn: CoverityScan + condition: succeededOrFailed() + timeoutInMinutes: "120" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + steps: + - checkout: self + fetchDepth: "0" + + - checkout: Templates + fetchDepth: "1" + + - pwsh: | + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $versionName = "$branchName-$($env:BUILD_BUILDID)-ubuntu" + } + + $versionName = "$versionName$templatesPostfix" + + Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" + Write-Host "Black Duck project version: $versionName" + displayName: Calculate Black Duck version (Ubuntu) + + - pwsh: | + $sourceRoot = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" + $archivePath = "$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip" + + if (Test-Path -LiteralPath $archivePath) { + Remove-Item -LiteralPath $archivePath -Force + } + + Compress-Archive -Path $sourceRoot -DestinationPath $archivePath -CompressionLevel Optimal + Write-Host "Created archive at $archivePath" + displayName: Archive sources and build outputs (Ubuntu) + + - task: BlackDuckDetectTask@10 + displayName: Black Duck scan (Ubuntu) + retryCountOnTaskFailure: 0 + inputs: + BlackDuckScaService: BlackDuckScanner + DetectVersion: latest + DetectFolder: $(Pipeline.Workspace)/detect-ubuntu + DetectArguments: | + --detect.project.name=$(Build.Repository.Name) + --detect.project.version.name=$(BlackDuckProjectVersion) + --detect.project.group.name=DabaconProductsGroup + --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework + --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip + --detect.cleanup=true + --detect.output.path=$(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu + --detect.detector.search.depth=10 + --detect.detector.search.continue=true + --detect.blackduck.signature.scanner.upload.source.mode=true + --detect.blackduck.signature.scanner.copyright.search=true + --detect.blackduck.signature.scanner.license.search=true + --logging.level.detect=DEBUG + --detect.diagnostic=true + --detect.timeout=1800 + --detect.blackduck.signature.scanner.arguments= --fs-wait-time=90 + --detect.wait.for.results=true + --detect.wait.for.results.timeout=6000000 + + - pwsh: | + $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY + $paths = @{ + HasBlackDuckLogsUbuntu = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-logs-ubuntu') + HasBlackDuckInputUbuntu = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-input-ubuntu.zip') + } + + foreach ($entry in $paths.GetEnumerator()) { + $value = if ($entry.Value) { 'true' } else { 'false' } + Write-Host "##vso[task.setvariable variable=$($entry.Key)]$value" + Write-Host "$($entry.Key): $value" + } + displayName: Detect Black Duck artifacts (Ubuntu) + condition: always() + + - publish: $(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu + artifact: BlackDuckLogsUbuntu + displayName: Publish Black Duck logs (Ubuntu) + condition: and(always(), eq(variables.HasBlackDuckLogsUbuntu, 'true')) + + - publish: $(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip + artifact: BlackDuckInputUbuntu + displayName: Publish Black Duck input archive (Ubuntu) + condition: and(always(), eq(variables.HasBlackDuckInputUbuntu, 'true')) From 40c84ae2801b4d35c4826e7dbb6e4508e0a4c6f3 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 17:08:20 +0100 Subject: [PATCH 34/59] Align Black Duck path with Templates --- azure-pipelines.yml | 41 ++++++++++++++++++++++++++++++++++++++++- 1 file changed, 40 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 80cfb21..013a224 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -22,6 +22,9 @@ parameters: - name: EnableSecurityValidation type: string default: "false" + - name: EnableUbuntuBlackDuckComparison + type: string + default: "false" variables: - template: variables.yml@Templates @@ -283,6 +286,42 @@ stages: - checkout: Templates fetchDepth: "1" + - pwsh: | + $workdir = "$(Pipeline.Workspace)\JDK" + New-Item -Path $workdir -ItemType Directory -Force | Out-Null + + $url = "https://github.com/adoptium/temurin8-binaries/releases/download/jdk8u452-b09/OpenJDK8U-jdk_x64_windows_hotspot_8u452b09.zip" + $zipPath = "$workdir\Temurin8.zip" + + Write-Host "Invoke-WebRequest -Uri $url -OutFile $zipPath" + [Net.ServicePointManager]::SecurityProtocol = "Tls12" + Invoke-WebRequest -Uri $url -OutFile $zipPath + + Write-Host "Expand-Archive -LiteralPath $zipPath -DestinationPath $workdir -Force" + Expand-Archive -LiteralPath $zipPath -DestinationPath $workdir -Force + displayName: Download Temurin JDK 8 + + - pwsh: | + $jdkFolder = Get-ChildItem -Path "$(Pipeline.Workspace)\JDK" -Directory | + Where-Object { $_.Name -like 'jdk8*' } | + Select-Object -First 1 + + if (-not $jdkFolder) { + Write-Error "Cannot locate a jdk8* folder under $(Pipeline.Workspace)\JDK" + exit 1 + } + + $javaHome = $jdkFolder.FullName + Write-Host "##vso[task.setvariable variable=JAVA_HOME]$javaHome" + Write-Host "##vso[task.setvariable variable=PATH]$javaHome\bin;$($env:PATH)" + Write-Host "JAVA_HOME is now set to $javaHome" + displayName: Configure JAVA_HOME for Temurin JDK 8 + + - pwsh: | + Write-Host "Verifying Java..." + java -version + displayName: Verify Java Installation + - pwsh: | $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` @@ -368,7 +407,7 @@ stages: - job: BlackDuckScanUbuntu displayName: Black Duck Scan Ubuntu dependsOn: CoverityScan - condition: succeededOrFailed() + condition: and(succeededOrFailed(), ${{ eq(parameters.EnableUbuntuBlackDuckComparison, 'true') }}) timeoutInMinutes: "120" pool: name: Technology-Microsoft-Hosted-Ubuntu From 042b0a5418dfe40ee8eec6a6a145ad8e9f58991b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 17:59:41 +0100 Subject: [PATCH 35/59] Remove invalid Black Duck project group --- azure-pipelines.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 013a224..46dc15f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -362,7 +362,6 @@ stages: DetectArguments: | --detect.project.name=$(Build.Repository.Name) --detect.project.version.name=$(BlackDuckProjectVersion) - --detect.project.group.name=DabaconProductsGroup --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip --detect.cleanup=true @@ -466,7 +465,6 @@ stages: DetectArguments: | --detect.project.name=$(Build.Repository.Name) --detect.project.version.name=$(BlackDuckProjectVersion) - --detect.project.group.name=DabaconProductsGroup --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip --detect.cleanup=true From c481c8a55b4e859abadb017ca36a840d0630ba33 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 18:37:58 +0100 Subject: [PATCH 36/59] Align Black Duck flow with Templates --- azure-pipelines.yml | 224 +++++++++++++++++++++++++++++++++++--------- 1 file changed, 180 insertions(+), 44 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 46dc15f..1080c39 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -97,6 +97,170 @@ stages: dependsOn: HostedTests condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: + - job: BlackDuckInitialisation + displayName: BlackDuck Initialise + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + steps: + - checkout: none + + - pwsh: | + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + + $branchVersion = "$env:BUILD_SOURCEBRANCHNAME$templatesPostfix" + + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $semanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)$templatesPostfix" + $ubuntuComparisonVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $semanticVersion = "$branchName-$($env:BUILD_BUILDID)$templatesPostfix" + $ubuntuComparisonVersion = "$branchName-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + } + + Write-Host "##vso[task.setvariable variable=BranchVersion]$branchVersion" + Write-Host "##vso[task.setvariable variable=BranchVersion;isOutput=true]$branchVersion" + Write-Host "##vso[task.setvariable variable=SemanticVersion]$semanticVersion" + Write-Host "##vso[task.setvariable variable=SemanticVersion;isOutput=true]$semanticVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion]$ubuntuComparisonVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion;isOutput=true]$ubuntuComparisonVersion" + displayName: Calculate Black Duck versions + name: CalculateVersion + + - pwsh: | + $headers = @{ + Authorization = "token $env:BLACKDUCK_ACCESS_TOKEN" + Accept = 'application/vnd.blackducksoftware.user-4+json' + } + + $uri = "$env:BLACKDUCK_URL/api/tokens/authenticate" + $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers + + Write-Host "##vso[task.setvariable variable=BaseUrl]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BaseUrl;isOutput=true]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BearerToken]$($response.bearerToken)" + Write-Host "##vso[task.setvariable variable=BearerToken;isOutput=true]$($response.bearerToken)" + displayName: Authenticate to Black Duck + name: Authenticate + retryCountOnTaskFailure: 3 + env: + BLACKDUCK_URL: https://aveva.app.blackduck.com + BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) + + - pwsh: | + $headers = @{ + Authorization = "Bearer $env:BearerToken" + Accept = 'application/vnd.blackducksoftware.project-detail-7+json' + } + + $projectName = $env:BUILD_REPOSITORY_NAME + $limit = 100 + $offset = 0 + $allProjects = @() + + do { + $uri = "$env:BaseUrl/api/projects?limit=$limit&offset=$offset&q=name:$projectName" + $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers + if ($response.items) { + $allProjects += $response.items + } + $offset += $limit + } while ($offset -lt $response.totalCount) + + $matchedProject = $allProjects | Where-Object { $_.name -eq $projectName } + + if (-not $matchedProject) { + Write-Host "##vso[task.setvariable variable=ProjectExists]false" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]false" + exit 0 + } + + $projectId = $matchedProject._meta.href.Split('/')[-1] + Write-Host "##vso[task.setvariable variable=ProjectId]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectId;isOutput=true]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectExists]true" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]true" + displayName: Get Black Duck project ID + name: ProjectId + retryCountOnTaskFailure: 3 + env: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + + - job: BlackDuckClone + displayName: BlackDuck Clone + dependsOn: BlackDuckInitialisation + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + - name: BearerToken + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BearerToken'] ] + - name: BaseUrl + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BaseUrl'] ] + - name: ProjectId + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectId'] ] + - name: ProjectExists + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'] ] + - name: BranchVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.BranchVersion'] ] + - name: SemanticVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] + - name: UbuntuComparisonVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] + steps: + - checkout: none + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: main + TargetVersion: $(BranchVersion) + Phase: DEVELOPMENT + RemoveTargetVersion: false + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: $(BranchVersion) + TargetVersion: $(SemanticVersion) + Phase: PLANNING + RemoveTargetVersion: true + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: $(BranchVersion) + TargetVersion: $(UbuntuComparisonVersion) + Phase: PLANNING + RemoveTargetVersion: true + - job: CoverityScan displayName: Coverity Scan timeoutInMinutes: "180" @@ -269,7 +433,10 @@ stages: - job: BlackDuckScan displayName: Black Duck Scan - dependsOn: CoverityScan + dependsOn: + - BlackDuckInitialisation + - BlackDuckClone + - CoverityScan condition: succeededOrFailed() timeoutInMinutes: "120" pool: @@ -279,6 +446,9 @@ stages: container: mcr.microsoft.com/dotnet/sdk:9.0-windowsservercore-ltsc2022 workspace: clean: all + variables: + - name: SemanticVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] steps: - checkout: self fetchDepth: "0" @@ -322,26 +492,6 @@ stages: java -version displayName: Verify Java Installation - - pwsh: | - $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') - $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` - ? "@$($templatesBranch)" ` - : '' - - if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" - } - else { - $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - $versionName = "$branchName-$($env:BUILD_BUILDID)" - } - - $versionName = "$versionName$templatesPostfix" - - Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" - Write-Host "Black Duck project version: $versionName" - displayName: Calculate Black Duck version - - task: ArchiveFiles@2 displayName: Archive sources and build outputs inputs: @@ -361,7 +511,7 @@ stages: DetectFolder: $(Pipeline.Workspace)\detect DetectArguments: | --detect.project.name=$(Build.Repository.Name) - --detect.project.version.name=$(BlackDuckProjectVersion) + --detect.project.version.name=$(SemanticVersion) --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip --detect.cleanup=true @@ -405,7 +555,10 @@ stages: - job: BlackDuckScanUbuntu displayName: Black Duck Scan Ubuntu - dependsOn: CoverityScan + dependsOn: + - BlackDuckInitialisation + - BlackDuckClone + - CoverityScan condition: and(succeededOrFailed(), ${{ eq(parameters.EnableUbuntuBlackDuckComparison, 'true') }}) timeoutInMinutes: "120" pool: @@ -416,6 +569,9 @@ stages: container: mcr.microsoft.com/dotnet/sdk:9.0 workspace: clean: all + variables: + - name: UbuntuComparisonVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] steps: - checkout: self fetchDepth: "0" @@ -423,26 +579,6 @@ stages: - checkout: Templates fetchDepth: "1" - - pwsh: | - $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') - $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` - ? "@$($templatesBranch)" ` - : '' - - if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - $versionName = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu" - } - else { - $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - $versionName = "$branchName-$($env:BUILD_BUILDID)-ubuntu" - } - - $versionName = "$versionName$templatesPostfix" - - Write-Host "##vso[task.setvariable variable=BlackDuckProjectVersion]$versionName" - Write-Host "Black Duck project version: $versionName" - displayName: Calculate Black Duck version (Ubuntu) - - pwsh: | $sourceRoot = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" $archivePath = "$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip" @@ -464,7 +600,7 @@ stages: DetectFolder: $(Pipeline.Workspace)/detect-ubuntu DetectArguments: | --detect.project.name=$(Build.Repository.Name) - --detect.project.version.name=$(BlackDuckProjectVersion) + --detect.project.version.name=$(UbuntuComparisonVersion) --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip --detect.cleanup=true From c730a8c8206a113a66b2325a41de68c87c862e0b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 18:44:15 +0100 Subject: [PATCH 37/59] Split Coverity and Black Duck stages --- azure-pipelines.yml | 339 ++++++++++++++++++++++---------------------- 1 file changed, 171 insertions(+), 168 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1080c39..532332f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -92,175 +92,11 @@ stages: ./scripts/Tests/Run-Tests.ps1 -Quick displayName: Run tests - - stage: SecurityValidation - displayName: Security Validation + - stage: CoverityValidation + displayName: Coverity Validation dependsOn: HostedTests condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - - job: BlackDuckInitialisation - displayName: BlackDuck Initialise - timeoutInMinutes: "10" - pool: - name: Technology-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/dotnet/sdk:9.0 - workspace: - clean: all - variables: - - group: BlackDuck on Polaris - steps: - - checkout: none - - - pwsh: | - $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') - $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` - ? "@$($templatesBranch)" ` - : '' - - $branchVersion = "$env:BUILD_SOURCEBRANCHNAME$templatesPostfix" - - if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - $semanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)$templatesPostfix" - $ubuntuComparisonVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" - } - else { - $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - $semanticVersion = "$branchName-$($env:BUILD_BUILDID)$templatesPostfix" - $ubuntuComparisonVersion = "$branchName-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" - } - - Write-Host "##vso[task.setvariable variable=BranchVersion]$branchVersion" - Write-Host "##vso[task.setvariable variable=BranchVersion;isOutput=true]$branchVersion" - Write-Host "##vso[task.setvariable variable=SemanticVersion]$semanticVersion" - Write-Host "##vso[task.setvariable variable=SemanticVersion;isOutput=true]$semanticVersion" - Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion]$ubuntuComparisonVersion" - Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion;isOutput=true]$ubuntuComparisonVersion" - displayName: Calculate Black Duck versions - name: CalculateVersion - - - pwsh: | - $headers = @{ - Authorization = "token $env:BLACKDUCK_ACCESS_TOKEN" - Accept = 'application/vnd.blackducksoftware.user-4+json' - } - - $uri = "$env:BLACKDUCK_URL/api/tokens/authenticate" - $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers - - Write-Host "##vso[task.setvariable variable=BaseUrl]$env:BLACKDUCK_URL" - Write-Host "##vso[task.setvariable variable=BaseUrl;isOutput=true]$env:BLACKDUCK_URL" - Write-Host "##vso[task.setvariable variable=BearerToken]$($response.bearerToken)" - Write-Host "##vso[task.setvariable variable=BearerToken;isOutput=true]$($response.bearerToken)" - displayName: Authenticate to Black Duck - name: Authenticate - retryCountOnTaskFailure: 3 - env: - BLACKDUCK_URL: https://aveva.app.blackduck.com - BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) - - - pwsh: | - $headers = @{ - Authorization = "Bearer $env:BearerToken" - Accept = 'application/vnd.blackducksoftware.project-detail-7+json' - } - - $projectName = $env:BUILD_REPOSITORY_NAME - $limit = 100 - $offset = 0 - $allProjects = @() - - do { - $uri = "$env:BaseUrl/api/projects?limit=$limit&offset=$offset&q=name:$projectName" - $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers - if ($response.items) { - $allProjects += $response.items - } - $offset += $limit - } while ($offset -lt $response.totalCount) - - $matchedProject = $allProjects | Where-Object { $_.name -eq $projectName } - - if (-not $matchedProject) { - Write-Host "##vso[task.setvariable variable=ProjectExists]false" - Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]false" - exit 0 - } - - $projectId = $matchedProject._meta.href.Split('/')[-1] - Write-Host "##vso[task.setvariable variable=ProjectId]$projectId" - Write-Host "##vso[task.setvariable variable=ProjectId;isOutput=true]$projectId" - Write-Host "##vso[task.setvariable variable=ProjectExists]true" - Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]true" - displayName: Get Black Duck project ID - name: ProjectId - retryCountOnTaskFailure: 3 - env: - BearerToken: $(BearerToken) - BaseUrl: $(BaseUrl) - - - job: BlackDuckClone - displayName: BlackDuck Clone - dependsOn: BlackDuckInitialisation - timeoutInMinutes: "10" - pool: - name: Technology-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/dotnet/sdk:9.0 - workspace: - clean: all - variables: - - group: BlackDuck on Polaris - - name: BearerToken - value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BearerToken'] ] - - name: BaseUrl - value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BaseUrl'] ] - - name: ProjectId - value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectId'] ] - - name: ProjectExists - value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'] ] - - name: BranchVersion - value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.BranchVersion'] ] - - name: SemanticVersion - value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] - - name: UbuntuComparisonVersion - value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] - steps: - - checkout: none - - - template: steps/blackduck-clone-version.yml@Templates - parameters: - BearerToken: $(BearerToken) - BaseUrl: $(BaseUrl) - ProjectId: $(ProjectId) - SourceVersion: main - TargetVersion: $(BranchVersion) - Phase: DEVELOPMENT - RemoveTargetVersion: false - - - template: steps/blackduck-clone-version.yml@Templates - parameters: - BearerToken: $(BearerToken) - BaseUrl: $(BaseUrl) - ProjectId: $(ProjectId) - SourceVersion: $(BranchVersion) - TargetVersion: $(SemanticVersion) - Phase: PLANNING - RemoveTargetVersion: true - - - template: steps/blackduck-clone-version.yml@Templates - parameters: - BearerToken: $(BearerToken) - BaseUrl: $(BaseUrl) - ProjectId: $(ProjectId) - SourceVersion: $(BranchVersion) - TargetVersion: $(UbuntuComparisonVersion) - Phase: PLANNING - RemoveTargetVersion: true - - job: CoverityScan displayName: Coverity Scan timeoutInMinutes: "180" @@ -431,12 +267,180 @@ stages: displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) + - stage: BlackDuckValidation + displayName: Black Duck Validation + dependsOn: HostedTests + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) + jobs: + - job: BlackDuckInitialisation + displayName: BlackDuck Initialise + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + steps: + - checkout: none + + - pwsh: | + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + + $branchVersion = "$env:BUILD_SOURCEBRANCHNAME$templatesPostfix" + + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $semanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)$templatesPostfix" + $ubuntuComparisonVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $semanticVersion = "$branchName-$($env:BUILD_BUILDID)$templatesPostfix" + $ubuntuComparisonVersion = "$branchName-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + } + + Write-Host "##vso[task.setvariable variable=BranchVersion]$branchVersion" + Write-Host "##vso[task.setvariable variable=BranchVersion;isOutput=true]$branchVersion" + Write-Host "##vso[task.setvariable variable=SemanticVersion]$semanticVersion" + Write-Host "##vso[task.setvariable variable=SemanticVersion;isOutput=true]$semanticVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion]$ubuntuComparisonVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion;isOutput=true]$ubuntuComparisonVersion" + displayName: Calculate Black Duck versions + name: CalculateVersion + + - pwsh: | + $headers = @{ + Authorization = "token $env:BLACKDUCK_ACCESS_TOKEN" + Accept = 'application/vnd.blackducksoftware.user-4+json' + } + + $uri = "$env:BLACKDUCK_URL/api/tokens/authenticate" + $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers + + Write-Host "##vso[task.setvariable variable=BaseUrl]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BaseUrl;isOutput=true]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BearerToken]$($response.bearerToken)" + Write-Host "##vso[task.setvariable variable=BearerToken;isOutput=true]$($response.bearerToken)" + displayName: Authenticate to Black Duck + name: Authenticate + retryCountOnTaskFailure: 3 + env: + BLACKDUCK_URL: https://aveva.app.blackduck.com + BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) + + - pwsh: | + $headers = @{ + Authorization = "Bearer $env:BearerToken" + Accept = 'application/vnd.blackducksoftware.project-detail-7+json' + } + + $projectName = $env:BUILD_REPOSITORY_NAME + $limit = 100 + $offset = 0 + $allProjects = @() + + do { + $uri = "$env:BaseUrl/api/projects?limit=$limit&offset=$offset&q=name:$projectName" + $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers + if ($response.items) { + $allProjects += $response.items + } + $offset += $limit + } while ($offset -lt $response.totalCount) + + $matchedProject = $allProjects | Where-Object { $_.name -eq $projectName } + + if (-not $matchedProject) { + Write-Host "##vso[task.setvariable variable=ProjectExists]false" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]false" + exit 0 + } + + $projectId = $matchedProject._meta.href.Split('/')[-1] + Write-Host "##vso[task.setvariable variable=ProjectId]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectId;isOutput=true]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectExists]true" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]true" + displayName: Get Black Duck project ID + name: ProjectId + retryCountOnTaskFailure: 3 + env: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + + - job: BlackDuckClone + displayName: BlackDuck Clone + dependsOn: BlackDuckInitialisation + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + - name: BearerToken + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BearerToken'] ] + - name: BaseUrl + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BaseUrl'] ] + - name: ProjectId + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectId'] ] + - name: ProjectExists + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'] ] + - name: BranchVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.BranchVersion'] ] + - name: SemanticVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] + - name: UbuntuComparisonVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] + steps: + - checkout: none + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: main + TargetVersion: $(BranchVersion) + Phase: DEVELOPMENT + RemoveTargetVersion: false + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: $(BranchVersion) + TargetVersion: $(SemanticVersion) + Phase: PLANNING + RemoveTargetVersion: true + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: $(BranchVersion) + TargetVersion: $(UbuntuComparisonVersion) + Phase: PLANNING + RemoveTargetVersion: true + - job: BlackDuckScan displayName: Black Duck Scan dependsOn: - BlackDuckInitialisation - BlackDuckClone - - CoverityScan condition: succeededOrFailed() timeoutInMinutes: "120" pool: @@ -558,7 +562,6 @@ stages: dependsOn: - BlackDuckInitialisation - BlackDuckClone - - CoverityScan condition: and(succeededOrFailed(), ${{ eq(parameters.EnableUbuntuBlackDuckComparison, 'true') }}) timeoutInMinutes: "120" pool: From 244ad3b6e000f77afc1be4ac0cfd0b89df93cf45 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 19:36:02 +0100 Subject: [PATCH 38/59] Fail early when Black Duck project is missing --- azure-pipelines.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 532332f..54b0505 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -358,9 +358,11 @@ stages: $matchedProject = $allProjects | Where-Object { $_.name -eq $projectName } if (-not $matchedProject) { + Write-Host "Project '$projectName' not found in Black Duck." Write-Host "##vso[task.setvariable variable=ProjectExists]false" Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]false" - exit 0 + Write-Host "##vso[task.logissue type=error]Black Duck project '$projectName' must exist before the scan runs. Create it or grant the scanner access to the existing project." + exit 1 } $projectId = $matchedProject._meta.href.Split('/')[-1] @@ -378,6 +380,7 @@ stages: - job: BlackDuckClone displayName: BlackDuck Clone dependsOn: BlackDuckInitialisation + condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true')) timeoutInMinutes: "10" pool: name: Technology-Microsoft-Hosted-Ubuntu @@ -441,7 +444,7 @@ stages: dependsOn: - BlackDuckInitialisation - BlackDuckClone - condition: succeededOrFailed() + condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true')) timeoutInMinutes: "120" pool: name: Technology-Microsoft-Hosted-Windows @@ -562,7 +565,7 @@ stages: dependsOn: - BlackDuckInitialisation - BlackDuckClone - condition: and(succeededOrFailed(), ${{ eq(parameters.EnableUbuntuBlackDuckComparison, 'true') }}) + condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true'), ${{ eq(parameters.EnableUbuntuBlackDuckComparison, 'true') }}) timeoutInMinutes: "120" pool: name: Technology-Microsoft-Hosted-Ubuntu From c61af6278fb61f1ea31cde23cad601eaebafe9ae Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 19:51:58 +0100 Subject: [PATCH 39/59] Add component Black Duck template comparison --- azure-pipelines.yml | 61 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 54b0505..5235131 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -25,6 +25,9 @@ parameters: - name: EnableUbuntuBlackDuckComparison type: string default: "false" + - name: EnableComponentTemplateBlackDuckComparison + type: string + default: "false" variables: - template: variables.yml@Templates @@ -647,3 +650,61 @@ stages: artifact: BlackDuckInputUbuntu displayName: Publish Black Duck input archive (Ubuntu) condition: and(always(), eq(variables.HasBlackDuckInputUbuntu, 'true')) + + - stage: BuildWithShims + displayName: Build With Shims + dependsOn: HostedTests + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}, ${{ eq(parameters.EnableComponentTemplateBlackDuckComparison, 'true') }}) + jobs: + - job: BuildWithShims + displayName: Build With Shims + timeoutInMinutes: "120" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + steps: + - checkout: self + fetchDepth: "0" + + - checkout: Templates + fetchDepth: "1" + + - task: UseDotNet@2 + displayName: Ensure .NET 9 SDK for shim build + retryCountOnTaskFailure: 3 + inputs: + packageType: sdk + version: 9.0.x + + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release + displayName: Build template and example projects with fake UE references + + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework + artifact: Source + displayName: Publish Source artifact for component template comparison + + - publish: $(Pipeline.Workspace)/s/Templates + artifact: Templates + displayName: Publish Templates artifact for component template comparison + + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release + artifact: Build.Release + displayName: Publish build artifact for component template comparison + + - stage: ComponentTemplateBlackDuckComparison + displayName: Component Template Black Duck Comparison + dependsOn: BuildWithShims + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}, ${{ eq(parameters.EnableComponentTemplateBlackDuckComparison, 'true') }}) + jobs: + - template: component-blackduck.yml@Templates + parameters: + BuildArtifact: Build.Release + BlackDuckGroup: DabaconGroup + IsIPR: false From cac6c539e053b5c9257e3b12eb67d3b9eee1696a Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 19:54:31 +0100 Subject: [PATCH 40/59] Use custom Templates branch for Black Duck comparison --- azure-pipelines.yml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 5235131..1133eac 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,7 +16,7 @@ resources: - repository: Templates type: git name: Technology/Templates - ref: refs/heads/main + ref: refs/heads/feature/uecf-blackduck-pool-params parameters: - name: EnableSecurityValidation @@ -708,3 +708,10 @@ stages: BuildArtifact: Build.Release BlackDuckGroup: DabaconGroup IsIPR: false + UbuntuPoolName: Technology-Microsoft-Hosted-Ubuntu + UbuntuPoolDemands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + WindowsPoolName: Technology-Microsoft-Hosted-Windows + WindowsPoolDemands: + - ImageOverride -equals windows-latest From 98c9d0fa333b24d99d39c26fb6c62b0ade1a394a Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 19:55:08 +0100 Subject: [PATCH 41/59] Use dedicated Templates resource for comparison --- azure-pipelines.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 1133eac..349550b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -16,6 +16,10 @@ resources: - repository: Templates type: git name: Technology/Templates + ref: refs/heads/main + - repository: TemplatesCustom + type: git + name: Dabacon Products/Templates ref: refs/heads/feature/uecf-blackduck-pool-params parameters: @@ -703,7 +707,7 @@ stages: dependsOn: BuildWithShims condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}, ${{ eq(parameters.EnableComponentTemplateBlackDuckComparison, 'true') }}) jobs: - - template: component-blackduck.yml@Templates + - template: component-blackduck.yml@TemplatesCustom parameters: BuildArtifact: Build.Release BlackDuckGroup: DabaconGroup From 4465cafa38d10d5ac9c5f6d96507526d0282f0d9 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 20:56:50 +0100 Subject: [PATCH 42/59] Set explicit Black Duck project name --- azure-pipelines.yml | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 349550b..6ef55d0 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -39,6 +39,8 @@ variables: value: .RnD.Polaris.Dabacon-Products.SecurityAdvisors - name: CoverityCliVersion value: 2025.9.0 + - name: BlackDuckProjectName + value: AVEVA.UnifiedEngineeringCustomizationFramework trigger: branches: @@ -203,7 +205,7 @@ stages: $polarisContent = @( 'version: 1' 'project:' - " name: $env:BUILD_REPOSITORY_NAME" + " name: $(BlackDuckProjectName)" " branch: $polarisBranch" " projectDir: $repoRoot" ' revision:' @@ -348,7 +350,7 @@ stages: Accept = 'application/vnd.blackducksoftware.project-detail-7+json' } - $projectName = $env:BUILD_REPOSITORY_NAME + $projectName = '$(BlackDuckProjectName)' $limit = 100 $offset = 0 $allProjects = @() @@ -524,7 +526,7 @@ stages: DetectVersion: latest DetectFolder: $(Pipeline.Workspace)\detect DetectArguments: | - --detect.project.name=$(Build.Repository.Name) + --detect.project.name=$(BlackDuckProjectName) --detect.project.version.name=$(SemanticVersion) --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip @@ -612,7 +614,7 @@ stages: DetectVersion: latest DetectFolder: $(Pipeline.Workspace)/detect-ubuntu DetectArguments: | - --detect.project.name=$(Build.Repository.Name) + --detect.project.name=$(BlackDuckProjectName) --detect.project.version.name=$(UbuntuComparisonVersion) --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip From c8cbb8bcdfafa68c594740a626948fdf0ea1651e Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 21:49:05 +0100 Subject: [PATCH 43/59] Retry Ubuntu Black Duck download --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 6ef55d0..365addd 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -608,7 +608,7 @@ stages: - task: BlackDuckDetectTask@10 displayName: Black Duck scan (Ubuntu) - retryCountOnTaskFailure: 0 + retryCountOnTaskFailure: 3 inputs: BlackDuckScaService: BlackDuckScanner DetectVersion: latest From b7c0f0125292baaf6f1a54d3927fd2e99219b8f9 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 22:09:57 +0100 Subject: [PATCH 44/59] Add Ubuntu Black Duck network diagnostics --- azure-pipelines.yml | 53 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 365addd..70811f5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -606,6 +606,53 @@ stages: Write-Host "Created archive at $archivePath" displayName: Archive sources and build outputs (Ubuntu) + - pwsh: | + $diagnosticPath = "$(Build.ArtifactStagingDirectory)/blackduck-network-ubuntu.txt" + $targets = @( + 'detect.blackduck.com', + 'aveva.app.blackduck.com' + ) + + $lines = New-Object System.Collections.Generic.List[string] + + foreach ($target in $targets) { + $lines.Add("=== $target ===") + + try { + $addresses = [System.Net.Dns]::GetHostAddresses($target) | ForEach-Object { $_.IPAddressToString } + $lines.Add("DNS: $($addresses -join ', ')") + } + catch { + $lines.Add("DNS ERROR: $($_.Exception.Message)") + } + + try { + $pingOutput = & ping -c 2 $target 2>&1 + $lines.Add('PING:') + foreach ($line in $pingOutput) { + $lines.Add([string]$line) + } + } + catch { + $lines.Add("PING ERROR: $($_.Exception.Message)") + } + + try { + $response = Invoke-WebRequest -Uri ("https://{0}" -f $target) -Method Head -SkipHttpErrorCheck -TimeoutSec 30 + $lines.Add("HTTPS: StatusCode=$($response.StatusCode)") + } + catch { + $lines.Add("HTTPS ERROR: $($_.Exception.Message)") + } + + $lines.Add('') + } + + Set-Content -LiteralPath $diagnosticPath -Value $lines -Encoding utf8 + Get-Content -LiteralPath $diagnosticPath + displayName: Diagnose Black Duck network reachability (Ubuntu) + condition: always() + - task: BlackDuckDetectTask@10 displayName: Black Duck scan (Ubuntu) retryCountOnTaskFailure: 3 @@ -637,6 +684,7 @@ stages: $paths = @{ HasBlackDuckLogsUbuntu = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-logs-ubuntu') HasBlackDuckInputUbuntu = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-input-ubuntu.zip') + HasBlackDuckNetworkDiagnosticsUbuntu = Test-Path -LiteralPath (Join-Path $artifactStaging 'blackduck-network-ubuntu.txt') } foreach ($entry in $paths.GetEnumerator()) { @@ -657,6 +705,11 @@ stages: displayName: Publish Black Duck input archive (Ubuntu) condition: and(always(), eq(variables.HasBlackDuckInputUbuntu, 'true')) + - publish: $(Build.ArtifactStagingDirectory)/blackduck-network-ubuntu.txt + artifact: BlackDuckNetworkDiagnosticsUbuntu + displayName: Publish Black Duck network diagnostics (Ubuntu) + condition: and(always(), eq(variables.HasBlackDuckNetworkDiagnosticsUbuntu, 'true')) + - stage: BuildWithShims displayName: Build With Shims dependsOn: HostedTests From 747203db17277c908478deaf6b5f86c520bafd96 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 22:52:18 +0100 Subject: [PATCH 45/59] Use manual Detect on Ubuntu comparison --- azure-pipelines.yml | 64 ++++++++++++++++++++++++++++----------------- 1 file changed, 40 insertions(+), 24 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 70811f5..bf648b5 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -585,6 +585,7 @@ stages: workspace: clean: all variables: + - group: BlackDuck on Polaris - name: UbuntuComparisonVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] steps: @@ -653,31 +654,46 @@ stages: displayName: Diagnose Black Duck network reachability (Ubuntu) condition: always() - - task: BlackDuckDetectTask@10 - displayName: Black Duck scan (Ubuntu) + - pwsh: | + $detectRoot = "$(Pipeline.Workspace)/detect-ubuntu" + $outputPath = "$(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu" + $detectScript = Join-Path $detectRoot 'detect10.sh' + $sourcePath = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" + $binaryScanPath = "$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip" + + New-Item -Path $detectRoot -ItemType Directory -Force | Out-Null + New-Item -Path $outputPath -ItemType Directory -Force | Out-Null + + Invoke-WebRequest -Uri 'https://detect.blackduck.com/detect10.sh' -OutFile $detectScript + & chmod +x $detectScript + + $arguments = @( + '--blackduck.url=https://aveva.app.blackduck.com' + "--blackduck.api.token=$env:BLACKDUCK_ACCESS_TOKEN" + '--detect.project.name=$(BlackDuckProjectName)' + '--detect.project.version.name=$(UbuntuComparisonVersion)' + "--detect.source.path=$sourcePath" + "--detect.binary.scan.file.path=$binaryScanPath" + '--detect.cleanup=true' + "--detect.output.path=$outputPath" + '--detect.detector.search.depth=10' + '--detect.detector.search.continue=true' + '--detect.blackduck.signature.scanner.upload.source.mode=true' + '--detect.blackduck.signature.scanner.copyright.search=true' + '--detect.blackduck.signature.scanner.license.search=true' + '--logging.level.detect=DEBUG' + '--detect.diagnostic=true' + '--detect.timeout=1800' + '--detect.blackduck.signature.scanner.arguments=--fs-wait-time=90' + '--detect.wait.for.results=true' + '--detect.wait.for.results.timeout=6000000' + ) + + & bash $detectScript @arguments + displayName: Black Duck scan (Ubuntu manual Detect) retryCountOnTaskFailure: 3 - inputs: - BlackDuckScaService: BlackDuckScanner - DetectVersion: latest - DetectFolder: $(Pipeline.Workspace)/detect-ubuntu - DetectArguments: | - --detect.project.name=$(BlackDuckProjectName) - --detect.project.version.name=$(UbuntuComparisonVersion) - --detect.source.path=$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework - --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip - --detect.cleanup=true - --detect.output.path=$(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu - --detect.detector.search.depth=10 - --detect.detector.search.continue=true - --detect.blackduck.signature.scanner.upload.source.mode=true - --detect.blackduck.signature.scanner.copyright.search=true - --detect.blackduck.signature.scanner.license.search=true - --logging.level.detect=DEBUG - --detect.diagnostic=true - --detect.timeout=1800 - --detect.blackduck.signature.scanner.arguments= --fs-wait-time=90 - --detect.wait.for.results=true - --detect.wait.for.results.timeout=6000000 + env: + BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) - pwsh: | $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY From 4d1aac4d1f20fe851deb2c2b24a4227151ab9091 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Tue, 31 Mar 2026 23:14:11 +0100 Subject: [PATCH 46/59] Bootstrap Java for Ubuntu Black Duck comparison --- azure-pipelines.yml | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bf648b5..18ba758 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -654,6 +654,45 @@ stages: displayName: Diagnose Black Duck network reachability (Ubuntu) condition: always() + - task: Cache@2 + displayName: Restore JDK cache (Ubuntu) + retryCountOnTaskFailure: 3 + inputs: + key: 'jdk | temurin17 | $(Agent.OS) | v1' + path: $(Pipeline.Workspace)/JDK-Ubuntu + cacheHitVar: JDK_UBUNTU_RESTORED + + - pwsh: | + if ($env:JDK_UBUNTU_RESTORED -eq 'true') { + Write-Host 'JDK cache restored.' + exit 0 + } + + $workdir = "$(Pipeline.Workspace)/JDK-Ubuntu" + $archivePath = Join-Path $workdir 'Temurin17.tar.gz' + $url = 'https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.14%2B7/OpenJDK17U-jdk_x64_linux_hotspot_17.0.14_7.tar.gz' + + New-Item -Path $workdir -ItemType Directory -Force | Out-Null + Invoke-WebRequest -Uri $url -OutFile $archivePath + & tar -xzf $archivePath -C $workdir + displayName: Download Temurin JDK 17 (Ubuntu) + condition: ne(variables.JDK_UBUNTU_RESTORED, 'true') + + - pwsh: | + $jdkFolder = Get-ChildItem -Path "$(Pipeline.Workspace)/JDK-Ubuntu" -Directory | + Where-Object { $_.Name -like 'jdk-17*' -or $_.Name -like 'jdk17*' } | + Select-Object -First 1 + + if (-not $jdkFolder) { + throw 'Unable to locate extracted Temurin JDK 17 folder under $(Pipeline.Workspace)/JDK-Ubuntu.' + } + + $javaHome = $jdkFolder.FullName + Write-Host "##vso[task.setvariable variable=JAVA_HOME]$javaHome" + Write-Host "##vso[task.prependpath]$javaHome/bin" + & "$javaHome/bin/java" -version + displayName: Configure JAVA_HOME (Ubuntu) + - pwsh: | $detectRoot = "$(Pipeline.Workspace)/detect-ubuntu" $outputPath = "$(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu" @@ -694,6 +733,7 @@ stages: retryCountOnTaskFailure: 3 env: BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) + JAVA_HOME: $(JAVA_HOME) - pwsh: | $artifactStaging = $env:BUILD_ARTIFACTSTAGINGDIRECTORY From b543df9adcade7acadc47c120b5eb6f07de1c5fc Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 02:52:06 +0100 Subject: [PATCH 47/59] Prepare component-style Source artifact --- azure-pipelines.yml | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 18ba758..7944824 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -801,7 +801,29 @@ stages: & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $preparedRoot = Join-Path $env:PIPELINE_WORKSPACE 'prepared-component-source' + $preparedSource = Join-Path $preparedRoot 'Source' + $preparedBuildRelease = Join-Path $preparedSource 'Build/Release' + $shimArtifacts = Join-Path $repoRoot 'shims/artifacts/Release' + + if (Test-Path -LiteralPath $preparedRoot) { + Remove-Item -LiteralPath $preparedRoot -Recurse -Force + } + + New-Item -Path $preparedSource -ItemType Directory -Force | Out-Null + Copy-Item -Path (Join-Path $repoRoot '*') -Destination $preparedSource -Recurse -Force + + if (Test-Path -LiteralPath $shimArtifacts) { + New-Item -Path $preparedBuildRelease -ItemType Directory -Force | Out-Null + Copy-Item -Path (Join-Path $shimArtifacts '*') -Destination $preparedBuildRelease -Recurse -Force + } + + Write-Host "Prepared component-template source artifact at $preparedSource" + displayName: Prepare component-style Source artifact + + - publish: $(Pipeline.Workspace)/prepared-component-source/Source artifact: Source displayName: Publish Source artifact for component template comparison @@ -823,6 +845,7 @@ stages: BuildArtifact: Build.Release BlackDuckGroup: DabaconGroup IsIPR: false + ProjectName: $(BlackDuckProjectName) UbuntuPoolName: Technology-Microsoft-Hosted-Ubuntu UbuntuPoolDemands: - ImageOverride -equals ubuntu-latest From f722be5a8838e36e3703db0210331dd99807de5e Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 03:50:12 +0100 Subject: [PATCH 48/59] Refine Black Duck source artifact flow --- azure-pipelines.yml | 195 ++++++++++++++++++++++++-------------------- 1 file changed, 106 insertions(+), 89 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 7944824..c01ecf1 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -276,9 +276,92 @@ stages: displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) + - stage: BuildWithShims + displayName: Build With Shims + dependsOn: HostedTests + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) + jobs: + - job: BuildWithShims + displayName: Build With Shims + timeoutInMinutes: "120" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + steps: + - checkout: self + fetchDepth: "0" + + - checkout: Templates + fetchDepth: "1" + + - task: UseDotNet@2 + displayName: Ensure .NET 9 SDK for shim build + retryCountOnTaskFailure: 3 + inputs: + packageType: sdk + version: 9.0.x + + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release + displayName: Build template and example projects with fake UE references + + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $preparedRoot = Join-Path $env:PIPELINE_WORKSPACE 'prepared-component-source' + $preparedSource = Join-Path $preparedRoot 'Source' + $preparedBuildRelease = Join-Path $preparedSource 'Build/Release' + $shimArtifacts = Join-Path $repoRoot 'shims/artifacts/Release' + $probeRoot = Join-Path $preparedSource 'detector-coverage-probe' + + if (Test-Path -LiteralPath $preparedRoot) { + Remove-Item -LiteralPath $preparedRoot -Recurse -Force + } + + New-Item -Path $preparedSource -ItemType Directory -Force | Out-Null + Get-ChildItem -Path $repoRoot -Force | ForEach-Object { + Copy-Item -LiteralPath $_.FullName -Destination $preparedSource -Recurse -Force + } + + if (Test-Path -LiteralPath $shimArtifacts) { + New-Item -Path $preparedBuildRelease -ItemType Directory -Force | Out-Null + Copy-Item -Path (Join-Path $shimArtifacts '*') -Destination $preparedBuildRelease -Recurse -Force + } + + New-Item -Path $probeRoot -ItemType Directory -Force | Out-Null + $packagesConfig = @( + '' + '' + ' ' + '' + ) -join "`n" + Set-Content -LiteralPath (Join-Path $probeRoot 'packages.config') -Value $packagesConfig -Encoding utf8 + + New-Item -Path (Join-Path $preparedSource '.artifactignore') -ItemType File -Force | Out-Null + + Write-Host "Prepared component-template source artifact at $preparedSource" + displayName: Prepare component-style Source artifact + + - publish: $(Pipeline.Workspace)/prepared-component-source/Source + artifact: Source + displayName: Publish Source artifact for Black Duck scans + + - publish: $(Pipeline.Workspace)/s/Templates + artifact: Templates + displayName: Publish Templates artifact for component template comparison + + - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release + artifact: Build.Release + displayName: Publish build artifact for component template comparison + - stage: BlackDuckValidation displayName: Black Duck Validation - dependsOn: HostedTests + dependsOn: BuildWithShims condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - job: BlackDuckInitialisation @@ -299,28 +382,32 @@ stages: - pwsh: | $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') - $templatesPostfix = ($env:DOBUILDANALYSIS -eq 'true' -and $templatesBranch -and $templatesBranch -ne 'main') ` + $templatesPostfix = ($templatesBranch -and $templatesBranch -ne 'main') ` ? "@$($templatesBranch)" ` : '' $branchVersion = "$env:BUILD_SOURCEBRANCHNAME$templatesPostfix" if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { - $semanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)$templatesPostfix" - $ubuntuComparisonVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + $baseSemanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" } else { $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' - $semanticVersion = "$branchName-$($env:BUILD_BUILDID)$templatesPostfix" - $ubuntuComparisonVersion = "$branchName-$($env:BUILD_BUILDID)-ubuntu$templatesPostfix" + $baseSemanticVersion = "$branchName-$($env:BUILD_BUILDID)" } + $semanticVersion = "$baseSemanticVersion-windows$templatesPostfix" + $ubuntuComparisonVersion = "$baseSemanticVersion-linux$templatesPostfix" + $componentComparisonVersion = "$baseSemanticVersion-components$templatesPostfix" + Write-Host "##vso[task.setvariable variable=BranchVersion]$branchVersion" Write-Host "##vso[task.setvariable variable=BranchVersion;isOutput=true]$branchVersion" Write-Host "##vso[task.setvariable variable=SemanticVersion]$semanticVersion" Write-Host "##vso[task.setvariable variable=SemanticVersion;isOutput=true]$semanticVersion" Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion]$ubuntuComparisonVersion" Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion;isOutput=true]$ubuntuComparisonVersion" + Write-Host "##vso[task.setvariable variable=ComponentComparisonVersion]$componentComparisonVersion" + Write-Host "##vso[task.setvariable variable=ComponentComparisonVersion;isOutput=true]$componentComparisonVersion" displayName: Calculate Black Duck versions name: CalculateVersion @@ -466,11 +553,10 @@ stages: - name: SemanticVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] steps: - - checkout: self - fetchDepth: "0" - - - checkout: Templates - fetchDepth: "1" + - download: current + artifact: Source + displayName: Download Source artifact + patterns: "**" - pwsh: | $workdir = "$(Pipeline.Workspace)\JDK" @@ -511,7 +597,7 @@ stages: - task: ArchiveFiles@2 displayName: Archive sources and build outputs inputs: - rootFolderOrFile: $(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + rootFolderOrFile: $(Pipeline.Workspace)\Source includeRootFolder: true archiveType: zip archiveFile: $(Build.ArtifactStagingDirectory)\blackduck-input.zip @@ -528,7 +614,7 @@ stages: DetectArguments: | --detect.project.name=$(BlackDuckProjectName) --detect.project.version.name=$(SemanticVersion) - --detect.source.path=$(Pipeline.Workspace)\s\UnifiedEngineeringCustomizationFramework + --detect.source.path=$(Pipeline.Workspace)\Source --detect.binary.scan.file.path=$(Build.ArtifactStagingDirectory)\blackduck-input.zip --detect.cleanup=true --detect.output.path=$(Build.ArtifactStagingDirectory)\blackduck-logs @@ -589,14 +675,13 @@ stages: - name: UbuntuComparisonVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] steps: - - checkout: self - fetchDepth: "0" - - - checkout: Templates - fetchDepth: "1" + - download: current + artifact: Source + displayName: Download Source artifact + patterns: "**" - pwsh: | - $sourceRoot = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" + $sourceRoot = "$(Pipeline.Workspace)/Source" $archivePath = "$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip" if (Test-Path -LiteralPath $archivePath) { @@ -697,7 +782,7 @@ stages: $detectRoot = "$(Pipeline.Workspace)/detect-ubuntu" $outputPath = "$(Build.ArtifactStagingDirectory)/blackduck-logs-ubuntu" $detectScript = Join-Path $detectRoot 'detect10.sh' - $sourcePath = "$(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework" + $sourcePath = "$(Pipeline.Workspace)/Source" $binaryScanPath = "$(Build.ArtifactStagingDirectory)/blackduck-input-ubuntu.zip" New-Item -Path $detectRoot -ItemType Directory -Force | Out-Null @@ -766,75 +851,6 @@ stages: displayName: Publish Black Duck network diagnostics (Ubuntu) condition: and(always(), eq(variables.HasBlackDuckNetworkDiagnosticsUbuntu, 'true')) - - stage: BuildWithShims - displayName: Build With Shims - dependsOn: HostedTests - condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}, ${{ eq(parameters.EnableComponentTemplateBlackDuckComparison, 'true') }}) - jobs: - - job: BuildWithShims - displayName: Build With Shims - timeoutInMinutes: "120" - pool: - name: Technology-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/dotnet/sdk:9.0 - workspace: - clean: all - steps: - - checkout: self - fetchDepth: "0" - - - checkout: Templates - fetchDepth: "1" - - - task: UseDotNet@2 - displayName: Ensure .NET 9 SDK for shim build - retryCountOnTaskFailure: 3 - inputs: - packageType: sdk - version: 9.0.x - - - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' - & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release - displayName: Build template and example projects with fake UE references - - - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' - $preparedRoot = Join-Path $env:PIPELINE_WORKSPACE 'prepared-component-source' - $preparedSource = Join-Path $preparedRoot 'Source' - $preparedBuildRelease = Join-Path $preparedSource 'Build/Release' - $shimArtifacts = Join-Path $repoRoot 'shims/artifacts/Release' - - if (Test-Path -LiteralPath $preparedRoot) { - Remove-Item -LiteralPath $preparedRoot -Recurse -Force - } - - New-Item -Path $preparedSource -ItemType Directory -Force | Out-Null - Copy-Item -Path (Join-Path $repoRoot '*') -Destination $preparedSource -Recurse -Force - - if (Test-Path -LiteralPath $shimArtifacts) { - New-Item -Path $preparedBuildRelease -ItemType Directory -Force | Out-Null - Copy-Item -Path (Join-Path $shimArtifacts '*') -Destination $preparedBuildRelease -Recurse -Force - } - - Write-Host "Prepared component-template source artifact at $preparedSource" - displayName: Prepare component-style Source artifact - - - publish: $(Pipeline.Workspace)/prepared-component-source/Source - artifact: Source - displayName: Publish Source artifact for component template comparison - - - publish: $(Pipeline.Workspace)/s/Templates - artifact: Templates - displayName: Publish Templates artifact for component template comparison - - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release - artifact: Build.Release - displayName: Publish build artifact for component template comparison - - stage: ComponentTemplateBlackDuckComparison displayName: Component Template Black Duck Comparison dependsOn: BuildWithShims @@ -846,6 +862,7 @@ stages: BlackDuckGroup: DabaconGroup IsIPR: false ProjectName: $(BlackDuckProjectName) + PackageVersion: $(Build.SourceBranchName)-$(Build.BuildId)-components UbuntuPoolName: Technology-Microsoft-Hosted-Ubuntu UbuntuPoolDemands: - ImageOverride -equals ubuntu-latest From ce068cd683155e425aa63e71c032506b37c493b6 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 04:07:26 +0100 Subject: [PATCH 49/59] Publish Source artifact earlier --- azure-pipelines.yml | 152 +++++++++++++++++++++++++------------------- 1 file changed, 85 insertions(+), 67 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c01ecf1..d5afb30 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -54,8 +54,55 @@ pr: - main stages: + - stage: InitialiseSource + displayName: Initialise Source + jobs: + - job: Source + displayName: Publish Source + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + steps: + - checkout: self + fetchDepth: "0" + path: Source + + - checkout: Templates + fetchDepth: "1" + path: Templates + + - pwsh: | + $sourceRoot = "$(Pipeline.Workspace)/Source" + $probeRoot = Join-Path $sourceRoot 'detector-coverage-probe' + + New-Item -Path $probeRoot -ItemType Directory -Force | Out-Null + $packagesConfig = @( + '' + '' + ' ' + '' + ) -join "`n" + Set-Content -LiteralPath (Join-Path $probeRoot 'packages.config') -Value $packagesConfig -Encoding utf8 + + New-Item -Path (Join-Path $sourceRoot '.artifactignore') -ItemType File -Force | Out-Null + displayName: Prepare Source artifact + + - publish: $(Pipeline.Workspace)/Source + artifact: Source + displayName: Publish Source + + - publish: $(Pipeline.Workspace)/Templates + artifact: Templates + displayName: Publish Templates + - stage: HostedTests displayName: Hosted Tests + dependsOn: InitialiseSource jobs: - job: Pester displayName: Pester @@ -68,8 +115,10 @@ stages: workspace: clean: all steps: - - checkout: self - fetchDepth: "0" + - download: current + artifact: Source + displayName: Download Source artifact + patterns: "**" - pwsh: | Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" @@ -100,10 +149,13 @@ stages: - pwsh: | ./scripts/Tests/Run-Tests.ps1 -Quick displayName: Run tests + workingDirectory: $(Pipeline.Workspace)/Source - stage: CoverityValidation displayName: Coverity Validation - dependsOn: HostedTests + dependsOn: + - InitialiseSource + - HostedTests condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - job: CoverityScan @@ -118,11 +170,15 @@ stages: workspace: clean: all steps: - - checkout: self - fetchDepth: "0" + - download: current + artifact: Source + displayName: Download Source artifact + patterns: "**" - - checkout: Templates - fetchDepth: "1" + - download: current + artifact: Templates + displayName: Download Templates artifact + patterns: "**" - pwsh: | dotnet --list-sdks @@ -140,12 +196,12 @@ stages: displayName: Show .NET SDK info - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' & (Join-Path $repoRoot 'scripts\Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' $paths = @{ HasShimSources = Test-Path -LiteralPath (Join-Path $repoRoot 'shims') HasShimManifest = Test-Path -LiteralPath (Join-Path $repoRoot 'shims/shim-projects.psd1') @@ -161,29 +217,29 @@ stages: displayName: Detect shim artifacts condition: always() - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims + - publish: $(Pipeline.Workspace)/Source/shims artifact: ShimSources displayName: Publish shim sources condition: and(always(), eq(variables.HasShimSources, 'true')) - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/shim-projects.psd1 + - publish: $(Pipeline.Workspace)/Source/shims/shim-projects.psd1 artifact: ShimManifest displayName: Publish shim manifest condition: and(always(), eq(variables.HasShimManifest, 'true')) - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.tmp/generated-shims + - publish: $(Pipeline.Workspace)/Source/.tmp/generated-shims artifact: GeneratedShimProjects displayName: Publish generated shim project files condition: and(always(), eq(variables.HasGeneratedShimProjects, 'true')) - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release + - publish: $(Pipeline.Workspace)/Source/shims/artifacts/Release artifact: ShimAssemblies displayName: Publish compiled shim assemblies condition: and(always(), eq(variables.HasShimAssemblies, 'true')) - pwsh: | $pwshPath = if ($IsLinux) { '/usr/bin/pwsh' } else { (Get-Command pwsh -ErrorAction Stop).Source } - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' $fakeCompileScriptPath = Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1' $branchName = "$env:BUILD_SOURCEBRANCH" -replace '^refs/heads/', '' @@ -258,7 +314,7 @@ stages: POLARIS_HOME: $(Pipeline.Workspace)/Coverity-CLI - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' $paths = @{ HasCoverityArtifacts = Test-Path -LiteralPath (Join-Path $repoRoot '.blackduck') } @@ -271,14 +327,16 @@ stages: displayName: Detect security artifacts condition: always() - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/.blackduck + - publish: $(Pipeline.Workspace)/Source/.blackduck artifact: Coverity displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) - stage: BuildWithShims displayName: Build With Shims - dependsOn: HostedTests + dependsOn: + - InitialiseSource + - HostedTests condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - job: BuildWithShims @@ -293,11 +351,15 @@ stages: workspace: clean: all steps: - - checkout: self - fetchDepth: "0" + - download: current + artifact: Source + displayName: Download Source artifact + patterns: "**" - - checkout: Templates - fetchDepth: "1" + - download: current + artifact: Templates + displayName: Download Templates artifact + patterns: "**" - task: UseDotNet@2 displayName: Ensure .NET 9 SDK for shim build @@ -307,55 +369,11 @@ stages: version: 9.0.x - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release displayName: Build template and example projects with fake UE references - - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 's/UnifiedEngineeringCustomizationFramework' - $preparedRoot = Join-Path $env:PIPELINE_WORKSPACE 'prepared-component-source' - $preparedSource = Join-Path $preparedRoot 'Source' - $preparedBuildRelease = Join-Path $preparedSource 'Build/Release' - $shimArtifacts = Join-Path $repoRoot 'shims/artifacts/Release' - $probeRoot = Join-Path $preparedSource 'detector-coverage-probe' - - if (Test-Path -LiteralPath $preparedRoot) { - Remove-Item -LiteralPath $preparedRoot -Recurse -Force - } - - New-Item -Path $preparedSource -ItemType Directory -Force | Out-Null - Get-ChildItem -Path $repoRoot -Force | ForEach-Object { - Copy-Item -LiteralPath $_.FullName -Destination $preparedSource -Recurse -Force - } - - if (Test-Path -LiteralPath $shimArtifacts) { - New-Item -Path $preparedBuildRelease -ItemType Directory -Force | Out-Null - Copy-Item -Path (Join-Path $shimArtifacts '*') -Destination $preparedBuildRelease -Recurse -Force - } - - New-Item -Path $probeRoot -ItemType Directory -Force | Out-Null - $packagesConfig = @( - '' - '' - ' ' - '' - ) -join "`n" - Set-Content -LiteralPath (Join-Path $probeRoot 'packages.config') -Value $packagesConfig -Encoding utf8 - - New-Item -Path (Join-Path $preparedSource '.artifactignore') -ItemType File -Force | Out-Null - - Write-Host "Prepared component-template source artifact at $preparedSource" - displayName: Prepare component-style Source artifact - - - publish: $(Pipeline.Workspace)/prepared-component-source/Source - artifact: Source - displayName: Publish Source artifact for Black Duck scans - - - publish: $(Pipeline.Workspace)/s/Templates - artifact: Templates - displayName: Publish Templates artifact for component template comparison - - - publish: $(Pipeline.Workspace)/s/UnifiedEngineeringCustomizationFramework/shims/artifacts/Release + - publish: $(Pipeline.Workspace)/Source/shims/artifacts/Release artifact: Build.Release displayName: Publish build artifact for component template comparison From 92212fa13bb5665964a0568cb997cb30a5a282d5 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 04:22:37 +0100 Subject: [PATCH 50/59] Use shared retrieve-source flow --- azure-pipelines.yml | 142 +++++++++++++++++++++++--------------------- 1 file changed, 74 insertions(+), 68 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index d5afb30..b77ad60 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -100,6 +100,51 @@ stages: artifact: Templates displayName: Publish Templates + - stage: BuildWithShims + displayName: Build With Shims + dependsOn: InitialiseSource + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) + jobs: + - job: BuildWithShims + displayName: Build With Shims + timeoutInMinutes: "120" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + steps: + - template: ../_templates/steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + FetchDepth: 0 + + - download: current + artifact: Templates + displayName: Download Templates artifact + patterns: "**" + + - task: UseDotNet@2 + displayName: Ensure .NET 9 SDK for shim build + retryCountOnTaskFailure: 3 + inputs: + packageType: sdk + version: 9.0.x + + - pwsh: | + $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' + & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release + displayName: Build template and example projects with fake UE references + + - publish: $(Pipeline.Workspace)/Source/shims/artifacts/Release + artifact: Build.Release + displayName: Publish build artifact for component template comparison + - stage: HostedTests displayName: Hosted Tests dependsOn: InitialiseSource @@ -115,10 +160,12 @@ stages: workspace: clean: all steps: - - download: current - artifact: Source - displayName: Download Source artifact - patterns: "**" + - template: ../_templates/steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + FetchDepth: 0 - pwsh: | Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" @@ -151,11 +198,9 @@ stages: displayName: Run tests workingDirectory: $(Pipeline.Workspace)/Source - - stage: CoverityValidation - displayName: Coverity Validation - dependsOn: - - InitialiseSource - - HostedTests + - stage: Coverity + displayName: Coverity + dependsOn: InitialiseSource condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: - job: CoverityScan @@ -170,10 +215,12 @@ stages: workspace: clean: all steps: - - download: current - artifact: Source - displayName: Download Source artifact - patterns: "**" + - template: ../_templates/steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + FetchDepth: 0 - download: current artifact: Templates @@ -332,53 +379,8 @@ stages: displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) - - stage: BuildWithShims - displayName: Build With Shims - dependsOn: - - InitialiseSource - - HostedTests - condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) - jobs: - - job: BuildWithShims - displayName: Build With Shims - timeoutInMinutes: "120" - pool: - name: Technology-Microsoft-Hosted-Ubuntu - demands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/dotnet/sdk:9.0 - workspace: - clean: all - steps: - - download: current - artifact: Source - displayName: Download Source artifact - patterns: "**" - - - download: current - artifact: Templates - displayName: Download Templates artifact - patterns: "**" - - - task: UseDotNet@2 - displayName: Ensure .NET 9 SDK for shim build - retryCountOnTaskFailure: 3 - inputs: - packageType: sdk - version: 9.0.x - - - pwsh: | - $repoRoot = Join-Path $env:PIPELINE_WORKSPACE 'Source' - & (Join-Path $repoRoot 'scripts/Invoke-FakeUECompile.ps1') -Directory templates,Examples -Configuration Release - displayName: Build template and example projects with fake UE references - - - publish: $(Pipeline.Workspace)/Source/shims/artifacts/Release - artifact: Build.Release - displayName: Publish build artifact for component template comparison - - - stage: BlackDuckValidation - displayName: Black Duck Validation + - stage: BlackDuck + displayName: Black Duck dependsOn: BuildWithShims condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: @@ -571,10 +573,12 @@ stages: - name: SemanticVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] steps: - - download: current - artifact: Source - displayName: Download Source artifact - patterns: "**" + - template: ../_templates/steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + FetchDepth: 0 - pwsh: | $workdir = "$(Pipeline.Workspace)\JDK" @@ -693,10 +697,12 @@ stages: - name: UbuntuComparisonVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] steps: - - download: current - artifact: Source - displayName: Download Source artifact - patterns: "**" + - template: ../_templates/steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + FetchDepth: 0 - pwsh: | $sourceRoot = "$(Pipeline.Workspace)/Source" From b511fe739db23a5ccf5c23b17b75dd62bffc3b82 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 04:22:58 +0100 Subject: [PATCH 51/59] Fix retrieve-source template paths --- azure-pipelines.yml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b77ad60..821a268 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -117,7 +117,7 @@ stages: workspace: clean: all steps: - - template: ../_templates/steps/retrieve-source.yml@Templates + - template: steps/retrieve-source.yml@Templates parameters: IsIPR: false ArtifactName: Source @@ -160,7 +160,7 @@ stages: workspace: clean: all steps: - - template: ../_templates/steps/retrieve-source.yml@Templates + - template: steps/retrieve-source.yml@Templates parameters: IsIPR: false ArtifactName: Source @@ -215,7 +215,7 @@ stages: workspace: clean: all steps: - - template: ../_templates/steps/retrieve-source.yml@Templates + - template: steps/retrieve-source.yml@Templates parameters: IsIPR: false ArtifactName: Source @@ -573,7 +573,7 @@ stages: - name: SemanticVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.SemanticVersion'] ] steps: - - template: ../_templates/steps/retrieve-source.yml@Templates + - template: steps/retrieve-source.yml@Templates parameters: IsIPR: false ArtifactName: Source @@ -697,7 +697,7 @@ stages: - name: UbuntuComparisonVersion value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] steps: - - template: ../_templates/steps/retrieve-source.yml@Templates + - template: steps/retrieve-source.yml@Templates parameters: IsIPR: false ArtifactName: Source From eebed82688214aa24cc7cd04c25b4a4c9717b0fc Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 04:36:23 +0100 Subject: [PATCH 52/59] Split Black Duck Windows and Ubuntu stages --- azure-pipelines.yml | 164 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 161 insertions(+), 3 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 821a268..2311050 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -156,7 +156,7 @@ stages: demands: - ImageOverride -equals ubuntu-latest - WorkFolder -equals /mnt/storage/sdc/storage - container: mcr.microsoft.com/powershell:7.4-ubuntu-22.04 + container: mcr.microsoft.com/dotnet/sdk:9.0 workspace: clean: all steps: @@ -379,8 +379,8 @@ stages: displayName: Publish Coverity artifacts condition: and(always(), eq(variables.HasCoverityArtifacts, 'true')) - - stage: BlackDuck - displayName: Black Duck + - stage: BlackDuckWindows + displayName: Black Duck - Windows dependsOn: BuildWithShims condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) jobs: @@ -677,6 +677,164 @@ stages: displayName: Publish Black Duck input archive condition: and(always(), eq(variables.HasBlackDuckInput, 'true')) + - stage: BlackDuckUbuntu + displayName: Black Duck - Ubuntu + dependsOn: BuildWithShims + condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}) + jobs: + - job: BlackDuckInitialisation + displayName: BlackDuck Initialise + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + steps: + - checkout: none + + - pwsh: | + $templatesBranch = "$env:TEMPLATES_REF".replace('refs/heads/', '') + $templatesPostfix = ($templatesBranch -and $templatesBranch -ne 'main') ` + ? "@$($templatesBranch)" ` + : '' + + $branchVersion = "$env:BUILD_SOURCEBRANCHNAME$templatesPostfix" + + if ($env:BUILD_REASON -eq 'PullRequest' -and $env:SYSTEM_PULLREQUEST_PULLREQUESTID) { + $baseSemanticVersion = "pr-$($env:SYSTEM_PULLREQUEST_PULLREQUESTID)-$($env:BUILD_BUILDID)" + } + else { + $branchName = $env:BUILD_SOURCEBRANCHNAME -replace '[^A-Za-z0-9._-]', '-' + $baseSemanticVersion = "$branchName-$($env:BUILD_BUILDID)" + } + + $ubuntuComparisonVersion = "$baseSemanticVersion-linux$templatesPostfix" + + Write-Host "##vso[task.setvariable variable=BranchVersion]$branchVersion" + Write-Host "##vso[task.setvariable variable=BranchVersion;isOutput=true]$branchVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion]$ubuntuComparisonVersion" + Write-Host "##vso[task.setvariable variable=UbuntuComparisonVersion;isOutput=true]$ubuntuComparisonVersion" + displayName: Calculate Black Duck versions + name: CalculateVersion + + - pwsh: | + $headers = @{ + Authorization = "token $env:BLACKDUCK_ACCESS_TOKEN" + Accept = 'application/vnd.blackducksoftware.user-4+json' + } + + $uri = "$env:BLACKDUCK_URL/api/tokens/authenticate" + $response = Invoke-RestMethod -Method POST -Uri $uri -Headers $headers + + Write-Host "##vso[task.setvariable variable=BaseUrl]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BaseUrl;isOutput=true]$env:BLACKDUCK_URL" + Write-Host "##vso[task.setvariable variable=BearerToken]$($response.bearerToken)" + Write-Host "##vso[task.setvariable variable=BearerToken;isOutput=true]$($response.bearerToken)" + displayName: Authenticate to Black Duck + name: Authenticate + retryCountOnTaskFailure: 3 + env: + BLACKDUCK_URL: https://aveva.app.blackduck.com + BLACKDUCK_ACCESS_TOKEN: $(BLACKDUCK_ACCESS_TOKEN) + + - pwsh: | + $headers = @{ + Authorization = "Bearer $env:BearerToken" + Accept = 'application/vnd.blackducksoftware.project-detail-7+json' + } + + $projectName = '$(BlackDuckProjectName)' + $limit = 100 + $offset = 0 + $allProjects = @() + + do { + $uri = "$env:BaseUrl/api/projects?limit=$limit&offset=$offset&q=name:$projectName" + $response = Invoke-RestMethod -Method GET -Uri $uri -Headers $headers + if ($response.items) { + $allProjects += $response.items + } + $offset += $limit + } while ($offset -lt $response.totalCount) + + $matchedProject = $allProjects | Where-Object { $_.name -eq $projectName } + + if (-not $matchedProject) { + Write-Host "Project '$projectName' not found in Black Duck." + Write-Host "##vso[task.setvariable variable=ProjectExists]false" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]false" + Write-Host "##vso[task.logissue type=error]Black Duck project '$projectName' must exist before the scan runs. Create it or grant the scanner access to the existing project." + exit 1 + } + + $projectId = $matchedProject._meta.href.Split('/')[-1] + Write-Host "##vso[task.setvariable variable=ProjectId]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectId;isOutput=true]$projectId" + Write-Host "##vso[task.setvariable variable=ProjectExists]true" + Write-Host "##vso[task.setvariable variable=ProjectExists;isOutput=true]true" + displayName: Get Black Duck project ID + name: ProjectId + retryCountOnTaskFailure: 3 + env: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + + - job: BlackDuckClone + displayName: BlackDuck Clone + dependsOn: BlackDuckInitialisation + condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true')) + timeoutInMinutes: "10" + pool: + name: Technology-Microsoft-Hosted-Ubuntu + demands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + container: mcr.microsoft.com/dotnet/sdk:9.0 + workspace: + clean: all + variables: + - group: BlackDuck on Polaris + - name: BearerToken + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BearerToken'] ] + - name: BaseUrl + value: $[ dependencies.BlackDuckInitialisation.outputs['Authenticate.BaseUrl'] ] + - name: ProjectId + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectId'] ] + - name: ProjectExists + value: $[ dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'] ] + - name: BranchVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.BranchVersion'] ] + - name: UbuntuComparisonVersion + value: $[ dependencies.BlackDuckInitialisation.outputs['CalculateVersion.UbuntuComparisonVersion'] ] + steps: + - checkout: none + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: main + TargetVersion: $(BranchVersion) + Phase: DEVELOPMENT + RemoveTargetVersion: false + + - template: steps/blackduck-clone-version.yml@Templates + parameters: + BearerToken: $(BearerToken) + BaseUrl: $(BaseUrl) + ProjectId: $(ProjectId) + SourceVersion: $(BranchVersion) + TargetVersion: $(UbuntuComparisonVersion) + Phase: PLANNING + RemoveTargetVersion: true + - job: BlackDuckScanUbuntu displayName: Black Duck Scan Ubuntu dependsOn: From 87e97ef2c724ac812a5580ca2abc1da8b8b15a01 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Wed, 1 Apr 2026 05:11:53 +0100 Subject: [PATCH 53/59] Add richer Black Duck detector probes --- azure-pipelines.yml | 58 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 2311050..c30c434 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -79,15 +79,69 @@ stages: - pwsh: | $sourceRoot = "$(Pipeline.Workspace)/Source" $probeRoot = Join-Path $sourceRoot 'detector-coverage-probe' + $classicProbeRoot = Join-Path $probeRoot 'ClassicProbe' + $sdkProbeRoot = Join-Path $probeRoot 'SdkProbe' New-Item -Path $probeRoot -ItemType Directory -Force | Out-Null - $packagesConfig = @( + New-Item -Path $classicProbeRoot -ItemType Directory -Force | Out-Null + New-Item -Path $sdkProbeRoot -ItemType Directory -Force | Out-Null + + $classicPackagesConfig = @( '' '' ' ' '' ) -join "`n" - Set-Content -LiteralPath (Join-Path $probeRoot 'packages.config') -Value $packagesConfig -Encoding utf8 + + $classicProject = @( + '' + ' ' + ' Debug' + ' AnyCPU' + ' Library' + ' v4.8.1' + ' ClassicProbe' + ' ClassicProbe' + ' ' + ' ' + ' ' + ' ' + ' ' + ' ' + '' + ) -join "`n" + + $classicSource = @( + 'namespace ClassicProbe' + '{' + ' public class Class1 { }' + '}' + ) -join "`n" + + $sdkProject = @( + '' + ' ' + ' net8.0' + ' disable' + ' disable' + ' ' + ' ' + ' ' + ' ' + '' + ) -join "`n" + + $sdkSource = @( + 'namespace SdkProbe;' + 'public class Class1 { }' + ) -join "`n" + + Set-Content -LiteralPath (Join-Path $classicProbeRoot 'packages.config') -Value $classicPackagesConfig -Encoding utf8 + Set-Content -LiteralPath (Join-Path $classicProbeRoot 'ClassicProbe.csproj') -Value $classicProject -Encoding utf8 + Set-Content -LiteralPath (Join-Path $classicProbeRoot 'Class1.cs') -Value $classicSource -Encoding utf8 + + Set-Content -LiteralPath (Join-Path $sdkProbeRoot 'SdkProbe.csproj') -Value $sdkProject -Encoding utf8 + Set-Content -LiteralPath (Join-Path $sdkProbeRoot 'Class1.cs') -Value $sdkSource -Encoding utf8 New-Item -Path (Join-Path $sourceRoot '.artifactignore') -ItemType File -Force | Out-Null displayName: Prepare Source artifact From fd79389c02172ae212c38ed529cecead0855e567 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Thu, 2 Apr 2026 17:26:53 +0100 Subject: [PATCH 54/59] Refactor Azure pipeline to use main Templates repository and adjust Black Duck parameters --- azure-pipelines.yml | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c30c434..07d5502 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,10 +17,6 @@ resources: type: git name: Technology/Templates ref: refs/heads/main - - repository: TemplatesCustom - type: git - name: Dabacon Products/Templates - ref: refs/heads/feature/uecf-blackduck-pool-params parameters: - name: EnableSecurityValidation @@ -607,7 +603,7 @@ stages: SourceVersion: $(BranchVersion) TargetVersion: $(UbuntuComparisonVersion) Phase: PLANNING - RemoveTargetVersion: true + RemoveTargetVersion: false - job: BlackDuckScan displayName: Black Duck Scan @@ -1092,7 +1088,7 @@ stages: dependsOn: BuildWithShims condition: and(succeeded(), ${{ eq(parameters.EnableSecurityValidation, 'true') }}, ${{ eq(parameters.EnableComponentTemplateBlackDuckComparison, 'true') }}) jobs: - - template: component-blackduck.yml@TemplatesCustom + - template: component-blackduck.yml@Templates parameters: BuildArtifact: Build.Release BlackDuckGroup: DabaconGroup From a6f5f226934187921c9e10bbd824ce7c4380897b Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Thu, 2 Apr 2026 17:36:12 +0100 Subject: [PATCH 55/59] Fix component Black Duck template parameters --- azure-pipelines.yml | 7 ------- 1 file changed, 7 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 07d5502..b3fb966 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1095,10 +1095,3 @@ stages: IsIPR: false ProjectName: $(BlackDuckProjectName) PackageVersion: $(Build.SourceBranchName)-$(Build.BuildId)-components - UbuntuPoolName: Technology-Microsoft-Hosted-Ubuntu - UbuntuPoolDemands: - - ImageOverride -equals ubuntu-latest - - WorkFolder -equals /mnt/storage/sdc/storage - WindowsPoolName: Technology-Microsoft-Hosted-Windows - WindowsPoolDemands: - - ImageOverride -equals windows-latest From 6d83310519761efcdaeb6006025d58645f2e060c Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Thu, 2 Apr 2026 17:53:35 +0100 Subject: [PATCH 56/59] Use Dabacon Templates directly --- azure-pipelines.yml | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index b3fb966..29a586f 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,7 @@ # The hosted test stage can run immediately on the Technology stateful hosted pools. # The security stage remains opt-in, but now uses the hosted-compatible fake-compile flow # on the Technology stateful hosted Ubuntu pool instead of generic vmImage selection. -# Templates is consumed from a Technology fork to avoid cross-project checkout permissions. +# Templates is consumed directly from Dabacon Products to avoid fork drift. # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available # - service connections named `Coverity on Polaris` and `BlackDuckScanner` @@ -15,7 +15,7 @@ resources: repositories: - repository: Templates type: git - name: Technology/Templates + name: Dabacon Products/Templates ref: refs/heads/main parameters: @@ -1095,3 +1095,10 @@ stages: IsIPR: false ProjectName: $(BlackDuckProjectName) PackageVersion: $(Build.SourceBranchName)-$(Build.BuildId)-components + UbuntuPoolName: Technology-Microsoft-Hosted-Ubuntu + UbuntuPoolDemands: + - ImageOverride -equals ubuntu-latest + - WorkFolder -equals /mnt/storage/sdc/storage + WindowsPoolName: Technology-Microsoft-Hosted-Windows + WindowsPoolDemands: + - ImageOverride -equals windows-latest From b9125d63e18106d17da1da1cf5eae78ee5b9cbc0 Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Thu, 2 Apr 2026 19:09:27 +0100 Subject: [PATCH 57/59] Repoint UE pipeline to Technology Templates --- azure-pipelines.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 29a586f..c455403 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -6,7 +6,7 @@ # The hosted test stage can run immediately on the Technology stateful hosted pools. # The security stage remains opt-in, but now uses the hosted-compatible fake-compile flow # on the Technology stateful hosted Ubuntu pool instead of generic vmImage selection. -# Templates is consumed directly from Dabacon Products to avoid fork drift. +# Templates is consumed from Technology/Templates. # The Technology project still needs: # - a self-hosted Windows pool with UE installed and MSBuild available # - service connections named `Coverity on Polaris` and `BlackDuckScanner` @@ -15,7 +15,7 @@ resources: repositories: - repository: Templates type: git - name: Dabacon Products/Templates + name: Technology/Templates ref: refs/heads/main parameters: From dbb516a87f58808648b1928dab0fa5f62372e4fe Mon Sep 17 00:00:00 2001 From: Jakub Pawlinski Date: Thu, 2 Apr 2026 20:00:34 +0100 Subject: [PATCH 58/59] Remove unshallow from source artifact retrieval --- azure-pipelines.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index c455403..64baf44 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -172,7 +172,6 @@ stages: IsIPR: false ArtifactName: Source DownloadPath: $(Pipeline.Workspace)/Source - FetchDepth: 0 - download: current artifact: Templates @@ -215,7 +214,6 @@ stages: IsIPR: false ArtifactName: Source DownloadPath: $(Pipeline.Workspace)/Source - FetchDepth: 0 - pwsh: | Write-Host "PowerShell version: $($PSVersionTable.PSVersion)" @@ -270,7 +268,6 @@ stages: IsIPR: false ArtifactName: Source DownloadPath: $(Pipeline.Workspace)/Source - FetchDepth: 0 - download: current artifact: Templates @@ -628,7 +625,6 @@ stages: IsIPR: false ArtifactName: Source DownloadPath: $(Pipeline.Workspace)/Source - FetchDepth: 0 - pwsh: | $workdir = "$(Pipeline.Workspace)\JDK" @@ -910,7 +906,6 @@ stages: IsIPR: false ArtifactName: Source DownloadPath: $(Pipeline.Workspace)/Source - FetchDepth: 0 - pwsh: | $sourceRoot = "$(Pipeline.Workspace)/Source" From fd92296cecf5eadf19849589857455f7fffbf511 Mon Sep 17 00:00:00 2001 From: quasarea <10334982+quasarea@users.noreply.github.com> Date: Wed, 8 Apr 2026 03:44:20 +0100 Subject: [PATCH 59/59] Add dedicated anti-malware pipeline job --- azure-pipelines.yml | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 64baf44..48bda45 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -17,6 +17,10 @@ resources: type: git name: Technology/Templates ref: refs/heads/main + - repository: antiMalwareTemplate + type: git + name: Architecture/Architecture + ref: refs/heads/antiMalwareVersions/latest parameters: - name: EnableSecurityValidation @@ -602,11 +606,44 @@ stages: Phase: PLANNING RemoveTargetVersion: false + - job: AntiMalwareScan + displayName: AntiMalware Scan + dependsOn: + - BlackDuckInitialisation + - BlackDuckClone + condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true')) + timeoutInMinutes: "60" + pool: + name: Technology-Microsoft-Hosted-Windows + demands: + - ImageOverride -equals windows-latest + workspace: + clean: all + steps: + - template: steps/retrieve-source.yml@Templates + parameters: + IsIPR: false + ArtifactName: Source + DownloadPath: $(Pipeline.Workspace)/Source + + - pwsh: | + Start-Process powershell.exe -ArgumentList "Update-MpSignature -UpdateSource MicrosoftUpdateServer" -Wait + Write-Output "updated the definitions successfully" + displayName: Antimalware Scan task + + - template: Pipelines/Templates/AntiMalware/AntiMalware.step.yml@antiMalwareTemplate + parameters: + scanType: Directory + scanDirectory: $(Pipeline.Workspace)\Source + artifactName: AntimalwareResults_Source + os: Windows_NT + - job: BlackDuckScan displayName: Black Duck Scan dependsOn: - BlackDuckInitialisation - BlackDuckClone + - AntiMalwareScan condition: and(succeeded(), eq(dependencies.BlackDuckInitialisation.outputs['ProjectId.ProjectExists'], 'true')) timeoutInMinutes: "120" pool: