mirror of
https://github.com/enso-org/enso.git
synced 2024-12-18 14:01:37 +03:00
496 lines
15 KiB
JavaScript
496 lines
15 KiB
JavaScript
/// GitHub workflow generator. We are using custom generator in order to re-use sub-steps. This
|
|
/// may not be needed anymore after this feature request will be resolved by GitHub:
|
|
/// https://github.community/t/support-for-yaml-anchors/16128
|
|
|
|
const fss = require('fs')
|
|
const path = require('path')
|
|
const paths = require('./paths')
|
|
const yaml = require('js-yaml')
|
|
|
|
// =================
|
|
// === Constants ===
|
|
// =================
|
|
|
|
const NODE_VERSION = '14.15.0'
|
|
const WASM_PACK_VERSION = '0.9.1'
|
|
const FLAG_NO_CHANGELOG_NEEDED = '[ci no changelog needed]'
|
|
const FLAG_FORCE_CI_BUILD = '[ci build]'
|
|
|
|
const LINUX_RUNNER_GITHUB_HOSTED = ['ubuntu-latest']
|
|
const MACOS_RUNNER_GITHUB_HOSTED = ['macOS-latest']
|
|
const WINDOWS_RUNNER_GITHUB_HOSTED = ['windows-latest']
|
|
|
|
// =============
|
|
// === Utils ===
|
|
// =============
|
|
|
|
function cached_linux_runner(cache_label) {
|
|
return ['Linux', cache_label]
|
|
}
|
|
|
|
function job(runners, name, steps, cfg) {
|
|
if (!cfg) {
|
|
cfg = {}
|
|
}
|
|
return {
|
|
name: name,
|
|
'runs-on': '${{ matrix.runner }}',
|
|
strategy: {
|
|
matrix: {
|
|
runner: runners,
|
|
},
|
|
'fail-fast': false,
|
|
},
|
|
// WARNING!
|
|
// Do not update to `checkout@v2` because it is broken:
|
|
// https://github.com/actions/checkout/issues/438
|
|
steps: list({ uses: 'actions/checkout@v1', with: { clean: false } }, ...steps),
|
|
...cfg,
|
|
}
|
|
}
|
|
|
|
function job_on_macos(...args) {
|
|
return job([MACOS_RUNNER_GITHUB_HOSTED], ...args)
|
|
}
|
|
|
|
function job_on_linux_cached(cache_label, ...args) {
|
|
return job([cached_linux_runner(cache_label)], ...args)
|
|
}
|
|
|
|
function job_on_linux(...args) {
|
|
return job([LINUX_RUNNER_GITHUB_HOSTED], [], ...args)
|
|
}
|
|
|
|
function job_on_ubuntu_18_04(...args) {
|
|
return job(['ubuntu-18.04'], ...args)
|
|
}
|
|
|
|
function list(...args) {
|
|
let out = []
|
|
for (let arg of args) {
|
|
if (Array.isArray(arg)) {
|
|
out.push(...arg)
|
|
} else {
|
|
out.push(arg)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// ============
|
|
// === Info ===
|
|
// ============
|
|
|
|
dumpGitHubContext = {
|
|
name: 'Dump GitHub context',
|
|
env: {
|
|
GITHUB_CONTEXT: '${{ toJson(github) }}',
|
|
},
|
|
run: 'echo "$GITHUB_CONTEXT"',
|
|
}
|
|
|
|
// ====================
|
|
// === Dependencies ===
|
|
// ====================
|
|
|
|
let installNode = {
|
|
name: 'Install Node',
|
|
uses: 'actions/setup-node@v1',
|
|
with: {
|
|
'node-version': NODE_VERSION,
|
|
},
|
|
}
|
|
|
|
// Install fixed version to avoid upgrading to a breaking version.
|
|
// Should be removed once this has a better solution as described here:
|
|
// https://github.com/enso-org/ide/issues/1772
|
|
let installTypeScript = {
|
|
name: 'Install TypeScript',
|
|
run: 'npm install -g ts-node@10.1.0',
|
|
}
|
|
|
|
function installWasmPackOn(name, sys, pkg) {
|
|
return {
|
|
name: `Install wasm-pack (${name})`,
|
|
env: {
|
|
WASMPACKURL: `https://github.com/rustwasm/wasm-pack/releases/download/v${WASM_PACK_VERSION}`,
|
|
WASMPACKDIR: `wasm-pack-v${WASM_PACK_VERSION}-x86_64-${pkg}`,
|
|
},
|
|
run: `
|
|
curl -L "$WASMPACKURL/$WASMPACKDIR.tar.gz" | tar -xz -C .
|
|
mv $WASMPACKDIR/wasm-pack ~/.cargo/bin
|
|
rm -r $WASMPACKDIR`,
|
|
shell: 'bash',
|
|
if: `startsWith(matrix.os,'${sys}')`,
|
|
}
|
|
}
|
|
|
|
let installWasmPackOnMacOS = installWasmPackOn('macOS', 'macOS', 'apple-darwin')
|
|
let installWasmPackOnWindows = installWasmPackOn('Windows', 'windows', 'pc-windows-msvc')
|
|
let installWasmPackOnLinux = installWasmPackOn('Linux', 'ubuntu', 'unknown-linux-musl')
|
|
|
|
// We could use cargo install wasm-pack, but that takes 3.5 minutes compared to few seconds.
|
|
let installWasmPack = [installWasmPackOnMacOS, installWasmPackOnWindows, installWasmPackOnLinux]
|
|
|
|
const installJava = {
|
|
uses: 'actions/setup-java@v2',
|
|
with: {
|
|
distribution: 'adopt',
|
|
'java-version': '11',
|
|
},
|
|
}
|
|
|
|
// =============================
|
|
// === Build, Lint, and Test ===
|
|
// =============================
|
|
|
|
let buildPackage = {
|
|
name: 'Build Package',
|
|
run: 'node ./run dist --no-rust --skip-version-validation',
|
|
shell: 'bash',
|
|
env: {
|
|
CSC_LINK: '${{secrets.APPLE_CODE_SIGNING_CERT}}',
|
|
CSC_KEY_PASSWORD: '${{secrets.APPLE_CODE_SIGNING_CERT_PASSWORD}}',
|
|
CSC_IDENTITY_AUTO_DISCOVERY: true,
|
|
APPLEID: '${{secrets.APPLE_NOTARIZATION_USERNAME}}',
|
|
APPLEIDPASS: '${{secrets.APPLE_NOTARIZATION_PASSWORD}}',
|
|
FIREBASE_API_KEY: '${{secrets.FIREBASE_API_KEY}}',
|
|
WIN_CSC_LINK: '${{secrets.MICROSOFT_CODE_SIGNING_CERT}}',
|
|
WIN_CSC_KEY_PASSWORD: '${{secrets.MICROSOFT_CODE_SIGNING_CERT_PASSWORD}}',
|
|
},
|
|
}
|
|
|
|
let lintRust = {
|
|
name: 'Lint Rust sources',
|
|
run: 'node ./run lint --skip-version-validation',
|
|
}
|
|
|
|
let testNoWASM = {
|
|
name: 'Run tests (no WASM)',
|
|
run: 'node ./run test --no-wasm --skip-version-validation',
|
|
}
|
|
|
|
let testWASM = {
|
|
name: 'Run tests (WASM)',
|
|
run: 'node ./run test --no-native --skip-version-validation',
|
|
}
|
|
|
|
let buildWASM = {
|
|
name: 'Build WASM',
|
|
run: 'node ./run build --no-js --skip-version-validation',
|
|
}
|
|
|
|
let uploadContentLinux = {
|
|
name: `Upload Content Artifacts`,
|
|
uses: 'actions/upload-artifact@v2',
|
|
with: {
|
|
name: 'content',
|
|
path: `dist/content`,
|
|
},
|
|
if: `runner.os == 'Linux'`,
|
|
}
|
|
|
|
let uploadWASM = {
|
|
name: `Upload IDE WASM artifacts`,
|
|
uses: 'actions/upload-artifact@v2',
|
|
with: {
|
|
name: 'ide-wasm',
|
|
path: `dist/wasm`,
|
|
},
|
|
}
|
|
|
|
let downloadWASM = {
|
|
name: `Download IDE WASM artifacts`,
|
|
uses: 'actions/download-artifact@v2',
|
|
with: {
|
|
name: 'ide-wasm',
|
|
path: `dist/wasm`,
|
|
},
|
|
}
|
|
|
|
// =================
|
|
// === Artifacts ===
|
|
// =================
|
|
|
|
function uploadArtifactsFor(name, ext, os) {
|
|
return {
|
|
name: `Upload Artifacts (${name}, ${ext})`,
|
|
uses: 'actions/upload-artifact@v1',
|
|
with: {
|
|
name: `enso-${os}-\${{fromJson(steps.changelog.outputs.content).version}}.${ext}`,
|
|
path: `dist/client/enso-${os}-\${{fromJson(steps.changelog.outputs.content).version}}.${ext}`,
|
|
},
|
|
if: `runner.os == '${name}'`,
|
|
}
|
|
}
|
|
|
|
function uploadBinArtifactsWithChecksumsFor(name, ext, os) {
|
|
return [uploadArtifactsFor(name, ext, os), uploadArtifactsFor(name, ext + '.sha256', os)]
|
|
}
|
|
|
|
uploadBinArtifactsForMacOS = uploadBinArtifactsWithChecksumsFor('macOS', 'dmg', 'mac')
|
|
uploadBinArtifactsForLinux = uploadBinArtifactsWithChecksumsFor('Linux', 'AppImage', 'linux')
|
|
uploadBinArtifactsForWindows = uploadBinArtifactsWithChecksumsFor('Windows', 'exe', 'win')
|
|
|
|
let downloadArtifacts = {
|
|
name: 'Download artifacts',
|
|
uses: 'actions/download-artifact@v2',
|
|
with: {
|
|
path: 'artifacts',
|
|
},
|
|
}
|
|
|
|
// ===========
|
|
// === Git ===
|
|
// ===========
|
|
|
|
/// Gets a space-separated list of changed files between this commit and the `develop` branch.
|
|
let getListOfChangedFiles = {
|
|
name: 'Get list of changed files',
|
|
id: 'changed_files',
|
|
run: `
|
|
list=\`git diff --name-only origin/\${{github.base_ref}} HEAD | tr '\\n' ' '\`
|
|
echo $list
|
|
echo "::set-output name=list::'$list'"`,
|
|
shell: 'bash',
|
|
if: `github.base_ref == 'develop' || github.base_ref == 'unstable' || github.base_ref == 'stable'`,
|
|
}
|
|
|
|
// =================
|
|
// === Changelog ===
|
|
// =================
|
|
|
|
let getCurrentReleaseChangelogInfo = {
|
|
name: 'Read changelog info',
|
|
id: 'changelog',
|
|
run: `
|
|
node ./run ci-gen --skip-version-validation
|
|
content=\`cat CURRENT_RELEASE_CHANGELOG.json\`
|
|
echo "::set-output name=content::$content"`,
|
|
shell: 'bash',
|
|
}
|
|
|
|
let assertChangelogWasUpdated = [
|
|
getListOfChangedFiles,
|
|
{
|
|
name: 'Assert if CHANGELOG.md was updated (on pull request)',
|
|
run: `if [[ \${{ contains(steps.changed_files.outputs.list,'CHANGELOG.md') || contains(github.event.head_commit.message,'${FLAG_NO_CHANGELOG_NEEDED}') || contains(github.event.pull_request.body,'${FLAG_NO_CHANGELOG_NEEDED}') }} == false ]]; then exit 1; fi`,
|
|
if: `github.base_ref == 'develop' || github.base_ref == 'unstable' || github.base_ref == 'stable'`,
|
|
},
|
|
]
|
|
|
|
// ======================
|
|
// === GitHub Release ===
|
|
// ======================
|
|
|
|
let uploadGitHubRelease = [
|
|
{
|
|
name: `Upload GitHub Release`,
|
|
uses: 'softprops/action-gh-release@v1',
|
|
env: {
|
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}',
|
|
},
|
|
with: {
|
|
files: 'artifacts/**/enso-*',
|
|
name: 'Enso ${{fromJson(steps.changelog.outputs.content).version}}',
|
|
tag_name: 'v${{fromJson(steps.changelog.outputs.content).version}}',
|
|
body: '${{fromJson(steps.changelog.outputs.content).body}}',
|
|
prerelease: '${{fromJson(steps.changelog.outputs.content).prerelease}}',
|
|
draft: true,
|
|
},
|
|
},
|
|
]
|
|
|
|
// ===================
|
|
// === CDN Release ===
|
|
// ===================
|
|
|
|
prepareAwsSessionCDN = {
|
|
shell: 'bash',
|
|
run: `
|
|
aws configure --profile s3-upload <<-EOF > /dev/null 2>&1
|
|
\${{ secrets.ARTEFACT_S3_ACCESS_KEY_ID }}
|
|
\${{ secrets.ARTEFACT_S3_SECRET_ACCESS_KEY }}
|
|
us-west-1
|
|
text
|
|
EOF`,
|
|
}
|
|
|
|
function uploadToCDN(...names) {
|
|
const actions = []
|
|
for (let name of names) {
|
|
const action = {
|
|
name: `Upload '${name}' to CDN`,
|
|
shell: 'bash',
|
|
run:
|
|
`aws s3 cp ./artifacts/content/assets/${name} ` +
|
|
`s3://ensocdn/ide/\${{fromJson(steps.changelog.outputs.content).version}}/${name} --profile ` +
|
|
`s3-upload --acl public-read`,
|
|
}
|
|
if (name.endsWith('.gz')) {
|
|
action.run += ' --content-encoding gzip'
|
|
}
|
|
if (name.endsWith('.wasm')) {
|
|
action.run += " --content-type 'application/wasm'"
|
|
}
|
|
actions.push(action)
|
|
}
|
|
return actions
|
|
}
|
|
|
|
// ==================
|
|
// === Assertions ===
|
|
// ==================
|
|
|
|
let assertVersionUnstable = {
|
|
name: 'Assert Version Unstable',
|
|
run: 'node ./run assert-version-unstable --skip-version-validation',
|
|
if: `github.ref == 'refs/heads/unstable' || github.base_ref == 'unstable'`,
|
|
}
|
|
|
|
let assertVersionStable = {
|
|
name: 'Assert Version Stable',
|
|
run: 'node ./run assert-version-stable --skip-version-validation',
|
|
if: `github.ref == 'refs/heads/stable' || github.base_ref == 'stable'`,
|
|
}
|
|
|
|
let assertReleaseDoNotExists = [
|
|
{
|
|
id: 'checkCurrentReleaseTag',
|
|
uses: 'mukunku/tag-exists-action@v1.0.0',
|
|
env: {
|
|
GITHUB_TOKEN: '${{ secrets.GITHUB_TOKEN }}',
|
|
},
|
|
with: {
|
|
tag: 'v${{fromJson(steps.changelog.outputs.content).version}}',
|
|
},
|
|
},
|
|
{
|
|
name: 'Fail if release already exists',
|
|
run: 'if [[ ${{ steps.checkCurrentReleaseTag.outputs.exists }} == true ]]; then exit 1; fi',
|
|
if: `github.base_ref == 'unstable' || github.base_ref == 'stable'`,
|
|
},
|
|
]
|
|
|
|
assertNoSquashCommitForRelease = {
|
|
name: `Fail if squash commit to the 'unstable' or the 'stable' branch.`,
|
|
run: 'if [[ "${{ github.base_ref }}" == "unstable" || "${{ github.base_ref }}" == "stable" ]]; then exit 1; fi',
|
|
}
|
|
|
|
let assertions = list(
|
|
assertVersionUnstable,
|
|
assertVersionStable,
|
|
assertReleaseDoNotExists,
|
|
assertChangelogWasUpdated,
|
|
assertNoSquashCommitForRelease
|
|
)
|
|
|
|
// ===============
|
|
// === Workflow ===
|
|
// ===============
|
|
|
|
/// Make a release only if it was a push to 'unstable' or 'stable'. Even if it was a pull request
|
|
/// FROM these branches, the `github.ref` will be different.
|
|
let releaseCondition = `github.ref == 'refs/heads/unstable' || github.ref == 'refs/heads/stable'`
|
|
|
|
let workflow = {
|
|
name: 'GUI CI',
|
|
on: {
|
|
push: {
|
|
branches: ['develop', 'unstable', 'stable'],
|
|
},
|
|
pull_request: {},
|
|
workflow_dispatch: {},
|
|
},
|
|
jobs: {
|
|
info: job_on_macos('Build Info', [dumpGitHubContext]),
|
|
version_assertions: job_on_macos('Assertions', [
|
|
getCurrentReleaseChangelogInfo,
|
|
assertions,
|
|
]),
|
|
lint: job_on_linux_cached('linter', 'Linter', [installNode, installTypeScript, lintRust]),
|
|
test: job_on_linux_cached('test_native', 'Native Tests', [
|
|
installNode,
|
|
installTypeScript,
|
|
testNoWASM,
|
|
]),
|
|
'wasm-test': job_on_linux_cached('test_wasm', 'WASM Tests', [
|
|
installNode,
|
|
installTypeScript,
|
|
installWasmPack,
|
|
testWASM,
|
|
]),
|
|
build_wasm: job_on_linux_cached('build_wasm', 'Build WASM', [
|
|
installNode,
|
|
installTypeScript,
|
|
installWasmPack,
|
|
installJava,
|
|
buildWASM,
|
|
uploadWASM,
|
|
]),
|
|
package: job(
|
|
[
|
|
MACOS_RUNNER_GITHUB_HOSTED,
|
|
WINDOWS_RUNNER_GITHUB_HOSTED,
|
|
cached_linux_runner('package'),
|
|
],
|
|
'Build package',
|
|
[
|
|
getCurrentReleaseChangelogInfo,
|
|
installNode,
|
|
installTypeScript,
|
|
installWasmPack,
|
|
installJava,
|
|
|
|
downloadWASM,
|
|
buildPackage,
|
|
uploadContentLinux,
|
|
uploadBinArtifactsForMacOS,
|
|
uploadBinArtifactsForWindows,
|
|
uploadBinArtifactsForLinux,
|
|
],
|
|
{ needs: ['build_wasm'] }
|
|
),
|
|
release_to_github: job_on_macos(
|
|
'GitHub Release',
|
|
[
|
|
downloadArtifacts,
|
|
getCurrentReleaseChangelogInfo,
|
|
// This assertion is checked earlier, but we should double-check it in case several
|
|
// CI jobs wil be run on this branch and a release was created when this workflow was
|
|
// running.
|
|
assertReleaseDoNotExists,
|
|
uploadGitHubRelease,
|
|
],
|
|
{ if: releaseCondition, needs: ['version_assertions', 'lint', 'test', 'package'] }
|
|
),
|
|
release_to_cdn: job_on_ubuntu_18_04(
|
|
'CDN Release',
|
|
[
|
|
downloadArtifacts,
|
|
getCurrentReleaseChangelogInfo,
|
|
prepareAwsSessionCDN,
|
|
uploadToCDN('index.js.gz', 'style.css', 'ide.wasm', 'wasm_imports.js.gz'),
|
|
],
|
|
{ if: releaseCondition, needs: ['version_assertions', 'lint', 'test', 'package'] }
|
|
),
|
|
},
|
|
}
|
|
|
|
let header = `# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
# DO NOT CHANGE THIS FILE. IT WAS GENERATED FROM 'build/workflow.js'. READ DOCS THERE TO LEARN MORE.
|
|
# !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
|
`
|
|
|
|
/// Generates a new GitHub workflow file (in .github/workflow/...).
|
|
function generate() {
|
|
let workflow_script = header + '\n' + yaml.dump(workflow, { noRefs: true, quotingType: '"' })
|
|
fss.writeFileSync(path.join(paths.github.workflows, 'gui.yml'), workflow_script)
|
|
}
|
|
|
|
// ===============
|
|
// === Exports ===
|
|
// ===============
|
|
|
|
module.exports = { generate }
|