|
| 1 | +# This workflow will build a .NET project |
| 2 | +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-net |
| 3 | + |
| 4 | +# TODO: Restore notification for failed actions |
| 5 | +# TODO: Review https://docs.github.com/en/actions/learn-github-actions/understanding-github-actions |
| 6 | +# TODO: Review https://docs.github.com/en/actions/security-guides/security-hardening-for-github-actions |
| 7 | +# TODO: Adapt name and .yml file name (resets package counter) |
| 8 | +name: .NET |
| 9 | + |
| 10 | +# Advantages |
| 11 | +# - Max parallel builds (jobs) is 20 (MacOS 5) |
| 12 | +# - Job timeout is 6 hours, workflow 35 days |
| 13 | +# - Artifact retention is 90 days |
| 14 | +# - Linux runs faster |
| 15 | +# - Integrated in GitHub |
| 16 | +# - Rich ecosystem of actions |
| 17 | +# - Timings per operation (build, test, cleanup) |
| 18 | +# - Test summaries |
| 19 | +# - Improved security |
| 20 | +# - Easier to try .NET preview versions |
| 21 | + |
| 22 | +on: |
| 23 | + push: |
| 24 | + branches: [ 'master', 'release/**' ] |
| 25 | + pull_request: |
| 26 | + branches: [ 'master', 'release/**' ] |
| 27 | + |
| 28 | +concurrency: |
| 29 | + group: ${{ github.workflow }}-${{ github.ref }} |
| 30 | + cancel-in-progress: true |
| 31 | + |
| 32 | +env: |
| 33 | + DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true |
| 34 | + DOTNET_CLI_TELEMETRY_OPTOUT: true |
| 35 | + # Windows comes with PostgreSQL pre-installed, and defines the PGPASSWORD environment variable. Remove it as it interferes |
| 36 | + # with the tests running from AppVeyor |
| 37 | + PGPASSWORD: "" |
| 38 | + |
| 39 | +jobs: |
| 40 | + build-and-test: |
| 41 | + # TODO: Update required checks in GitHub settings |
| 42 | + # TODO: Update status badge in /README.md |
| 43 | + timeout-minutes: 30 |
| 44 | + strategy: |
| 45 | + fail-fast: false |
| 46 | + matrix: |
| 47 | + os: [ubuntu-latest, windows-latest, macos-latest] |
| 48 | + runs-on: ${{ matrix.os }} |
| 49 | + steps: |
| 50 | + - name: Setup PostgreSQL |
| 51 | + uses: ikalnytskyi/action-setup-postgres@v4 |
| 52 | + with: |
| 53 | + username: postgres |
| 54 | + password: postgres |
| 55 | + - name: Setup .NET |
| 56 | + uses: actions/setup-dotnet@v3 |
| 57 | + with: |
| 58 | + dotnet-version: 6.0.x |
| 59 | + - name: Setup PowerShell (Ubuntu) |
| 60 | + if: matrix.os == 'ubuntu-latest' |
| 61 | + run: | |
| 62 | + dotnet tool install --global PowerShell |
| 63 | + - name: Setup PowerShell (Windows) |
| 64 | + if: matrix.os == 'windows-latest' |
| 65 | + shell: cmd |
| 66 | + run: | |
| 67 | + curl --location --output "%RUNNER_TEMP%\PowerShell-7.3.6-win-x64.msi" https://github.com/PowerShell/PowerShell/releases/download/v7.3.6/PowerShell-7.3.6-win-x64.msi |
| 68 | + msiexec.exe /package "%RUNNER_TEMP%\PowerShell-7.3.6-win-x64.msi" /quiet USE_MU=1 ENABLE_MU=1 ADD_PATH=1 DISABLE_TELEMETRY=1 |
| 69 | + - name: Setup PowerShell (macOS) |
| 70 | + if: matrix.os == 'macos-latest' |
| 71 | + run: | |
| 72 | + /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)" |
| 73 | + brew install --cask powershell |
| 74 | + - name: Show installed versions |
| 75 | + shell: pwsh |
| 76 | + run: | |
| 77 | + Write-Host "$(pwsh --version) is installed at $PSHOME" |
| 78 | + psql --version |
| 79 | + Write-Host "Using .NET SDK: $(dotnet --version)" |
| 80 | + #Write-Host "Installed .NET SDKs:" |
| 81 | + #dotnet --list-sdks --list-runtimes |
| 82 | + #Write-Host "Installed .NET runtimes:" |
| 83 | + #dotnet --list-runtimes |
| 84 | + #Write-Host "Environment variables:" |
| 85 | + #dir env: | %{"{0}={1}" -f $_.Name,$_.Value} |
| 86 | + - name: Git checkout |
| 87 | + uses: actions/checkout@v3 |
| 88 | + - name: Restore tools |
| 89 | + run: dotnet tool restore |
| 90 | + - name: Restore packages |
| 91 | + run: dotnet restore |
| 92 | + - name: Calculate version suffix |
| 93 | + shell: pwsh |
| 94 | + run: | |
| 95 | + # TODO: Fail when package tag prefix does not match the version prefix stored in Directory.Build.props? |
| 96 | + if ($env:GITHUB_REF_TYPE -eq 'tag') { |
| 97 | + # Get the version suffix from the repo tag. Example: v1.0.0-preview1-final => preview1-final |
| 98 | + $segments = $env:GITHUB_REF_NAME -split "-" |
| 99 | + $suffixSegments = $segments[1..-1] |
| 100 | + $versionSuffix = $suffixSegments -join "-" |
| 101 | + } |
| 102 | + else { |
| 103 | + # Get the version suffix from the auto-incrementing build number. Example: 123 => master-0123 |
| 104 | + $revision = "{0:D4}" -f [convert]::ToInt32($env:GITHUB_RUN_NUMBER, 10) |
| 105 | + $versionSuffix = "$($env:GITHUB_HEAD_REF ?? $env:GITHUB_REF_NAME)-$revision" |
| 106 | + } |
| 107 | + Write-Output "Using version suffix: $versionSuffix" |
| 108 | + Write-Output "PACKAGE_VERSION_SUFFIX=$versionSuffix" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append |
| 109 | + - name: Build |
| 110 | + shell: pwsh |
| 111 | + run: dotnet build --no-restore --configuration Release --version-suffix=$env:PACKAGE_VERSION_SUFFIX |
| 112 | + - name: Test |
| 113 | + run: dotnet test --no-build --configuration Release --collect:"XPlat Code Coverage" --logger "GitHubActions" |
| 114 | + - name: Upload coverage to codecov.io |
| 115 | + # TODO: Why does codecov report 3.83% decreased coverage? |
| 116 | + if: matrix.os == 'ubuntu-latest' |
| 117 | + uses: codecov/codecov-action@v3 |
| 118 | + - name: Create packages |
| 119 | + shell: pwsh |
| 120 | + run: dotnet pack --no-build --configuration Release --output $env:GITHUB_WORKSPACE/artifacts/packages --version-suffix=$env:PACKAGE_VERSION_SUFFIX |
| 121 | + - name: Upload packages to artifacts |
| 122 | + uses: actions/upload-artifact@v3 |
| 123 | + with: |
| 124 | + name: packages-${{ matrix.os }} |
| 125 | + path: artifacts/packages |
| 126 | +# TODO: Upload pre-release NuGet package - https://docs.github.com/en/packages/working-with-a-github-packages-registry/working-with-the-nuget-registry |
| 127 | +# TODO: Push to NuGet on new release |
| 128 | +# TODO: Try CodeQL Analysis |
| 129 | +# TODO: Add dependabot.yml (GH Security: Enable Dependabot version updates) |
| 130 | +# Keep actions up-to-date: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates |
| 131 | +# in .github/dependabot.yml: |
| 132 | +# version: 2 |
| 133 | +# updates: |
| 134 | +# - package-ecosystem: "github-actions" |
| 135 | +# directory: "/" |
| 136 | +# schedule: |
| 137 | +# interval: "daily" |
| 138 | +# labels: |
| 139 | +# - "CI/CD" |
| 140 | +# commit-message: |
| 141 | +# prefix: ci |
| 142 | +# TODO: Reorder steps so that irreversible actions come last |
| 143 | + - name: Generate documentation |
| 144 | + shell: pwsh |
| 145 | + env: |
| 146 | + DOCFX_SOURCE_BRANCH_NAME: ${{ github.base_ref || github.ref_name }} |
| 147 | + run: | |
| 148 | + Write-Host "Using docfx branch name: $env:DOCFX_SOURCE_BRANCH_NAME" |
| 149 | + cd docs |
| 150 | + & ./generate-examples.ps1 |
| 151 | + dotnet docfx docfx.json |
| 152 | + Copy-Item CNAME _site/CNAME |
| 153 | + Copy-Item home/*.html _site/ |
| 154 | + Copy-Item home/*.ico _site/ |
| 155 | + New-Item -Force _site/styles -ItemType Directory | Out-Null |
| 156 | + Copy-Item -Recurse home/assets/* _site/styles/ |
| 157 | + - name: Upload documentation to artifacts |
| 158 | + uses: actions/upload-artifact@v3 |
| 159 | + with: |
| 160 | + name: documentation-${{ matrix.os }} |
| 161 | + path: docs/_site |
| 162 | + - name: Publish documentation |
| 163 | + # TODO: Setup Deployment protection rules (https://docs.github.com/en/pages/getting-started-with-github-pages/configuring-a-publishing-source-for-your-github-pages-site) |
| 164 | + # Better for us: https://github.com/peaceiris/actions-gh-pages |
| 165 | + #if: (github.event_name == 'push' && github.ref == 'refs/heads/main') |
| 166 | + if: matrix.os == 'ubuntu-latest' |
| 167 | + uses: peaceiris/actions-gh-pages@v3 |
| 168 | + with: |
| 169 | + # TODO: Delete GitHub PAT |
| 170 | + github_token: ${{ secrets.GITHUB_TOKEN }} |
| 171 | + publish_branch: gh-pages-test |
| 172 | + publish_dir: ./docs/_site |
| 173 | + user_name: 'github-actions-bot' |
| 174 | + user_email: 'github-actions-bot@users.noreply.github.com' |
| 175 | + commit_message: Automated commit from cibuild |
| 176 | + #full_commit_message: ${{ github.event.head_commit.message }} |
| 177 | + |
| 178 | + inspect-code: |
| 179 | + timeout-minutes: 30 |
| 180 | + runs-on: ubuntu-latest |
| 181 | + steps: |
| 182 | + - name: Git checkout |
| 183 | + uses: actions/checkout@v3 |
| 184 | + - name: Setup .NET |
| 185 | + uses: actions/setup-dotnet@v3 |
| 186 | + with: |
| 187 | + dotnet-version: 6.0.x |
| 188 | + - name: Restore tools |
| 189 | + run: dotnet tool restore |
| 190 | + - name: InspectCode |
| 191 | + shell: pwsh |
| 192 | + run: | |
| 193 | + $inspectCodeOutputPath = Join-Path $env:RUNNER_TEMP 'jetbrains-inspectcode-results.xml' |
| 194 | + Write-Output "INSPECT_CODE_OUTPUT_PATH=$inspectCodeOutputPath" | Out-File -FilePath $env:GITHUB_ENV -Encoding utf8 -Append |
| 195 | + dotnet jb inspectcode JsonApiDotNetCore.sln --build --output="$inspectCodeOutputPath" --profile=WarningSeverities.DotSettings --properties:Configuration=Release --severity=WARNING --verbosity=WARN -dsl=GlobalAll -dsl=GlobalPerProduct -dsl=SolutionPersonal -dsl=ProjectPersonal |
| 196 | + - name: Verify outcome |
| 197 | + shell: pwsh |
| 198 | + run: | |
| 199 | + [xml]$xml = Get-Content $env:INSPECT_CODE_OUTPUT_PATH |
| 200 | + if ($xml.report.Issues -and $xml.report.Issues.Project) { |
| 201 | + foreach ($project in $xml.report.Issues.Project) { |
| 202 | + if ($project.Issue.Count -gt 0) { |
| 203 | + $project.ForEach({ |
| 204 | + Write-Output "`nProject $($project.Name)" |
| 205 | + $failed = $true |
| 206 | +
|
| 207 | + $_.Issue.ForEach({ |
| 208 | + $issueType = $xml.report.IssueTypes.SelectSingleNode("IssueType[@Id='$($_.TypeId)']") |
| 209 | + $severity = $_.Severity ?? $issueType.Severity |
| 210 | +
|
| 211 | + Write-Output "[$severity] $($_.File):$($_.Line) $($_.TypeId): $($_.Message)" |
| 212 | + }) |
| 213 | + }) |
| 214 | + } |
| 215 | + } |
| 216 | +
|
| 217 | + if ($failed) { |
| 218 | + Write-Error "One or more projects failed code inspection." |
| 219 | + } |
| 220 | + else { |
| 221 | + Write-Output "No issues found." |
| 222 | + } |
| 223 | + } |
| 224 | + cleanup-code: |
| 225 | + timeout-minutes: 30 |
| 226 | + # TODO: Test with PR from community user (pull_request_target) |
| 227 | + runs-on: ubuntu-latest |
| 228 | + steps: |
| 229 | + - name: Git checkout |
| 230 | + uses: actions/checkout@v3 |
| 231 | + with: |
| 232 | + fetch-depth: 2 |
| 233 | + - name: Setup .NET |
| 234 | + uses: actions/setup-dotnet@v3 |
| 235 | + with: |
| 236 | + dotnet-version: 6.0.x |
| 237 | + - name: Restore tools |
| 238 | + run: dotnet tool restore |
| 239 | + - name: Restore packages |
| 240 | + run: dotnet restore |
| 241 | + - name: CleanupCode (on PR diff) |
| 242 | + if: github.event_name == 'pull_request' |
| 243 | + shell: pwsh |
| 244 | + run: | |
| 245 | + # Not using the environment variables for SHAs, because they may be outdated. This happens on force-push after the build is queued, but before it starts. |
| 246 | + # The below works because HEAD is at the merge commit, so HEAD~1 is at the base branch. |
| 247 | + $headCommitHash = git rev-parse HEAD |
| 248 | + $baseCommitHash = git rev-parse HEAD~1 |
| 249 | +
|
| 250 | + # TODO: What happens when PR is empty? |
| 251 | + if ($baseCommitHash -ne $headCommitHash) { |
| 252 | + Write-Output "Running code cleanup on commit range $baseCommitHash..$headCommitHash in pull request." |
| 253 | + dotnet regitlint -s JsonApiDotNetCore.sln --print-command --skip-tool-check --max-runs=5 --jb-profile="JADNC Full Cleanup" --jb --properties:Configuration=Release --jb --verbosity=WARN -f commits -a $headCommitHash -b $baseCommitHash --fail-on-diff --print-diff |
| 254 | + } |
| 255 | + else { |
| 256 | + Write-Output "No changed files in pull request." |
| 257 | + } |
| 258 | + # TODO: Full cleanup on branch |
0 commit comments