CI/CD #1533
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: CI/CD | |
on: | |
push: | |
branches: ['main'] | |
pull_request: | |
branches: ['main'] | |
schedule: | |
- cron: '0 0 * * *' | |
workflow_dispatch: | |
inputs: | |
release: | |
description: Create release? | |
required: false | |
type: boolean | |
version: | |
description: New version to release | |
required: false | |
type: string | |
jobs: | |
dependabot: | |
name: Auto-merge dependabot PR | |
if: github.event_name == 'pull_request' && github.event.sender.login == 'dependabot[bot]' | |
runs-on: ubuntu-latest | |
concurrency: | |
group: dependabot-${{ github.ref }} | |
cancel-in-progress: true | |
permissions: | |
contents: write | |
pull-requests: write | |
steps: | |
- name: Approve and Auto-merge | |
run: | | |
gh pr review --approve "${{ github.event.pull_request.html_url }}" | |
gh pr merge --auto --squash "${{ github.event.pull_request.html_url }}" | |
env: | |
GITHUB_TOKEN: ${{ secrets.COMMIT_TOKEN }} | |
ci: | |
name: Check requirements | |
runs-on: ubuntu-latest | |
outputs: | |
run: ${{ github.event_name != 'push' }} | |
release: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.release == 'true' }} | |
check_latest: ${{ steps.swift-org.outputs.check_latest }} | |
update_latest: ${{ github.ref_name == github.event.repository.default_branch && steps.swift-org.outputs.update_latest == 'true' }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup npm pacakges | |
run: npm install --legacy-peer-deps | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ github.event_name == 'schedule' && '{"commit":"HEAD"}' }} | |
- name: Check latest swift.org | |
id: swift-org | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const hook = require('.github/utils/update_metadata.js'); | |
const {stdout} = await exec.getExecOutput('git', ['rev-parse', 'HEAD'], {cwd: 'swiftorg'}); | |
const oldCommit = (await hook.currentData()).commit; | |
const newCommit = stdout.trim(); | |
core.setOutput('check_latest', newCommit); | |
core.setOutput('update_latest', newCommit != oldCommit); | |
analyze: | |
name: Run CodeQL analysis | |
if: github.event_name != 'workflow_dispatch' | |
needs: ci | |
runs-on: ubuntu-latest | |
concurrency: | |
group: analyze-${{ github.ref }} | |
cancel-in-progress: true | |
permissions: | |
actions: read | |
contents: read | |
security-events: write | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
# Initializes the CodeQL tools for scanning. | |
- name: Initialize CodeQL | |
uses: github/codeql-action/init@v3 | |
with: | |
languages: javascript | |
- name: Perform CodeQL Analysis | |
uses: github/codeql-action/analyze@v3 | |
unit-test: | |
name: Run unit tests | |
needs: ci | |
runs-on: ubuntu-latest | |
concurrency: | |
group: unit-test-${{ github.ref }} | |
cancel-in-progress: true | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ format('{{"commit":"{0}"}}', needs.ci.outputs.check_latest) }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup and Run unit tests | |
run: | | |
npm install --legacy-peer-deps | |
npm run lint | |
npm run test | |
- name: Codecov upload | |
uses: codecov/[email protected] | |
with: | |
token: ${{ secrets.CODECOV_TOKEN }} | |
fail_ci_if_error: ${{ github.event_name == 'push' }} | |
integration-test: | |
name: Integrate Swift ${{ matrix.swift }} on ${{ matrix.os }} with development ${{ matrix.development }} | |
if: needs.ci.outputs.run == 'true' | |
needs: ci | |
runs-on: ${{ matrix.os }} | |
concurrency: | |
group: integration-test-${{ github.ref }}-${{ matrix.os }}-${{ matrix.swift }}-${{ matrix.development }} | |
cancel-in-progress: true | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ format('{{"commit":"{0}"}}', needs.ci.outputs.check_latest) }} | |
strategy: | |
matrix: | |
os: [ubuntu-latest, macos-latest, windows-latest] | |
swift: ['latest'] | |
development: [false, true] | |
include: | |
- os: macos-latest | |
swift: '5.0.0' # oldest | |
development: false | |
- os: macos-13 | |
swift: 'latest' # action caching architecture mismatch | |
development: false | |
- os: macos-13 | |
swift: '5.9' # Xcode toolchain inclusion optimization | |
development: false | |
- os: windows-latest | |
swift: '5.9' # 2nd installation approach | |
development: false | |
- os: windows-2019 | |
swift: '5.3' | |
development: false | |
- os: ubuntu-latest | |
swift: '5.3.0' # oldest | |
development: false | |
- os: windows-latest | |
swift: '5.3' # 1st installation approach | |
development: false | |
- os: ubuntu-22.04 | |
swift: ${{ fromJSON(vars.SETUPSWIFT_CUSTOM_TOOLCHAINS).ubuntu2204 }} # custom toolchain | |
development: true | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup and Build | |
run: | | |
npm install --legacy-peer-deps | |
npm run build | |
npm run package | |
- name: Run | |
id: setup-swift | |
uses: ./ | |
with: | |
swift-version: ${{ matrix.swift }} | |
development: ${{ matrix.development }} | |
check-latest: ${{ needs.ci.outputs.check_latest }} | |
cache-snapshot: ${{ !matrix.development }} | |
- name: Verify Swift version in macos | |
if: runner.os == 'macOS' | |
run: xcrun --toolchain ${{ env.TOOLCHAINS || '""' }} swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 | |
- name: Verify Swift version | |
run: swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 | |
- name: Check link | |
if: runner.os == 'Windows' | |
run: which link | grep "Microsoft Visual Studio" || exit 1 | |
- name: Get cached installation | |
id: get-tool | |
if: failure() | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const os = require('os'); | |
const path = require('path'); | |
const fs = require('fs/promises'); | |
const toolCache = require('@actions/tool-cache'); | |
const {exec: actionExec} = require('@actions/exec'); | |
let arch = ''; | |
switch (os.arch()) { | |
case 'x64': | |
arch = 'x86_64'; | |
break; | |
case 'arm64': | |
arch = 'aarch64'; | |
break; | |
default: | |
arch = os.arch(); | |
break; | |
} | |
const key = process.env['SWIFT_SETUP_TOOL_KEY']; | |
if (!key) { | |
core.debug(`Toolcache not set`); | |
return; | |
} | |
const versions = toolCache.findAllVersions(key, arch); | |
if (!versions.length) { | |
core.debug(`No versions found for "${key}" with arch ${arch}`); | |
return; | |
} else { | |
core.debug(`Found versions "${versions.join(', ')}" for "${key}" with arch ${arch}`); | |
} | |
const tool = toolCache.find(key, versions[0], arch).trim(); | |
await fs.access(tool); | |
const tmpDir = process.env.RUNNER_TEMP || os.tmpdir(); | |
const zip = path.join(tmpDir, `${key}.zip`); | |
await actionExec('zip', ['-r', zip, tool, '-x', '*.DS_Store']); | |
return { key: key, tool: zip }; | |
- name: Upload cached installation as artifact | |
if: always() && steps.get-tool.outputs.result != '' | |
uses: actions/upload-artifact@v4 | |
with: | |
name: ${{ fromJson(steps.get-tool.outputs.result).key }}-${{ matrix.os }}-tool | |
path: ${{ fromJson(steps.get-tool.outputs.result).tool }} | |
dry-run: | |
name: Check action with dry run | |
if: needs.ci.outputs.run == 'true' | |
needs: ci | |
runs-on: ubuntu-latest | |
continue-on-error: true | |
concurrency: | |
group: dry-run-${{ github.ref }} | |
cancel-in-progress: true | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ format('{{"commit":"{0}"}}', needs.ci.outputs.check_latest) }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup and Build | |
run: | | |
npm install --legacy-peer-deps | |
npm run build | |
npm run package | |
- name: Run | |
id: setup-swift | |
uses: ./ | |
with: | |
check-latest: ${{ needs.ci.outputs.check_latest }} | |
dry-run: true | |
- name: Verify Swift version | |
uses: addnab/docker-run-action@v3 | |
with: | |
image: swift:${{ fromJSON(steps.setup-swift.outputs.toolchain).docker }} | |
run: swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 | |
e2e-test: | |
name: End-to-end test latest Swift on ${{ matrix.os }} | |
if: needs.ci.outputs.run == 'true' | |
needs: ci | |
runs-on: ${{ matrix.os }} | |
concurrency: | |
group: e2e-test-${{ github.ref }}-${{ matrix.os }} | |
cancel-in-progress: true | |
env: | |
COMPOSITE: ./.ref-download-test | |
strategy: | |
matrix: | |
os: [ubuntu-latest, macos-latest, windows-latest] | |
steps: | |
- name: Setup wrapper composite action at ${{ env.COMPOSITE }} | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const path = require('path'); | |
const fs = require('fs/promises'); | |
const composite = '${{ env.COMPOSITE }}'; | |
const repo = '${{ github.event.pull_request.head.repo.full_name || github.repository }}'; | |
const branch = '${{ github.head_ref || github.ref_name }}'; | |
const action = { | |
outputs: { | |
'swift-version': { | |
value: '$' + `{{ steps.run.outputs.swift-version }}` | |
} | |
}, | |
runs: { | |
using: 'composite', | |
steps: [ | |
{ | |
name: 'Cleanup', | |
uses: `actions/github-script@${{ github.action_ref }}`, | |
with: { | |
script: `await io.rmRF('${composite}')` | |
} | |
}, | |
{ | |
name: `Run ${repo}@${branch}`, | |
id: 'run', | |
uses: `${repo}@${branch}`, | |
with: { | |
'swift-version': 'latest', | |
'check-latest': '${{ needs.ci.outputs.check_latest }}' | |
} | |
} | |
] | |
} | |
}; | |
const content = JSON.stringify(action); | |
core.debug(`Writing action:\n${content}\n`); | |
await io.mkdirP(composite); | |
await fs.writeFile(path.join(composite, 'action.yml'), content); | |
- name: Run composite action ${{ env.COMPOSITE }} | |
id: setup-swift | |
uses: ./.ref-download-test | |
- name: Cleanup | |
if: always() | |
uses: actions/github-script@v7 | |
continue-on-error: true | |
with: | |
script: await io.rmRF('${{ env.COMPOSITE }}'); | |
- name: Verify Swift version in macos | |
if: runner.os == 'macOS' | |
run: xcrun --toolchain ${{ env.TOOLCHAINS || '""' }} swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 | |
- name: Verify Swift version | |
run: swift --version | grep ${{ steps.setup-swift.outputs.swift-version }} || exit 1 | |
- name: Check link | |
if: runner.os == 'Windows' | |
run: which link | grep "Microsoft Visual Studio" || exit 1 | |
- name: Test Swift package | |
run: | | |
swift package init --type library --name SetupLib | |
swift build --build-tests | |
swift test | |
pages: | |
name: Publish metadata to GitHub Pages | |
if: | | |
always() && needs.ci.outputs.update_latest == 'true' && | |
(needs.analyze.result == 'success' || needs.analyze.result == 'skipped') && | |
(needs.unit-test.result == 'success' || needs.unit-test.result == 'skipped') && | |
(needs.integration-test.result == 'success' || needs.integration-test.result == 'skipped') && | |
(needs.dry-run.result == 'success' || needs.dry-run.result == 'skipped') && | |
(needs.e2e-test.result == 'success' || needs.e2e-test.result == 'skipped') | |
needs: [ci, analyze, unit-test, integration-test, dry-run, e2e-test] | |
environment: | |
name: github-pages | |
url: ${{ steps.deployment.outputs.page_url }} | |
concurrency: | |
group: pages-update | |
cancel-in-progress: true | |
permissions: | |
id-token: write | |
pages: write | |
runs-on: ubuntu-latest | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ format('{{"commit":"{0}"}}', needs.ci.outputs.check_latest) }} | |
steps: | |
- name: Checkout | |
uses: actions/checkout@v4 | |
- name: Setup Pages | |
uses: actions/configure-pages@v5 | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup npm pacakges | |
run: npm install --legacy-peer-deps | |
- name: Generate swift.org metadata | |
id: swiftorg-metadata | |
uses: actions/github-script@v7 | |
with: | |
script: | | |
const hook = require('.github/utils/update_metadata.js'); | |
await hook.update(); | |
return true; | |
- name: Upload artifact | |
if: steps.swiftorg-metadata.outputs.result == 'true' | |
uses: actions/upload-pages-artifact@v3 | |
with: | |
path: pages | |
- name: Deploy to GitHub Pages | |
if: steps.swiftorg-metadata.outputs.result == 'true' | |
id: deployment | |
uses: actions/deploy-pages@v4 | |
cd: | |
name: Create release | |
if: | | |
always() && needs.ci.outputs.release == 'true' && | |
(needs.analyze.result == 'success' || needs.analyze.result == 'skipped') && | |
(needs.unit-test.result == 'success' || needs.unit-test.result == 'skipped') && | |
(needs.integration-test.result == 'success' || needs.integration-test.result == 'skipped') && | |
(needs.dry-run.result == 'success' || needs.dry-run.result == 'skipped') && | |
(needs.e2e-test.result == 'success' || needs.e2e-test.result == 'skipped') | |
needs: [ci, analyze, unit-test, integration-test, dry-run, e2e-test] | |
runs-on: ubuntu-latest | |
concurrency: | |
group: cd-${{ github.ref }} | |
cancel-in-progress: true | |
env: | |
SETUPSWIFT_SWIFTORG_METADATA: ${{ format('{{"commit":"{0}"}}', needs.ci.outputs.check_latest) }} | |
steps: | |
- name: Checkout repository | |
uses: actions/checkout@v4 | |
with: | |
fetch-tags: true | |
token: ${{ secrets.COMMIT_TOKEN }} | |
- name: Import GPG | |
uses: crazy-max/[email protected] | |
with: | |
gpg_private_key: ${{ secrets.COMMIT_SIGN_KEY }} | |
passphrase: ${{ secrets.COMMIT_SIGN_KEY_PASSPHRASE }} | |
git_user_signingkey: true | |
git_commit_gpgsign: true | |
git_tag_gpgsign: true | |
git_push_gpgsign: if-asked | |
- name: Setup Node.js | |
uses: actions/setup-node@v4 | |
with: | |
cache: npm | |
- name: Cache dependencies | |
uses: actions/[email protected] | |
with: | |
key: node-${{ github.ref }} | |
path: node_modules | |
- name: Setup npm pacakges | |
run: npm install --legacy-peer-deps | |
- name: Generate changelog | |
id: changelog | |
uses: TriPSs/conventional-changelog-action@v6 | |
with: | |
github-token: ${{ secrets.COMMIT_TOKEN }} | |
git-message: 'chore(CHANGELOG): update for {version}' | |
git-user-name: swiftylab-ci | |
git-user-email: [email protected] | |
release-count: 0 | |
version-file: ./package.json | |
version-path: version | |
fallback-version: ${{ inputs.version || '1.0.0' }} | |
config-file-path: .github/changelog/config.js | |
pre-commit: .github/changelog/pre_commit_hook.js | |
pre-changelog-generation: .github/changelog/pre_changelog_hook.js | |
create-summary: true | |
env: | |
VERSION: ${{ inputs.version }} | |
NODE_PATH: ${{ github.workspace }}/node_modules | |
- name: Update latest major version tag | |
uses: actions/github-script@v7 | |
with: | |
github-token: ${{ secrets.COMMIT_TOKEN }} | |
script: | | |
const semver = require('semver'); | |
const fs = require('fs/promises'); | |
const package = JSON.parse(await fs.readFile('package.json', 'utf8')); | |
const version = `v${semver.coerce(package.version).major}`; | |
await exec.exec('git', ['push', '--signed=if-asked', 'origin', `:refs/tags/${version}`]); | |
await exec.exec('git', ['tag', '-sfa', version, '-m', version]); | |
await exec.exec('git', ['push', '--signed=if-asked', 'origin', '--tags']); | |
return true; | |
- name: Update latest tag | |
run: | | |
git push --signed=if-asked origin :refs/tags/latest | |
git tag -sfa latest -m latest | |
git push --signed=if-asked origin --tags | |
- name: Create GitHub release | |
uses: ncipollo/release-action@v1 | |
with: | |
token: ${{ github.token }} | |
tag: ${{ steps.changelog.outputs.tag }} | |
body: ${{ steps.changelog.outputs.changelog }} |