Notepads CI/CD Pipeline #2006
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
name: Notepads CI/CD Pipeline | |
on: | |
push: | |
#paths-ignore: | |
#- '**.md' | |
#- 'ScreenShots/**' | |
#- '.whitesource' | |
#- 'azure-pipelines.yml' | |
#- '.github/**' | |
#- '!.github/workflows/main.yml' | |
branches-ignore: | |
# PRs made by bots trigger both 'push' and 'pull_request' event, ignore 'push' event in that case | |
- 'dependabot**' | |
- 'imgbot**' | |
tags-ignore: | |
- '**' | |
pull_request: | |
paths-ignore: | |
- '**.md' | |
- 'ScreenShots/**' | |
- '.whitesource' | |
- 'azure-pipelines.yml' | |
- '.github/**' | |
- '!.github/workflows/main.yml' | |
workflow_dispatch: | |
inputs: | |
param: | |
description: Optional parameter for additional actions | |
# Type '(major|maj) (realease|rel)' or '(minor|min) (realease|rel)' or release for major,miner,patch release respectively | |
# Or explicitly provide version number to create release with that version | |
required: false | |
schedule: | |
- cron: '0 8 * * *' | |
jobs: | |
setup: | |
runs-on: windows-latest | |
outputs: | |
matrix: ${{ steps.set_matrix.outputs.matrix }} | |
steps: | |
- name: Setup strategy matrix | |
id: set_matrix | |
shell: pwsh | |
run: | | |
$MATRIX = @{ | |
include = @( [ordered]@{ | |
configuration= "Debug" | |
appxBundlePlatforms = "x86|x64" | |
oldVersion = "" | |
newVersion = "" | |
debug = $true | |
runCodeqlAnalysis = $false | |
runSonarCloudScan = $false | |
}, [ordered]@{ | |
configuration= "Release" | |
appxBundlePlatforms= "x86|x64|ARM64" | |
oldVersion = "" | |
newVersion = "" | |
debug = $true | |
runCodeqlAnalysis= $false | |
runSonarCloudScan= $false | |
}, [ordered]@{ | |
configuration= "Production" | |
appxBundlePlatforms= "x86|x64|ARM64" | |
oldVersion = "" | |
newVersion = "" | |
debug = $true | |
runCodeqlAnalysis= $false | |
runSonarCloudScan= $false | |
} | |
) | |
} | |
if ( ( $env:GITHUB_EVENT -eq 'pull_request' ) ` | |
-or ( $env:GITHUB_EVENT -eq 'schedule' ) ` | |
-or ( $env:FORK -eq 'true' ) ) { | |
$MATRIX.include | Foreach-Object { $_.runSonarCloudScan = $false } | |
} | |
if ( ( $env:GITHUB_EVENT -ne 'push' ) ` | |
-and ( $env:GITHUB_EVENT -ne 'pull_request' ) ) { | |
$MATRIX.include = @($MATRIX.include | Where-Object { $_.configuration -eq "$env:RELEASE_CONFIGURATION" }) | |
if ( ( $env:GITHUB_EVENT -eq 'workflow_dispatch' ) ` | |
-and ( $env:GITHUB_REF -eq 'refs/heads/master' ) ) { | |
$FETCH_URL = "https://api.github.com/repos/$env:GIT_REPOSITORY/tags?per_page=1" | |
$OLD_VER = [System.Version]::Parse($(Invoke-RestMethod -Method Get -Uri $FETCH_URL).name -replace 'v') | |
[System.Int32[]]$VER_INPUT = $($env:PARAM -replace '[a-zA-Z]| ').Split('.') | |
if ( ( $VER_INPUT.Count -gt 1 ) -or ( $VER_INPUT[0] -gt 0 ) ) { | |
$NEW_VER = [System.Version]::new($VER_INPUT[0],` | |
(if ( $VER_INPUT.Count -ge 1 ) { $VER_INPUT[1] } else { 0 }),` | |
(if ( $VER_INPUT.Count -ge 2 ) { $VER_INPUT[2] } else { 0 }),` | |
(if ( $VER_INPUT.Count -ge 3 ) { $VER_INPUT[3] } else { 0 })) | |
} elseif ( $env:PARAM -match 'rel' ) { | |
if ( $env:PARAM -match 'maj' ) { | |
$NEW_VER = [System.Version]::new($OLD_VER.Major + 1, 0, 0, 0) | |
} elseif ( $env:PARAM -match 'min' ) { | |
$NEW_VER = [System.Version]::new($OLD_VER.Major, $OLD_VER.Minor + 1, 0, 0) | |
} else { | |
$NEW_VER = [System.Version]::new($OLD_VER.Major, $OLD_VER.Minor, $OLD_VER.Build + 1, 0) | |
} | |
} | |
if ( ![System.String]::IsNullOrEmpty($OLD_VER) ` | |
-and ![System.String]::IsNullOrEmpty($NEW_VER) ` | |
-and ( $NEW_VER -gt $OLD_VER ) ) { | |
$MATRIX.include | Foreach-Object { $_.oldVersion = $OLD_VER.ToString() } | |
$MATRIX.include | Foreach-Object { $_.newVersion = $NEW_VER.ToString() } | |
} | |
$MATRIX.include | Foreach-Object { $_.runCodeqlAnalysis = $false } | |
$MATRIX.include | Foreach-Object { | |
if ( $_.configuration -eq "$env:RELEASE_CONFIGURATION" ) { $_.release = $true } | |
} | |
} else { | |
$MATRIX.include | Foreach-Object { $_.appxBundlePlatforms = 'x64' } | |
if ( $env:GITHUB_EVENT -ne 'schedule' ) { | |
$MATRIX.include | Foreach-Object { $_.runCodeqlAnalysis = $false } | |
} | |
} | |
} | |
echo "::set-output name=matrix::$($MATRIX | ConvertTo-Json -depth 32 -Compress)" | |
env: | |
FORK: ${{ github.event.repository.fork }} | |
PARAM: ${{ github.event.inputs.param }} | |
GITHUB_REF: ${{ github.ref }} | |
GITHUB_EVENT: ${{ github.event_name }} | |
RELEASE_CONFIGURATION: Production | |
ci: | |
needs: setup | |
runs-on: windows-latest | |
strategy: | |
matrix: ${{ fromJson(needs.setup.outputs.matrix) }} | |
outputs: | |
old_version: ${{ matrix.oldVersion }} | |
new_version: ${{ matrix.newVersion }} | |
env: | |
SOLUTION_NAME: src\Notepads.sln | |
CONFIGURATION: ${{ matrix.configuration }} | |
DEFAULT_DIR: ${{ github.workspace }} | |
steps: | |
- if: matrix.runSonarCloudScan | |
name: Set up JDK 11 | |
id: Setup_JDK | |
uses: actions/setup-java@v4 | |
with: | |
java-version: 1.11 | |
- name: Setup MSBuild | |
id: setup_msbuild | |
uses: microsoft/setup-msbuild@v2 | |
- name: Setup NuGet | |
id: setup-nuget | |
uses: NuGet/setup-nuget@v2.0.0 | |
- name: Checkout repository | |
id: checkout_repo | |
uses: actions/checkout@v4 | |
with: | |
fetch-depth: 50 | |
token: ${{ secrets.GITHUB_TOKEN }} | |
# Due to the insufficient memory allocated by default, CodeQL sometimes requires more to be manually allocated | |
- if: matrix.runCodeqlAnalysis | |
name: Configure Pagefile | |
id: config_pagefile | |
uses: al-cheb/configure-pagefile-action@v1.4 | |
with: | |
minimum-size: 8GB | |
maximum-size: 10GB | |
- if: matrix.newVersion != '' | |
name: Bump GitHub tag and Update manifest | |
id: tag_manifest_generator | |
shell: pwsh | |
run: | | |
git config --global user.name $env:GIT_USER_NAME | |
git config --global user.email $env:GIT_USER_EMAIL | |
git tag -a -m "$env:NEW_VERSION_TAG" $env:NEW_VERSION_TAG | |
git push --follow-tags | |
$xml = [xml](Get-Content $env:APPXMANIFEST_PATH) | |
$xml.Package.Identity.Version = $env:NEW_VERSION | |
$xml.save($env:APPXMANIFEST_PATH) | |
env: | |
GIT_USER_NAME: ${{ secrets.GIT_USER_NAME }} | |
GIT_USER_EMAIL: ${{ secrets.GIT_USER_EMAIL }} | |
APPXMANIFEST_PATH: src\Notepads\Package.appxmanifest | |
NEW_VERSION: ${{ matrix.newVersion }} | |
NEW_VERSION_TAG: v${{ matrix.newVersion }} | |
- if: matrix.runSonarCloudScan | |
name: Cache SonarCloud packages | |
id: cache_sonar_packages | |
uses: actions/cache@v4.0.2 | |
with: | |
path: ~\sonar\cache | |
key: ${{ runner.os }}-sonar | |
restore-keys: ${{ runner.os }}-sonar | |
- if: matrix.runSonarCloudScan | |
name: Cache SonarCloud scanner | |
id: cache_sonar_scanner | |
uses: actions/cache@v4.0.2 | |
with: | |
path: .\.sonar\scanner | |
key: ${{ runner.os }}-sonar-scanner | |
restore-keys: ${{ runner.os }}-sonar-scanner | |
- if: matrix.runSonarCloudScan && steps.cache_sonar_scanner.outputs.cache-hit != 'true' | |
name: Install SonarCloud scanner | |
id: install_sonar_scanner | |
shell: pwsh | |
run: | | |
New-Item -Path .\.sonar\scanner -ItemType Directory | |
dotnet tool update dotnet-sonarscanner --tool-path .\.sonar\scanner | |
- if: matrix.runSonarCloudScan | |
name: Initialize SonarCloud scanner | |
id: init_sonar_scanner | |
shell: pwsh | |
run: | | |
$LOWERCASE_REPOSITORY_NAME = "${{ github.event.repository.name }}".ToLower() | |
.\.sonar\scanner\dotnet-sonarscanner begin ` | |
/k:"${{ github.repository_owner }}_${{ github.event.repository.name }}" ` | |
/o:"$LOWERCASE_REPOSITORY_NAME" ` | |
/d:sonar.login="$env:SONAR_TOKEN" ` | |
/d:sonar.host.url="https://sonarcloud.io" | |
env: | |
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
- if: matrix.newVersion != '' | |
name: Create and validate PFX certificate for AppxBundle | |
id: create_validate_pfx_cert | |
shell: pwsh | |
run: | | |
$TARGET_FILE = "$env:DEFAULT_DIR\cert.pfx" | |
$FROM_BASE64_STR = [System.Convert]::FromBase64String($env:BASE64_STR) | |
[System.IO.File]::WriteAllBytes($TARGET_FILE, $FROM_BASE64_STR) | |
$FILE_STREAM = [System.IO.File]::OpenRead($TARGET_FILE) | |
$FILE_STREAM.Position = 0 | |
$SHA256 = [System.Security.Cryptography.SHA256]::Create() | |
$HASH_BUILDER = [System.Text.StringBuilder]::new() | |
$SHA256.ComputeHash($FILE_STREAM) | ForEach-Object { $HASH_BUILDER.Append($_.ToString("x2")) } | |
if ( $HASH_BUILDER.ToString() -cne $env:SHA256_HASH ) { | |
throw [System.Exception]::new("Created certificate hash $($HASH_BUILDER.ToString()) $( | |
)doesn't match provided hash $($env:SHA256_HASH)") | |
} | |
env: | |
BASE64_STR: ${{ secrets.PACKAGE_CERTIFICATE_BASE64 }} | |
SHA256_HASH: ${{ secrets.PACKAGE_CERTIFICATE_SHA256 }} | |
- name: Restore the application | |
id: restore_application | |
shell: pwsh | |
run: | | |
msbuild $env:SOLUTION_NAME /t:Restore | |
nuget restore $env:SOLUTION_NAME | |
- if: matrix.runCodeqlAnalysis | |
name: Initialize CodeQL | |
id: init_codeql | |
uses: github/codeql-action/init@v3 | |
with: | |
queries: security-and-quality | |
languages: csharp | |
- name: Build and generate bundles | |
id: build_app | |
shell: pwsh | |
run: | | |
msbuild $env:SOLUTION_NAME ` | |
/p:Platform=$env:PLATFORM ` | |
/p:Configuration=$env:CONFIGURATION ` | |
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE ` | |
/p:AppxBundle=$env:APPX_BUNDLE ` | |
/p:AppxPackageSigningEnabled=$env:APPX_PACKAGE_SIGNING_ENABLED ` | |
/p:AppxBundlePlatforms=$env:APPX_BUNDLE_PLATFORMS ` | |
/p:AppxPackageDir=$env:ARTIFACTS_DIR ` | |
/p:PackageCertificateKeyFile=$env:PACKAGE_CERTIFICATE_KEYFILE ` | |
/p:PackageCertificatePassword=$env:PACKAGE_CERTIFICATE_PASSWORD ` | |
/p:AppCenterSecret=$env:APP_CENTER_SECRET | |
env: | |
PLATFORM: x64 | |
UAP_APPX_PACKAGE_BUILD_MODE: StoreUpload | |
APPX_BUNDLE: Always | |
APPX_PACKAGE_SIGNING_ENABLED: ${{ matrix.newVersion != '' }} | |
APPX_BUNDLE_PLATFORMS: ${{ matrix.appxBundlePlatforms }} | |
ARTIFACTS_DIR: ${{ github.workspace }}\Artifacts | |
PACKAGE_CERTIFICATE_KEYFILE: ${{ github.workspace }}\cert.pfx | |
PACKAGE_CERTIFICATE_PASSWORD: ${{ secrets.PACKAGE_CERTIFICATE_PWD }} | |
APP_CENTER_SECRET: ${{ secrets.APP_CENTER_SECRET }} | |
- if: matrix.debug && !contains( matrix.appxBundlePlatforms, 'arm64' ) | |
name: Test ARM build in debug configuration | |
id: build_app_arm_debug | |
shell: pwsh | |
run: | | |
msbuild $env:SOLUTION_NAME ` | |
/p:Platform=$env:PLATFORM ` | |
/p:Configuration=$env:CONFIGURATION ` | |
/p:UapAppxPackageBuildMode=$env:UAP_APPX_PACKAGE_BUILD_MODE ` | |
/p:AppxBundle=$env:APPX_BUNDLE ` | |
/p:AppxBundlePlatforms=$env:APPX_BUNDLE_PLATFORMS | |
env: | |
PLATFORM: ARM64 | |
UAP_APPX_PACKAGE_BUILD_MODE: StoreUpload | |
APPX_BUNDLE: Always | |
APPX_BUNDLE_PLATFORMS: ARM64 | |
- if: matrix.runCodeqlAnalysis | |
name: Perform CodeQL Analysis | |
id: analyze_codeql | |
uses: github/codeql-action/analyze@v3 | |
continue-on-error: true | |
- if: matrix.runSonarCloudScan | |
name: Send SonarCloud results | |
id: send_sonar_results | |
shell: pwsh | |
run: | | |
.\.sonar\scanner\dotnet-sonarscanner end ` | |
/d:sonar.login="$env:SONAR_TOKEN" | |
env: | |
GITHUB_TOKEN: ${{ secrets.SONAR_GITHUB_TOKEN }} | |
SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} | |
- if: matrix.newVersion != '' | |
name: Upload build artifacts | |
id: upload_artifacts | |
uses: actions/upload-artifact@v4 | |
with: | |
name: Build artifacts | |
path: Artifacts/ | |
cd: | |
# This job will execute when the workflow is triggered on a 'workflow_dispatch' event, | |
# the target branch is 'master' and required parameter provided for release. | |
if: needs.ci.outputs.new_version != '' | |
needs: [ setup, ci ] | |
runs-on: windows-latest | |
env: | |
OLD_VERSION: ${{ needs.ci.outputs.old_version }} | |
NEW_VERSION: ${{ needs.ci.outputs.new_version }} | |
steps: | |
- name: Checkout repository | |
id: checkout_repo | |
uses: actions/checkout@v4 | |
- name: Download and extract MSIX package | |
id: dl_package_artifact | |
uses: actions/download-artifact@v4 | |
with: | |
name: Build artifacts | |
path: Artifacts/ | |
- name: Create deployment payload | |
id: create_notepads_zip | |
shell: pwsh | |
run: | | |
Get-ChildItem -Filter *Production* -Recurse | Rename-Item -NewName { $_.name -replace "_Production|_Test",'' } | |
Compress-Archive -Path "Notepads_$($env:NEW_VERSION)\*" ` | |
-DestinationPath "Notepads_$($env:NEW_VERSION)\Notepads_$($env:NEW_VERSION)_x86_x64_ARM64.zip" | |
working-directory: ./Artifacts | |
- name: Generate changelog | |
id: generate_changlog | |
uses: mrchief/universal-changelog-action@v1.3.2 | |
with: | |
previousReleaseTagNameOrSha: v${{ env.OLD_VERSION }} | |
nextReleaseTagName: v${{ env.NEW_VERSION }} | |
nextReleaseName: v${{ env.NEW_VERSION }} | |
configFilePath: .github/RELEASE_TEMPLATE/changelog_config.json | |
- name: Create and publish release | |
id: create_release | |
uses: ncipollo/release-action@v1.14.0 | |
with: | |
allowUpdates: true | |
replacesArtifacts: true | |
tag: v${{ env.NEW_VERSION }} | |
name: Notepads v${{ env.NEW_VERSION }} | |
body: ${{ steps.generate_changlog.outputs.changelog }} | |
token: ${{ secrets.GITHUB_TOKEN }} | |
artifacts: | |
Artifacts/Notepads_${{ env.NEW_VERSION }}/Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64.msixbundle | |
Artifacts/Notepads_${{ env.NEW_VERSION }}/Notepads_${{ env.NEW_VERSION }}_x86_x64_ARM64.zip | |
# - name: Publish to Windows Store | |
# id: publish_to_store | |
# uses: isaacrlevin/windows-store-action@1.0 | |
# with: | |
# tenant-id: ${{ secrets.AZURE_AD_TENANT_ID }} | |
# client-id: ${{ secrets.AZURE_AD_APPLICATION_CLIENT_ID }} | |
# client-secret: ${{ secrets.AZURE_AD_APPLICATION_SECRET }} | |
# app-id: ${{ secrets.STORE_APP_ID }} | |
# package-path: "${{ github.workspace }}/Artifacts/" | |
# Built with ❤ by [Pipeline Foundation](https://pipeline.foundation) |