diff --git a/.vscode/cspell-devops-ext.txt b/.vscode/cspell-devops-ext.txt index 8de1a582407..bac751ea95f 100644 --- a/.vscode/cspell-devops-ext.txt +++ b/.vscode/cspell-devops-ext.txt @@ -3,5 +3,6 @@ codepaths LASTEXITCODE tmrm toolrunner +TOOLSDIRECTORY tsbuildinfo -vsix +vsix \ No newline at end of file diff --git a/eng/pipelines/release-azuredevops.yml b/eng/pipelines/release-azuredevops.yml new file mode 100644 index 00000000000..4c623e54ea2 --- /dev/null +++ b/eng/pipelines/release-azuredevops.yml @@ -0,0 +1,25 @@ +# Continuous deployment trigger +trigger: + branches: + include: + - main + paths: + include: + - ext/azuredevops + - eng/pipelines/release-azuredevops.yml + +pr: + paths: + include: + - ext/azuredevops + - eng/pipelines/release-azuredevops.yml + +extends: + template: /eng/pipelines/templates/stages/1es-redirect.yml + parameters: + stages: + - template: /eng/pipelines/templates/stages/azuredevops-build-and-test.yml + + - template: /eng/pipelines/templates/stages/azuredevops-sign.yml + + - template: /eng/pipelines/templates/stages/azuredevops-publish-manual.yml diff --git a/eng/pipelines/templates/jobs/azuredevops-build.yml b/eng/pipelines/templates/jobs/azuredevops-build.yml new file mode 100644 index 00000000000..3511ee3513d --- /dev/null +++ b/eng/pipelines/templates/jobs/azuredevops-build.yml @@ -0,0 +1,72 @@ +parameters: + - name: NameSuffix + type: string + - name: Pool + type: string + - name: ImageKey + type: string + default: image + - name: OSVmImage + type: string + - name: OS + type: string + - name: Variables + type: object + +jobs: + - job: Build_${{ parameters.NameSuffix }} + displayName: Build ${{ parameters.NameSuffix }} + + pool: + name: ${{ parameters.Pool }} + ${{ parameters.ImageKey }}: ${{ parameters.OSVmImage }} + os: ${{ parameters.OS }} + + variables: + # TODO: What version of Node to use for Azure DevOps extension build? + NodeVersion: 20.x + ${{ insert }}: ${{ parameters.Variables }} + + steps: + - checkout: self + + - task: NodeTool@0 + inputs: + versionSpec: $(NodeVersion) + + - pwsh: | + npm i -g npm tfx-cli + npm ci + displayName: Install build tools and dependencies + workingDirectory: ext/azuredevops/setupAzd + + # TODO: If private build, update vss-extension.json and other JSONs to + # set a dev version + + - task: PowerShell@2 + inputs: + targetType: 'filePath' + filePath: '$(Build.SourcesDirectory)/ext/azuredevops/ci-test.ps1' + workingDirectory: '$(Build.SourcesDirectory)/ext/azuredevops' + displayName: Test + + - task: PowerShell@2 + inputs: + targetType: 'filePath' + filePath: '$(Build.SourcesDirectory)/ext/azuredevops/ci-package.ps1' + workingDirectory: '$(Build.SourcesDirectory)/ext/azuredevops' + displayName: Package Extension + + - pwsh: | + Copy-Item ` + -Path "$(Build.SourcesDirectory)/ext/azuredevops/*.vsix" ` + -Destination "$(Build.ArtifactStagingDirectory)/" + condition: and(succeeded(), eq('true', variables['UploadArtifact'])) + displayName: Copy VSIX to staging directory + + templateContext: + outputs: + - output: pipelineArtifact + path: $(Build.ArtifactStagingDirectory) + condition: and(succeeded(), eq('true', variables['UploadArtifact'])) + artifact: vsix diff --git a/eng/pipelines/templates/stages/1es-redirect.yml b/eng/pipelines/templates/stages/1es-redirect.yml index afdebbf7890..472550140c1 100644 --- a/eng/pipelines/templates/stages/1es-redirect.yml +++ b/eng/pipelines/templates/stages/1es-redirect.yml @@ -7,7 +7,7 @@ resources: - repository: azure-sdk-build-tools type: git name: internal/azure-sdk-build-tools - ref: refs/tags/azure-sdk-build-tools_20251112.1 + ref: refs/tags/azure-sdk-build-tools_20260112.5 parameters: - name: stages diff --git a/eng/pipelines/templates/stages/azuredevops-build-and-test.yml b/eng/pipelines/templates/stages/azuredevops-build-and-test.yml new file mode 100644 index 00000000000..5dba44f1001 --- /dev/null +++ b/eng/pipelines/templates/stages/azuredevops-build-and-test.yml @@ -0,0 +1,45 @@ +parameters: + - name: BuildMatrix + type: object + default: + Windows: + Pool: $(WINDOWSPOOL) + ImageKey: image + OSVmImage: $(WINDOWSVMIMAGE) + OS: windows + Variables: {} + + Linux: + Pool: $(LINUXPOOL) + ImageKey: image + OSVmImage: $(LINUXVMIMAGE) + OS: linux + Variables: + UploadArtifact: 'true' + Codeql.Enabled: true + Codeql.SkipTaskAutoInjection: false + Codeql.BuildIdentifier: azuredevops_linux + + Mac: + Pool: Azure Pipelines + ImageKey: vmImage + OSVmImage: $(MACVMIMAGE) + OS: macOS + Variables: {} + +stages: + - stage: BuildAndTest + variables: + - template: /eng/pipelines/templates/variables/globals.yml + - template: /eng/pipelines/templates/variables/image.yml + + jobs: + - ${{ each build in parameters.BuildMatrix }}: + - template: /eng/pipelines/templates/jobs/azuredevops-build.yml + parameters: + NameSuffix: ${{ build.key }} + Pool: ${{ build.value.Pool }} + ImageKey: ${{ build.value.ImageKey }} + OSVmImage: ${{ build.value.OSVmImage }} + OS: ${{ build.value.OS }} + Variables: ${{ build.value.Variables }} diff --git a/eng/pipelines/templates/stages/azuredevops-publish-manual.yml b/eng/pipelines/templates/stages/azuredevops-publish-manual.yml new file mode 100644 index 00000000000..420e49b4425 --- /dev/null +++ b/eng/pipelines/templates/stages/azuredevops-publish-manual.yml @@ -0,0 +1,64 @@ +stages: + - stage: PublishManual + dependsOn: Sign + condition: >- + and( + succeeded(), + ne(variables['Skip.Release'], 'true'), + or( + eq('Manual', variables['BuildReasonOverride']), + and( + eq('', variables['BuildReasonOverride']), + eq(variables['Build.Reason'], 'Manual') + ) + ) + ) + + variables: + - template: /eng/pipelines/templates/variables/globals.yml + - template: /eng/pipelines/templates/variables/image.yml + + jobs: + - deployment: Publish_Release + environment: package-publish + pool: + name: azsdk-pool + image: ubuntu-24.04 + os: linux + + templateContext: + type: releaseJob + isProduction: true + inputs: + - input: pipelineArtifact + artifactName: signed + targetPath: signed + + strategy: + runOnce: + deploy: + steps: + - pwsh: | + $files = Get-ChildItem -Path "$(Build.SourcesDirectory)/signed/*.vsix" + if ($files -is [Array]) { + if ($files.Count -gt 1) { + Write-Host "More than one VSIX file found in signed artifact." + exit 1 + } + $vsixFile = $files[0].FullName + } else { + $vsixFile = $files.FullName + } + + Write-Host "##vso[task.setvariable variable=VSIX_FILE]$vsixFile" + displayName: Get VSIX Filename + + - task: 1ES.PublishAzureDevOpsExtension@1 + displayName: Publish Extension + inputs: + targetPath: "$(Build.SourcesDirectory)/signed" + connectedServiceNameAzureRM: azure-sdk-vsmarketplace + fileType: vsix + vsixFile: "$(VSIX_FILE)" + useV5: true + validateExtension: false diff --git a/eng/pipelines/templates/stages/azuredevops-sign.yml b/eng/pipelines/templates/stages/azuredevops-sign.yml new file mode 100644 index 00000000000..4f34fc615c9 --- /dev/null +++ b/eng/pipelines/templates/stages/azuredevops-sign.yml @@ -0,0 +1,54 @@ +stages: + - stage: Sign + dependsOn: BuildAndTest + variables: + - template: /eng/pipelines/templates/variables/globals.yml + - template: /eng/pipelines/templates/variables/image.yml + + jobs: + - job: Sign + ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual') }}: + displayName: Sign + ${{ else }}: + displayName: SKIP Signing + + pool: + name: $(WINDOWSPOOL) + image: $(WINDOWSVMIMAGE) + os: windows + + steps: + - task: DownloadPipelineArtifact@2 + inputs: + artifact: vsix + path: vsix + + - ${{ if in(variables['Build.Reason'], 'IndividualCI', 'BatchedCI', 'Manual') }}: + - template: pipelines/steps/azd-azdo-extension-signing.yml@azure-sdk-build-tools + parameters: + Path: $(Build.SourcesDirectory)\vsix + Pattern: '*.vsix' + + - ${{ else }}: + - pwsh: Write-Host "Skipping signing. Build reason - $(Build.Reason)" + displayName: Signing process skipped for non-release build + + - pwsh: | + New-Item -ItemType Directory -Path signed + Copy-Item vsix/*.vsix signed/ + displayName: Copy signing outputs + condition: always() + + templateContext: + outputs: + - output: pipelineArtifact + condition: succeeded() + displayName: Publish Signed Artifacts + artifact: signed + path: signed/ + + - output: pipelineArtifact + condition: failed() + displayName: Publish failed Signed Artifacts + artifact: signed-FailedAttempt$(System.JobAttempt) + path: signed/ diff --git a/ext/azuredevops/ci-package.ps1 b/ext/azuredevops/ci-package.ps1 new file mode 100644 index 00000000000..c22170da89d --- /dev/null +++ b/ext/azuredevops/ci-package.ps1 @@ -0,0 +1,23 @@ +#!/usr/bin/env pwsh + +$originalLocation = Get-Location + +try { + Set-Location $PSScriptRoot + + Write-Host "Running tsc build" + npm --prefix $PSScriptRoot/setupAzd/ run build + if ($LASTEXITCODE) { + Write-Host "Build failed" + exit $LASTEXITCODE + } + Write-Host "Building Azure DevOps extension package" + tfx extension create --manifest-globs vss-extension.json + if ($LASTEXITCODE) { + Write-Host "Packaging failed" + exit $LASTEXITCODE + } + +} finally { + Set-Location $originalLocation +} diff --git a/ext/azuredevops/ci-test.ps1 b/ext/azuredevops/ci-test.ps1 new file mode 100644 index 00000000000..518cc2bdbc6 --- /dev/null +++ b/ext/azuredevops/ci-test.ps1 @@ -0,0 +1,2 @@ +#!/usr/bin/env pwsh +npm --prefix $PSScriptRoot/setupAzd/ test diff --git a/ext/azuredevops/setupAzd/tests/_suite.ts b/ext/azuredevops/setupAzd/tests/_suite.ts index 935a95462f8..a14a0ae6d97 100644 --- a/ext/azuredevops/setupAzd/tests/_suite.ts +++ b/ext/azuredevops/setupAzd/tests/_suite.ts @@ -5,15 +5,20 @@ import * as ttm from 'azure-pipelines-task-lib/mock-test'; describe('Setup azd task tests', function () { before(function() { - // Setup before tests + // Disable tool download for tests to prevent actual network calls + process.env.AGENT_TOOLSDIRECTORY = '/tmp/test-tools'; + process.env.RUNNER_TOOL_CACHE = '/tmp/test-tools'; }); after(() => { // Cleanup after tests + delete process.env.AGENT_TOOLSDIRECTORY; + delete process.env.RUNNER_TOOL_CACHE; }); it('should succeed with default version (latest)', function(done: Mocha.Done) { - this.timeout(5000); + // Increased timeout for macOS where tool cache operations can take longer + this.timeout(30000); const tp: string = path.join(__dirname, 'success.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); @@ -30,7 +35,8 @@ describe('Setup azd task tests', function () { }); it('should succeed with specific version', function(done: Mocha.Done) { - this.timeout(5000); + // Increased timeout for macOS compatibility + this.timeout(30000); const tp: string = path.join(__dirname, 'successVersion.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp); @@ -47,7 +53,8 @@ describe('Setup azd task tests', function () { }); it('should fail with invalid version', function(done: Mocha.Done) { - this.timeout(5000); + // Increased timeout for macOS compatibility + this.timeout(30000); const tp: string = path.join(__dirname, 'invalidVersion.js'); const tr: ttm.MockTestRunner = new ttm.MockTestRunner(tp);