mirror of
https://github.com/ClementTsang/bottom.git
synced 2026-05-03 21:40:32 +00:00
ci: migrate from cirrus for 2.17 to cross (#2022)
* ci: migrate from cirrus for 2.17 to cross CirrusCI is going to be stopped soon after the OAI acquisition, so we're on a clock to move away from it. Thankfully, we only use it for 2.17 builds for CentOS. Looks like supporting 2.17 is natively supported by cross (https://github.com/cross-rs/cross/issues/680), so this PR tries to use that to do it instead. * it's uppercase * use older version * revert + pin version * typo * allow overriding artifact name * update comment
This commit is contained in:
-71
@@ -1,71 +0,0 @@
|
||||
%YAML 1.1
|
||||
---
|
||||
# Configuration for CirrusCI. This is primarily used for testing and building FreeBSD and old versions of Linux,
|
||||
# since other CI platforms don't support build jobs for these configurations.
|
||||
#
|
||||
# Note that we set the YAML directive above to prevent some linting errors around the templates.
|
||||
|
||||
setup_template: &SETUP_TEMPLATE
|
||||
setup_script:
|
||||
- curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs --output rustup.sh
|
||||
- sh rustup.sh --default-toolchain stable -y
|
||||
|
||||
cache_template: &CACHE_TEMPLATE
|
||||
registry_cache:
|
||||
folder: $HOME/.cargo/registry
|
||||
reupload_on_changes: "true"
|
||||
fingerprint_script:
|
||||
- $HOME/.cargo/bin/rustc --version
|
||||
- cat Cargo.lock
|
||||
- echo $CIRRUS_OS
|
||||
- echo $CIRRUS_TASK_NAME
|
||||
target_cache:
|
||||
folder: target
|
||||
reupload_on_changes: "true"
|
||||
fingerprint_script:
|
||||
- $HOME/.cargo/bin/rustc --version
|
||||
- cat Cargo.lock
|
||||
- echo $CIRRUS_OS
|
||||
- echo $CIRRUS_TASK_NAME
|
||||
|
||||
cleanup_template: &CLEANUP_TEMPLATE
|
||||
before_cache_script:
|
||||
- rm -rf $HOME/.cargo/registry/index
|
||||
- rm -rf $HOME/.cargo/registry/src
|
||||
- rm -f ./target/.rustc_info.json
|
||||
|
||||
env:
|
||||
CARGO_INCREMENTAL: "0"
|
||||
CARGO_PROFILE_DEV_DEBUG: "0"
|
||||
CARGO_HUSKY_DONT_INSTALL_HOOKS: "true"
|
||||
|
||||
release_task:
|
||||
auto_cancellation: "false"
|
||||
only_if: $CIRRUS_BUILD_SOURCE == "api" && $BTM_BUILD_RELEASE_CALLER == "ci"
|
||||
timeout_in: "30m"
|
||||
env:
|
||||
BTM_GENERATE: "true"
|
||||
COMPLETION_DIR: "target/tmp/bottom/completion/"
|
||||
MANPAGE_DIR: "target/tmp/bottom/manpage/"
|
||||
# -PLACEHOLDER FOR CI-
|
||||
matrix:
|
||||
- name: "Legacy Linux (2.17)"
|
||||
alias: "linux_2_17_build"
|
||||
container:
|
||||
image: quay.io/pypa/manylinux2014_x86_64
|
||||
env:
|
||||
TARGET: "x86_64-unknown-linux-gnu"
|
||||
NAME: "x86_64-unknown-linux-gnu-2-17"
|
||||
<<: *SETUP_TEMPLATE
|
||||
<<: *CACHE_TEMPLATE
|
||||
build_script:
|
||||
- . $HOME/.cargo/env
|
||||
- cargo build --release --locked --features deploy
|
||||
- mv ./target/release/btm ./
|
||||
- ./btm -V
|
||||
- mv "$COMPLETION_DIR" completion
|
||||
- mv "$MANPAGE_DIR" manpage
|
||||
- tar -czvf bottom_$NAME.tar.gz btm completion
|
||||
binaries_artifacts:
|
||||
path: bottom_$NAME.tar.gz
|
||||
<<: *CLEANUP_TEMPLATE
|
||||
@@ -1,7 +1,6 @@
|
||||
# Builds the following releases:
|
||||
# - Binaries
|
||||
# - Binaries via VMs
|
||||
# - Cirrus binaries (currently just Linux 2.17)
|
||||
# - MSI installer for Windows (.msi)
|
||||
# - .deb releases
|
||||
# - .rpm releases
|
||||
@@ -129,6 +128,15 @@ jobs:
|
||||
cross: true,
|
||||
}
|
||||
|
||||
# Linux 2.17 (CentOS 7)
|
||||
- {
|
||||
target: "x86_64-unknown-linux-gnu",
|
||||
os: "ubuntu-22.04",
|
||||
cross: true,
|
||||
cross_image: "ghcr.io/cross-rs/x86_64-unknown-linux-gnu@sha256:ab2070e67704b2c85d0d1fa50a0165ec438ed87fd1dc1a4058692a2348c071cb", # edge-centos
|
||||
artifact_name: "x86_64-unknown-linux-gnu-2-17",
|
||||
}
|
||||
|
||||
# Android
|
||||
- {
|
||||
target: "aarch64-linux-android",
|
||||
@@ -201,6 +209,12 @@ jobs:
|
||||
echo "Will build with the following features: $BUILD_FEATURES"
|
||||
echo "BUILD_FEATURES=$BUILD_FEATURES" >> $GITHUB_ENV
|
||||
|
||||
- name: Set cross docker image if set
|
||||
if: matrix.info.cross == true && matrix.info.cross_image != ''
|
||||
shell: bash
|
||||
run: |
|
||||
echo "CROSS_TARGET_X86_64_UNKNOWN_LINUX_GNU_IMAGE=${{ matrix.info.cross_image }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Build
|
||||
uses: ClementTsang/cargo-action@2438cc5f3ba4e971289fffca2a00dedea6911f14 # v0.0.7
|
||||
env:
|
||||
@@ -304,7 +318,7 @@ jobs:
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
retention-days: 3
|
||||
name: "release-${{ matrix.info.target }}"
|
||||
name: "release-${{ matrix.info.artifact_name || matrix.info.target }}"
|
||||
path: release
|
||||
|
||||
build-msi:
|
||||
@@ -387,44 +401,6 @@ jobs:
|
||||
name: "release-${{ matrix.info.target }}-msi"
|
||||
path: release
|
||||
|
||||
build-cirrus:
|
||||
name: "Build using Cirrus CI"
|
||||
runs-on: "ubuntu-24.04"
|
||||
timeout-minutes: 12
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- name: Create release directory
|
||||
run: |
|
||||
mkdir -p release
|
||||
|
||||
- name: Execute Cirrus CI build script
|
||||
env:
|
||||
CIRRUS_KEY: ${{ secrets.CIRRUS_TOKEN }}
|
||||
run: |
|
||||
if [[ "${{ github.ref_type }}" == "branch" ]]; then
|
||||
BRANCH="${{ github.ref_name }}";
|
||||
else
|
||||
raw=$(git branch -r --contains '${{ github.ref_name }}');
|
||||
BRANCH=${raw##*/};
|
||||
fi
|
||||
python ./scripts/ci/cirrus_release.py "$BRANCH" "release/" "${{ inputs.caller }}"
|
||||
|
||||
- name: Generate artifact attestation for file
|
||||
uses: actions/attest-build-provenance@6149ea5740be74af77f260b9db67e633f6b0a9a1 # v1.4.2
|
||||
with:
|
||||
subject-path: "release/**/*.tar.gz"
|
||||
|
||||
- name: Save release as artifact
|
||||
uses: actions/upload-artifact@26f96dfa697d77e81fd5907df203aa23a56210a8 # v4.3.0
|
||||
with:
|
||||
retention-days: 3
|
||||
name: release-build-cirrus
|
||||
path: release
|
||||
|
||||
build-deb:
|
||||
name: "Build .deb software packages"
|
||||
runs-on: ${{ matrix.info.os || 'ubuntu-24.04' }}
|
||||
|
||||
@@ -1,7 +1,4 @@
|
||||
# How we deploy a release. Covers binary builds. Also manages packaging for choco.
|
||||
#
|
||||
# Binaries are primarily built by GHA, though some Linux, M1 macOS, and FreeBSD builds are
|
||||
# handled by CirrusCI.
|
||||
|
||||
name: deployment
|
||||
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# Workflow to publish to GitHub Pages. Based on the normal
|
||||
# job (e.g. https://github.com/ClementTsang/bottom/actions/runs/19805277892),
|
||||
# but re-implemented so I can pin hashes.
|
||||
#
|
||||
# This action uses actions/upload-pages-artifact, which uses actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 (v4.6.2).
|
||||
|
||||
name: Publish GitHub Pages
|
||||
|
||||
|
||||
@@ -26,7 +26,6 @@ exclude = [
|
||||
"scripts/",
|
||||
"wix/",
|
||||
".all-contributorsrc",
|
||||
".cirrus.yml",
|
||||
".gitignore",
|
||||
".markdownlint.json",
|
||||
"CHANGELOG.md",
|
||||
|
||||
@@ -90,10 +90,7 @@ fn nightly_version() {
|
||||
Some(var) if !var.is_empty() && var == "ci" => {
|
||||
let version = env!("CARGO_PKG_VERSION");
|
||||
|
||||
if let Some(hash) = extract_sha(option_env!("CIRRUS_CHANGE_IN_REPO")) {
|
||||
// May be set if we're building with Cirrus CI.
|
||||
output_nightly_version(version, hash);
|
||||
} else if let Some(hash) = extract_sha(option_env!("GITHUB_SHA")) {
|
||||
if let Some(hash) = extract_sha(option_env!("GITHUB_SHA")) {
|
||||
// May be set if we're building with GHA.
|
||||
output_nightly_version(version, hash);
|
||||
} else if let Ok(output) = std::process::Command::new("git")
|
||||
@@ -110,7 +107,6 @@ fn nightly_version() {
|
||||
}
|
||||
|
||||
println!("cargo:rerun-if-env-changed={ENV_KEY}");
|
||||
println!("cargo:rerun-if-env-changed=CIRRUS_CHANGE_IN_REPO");
|
||||
}
|
||||
|
||||
fn main() -> io::Result<()> {
|
||||
|
||||
@@ -13,9 +13,9 @@ process.
|
||||
You can also find a nightly build in the [releases page](https://github.com/ClementTsang/bottom/releases), built every
|
||||
day at 00:00 UTC off of the `main` branch.
|
||||
|
||||
In both cases, we use a combination of GitHub Actions and CirrusCI (mainly for FreeBSD and macOS M1) to create our
|
||||
release binaries. [`build_releases.yml`](https://github.com/ClementTsang/bottom/blob/main/.github/workflows/build_releases.yml)
|
||||
contains the GitHub Action workflow used to do both of these, if reference is needed.
|
||||
In both cases, we use GitHub Actions to create our release binaries.
|
||||
[`build_releases.yml`](https://github.com/ClementTsang/bottom/blob/main/.github/workflows/build_releases.yml)
|
||||
contains the GitHub Action workflow for this.
|
||||
|
||||
## Building manually
|
||||
|
||||
|
||||
@@ -1,302 +0,0 @@
|
||||
#!/bin/python3
|
||||
|
||||
# A simple script to trigger Cirrus CI builds and download the release artifacts
|
||||
# through Cirrus CI's GraphQL interface.
|
||||
#
|
||||
# Expects the Cirrus CI API key to be set in the CIRRUS_KEY environment variable.
|
||||
#
|
||||
# TODO: Explain this in docs how the heck this works.
|
||||
|
||||
import os
|
||||
import json
|
||||
import sys
|
||||
import traceback
|
||||
from textwrap import dedent
|
||||
from time import sleep, time
|
||||
from pathlib import Path
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
from urllib.request import Request, urlopen, urlretrieve
|
||||
|
||||
# Form of each task is (TASK_ALIAS, FILE_NAME).
|
||||
TASKS: List[Tuple[str, str]] = [
|
||||
("linux_2_17_build", "bottom_x86_64-unknown-linux-gnu-2-17.tar.gz"),
|
||||
]
|
||||
URL = "https://api.cirrus-ci.com/graphql"
|
||||
DL_URL_TEMPLATE = "https://api.cirrus-ci.com/v1/artifact/build/%s/%s/binaries/%s"
|
||||
|
||||
|
||||
def make_query_request(key: str, branch: str, mutation_id: str):
|
||||
print("Creating query request.")
|
||||
|
||||
# Dumb but if it works...
|
||||
config_override = (
|
||||
Path(".cirrus.yml")
|
||||
.read_text()
|
||||
.replace("# -PLACEHOLDER FOR CI-", 'BTM_BUILD_RELEASE_CALLER: "ci"')
|
||||
)
|
||||
|
||||
query = """
|
||||
mutation CreateCirrusCIBuild (
|
||||
$repo: ID!,
|
||||
$branch: String!,
|
||||
$mutation_id: String!,
|
||||
$config_override: String,
|
||||
) {
|
||||
createBuild (
|
||||
input: {
|
||||
repositoryId: $repo,
|
||||
branch: $branch,
|
||||
clientMutationId: $mutation_id,
|
||||
configOverride: $config_override
|
||||
}
|
||||
) {
|
||||
build {
|
||||
id,
|
||||
status
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
params = {
|
||||
"repo": "6646638922956800",
|
||||
"branch": branch,
|
||||
"mutation_id": mutation_id,
|
||||
"config_override": dedent(config_override),
|
||||
}
|
||||
|
||||
data = {"query": dedent(query), "variables": params}
|
||||
data = json.dumps(data).encode()
|
||||
|
||||
request = Request(URL, data=data, method="POST")
|
||||
request.add_header("Authorization", "Bearer {}".format(key))
|
||||
|
||||
return request
|
||||
|
||||
|
||||
def check_build_status(key: str, build_id: str) -> Optional[str]:
|
||||
query = """
|
||||
query BuildStatus($id: ID!) {
|
||||
build(id: $id) {
|
||||
status
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
params = {
|
||||
"id": build_id,
|
||||
}
|
||||
|
||||
data = {"query": dedent(query), "variables": params}
|
||||
data = json.dumps(data).encode()
|
||||
|
||||
request = Request(URL, data=data, method="POST")
|
||||
request.add_header("Authorization", "Bearer {}".format(key))
|
||||
with urlopen(request) as response:
|
||||
response = json.load(response)
|
||||
if response.get("errors") is not None:
|
||||
print("There was an error in the returned response.")
|
||||
return None
|
||||
|
||||
try:
|
||||
status = response["data"]["build"]["status"]
|
||||
return status
|
||||
except KeyError:
|
||||
print("There was an issue with checking the build status.")
|
||||
return None
|
||||
|
||||
|
||||
def check_build_tasks(key: str, build_id: str) -> Optional[List[str]]:
|
||||
query = """
|
||||
query Build($id:ID!) {
|
||||
build(id:$id){
|
||||
tasks {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
params = {
|
||||
"id": build_id,
|
||||
}
|
||||
|
||||
data = {"query": dedent(query), "variables": params}
|
||||
data = json.dumps(data).encode()
|
||||
|
||||
request = Request(URL, data=data, method="POST")
|
||||
request.add_header("Authorization", "Bearer {}".format(key))
|
||||
with urlopen(request) as response:
|
||||
response = json.load(response)
|
||||
|
||||
if response.get("errors") is not None:
|
||||
print("There was an error in the returned response.")
|
||||
return None
|
||||
|
||||
try:
|
||||
tasks = [task["id"] for task in response["data"]["build"]["tasks"]]
|
||||
return tasks
|
||||
except KeyError:
|
||||
print("There was an issue with getting the list of task ids.")
|
||||
return None
|
||||
|
||||
|
||||
def stop_build_tasks(key: str, task_ids: List[str], mutation_id: str) -> bool:
|
||||
query = """
|
||||
mutation StopCirrusCiTasks (
|
||||
$task_ids: [ID!]!,
|
||||
$mutation_id: String!,
|
||||
) {
|
||||
batchAbort (
|
||||
input: {
|
||||
taskIds: $task_ids,
|
||||
clientMutationId: $mutation_id
|
||||
}
|
||||
) {
|
||||
tasks {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
"""
|
||||
|
||||
params = {
|
||||
"task_ids": task_ids,
|
||||
"mutation_id": mutation_id,
|
||||
}
|
||||
|
||||
data = {"query": dedent(query), "variables": params}
|
||||
data = json.dumps(data).encode()
|
||||
|
||||
request = Request(URL, data=data, method="POST")
|
||||
request.add_header("Authorization", "Bearer {}".format(key))
|
||||
|
||||
with urlopen(request) as response:
|
||||
response = json.load(response)
|
||||
return len(response["data"]["batchAbort"]["tasks"]) == len(task_ids)
|
||||
|
||||
|
||||
def try_download(build_id: str, dl_path: Path):
|
||||
for task, file in TASKS:
|
||||
url = DL_URL_TEMPLATE % (build_id, task, file)
|
||||
out = os.path.join(dl_path, file)
|
||||
print(f"Downloading {file} to {out}")
|
||||
urlretrieve(url, out)
|
||||
|
||||
|
||||
def main():
|
||||
args = sys.argv
|
||||
env = os.environ
|
||||
|
||||
key = env["CIRRUS_KEY"]
|
||||
branch = args[1]
|
||||
dl_path = args[2] if len(args) >= 3 else ""
|
||||
dl_path = Path(dl_path)
|
||||
build_type = args[3] if len(args) >= 4 else "build"
|
||||
build_id = args[4] if len(args) >= 5 else None
|
||||
|
||||
print(f"Running Cirrus script with branch '{branch}'")
|
||||
|
||||
# Check if this build has already been completed before.
|
||||
if build_id is not None:
|
||||
print("Previous build ID was provided, checking if complete.")
|
||||
status = check_build_status(key, build_id)
|
||||
if status.startswith("COMPLETE"):
|
||||
print("Starting download of previous build ID")
|
||||
try_download(build_id, dl_path)
|
||||
else:
|
||||
# Try up to three times
|
||||
MAX_ATTEMPTS = 5
|
||||
success = False
|
||||
tasks = []
|
||||
mutation_id = None
|
||||
|
||||
for i in range(MAX_ATTEMPTS):
|
||||
if success:
|
||||
break
|
||||
|
||||
print(f"Attempt {i + 1}:")
|
||||
|
||||
if tasks and mutation_id:
|
||||
print("Killing previous tasks first...")
|
||||
|
||||
if stop_build_tasks(key, tasks, mutation_id):
|
||||
print("All previous tasks successfully stopped.")
|
||||
else:
|
||||
print(
|
||||
"Not all previous tasks stopped. This isn't a problem but it is a waste."
|
||||
)
|
||||
|
||||
tasks = []
|
||||
mutation_id = "Cirrus CI Build {}-{}-{}".format(
|
||||
build_type, branch, int(time())
|
||||
)
|
||||
|
||||
with urlopen(make_query_request(key, branch, mutation_id)) as response:
|
||||
response = json.load(response)
|
||||
errors = response.get("errors")
|
||||
|
||||
if errors is not None:
|
||||
print(f"There was an error in the returned response: {str(errors)}")
|
||||
continue
|
||||
|
||||
try:
|
||||
build_id = response["data"]["createBuild"]["build"]["id"]
|
||||
print(f"Created build job {build_id}.")
|
||||
except KeyError:
|
||||
print("There was an issue with creating a build job.")
|
||||
continue
|
||||
|
||||
# First, sleep X minutes total, as it's unlikely it'll finish before then.
|
||||
SLEEP_MINUTES = 4
|
||||
print(f"Sleeping for {SLEEP_MINUTES} minutes.")
|
||||
|
||||
# Sleep and check for tasks out every 10 seconds
|
||||
for _ in range(SLEEP_MINUTES * 6):
|
||||
sleep(10)
|
||||
if not tasks:
|
||||
tasks = check_build_tasks(key, build_id)
|
||||
|
||||
MINUTES = 10
|
||||
SLEEP_SEC = 30
|
||||
TRIES = int(MINUTES * (60 / SLEEP_SEC)) # Works out to 20 tries.
|
||||
|
||||
print(f"Mandatory nap over. Checking for completion for {MINUTES} min.")
|
||||
|
||||
for attempt in range(TRIES):
|
||||
print("Checking...")
|
||||
try:
|
||||
status = check_build_status(key, build_id)
|
||||
if status.startswith("COMPLETE"):
|
||||
print("Build complete. Downloading artifact files.")
|
||||
sleep(5)
|
||||
try_download(build_id, dl_path)
|
||||
success = True
|
||||
break
|
||||
else:
|
||||
print(f"Build status: {(status or 'unknown')}")
|
||||
|
||||
if status == "ABORTED":
|
||||
print("Build aborted, bailing.")
|
||||
break
|
||||
elif status.lower().startswith("fail"):
|
||||
print("Build failed, bailing.")
|
||||
break
|
||||
elif attempt + 1 < TRIES:
|
||||
sleep(SLEEP_SEC)
|
||||
except Exception as ex:
|
||||
print("Unexpected error:")
|
||||
print(ex)
|
||||
print(traceback.format_exc())
|
||||
# Sleep for a minute if something went wrong, just in case.
|
||||
sleep(60)
|
||||
else:
|
||||
print(f"Build failed to complete after {MINUTES} minutes, bailing.")
|
||||
|
||||
if not success:
|
||||
exit(2)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user