mirror of
https://github.com/enso-org/enso.git
synced 2024-08-16 16:30:33 +03:00
Release process (#3909)
This commit is contained in:
parent
d9cdb32121
commit
4de8e44871
743
.github/workflows/nightly.yml
vendored
743
.github/workflows/nightly.yml
vendored
@ -4,742 +4,11 @@ on:
|
||||
- cron: 0 5 * * 2-6
|
||||
workflow_dispatch: {}
|
||||
jobs:
|
||||
enso-build-cli-ci-gen-draft-release-linux:
|
||||
name: Create release draft
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- id: prepare
|
||||
run: ./run release create-draft
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
outputs:
|
||||
ENSO_RELEASE_ID: ${{ steps.prepare.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ steps.prepare.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-job-build-wasm-linux:
|
||||
name: Build GUI (WASM) (linux)
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run --upload-artifacts ${{ runner.os == 'Linux' }} wasm build
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
enso-build-cli-ci-gen-job-deploy-gui-linux:
|
||||
name: Upload GUI to S3 (linux)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-upload-ide-linux
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run release deploy-gui
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ARTEFACT_S3_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_S3_SECRET_ACCESS_KEY }}
|
||||
GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
enso-build-cli-ci-gen-job-deploy-runtime-linux:
|
||||
name: Upload Runtime to ECR (linux)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
- enso-build-cli-ci-gen-job-upload-backend-linux
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run release deploy-runtime
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ECR_PUSH_RUNTIME_ACCESS_KEY_ID }}
|
||||
AWS_DEFAULT_REGION: eu-west-1
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ECR_PUSH_RUNTIME_SECRET_ACCESS_KEY }}
|
||||
ENSO_BUILD_ECR_REPOSITORY: runtime
|
||||
GITHUB_TOKEN: ${{ secrets.CI_PRIVATE_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-job-upload-backend-linux:
|
||||
name: Upload Backend (linux)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run backend upload
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-job-upload-backend-macos:
|
||||
name: Upload Backend (macos)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
runs-on:
|
||||
- macos-latest
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run backend upload
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-job-upload-backend-windows:
|
||||
name: Upload Backend (windows)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Windows
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run backend upload
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-publish-release-linux:
|
||||
name: Publish release (linux)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
- enso-build-cli-ci-gen-job-deploy-gui-linux
|
||||
- enso-build-cli-ci-gen-job-deploy-runtime-linux
|
||||
- enso-build-cli-ci-gen-upload-ide-linux
|
||||
- enso-build-cli-ci-gen-upload-ide-macos
|
||||
- enso-build-cli-ci-gen-upload-ide-windows
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run release publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ARTEFACT_S3_ACCESS_KEY_ID }}
|
||||
AWS_REGION: us-west-1
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_S3_SECRET_ACCESS_KEY }}
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-upload-ide-linux:
|
||||
name: Build IDE (linux)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
- enso-build-cli-ci-gen-job-build-wasm-linux
|
||||
- enso-build-cli-ci-gen-job-upload-backend-linux
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-upload-ide-macos:
|
||||
name: Build IDE (macos)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
- enso-build-cli-ci-gen-job-build-wasm-linux
|
||||
- enso-build-cli-ci-gen-job-upload-backend-macos
|
||||
runs-on:
|
||||
- macos-latest
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}
|
||||
env:
|
||||
APPLEID: ${{ secrets.APPLE_NOTARIZATION_USERNAME }}
|
||||
APPLEIDPASS: ${{ secrets.APPLE_NOTARIZATION_PASSWORD }}
|
||||
CSC_IDENTITY_AUTO_DISCOVERY: "true"
|
||||
CSC_KEY_PASSWORD: ${{ secrets.APPLE_CODE_SIGNING_CERT_PASSWORD }}
|
||||
CSC_LINK: ${{ secrets.APPLE_CODE_SIGNING_CERT }}
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
enso-build-cli-ci-gen-upload-ide-windows:
|
||||
name: Build IDE (windows)
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-draft-release-linux
|
||||
- enso-build-cli-ci-gen-job-build-wasm-linux
|
||||
- enso-build-cli-ci-gen-job-upload-backend-windows
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Windows
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- run: ./run ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
WIN_CSC_KEY_PASSWORD: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT_PASSWORD }}
|
||||
WIN_CSC_LINK: ${{ secrets.MICROSOFT_CODE_SIGNING_CERT }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
promote-nightly:
|
||||
name: Promote nightly
|
||||
uses: ./.github/workflows/promote.yml
|
||||
with:
|
||||
designator: nightly
|
||||
secrets: inherit
|
||||
env:
|
||||
ENSO_BUILD_KIND: nightly
|
||||
ENSO_BUILD_SKIP_VERSION_CHECK: "true"
|
||||
RUST_BACKTRACE: full
|
||||
concurrency: release
|
||||
|
97
.github/workflows/promote.yml
vendored
Normal file
97
.github/workflows/promote.yml
vendored
Normal file
@ -0,0 +1,97 @@
|
||||
name: Generate a new version
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
designator:
|
||||
description: What kind of release should be promoted.
|
||||
required: true
|
||||
type: choice
|
||||
options:
|
||||
- stable
|
||||
- patch
|
||||
- rc
|
||||
- nightly
|
||||
workflow_call:
|
||||
inputs:
|
||||
designator:
|
||||
description: What kind of release should be promoted.
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
enso-build-cli-ci-gen-promote-release-job-linux:
|
||||
name: Promote release (linux)
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
- engine
|
||||
steps:
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Setup conda (GH runners only)
|
||||
uses: s-weigand/setup-conda@v1.0.5
|
||||
with:
|
||||
update-conda: false
|
||||
conda-channels: anaconda, conda-forge
|
||||
- if: startsWith(runner.name, 'GitHub Actions') || startsWith(runner.name, 'Hosted Agent')
|
||||
name: Installing wasm-pack
|
||||
uses: jetli/wasm-pack-action@v0.3.0
|
||||
with:
|
||||
version: v0.10.2
|
||||
- name: Expose Artifact API and context information.
|
||||
uses: actions/github-script@v6
|
||||
with:
|
||||
script: "\n core.exportVariable(\"ACTIONS_RUNTIME_TOKEN\", process.env[\"ACTIONS_RUNTIME_TOKEN\"])\n core.exportVariable(\"ACTIONS_RUNTIME_URL\", process.env[\"ACTIONS_RUNTIME_URL\"])\n core.exportVariable(\"GITHUB_RETENTION_DAYS\", process.env[\"GITHUB_RETENTION_DAYS\"])\n console.log(context)\n "
|
||||
- if: runner.os == 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (Windows)
|
||||
run: '"c:\Program Files\Git\bin\bash.exe" -c "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"'
|
||||
shell: cmd
|
||||
- if: runner.os != 'Windows'
|
||||
name: Workaround for https://github.com/actions/checkout/issues/590 (non-Windows)
|
||||
run: "git checkout -f $(git -c user.name=x -c user.email=x@x commit-tree $(git hash-object -t tree /dev/null) < /dev/null) || :"
|
||||
shell: bash
|
||||
- name: Checking out the repository
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
clean: false
|
||||
submodules: recursive
|
||||
- name: Build Script Setup
|
||||
run: ./run --help
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: "contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean before
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- id: promote
|
||||
run: ./run release promote ${{ inputs.designator }}
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
- if: failure() && runner.os == 'Windows'
|
||||
name: List files if failed (Windows)
|
||||
run: Get-ChildItem -Force -Recurse
|
||||
- if: failure() && runner.os != 'Windows'
|
||||
name: List files if failed (non-Windows)
|
||||
run: ls -lAR
|
||||
- if: "always() && always() && contains(github.event.pull_request.labels.*.name, 'CI: Clean build required')"
|
||||
name: Clean after
|
||||
run: ./run git-clean
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
outputs:
|
||||
ENSO_EDITION: ${{ steps.promote.outputs.ENSO_EDITION }}
|
||||
ENSO_RELEASE_MODE: ${{ steps.promote.outputs.ENSO_RELEASE_MODE }}
|
||||
ENSO_VERSION: ${{ steps.promote.outputs.ENSO_VERSION }}
|
||||
release:
|
||||
name: Release
|
||||
needs:
|
||||
- enso-build-cli-ci-gen-promote-release-job-linux
|
||||
uses: ./.github/workflows/release.yml
|
||||
with:
|
||||
version: ${{ needs.enso-build-cli-ci-gen-promote-release-job-linux.outputs.ENSO_VERSION }}
|
||||
secrets: inherit
|
||||
env:
|
||||
ENSO_BUILD_SKIP_VERSION_CHECK: "true"
|
76
.github/workflows/release-publish-edition.yml
vendored
76
.github/workflows/release-publish-edition.yml
vendored
@ -1,76 +0,0 @@
|
||||
name: Publish Edition to S3
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
env:
|
||||
# Please ensure that this is in sync with graalVersion in build.sbt
|
||||
graalVersion: 22.3.0
|
||||
# Please ensure that this is in sync with javaVersion in build.sbt
|
||||
javaVersion: 11
|
||||
# Please ensure that this is in sync with project/build.properties
|
||||
sbtVersion: 1.5.2
|
||||
# Please ensure that this is in sync with NIGHTLIES_TO_KEEP in nightly.yml
|
||||
NIGHTLIES_TO_KEEP: 20
|
||||
|
||||
concurrency: "releases"
|
||||
|
||||
jobs:
|
||||
update-editions-on-s3:
|
||||
name: Update Editions on S3
|
||||
runs-on: ubuntu-18.04
|
||||
timeout-minutes: 30
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v2
|
||||
with:
|
||||
path: repo
|
||||
- name: Setup GraalVM Environment
|
||||
uses: ayltai/setup-graalvm@v1
|
||||
with:
|
||||
graalvm-version: ${{ env.graalVersion }}
|
||||
java-version: ${{ env.javaVersion }}
|
||||
native-image: true
|
||||
- name: Set Up SBT
|
||||
shell: bash
|
||||
run: |
|
||||
curl -fsSL -o sbt.tgz https://github.com/sbt/sbt/releases/download/v${{env.sbtVersion}}/sbt-${{env.sbtVersion}}.tgz
|
||||
tar -xzf sbt.tgz
|
||||
echo $GITHUB_WORKSPACE/sbt/bin/ >> $GITHUB_PATH
|
||||
|
||||
# Caches
|
||||
- name: Cache SBT
|
||||
uses: actions/cache@v2
|
||||
with:
|
||||
path: |
|
||||
~/.sbt
|
||||
~/.ivy2/cache
|
||||
~/.cache
|
||||
key: ${{ runner.os }}-sbt-${{ hashFiles('**build.sbt') }}
|
||||
restore-keys: ${{ runner.os }}-sbt-
|
||||
|
||||
- name: Prepare AWS Session
|
||||
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 }}
|
||||
eu-central-1
|
||||
text
|
||||
EOF
|
||||
|
||||
- name: Update the Editions Bucket
|
||||
shell: bash
|
||||
working-directory: repo
|
||||
run: sbt edition-uploader/run
|
||||
|
||||
- name: Teardown AWS Session
|
||||
shell: bash
|
||||
run: |
|
||||
aws configure --profile s3-upload <<-EOF > /dev/null 2>&1
|
||||
null
|
||||
null
|
||||
null
|
||||
text
|
||||
EOF
|
52
.github/workflows/release.yml
vendored
52
.github/workflows/release.yml
vendored
@ -1,9 +1,20 @@
|
||||
name: Release Candidate
|
||||
name: Release
|
||||
on:
|
||||
workflow_dispatch: {}
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
version:
|
||||
description: What version number this release should get.
|
||||
required: true
|
||||
type: string
|
||||
workflow_call:
|
||||
inputs:
|
||||
version:
|
||||
description: What version number this release should get.
|
||||
required: true
|
||||
type: string
|
||||
jobs:
|
||||
enso-build-cli-ci-gen-draft-release-linux:
|
||||
name: Create release draft
|
||||
name: Create a release draft.
|
||||
runs-on:
|
||||
- self-hosted
|
||||
- Linux
|
||||
@ -248,8 +259,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-job-upload-backend-linux:
|
||||
name: Upload Backend (linux)
|
||||
needs:
|
||||
@ -315,8 +326,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-job-upload-backend-macos:
|
||||
name: Upload Backend (macos)
|
||||
needs:
|
||||
@ -380,8 +391,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-job-upload-backend-windows:
|
||||
name: Upload Backend (windows)
|
||||
needs:
|
||||
@ -447,8 +458,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-publish-release-linux:
|
||||
name: Publish release (linux)
|
||||
needs:
|
||||
@ -522,8 +533,8 @@ jobs:
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.ARTEFACT_S3_ACCESS_KEY_ID }}
|
||||
AWS_REGION: us-west-1
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_S3_SECRET_ACCESS_KEY }}
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-upload-ide-linux:
|
||||
name: Build IDE (linux)
|
||||
needs:
|
||||
@ -591,8 +602,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-upload-ide-macos:
|
||||
name: Build IDE (macos)
|
||||
needs:
|
||||
@ -663,8 +674,8 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
enso-build-cli-ci-gen-upload-ide-windows:
|
||||
name: Build IDE (windows)
|
||||
needs:
|
||||
@ -734,10 +745,11 @@ jobs:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
timeout-minutes: 360
|
||||
env:
|
||||
ENSO_RELEASE_ID: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID}}
|
||||
ENSO_VERSION: ${{needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION}}
|
||||
ENSO_RELEASE_ID: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_RELEASE_ID }}
|
||||
ENSO_VERSION: ${{ needs.enso-build-cli-ci-gen-draft-release-linux.outputs.ENSO_VERSION }}
|
||||
env:
|
||||
ENSO_BUILD_KIND: rc
|
||||
ENSO_BUILD_SKIP_VERSION_CHECK: "true"
|
||||
ENSO_EDITION: ${{ inputs.version }}
|
||||
ENSO_VERSION: ${{ inputs.version }}
|
||||
RUST_BACKTRACE: full
|
||||
concurrency: release
|
||||
|
75
Cargo.lock
generated
75
Cargo.lock
generated
@ -1865,6 +1865,7 @@ dependencies = [
|
||||
"futures 0.3.25",
|
||||
"futures-util",
|
||||
"glob",
|
||||
"handlebars",
|
||||
"heck",
|
||||
"humantime 2.1.0",
|
||||
"ide-ci",
|
||||
@ -3566,6 +3567,20 @@ version = "1.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7"
|
||||
|
||||
[[package]]
|
||||
name = "handlebars"
|
||||
version = "4.3.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "433e4ab33f1213cdc25b5fa45c76881240cfe79284cf2b395e8b9e312a30a2fd"
|
||||
dependencies = [
|
||||
"log 0.4.17",
|
||||
"pest",
|
||||
"pest_derive",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"thiserror",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashbrown"
|
||||
version = "0.12.3"
|
||||
@ -3897,6 +3912,7 @@ dependencies = [
|
||||
"lazy_static",
|
||||
"log 0.4.17",
|
||||
"mime 0.3.16",
|
||||
"multimap",
|
||||
"new_mime_guess",
|
||||
"nix",
|
||||
"octocrab",
|
||||
@ -4641,6 +4657,15 @@ version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bba551d6d795f74a01767577ea8339560bf0a65354e0417b7e915ed608443d46"
|
||||
|
||||
[[package]]
|
||||
name = "multimap"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e5ce46fe64a9d73be07dcbe690a38ce1b293be448fd8ce1e6c1b8062c9f72c6a"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "multipart"
|
||||
version = "0.18.0"
|
||||
@ -5226,6 +5251,50 @@ version = "2.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
|
||||
|
||||
[[package]]
|
||||
name = "pest"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a528564cc62c19a7acac4d81e01f39e53e25e17b934878f4c6d25cc2836e62f8"
|
||||
dependencies = [
|
||||
"thiserror",
|
||||
"ucd-trie",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_derive"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d5fd9bc6500181952d34bd0b2b0163a54d794227b498be0b7afa7698d0a7b18f"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_generator",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_generator"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d2610d5ac5156217b4ff8e46ddcef7cdf44b273da2ac5bca2ecbfa86a330e7c4"
|
||||
dependencies = [
|
||||
"pest",
|
||||
"pest_meta",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pest_meta"
|
||||
version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "824749bf7e21dd66b36fbe26b3f45c713879cccd4a009a917ab8e045ca8246fe"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
"pest",
|
||||
"sha1 0.10.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pin-project"
|
||||
version = "1.0.12"
|
||||
@ -7118,6 +7187,12 @@ version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dcf81ac59edc17cc8697ff311e8f5ef2d99fcbd9817b34cec66f90b6c3dfd987"
|
||||
|
||||
[[package]]
|
||||
name = "ucd-trie"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9e79c4d996edb816c91e4308506774452e55e95c3c9de07b6729e17e15a5ef81"
|
||||
|
||||
[[package]]
|
||||
name = "unicase"
|
||||
version = "1.4.2"
|
||||
|
@ -9,7 +9,7 @@ use std::any::type_name;
|
||||
|
||||
/// An equivalent of standard's library `std::str::FromStr` trait, but with nice error messages.
|
||||
pub trait FromString: Sized {
|
||||
/// Parse a string into a value of this type. See: `std::str::FromStr::from_str`.
|
||||
/// Parse a string into a value of this type. See: [`std::str::FromStr::from_str`].
|
||||
fn from_str(s: &str) -> Result<Self>;
|
||||
|
||||
/// Parse a string into a value of this type and then convert it to `R`.
|
||||
|
@ -46,6 +46,16 @@ pub trait TryFutureExt: TryFuture {
|
||||
self.map_err(|err| err.into().context(context)).boxed()
|
||||
}
|
||||
|
||||
/// Convert the error type of this future to [`anyhow::Error`] and add the context.
|
||||
fn with_context<F, C>(self, context: F) -> BoxFuture<'static, Result<Self::Ok>>
|
||||
where
|
||||
Self: Sized + Send + 'static,
|
||||
std::result::Result<Self::Ok, Self::Error>: anyhow::Context<Self::Ok, Self::Error>,
|
||||
F: FnOnce() -> C + Send + Sync + 'static,
|
||||
C: Display + Send + Sync + 'static, {
|
||||
self.into_future().map(|res| res.with_context(context)).boxed()
|
||||
}
|
||||
|
||||
/// Convert the error type of this future to [`anyhow::Error`].
|
||||
fn anyhow_err(self) -> MapErr<Self, fn(Self::Error) -> anyhow::Error>
|
||||
where
|
||||
|
@ -22,9 +22,11 @@ pub trait PathExt: AsRef<Path> {
|
||||
|
||||
/// Appends a new extension to the file.
|
||||
///
|
||||
/// Does not try to replace previous extension, unlike `set_extension`.
|
||||
/// Does not try to replace previous extension, unlike [`PathBuf::set_extension`].
|
||||
/// Does nothing when given extension string is empty.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use enso_build_base::extensions::path::PathExt;
|
||||
/// use std::path::PathBuf;
|
||||
@ -141,7 +143,20 @@ pub trait PathExt: AsRef<Path> {
|
||||
|
||||
impl<T: AsRef<Path>> PathExt for T {}
|
||||
|
||||
/// A method that displays a value using `Display` trait.
|
||||
/// A method that outputs a path to a formatter using [`Path::display`].
|
||||
///
|
||||
/// This is useful in combination with macros like `Derivative`, as demonstrated in the example
|
||||
/// below.
|
||||
///
|
||||
/// # Example
|
||||
/// ```ignore
|
||||
/// #[derive(Derivative)]
|
||||
/// #[derivative(Debug)]
|
||||
/// pub struct Foo {
|
||||
/// #[derivative(Debug(format_with = "display_fmt"))]
|
||||
/// path: PathBuf,
|
||||
/// }
|
||||
/// ```
|
||||
pub fn display_fmt(path: &Path, f: &mut Formatter) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&path.display(), f)
|
||||
}
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use futures::future::Either;
|
||||
use std::future::Ready;
|
||||
|
||||
|
||||
|
||||
/// Extension methods for [`Result`].
|
||||
@ -11,9 +14,9 @@ pub trait ResultExt<T, E>: Sized {
|
||||
fn map_async<'a, T2, F, Fut>(
|
||||
self,
|
||||
f: F,
|
||||
) -> futures::future::Either<
|
||||
) -> Either<
|
||||
futures::future::Map<Fut, fn(T2) -> std::result::Result<T2, E>>,
|
||||
std::future::Ready<std::result::Result<T2, E>>,
|
||||
Ready<std::result::Result<T2, E>>,
|
||||
>
|
||||
where
|
||||
F: FnOnce(T) -> Fut,
|
||||
@ -24,7 +27,7 @@ pub trait ResultExt<T, E>: Sized {
|
||||
fn and_then_async<'a, T2, E2, F, Fut>(
|
||||
self,
|
||||
f: F,
|
||||
) -> futures::future::Either<Fut, std::future::Ready<std::result::Result<T2, E2>>>
|
||||
) -> Either<Fut, Ready<std::result::Result<T2, E2>>>
|
||||
where
|
||||
F: FnOnce(T) -> Fut,
|
||||
Fut: Future<Output = std::result::Result<T2, E2>> + Send + 'a,
|
||||
@ -33,6 +36,13 @@ pub trait ResultExt<T, E>: Sized {
|
||||
E2: Send + 'a;
|
||||
|
||||
|
||||
/// Executes another future if this is an error. The error value is passed to a closure to
|
||||
/// create this subsequent future.
|
||||
fn or_else_async<F, Fut>(self, f: F) -> Either<Ready<Self>, futures::future::IntoFuture<Fut>>
|
||||
where
|
||||
F: FnOnce(E) -> Fut,
|
||||
Fut: TryFuture<Ok = T, Error = E>;
|
||||
|
||||
/// Convert the error type to [`anyhow::Error`].
|
||||
///
|
||||
/// If there are additional context-specific information, use [`context`] instead.
|
||||
@ -42,10 +52,7 @@ pub trait ResultExt<T, E>: Sized {
|
||||
/// Convert the `[Result]<[Future]>` to `Future<Result>`.
|
||||
fn flatten_fut(
|
||||
self,
|
||||
) -> futures::future::Either<
|
||||
std::future::Ready<std::result::Result<T::Ok, T::Error>>,
|
||||
futures::future::IntoFuture<T>,
|
||||
>
|
||||
) -> Either<Ready<std::result::Result<T::Ok, T::Error>>, futures::future::IntoFuture<T>>
|
||||
where T: TryFuture<Error: From<E>>;
|
||||
}
|
||||
|
||||
@ -53,9 +60,9 @@ impl<T, E> ResultExt<T, E> for std::result::Result<T, E> {
|
||||
fn map_async<'a, T2, F, Fut>(
|
||||
self,
|
||||
f: F,
|
||||
) -> futures::future::Either<
|
||||
) -> Either<
|
||||
futures::future::Map<Fut, fn(T2) -> std::result::Result<T2, E>>,
|
||||
std::future::Ready<std::result::Result<T2, E>>,
|
||||
Ready<std::result::Result<T2, E>>,
|
||||
>
|
||||
where
|
||||
F: FnOnce(T) -> Fut,
|
||||
@ -67,11 +74,24 @@ impl<T, E> ResultExt<T, E> for std::result::Result<T, E> {
|
||||
}
|
||||
}
|
||||
|
||||
fn or_else_async<'a, F, Fut>(
|
||||
self,
|
||||
f: F,
|
||||
) -> Either<Ready<Self>, futures::future::IntoFuture<Fut>>
|
||||
where
|
||||
F: FnOnce(E) -> Fut,
|
||||
Fut: TryFuture<Ok = T, Error = E>,
|
||||
{
|
||||
match self {
|
||||
Ok(v) => ready(Ok(v)).left_future(),
|
||||
Err(e) => f(e).into_future().right_future(),
|
||||
}
|
||||
}
|
||||
|
||||
fn and_then_async<'a, T2, E2, F, Fut>(
|
||||
self,
|
||||
f: F,
|
||||
) -> futures::future::Either<Fut, std::future::Ready<std::result::Result<T2, E2>>>
|
||||
) -> Either<Fut, Ready<std::result::Result<T2, E2>>>
|
||||
where
|
||||
F: FnOnce(T) -> Fut,
|
||||
Fut: Future<Output = std::result::Result<T2, E2>> + Send + 'a,
|
||||
@ -92,10 +112,7 @@ impl<T, E> ResultExt<T, E> for std::result::Result<T, E> {
|
||||
|
||||
fn flatten_fut(
|
||||
self,
|
||||
) -> futures::future::Either<
|
||||
std::future::Ready<std::result::Result<T::Ok, T::Error>>,
|
||||
futures::future::IntoFuture<T>,
|
||||
>
|
||||
) -> Either<Ready<std::result::Result<T::Ok, T::Error>>, futures::future::IntoFuture<T>>
|
||||
where T: TryFuture<Error: From<E>> {
|
||||
match self {
|
||||
Ok(fut) => fut.into_future().right_future(),
|
||||
|
@ -29,7 +29,7 @@ futures = "0.3.17"
|
||||
futures-util = "0.3.17"
|
||||
glob = "0.3.0"
|
||||
#graphql_client = "0.10.0"
|
||||
#handlebars = "4.2.1"
|
||||
handlebars = "4.3.5"
|
||||
heck = "0.4.0"
|
||||
humantime = "2.1.0"
|
||||
enso-build-base = { path = "../base" }
|
||||
|
@ -8,6 +8,7 @@
|
||||
changelog.yml:
|
||||
gui.yml:
|
||||
nightly.yml:
|
||||
promote.yml:
|
||||
release.yml:
|
||||
scala-new.yml:
|
||||
app/:
|
||||
|
71
build/build/release-body.md
Normal file
71
build/build/release-body.md
Normal file
@ -0,0 +1,71 @@
|
||||
{{!-- This is a template of the markdown text that is used as a release body.
|
||||
|
||||
Please see [this](src/release.rs) for the code that uses this template and
|
||||
available placeholders.
|
||||
|
||||
The template itself is written in [Handlebars](https://handlebarsjs.com/).
|
||||
|
||||
--}}
|
||||
|
||||
# Download
|
||||
|
||||
## Enso IDE
|
||||
|
||||
Enso IDE is the main product of the Enso project. The packages are stand-alone,
|
||||
they contain both GUI and the backend.
|
||||
|
||||
Download links:
|
||||
|
||||
- [Linux]({{download_prefix}}/enso-linux-{{version}}.AppImage) (AppImage);
|
||||
- [macOS]({{download_prefix}}/enso-mac-{{version}}.dmg) (DMG);
|
||||
- [Windows]({{download_prefix}}/enso-win-{{version}}.exe) (Installer
|
||||
Executable).
|
||||
|
||||
This is the recommended download for most users.
|
||||
|
||||
## Enso Engine
|
||||
|
||||
If you are interested in using Enso Engine command line tools only, download the
|
||||
Enso Engine bundle.
|
||||
|
||||
Download links:
|
||||
|
||||
- [Linux]({{download_prefix}}/enso-bundle-{{version}}-linux-amd64.tar.gz);
|
||||
- [macOS]({{download_prefix}}/enso-bundle-{{version}}-macos-amd64.tar.gz);
|
||||
- [Windows]({{download_prefix}}/enso-bundle-{{version}}-windows-amd64.zip).
|
||||
|
||||
These are archives containing the
|
||||
[Enso portable distribution](https://enso.org/docs/developer/enso/distribution/distribution.html#portable-enso-distribution-layout).
|
||||
User is responsible for setting up the environment variables and adding the
|
||||
`bin` directory to the `PATH`.
|
||||
|
||||
Note that these distributions do not allow you to use the Enso IDE.
|
||||
|
||||
It is recommended only for advanced users, who want to just try the compiler
|
||||
CLI.
|
||||
|
||||
# Changelog
|
||||
|
||||
{{changelog}}
|
||||
|
||||
# Anonymous Data Collection
|
||||
|
||||
Please note that this release collects anonymous usage data which will be used
|
||||
to improve Enso and prepare it for a stable release. We will switch to opt-in
|
||||
data collection in stable version releases. The usage data will not contain your
|
||||
code (expressions above nodes), however, reported errors may contain brief
|
||||
snippets of out of context code that specifically leads to the error, like "the
|
||||
method 'foo' does not exist on Number". The following data will be collected:
|
||||
|
||||
- Session length.
|
||||
- Graph editing events (node creation, deletion, position change, connect,
|
||||
disconnect, collapse, edit start, edit end). This will not include any
|
||||
information about node expressions used.
|
||||
- Navigation events (camera movement, scope change).
|
||||
- Visualization events (visualization open, close, switch). This will not
|
||||
include any information about the displayed data nor the rendered
|
||||
visualization itself.
|
||||
- Project management events (project open, close, rename).
|
||||
- Errors (IDE crashes, WASM panics, Project Manager errors, Language Server
|
||||
errors, Compiler errors).
|
||||
- Performance statistics (minimum, maximum, average GUI refresh rate).
|
@ -4,7 +4,6 @@ use crate::paths::TargetTriple;
|
||||
|
||||
use derivative::Derivative;
|
||||
use ide_ci::github;
|
||||
use ide_ci::programs::git;
|
||||
use octocrab::models::repos::Release;
|
||||
use octocrab::models::ReleaseId;
|
||||
|
||||
@ -29,12 +28,16 @@ pub struct BuildContext {
|
||||
}
|
||||
|
||||
impl BuildContext {
|
||||
/// Get the current commit hash.
|
||||
///
|
||||
/// If there is GITHUB_SHA environment variable, it is used. Otherwise, the current commit hash
|
||||
/// is determined using `git` command.
|
||||
pub fn commit(&self) -> BoxFuture<'static, Result<String>> {
|
||||
let root = self.repo_root.to_path_buf();
|
||||
let git = self.git();
|
||||
async move {
|
||||
match ide_ci::actions::env::GITHUB_SHA.get() {
|
||||
Ok(commit) => Ok(commit),
|
||||
Err(_e) => git::Context::new(root).await?.head_hash().await,
|
||||
Err(_e) => git.await?.head_hash().await,
|
||||
}
|
||||
}
|
||||
.boxed()
|
||||
@ -57,11 +60,9 @@ impl BuildContext {
|
||||
tag => repository.find_release_by_text(tag).await?,
|
||||
}
|
||||
};
|
||||
Ok(release)
|
||||
Result::Ok(release)
|
||||
}
|
||||
.map_err(move |e: anyhow::Error| {
|
||||
e.context(format!("Failed to resolve release designator `{designator_cp}`."))
|
||||
})
|
||||
.with_context(move || format!("Failed to resolve release designator `{}`", designator_cp))
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
@ -64,7 +64,7 @@ impl Bundle for ProjectManager {
|
||||
}
|
||||
}
|
||||
|
||||
// #[context("Placing a GraalVM package under {}", target_directory.as_ref().display())]
|
||||
#[context("Placing a GraalVM package under {}", target_directory.as_ref().display())]
|
||||
pub async fn place_graal_under(target_directory: impl AsRef<Path>) -> Result {
|
||||
let graal_path = {
|
||||
let java_home = JAVA_HOME.get()?;
|
||||
|
@ -96,7 +96,7 @@ impl RunContext {
|
||||
let prepare_simple_library_server = {
|
||||
if self.config.test_scala {
|
||||
let simple_server_path = &self.paths.repo_root.tools.simple_library_server;
|
||||
ide_ci::programs::git::Context::new(simple_server_path)
|
||||
ide_ci::programs::git::new(simple_server_path)
|
||||
.await?
|
||||
.cmd()?
|
||||
.clean()
|
||||
|
@ -112,34 +112,53 @@ pub fn retrieve_github_access_token() -> Result<String> {
|
||||
fn get_token_from_file() -> Result<String> {
|
||||
let path =
|
||||
dirs::home_dir().context("Failed to locate home directory.")?.join("GITHUB_TOKEN");
|
||||
debug!("Looking for GitHub token in the file {}", path.display());
|
||||
let content = ide_ci::fs::read_to_string(path)?;
|
||||
Ok(content.trim().into())
|
||||
}
|
||||
|
||||
ide_ci::env::expect_var("GITHUB_TOKEN")
|
||||
.inspect(|_| debug!("Will use GITHUB_TOKEN environment variable."))
|
||||
.inspect_err(|e| debug!("Failed to retrieve GitHub authentication from environment: {e}"))
|
||||
.or_else(|_| get_token_from_file())
|
||||
}
|
||||
|
||||
#[context("Failed to setup GitHub API client.")]
|
||||
pub async fn setup_octocrab() -> Result<Octocrab> {
|
||||
let mut builder = octocrab::OctocrabBuilder::new();
|
||||
if let Ok(access_token) = retrieve_github_access_token() {
|
||||
builder = builder.personal_token(access_token);
|
||||
let octocrab = builder.build()?;
|
||||
match octocrab.ratelimit().get().await {
|
||||
Ok(rate) => info!(
|
||||
"GitHub API rate limit: {}/{}.",
|
||||
rate.resources.core.used, rate.resources.core.limit
|
||||
),
|
||||
Err(e) => bail!(
|
||||
"Failed to get rate limit info: {e}. GitHub Personal Access Token might be invalid."
|
||||
),
|
||||
}
|
||||
Ok(octocrab)
|
||||
let builder = octocrab::OctocrabBuilder::new();
|
||||
let octocrab = if let Ok(access_token) = retrieve_github_access_token() {
|
||||
let octocrab = builder.personal_token(access_token).build()?;
|
||||
let username = octocrab
|
||||
.current()
|
||||
.user()
|
||||
.await
|
||||
.inspect_err(|e| warn!("Failed to retrieve GitHub username: {e}"))
|
||||
.map_or_else(|_| "N/A".to_string(), |user| user.login);
|
||||
info!("Using GitHub API with personal access token. Authenticated as {username}.",);
|
||||
octocrab
|
||||
} else {
|
||||
builder.build().anyhow_err()
|
||||
info!("No GitHub Personal Access Token found. Will use anonymous API access.");
|
||||
warn!(
|
||||
"Anonymous GitHub API access is rate-limited. If you are experiencing issues, please \
|
||||
set the GITHUB_TOKEN environment variable."
|
||||
);
|
||||
warn!(
|
||||
"Additionally some APIs may not be available to anonymous users. This primarily \
|
||||
pertains the release-related APIs."
|
||||
);
|
||||
builder.build()?
|
||||
};
|
||||
|
||||
match octocrab.ratelimit().get().await {
|
||||
Ok(rate) => info!(
|
||||
"GitHub API rate limit: {}/{}.",
|
||||
rate.resources.core.used, rate.resources.core.limit
|
||||
),
|
||||
Err(e) => bail!(
|
||||
"Failed to get rate limit info: {e}. GitHub Personal Access Token might be invalid."
|
||||
),
|
||||
}
|
||||
Ok(octocrab)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -16,6 +16,7 @@ use ide_ci::actions::artifacts;
|
||||
use ide_ci::cache;
|
||||
use ide_ci::cache::Cache;
|
||||
use ide_ci::ok_ready_boxed;
|
||||
use ide_ci::programs::git;
|
||||
use octocrab::models::repos::Asset;
|
||||
|
||||
|
||||
@ -80,8 +81,8 @@ impl<T> PlainArtifact<T> {
|
||||
pub struct Context {
|
||||
/// GitHub API client.
|
||||
///
|
||||
/// If authorized, it will count API rate limits against our identity and allow operations like
|
||||
/// managing releases or downloading CI run artifacts.
|
||||
/// If authenticated, it will count API rate limits against our identity and allow operations
|
||||
/// like managing releases or downloading CI run artifacts.
|
||||
#[derivative(Debug = "ignore")]
|
||||
pub octocrab: Octocrab,
|
||||
|
||||
@ -99,6 +100,14 @@ pub struct Context {
|
||||
pub repo_root: crate::paths::generated::RepoRoot,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Get a `git` program handle for the repository.
|
||||
pub fn git(&self) -> impl Future<Output = Result<git::Context>> + 'static {
|
||||
let root = self.repo_root.to_path_buf();
|
||||
git::new(root)
|
||||
}
|
||||
}
|
||||
|
||||
/// Build targets, like GUI or Project Manager.
|
||||
///
|
||||
/// Built target generates artifacts that can be stored as a release asset or CI run artifacts.
|
||||
|
@ -1,5 +1,8 @@
|
||||
//! Support code for managing Enso releases.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::changelog::Changelog;
|
||||
use crate::context::BuildContext;
|
||||
use crate::paths::generated;
|
||||
use crate::paths::TargetTriple;
|
||||
@ -10,34 +13,119 @@ use crate::project::Gui;
|
||||
use crate::project::IsTarget;
|
||||
use crate::source::ExternalSource;
|
||||
use crate::source::FetchTargetJob;
|
||||
use crate::version;
|
||||
use crate::version::promote::Designation;
|
||||
use crate::version::Versions;
|
||||
|
||||
use ide_ci::github;
|
||||
use ide_ci::github::release::ReleaseHandle;
|
||||
use ide_ci::io::web::handle_error_response;
|
||||
use ide_ci::programs::Docker;
|
||||
use ide_ci::programs::SevenZip;
|
||||
use octocrab::models::repos::Release;
|
||||
use octocrab::params::repos::Reference;
|
||||
use reqwest::Response;
|
||||
use serde_json::json;
|
||||
use tempfile::tempdir;
|
||||
|
||||
|
||||
|
||||
pub fn release_from_env(context: &BuildContext) -> Result<ReleaseHandle> {
|
||||
let release_id = crate::env::ENSO_RELEASE_ID.get()?;
|
||||
Ok(ReleaseHandle::new(&context.octocrab, context.remote_repo.clone(), release_id))
|
||||
/// Get the prefix of URL of the release's asset in GitHub.
|
||||
///
|
||||
/// By joining it with the asset name, we can get the URL of the asset.
|
||||
///
|
||||
/// ```
|
||||
/// # use enso_build::prelude::*;
|
||||
/// # use enso_build::release::download_asset_prefix;
|
||||
/// # use ide_ci::github::RepoRef;
|
||||
/// let repo = RepoRef::new("enso-org", "enso");
|
||||
/// let version = Version::from_str("2020.1.1").unwrap();
|
||||
/// let prefix = download_asset_prefix(&repo, &version);
|
||||
/// assert_eq!(prefix, "https://github.com/enso-org/enso/releases/download/2020.1.1");
|
||||
/// ```
|
||||
pub fn download_asset_prefix(repo: &impl IsRepo, version: &Version) -> String {
|
||||
format!("https://github.com/{repo}/releases/download/{version}",)
|
||||
}
|
||||
|
||||
pub async fn draft_a_new_release(context: &BuildContext) -> Result<Release> {
|
||||
let versions = &context.triple.versions;
|
||||
let commit = ide_ci::actions::env::GITHUB_SHA.get()?;
|
||||
/// Generate placeholders for the release notes.
|
||||
pub fn release_body_placeholders(
|
||||
context: &BuildContext,
|
||||
) -> Result<BTreeMap<&'static str, serde_json::Value>> {
|
||||
let mut ret = BTreeMap::new();
|
||||
ret.insert(
|
||||
"is_stable",
|
||||
(!version::Kind::deduce(&context.triple.versions.version)?.is_prerelease()).into(),
|
||||
);
|
||||
ret.insert("version", context.triple.versions.version.to_string().into());
|
||||
ret.insert("edition", context.triple.versions.edition_name().into());
|
||||
ret.insert("repo", serde_json::to_value(&context.remote_repo)?);
|
||||
ret.insert(
|
||||
"download_prefix",
|
||||
format!(
|
||||
"https://github.com/{}/releases/download/{}",
|
||||
context.remote_repo, context.triple.versions.version
|
||||
)
|
||||
.into(),
|
||||
);
|
||||
|
||||
// Generate the release notes.
|
||||
let changelog_contents = ide_ci::fs::read_to_string(&context.repo_root.changelog_md)?;
|
||||
let latest_changelog_body =
|
||||
crate::changelog::Changelog(&changelog_contents).top_release_notes()?;
|
||||
let latest_changelog_body = Changelog(&changelog_contents).top_release_notes()?;
|
||||
ret.insert("changelog", latest_changelog_body.contents.into());
|
||||
Ok(ret)
|
||||
}
|
||||
|
||||
/// Generate the release notes.
|
||||
///
|
||||
/// They are generated from the template file `release-body.md` by replacing the placeholders.
|
||||
pub async fn generate_release_body(context: &BuildContext) -> Result<String> {
|
||||
let available_placeholders = release_body_placeholders(context)?;
|
||||
|
||||
let handlebars = handlebars::Handlebars::new();
|
||||
let template = include_str!("../release-body.md");
|
||||
let body = handlebars.render_template(template, &available_placeholders)?;
|
||||
Ok(body)
|
||||
}
|
||||
|
||||
/// Create a new release draft on GitHub.
|
||||
///
|
||||
/// As it is a draft, it will not be visible to the public until it is published.
|
||||
#[context("Failed to draft a new release {} from the commit {}.", context.triple.versions.tag(),
|
||||
commit)]
|
||||
pub async fn draft_a_new_release(context: &BuildContext, commit: &str) -> Result<Release> {
|
||||
let versions = &context.triple.versions;
|
||||
let tag = versions.tag();
|
||||
|
||||
// We don't require being run from a git repository context, but if we do happen to be, then
|
||||
// the local HEAD is required to be the same as the commit we read from environment.
|
||||
if let Ok(git) = context.git().await {
|
||||
let head_hash = git.head_hash().await?;
|
||||
ensure!(
|
||||
head_hash == commit,
|
||||
"Local HEAD {} is not the same as the commit {} we were ordered to build.",
|
||||
head_hash,
|
||||
commit
|
||||
);
|
||||
}
|
||||
|
||||
// Make sure that this version has not been released yet.
|
||||
let repo = &context.remote_repo_handle();
|
||||
if let Ok(colliding_release) = repo.find_release_by_tag(&tag).await {
|
||||
ensure!(colliding_release.draft, "Release {} has already been published.", tag);
|
||||
warn!(
|
||||
"Release {tag} has already been drafted: {}. Creating a new one.",
|
||||
colliding_release.html_url
|
||||
);
|
||||
}
|
||||
|
||||
// Check if the remote repository contains a conflicting tag.
|
||||
let colliding_remote_tag = repo.get_ref(&Reference::Tag(tag.clone())).await;
|
||||
if let Ok(colliding_remote_tag) = colliding_remote_tag {
|
||||
bail!("Tag {} already exists on remote as {:?}.", tag, colliding_remote_tag.object);
|
||||
}
|
||||
|
||||
let is_prerelease = version::Kind::deduce(&versions.version)?.is_prerelease();
|
||||
|
||||
debug!("Preparing release {} for commit {}", versions.version, commit);
|
||||
|
||||
let release = context
|
||||
.remote_repo_handle()
|
||||
.repos()
|
||||
@ -45,8 +133,8 @@ pub async fn draft_a_new_release(context: &BuildContext) -> Result<Release> {
|
||||
.create(&versions.tag())
|
||||
.target_commitish(&commit)
|
||||
.name(&versions.pretty_name())
|
||||
.body(&latest_changelog_body.contents)
|
||||
.prerelease(true)
|
||||
.body(&generate_release_body(context).await?)
|
||||
.prerelease(is_prerelease)
|
||||
.draft(true)
|
||||
.send()
|
||||
.await?;
|
||||
@ -55,6 +143,9 @@ pub async fn draft_a_new_release(context: &BuildContext) -> Result<Release> {
|
||||
Ok(release)
|
||||
}
|
||||
|
||||
/// Publish a previously drafted release and update edition file in the cloud.
|
||||
///
|
||||
/// Requires the release ID to be set in the environment variable `ENSO_RELEASE_ID`.
|
||||
pub async fn publish_release(context: &BuildContext) -> Result {
|
||||
let remote_repo = context.remote_repo_handle();
|
||||
let BuildContext { inner: project::Context { .. }, triple, .. } = context;
|
||||
@ -210,6 +301,7 @@ pub async fn put_files_gzipping(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Deploy the built GUI (frontend) to the cloud.
|
||||
#[context("Failed to notify the cloud about GUI upload in version {}.", version)]
|
||||
pub async fn notify_cloud_about_gui(version: &Version) -> Result<Response> {
|
||||
let body = json!({
|
||||
@ -217,7 +309,7 @@ pub async fn notify_cloud_about_gui(version: &Version) -> Result<Response> {
|
||||
"versionType": "Ide"
|
||||
});
|
||||
let response = reqwest::Client::new()
|
||||
.post("https://nngmxi3zr4.execute-api.eu-west-1.amazonaws.com/versions")
|
||||
.post("https://7aqkn3tnbc.execute-api.eu-west-1.amazonaws.com/versions")
|
||||
.header("x-enso-organization-id", "org-2BqGX0q2yCdONdmx3Om1MVZzmv3")
|
||||
.header("Content-Type", "application/json")
|
||||
.json(&body)
|
||||
@ -227,6 +319,28 @@ pub async fn notify_cloud_about_gui(version: &Version) -> Result<Response> {
|
||||
handle_error_response(response).await
|
||||
}
|
||||
|
||||
/// Generate a new version number of a requested kind.
|
||||
pub async fn resolve_version_designation(
|
||||
context: &BuildContext,
|
||||
designation: Designation,
|
||||
) -> Result<Version> {
|
||||
let git = context.git().await?;
|
||||
let version_set = version::promote::Releases::from_remote(&git).await?;
|
||||
version_set.generate_version(designation)
|
||||
}
|
||||
|
||||
/// Generate a new version and make it visible to CI.
|
||||
///
|
||||
/// Sets `ENSO_VERSION`, `ENSO_EDITION` and `ENSO_RELEASE_MODE` environment variables.
|
||||
#[instrument]
|
||||
pub async fn promote_release(context: &BuildContext, version_designation: Designation) -> Result {
|
||||
let version = resolve_version_designation(context, version_designation).await?;
|
||||
let versions = Versions::new(version);
|
||||
versions.publish().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -13,7 +13,7 @@ pub mod cloud;
|
||||
/// repository.
|
||||
///
|
||||
/// Current heuristic is: contains Cargo workspace root and SBT build configuration file.
|
||||
#[instrument(fields(path = %path.as_ref().display()), ret)]
|
||||
#[instrument(level = "trace", fields(path = %path.as_ref().display()), ret)]
|
||||
pub fn looks_like_enso_repository_root(path: impl AsRef<Path>) -> bool {
|
||||
(move || -> Result<bool> {
|
||||
let cargo_toml = path.as_ref().join("Cargo.toml");
|
||||
|
@ -1,6 +1,5 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use ide_ci::github;
|
||||
use ide_ci::github::RepoRef;
|
||||
|
||||
|
||||
@ -13,10 +12,11 @@ pub const BUILD_IMAGE_WORKFLOW: &str = "build-image.yaml";
|
||||
/// https://github.com/enso-org/cloud-v2/blob/main/.github/workflows/build-image.yaml#L4
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Deserialize, Serialize)]
|
||||
pub struct BuildImageInput<T> {
|
||||
runtime_version: T,
|
||||
pub runtime_version: T,
|
||||
}
|
||||
|
||||
impl<T> BuildImageInput<T> {
|
||||
/// Create a new model of "build-image" workflow input.
|
||||
pub fn new(runtime_version: T) -> Self {
|
||||
Self { runtime_version }
|
||||
}
|
||||
@ -28,19 +28,7 @@ impl<T> BuildImageInput<T> {
|
||||
#[instrument(fields(%version), skip(octocrab))]
|
||||
pub async fn build_image_workflow_dispatch_input(octocrab: &Octocrab, version: &Version) -> Result {
|
||||
let repo = CLOUD_REPO.handle(octocrab);
|
||||
|
||||
// We want to call our workflow on the default branch.
|
||||
let default_branch = repo.get().await?.default_branch.with_context(|| {
|
||||
format!(
|
||||
"Failed to get the default branch of the {} repository. Missing field: `default_branch`.",
|
||||
CLOUD_REPO
|
||||
)
|
||||
})?;
|
||||
|
||||
debug!("Will invoke on ref: '{}'", default_branch);
|
||||
let input = BuildImageInput::new(version);
|
||||
info!("Dispatching the cloud workflow to build the image.");
|
||||
github::workflow::dispatch(&repo, BUILD_IMAGE_WORKFLOW, default_branch, &input).await
|
||||
repo.dispatch_workflow(BUILD_IMAGE_WORKFLOW, &BuildImageInput::new(version.to_string())).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -1,4 +1,8 @@
|
||||
//! Code that deals with the version of Enso.
|
||||
//!
|
||||
//! Enso uses [Semantic Versioning 2.0.0](https://semver.org/). By the convention the major version
|
||||
//! number is fixed to the year of the release. The minor version number is incremented for each
|
||||
//! (backwards incompatible) release.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
@ -10,18 +14,27 @@ use ide_ci::env::new::TypedVariable;
|
||||
use ide_ci::github;
|
||||
use octocrab::models::repos::Release;
|
||||
use semver::Prerelease;
|
||||
use std::collections::BTreeSet;
|
||||
use strum::EnumIter;
|
||||
use strum::EnumString;
|
||||
use strum::IntoEnumIterator;
|
||||
use tracing::instrument;
|
||||
|
||||
|
||||
// ==============
|
||||
// === Export ===
|
||||
// ==============
|
||||
|
||||
pub mod nightly;
|
||||
pub mod promote;
|
||||
|
||||
|
||||
|
||||
// Variable that stores Enso Engine version.
|
||||
// They are also used by the SBT part of the build.
|
||||
define_env_var! {
|
||||
/// The version of Enso (shared by GUI and Engine).
|
||||
ENSO_VERSION, Version;
|
||||
|
||||
/// Edition name for the build.
|
||||
///
|
||||
/// By convention, this is the same as the version.
|
||||
@ -31,18 +44,14 @@ define_env_var! {
|
||||
ENSO_RELEASE_MODE, bool;
|
||||
}
|
||||
|
||||
|
||||
pub const LOCAL_BUILD_PREFIX: &str = "dev";
|
||||
pub const NIGHTLY_BUILD_PREFIX: &str = "nightly";
|
||||
pub const RC_BUILD_PREFIX: &str = "rc";
|
||||
|
||||
/// Check if the given GitHub release matches the provided kind.
|
||||
pub fn is_release_of_kind(release: &Release, kind: Kind) -> bool {
|
||||
match kind {
|
||||
Kind::Dev => release.tag_name.contains(LOCAL_BUILD_PREFIX),
|
||||
Kind::Nightly => release.tag_name.contains(NIGHTLY_BUILD_PREFIX),
|
||||
Kind::Rc => release.tag_name.contains(RC_BUILD_PREFIX),
|
||||
Kind::Stable => !release.prerelease,
|
||||
}
|
||||
matches!(release.tag_name.parse2(), Ok(version) if kind.matches(&version))
|
||||
}
|
||||
|
||||
/// List all releases in the GitHub repository that are of a given kind.
|
||||
@ -54,6 +63,7 @@ pub async fn releases_of_kind(
|
||||
}
|
||||
|
||||
/// Get the latest nightly release in the GitHub repository.
|
||||
#[context("Failed to get the latest nightly release in {repo}.")]
|
||||
pub async fn latest_nightly_release(repo: &github::repo::Handle<impl IsRepo>) -> Result<Release> {
|
||||
// TODO: this assumes that releases are returned in date order, to be confirmed
|
||||
// (but having to download all the pages to see which is latest wouldn't be nice)
|
||||
@ -110,74 +120,6 @@ impl Versions {
|
||||
Prerelease::new(LOCAL_BUILD_PREFIX).anyhow_err()
|
||||
}
|
||||
|
||||
pub async fn nightly_prerelease(
|
||||
repo: &github::repo::Handle<impl IsRepo>,
|
||||
) -> Result<Prerelease> {
|
||||
let date = chrono::Utc::now();
|
||||
let date = date.format("%F").to_string();
|
||||
|
||||
let todays_pre_text = format!("{}.{}", NIGHTLY_BUILD_PREFIX, date);
|
||||
let generate_ith = |index: u32| -> Result<Prerelease> {
|
||||
let pre = if index == 0 {
|
||||
Prerelease::from_str(&todays_pre_text)?
|
||||
} else {
|
||||
Prerelease::from_str(&format!("{}.{}", todays_pre_text, index))?
|
||||
};
|
||||
Ok(pre)
|
||||
};
|
||||
|
||||
let relevant_nightly_versions = releases_of_kind(repo, Kind::Nightly)
|
||||
.await?
|
||||
.filter_map(|release| {
|
||||
if release.tag_name.contains(&todays_pre_text) {
|
||||
let version = Version::parse(&release.tag_name).ok()?;
|
||||
Some(version.pre)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
// Generate subsequent tonight nightly subreleases, until a free one is found.
|
||||
// Should happen rarely.
|
||||
for index in 0.. {
|
||||
let pre = generate_ith(index)?;
|
||||
if !relevant_nightly_versions.contains(&pre) {
|
||||
return Ok(pre);
|
||||
}
|
||||
}
|
||||
unreachable!("After infinite loop.")
|
||||
}
|
||||
|
||||
/// Generate prerelease string for the "release candidate" release.
|
||||
///
|
||||
/// We list all the RC releases in the repository, and increment the number of the latest one.
|
||||
pub async fn rc_prerelease(
|
||||
version: &Version,
|
||||
repo: &github::repo::Handle<impl IsRepo>,
|
||||
) -> Result<Prerelease> {
|
||||
let relevant_rc_versions = releases_of_kind(repo, Kind::Rc)
|
||||
.await?
|
||||
.filter_map(|release| {
|
||||
let release_version = Version::parse(&release.tag_name).ok()?;
|
||||
let version_matches = release_version.major == version.major
|
||||
&& release_version.minor == version.minor
|
||||
&& release_version.patch == version.patch;
|
||||
version_matches.then_some(release_version.pre)
|
||||
})
|
||||
.collect::<BTreeSet<_>>();
|
||||
|
||||
// Generate subsequent RC sub-releases, until a free one is found.
|
||||
// Should happen rarely.
|
||||
for index in 0.. {
|
||||
let pre = Prerelease::from_str(&format!("{}.{}", RC_BUILD_PREFIX, index))?;
|
||||
if !relevant_rc_versions.contains(&pre) {
|
||||
return Ok(pre);
|
||||
}
|
||||
}
|
||||
unreachable!("After infinite loop.")
|
||||
}
|
||||
|
||||
/// Get a git tag that should be applied to a commit released as this version.
|
||||
pub fn tag(&self) -> String {
|
||||
self.version.to_string()
|
||||
@ -219,12 +161,13 @@ pub fn base_version(changelog_path: impl AsRef<Path>) -> Result<Version> {
|
||||
.iterate_headers()
|
||||
.map(|h| Version::find_in_text(h.text));
|
||||
|
||||
let year = current_year();
|
||||
let version = match headers.next() {
|
||||
None => generate_initial_version(),
|
||||
None => generate_initial_version(year),
|
||||
Some(Ok(top_version)) => top_version,
|
||||
Some(Err(_top_non_version_thingy)) => match headers.next() {
|
||||
Some(Ok(version)) => suggest_next_version(&version),
|
||||
None => generate_initial_version(),
|
||||
Some(Ok(version)) => suggest_next_version(&version, year),
|
||||
None => generate_initial_version(year),
|
||||
Some(Err(_)) => bail!("Two leading release headers have no version number in them."),
|
||||
},
|
||||
};
|
||||
@ -235,21 +178,66 @@ pub fn current_year() -> u64 {
|
||||
chrono::Utc::now().year() as u64
|
||||
}
|
||||
|
||||
pub fn generate_initial_version() -> Version {
|
||||
Version::new(current_year(), 1, 1)
|
||||
pub fn generate_initial_version(current_year: u64) -> Version {
|
||||
Version::new(current_year, 1, 1)
|
||||
}
|
||||
|
||||
pub fn suggest_next_version(previous: &Version) -> Version {
|
||||
let year = current_year();
|
||||
if previous.major == year {
|
||||
Version::new(year, previous.minor + 1, 1)
|
||||
pub fn suggest_next_version(previous: &Version, current_year: u64) -> Version {
|
||||
if previous.major == current_year {
|
||||
Version::new(current_year, previous.minor + 1, 1)
|
||||
} else {
|
||||
generate_initial_version()
|
||||
generate_initial_version(current_year)
|
||||
}
|
||||
}
|
||||
|
||||
/// Generate a next RC version for the given version.
|
||||
///
|
||||
/// ```
|
||||
/// use enso_build::prelude::*;
|
||||
/// use enso_build::version::increment_rc_version;
|
||||
/// let rc_version = Version::from_str("0.2.3-rc.1").unwrap();
|
||||
/// assert_eq!(increment_rc_version(&rc_version).unwrap().to_string(), "0.2.3-rc.2");
|
||||
/// ```
|
||||
pub fn increment_rc_version(version: &Version) -> Result<Version> {
|
||||
ensure!(Kind::Rc.matches(version), "Version is not an RC version: {}.", version);
|
||||
match version.pre.split('.').collect_vec().as_slice() {
|
||||
[RC_BUILD_PREFIX, index] => {
|
||||
let index = index.parse2::<u32>().context("Parsing RC index.")?;
|
||||
let pre = generate_rc_prerelease(index + 1)?;
|
||||
Ok(Version { pre, ..version.clone() })
|
||||
}
|
||||
_ => bail!("RC version has an unexpected prerelease: {}.", version),
|
||||
}
|
||||
}
|
||||
|
||||
/// Compare version core, i.e. the part that is not a prerelease/build.
|
||||
///
|
||||
/// This is used to determine if a version is a prerelease of another version.
|
||||
///
|
||||
/// # Example
|
||||
/// ```
|
||||
/// use enso_build::prelude::*;
|
||||
/// use enso_build::version::same_core_version;
|
||||
///
|
||||
/// let version = Version::from_str("1.2.3").unwrap();
|
||||
/// assert!(same_core_version(&version, &version));
|
||||
/// let version_beta = Version::from_str("1.2.3-beta").unwrap();
|
||||
/// assert!(same_core_version(&version, &version_beta));
|
||||
/// let version_build = Version::from_str("1.2.3+build").unwrap();
|
||||
/// assert!(same_core_version(&version, &version_build));
|
||||
/// let other_version = Version::from_str("1.2.4").unwrap();
|
||||
/// assert!(!same_core_version(&version, &other_version));
|
||||
/// ```
|
||||
pub fn same_core_version(a: &Version, b: &Version) -> bool {
|
||||
a.major == b.major && a.minor == b.minor && a.patch == b.patch
|
||||
}
|
||||
|
||||
pub fn generate_rc_prerelease(index: u32) -> Result<Prerelease> {
|
||||
Prerelease::from_str(&format!("{}.{}", RC_BUILD_PREFIX, index))
|
||||
}
|
||||
|
||||
#[instrument(ret)]
|
||||
pub fn versions_from_env(expected_build_kind: Option<Kind>) -> Result<Option<Versions>> {
|
||||
pub fn versions_from_env() -> Result<Option<Versions>> {
|
||||
if let Ok(version) = ENSO_VERSION.get() {
|
||||
// The currently adopted version scheme uses same string for version and edition name,
|
||||
// so we enforce it here. There are no fundamental reasons for this requirement.
|
||||
@ -261,15 +249,6 @@ pub fn versions_from_env(expected_build_kind: Option<Kind>) -> Result<Option<Ver
|
||||
ENSO_EDITION.name
|
||||
);
|
||||
}
|
||||
if let Some(expected_build_kind) = expected_build_kind {
|
||||
let found_build_kind = Kind::deduce(&version)?;
|
||||
ensure!(
|
||||
found_build_kind == expected_build_kind,
|
||||
"Build kind mismatch. Found: {}, expected: {}.",
|
||||
found_build_kind,
|
||||
expected_build_kind
|
||||
)
|
||||
}
|
||||
let versions = Versions::new(version);
|
||||
Ok(Some(versions))
|
||||
} else {
|
||||
@ -278,51 +257,19 @@ pub fn versions_from_env(expected_build_kind: Option<Kind>) -> Result<Option<Ver
|
||||
}
|
||||
|
||||
#[instrument(skip_all, ret)]
|
||||
pub async fn deduce_or_generate(
|
||||
repo: Result<&github::repo::Handle<impl IsRepo>>,
|
||||
kind: Kind,
|
||||
root_path: impl AsRef<Path>,
|
||||
) -> Result<Versions> {
|
||||
pub async fn deduce_or_generate<P, F>(release_provider: P) -> Result<Versions>
|
||||
where
|
||||
P: FnOnce() -> F,
|
||||
F: Future<Output = Result<Vec<Version>>>, {
|
||||
debug!("Deciding on version to target.");
|
||||
if let Some(versions) = versions_from_env(Some(kind))? {
|
||||
if let Some(versions) = versions_from_env()? {
|
||||
Ok(versions)
|
||||
} else {
|
||||
let changelog_path = crate::paths::root_to_changelog(&root_path);
|
||||
let base_version = base_version(&changelog_path)?;
|
||||
let version = Version {
|
||||
pre: match kind {
|
||||
Kind::Dev => Versions::local_prerelease()?,
|
||||
Kind::Nightly => Versions::nightly_prerelease(repo?).await?,
|
||||
Kind::Rc => Versions::rc_prerelease(&base_version, repo?).await?,
|
||||
Kind::Stable => todo!(), //Versions::stable(repo?).await?,
|
||||
},
|
||||
..base_version
|
||||
};
|
||||
Ok(Versions::new(version))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_nightly_test() {
|
||||
let is_nightly = |text: &str| Kind::Nightly.matches(&Version::parse(text).unwrap());
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
assert!(is_nightly("2022.1.1-nightly"));
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
|
||||
let version = Version::parse("2022.1.1-nightly.2022-06-06.3").unwrap();
|
||||
assert!(Kind::deduce(&version).contains(&Kind::Nightly));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn iii() -> Result {
|
||||
dbg!(base_version(r"H:\nbo\enso\app\gui\changelog.md")?);
|
||||
Ok(())
|
||||
let releases = release_provider().await?;
|
||||
let releases = promote::Releases::new_now(releases)?;
|
||||
let next_stable = releases.generate_version(promote::Designation::Stable)?;
|
||||
let dev_version = Version { pre: Prerelease::from_str(LOCAL_BUILD_PREFIX)?, ..next_stable };
|
||||
Ok(Versions::new(dev_version))
|
||||
}
|
||||
}
|
||||
|
||||
@ -347,22 +294,79 @@ pub enum Kind {
|
||||
}
|
||||
|
||||
impl Kind {
|
||||
pub fn prerelease_prefix(self) -> &'static str {
|
||||
/// Get the first piece of prerelease identifier that is used to designate this kind of version.
|
||||
///
|
||||
/// Returns `None` if this kind of version requires empty prerelease.
|
||||
pub fn prerelease_prefix(self) -> Option<&'static str> {
|
||||
match self {
|
||||
Kind::Dev => LOCAL_BUILD_PREFIX,
|
||||
Kind::Nightly => NIGHTLY_BUILD_PREFIX,
|
||||
Kind::Rc => RC_BUILD_PREFIX,
|
||||
Kind::Stable => "",
|
||||
Kind::Dev => Some(LOCAL_BUILD_PREFIX),
|
||||
Kind::Nightly => Some(NIGHTLY_BUILD_PREFIX),
|
||||
Kind::Rc => Some(RC_BUILD_PREFIX),
|
||||
Kind::Stable => None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Check if the given version number matches this kind.
|
||||
///
|
||||
/// ```
|
||||
/// use enso_build::version::Kind;
|
||||
/// use semver::Version;
|
||||
///
|
||||
/// assert!(Kind::Nightly.matches(&Version::parse("2022.1.1-nightly.2022.1.1").unwrap()));
|
||||
/// assert!(Kind::Nightly.matches(&Version::parse("2022.1.1-nightly").unwrap()));
|
||||
/// assert!(Kind::Rc.matches(&Version::parse("2022.1.1-rc.1").unwrap()));
|
||||
/// assert!(!Kind::Nightly.matches(&Version::parse("2022.1.1-rc.1").unwrap()));
|
||||
/// assert!(!Kind::Stable.matches(&Version::parse("2022.1.1-rc.1").unwrap()));
|
||||
/// assert!(!Kind::Stable.matches(&Version::parse("2022.1.1-nightly").unwrap()));
|
||||
/// assert!(Kind::Stable.matches(&Version::parse("2022.1.1").unwrap()));
|
||||
/// ```
|
||||
pub fn matches(self, version: &Version) -> bool {
|
||||
version.pre.as_str().starts_with(self.prerelease_prefix())
|
||||
if let Some(prefix) = self.prerelease_prefix() {
|
||||
version.pre.starts_with(prefix)
|
||||
} else {
|
||||
version.pre.is_empty()
|
||||
}
|
||||
}
|
||||
|
||||
/// Deduce the kind of the version.
|
||||
///
|
||||
/// ```
|
||||
/// use enso_build::prelude::*;
|
||||
/// use enso_build::version::Kind;
|
||||
///
|
||||
/// fn main() -> Result {
|
||||
/// assert_eq!(Kind::deduce(&Version::parse("2022.1.1-dev")?)?, Kind::Dev);
|
||||
/// assert_eq!(Kind::deduce(&Version::parse("2022.1.1-nightly.2022.1.1")?)?, Kind::Nightly);
|
||||
/// assert_eq!(Kind::deduce(&Version::parse("2022.1.1-rc.1")?)?, Kind::Rc);
|
||||
/// assert_eq!(Kind::deduce(&Version::parse("2022.1.1")?)?, Kind::Stable);
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub fn deduce(version: &Version) -> Result<Self> {
|
||||
Kind::iter()
|
||||
.find(|kind| kind.matches(version))
|
||||
.context(format!("Failed to deduce build kind for version {version}"))
|
||||
.with_context(|| format!("Failed to deduce build kind for version {version}"))
|
||||
}
|
||||
|
||||
/// Check if this is one of the prerelease kinds.
|
||||
pub fn is_prerelease(self) -> bool {
|
||||
self != Kind::Stable
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn is_nightly_test() {
|
||||
let is_nightly = |text: &str| Kind::Nightly.matches(&Version::parse(text).unwrap());
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
assert!(is_nightly("2022.1.1-nightly"));
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
assert!(is_nightly("2022.1.1-nightly.2022.1.1"));
|
||||
|
||||
let version = Version::parse("2022.1.1-nightly.2022-06-06.3").unwrap();
|
||||
assert!(Kind::deduce(&version).contains(&Kind::Nightly));
|
||||
}
|
||||
}
|
||||
|
184
build/build/src/version/nightly.rs
Normal file
184
build/build/src/version/nightly.rs
Normal file
@ -0,0 +1,184 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::version;
|
||||
|
||||
use chrono::Datelike;
|
||||
use semver::Prerelease;
|
||||
use semver::Version;
|
||||
|
||||
|
||||
|
||||
/// Parsed nightly build [prerelease](https://semver.org/#spec-item-9) piece.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct NightlyPrerelease {
|
||||
/// The date of the nightly build.
|
||||
pub date: chrono::NaiveDate,
|
||||
/// The number of the nightly build.
|
||||
///
|
||||
/// The first nightly build of the day has None, subsequent builds have Some(1), Some(2), etc.
|
||||
pub index: Option<u64>,
|
||||
}
|
||||
|
||||
impl NightlyPrerelease {
|
||||
#[allow(missing_docs)]
|
||||
pub fn new(date: chrono::NaiveDate, index: Option<u64>) -> Self {
|
||||
Self { date, index }
|
||||
}
|
||||
|
||||
/// Generate a next nightly build version for the given date.
|
||||
///
|
||||
/// ```
|
||||
/// use enso_build::version::nightly::NightlyPrerelease;
|
||||
/// let date = chrono::NaiveDate::from_ymd_opt(2020, 1, 1).unwrap();
|
||||
/// let first = NightlyPrerelease::new(date, None);
|
||||
/// let second = first.next();
|
||||
/// assert_eq!(second, NightlyPrerelease::new(date, Some(1)));
|
||||
/// let third = second.next();
|
||||
/// assert_eq!(third, NightlyPrerelease::new(date, Some(2)));
|
||||
/// ```
|
||||
pub fn next(mut self) -> Self {
|
||||
self.index = Some(self.index.unwrap_or(0) + 1);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Prerelease> for NightlyPrerelease {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
#[context("Failed to parse nightly version prerelease: `{prerelease}`.")]
|
||||
fn try_from(prerelease: &Prerelease) -> std::result::Result<Self, Self::Error> {
|
||||
let prerelease = prerelease.as_str();
|
||||
let identifiers = prerelease.split('.').collect_vec();
|
||||
ensure!(
|
||||
identifiers.first().contains(&&version::NIGHTLY_BUILD_PREFIX),
|
||||
"Not a nightly build."
|
||||
);
|
||||
ensure!(identifiers.len() == 4 || identifiers.len() == 5, "Wrong number of identifiers.");
|
||||
let year = identifiers.get(1).context("Missing year")?.parse2().context("Invalid year")?;
|
||||
let month =
|
||||
identifiers.get(2).context("Missing month")?.parse2().context("Invalid month")?;
|
||||
let day = identifiers.get(3).context("Missing day")?.parse2().context("Invalid day")?;
|
||||
let index =
|
||||
identifiers.get(4).map(|index| index.parse2()).transpose().context("Invalid index")?;
|
||||
let date = chrono::NaiveDate::from_ymd_opt(year, month, day)
|
||||
.with_context(|| format!("Invalid date: {}-{}-{}", year, month, day))?;
|
||||
Ok(Self::new(date, index))
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for NightlyPrerelease {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
let Self { date, index } = self;
|
||||
write!(
|
||||
f,
|
||||
"{}.{}.{}.{}",
|
||||
version::NIGHTLY_BUILD_PREFIX,
|
||||
date.year(),
|
||||
date.month(),
|
||||
date.day()
|
||||
)?;
|
||||
if let Some(index) = index {
|
||||
write!(f, ".{}", index)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl TryInto<Prerelease> for NightlyPrerelease {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_into(self) -> std::result::Result<Prerelease, Self::Error> {
|
||||
let as_string = self.to_string();
|
||||
Prerelease::from_str(&as_string)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Version> for NightlyPrerelease {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
#[context("Failed to parse nightly version: `{value}`.")]
|
||||
fn try_from(value: &Version) -> std::result::Result<Self, Self::Error> {
|
||||
Self::try_from(&value.pre)
|
||||
}
|
||||
}
|
||||
|
||||
/// Nightly build version.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord)]
|
||||
pub struct Nightly {
|
||||
full_version: Version,
|
||||
prerelease: NightlyPrerelease,
|
||||
}
|
||||
|
||||
impl Nightly {
|
||||
pub fn new(full_version: Version) -> anyhow::Result<Self> {
|
||||
let prerelease = NightlyPrerelease::try_from(&full_version)?;
|
||||
Ok(Self { full_version, prerelease })
|
||||
}
|
||||
|
||||
pub fn next(&self) -> Self {
|
||||
let prerelease = self.prerelease.next();
|
||||
let full_version =
|
||||
Version { pre: prerelease.try_into().unwrap(), ..self.full_version.clone() };
|
||||
Self { full_version, prerelease }
|
||||
}
|
||||
|
||||
/// Get as [`Version`].
|
||||
pub fn version(&self) -> &Version {
|
||||
&self.full_version
|
||||
}
|
||||
|
||||
/// Get the parser prerelease piece of the version.
|
||||
pub fn prerelease(&self) -> &NightlyPrerelease {
|
||||
&self.prerelease
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&Version> for Nightly {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
#[context("Failed to parse nightly version: `{value}`.")]
|
||||
fn try_from(value: &Version) -> std::result::Result<Self, Self::Error> {
|
||||
let prerelease = NightlyPrerelease::try_from(value)?;
|
||||
Ok(Self { full_version: value.clone(), prerelease })
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Nightly> for Version {
|
||||
fn from(value: Nightly) -> Self {
|
||||
value.full_version
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
#[test]
|
||||
fn parsing_nightly_prerelease() -> Result {
|
||||
let nightly = Version::from_str("2020.1.1-nightly.2020.12.31")?;
|
||||
assert_eq!(NightlyPrerelease::try_from(&nightly)?, NightlyPrerelease {
|
||||
date: chrono::NaiveDate::from_ymd_opt(2020, 12, 31).unwrap(),
|
||||
index: None,
|
||||
});
|
||||
let nightly_with_index = Version::from_str("2020.1.1-nightly.2020.12.31.1")?;
|
||||
assert_eq!(NightlyPrerelease::try_from(&nightly_with_index)?, NightlyPrerelease {
|
||||
date: chrono::NaiveDate::from_ymd_opt(2020, 12, 31).unwrap(),
|
||||
index: Some(1),
|
||||
});
|
||||
|
||||
let nightly_with_too_many_identifiers =
|
||||
Version::from_str("2020.1.1-nightly.2020.12.31.1.1")?;
|
||||
assert!(NightlyPrerelease::try_from(&nightly_with_too_many_identifiers).is_err());
|
||||
|
||||
let nightly_with_missing_year = Version::from_str("2020.1.1-nightly.12.31")?;
|
||||
assert!(NightlyPrerelease::try_from(&nightly_with_missing_year).is_err());
|
||||
|
||||
let non_nightly = Version::from_str("2020.1.1")?;
|
||||
assert!(NightlyPrerelease::try_from(&non_nightly).is_err());
|
||||
|
||||
let rc = Version::from_str("2020.1.1-rc.1")?;
|
||||
assert!(NightlyPrerelease::try_from(&rc).is_err());
|
||||
Ok(())
|
||||
}
|
||||
}
|
198
build/build/src/version/promote.rs
Normal file
198
build/build/src/version/promote.rs
Normal file
@ -0,0 +1,198 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::version;
|
||||
use crate::version::nightly::Nightly;
|
||||
use crate::version::nightly::NightlyPrerelease;
|
||||
|
||||
use chrono::Datelike;
|
||||
use ide_ci::programs::git;
|
||||
use ide_ci::programs::git::Ref;
|
||||
|
||||
|
||||
|
||||
/// Describes what kind of version should be generated.
|
||||
#[derive(
|
||||
clap::ArgEnum,
|
||||
Clone,
|
||||
Copy,
|
||||
Debug,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Hash,
|
||||
strum::EnumString,
|
||||
strum::EnumIter,
|
||||
strum::AsRefStr
|
||||
)]
|
||||
#[strum(serialize_all = "lowercase")]
|
||||
pub enum Designation {
|
||||
/// Create a new stable release.
|
||||
Stable,
|
||||
/// Create a new patch release.
|
||||
Patch,
|
||||
/// Create a new RC release.
|
||||
Rc,
|
||||
/// Create a new nightly release.
|
||||
Nightly,
|
||||
}
|
||||
|
||||
pub async fn releases_on_remote(git: &git::Context) -> Result<Vec<Version>> {
|
||||
let remote_tags = git.list_remote_tags().await?;
|
||||
Ok(remote_tags
|
||||
.into_iter()
|
||||
.filter_map(|entry| match entry.r#ref {
|
||||
Ref::Tag { name, .. } => Version::from_str(&name).ok(),
|
||||
_ => None,
|
||||
})
|
||||
.collect())
|
||||
}
|
||||
|
||||
/// List of all releases on the remote.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Releases {
|
||||
/// A list of all released versions.
|
||||
pub versions: BTreeSet<Version>,
|
||||
/// Current date.
|
||||
///
|
||||
/// It is used to determine major version in some cases, we keep it as parameter to keep the
|
||||
/// code testable. Also, defines a day for nightly releases.
|
||||
pub date: chrono::NaiveDate,
|
||||
}
|
||||
|
||||
impl Releases {
|
||||
/// Create, while using the current date.
|
||||
pub fn new_now(versions: impl IntoIterator<Item = Version>) -> Result<Self> {
|
||||
let date = chrono::Utc::now().naive_utc().date();
|
||||
Self::new(versions, date)
|
||||
}
|
||||
|
||||
/// Create with a given date.
|
||||
pub fn new(
|
||||
versions: impl IntoIterator<Item = Version>,
|
||||
date: chrono::NaiveDate,
|
||||
) -> Result<Self> {
|
||||
let versions: BTreeSet<_> = versions.into_iter().collect();
|
||||
for version in &versions {
|
||||
ensure!(
|
||||
version.major <= (date.year() as u64),
|
||||
"Found version from the future: {}.",
|
||||
version
|
||||
);
|
||||
}
|
||||
Ok(Self { versions, date })
|
||||
}
|
||||
|
||||
/// Generate, based on all releases on the remote.
|
||||
pub async fn from_remote(git: &git::Context) -> Result<Self> {
|
||||
let date = chrono::Utc::now();
|
||||
let versions = releases_on_remote(git).await?;
|
||||
Self::new(versions, date.date_naive())
|
||||
}
|
||||
|
||||
/// Iterates over releases of a given [`version::Kind`].
|
||||
pub fn of_kind(&self, kind: version::Kind) -> impl Iterator<Item = &Version> {
|
||||
self.versions.iter().filter(move |version| kind.matches(version))
|
||||
}
|
||||
|
||||
/// Get the latest release of a given [`version::Kind`].
|
||||
pub fn latest_of_kind(&self, kind: version::Kind) -> Option<&Version> {
|
||||
self.of_kind(kind).max()
|
||||
}
|
||||
|
||||
/// Generate a version for next release of a given [`Designation`].
|
||||
pub fn generate_version(&self, designation: Designation) -> Result<Version> {
|
||||
let latest_stable = self.latest_of_kind(version::Kind::Stable);
|
||||
let next_stable = if let Some(latest_stable) = latest_stable {
|
||||
version::suggest_next_version(latest_stable, self.date.year() as u64)
|
||||
} else {
|
||||
version::generate_initial_version(self.date.year() as u64)
|
||||
};
|
||||
|
||||
let ret = match designation {
|
||||
Designation::Stable => next_stable,
|
||||
Designation::Patch => {
|
||||
let latest_stable = latest_stable.with_context(|| {
|
||||
"No stable releases found. There must be some stable release before creating \
|
||||
a patch release."
|
||||
})?;
|
||||
Version::new(latest_stable.major, latest_stable.minor, latest_stable.patch + 1)
|
||||
}
|
||||
Designation::Rc => {
|
||||
let last_relevant_rc = self
|
||||
.of_kind(version::Kind::Rc)
|
||||
.filter(|version| version::same_core_version(version, &next_stable))
|
||||
.max();
|
||||
if let Some(last_relevant_rc) = last_relevant_rc {
|
||||
version::increment_rc_version(last_relevant_rc)?
|
||||
} else {
|
||||
Version { pre: version::generate_rc_prerelease(1)?, ..next_stable }
|
||||
}
|
||||
}
|
||||
Designation::Nightly => {
|
||||
let last_relevant_nightly = self
|
||||
.of_kind(version::Kind::Nightly)
|
||||
.filter(|v| v.same_core(&next_stable))
|
||||
.filter_map(|v| Nightly::try_from(v).ok())
|
||||
.filter(|v| v.prerelease().date == self.date)
|
||||
.max();
|
||||
if let Some(last_relevant_nightly) = last_relevant_nightly {
|
||||
last_relevant_nightly.next().into()
|
||||
} else {
|
||||
let prerelease = NightlyPrerelease::new(self.date, None);
|
||||
Version { pre: prerelease.try_into()?, ..next_stable }
|
||||
}
|
||||
}
|
||||
};
|
||||
Ok(ret)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
fn expect_next_release(
|
||||
date: chrono::NaiveDate,
|
||||
releases_so_far: impl IntoIterator<Item: AsRef<str>>,
|
||||
designation: Designation,
|
||||
expected: &str,
|
||||
) {
|
||||
let releases_so_far =
|
||||
releases_so_far.into_iter().map(|version| Version::from_str(version.as_ref()).unwrap());
|
||||
let releases = Releases::new(releases_so_far, date).unwrap();
|
||||
let next_release = releases.generate_version(designation).unwrap();
|
||||
assert_eq!(next_release.to_string(), expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_version_generation() -> Result {
|
||||
use Designation::*;
|
||||
let previously_released_versions = ["2021.1.1", "2021.1.2"];
|
||||
let date = chrono::NaiveDate::from_ymd_opt(2021, 2, 22).context("Invalid date")?;
|
||||
let next_date = chrono::NaiveDate::from_ymd_opt(2021, 2, 23).context("Invalid date")?;
|
||||
expect_next_release(date, previously_released_versions, Stable, "2021.2.1");
|
||||
expect_next_release(date, previously_released_versions, Patch, "2021.1.3");
|
||||
expect_next_release(date, previously_released_versions, Rc, "2021.2.1-rc.1");
|
||||
expect_next_release(
|
||||
date,
|
||||
previously_released_versions,
|
||||
Nightly,
|
||||
"2021.2.1-nightly.2021.2.22",
|
||||
);
|
||||
|
||||
let previously_released_versions = ["2021.1.1", "2021.1.2", "2021.2.1-nightly.2021.2.22"];
|
||||
expect_next_release(
|
||||
date,
|
||||
previously_released_versions,
|
||||
Nightly,
|
||||
"2021.2.1-nightly.2021.2.22.1",
|
||||
);
|
||||
expect_next_release(
|
||||
next_date,
|
||||
previously_released_versions,
|
||||
Nightly,
|
||||
"2021.2.1-nightly.2021.2.23",
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ itertools = "0.10.1"
|
||||
lazy_static = "1.4.0"
|
||||
log = "0.4.14"
|
||||
mime = "0.3.16"
|
||||
multimap = "0.8.3"
|
||||
new_mime_guess = "4.0.0"
|
||||
nix = { workspace = true }
|
||||
octocrab = { git = "https://github.com/enso-org/octocrab", default-features = false, features = [
|
||||
|
@ -22,10 +22,12 @@ pub fn is_in_env() -> bool {
|
||||
///
|
||||
/// See: <https://docs.github.com/en/actions/learn-github-actions/workflow-commands-for-github-actions#setting-an-output-parameter>
|
||||
pub async fn set_output(name: &str, value: &(impl ToString + ?Sized)) -> Result {
|
||||
let value = value.to_string();
|
||||
if is_in_env() {
|
||||
let value = value.to_string();
|
||||
debug!("Setting GitHub Actions step output {name} to {value}.");
|
||||
env_file::GITHUB_OUTPUT.append_key_value(name, &value).await?;
|
||||
} else {
|
||||
debug!("Not setting GitHub Actions step output {name} to {value} because we are not in GitHub Actions environment.");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
@ -178,23 +178,29 @@ impl Workflow {
|
||||
key
|
||||
}
|
||||
|
||||
pub fn add<J: JobArchetype>(&mut self, os: OS) -> String {
|
||||
self.add_customized::<J>(os, |_| {})
|
||||
pub fn add(&mut self, os: OS, job: impl JobArchetype) -> String {
|
||||
self.add_customized(os, job, |_| {})
|
||||
}
|
||||
|
||||
pub fn add_customized<J: JobArchetype>(&mut self, os: OS, f: impl FnOnce(&mut Job)) -> String {
|
||||
let (key, mut job) = J::entry(os);
|
||||
pub fn add_customized(
|
||||
&mut self,
|
||||
os: OS,
|
||||
job: impl JobArchetype,
|
||||
f: impl FnOnce(&mut Job),
|
||||
) -> String {
|
||||
let (key, mut job) = job.entry(os);
|
||||
f(&mut job);
|
||||
self.jobs.insert(key.clone(), job);
|
||||
key
|
||||
}
|
||||
|
||||
pub fn add_dependent<J: JobArchetype>(
|
||||
pub fn add_dependent(
|
||||
&mut self,
|
||||
os: OS,
|
||||
job: impl JobArchetype,
|
||||
needed: impl IntoIterator<Item: AsRef<str>>,
|
||||
) -> String {
|
||||
let (key, mut job) = J::entry(os);
|
||||
let (key, mut job) = job.entry(os);
|
||||
for needed in needed {
|
||||
self.expose_outputs(needed.as_ref(), &mut job);
|
||||
}
|
||||
@ -308,7 +314,7 @@ pub enum WorkflowDispatchInputType {
|
||||
Choice {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default: Option<String>,
|
||||
choices: Vec<String>, // should be non-empty
|
||||
options: Vec<String>, // should be non-empty
|
||||
},
|
||||
Boolean {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
@ -355,14 +361,32 @@ impl WorkflowDispatchInput {
|
||||
pub fn new_string(
|
||||
description: impl Into<String>,
|
||||
required: bool,
|
||||
default: impl Into<String>,
|
||||
default: Option<impl Into<String>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
r#type: WorkflowDispatchInputType::String { default: Some(default.into()) },
|
||||
r#type: WorkflowDispatchInputType::String { default: default.map(Into::into) },
|
||||
..Self::new(description, required)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_choice(
|
||||
description: impl Into<String>,
|
||||
required: bool,
|
||||
options: impl IntoIterator<Item: Into<String>>,
|
||||
default: Option<impl Into<String>>,
|
||||
) -> Result<Self> {
|
||||
let options = options.into_iter().map(Into::into).collect_vec();
|
||||
ensure!(!options.is_empty(), "The options for a choice input must not be empty.");
|
||||
let default = default.map(Into::into);
|
||||
if let Some(default) = &default {
|
||||
ensure!(options.contains(default), "The default value must be one of the options.");
|
||||
}
|
||||
Ok(Self {
|
||||
r#type: WorkflowDispatchInputType::Choice { default, options },
|
||||
..Self::new(description, required)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_boolean(description: impl Into<String>, required: bool, default: bool) -> Self {
|
||||
Self {
|
||||
r#type: WorkflowDispatchInputType::Boolean { default: Some(default) },
|
||||
@ -394,8 +418,115 @@ impl WorkflowDispatch {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
#[serde(tag = "type")]
|
||||
pub enum WorkflowCallInputType {
|
||||
String {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default: Option<String>,
|
||||
},
|
||||
Boolean {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default: Option<bool>,
|
||||
},
|
||||
Number {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
default: Option<f64>,
|
||||
},
|
||||
}
|
||||
|
||||
impl Default for WorkflowCallInputType {
|
||||
fn default() -> Self {
|
||||
Self::String { default: None }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<WorkflowDispatchInputType> for WorkflowCallInputType {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: WorkflowDispatchInputType) -> Result<Self> {
|
||||
Ok(match value {
|
||||
WorkflowDispatchInputType::String { default } => Self::String { default },
|
||||
WorkflowDispatchInputType::Boolean { default } => Self::Boolean { default },
|
||||
WorkflowDispatchInputType::Choice { default, .. } => Self::String { default },
|
||||
WorkflowDispatchInputType::Environment { .. } =>
|
||||
bail!("Environment is not supported for workflow call inputs!"),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Input to the workflow_call trigger.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct WorkflowCallInput {
|
||||
/// A string description of the input parameter.
|
||||
pub description: String,
|
||||
/// A boolean to indicate whether the action requires the input parameter.
|
||||
pub required: bool,
|
||||
/// A string representing the type of the input.
|
||||
#[serde(flatten)]
|
||||
pub r#type: WorkflowCallInputType,
|
||||
}
|
||||
|
||||
impl TryFrom<WorkflowDispatchInput> for WorkflowCallInput {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: WorkflowDispatchInput) -> Result<Self> {
|
||||
Ok(Self {
|
||||
description: value.description,
|
||||
required: value.required,
|
||||
r#type: value.r#type.try_into()?,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// A workflow call output.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub struct WorkflowCallOutput {
|
||||
/// A string description of the output parameter.
|
||||
pub description: String,
|
||||
/// Expression to defining the output parameter.
|
||||
pub value: String,
|
||||
}
|
||||
|
||||
/// A workflow call secret parameter.
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
pub struct WorkflowCallSecret {
|
||||
/// A string description of the secret parameter.
|
||||
pub description: String,
|
||||
/// A boolean specifying whether the secret must be supplied.
|
||||
pub required: bool,
|
||||
}
|
||||
|
||||
/// A workflow call trigger.
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct WorkflowCall {
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub inputs: BTreeMap<String, WorkflowCallInput>,
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub outputs: BTreeMap<String, WorkflowCallOutput>,
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub secrets: BTreeMap<String, WorkflowCallSecret>,
|
||||
}
|
||||
|
||||
impl TryFrom<WorkflowDispatch> for WorkflowCall {
|
||||
type Error = anyhow::Error;
|
||||
|
||||
fn try_from(value: WorkflowDispatch) -> Result<Self> {
|
||||
Ok(Self {
|
||||
inputs: value
|
||||
.inputs
|
||||
.into_iter()
|
||||
.map(|(k, v)| Result::Ok((k, v.try_into()?)))
|
||||
.try_collect()?,
|
||||
..Default::default()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
pub struct Event {
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub push: Option<Push>,
|
||||
@ -405,6 +536,8 @@ pub struct Event {
|
||||
pub schedule: Vec<Schedule>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workflow_dispatch: Option<WorkflowDispatch>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub workflow_call: Option<WorkflowCall>,
|
||||
}
|
||||
|
||||
impl Event {
|
||||
@ -433,6 +566,13 @@ impl Event {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub enum JobSecrets {
|
||||
Inherit,
|
||||
Map(BTreeMap<String, String>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
pub struct Job {
|
||||
@ -441,7 +581,9 @@ pub struct Job {
|
||||
pub needs: BTreeSet<String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub r#if: Option<String>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub runs_on: Vec<RunnerLabel>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub steps: Vec<Step>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub concurrency: Option<Concurrency>,
|
||||
@ -453,6 +595,12 @@ pub struct Job {
|
||||
pub strategy: Option<Strategy>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub timeout_minutes: Option<u32>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub uses: Option<String>,
|
||||
#[serde(skip_serializing_if = "BTreeMap::is_empty")]
|
||||
pub with: BTreeMap<String, String>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub secrets: Option<JobSecrets>,
|
||||
}
|
||||
|
||||
impl Job {
|
||||
@ -464,7 +612,12 @@ impl Job {
|
||||
let step = step_id.as_ref();
|
||||
let output = output_name.into();
|
||||
let value = format!("${{{{ steps.{step}.outputs.{output} }}}}");
|
||||
self.outputs.insert(output, value);
|
||||
if let Some(old_value) = self.outputs.insert(output.clone(), value) {
|
||||
warn!(
|
||||
"Overwriting output `{}` of job `{}` with value `{}`",
|
||||
output, self.name, old_value
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn env(&mut self, name: impl Into<String>, value: impl Into<String>) {
|
||||
@ -478,7 +631,7 @@ impl Job {
|
||||
pub fn use_job_outputs(&mut self, job_id: impl Into<String>, job: &Job) {
|
||||
let job_id = job_id.into();
|
||||
for output_name in job.outputs.keys() {
|
||||
let reference = format!("${{{{needs.{}.outputs.{}}}}}", job_id, output_name);
|
||||
let reference = wrap_expression(format!("needs.{job_id}.outputs.{output_name}"));
|
||||
self.env.insert(output_name.into(), reference);
|
||||
}
|
||||
self.needs(job_id);
|
||||
@ -487,6 +640,15 @@ impl Job {
|
||||
pub fn needs(&mut self, job_id: impl Into<String>) {
|
||||
self.needs.insert(job_id.into());
|
||||
}
|
||||
|
||||
pub fn with(&mut self, name: impl Into<String>, value: impl Into<String>) {
|
||||
self.with.insert(name.into(), value.into());
|
||||
}
|
||||
|
||||
pub fn with_with(mut self, name: impl Into<String>, value: impl Into<String>) -> Self {
|
||||
self.with(name, value);
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize, Deserialize)]
|
||||
@ -768,27 +930,27 @@ pub fn checkout_repo_step() -> impl IntoIterator<Item = Step> {
|
||||
}
|
||||
|
||||
pub trait JobArchetype {
|
||||
fn id_key_base() -> String {
|
||||
fn id_key_base(&self) -> String {
|
||||
std::any::type_name::<Self>().to_kebab_case()
|
||||
}
|
||||
|
||||
fn key(os: OS) -> String {
|
||||
format!("{}-{}", Self::id_key_base(), os)
|
||||
fn key(&self, os: OS) -> String {
|
||||
format!("{}-{}", self.id_key_base(), os)
|
||||
}
|
||||
|
||||
fn job(os: OS) -> Job;
|
||||
fn job(&self, os: OS) -> Job;
|
||||
|
||||
fn entry(os: OS) -> (String, Job) {
|
||||
(Self::key(os), Self::job(os))
|
||||
fn entry(&self, os: OS) -> (String, Job) {
|
||||
(self.key(os), self.job(os))
|
||||
}
|
||||
|
||||
// [Step ID] => [variable names]
|
||||
fn outputs() -> BTreeMap<String, Vec<String>> {
|
||||
fn outputs(&self) -> BTreeMap<String, Vec<String>> {
|
||||
default()
|
||||
}
|
||||
|
||||
fn expose_outputs(job: &mut Job) {
|
||||
for (step_id, outputs) in Self::outputs() {
|
||||
fn expose_outputs(&self, job: &mut Job) {
|
||||
for (step_id, outputs) in self.outputs() {
|
||||
for output in outputs {
|
||||
job.expose_output(&step_id, output);
|
||||
}
|
||||
|
@ -223,6 +223,12 @@ pub mod new {
|
||||
}
|
||||
}
|
||||
|
||||
impl<Value, Borrowed: ?Sized> Display for SimpleVariable<Value, Borrowed> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.name)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Display, Ord, PartialOrd, Eq, PartialEq)]
|
||||
pub struct PathLike(pub &'static str);
|
||||
|
||||
|
@ -1,16 +1,54 @@
|
||||
use crate::prelude::*;
|
||||
|
||||
use semver::Prerelease;
|
||||
|
||||
|
||||
|
||||
pub trait VersionExt {
|
||||
fn triple(&self) -> (u64, u64, u64);
|
||||
fn same_triple(&self, other: &Self) -> bool {
|
||||
self.triple() == other.triple()
|
||||
fn core(&self) -> (u64, u64, u64);
|
||||
fn same_core(&self, other: &Self) -> bool {
|
||||
self.core() == other.core()
|
||||
}
|
||||
/// Get the identifiers (i.e. the dot-separated parts after the hyphen) of the prerelease.
|
||||
fn identifiers(&self) -> Vec<&str>;
|
||||
}
|
||||
|
||||
impl VersionExt for Version {
|
||||
fn triple(&self) -> (u64, u64, u64) {
|
||||
fn core(&self) -> (u64, u64, u64) {
|
||||
(self.major, self.minor, self.patch)
|
||||
}
|
||||
fn identifiers(&self) -> Vec<&str> {
|
||||
self.pre.identifiers()
|
||||
}
|
||||
}
|
||||
|
||||
pub trait PrereleaseExt {
|
||||
/// Get the identifiers (i.e. the dot-separated parts) of the prerelease.
|
||||
fn identifiers(&self) -> Vec<&str>;
|
||||
}
|
||||
|
||||
impl PrereleaseExt for Prerelease {
|
||||
fn identifiers(&self) -> Vec<&str> {
|
||||
if self.is_empty() {
|
||||
default()
|
||||
} else {
|
||||
self.split('.').collect()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn split_identifiers() -> Result {
|
||||
let version = Version::parse("1.2.3-alpha.1")?;
|
||||
assert_eq!(version.identifiers(), vec!["alpha", "1"]);
|
||||
|
||||
let version = Version::parse("1.2.3")?;
|
||||
assert_eq!(version.identifiers(), Vec::<&str>::new());
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -16,14 +16,15 @@ pub async fn open(path: impl AsRef<Path>) -> Result<File> {
|
||||
File::open(&path).await.anyhow_err()
|
||||
}
|
||||
|
||||
// #[context("Failed to open path for reading: {}", path.as_ref().display())]
|
||||
pub fn open_stream(path: impl AsRef<Path>) -> BoxFuture<'static, Result<ReaderStream<File>>> {
|
||||
let error_message = format!("Failed to open path for reading: {}", path.as_ref().display());
|
||||
let path = path.as_ref().to_owned();
|
||||
let file = open(path);
|
||||
async move {
|
||||
let file = file.await?;
|
||||
Ok(ReaderStream::new(file))
|
||||
Result::Ok(ReaderStream::new(file))
|
||||
}
|
||||
.context(error_message)
|
||||
.boxed()
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,6 @@ pub trait IsReleaseExt: IsRelease + Sync {
|
||||
/// Upload a new asset to the release from a given file.
|
||||
///
|
||||
/// The filename will be used to name the asset and deduce MIME content type.
|
||||
// #[context("Failed to upload an asset from the file under {}", path.as_ref().display())]
|
||||
#[instrument(skip_all, fields(source = %path.as_ref().display()))]
|
||||
async fn upload_asset_file(&self, path: impl AsRef<Path> + Send) -> Result<Asset> {
|
||||
let error_msg =
|
||||
|
@ -8,6 +8,7 @@ use crate::github::MAX_PER_PAGE;
|
||||
use headers::HeaderMap;
|
||||
use headers::HeaderValue;
|
||||
use octocrab::models::repos::Asset;
|
||||
use octocrab::models::repos::Ref;
|
||||
use octocrab::models::repos::Release;
|
||||
use octocrab::models::workflows::WorkflowListArtifact;
|
||||
use octocrab::models::ArtifactId;
|
||||
@ -15,6 +16,7 @@ use octocrab::models::AssetId;
|
||||
use octocrab::models::ReleaseId;
|
||||
use octocrab::models::RunId;
|
||||
use octocrab::params::actions::ArchiveFormat;
|
||||
use octocrab::params::repos::Reference;
|
||||
use reqwest::Response;
|
||||
|
||||
|
||||
@ -194,14 +196,31 @@ impl<R: IsRepo> Handle<R> {
|
||||
.context(format!("Failed to find release by id `{release_id}` in `{self}`."))
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(%self), skip(predicate), err)]
|
||||
pub async fn find_release_if(&self, predicate: impl Fn(&Release) -> bool) -> Result<Release> {
|
||||
let releases = self.all_releases().await?;
|
||||
let release = releases.into_iter().find(predicate);
|
||||
release.context("Failed to find a release that satisfies the predicate.")
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(%self, %text), err)]
|
||||
pub async fn find_release_by_text(&self, text: &str) -> anyhow::Result<Release> {
|
||||
self.all_releases()
|
||||
.await?
|
||||
.into_iter()
|
||||
.find(|release| release.tag_name.contains(text))
|
||||
self.find_release_if(|release| release.tag_name.contains(text))
|
||||
.await
|
||||
.inspect(|release| info!("Found release at: {} (id={}).", release.html_url, release.id))
|
||||
.context(format!("No release with tag matching `{text}` in {self}."))
|
||||
.with_context(|| format!("No release with tag matching `{text}` in {self}."))
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(%self, %text), err)]
|
||||
pub async fn find_release_by_tag(&self, text: &str) -> anyhow::Result<Release> {
|
||||
self.find_release_if(|release| release.tag_name == text)
|
||||
.await
|
||||
.inspect(|release| info!("Found release at: {} (id={}).", release.html_url, release.id))
|
||||
.with_context(|| format!("No release with tag equal to `{text}` in {self}."))
|
||||
}
|
||||
|
||||
pub async fn get_ref(&self, r#ref: &Reference) -> Result<Ref> {
|
||||
self.repos().get_ref(r#ref).await.context(format!("Failed to get ref `{ref}` in {self}."))
|
||||
}
|
||||
|
||||
#[tracing::instrument(fields(%self, %run_id, %name), err, ret)]
|
||||
@ -302,4 +321,24 @@ impl<R: IsRepo> Handle<R> {
|
||||
.await
|
||||
.with_context(|| format!("Failed to get the infomation for the {self} repository."))
|
||||
}
|
||||
|
||||
/// Get the name of the default branch.
|
||||
pub async fn default_branch(&self) -> Result<String> {
|
||||
self.get().await?.default_branch.with_context(|| {
|
||||
format!(
|
||||
"Failed to get the default branch of the {} repository. Missing field: `default_branch`.",
|
||||
self
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
/// Dispatches the workflow using the head of the default branch.
|
||||
pub async fn dispatch_workflow(
|
||||
&self,
|
||||
workflow_id: impl AsRef<str> + Send + Sync + 'static,
|
||||
inputs: &impl Serialize,
|
||||
) -> Result {
|
||||
let default_branch = self.default_branch().await?;
|
||||
crate::github::workflow::dispatch(self, workflow_id, default_branch, inputs).await
|
||||
}
|
||||
}
|
||||
|
@ -115,6 +115,8 @@ pub mod prelude {
|
||||
pub use crate::env::new::TypedVariable as _;
|
||||
pub use crate::extensions::clap::ArgExt as _;
|
||||
pub use crate::extensions::command::CommandExt as _;
|
||||
pub use crate::extensions::version::PrereleaseExt as _;
|
||||
pub use crate::extensions::version::VersionExt as _;
|
||||
pub use crate::github::release::IsReleaseExt as _;
|
||||
pub use crate::program::command::provider::CommandProviderExt as _;
|
||||
pub use crate::program::version::IsVersion as _;
|
||||
@ -141,6 +143,8 @@ pub const USER_AGENT: &str = "enso-build";
|
||||
|
||||
pub const UNREGISTERED_PORTS: Range<u16> = 49152..65535;
|
||||
|
||||
pub const RECORD_SEPARATOR: &str = "\u{1E}";
|
||||
|
||||
/// Looks up a free port in the IANA private or dynamic port range.
|
||||
pub fn get_free_port() -> Result<u16> {
|
||||
let port_range = UNREGISTERED_PORTS;
|
||||
|
@ -16,6 +16,7 @@ use tracing_subscriber::Registry;
|
||||
|
||||
|
||||
pub fn is_our_module_path(path: impl AsRef<str>) -> bool {
|
||||
// true
|
||||
["ide_ci::", "enso"].into_iter().any(|prefix| path.as_ref().starts_with(prefix))
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,14 @@
|
||||
//! Wrappers over git commands.
|
||||
//!
|
||||
//! As a reasonable starting points, please refer to:
|
||||
//! * [`new`] to obtain a context for an existing repository.
|
||||
//! * [`init`] to initialize a new repository.
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
use crate::new_command_type;
|
||||
use crate::programs::git::pretty_format::refs_from_decoration;
|
||||
use crate::RECORD_SEPARATOR;
|
||||
|
||||
|
||||
// ==============
|
||||
@ -8,11 +16,23 @@ use crate::new_command_type;
|
||||
// ==============
|
||||
|
||||
pub mod clean;
|
||||
pub mod pretty_format;
|
||||
|
||||
|
||||
|
||||
pub mod r#ref;
|
||||
|
||||
pub use clean::Clean;
|
||||
pub use r#ref::Ref;
|
||||
|
||||
/// The default name of the remote repository.
|
||||
///
|
||||
/// If use case arises, we can make it configurable in the future.
|
||||
const DEFAULT_REMOTE: &str = "origin";
|
||||
|
||||
|
||||
/// Git version control system.
|
||||
///
|
||||
/// See: <https://git-scm.com/>
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Git;
|
||||
|
||||
@ -25,11 +45,14 @@ impl Program for Git {
|
||||
|
||||
impl Git {
|
||||
/// Create a new, empty git repository in the given directory.
|
||||
pub fn init(&self, path: impl AsRef<Path>) -> Result<GitCommand> {
|
||||
let mut cmd = self.cmd()?;
|
||||
cmd.arg(Command::Init);
|
||||
cmd.current_dir(path);
|
||||
Ok(cmd)
|
||||
#[context("Failed to initialize git repository in {}", path.as_ref().display())]
|
||||
pub async fn init(&self, path: impl AsRef<Path>) -> Result<Context> {
|
||||
crate::fs::tokio::create_dir_if_missing(path.as_ref()).await?;
|
||||
self.cmd()?.arg(Command::Init).current_dir(path.as_ref()).run_ok().await?;
|
||||
Ok(Context {
|
||||
working_dir: path.as_ref().to_path_buf(),
|
||||
repository_root: path.as_ref().to_path_buf(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,11 +68,15 @@ pub struct Context {
|
||||
repository_root: PathBuf,
|
||||
/// Directory in which commands will be invoked.
|
||||
/// It might not be the repository root and it makes difference for many commands.
|
||||
/// If it is different from the repository root, it must be in its subdirectory.
|
||||
working_dir: PathBuf,
|
||||
}
|
||||
|
||||
impl Context {
|
||||
/// Initialize a new command invoking git.
|
||||
///
|
||||
/// This should be used as a starting point for all git commands.
|
||||
/// The command comes with current working directory set, please do not override it.
|
||||
pub fn cmd(&self) -> Result<GitCommand> {
|
||||
Ok(Git.cmd()?.with_current_dir(&self.working_dir))
|
||||
}
|
||||
@ -68,7 +95,7 @@ impl Context {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a `Git` invocation context within a given directory.
|
||||
/// Create a `git` invocation context within a given directory.
|
||||
///
|
||||
/// The `working_dir` is the directory in which git commands will be invoked. It is expected to
|
||||
/// be a part of some git repository.
|
||||
@ -84,18 +111,26 @@ impl Context {
|
||||
Ok(Context { repository_root: repo_root, working_dir: temp_git.working_dir })
|
||||
}
|
||||
|
||||
/// Create a git context for the current working directory.
|
||||
pub async fn new_current() -> Result<Self> {
|
||||
Context::new(crate::env::current_dir()?).await
|
||||
}
|
||||
|
||||
/// Get the hash of the current [HEAD commit](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefHEADaHEAD).
|
||||
#[context("Failed to get a HEAD hash.")]
|
||||
pub async fn head_hash(&self) -> Result<String> {
|
||||
self.cmd()?.args(["rev-parse", "--verify", "HEAD"]).output_ok().await?.single_line_stdout()
|
||||
self.cmd()?
|
||||
.arg(Command::RevParse)
|
||||
.args(["--verify", "HEAD"])
|
||||
.output_ok()
|
||||
.await?
|
||||
.single_line_stdout()
|
||||
}
|
||||
|
||||
/// Fetch a branch from a remote repository.
|
||||
#[context("Failed to fetch branch {} from remote {}.", branch, remote)]
|
||||
pub async fn fetch_branch(&self, remote: &str, branch: &str) -> Result {
|
||||
self.cmd()?.args(["fetch", remote, branch]).run_ok().await
|
||||
self.cmd()?.arg(Command::Fetch).args([remote, branch]).run_ok().await
|
||||
}
|
||||
|
||||
/// List of files that are different than the compared commit.
|
||||
@ -104,7 +139,8 @@ impl Context {
|
||||
let root = self.repository_root.as_path();
|
||||
Ok(self
|
||||
.cmd()?
|
||||
.args(["diff", "--name-only", compare_against.as_ref()])
|
||||
.arg(Command::Diff)
|
||||
.args(["--name-only", compare_against.as_ref()])
|
||||
.output_ok()
|
||||
.await?
|
||||
.into_stdout_string()?
|
||||
@ -113,16 +149,94 @@ impl Context {
|
||||
.collect_vec())
|
||||
}
|
||||
|
||||
/// Get the repository root directory.
|
||||
pub async fn repository_root(&self) -> Result<PathBuf> {
|
||||
let output = self
|
||||
.cmd()?
|
||||
.args(["rev-parse", "--show-toplevel"])
|
||||
.arg(Command::RevParse)
|
||||
.args(["--show-toplevel"])
|
||||
.output_ok()
|
||||
.await?
|
||||
.single_line_stdout()?;
|
||||
let path = PathBuf::from(output).normalize();
|
||||
Ok(path)
|
||||
}
|
||||
|
||||
/// Fetch the tags from the remote repository and prune the local tags that are no longer on the
|
||||
/// remote.
|
||||
pub async fn sync_tags(&self) -> Result {
|
||||
trace!("Fetching the remotes and pruning tags.");
|
||||
self.cmd()?.arg(Command::Fetch).args(["--prune", "--prune-tags", "--tags"]).run_ok().await
|
||||
}
|
||||
|
||||
/// List the tags on the remote repository.
|
||||
///
|
||||
/// Note that tag objects will appear twice: once for the tag object itself, and once in the
|
||||
/// dereferenced form (e.g. `refs/tags/enso-0.2.0^{}`), for the commit object that the tag
|
||||
/// points to.
|
||||
pub async fn list_remote_tags(&self) -> Result<Vec<RemoteLsEntry>> {
|
||||
let output = self
|
||||
.cmd()?
|
||||
.arg(Command::LsRemote)
|
||||
.args(["--tags", DEFAULT_REMOTE])
|
||||
.output_ok()
|
||||
.await?
|
||||
.into_stdout_string()?;
|
||||
output.lines().map(RemoteLsEntry::from_str).try_collect()
|
||||
}
|
||||
|
||||
/// Get the commit history of the current branch.
|
||||
///
|
||||
/// Fails if there are no commits in the repository.
|
||||
pub async fn log(&self) -> Result<Vec<LogEntry>> {
|
||||
use pretty_format::Placeholder::Hash;
|
||||
use pretty_format::Placeholder::RefNames;
|
||||
let fields = [Hash, RefNames];
|
||||
let history = self.log_fields(&fields).await?;
|
||||
|
||||
history
|
||||
.into_iter()
|
||||
.map(|fields| {
|
||||
let mut fields = fields.into_iter();
|
||||
let hash = fields.next().context("Missing hash.")?;
|
||||
let refs = fields.next().context("Missing refs.")?;
|
||||
let refs = refs_from_decoration(&refs);
|
||||
let refs = refs.into_iter().map(|s| s.parse2()).try_collect()?;
|
||||
Ok(LogEntry { hash, refs })
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
|
||||
/// Get the commit history of the current branch with the given fields.
|
||||
pub async fn log_fields(
|
||||
&self,
|
||||
fields: &[pretty_format::Placeholder],
|
||||
) -> Result<Vec<Vec<String>>> {
|
||||
let field_count = fields.len();
|
||||
let format = fields.iter().map(ToString::to_string).join(RECORD_SEPARATOR);
|
||||
let output = self
|
||||
.cmd()?
|
||||
.arg(Command::Log)
|
||||
.args(["--decorate=full", &format!("--pretty=format:{format}")])
|
||||
.output_ok()
|
||||
.await?
|
||||
.into_stdout_string()?;
|
||||
|
||||
output
|
||||
.lines()
|
||||
.map(|line| {
|
||||
let fields = line.split(RECORD_SEPARATOR).map(ToString::to_string).collect_vec();
|
||||
ensure!(fields.len() == field_count, "Wrong number of fields in log entry.");
|
||||
Ok(fields)
|
||||
})
|
||||
.try_collect()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct LogEntry {
|
||||
pub hash: String,
|
||||
pub refs: Vec<Ref>,
|
||||
}
|
||||
|
||||
|
||||
@ -140,7 +254,8 @@ impl GitCommand {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
/// A top-level command for git.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Command {
|
||||
/// Remove untracked files from the working tree.
|
||||
Clean,
|
||||
@ -148,15 +263,75 @@ pub enum Command {
|
||||
Diff,
|
||||
/// Create an empty Git repository or reinitialize an existing one.
|
||||
Init,
|
||||
/// Download objects and refs from another repository.
|
||||
Fetch,
|
||||
/// Show commit logs.
|
||||
Log,
|
||||
/// List references in a remote repository.
|
||||
LsRemote,
|
||||
/// Pick out and massage parameters.
|
||||
RevParse,
|
||||
}
|
||||
|
||||
impl AsRef<OsStr> for Command {
|
||||
fn as_ref(&self) -> &OsStr {
|
||||
match self {
|
||||
Command::Clean => OsStr::new("clean"),
|
||||
Command::Diff => OsStr::new("diff"),
|
||||
Command::Init => OsStr::new("init"),
|
||||
}
|
||||
OsStr::new(match self {
|
||||
Command::Clean => "clean",
|
||||
Command::Diff => "diff",
|
||||
Command::Init => "init",
|
||||
Command::Fetch => "fetch",
|
||||
Command::Log => "log",
|
||||
Command::LsRemote => "ls-remote",
|
||||
Command::RevParse => "rev-parse",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new git wrapper for the repository owning the given path.
|
||||
///
|
||||
/// Path is not required to be a repository root, but it must be inside the repository.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use enso_build_base::prelude::*;
|
||||
/// use ide_ci::programs::git;
|
||||
/// use ide_ci::programs::Git;
|
||||
/// #[tokio::main]
|
||||
/// async fn main() -> Result {
|
||||
/// let temp = tempfile::tempdir()?;
|
||||
/// // There is no repository yet, cannot create a context.
|
||||
/// assert!(git::new(temp.path()).await.is_err());
|
||||
///
|
||||
/// // Initialize a new repository in the temp path.
|
||||
/// Git.init(temp.path()).await?;
|
||||
/// let git = git::new(temp.path()).await?;
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
pub async fn new(path: impl Into<PathBuf>) -> Result<Context> {
|
||||
Context::new(path).await
|
||||
}
|
||||
|
||||
/// Reference available in a remote repository along with the associated commit ID.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub struct RemoteLsEntry {
|
||||
/// Commit ID (hash).
|
||||
pub hash: String,
|
||||
/// Reference name.
|
||||
pub r#ref: Ref,
|
||||
}
|
||||
|
||||
/// Construct from a line of output of `git ls-remote`.
|
||||
impl std::str::FromStr for RemoteLsEntry {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
#[context("Failed to parse remote ls entry from string: {}", line)]
|
||||
fn from_str(line: &str) -> std::result::Result<Self, Self::Err> {
|
||||
let mut parts = line.split_whitespace();
|
||||
let hash = parts.next().context("Missing hash")?.to_string();
|
||||
let r#ref = parts.next().context("Missing reference")?.parse2()?;
|
||||
ensure!(parts.next().is_none(), "Unexpected trailing extra parts.");
|
||||
Ok(Self { hash, r#ref })
|
||||
}
|
||||
}
|
||||
|
||||
@ -164,6 +339,34 @@ impl AsRef<OsStr> for Command {
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[tokio::test]
|
||||
async fn github_wrapping_non_directory() -> Result {
|
||||
let temp = tempfile::tempdir()?;
|
||||
let git = new(temp.path()).await;
|
||||
assert!(git.is_err());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn list_remote_tags_test() -> Result {
|
||||
let git = Context::new_current().await?;
|
||||
let tags = git.list_remote_tags().await?;
|
||||
let map: multimap::MultiMap<_, _> =
|
||||
tags.into_iter().map(|tag| (tag.hash, tag.r#ref)).collect();
|
||||
dbg!(&map);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn log_test() -> Result {
|
||||
let git = Context::new_current().await?;
|
||||
dbg!(git.log().await)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
#[tokio::test]
|
||||
#[ignore]
|
||||
async fn repo_root() -> Result {
|
||||
|
@ -125,7 +125,7 @@ mod tests {
|
||||
setup_logging()?;
|
||||
let dir = PathBuf::from(r"C:\temp\test_cleaning");
|
||||
crate::fs::tokio::reset_dir(&dir).await?;
|
||||
Git.init(&dir)?.run_ok().await?;
|
||||
Git.init(&dir).await?;
|
||||
|
||||
let foo = dir.join("foo");
|
||||
let foo_target = foo.join("target");
|
||||
|
263
build/ci_utils/src/programs/git/pretty_format.rs
Normal file
263
build/ci_utils/src/programs/git/pretty_format.rs
Normal file
@ -0,0 +1,263 @@
|
||||
//! Customized formatting utilities.
|
||||
//!
|
||||
//! See:
|
||||
//! <https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt>
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
|
||||
/// Placeholders that can be used with `format:<...>` in git commands.
|
||||
/// See: <https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt>
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum Placeholder {
|
||||
// === Placeholders that expand to a single literal ===
|
||||
/// A newline character.
|
||||
Newline,
|
||||
/// A raw percent sign.
|
||||
Percent,
|
||||
/// Hex-encoded byte character.
|
||||
HexCode(u8),
|
||||
|
||||
// === Placeholders that control formatting ===
|
||||
// TODO: Add, as needed.
|
||||
|
||||
// === Placeholders for commit information ===
|
||||
/// The commit hash.
|
||||
Hash,
|
||||
/// Abbreviated commit hash.
|
||||
AbbreviatedHash,
|
||||
/// Tree hash.
|
||||
TreeHash,
|
||||
/// Abbreviated tree hash.
|
||||
AbbreviatedTreeHash,
|
||||
/// Parent hashes.
|
||||
ParentHashes,
|
||||
/// Abbreviated parent hashes.
|
||||
AbbreviatedParentHashes,
|
||||
/// Author name.
|
||||
AuthorName,
|
||||
/// Author name respecting `.mailmap`.
|
||||
AuthorNameMailmap,
|
||||
/// Author email.
|
||||
AuthorEmail,
|
||||
/// Author email respecting `.mailmap`.
|
||||
AuthorEmailMailmap,
|
||||
/// Author email local part (the part before the @).
|
||||
AuthorEmailLocal,
|
||||
/// Author email local part respecting `.mailmap`.
|
||||
AuthorEmailLocalMailmap,
|
||||
/// Author date, respecting the --date=<format> option.
|
||||
AuthorDate,
|
||||
/// Author date in [RFC 2822 format](https://www.rfc-editor.org/rfc/rfc2822).
|
||||
AuthorDateRfc2822,
|
||||
/// Author date, relative to current time (e.g. "2 weeks ago").
|
||||
AuthorDateRelative,
|
||||
/// Author date in UNIX timestamp format.
|
||||
AuthorDateUnixTimestamp,
|
||||
/// Author date, ISO 8601-like format. The differences to the strict ISO 8601 format are:
|
||||
/// * a space instead of the T date/time delimiter;
|
||||
/// * a space between time and time zone;
|
||||
/// * no colon between hours and minutes of the time zone.
|
||||
AuthorDateIso8601Like,
|
||||
/// Author date, strict ISO 8601 format.
|
||||
AuthorDateIso8601Strict,
|
||||
/// Author date, short format (YYYY-MM-DD).
|
||||
AuthorDateShort,
|
||||
/// Author date, human-friendly format.
|
||||
///
|
||||
/// This shows the timezone if the timezone does not match the current time-zone, and doesn't
|
||||
/// print the whole date if that matches (ie skip printing year for dates that are "this year",
|
||||
/// but also skip the whole date itself if it’s in the last few days and we can just say what
|
||||
/// weekday it was). For older dates the hour and minute is also omitted.
|
||||
AuthorDateHuman,
|
||||
/// Committer name.
|
||||
CommitterName,
|
||||
/// Committer name respecting `.mailmap`.
|
||||
CommitterNameMailmap,
|
||||
/// Committer email.
|
||||
CommitterEmail,
|
||||
/// Committer email respecting `.mailmap`.
|
||||
CommitterEmailMailmap,
|
||||
/// Committer email local part (the part before the @).
|
||||
CommitterEmailLocal,
|
||||
/// Committer email local part respecting `.mailmap`.
|
||||
CommitterEmailLocalMailmap,
|
||||
/// Committer date, respecting the `--date=<format>` option.
|
||||
CommitterDate,
|
||||
/// Committer date in [RFC 2822 format](https://www.rfc-editor.org/rfc/rfc2822).
|
||||
CommitterDateRfc2822,
|
||||
/// Committer date, relative to current time (e.g. "2 weeks ago").
|
||||
CommitterDateRelative,
|
||||
/// Committer date in UNIX timestamp format.
|
||||
CommitterDateUnixTimestamp,
|
||||
/// Committer date, ISO 8601-like format. The differences to the strict ISO 8601 format are:
|
||||
/// * a space instead of the T date/time delimiter;
|
||||
/// * a space between time and time zone;
|
||||
/// * no colon between hours and minutes of the time zone.
|
||||
CommitterDateIso8601Like,
|
||||
/// Committer date, strict ISO 8601 format.
|
||||
CommitterDateIso8601Strict,
|
||||
/// Committer date, short format (YYYY-MM-DD).
|
||||
CommitterDateShort,
|
||||
/// Committer date, human-friendly format.
|
||||
///
|
||||
/// This shows the timezone if the timezone does not match the current time-zone, and doesn’t
|
||||
/// print the whole date if that matches (ie skip printing year for dates that are "this year",
|
||||
/// but also skip the whole date itself if it’s in the last few days and we can just say what
|
||||
/// weekday it was). For older dates the hour and minute is also omitted.
|
||||
CommitterDateHuman,
|
||||
/// Ref names, like the --decorate option of [git-log](https://git-scm.com/docs/git-log).
|
||||
RefNamesParenthesized,
|
||||
/// Ref names without the parentheses wrapping.
|
||||
RefNames,
|
||||
/// Ref name that was used to find the commit. Works only with `git-log`.
|
||||
RefNameUsed,
|
||||
/// Encoding name.
|
||||
Encoding,
|
||||
/// Subject. This is the commit message without the body.
|
||||
Subject,
|
||||
/// Sanitized subject line, suitable for a filename.
|
||||
SubjectSanitized,
|
||||
/// Body. This is the commit message without the subject line.
|
||||
Body,
|
||||
/// Raw body.
|
||||
RawBody,
|
||||
/// Commit notes.
|
||||
Notes,
|
||||
/// Raw verification message from GPG for a signed commit.
|
||||
GpgVerificationMessage,
|
||||
/// GPG verification status for a signed commit.
|
||||
///
|
||||
/// Show "G" for a good (valid) signature, "B" for a bad signature, "U" for a good signature
|
||||
/// with unknown validity, "X" for a good signature that has expired, "Y" for a good signature
|
||||
/// made by an expired key, "R" for a good signature made by a revoked key, "E" if the
|
||||
/// signature cannot be checked (e.g. missing key) and "N" for no signature.
|
||||
GpgVerificationStatus,
|
||||
/// The name of the signer of a signed commit.
|
||||
GpgSignerName,
|
||||
/// The key used to sign a signed commit.
|
||||
GpgSignerKey,
|
||||
/// The fingerprint of the key used to sign a signed commit.
|
||||
GpgSignerFingerprint,
|
||||
/// The fingerprint of the primary key whose subkey was used to sign a signed commit.
|
||||
GpgSignerPrimaryFingerprint,
|
||||
/// Trust level of the key used to sign a signed commit.
|
||||
GpgSignerTrustLevel,
|
||||
/// Reflog selector.
|
||||
ReflogSelector,
|
||||
/// Shortened reflog selector.
|
||||
ReflogSelectorShort,
|
||||
/// Reflog identity name.
|
||||
ReflogIdentity,
|
||||
/// Reflog identity mail respecting `.mailmap`.
|
||||
ReflogIdentityMailmap,
|
||||
/// Reflog identity email.
|
||||
ReflogIdentityEmail,
|
||||
/// Reflog identity email respecting `.mailmap`.
|
||||
ReflogIdentityEmailMailmap,
|
||||
/// Reflog subject.
|
||||
ReflogSubject,
|
||||
// TODO: trailers of the body
|
||||
// https://git-scm.com/docs/pretty-formats#Documentation/pretty-formats.txt-emtrailersoptionsem
|
||||
}
|
||||
|
||||
impl Display for Placeholder {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Placeholder::Newline => write!(f, "%n"),
|
||||
Placeholder::Percent => write!(f, "%%"),
|
||||
Placeholder::HexCode(code) => write!(f, "%{:02x}", code),
|
||||
Placeholder::Hash => write!(f, "%H"),
|
||||
Placeholder::AbbreviatedHash => write!(f, "%h"),
|
||||
Placeholder::TreeHash => write!(f, "%T"),
|
||||
Placeholder::AbbreviatedTreeHash => write!(f, "%t"),
|
||||
Placeholder::ParentHashes => write!(f, "%P"),
|
||||
Placeholder::AbbreviatedParentHashes => write!(f, "%p"),
|
||||
Placeholder::AuthorName => write!(f, "%an"),
|
||||
Placeholder::AuthorNameMailmap => write!(f, "%aN"),
|
||||
Placeholder::AuthorEmail => write!(f, "%ae"),
|
||||
Placeholder::AuthorEmailMailmap => write!(f, "%aE"),
|
||||
Placeholder::AuthorEmailLocal => write!(f, "%al"),
|
||||
Placeholder::AuthorEmailLocalMailmap => write!(f, "%aL"),
|
||||
Placeholder::AuthorDate => write!(f, "%ad"),
|
||||
Placeholder::AuthorDateRfc2822 => write!(f, "%aD"),
|
||||
Placeholder::AuthorDateRelative => write!(f, "%ar"),
|
||||
Placeholder::AuthorDateUnixTimestamp => write!(f, "%at"),
|
||||
Placeholder::AuthorDateIso8601Like => write!(f, "%ai"),
|
||||
Placeholder::AuthorDateIso8601Strict => write!(f, "%aI"),
|
||||
Placeholder::AuthorDateShort => write!(f, "%as"),
|
||||
Placeholder::AuthorDateHuman => write!(f, "%ah"),
|
||||
Placeholder::CommitterName => write!(f, "%cn"),
|
||||
Placeholder::CommitterNameMailmap => write!(f, "%cN"),
|
||||
Placeholder::CommitterEmail => write!(f, "%ce"),
|
||||
Placeholder::CommitterEmailMailmap => write!(f, "%cE"),
|
||||
Placeholder::CommitterEmailLocal => write!(f, "%cl"),
|
||||
Placeholder::CommitterEmailLocalMailmap => write!(f, "%cL"),
|
||||
Placeholder::CommitterDate => write!(f, "%cd"),
|
||||
Placeholder::CommitterDateRfc2822 => write!(f, "%cD"),
|
||||
Placeholder::CommitterDateRelative => write!(f, "%cr"),
|
||||
Placeholder::CommitterDateUnixTimestamp => write!(f, "%ct"),
|
||||
Placeholder::CommitterDateIso8601Like => write!(f, "%ci"),
|
||||
Placeholder::CommitterDateIso8601Strict => write!(f, "%cI"),
|
||||
Placeholder::CommitterDateShort => write!(f, "%cs"),
|
||||
Placeholder::CommitterDateHuman => write!(f, "%ch"),
|
||||
Placeholder::RefNamesParenthesized => write!(f, "%d"),
|
||||
Placeholder::RefNames => write!(f, "%D"),
|
||||
Placeholder::RefNameUsed => write!(f, "%S"),
|
||||
Placeholder::Encoding => write!(f, "%e"),
|
||||
Placeholder::Subject => write!(f, "%s"),
|
||||
Placeholder::SubjectSanitized => write!(f, "%f"),
|
||||
Placeholder::Body => write!(f, "%b"),
|
||||
Placeholder::RawBody => write!(f, "%B"),
|
||||
Placeholder::Notes => write!(f, "%N"),
|
||||
Placeholder::GpgVerificationMessage => write!(f, "%GG"),
|
||||
Placeholder::GpgVerificationStatus => write!(f, "%G?"),
|
||||
Placeholder::GpgSignerName => write!(f, "%GS"),
|
||||
Placeholder::GpgSignerKey => write!(f, "%GK"),
|
||||
Placeholder::GpgSignerFingerprint => write!(f, "%GF"),
|
||||
Placeholder::GpgSignerPrimaryFingerprint => write!(f, "%GP"),
|
||||
Placeholder::GpgSignerTrustLevel => write!(f, "%GT"),
|
||||
Placeholder::ReflogSelector => write!(f, "%gD"),
|
||||
Placeholder::ReflogSelectorShort => write!(f, "%gd"),
|
||||
Placeholder::ReflogIdentity => write!(f, "%gn"),
|
||||
Placeholder::ReflogIdentityMailmap => write!(f, "%gN"),
|
||||
Placeholder::ReflogIdentityEmail => write!(f, "%ge"),
|
||||
Placeholder::ReflogIdentityEmailMailmap => write!(f, "%gE"),
|
||||
Placeholder::ReflogSubject => write!(f, "%gs"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/// Get reference names from the decoration printed from `git log` with `--decorate=full` and
|
||||
/// `%D` format ([Placeholder::RefNames]).
|
||||
///
|
||||
/// Such names can be further parsed, e.g. into [`Ref`].
|
||||
///
|
||||
/// ```
|
||||
/// use ide_ci::programs::git::pretty_format::refs_from_decoration;
|
||||
/// let text = "HEAD -> refs/heads/wip/mwu/stable-release, tag: refs/tags/2022.1.1-nightly.2022-11-09, refs/heads/develop";
|
||||
/// let refs = refs_from_decoration(text);
|
||||
/// assert_eq!(refs, vec![
|
||||
/// "HEAD",
|
||||
/// "refs/heads/wip/mwu/stable-release",
|
||||
/// "refs/tags/2022.1.1-nightly.2022-11-09",
|
||||
/// "refs/heads/develop"
|
||||
/// ]);
|
||||
/// ```
|
||||
pub fn refs_from_decoration(text: &str) -> Vec<&str> {
|
||||
text.split(',')
|
||||
.map(|s| s.trim())
|
||||
.filter(|s| !s.is_empty())
|
||||
.flat_map(|s| {
|
||||
if let Some(stripped) = s.strip_prefix("HEAD -> ") {
|
||||
vec!["HEAD", stripped]
|
||||
} else {
|
||||
vec![s]
|
||||
}
|
||||
.into_iter()
|
||||
})
|
||||
.map(|s| s.trim_start_matches("tag: "))
|
||||
.collect_vec()
|
||||
}
|
116
build/ci_utils/src/programs/git/ref.rs
Normal file
116
build/ci_utils/src/programs/git/ref.rs
Normal file
@ -0,0 +1,116 @@
|
||||
//! Code for representing git references.
|
||||
//!
|
||||
//! See [Git Reference Documentation](https://git-scm.com/book/en/v2/Git-Internals-Git-References).
|
||||
|
||||
use crate::prelude::*;
|
||||
|
||||
|
||||
|
||||
/// A git reference.
|
||||
///
|
||||
/// It is a name that points to an object in the repository or another reference.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use enso_build_base::prelude::FromString;
|
||||
/// use ide_ci::programs::git::Ref;
|
||||
/// let reference = Ref::from_str("refs/heads/master").unwrap();
|
||||
/// assert_eq!(reference, Ref::Branch { name: "master".into() });
|
||||
/// ```
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Ref {
|
||||
/// The current branch.
|
||||
///
|
||||
/// HEAD is a reference to one of the heads in your repository, except when using a detached
|
||||
/// HEAD, in which case it directly references an arbitrary commit.
|
||||
Head,
|
||||
/// Head of a local branch.
|
||||
Branch {
|
||||
/// Branch name, stripped of the `refs/heads/` prefix.
|
||||
name: String,
|
||||
},
|
||||
/// Head of a remote branch.
|
||||
RemoteBranch {
|
||||
/// Name of the remote.
|
||||
remote: String,
|
||||
/// Branch name, stripped of the `refs/remotes/<remote>/` prefix.
|
||||
branch: String,
|
||||
},
|
||||
/// A ref under refs/tags/ namespace that points to an object of an arbitrary type (typically a
|
||||
/// tag points to either a tag or a commit object).
|
||||
Tag {
|
||||
/// Name of the tag. Any trailing `^{}` suffix is stripped.
|
||||
name: String,
|
||||
/// Whether the tag was peeled, i.e. had `^{}` suffix.
|
||||
peeled: bool,
|
||||
},
|
||||
/// All other refs, e.g. `refs/notes/commits` or `MERGE_HEAD`.
|
||||
Other {
|
||||
/// Full reference name.
|
||||
name: String,
|
||||
},
|
||||
}
|
||||
|
||||
impl std::str::FromStr for Ref {
|
||||
type Err = anyhow::Error;
|
||||
|
||||
/// Parse the reference from the full decorated name, like `refs/heads/main`.
|
||||
/// See: <https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefdecorateadecorate>
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use enso_build_base::prelude::*;
|
||||
/// use ide_ci::programs::git::r#ref::Ref;
|
||||
/// fn main() -> Result {
|
||||
/// assert_eq!(Ref::from_str("HEAD")?, Ref::Head);
|
||||
/// assert_eq!(Ref::from_str("refs/heads/main")?, Ref::Branch { name: "main".into() });
|
||||
/// assert_eq!(Ref::from_str("refs/remotes/origin/main")?, Ref::RemoteBranch {
|
||||
/// remote: "origin".into(),
|
||||
/// branch: "main".into(),
|
||||
/// });
|
||||
/// assert_eq!(Ref::from_str("refs/tags/2022.1.1-nightly.2022-11-09")?, Ref::Tag {
|
||||
/// name: "2022.1.1-nightly.2022-11-09".into(),
|
||||
/// peeled: false,
|
||||
/// });
|
||||
/// assert_eq!(Ref::from_str("refs/tags/2022.1.1-nightly.2022-11-09^{}")?, Ref::Tag {
|
||||
/// name: "2022.1.1-nightly.2022-11-09".into(),
|
||||
/// peeled: true,
|
||||
/// });
|
||||
/// Ok(())
|
||||
/// }
|
||||
/// ```
|
||||
fn from_str(name: &str) -> Result<Self> {
|
||||
let name = name.trim();
|
||||
Ok(if name == "HEAD" {
|
||||
Ref::Head
|
||||
} else if let Some(name) = name.strip_prefix("refs/heads/") {
|
||||
Ref::Branch { name: name.to_string() }
|
||||
} else if let Some(name) = name.strip_prefix("refs/remotes/") {
|
||||
let mut parts = name.splitn(2, '/');
|
||||
let remote = parts.next().context("Missing remote name.")?.to_string();
|
||||
let branch = parts.next().context("Missing branch name.")?.to_string();
|
||||
Ref::RemoteBranch { remote, branch }
|
||||
} else if let Some(name) = name.strip_prefix("refs/tags/") {
|
||||
if let Some(peeled_name) = name.strip_suffix("^{}") {
|
||||
Ref::Tag { name: peeled_name.to_string(), peeled: true }
|
||||
} else {
|
||||
Ref::Tag { name: name.to_string(), peeled: false }
|
||||
}
|
||||
} else {
|
||||
bail!("Unknown ref name: `{}`.", name);
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Ref {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Ref::Head => write!(f, "HEAD"),
|
||||
Ref::Branch { name } => write!(f, "refs/heads/{name}"),
|
||||
Ref::RemoteBranch { remote, branch } => write!(f, "refs/remotes/{remote}/{branch}"),
|
||||
Ref::Tag { name, peeled } =>
|
||||
write!(f, "refs/tags/{name}{}", if *peeled { "^{}" } else { "" }),
|
||||
Ref::Other { name } => write!(f, "{name}"),
|
||||
}
|
||||
}
|
||||
}
|
@ -160,10 +160,6 @@ pub struct Cli {
|
||||
#[clap(long, global = true, default_value_t = default_repo_remote(), enso_env())]
|
||||
pub repo_remote: Repo,
|
||||
|
||||
/// The build kind. Affects the default version generation.
|
||||
#[clap(long, global = true, arg_enum, default_value_t = enso_build::version::Kind::Dev, env = *crate::ENSO_BUILD_KIND)]
|
||||
pub build_kind: enso_build::version::Kind,
|
||||
|
||||
/// Platform to target. Currently cross-compilation is enabled only for GUI/IDE (without
|
||||
/// Project Manager) on platforms where Electron Builder supports this.
|
||||
#[clap(long, global = true, default_value_t = TARGET_OS, enso_env(), possible_values=[OS::Windows.as_str(), OS::Linux.as_str(), OS::MacOS.as_str()])]
|
||||
|
@ -14,14 +14,24 @@ pub struct DeployRuntime {
|
||||
#[derive(Args, Clone, Copy, Debug)]
|
||||
pub struct DeployGui {}
|
||||
|
||||
/// Structure that represents `promote` subcommand arguments.
|
||||
#[derive(Args, Clone, Copy, Debug)]
|
||||
pub struct Promote {
|
||||
/// What kind of version is to be created.
|
||||
#[clap(arg_enum)]
|
||||
pub designation: enso_build::version::promote::Designation,
|
||||
}
|
||||
|
||||
#[derive(Subcommand, Clone, Debug)]
|
||||
pub enum Action {
|
||||
/// Create a release draft on GitHub.
|
||||
CreateDraft,
|
||||
/// Build the runtime image and push it to ECR.
|
||||
DeployRuntime(DeployRuntime),
|
||||
/// Upload the GUI to the S3 Bucket and notify.
|
||||
DeployGui(DeployGui),
|
||||
Publish,
|
||||
Promote(Promote),
|
||||
}
|
||||
|
||||
#[derive(Args, Clone, Debug)]
|
||||
|
@ -7,20 +7,30 @@
|
||||
use enso_build_cli::prelude::*;
|
||||
|
||||
use enso_build::setup_octocrab;
|
||||
use ide_ci::github::Repo;
|
||||
use ide_ci::github::RepoRef;
|
||||
use ide_ci::io::web::handle_error_response;
|
||||
use ide_ci::log::setup_logging;
|
||||
|
||||
|
||||
|
||||
const REPO: RepoRef = RepoRef { owner: "enso-org", name: "enso" };
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result {
|
||||
setup_logging()?;
|
||||
info!("Removing draft releases from GitHub.");
|
||||
let octo = setup_octocrab().await?;
|
||||
let repo = Repo::from_str("enso-org/enso")?.handle(&octo);
|
||||
|
||||
if let Err(e) = octo.current().user().await {
|
||||
bail!(
|
||||
"Failed to authenticate: {}.\nBeing authenticated is necessary to see draft releases.",
|
||||
e
|
||||
);
|
||||
}
|
||||
let repo = REPO.handle(&octo);
|
||||
info!("Fetching all releases.");
|
||||
let releases = repo.all_releases().await?;
|
||||
let draft_releases = releases.into_iter().filter(|r| r.draft);
|
||||
let draft_releases = releases.into_iter().filter(|r| r.draft).collect_vec();
|
||||
info!("Found {} draft releases.", draft_releases.len());
|
||||
for release in draft_releases {
|
||||
let id = release.id;
|
||||
|
||||
@ -30,6 +40,6 @@ async fn main() -> Result {
|
||||
handle_error_response(response).await?;
|
||||
}
|
||||
|
||||
|
||||
info!("Done.");
|
||||
Ok(())
|
||||
}
|
||||
|
@ -5,7 +5,10 @@ use crate::ci_gen::job::plain_job;
|
||||
use crate::ci_gen::job::plain_job_customized;
|
||||
use crate::ci_gen::job::RunsOn;
|
||||
|
||||
use enso_build::version;
|
||||
use enso_build::version::promote::Designation;
|
||||
use enso_build::version::ENSO_EDITION;
|
||||
use enso_build::version::ENSO_RELEASE_MODE;
|
||||
use enso_build::version::ENSO_VERSION;
|
||||
use ide_ci::actions::workflow::definition::checkout_repo_step;
|
||||
use ide_ci::actions::workflow::definition::is_non_windows_runner;
|
||||
use ide_ci::actions::workflow::definition::is_windows_runner;
|
||||
@ -19,6 +22,7 @@ use ide_ci::actions::workflow::definition::Concurrency;
|
||||
use ide_ci::actions::workflow::definition::Event;
|
||||
use ide_ci::actions::workflow::definition::Job;
|
||||
use ide_ci::actions::workflow::definition::JobArchetype;
|
||||
use ide_ci::actions::workflow::definition::JobSecrets;
|
||||
use ide_ci::actions::workflow::definition::PullRequest;
|
||||
use ide_ci::actions::workflow::definition::PullRequestActivityType;
|
||||
use ide_ci::actions::workflow::definition::Push;
|
||||
@ -26,9 +30,11 @@ use ide_ci::actions::workflow::definition::RunnerLabel;
|
||||
use ide_ci::actions::workflow::definition::Schedule;
|
||||
use ide_ci::actions::workflow::definition::Step;
|
||||
use ide_ci::actions::workflow::definition::Workflow;
|
||||
use ide_ci::actions::workflow::definition::WorkflowCall;
|
||||
use ide_ci::actions::workflow::definition::WorkflowDispatch;
|
||||
use ide_ci::actions::workflow::definition::WorkflowDispatchInput;
|
||||
use ide_ci::actions::workflow::definition::WorkflowDispatchInputType;
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
|
||||
// ==============
|
||||
@ -54,6 +60,12 @@ pub const DEFAULT_BRANCH_NAME: &str = "develop";
|
||||
|
||||
pub const RELEASE_CONCURRENCY_GROUP: &str = "release";
|
||||
|
||||
pub const DESIGNATOR_INPUT_NAME: &str = "designator";
|
||||
|
||||
pub const PROMOTE_WORKFLOW_PATH: &str = "./.github/workflows/promote.yml";
|
||||
|
||||
pub const RELEASE_WORKFLOW_PATH: &str = "./.github/workflows/release.yml";
|
||||
|
||||
/// Secrets set up in our organization.
|
||||
///
|
||||
/// To manage, see: https://github.com/organizations/enso-org/settings/secrets/actions
|
||||
@ -92,6 +104,12 @@ pub fn release_concurrency() -> Concurrency {
|
||||
Concurrency::new(RELEASE_CONCURRENCY_GROUP)
|
||||
}
|
||||
|
||||
/// Get expression that gets input from the workflow dispatch. See:
|
||||
/// <https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#providing-inputs>
|
||||
pub fn get_input_expression(name: impl Into<String>) -> String {
|
||||
wrap_expression(format!("inputs.{}", name.into()))
|
||||
}
|
||||
|
||||
impl RunsOn for DeluxeRunner {
|
||||
fn runs_on(&self) -> Vec<RunnerLabel> {
|
||||
vec![RunnerLabel::MwuDeluxe]
|
||||
@ -179,20 +197,19 @@ pub fn setup_script_and_steps(command_line: impl AsRef<str>) -> Vec<Step> {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DraftRelease;
|
||||
impl JobArchetype for DraftRelease {
|
||||
fn job(os: OS) -> Job {
|
||||
let name = "Create release draft".into();
|
||||
fn job(&self, os: OS) -> Job {
|
||||
let name = "Create a release draft.".into();
|
||||
|
||||
let prepare_step = run("release create-draft").with_id(Self::PREPARE_STEP_ID);
|
||||
|
||||
let mut steps = setup_script_steps();
|
||||
steps.push(prepare_step);
|
||||
|
||||
let mut ret = Job { name, runs_on: runs_on(os), steps, ..default() };
|
||||
Self::expose_outputs(&mut ret);
|
||||
self.expose_outputs(&mut ret);
|
||||
ret
|
||||
}
|
||||
|
||||
fn outputs() -> BTreeMap<String, Vec<String>> {
|
||||
fn outputs(&self) -> BTreeMap<String, Vec<String>> {
|
||||
let mut ret = BTreeMap::new();
|
||||
ret.insert(Self::PREPARE_STEP_ID.into(), vec![
|
||||
"ENSO_VERSION".into(),
|
||||
@ -209,7 +226,7 @@ impl DraftRelease {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PublishRelease;
|
||||
impl JobArchetype for PublishRelease {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
let mut ret = plain_job(&os, "Publish release", "release publish");
|
||||
ret.expose_secret_as(secret::ARTEFACT_S3_ACCESS_KEY_ID, "AWS_ACCESS_KEY_ID");
|
||||
ret.expose_secret_as(secret::ARTEFACT_S3_SECRET_ACCESS_KEY, "AWS_SECRET_ACCESS_KEY");
|
||||
@ -221,13 +238,39 @@ impl JobArchetype for PublishRelease {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UploadIde;
|
||||
impl JobArchetype for UploadIde {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Build IDE", "ide upload --wasm-source current-ci-run --backend-source release --backend-release ${{env.ENSO_RELEASE_ID}}", |step|
|
||||
vec![expose_os_specific_signing_secret(os, step)]
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PromoteReleaseJob;
|
||||
impl JobArchetype for PromoteReleaseJob {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
let command = format!("release promote {}", get_input_expression(DESIGNATOR_INPUT_NAME));
|
||||
let mut job = plain_job_customized(&os, "Promote release", command, |step| {
|
||||
vec![step.with_id(Self::PROMOTE_STEP_ID)]
|
||||
});
|
||||
self.expose_outputs(&mut job);
|
||||
job
|
||||
}
|
||||
|
||||
fn outputs(&self) -> BTreeMap<String, Vec<String>> {
|
||||
let mut ret = BTreeMap::new();
|
||||
ret.insert(Self::PROMOTE_STEP_ID.into(), vec![
|
||||
ENSO_VERSION.name.to_string(),
|
||||
ENSO_EDITION.name.to_string(),
|
||||
ENSO_RELEASE_MODE.name.to_string(),
|
||||
]);
|
||||
ret
|
||||
}
|
||||
}
|
||||
impl PromoteReleaseJob {
|
||||
pub const PROMOTE_STEP_ID: &'static str = "promote";
|
||||
}
|
||||
|
||||
/// Generate a workflow that checks if the changelog has been updated (if needed).
|
||||
pub fn changelog() -> Result<Workflow> {
|
||||
use PullRequestActivityType::*;
|
||||
@ -256,27 +299,24 @@ pub fn nightly() -> Result<Workflow> {
|
||||
..default()
|
||||
};
|
||||
|
||||
let mut workflow = Workflow {
|
||||
on,
|
||||
name: "Nightly Release".into(),
|
||||
concurrency: Some(release_concurrency()),
|
||||
..default()
|
||||
};
|
||||
let mut workflow = Workflow { on, name: "Nightly Release".into(), ..default() };
|
||||
|
||||
add_release_steps(&mut workflow, version::Kind::Nightly)?;
|
||||
let job = workflow_call_job("Promote nightly", PROMOTE_WORKFLOW_PATH)
|
||||
.with_with(DESIGNATOR_INPUT_NAME, Designation::Nightly.as_ref());
|
||||
workflow.add_job(job);
|
||||
Ok(workflow)
|
||||
}
|
||||
|
||||
fn add_release_steps(workflow: &mut Workflow, kind: version::Kind) -> Result {
|
||||
let prepare_job_id = workflow.add::<DraftRelease>(PRIMARY_OS);
|
||||
let build_wasm_job_id = workflow.add::<job::BuildWasm>(PRIMARY_OS);
|
||||
fn add_release_steps(workflow: &mut Workflow) -> Result {
|
||||
let prepare_job_id = workflow.add(PRIMARY_OS, DraftRelease);
|
||||
let build_wasm_job_id = workflow.add(PRIMARY_OS, job::BuildWasm);
|
||||
let mut packaging_job_ids = vec![];
|
||||
|
||||
// Assumed, because Linux is necessary to deploy ECR runtime image.
|
||||
assert!(TARGETED_SYSTEMS.contains(&OS::Linux));
|
||||
for os in TARGETED_SYSTEMS {
|
||||
let backend_job_id = workflow.add_dependent::<job::UploadBackend>(os, [&prepare_job_id]);
|
||||
let build_ide_job_id = workflow.add_dependent::<UploadIde>(os, [
|
||||
let backend_job_id = workflow.add_dependent(os, job::UploadBackend, [&prepare_job_id]);
|
||||
let build_ide_job_id = workflow.add_dependent(os, UploadIde, [
|
||||
&prepare_job_id,
|
||||
&backend_job_id,
|
||||
&build_wasm_job_id,
|
||||
@ -289,11 +329,11 @@ fn add_release_steps(workflow: &mut Workflow, kind: version::Kind) -> Result {
|
||||
if os == OS::Linux {
|
||||
let runtime_requirements = [&prepare_job_id, &backend_job_id];
|
||||
let upload_runtime_job_id =
|
||||
workflow.add_dependent::<job::DeployRuntime>(os, runtime_requirements);
|
||||
workflow.add_dependent(os, job::DeployRuntime, runtime_requirements);
|
||||
packaging_job_ids.push(upload_runtime_job_id);
|
||||
|
||||
let gui_requirements = [build_ide_job_id];
|
||||
let deploy_gui_job_id = workflow.add_dependent::<job::DeployGui>(os, gui_requirements);
|
||||
let deploy_gui_job_id = workflow.add_dependent(os, job::DeployGui, gui_requirements);
|
||||
packaging_job_ids.push(deploy_gui_job_id);
|
||||
}
|
||||
}
|
||||
@ -304,27 +344,76 @@ fn add_release_steps(workflow: &mut Workflow, kind: version::Kind) -> Result {
|
||||
};
|
||||
|
||||
|
||||
let _publish_job_id = workflow.add_dependent::<PublishRelease>(PRIMARY_OS, publish_deps);
|
||||
|
||||
let global_env = [(*crate::ENSO_BUILD_KIND, kind.as_ref()), ("RUST_BACKTRACE", "full")];
|
||||
for (var_name, value) in global_env {
|
||||
workflow.env(var_name, value);
|
||||
}
|
||||
|
||||
let _publish_job_id = workflow.add_dependent(PRIMARY_OS, PublishRelease, publish_deps);
|
||||
workflow.env("RUST_BACKTRACE", "full");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn release_candidate() -> Result<Workflow> {
|
||||
let on = Event { workflow_dispatch: Some(default()), ..default() };
|
||||
pub fn workflow_call_job(name: impl Into<String>, path: impl Into<String>) -> Job {
|
||||
Job {
|
||||
name: name.into(),
|
||||
uses: Some(path.into()),
|
||||
secrets: Some(JobSecrets::Inherit),
|
||||
..default()
|
||||
}
|
||||
}
|
||||
|
||||
pub fn call_release_job(version_input_expr: &str) -> Job {
|
||||
workflow_call_job("Release", RELEASE_WORKFLOW_PATH).with_with("version", version_input_expr)
|
||||
}
|
||||
|
||||
pub fn release() -> Result<Workflow> {
|
||||
let version_input = WorkflowDispatchInput::new_string(
|
||||
"What version number this release should get.",
|
||||
true,
|
||||
None::<String>,
|
||||
);
|
||||
let workflow_dispatch = WorkflowDispatch::default().with_input("version", version_input);
|
||||
let workflow_call = WorkflowCall::try_from(workflow_dispatch.clone())?;
|
||||
let on = Event {
|
||||
workflow_dispatch: Some(workflow_dispatch),
|
||||
workflow_call: Some(workflow_call),
|
||||
..default()
|
||||
};
|
||||
|
||||
let mut workflow = Workflow {
|
||||
on,
|
||||
name: "Release Candidate".into(),
|
||||
name: "Release".into(),
|
||||
concurrency: Some(release_concurrency()),
|
||||
..default()
|
||||
};
|
||||
|
||||
add_release_steps(&mut workflow, version::Kind::Rc)?;
|
||||
add_release_steps(&mut workflow)?;
|
||||
let version_input_expression = get_input_expression("version");
|
||||
workflow.env(ENSO_EDITION.name, &version_input_expression);
|
||||
workflow.env(ENSO_VERSION.name, &version_input_expression);
|
||||
Ok(workflow)
|
||||
}
|
||||
|
||||
|
||||
pub fn promote() -> Result<Workflow> {
|
||||
let designator = WorkflowDispatchInput::new_choice(
|
||||
"What kind of release should be promoted.",
|
||||
true,
|
||||
Designation::iter().map(|d| d.as_ref().to_string()),
|
||||
None::<String>,
|
||||
)?;
|
||||
let workflow_dispatch =
|
||||
WorkflowDispatch::default().with_input(DESIGNATOR_INPUT_NAME, designator);
|
||||
let on = Event {
|
||||
workflow_call: Some(WorkflowCall::try_from(workflow_dispatch.clone())?),
|
||||
workflow_dispatch: Some(workflow_dispatch),
|
||||
..default()
|
||||
};
|
||||
let mut workflow = Workflow { on, name: "Generate a new version".into(), ..default() };
|
||||
let promote_job_id = workflow.add(PRIMARY_OS, PromoteReleaseJob);
|
||||
|
||||
|
||||
let version_input = format!("needs.{promote_job_id}.outputs.{ENSO_VERSION}");
|
||||
let mut release_job = call_release_job(&wrap_expression(version_input));
|
||||
release_job.needs(&promote_job_id);
|
||||
workflow.add_job(release_job);
|
||||
|
||||
Ok(workflow)
|
||||
}
|
||||
|
||||
@ -340,10 +429,10 @@ pub fn typical_check_triggers() -> Event {
|
||||
pub fn gui() -> Result<Workflow> {
|
||||
let on = typical_check_triggers();
|
||||
let mut workflow = Workflow { name: "GUI CI".into(), on, ..default() };
|
||||
workflow.add::<job::CancelWorkflow>(PRIMARY_OS);
|
||||
workflow.add::<job::Lint>(PRIMARY_OS);
|
||||
workflow.add::<job::WasmTest>(PRIMARY_OS);
|
||||
workflow.add::<job::NativeTest>(PRIMARY_OS);
|
||||
workflow.add(PRIMARY_OS, job::CancelWorkflow);
|
||||
workflow.add(PRIMARY_OS, job::Lint);
|
||||
workflow.add(PRIMARY_OS, job::WasmTest);
|
||||
workflow.add(PRIMARY_OS, job::NativeTest);
|
||||
|
||||
// FIXME: Integration tests are currently always failing.
|
||||
// The should be reinstated when fixed.
|
||||
@ -353,14 +442,14 @@ pub fn gui() -> Result<Workflow> {
|
||||
|
||||
// Because WASM upload happens only for the Linux build, all other platforms needs to depend on
|
||||
// it.
|
||||
let wasm_job_linux = workflow.add::<job::BuildWasm>(OS::Linux);
|
||||
let wasm_job_linux = workflow.add(OS::Linux, job::BuildWasm);
|
||||
for os in TARGETED_SYSTEMS {
|
||||
if os != OS::Linux {
|
||||
// Linux was already added above.
|
||||
let _wasm_job = workflow.add::<job::BuildWasm>(os);
|
||||
let _wasm_job = workflow.add(os, job::BuildWasm);
|
||||
}
|
||||
let project_manager_job = workflow.add::<job::BuildBackend>(os);
|
||||
workflow.add_customized::<job::PackageIde>(os, |job| {
|
||||
let project_manager_job = workflow.add(os, job::BuildBackend);
|
||||
workflow.add_customized(os, job::PackageIde, |job| {
|
||||
job.needs.insert(wasm_job_linux.clone());
|
||||
job.needs.insert(project_manager_job);
|
||||
});
|
||||
@ -371,9 +460,9 @@ pub fn gui() -> Result<Workflow> {
|
||||
pub fn backend() -> Result<Workflow> {
|
||||
let on = typical_check_triggers();
|
||||
let mut workflow = Workflow { name: "Engine CI".into(), on, ..default() };
|
||||
workflow.add::<job::CancelWorkflow>(PRIMARY_OS);
|
||||
workflow.add(PRIMARY_OS, job::CancelWorkflow);
|
||||
for os in TARGETED_SYSTEMS {
|
||||
workflow.add::<job::CiCheckBackend>(os);
|
||||
workflow.add(os, job::CiCheckBackend);
|
||||
}
|
||||
Ok(workflow)
|
||||
}
|
||||
@ -413,6 +502,7 @@ pub fn generate(repo_root: &enso_build::paths::generated::RepoRootGithubWorkflow
|
||||
repo_root.scala_new_yml.write_as_yaml(&backend()?)?;
|
||||
repo_root.gui_yml.write_as_yaml(&gui()?)?;
|
||||
repo_root.benchmark_yml.write_as_yaml(&benchmark()?)?;
|
||||
repo_root.release_yml.write_as_yaml(&release_candidate()?)?;
|
||||
repo_root.release_yml.write_as_yaml(&release()?)?;
|
||||
repo_root.promote_yml.write_as_yaml(&promote()?)?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -70,7 +70,7 @@ pub fn plain_job_customized(
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CancelWorkflow;
|
||||
impl JobArchetype for CancelWorkflow {
|
||||
fn job(_os: OS) -> Job {
|
||||
fn job(&self, _os: OS) -> Job {
|
||||
Job {
|
||||
name: "Cancel Previous Runs".into(),
|
||||
// It is important that this particular job runs pretty much everywhere (we use x64,
|
||||
@ -87,7 +87,7 @@ impl JobArchetype for CancelWorkflow {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct Lint;
|
||||
impl JobArchetype for Lint {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(&os, "Lint", "lint")
|
||||
}
|
||||
}
|
||||
@ -95,7 +95,7 @@ impl JobArchetype for Lint {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct NativeTest;
|
||||
impl JobArchetype for NativeTest {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(&os, "Native GUI tests", "wasm test --no-wasm")
|
||||
}
|
||||
}
|
||||
@ -103,7 +103,7 @@ impl JobArchetype for NativeTest {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct WasmTest;
|
||||
impl JobArchetype for WasmTest {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(&os, "WASM GUI tests", "wasm test --no-native")
|
||||
}
|
||||
}
|
||||
@ -111,7 +111,7 @@ impl JobArchetype for WasmTest {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct IntegrationTest;
|
||||
impl JobArchetype for IntegrationTest {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(
|
||||
&os,
|
||||
"IDE integration tests",
|
||||
@ -123,7 +123,7 @@ impl JobArchetype for IntegrationTest {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BuildWasm;
|
||||
impl JobArchetype for BuildWasm {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(
|
||||
&os,
|
||||
"Build GUI (WASM)",
|
||||
@ -135,7 +135,7 @@ impl JobArchetype for BuildWasm {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct BuildBackend;
|
||||
impl JobArchetype for BuildBackend {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(&os, "Build Backend", "backend get")
|
||||
}
|
||||
}
|
||||
@ -143,7 +143,7 @@ impl JobArchetype for BuildBackend {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct UploadBackend;
|
||||
impl JobArchetype for UploadBackend {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job(&os, "Upload Backend", "backend upload")
|
||||
}
|
||||
}
|
||||
@ -151,7 +151,7 @@ impl JobArchetype for UploadBackend {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DeployRuntime;
|
||||
impl JobArchetype for DeployRuntime {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Upload Runtime to ECR", "release deploy-runtime", |step| {
|
||||
let step = step
|
||||
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN")
|
||||
@ -170,7 +170,7 @@ impl JobArchetype for DeployRuntime {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct DeployGui;
|
||||
impl JobArchetype for DeployGui {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Upload GUI to S3", "release deploy-gui", |step| {
|
||||
let step = step
|
||||
.with_secret_exposed_as("CI_PRIVATE_TOKEN", "GITHUB_TOKEN")
|
||||
@ -220,7 +220,7 @@ pub fn expose_os_specific_signing_secret(os: OS, step: Step) -> Step {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct PackageIde;
|
||||
impl JobArchetype for PackageIde {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(
|
||||
&os,
|
||||
"Package IDE",
|
||||
@ -233,7 +233,7 @@ impl JobArchetype for PackageIde {
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct CiCheckBackend;
|
||||
impl JobArchetype for CiCheckBackend {
|
||||
fn job(os: OS) -> Job {
|
||||
fn job(&self, os: OS) -> Job {
|
||||
plain_job_customized(&os, "Engine", "backend ci-check", |main_step| {
|
||||
vec![main_step, step::engine_test_reporter(os), step::stdlib_test_reporter(os)]
|
||||
})
|
||||
|
@ -75,6 +75,7 @@ use enso_build::source::ReleaseSource;
|
||||
use enso_build::source::Source;
|
||||
use enso_build::source::WatchTargetJob;
|
||||
use enso_build::source::WithDestination;
|
||||
use enso_build::version;
|
||||
use futures_util::future::try_join;
|
||||
use ide_ci::actions::workflow::is_in_env;
|
||||
use ide_ci::cache::Cache;
|
||||
@ -83,6 +84,7 @@ use ide_ci::fs::remove_if_exists;
|
||||
use ide_ci::global;
|
||||
use ide_ci::ok_ready_boxed;
|
||||
use ide_ci::programs::cargo;
|
||||
use ide_ci::programs::git;
|
||||
use ide_ci::programs::git::clean;
|
||||
use ide_ci::programs::rustc;
|
||||
use ide_ci::programs::Cargo;
|
||||
@ -121,13 +123,9 @@ impl Processor {
|
||||
pub async fn new(cli: &Cli) -> Result<Self> {
|
||||
let absolute_repo_path = cli.repo_path.absolutize()?;
|
||||
let octocrab = setup_octocrab().await?;
|
||||
let remote_repo = cli.repo_remote.handle(&octocrab);
|
||||
let versions = enso_build::version::deduce_or_generate(
|
||||
Ok(&remote_repo),
|
||||
cli.build_kind,
|
||||
&absolute_repo_path,
|
||||
)
|
||||
.await?;
|
||||
let git = git::new(absolute_repo_path.as_ref()).await?;
|
||||
let release_provider = || version::promote::releases_on_remote(&git);
|
||||
let versions = version::deduce_or_generate(release_provider).await?;
|
||||
let mut triple = TargetTriple::new(versions);
|
||||
triple.os = cli.target_os;
|
||||
triple.versions.publish().await?;
|
||||
@ -845,7 +843,8 @@ pub async fn main_internal(config: Option<enso_build::config::Config>) -> Result
|
||||
}
|
||||
Target::Release(release) => match release.action {
|
||||
Action::CreateDraft => {
|
||||
enso_build::release::draft_a_new_release(&ctx).await?;
|
||||
let commit = ide_ci::actions::env::GITHUB_SHA.get()?;
|
||||
enso_build::release::draft_a_new_release(&ctx, &commit).await?;
|
||||
}
|
||||
Action::DeployRuntime(args) => {
|
||||
enso_build::release::deploy_to_ecr(&ctx, args.ecr_repository).await?;
|
||||
@ -862,6 +861,10 @@ pub async fn main_internal(config: Option<enso_build::config::Config>) -> Result
|
||||
Action::Publish => {
|
||||
enso_build::release::publish_release(&ctx).await?;
|
||||
}
|
||||
Action::Promote(args) => {
|
||||
let crate::arg::release::Promote { designation } = args;
|
||||
enso_build::release::promote_release(&ctx, designation).await?;
|
||||
}
|
||||
},
|
||||
Target::CiGen => ci_gen::generate(
|
||||
&enso_build::paths::generated::RepoRootGithubWorkflows::new(cli.repo_path),
|
||||
|
@ -160,6 +160,7 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn foo() -> Result {
|
||||
let code = "enum Foo {
|
||||
#[arg]
|
||||
@ -169,10 +170,38 @@ mod tests {
|
||||
#[arg]
|
||||
Quux,
|
||||
}";
|
||||
let token_stream = syn::parse_str::<TokenStream>(code)?;
|
||||
let _token_stream = syn::parse_str::<TokenStream>(code)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Structure with AST of parenthesized sequence of assignments.
|
||||
///
|
||||
/// For example, `(a = 1, b = ToString::to_string)`.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Assignments {
|
||||
pub paren_token: syn::token::Paren,
|
||||
pub assignments: syn::punctuated::Punctuated<syn::ExprAssign, syn::Token![,]>,
|
||||
}
|
||||
|
||||
dbg!(token_stream);
|
||||
impl Parse for Assignments {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let content;
|
||||
let paren_token = syn::parenthesized!(content in input);
|
||||
let assignments = content.parse_terminated(syn::ExprAssign::parse)?;
|
||||
Ok(Self { paren_token, assignments })
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn parse_attribute() -> Result {
|
||||
let attribute = r#"(format = ToString :: to_string)"#;
|
||||
let token_stream = syn::parse_str::<TokenStream>(attribute)?;
|
||||
dbg!(&token_stream);
|
||||
let foo = syn::parse2::<Assignments>(token_stream)?;
|
||||
dbg!(foo);
|
||||
// let attribute = syn::parse2::<syn::Attribute>(token_stream)?;
|
||||
// dbg!(attribute);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
23
build/macros/tests/with_attr.rs
Normal file
23
build/macros/tests/with_attr.rs
Normal file
@ -0,0 +1,23 @@
|
||||
// use enso_build_base::prelude::*;
|
||||
//
|
||||
// use itertools::Itertools;
|
||||
//
|
||||
|
||||
// #[derive(enso_build_macros::Arg)]
|
||||
// pub enum Foo {
|
||||
// Bar,
|
||||
// #[arg(format = ToString::to_string)]
|
||||
// TaraPon(u32),
|
||||
// }
|
||||
//
|
||||
// #[test]
|
||||
// fn test_argument_formatting() {
|
||||
// let bar = Foo::Bar;
|
||||
// assert_eq!(bar.into_iter().collect_vec(), vec![OsString::from("--bar")]);
|
||||
//
|
||||
// let tara_pon = Foo::TaraPon(42);
|
||||
// assert_eq!(tara_pon.into_iter().collect_vec(), vec![
|
||||
// OsString::from("--tara-pon"),
|
||||
// OsString::from("42")
|
||||
// ]);
|
||||
// }
|
@ -22,18 +22,15 @@ This document describes the infrastructure for Enso's automated nightly builds.
|
||||
|
||||
The build can be triggered by two possible events:
|
||||
|
||||
- automatically, at 4am UTC after each working day (that is, on Tuesday to
|
||||
- automatically, at 5am UTC after each working day (that is, on Tuesday to
|
||||
Saturday),
|
||||
- manually, if a commit with message containing `[release: nightly]` is pushed
|
||||
to the `main` branch.
|
||||
- manually, dispatching the
|
||||
[nightly.yml](https://github.com/enso-org/enso/actions/workflows/nightly.yml)
|
||||
workflow.
|
||||
|
||||
The nightly build is based off of the state of the `main` branch at the moment
|
||||
when it was triggered.
|
||||
|
||||
However, when a nightly build is triggered (by any of the two above conditions),
|
||||
it will only proceed if there are any changes. That is, if the current commit is
|
||||
the same as the one used for the previous nightly build, the build will not
|
||||
proceed because there are no changes.
|
||||
The nightly build is based off of the state of the `develop` branch at the
|
||||
moment when it was triggered, unless a different branch has been specified when
|
||||
triggering the build.
|
||||
|
||||
Thanks to
|
||||
[GitHub's concurrency settings](https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions#concurrency),
|
||||
@ -43,48 +40,48 @@ the first one finishes.
|
||||
|
||||
## Nightly Build Versions
|
||||
|
||||
The nightly build will modify the version (which is set in `build.sbt`) to
|
||||
indicate that this is a specific nightly build.
|
||||
Build script automatically generates a new version for each nightly build, based
|
||||
on the current date and previous tags.
|
||||
|
||||
If the version does not include a `SNAPSHOT` suffix, it is added. Then the
|
||||
current date is appended. Moreover, if the build is not the first nightly build
|
||||
to be done on a given day, an increasing numeric suffix is appended, to
|
||||
differentiate between builds from a single day.
|
||||
The nightly release version is consists of the following parts:
|
||||
|
||||
For example, if `build.sbt` specifies the version to be `1.2.3` or
|
||||
`1.2.3-SNAPSHOT`, the nightly build based off of that version, triggered on 1st
|
||||
of February 2021 will result in version `1.2.3-SNAPSHOT.2021-02-01`. If a
|
||||
subsequent nightly build is triggered on the same day it will be
|
||||
`1.2.3-SNAPSHOT.2021-02-01.1` etc.
|
||||
- core version (`major.minor.patch`), which is the next stable version after the
|
||||
last release,
|
||||
- prerelease suffix (`-nightly.YYYY.MM.DD`), where `YYYY.MM.DD` is the date of
|
||||
the build,
|
||||
- optionally, index suffix (`.N`), where `N` is the index of the nightly build
|
||||
on a given day.
|
||||
|
||||
Only the 3 most recent nightly builds are kept in the repository, any older
|
||||
builds are removed from the releases page and their corresponding tags are also
|
||||
removed.
|
||||
For example, if the last stable release was `2021.1.1`, the next nightly build
|
||||
will be `2021.2.0-nightly.2021.05.10`.
|
||||
|
||||
Nightly releases are periodically removed from the GitHub releases page, so they
|
||||
should not be used for anything other than testing.
|
||||
|
||||
As each release has a default edition associated with it, so do the nightlies.
|
||||
For the first nightly released on a given date, the edition associated with it
|
||||
will be named `nightly-2021-02-01`. For subsequent nightly releases on the same
|
||||
day, a prefix will be added in the same way as with versions. So for example the
|
||||
second release on that day will be associated with edition
|
||||
`nightly-2021-02-01.1` etc.
|
||||
We follow convention that the edition name is the same as the version. This
|
||||
convention should not be relied upon, as it may change in the future.
|
||||
|
||||
## Release Notes
|
||||
|
||||
Each PR should update the first, `Enso Next`, section in `RELEASES.md`. These
|
||||
The release notes for nightly builds are generated automatically from the
|
||||
template in `build/build/release-body.md`. The template is filled with a number
|
||||
of placeholders, which are then replaced with the actual values. Please refer to
|
||||
the file to learn more.
|
||||
|
||||
## Changelog
|
||||
|
||||
Each PR should update the first, `Enso Next`, section in `CHANGELOG.md`. These
|
||||
changes will be later moved to the specific section for the full release. This
|
||||
section is also used to fill the release notes for the nightly builds.
|
||||
|
||||
Most PRs should update the release notes, so there is a PR check that ensures
|
||||
the file was modified. However in some situations there is no need to update the
|
||||
notes. If any commit included as part of a PR includes `[no-changelog]` within
|
||||
its message, that check is ignored.
|
||||
the file was modified. However, in some situations there is no need to update
|
||||
the notes. In such case `CI: No changelog needed` label should be added to the
|
||||
PR, so the check is skipped.
|
||||
|
||||
The changelog should keep consistent formatting:
|
||||
|
||||
- each version should be delimited by a top-level section (`#` in Markdown),
|
||||
- the first section should always be called `Enso Next`,
|
||||
- all subsequent sections should be called `Enso <version> (<date>)`.
|
||||
|
||||
A heuristic check is ran for PRs, checking that at least the first two sections
|
||||
satisfy the above requirements - which is necessary for the nightly build
|
||||
pipeline to be able to correctly infer the release notes.
|
||||
|
@ -45,12 +45,14 @@ Where `a.b.c-tag` is the version string, `a` is the major version, `b`, is the
|
||||
minor version, `c` is the patch version, and `tag` is additional metadata, the
|
||||
following hold:
|
||||
|
||||
- major version `a` represents the year of the release, e.g. `2020.1.1` is the
|
||||
first release of 2020.
|
||||
- Breaking changes to language behaviour or the public API will result in a
|
||||
major version increase.
|
||||
minor version increase.
|
||||
- Addition of functionality in a backwards-compatible manner will result in a
|
||||
minor version increase.
|
||||
- Backwards-compatible bug fixes will result in a patch version increase.
|
||||
- The tag will indicate pre-release or beta versions, and will increase when any
|
||||
- The tag will indicate pre-release versions, and will increase when any
|
||||
pre-release change is made. These are not intended to be stable.
|
||||
|
||||
### Launcher Versioning
|
||||
@ -58,162 +60,49 @@ following hold:
|
||||
The launcher is released alongside Enso releases, so the launcher version is
|
||||
tied to the Enso version that it is released with.
|
||||
|
||||
## Release Branches
|
||||
|
||||
A release branch in the Enso repository is a branch prefixed with `release/`.
|
||||
Release branches obey the following rules:
|
||||
|
||||
- One release branch exists per major version, and is named `release/n.x`, where
|
||||
`n` is the major version, and the rest is literal.
|
||||
- A release branch must contain _tags_ corresponding to released versions of
|
||||
Enso. Once a release has been made, no further changes may be made to that
|
||||
release.
|
||||
- A tagged release must contain a `RELEASES.md` file that describes the changes
|
||||
contained in that release.
|
||||
|
||||
It should be noted that general development still takes place on the `main`
|
||||
branch of the repository.
|
||||
|
||||
## Release Workflow
|
||||
|
||||
Enso does not use release branches, but instead uses tags to mark releases. The
|
||||
same commit may be tagged multiple times, once for each release that it is a
|
||||
part of.
|
||||
|
||||
Cutting a release for Enso proceeds as follows:
|
||||
|
||||
1. If no release branch exists for the current major version, one should be
|
||||
created.
|
||||
2. Create a branch called `wip/<initials/release-bump`. On this branch, ensure
|
||||
that the release notes are up-to-date in `RELEASES.md` (follow the existing
|
||||
format), and that the new version number and edition name have been set in
|
||||
`build.sbt`. These new versions usually involve removing `-SNAPSHOT` from the
|
||||
versions. This version and edition name should _not_ contain `SNAPSHOT`.
|
||||
3. Open a PR for this branch into `main`.
|
||||
4. Once the changes have been reviewed, merge the PR into main (getting commit
|
||||
hash `xxxxxxx`). The message should be `Prepare for the $version release` as
|
||||
this message has semantic meaning (it's used in the nightly tooling). Just
|
||||
before merging, remember to notify the team on Discord to suppress any other
|
||||
merges to the `main` branch until the next step (bumping versions) is
|
||||
completed.
|
||||
5. Immediately push a commit to `main` that updates the version and edition in
|
||||
`build.sbt` to the new snapshot version. If unclear, bump the patch version
|
||||
by one and append `-SNAPSHOT` (e.g. `0.2.10` becomes `0.2.11-SNAPSHOT`). The
|
||||
edition name should have the number after the dot increased and `-SNAPSHOT`
|
||||
appended, so that `2021.3` becomes `2021.4-SNAPSHOT`. The only exception is
|
||||
when making the first release in a new year, where the first number should be
|
||||
bumped to the next year and the second number should be set to 1, for example
|
||||
`2022.1`. The message should be `Bump the snapshot version`.
|
||||
6. Find the commit hash of the last "Bump the snapshot version" commit. Let's
|
||||
say this is `yyyyyyy`.
|
||||
7. Run a `rebase --onto` the release branch from the `yyyyyyy` commit to the
|
||||
`xxxxxxx` commit. For example:
|
||||
1. Ensure that the release notes are up to date and that the top header refers
|
||||
to the version that is being released.
|
||||
2. Invoke the "Promote Release" workflow, either by:
|
||||
|
||||
```
|
||||
git rebase --onto origin/release/0.x yyyyyyy~1 xxxxxxx
|
||||
```
|
||||
- Triggering it using
|
||||
[web interface](https://github.com/enso-org/enso/actions/workflows/promote.yml);
|
||||
- Triggering it using [GitHub CLI](https://cli.github.com/). The following
|
||||
command should be issued from the root of the repository:
|
||||
```bash
|
||||
gh workflow run promote.yml -f designator=<designator>
|
||||
```
|
||||
where `<designator>` is denotes what kind of release is being made. It can
|
||||
be one of:
|
||||
- `stable` - a stable release (bump to minor version);
|
||||
- `patch` - a patch release (stable release with a bump to patch version);
|
||||
- `rc` - a release candidate for the next stable release;
|
||||
- `nightly` - a nightly release.
|
||||
|
||||
8. This will put you into a "detached HEAD" state at commit `zzzzzzz`, so you
|
||||
need to make a new branch: `git branch release-update zzzzzzz`
|
||||
9. This new branch is a fast-forward merge away from the release branch. Check
|
||||
out the release branch and then fast-forward merge `release-update` into it.
|
||||
For example:
|
||||
The `promote` workflow acts in the following steps:
|
||||
|
||||
```
|
||||
git checkout release/0.x
|
||||
git merge --ff-only release-update
|
||||
```
|
||||
- generate a new version string for the release;
|
||||
- create a release draft on GitHub;
|
||||
- build and upload assets for the release on all platforms;
|
||||
- publish the release on GitHub.
|
||||
|
||||
10. As long as the fast-forward proceeds cleanly, you can push the updated
|
||||
release branch to the origin.
|
||||
11. Create a tag for the commit at the HEAD of the release branch. It should be
|
||||
named as above. As the tag is signed, it must contain a message. The message
|
||||
should be `Enso <version>`. For example:
|
||||
The final step also tags the released commit with the version string.
|
||||
|
||||
```
|
||||
git tag --sign enso-0.2.11
|
||||
```
|
||||
|
||||
12. Push the tag to the remote (using `git push --follow-tags`). This will start
|
||||
the release build automatically.
|
||||
13. CI will create a draft release for this tag, as well as build and upload the
|
||||
appropriate artefacts. **Do not** create a release for this tag manually.
|
||||
14. The release notes for the version being released should be copied from the
|
||||
new section in `RELEASES.md` into the GitHub release description with the
|
||||
line breaks removed.
|
||||
15. The title of the release should be `Enso Engine <version>` (e.g.
|
||||
`Enso Engine 0.2.11`).
|
||||
16. Once verification has been performed, the release can be published. It
|
||||
should _not_ be a pre-releases as we reserve these for nightly builds.
|
||||
|
||||
### Breaking Release Workflow
|
||||
|
||||
If, however, the engine needs to release but the `HEAD` of `main` is not in a
|
||||
compatible state with the IDE, the process has to differ a little bit. Please
|
||||
note that the instructions here are more vague than the above, as exactly what
|
||||
is required may vary based on the situation.
|
||||
|
||||
Consider a scenario where there are four new commits since the last release:
|
||||
`wwwwwww`, `xxxxxxx`, `yyyyyyy`, `zzzzzzz`. The commit `yyyyyyy` contains
|
||||
breaking changes that are _not yet integrated with the IDE_. Releasing a package
|
||||
containing that commit (and those that depend on it) would break the IDE, but we
|
||||
nevertheless want to release as much as possible:
|
||||
|
||||
1. If no release branch exists for the current major version, one should be
|
||||
created.
|
||||
2. Rebase the commits that are wanted onto the release branch:
|
||||
|
||||
```
|
||||
git rebase --onto origin/release/0.x wwwwwww~1 xxxxxxx
|
||||
```
|
||||
|
||||
3. This will put you into a "detached HEAD" state at commit `aaaaaaa`, so you
|
||||
need to make a new branch: `git branch release-update aaaaaaa`, whose `HEAD`
|
||||
commit is the same as `xxxxxxx`.
|
||||
4. This new branch is a fast-forward merge away from the release branch. Check
|
||||
out the release branch and then fast-forward merge `release-update` into it.
|
||||
For example:
|
||||
|
||||
```
|
||||
git checkout release/0.x
|
||||
git merge --ff-only release-update
|
||||
```
|
||||
|
||||
5. On the release branch, ensure that the release notes are up to date in
|
||||
`RELEASES.md` (follow the existing format), and that the new version number
|
||||
and edition name have been set in `build.sbt`. This version and edition name
|
||||
should _not_ contain `SNAPSHOT`.
|
||||
6. Once this is done, create a tag for the commit at the HEAD of the release
|
||||
branch. It should be named as above. The tag message should be
|
||||
`Enso <version>`. For example:
|
||||
|
||||
```
|
||||
git tag --sign enso-0.2.11
|
||||
```
|
||||
|
||||
7. Push the tag to the remote. This will start the release build.
|
||||
8. CI will create a draft release for this tag, as well as build and upload the
|
||||
appropriate artefacts. **Do not** create a release for this tag manually.
|
||||
9. The release notes for the version being released should be copied from the
|
||||
new section in `RELEASES.md` into the GitHub release description with the
|
||||
line breaks removed.
|
||||
10. The title of the release should be `Enso Engine <version>` (e.g.
|
||||
`Enso Engine 0.2.11`).
|
||||
11. Check out the main branch, and then synchronise the changes to `RELEASES.md`
|
||||
on the release branch with the changes on `main`.
|
||||
12. In the same commit, Update the build version number in `build.sbt` to the
|
||||
new snapshot version. If unclear, bump the patch version by one and append
|
||||
`-SNAPSHOT` (e.g. `0.2.10` becomes `0.2.11-SNAPSHOT`). The edition name
|
||||
should have the number after the dot increased and `-SNAPSHOT` appended, so
|
||||
that `2021.3` becomes `2021.4-SNAPSHOT`. The message should be
|
||||
`Bump the snapshot version`.
|
||||
13. Push this commit into `origin/main`, or merge via PR if unable to directly
|
||||
push.
|
||||
|
||||
It is recommended that you instigate a freeze on merges to `main` whilst
|
||||
performing this process.
|
||||
3. If the release was stable or patch, immediately update the
|
||||
[changelog](../CHANGELOG.md) by adding a new header for the next release, and
|
||||
marking the released one with the version generated.
|
||||
|
||||
### Tag Naming
|
||||
|
||||
Tags for releases are named as follows `enso-version`, where `version` is the
|
||||
semver string (see [versioning](#versioning)) representing the version being
|
||||
released.
|
||||
Tags for releases are named as follows `version`, where `version` is the semver
|
||||
string (see [versioning](#versioning)) representing the version being released.
|
||||
|
||||
### Manifest Files
|
||||
|
||||
@ -378,88 +267,9 @@ that will persist the broken mark to S3 is not triggered for release drafts.
|
||||
> **When marking the release as broken, you should make sure that the workflow
|
||||
> persisting the broken mark to Se has succeeded and re-run it if necessary.**
|
||||
|
||||
### Release Notes
|
||||
|
||||
Release notes should contain a summary of the changes made between the last
|
||||
release and the current release. They should follow the template given below,
|
||||
and are contained in the `RELEASES.md` file in the repository root.
|
||||
|
||||
```md
|
||||
# Enso x.y.z (YYYY-MM-DD)
|
||||
|
||||
## Language
|
||||
|
||||
- A list of language-level changes.
|
||||
|
||||
## Interpreter/Runtime
|
||||
|
||||
- A list of changes to the Enso interpreter.
|
||||
|
||||
## Type System
|
||||
|
||||
- A list of type-system changes.
|
||||
|
||||
## Tooling
|
||||
|
||||
- A list of changes to the Enso language tooling.
|
||||
|
||||
## Libraries
|
||||
|
||||
- A list of changes to the Enso core libraries.
|
||||
|
||||
## Stabilised Features
|
||||
|
||||
- A list of stabilised APIs and/or features.
|
||||
|
||||
## Misc
|
||||
|
||||
- A list of miscellaneous changes.
|
||||
|
||||
## Internal Only
|
||||
|
||||
- A list of changes that do not have user-facing impact, but represent
|
||||
significant improvements to the internals of Enso and related tools.
|
||||
```
|
||||
|
||||
If there are no changes for a section, the section may be removed.
|
||||
|
||||
The releases file is an ongoing record of changes, and may diverge between
|
||||
`main` and the various release branches.
|
||||
|
||||
## Version Support
|
||||
|
||||
We aim to support a given major version for some period of time after the
|
||||
release of the next major version. For a detailed breakdown of the major
|
||||
versions that are currently supported, please see the [security](./security.md)
|
||||
document.
|
||||
|
||||
## Working on the Current Release
|
||||
|
||||
When working on the current release, development should take place against the
|
||||
`main` branch. When it is time to cut a release, the new commits on the main
|
||||
branch are cherry-picked onto the current release branch. From there, the
|
||||
release proceeds as described in [release workflow](#release-workflow) above.
|
||||
|
||||
## Backporting Fixes
|
||||
|
||||
Supporting a major version for some time after the release of the next major
|
||||
version will sometimes require backporting a fix to the previous major version
|
||||
from the current version or from `main`.
|
||||
|
||||
Backporting should only be used for applying _fixes_, not the addition of new
|
||||
features.
|
||||
|
||||
The process for performing such a backport is as follows:
|
||||
|
||||
1. Create a new branch called `backport/version/fix-name`, where `version`
|
||||
matches the version string of the corresponding release branch. This branch
|
||||
should branch off the corresponding release branch.
|
||||
2. Back-port the fix to the newly created `backport` branch. This can be done
|
||||
by:
|
||||
- Cherry-picking the commit and performing fixups (preferred).
|
||||
- Re-implementing the fix manually (if cherry-picking will not work due to
|
||||
progression of the codebase).
|
||||
3. Submit your `backport/version/fix-name` branch for review as a pull-request
|
||||
into the `release/version` branch.
|
||||
4. Once the PR has passed CI and been approved by the appropriate reviewers, it
|
||||
can be merged into the release branch.
|
||||
|
@ -87,6 +87,7 @@ public class DebuggingEnsoTest {
|
||||
* checks the value of the `accumulator` variable.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void recursiveFactorialCall() throws Exception {
|
||||
final URI facUri = new URI("memory://fac.enso");
|
||||
final Source facSrc = Source.newBuilder("enso", """
|
||||
@ -125,6 +126,7 @@ public class DebuggingEnsoTest {
|
||||
* stack frames, including the stack frame of the caller method.
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void callerVariablesAreVisibleOnPreviousStackFrame() {
|
||||
URI fooUri = URI.create("memory://tmp.enso");
|
||||
Source fooSource = Source.newBuilder("enso", """
|
||||
|
11
run.ps1
11
run.ps1
@ -13,8 +13,9 @@ $BuildScriptBin = "enso-build-cli"
|
||||
$TargetExe = Join-Path $TargetDir $BuildScriptProfile $BuildScriptBin
|
||||
|
||||
$BuildArgs = "build", "--profile", $BuildScriptProfile, "--target-dir", $TargetDir, "--package", $BuildScriptBin
|
||||
Set-Location $PSScriptRoot
|
||||
Start-Process cargo -NoNewWindow -Wait -ArgumentList $BuildArgs
|
||||
if (!$?) { Exit $LASTEXITCODE }
|
||||
Start-Process $TargetExe -NoNewWindow -Wait -ArgumentList $args
|
||||
Exit $LASTEXITCODE
|
||||
$BuildScriptProcess = Start-Process cargo -NoNewWindow -PassThru -Wait -WorkingDirectory $PSScriptRoot -ArgumentList $BuildArgs
|
||||
if ($BuildScriptProcess.ExitCode -ne 0) {
|
||||
Exit $BuildScriptProcess.ExitCode
|
||||
}
|
||||
$BuildScriptBinProcess = Start-Process $TargetExe -NoNewWindow -PassThru -Wait -WorkingDirectory $PSScriptRoot -ArgumentList $args
|
||||
Exit $BuildScriptBinProcess.ExitCode
|
||||
|
Loading…
Reference in New Issue
Block a user