diff --git a/.github/actions/check-formatting/action.yml b/.github/actions/check-formatting/action.yml new file mode 100644 index 0000000..52bb7b8 --- /dev/null +++ b/.github/actions/check-formatting/action.yml @@ -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 \ No newline at end of file diff --git a/.github/actions/check-target/action.yml b/.github/actions/check-target/action.yml new file mode 100644 index 0000000..646553e --- /dev/null +++ b/.github/actions/check-target/action.yml @@ -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 \ No newline at end of file diff --git a/.github/actions/review-pr/action.yml b/.github/actions/review-pr/action.yml new file mode 100644 index 0000000..e09d340 --- /dev/null +++ b/.github/actions/review-pr/action.yml @@ -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 + }); + } \ No newline at end of file diff --git a/.github/actions/setup-platform/action.yml b/.github/actions/setup-platform/action.yml new file mode 100644 index 0000000..7660bde --- /dev/null +++ b/.github/actions/setup-platform/action.yml @@ -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 \ No newline at end of file diff --git a/.github/actions/setup-rust/action.yml b/.github/actions/setup-rust/action.yml new file mode 100644 index 0000000..e1bf5ba --- /dev/null +++ b/.github/actions/setup-rust/action.yml @@ -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') }} + 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 }} \ No newline at end of file diff --git a/.github/workflows/npm-ci.yml b/.github/workflows/npm-ci.yml new file mode 100644 index 0000000..d9a45be --- /dev/null +++ b/.github/workflows/npm-ci.yml @@ -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 \ No newline at end of file diff --git a/.github/workflows/rust-ci.yml b/.github/workflows/rust-ci.yml new file mode 100644 index 0000000..df2f97f --- /dev/null +++ b/.github/workflows/rust-ci.yml @@ -0,0 +1,134 @@ +name: Reusable Rust CI + +on: + workflow_call: + inputs: + cargo-features: + type: string + description: "Which cargo features to enable" + required: false + default: "--all-features" + + extra-test-flags: + type: string + description: "Extra flags for cargo test" + required: false + default: "--verbose" + + run-doc-tests: + type: boolean + description: "Whether to run documentation tests" + required: false + default: true + + custom-build-script: + type: string + description: "Path to a custom build script to run instead of cargo build" + required: false + default: "" + + targets: + type: string + description: "JSON string containing matrix configuration of build targets" + required: false + default: | + { + "include": [ + { + "name": "Linux", + "target": "x86_64-unknown-linux-gnu", + "os": "ubuntu-24.04" + }, + { + "name": "Windows", + "target": "x86_64-pc-windows-msvc", + "os": "windows-latest" + }, + { + "name": "macOS", + "target": "x86_64-apple-darwin", + "os": "macos-latest" + } + ] + } + +jobs: + build-lint-test: + name: Build, Lint, and Test + runs-on: ubuntu-latest + + steps: + - name: Log event name + run: echo "Event that triggered the workflow is ${{github.event_name}}" + + - name: Setup Platform Dependencies + uses: asimov-protocol/.github/.github/actions/setup-platform@v1.0 + with: + platform: Linux + + - name: Check out code + uses: actions/checkout@v4 + + - 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 with rustfmt + run: cargo fmt --all -- --check + + - name: Lint with Clippy + run: cargo clippy ${{ inputs.cargo-features }} --all-targets -- -D warnings + - name: Build (using cargo or custom script) + run: | + if [ -n "${{ inputs.custom-build-script }}" ]; then + echo "Using custom build script: ${{ inputs.custom-build-script }}" + chmod +x "${{ inputs.custom-build-script }}" + ./"${{ inputs.custom-build-script }}" + else + echo "Using default cargo build command" + cargo build ${{ inputs.cargo-features }} ${{ inputs.extra-test-flags }} + fi + + - name: Run Tests + run: cargo test ${{ inputs.cargo-features }} ${{ inputs.extra-test-flags }} + + - name: Security audit with cargo-audit + run: | + cargo install --locked cargo-audit + cargo audit + + - name: Documentation Tests + if: inputs.run-doc-tests == true + run: cargo test --doc + + check-targets: + name: Check ${{ matrix.name }} + needs: build-lint-test + # strategy: + # fail-fast: false + # matrix: + # include: + # - name: Linux + # target: x86_64-unknown-linux-gnu + # os: ubuntu-24.04 + # - name: Windows + # target: x86_64-pc-windows-msvc + # os: windows-latest + # - name: macOS + # target: x86_64-apple-darwin + # os: macos-latest + strategy: + fail-fast: false + matrix: ${{ fromJson(inputs.targets) }} + runs-on: ${{ matrix.os }} + steps: + - name: Check Rust target + uses: asimov-protocol/.github/.github/actions/check-target@v1.0 + with: + ref: ${{ github.event.pull_request.head.sha }} + repository: ${{ github.event.pull_request.head.repo.full_name }} + target: ${{ matrix.target }} + toolchain: 1.81.0