Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions .github/actions/check-formatting/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
name: Check Rust Formatting
description: Checks formatting using rustfmt

inputs:
ref:
description: Git ref of the PR head
required: true
repository:
description: Repo full name (owner/repo)
required: true

runs:
using: "composite"
steps:
- name: Checkout PR Head
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
repository: ${{ inputs.repository }}

- name: Setup Rust environment
id: setup-rust
uses: asimov-protocol/.github/.github/actions/setup-rust@v1.0
with:
toolchain: stable
components: rustfmt

- name: Check formatting
run: cargo +${{ steps.setup-rust.outputs.name }} fmt --all -- --check 2>/dev/null
shell: bash
64 changes: 64 additions & 0 deletions .github/actions/check-target/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
name: Check Target
description: Checks binaries and tests for a given target

inputs:
ref:
description: Git ref of the PR head
required: true
repository:
description: Repo full name (owner/repo)
required: true
target:
description: Rust target triple (e.g., x86_64-unknown-linux-gnu)
required: true
toolchain:
description: Rust toolchain version (e.g., 1.81.0)
required: false
default: 1.81.0

runs:
using: "composite"
steps:
- name: Setup platform dependencies
uses: asimov-protocol/.github/.github/actions/setup-platform@v1.0
with:
platform: ${{ runner.os }}

- name: Checkout PR Head
uses: actions/checkout@v4
with:
ref: ${{ inputs.ref }}
repository: ${{ inputs.repository }}

- name: Setup Rust environment
id: setup-rust
uses: asimov-protocol/.github/.github/actions/setup-rust@v1.0
with:
toolchain: ${{ inputs.toolchain }}
components: rustfmt
targets: ${{ inputs.target }}

- name: Cache Cargo target directory
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-target-${{ inputs.target }}-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-target-${{ inputs.target }}-

- name: Check binaries
id: check-bins
shell: bash
run: cargo +${{ steps.setup-rust.outputs.name }} check --target ${{ inputs.target }} --workspace --keep-going --bins

- name: Check tests
id: check-tests
if: steps.check-bins.outcome == 'success'
shell: bash
run: cargo +${{ steps.setup-rust.outputs.name }} check --target ${{ inputs.target }} --workspace --keep-going --tests

- name: Run tests
id: run-tests
if: steps.check-tests.outcome == 'success'
shell: bash
run: cargo +${{ steps.setup-rust.outputs.name }} test --target ${{ inputs.target }} --workspace --tests --no-fail-fast
98 changes: 98 additions & 0 deletions .github/actions/review-pr/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
name: Review PR
description: Posts a review on the PR based on formatting + build results

inputs:
formatting_job_name:
description: The name of the formatting job (e.g. "Check formatting")
required: true
verbose:
description: Whether to show all steps, or just failures
required: false
default: "true"

runs:
using: "composite"
steps:
- name: Post PR Review
uses: actions/github-script@v7
with:
script: |
const FORMATTING_JOB_NAME = '${{ inputs.formatting_job_name }}';
const VERBOSE = '${{ inputs.verbose }}' === 'true';

// Fetch all jobs from the workflow run
const response = await github.rest.actions.listJobsForWorkflowRunAttempt({
...context.repo,
run_id: context.runId,
attempt_number: context.runAttempt || 1
});

// Helper functions for URLs
function getJobUrl(jobId) {
return `https://github.com/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}/job/${jobId}?check_suite_focus=true`;
}

function getStepUrl(jobId, stepNumber) {
return `${getJobUrl(jobId)}#step:${stepNumber}:0`;
}

// Process jobs and build report
let allSuccess = true;
let body = `## CI Results\n\n| Platform | Name | Status | Details |\n| --- | --- | --- | --- |\n`;

// Process formatting job first if it exists
let formattingJob = response.data.jobs.find(job => job.name.includes(FORMATTING_JOB_NAME));
if (formattingJob && (VERBOSE || formattingJob.conclusion !== 'success')) {
const status = formattingJob.conclusion === 'success' ? '✅' : '❌';
allSuccess = allSuccess && formattingJob.conclusion === 'success';
body += `| All | Formatting | ${status} | [Details](${getJobUrl(formattingJob.id)}) |\n`;
}

// Process all other jobs
for (let job of response.data.jobs) {
if (!formattingJob || job.id !== formattingJob.id) {
const platformMatch = job.name.match(/(Windows|Linux|macOS|Ubuntu)/i);
const platform = platformMatch ? platformMatch[1] : 'Unknown';
let jobFailed = false;

// Add details for each step if verbose or any failures
for (let step of job.steps) {
if (VERBOSE || step.conclusion === 'failure') {
const status =
step.conclusion === 'success' ? '✅' :
step.conclusion === 'skipped' ? '⏩' :
step.conclusion === 'failure' ? '❌' : '❓';

body += `| ${platform} | ${step.name} | ${status} | [Details](${getStepUrl(job.id, step.number)}) |\n`;
if (step.conclusion !== 'success' && step.conclusion !== 'skipped') {
jobFailed = true;
}
}
}

// Add summary row for successful jobs if verbose
if (!jobFailed && VERBOSE) {
body += `| ${platform} | All steps | ✅ | [Details](${getJobUrl(job.id)}) |\n`;
}

if (jobFailed) {
allSuccess = false;
}
}
}

// Post comment or review based on results
if (allSuccess) {
await github.rest.issues.createComment({
...context.repo,
issue_number: context.issue.number,
body: "✅ CI passed all checks. Ready for manual review."
});
} else {
await github.rest.pulls.createReview({
...context.repo,
pull_number: context.issue.number,
event: 'REQUEST_CHANGES',
body
});
}
40 changes: 40 additions & 0 deletions .github/actions/setup-platform/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
name: Setup Platform
description: Sets up platform-specific dependencies

inputs:
platform:
description: The platform to set up (Linux, Windows, macOS)
required: true

runs:
using: "composite"
steps:
- name: Install dependencies on Linux
if: inputs.platform == 'Linux'
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y libudev-dev pkg-config
echo "Checking installation of libudev-dev and pkg-config:"
dpkg -s libudev-dev pkg-config || true

echo "Which pkg-config is being used?"
which pkg-config || true

echo "pkg-config version:"
pkg-config --version || true

- name: Install OpenSSL (Windows)
if: inputs.platform == 'Windows'
shell: pwsh
run: |
choco install openssl --no-progress

- name: Set OpenSSL environment variables (Windows)
if: inputs.platform == 'Windows'
shell: pwsh
run: |
echo "OPENSSL_NO_VENDOR=1" >> $env:GITHUB_ENV
echo "OPENSSL_DIR=C:\Program Files\OpenSSL" >> $env:GITHUB_ENV
echo "OPENSSL_INCLUDE_DIR=C:\Program Files\OpenSSL\include" >> $env:GITHUB_ENV
echo "OPENSSL_LIB_DIR=C:\Program Files\OpenSSL\lib" >> $env:GITHUB_ENV
50 changes: 50 additions & 0 deletions .github/actions/setup-rust/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Setup Rust
description: Sets up Rust environment with caching

inputs:
toolchain:
description: Rust toolchain version (e.g., 1.81.0)
required: false
default: stable
components:
description: Rust components to install (e.g., rustfmt, clippy)
required: false
default: ''
targets:
description: Additional Rust targets to install
required: false
default: ''

outputs:
name:
description: "The name of the installed toolchain"
value: ${{ steps.install-rust.outputs.name }}

runs:
using: "composite"
steps:
- name: Cache Cargo registry + index
uses: actions/cache@v4
with:
path: |
~/.cargo/registry
~/.cargo/git
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('**/Cargo.lock') }}
Copy link
Member

@SamuelSarle SamuelSarle Apr 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lib packages usually don't include lock files while binaries do. Does it matter here? (hashFiles would always return the same, hash of zero files).

Some other project for example does this:

This cache is automatically keyed by:

  • ...
  • a hash of all Cargo.lock / Cargo.toml files found anywhere in the repository (if present).

Probably it'd make sense to do hashFiles('**/Cargo.toml', '**/Cargo.lock')?

There is the minor issue of cache hit/miss but also leads to:

  1. Possibly multiple actions incorrectly fighting over the same cache key where they restore from cache, don't see the relevant items and redownload, then overwrite the cache.
  2. The cache keeps growing whenever dependencies change in a project without Cargo.lock. The cache key always matches so it's restored, new items are added, and then the bigger cache is saved. Old cache items are never evicted.

restore-keys: |
${{ runner.os }}-cargo-registry-

- name: Cache target directory
uses: actions/cache@v4
with:
path: target
key: ${{ runner.os }}-cargo-target-${{ hashFiles('**/Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-target-

- name: Install Rust
id: install-rust
uses: dtolnay/rust-toolchain@master
with:
toolchain: ${{ inputs.toolchain }}
components: ${{ inputs.components }}
targets: ${{ inputs.targets }}
44 changes: 44 additions & 0 deletions .github/workflows/npm-ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Reusable CI

on:
workflow_call:
inputs:
skip-tests:
type: boolean
default: false

jobs:
build-and-test:
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Use Node.js
uses: actions/setup-node@v4
with:
node-version: 18

- name: Cache node_modules
uses: actions/cache@v4
id: node-cache
with:
path: node_modules
key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }}
restore-keys: |
${{ runner.os }}-node-

- name: Install dependencies
if: steps.node-cache.outputs.cache-hit != 'true'
run: npm ci

- name: Lint
run: npm run lint

- name: Test
if: ${{ inputs.skip-tests == false }}
run: npm test

- name: Build
run: npm run build
Loading