Add input exclude, allow list in package (#40)

* Add exclude, allow lists in package

* Add test for input exclude

* Second dummy crate in test workspace

* New tests

* Fix cache key

* Forgot about flat()

* Reflect changes in docs

* New key in cache test

* Shorter job name

* Allowed to fail -> expected to fail

* Update README.md

Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>

* Hash inputs in cache key

* Update cache test

* Update README

---------

Co-authored-by: Predrag Gruevski <2348618+obi1kenobi@users.noreply.github.com>
This commit is contained in:
Mieszko Grodzicki 2023-04-23 20:24:06 +02:00 committed by GitHub
parent 7eca687541
commit 8b981cde1a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
10 changed files with 114 additions and 40 deletions

View file

@ -14,10 +14,12 @@ runs:
persist-credentials: true persist-credentials: true
path: ref_slice path: ref_slice
- name: Create dummy crate - name: Create dummy crate
# This crate does not have a matching baseline on crates.io, so any try # This crates do not have matching baselines on crates.io, so any try
# of checking it should make cargo-semver-checks fail. # of checking them should make cargo-semver-checks fail.
run: cargo new cargo-semver-action-dummy --lib run: |
cargo new cargo-semver-action-dummy --lib
cargo new cargo-semver-action-dummy-2 --lib
shell: bash shell: bash
- name: Create workspace Cargo.toml - name: Create workspace Cargo.toml
run: echo -e "[workspace]\nmembers=['ref_slice','cargo-semver-action-dummy']" > Cargo.toml run: echo -e "[workspace]\nmembers=['ref_slice','cargo-semver-action-dummy','cargo-semver-action-dummy-2']" > Cargo.toml
shell: bash shell: bash

View file

@ -36,7 +36,7 @@ jobs:
run: | run: |
git fetch origin major_change git fetch origin major_change
git checkout major_change git checkout major_change
- name: Run the action (allowed to fail) - name: Run the action (expected to fail)
id: action_major id: action_major
uses: ./action/ uses: ./action/
with: with:

View file

@ -95,7 +95,7 @@ jobs:
run: | run: |
RUSTC=$(rustc --version | sed -e 's/\s\+/-/g') RUSTC=$(rustc --version | sed -e 's/\s\+/-/g')
SEMVER_CHECKS=$(cargo semver-checks --version | sed -e 's/\s\+/-/g') SEMVER_CHECKS=$(cargo semver-checks --version | sed -e 's/\s\+/-/g')
echo "KEY=testprefix-test-cache-exists-default-ref_slice-8e11dadaa21a8bf3112b6c764012883b-linux-$RUSTC-$SEMVER_CHECKS-da39a3ee5e6b4b0d3255bfef95601890afd80709-semver-checks-rustdoc" >> $GITHUB_OUTPUT echo "KEY=testprefix-test-cache-exists-default-d45618ed191f0a73-linux-$RUSTC-$SEMVER_CHECKS-da39a3ee5e6b4b0d3255bfef95601890afd80709-semver-checks-rustdoc" >> $GITHUB_OUTPUT
- name: Download saved cache - name: Download saved cache
uses: actions/cache/restore@v3 uses: actions/cache/restore@v3
with: with:

View file

@ -26,7 +26,7 @@ jobs:
uses: ./action/ uses: ./action/
with: with:
package: ref_slice package: ref_slice
- name: Run the action on the whole workspace (allowed to fail) - name: Run the action on the whole workspace (expected to fail)
id: action_all id: action_all
uses: ./action/ uses: ./action/
continue-on-error: true continue-on-error: true
@ -35,6 +35,17 @@ jobs:
run: | run: |
echo "Error! The action should have failed because of checking the dummy crate, but it has not!" echo "Error! The action should have failed because of checking the dummy crate, but it has not!"
exit 1 exit 1
- name: Run the action on ref_slice fork and one dummy crate (expected to fail)
id: action_one_dummy
uses: ./action/
with:
package: ref_slice, cargo-semver-action-dummy
continue-on-error: true
- name: Fail if the action has not returned any errors (but it should have)
if: steps.action_one_dummy.outcome != 'failure'
run: |
echo "Error! The action should have failed because of checking the dummy crate, but it has not!"
exit 1
test-package-major: test-package-major:
name: Test input package (major change) name: Test input package (major change)
@ -48,7 +59,7 @@ jobs:
uses: ./action/.github/workflows/setup-test-workspace uses: ./action/.github/workflows/setup-test-workspace
with: with:
ref-slice-ref: major_change ref-slice-ref: major_change
- name: Run the action on ref_slice major change (allowed to fail) - name: Run the action on ref_slice major change (expected to fail)
id: action_major id: action_major
uses: ./action/ uses: ./action/
with: with:
@ -60,6 +71,56 @@ jobs:
echo "Error! The action should have failed because of the breaking change, but it has not." echo "Error! The action should have failed because of the breaking change, but it has not."
exit 1 exit 1
test-package-exclude-rio:
name: Test inputs package and exclude on Rio library
runs-on: ubuntu-latest
steps:
- name: Checkout the Rio repository
uses: actions/checkout@v3
with:
repository: oxigraph/rio
ref: 3bd01c2c977a0b01c918f6840cd05356477db358 # branch main
- name: Checkout the action
uses: actions/checkout@v3
with:
path: action
- name: Run the action using input package
uses: ./action/
with:
package: rio_api, rio_turtle, rio_xml
- name: Run the action using input exclude
uses: ./action/
with:
exclude: rio_testsuite
test-exclude:
name: Test input exclude
runs-on: ubuntu-latest
steps:
- name: Checkout the action
uses: actions/checkout@v3
with:
path: action
- name: Setup the workspace with ref_slice patch change
uses: ./action/.github/workflows/setup-test-workspace
with:
ref-slice-ref: patch_change
- name: Run the action excluding both dummy crates
uses: ./action/
with:
exclude: cargo-semver-action-dummy, cargo-semver-action-dummy-2
- name: Run the action excluding only one of the dummy crates (expected to fail)
id: action_major
uses: ./action/
with:
exclude: cargo-semver-action-dummy
continue-on-error: true
- name: Fail if the action has not returned any errors (but it should have)
if: steps.action_major.outcome != 'failure'
run: |
echo "Error! The action should have failed because of the breaking change, but it has not."
exit 1
test-verbose: test-verbose:
# There is currently no way of asserting that the output is indeed verbose, # There is currently no way of asserting that the output is indeed verbose,
# so we at least check if the action runs without an error when the # so we at least check if the action runs without an error when the
@ -101,7 +162,7 @@ jobs:
uses: ./action/ uses: ./action/
with: with:
manifest-path: ref_slice manifest-path: ref_slice
- name: Run the action on the whole workspace (Cargo.toml path, allowed to fail) - name: Run the action on the whole workspace (Cargo.toml path, expected to fail)
id: action_all_cargo_toml id: action_all_cargo_toml
uses: ./action/ uses: ./action/
with: with:
@ -125,7 +186,7 @@ jobs:
uses: ./action/.github/workflows/setup-test-workspace uses: ./action/.github/workflows/setup-test-workspace
with: with:
ref-slice-ref: major_change ref-slice-ref: major_change
- name: Run the action on ref_slice major change (Cargo.toml path, allowed to fail) - name: Run the action on ref_slice major change (Cargo.toml path, expected to fail)
id: action_major_cargo_toml id: action_major_cargo_toml
uses: ./action/ uses: ./action/
with: with:
@ -136,7 +197,7 @@ jobs:
run: | run: |
echo "Error! The action should have failed because of the breaking change, but it has not." echo "Error! The action should have failed because of the breaking change, but it has not."
exit 1 exit 1
- name: Run the action on ref_slice major change (crate path, allowed to fail) - name: Run the action on ref_slice major change (crate path, expected to fail)
id: action_major_crate id: action_major_crate
uses: ./action/ uses: ./action/
with: with:
@ -174,7 +235,7 @@ jobs:
cd "ref slice" cd "ref slice"
git fetch origin major_change git fetch origin major_change
git checkout major_change git checkout major_change
- name: Run the action (allowed to fail) - name: Run the action (expected to fail)
id: action_major id: action_major
uses: ./action/ uses: ./action/
with: with:
@ -310,7 +371,7 @@ jobs:
exit 1 exit 1
- name: Uninstall Rust - name: Uninstall Rust
run: rustup self uninstall -y run: rustup self uninstall -y
- name: Run the action with manual rust-toolchain (allowed to fail) - name: Run the action with manual rust-toolchain (expected to fail)
id: action_without_rust id: action_without_rust
uses: ./action/ uses: ./action/
with: with:

View file

@ -16,11 +16,12 @@ Every argument is optional.
| Input | Description | Default | | Input | Description | Default |
|--------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------| |--------------------|-----------------------------------------------------------------------------------------------------------------------------------|---------|
| `package` | The package whose API to check for semver (in Package Id Specification format, see https://doc.rust-lang.org/cargo/reference/pkgid-spec.html for reference). If not set, all packages defined in the Cargo.toml file are processed. | | | `package` | Comma-separated list of the packages whose API to check for semver (in Package Id Specification format, see https://doc.rust-lang.org/cargo/reference/pkgid-spec.html for reference). If not set, all packages defined in the Cargo.toml file are processed. | |
| `exclude` | Comma-separated list of the packages that will be excluded from being processed. Has effect only if the input `package` is not specified. | |
| `manifest-path` | Path to Cargo.toml of crate or workspace to check. If not specified, the action assumes the manifest is under the default [`GITHUB_WORKSPACE`](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables) path. | | | `manifest-path` | Path to Cargo.toml of crate or workspace to check. If not specified, the action assumes the manifest is under the default [`GITHUB_WORKSPACE`](https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables) path. | |
| `verbose` | Enables verbose output of `cargo-semver-checks`. | `false` | | `verbose` | Enables verbose output of `cargo-semver-checks`. | `false` |
| `rust-toolchain` | Rust toolchain name to use, e.g. `stable`, `nightly` or `1.68.0`. It will be installed if necessary and used regardless of local overrides and the `rust-toolchain.toml` file. However, if the input is set to `manual`, the action assumes some Rust toolchain is already installed and uses the default one. | `stable` | | `rust-toolchain` | Rust toolchain name to use, e.g. `stable`, `nightly` or `1.68.0`. It will be installed if necessary and used regardless of local overrides and the `rust-toolchain.toml` file. However, if the input is set to `manual`, the action assumes some Rust toolchain is already installed and uses the default one. | `stable` |
| `shared-key` | A cache key that will be used instead of the automatic key based on the name of the GitHub job and values of the inputs `package` and `manifest-path`. Might be provided e.g. to share the cache between the jobs. | | | `shared-key` | A cache key that will be used instead of the automatic key based on the name of the GitHub job and values of the inputs `package`, `exclude` and `manifest-path`. Might be provided e.g. to share the cache between the jobs. | |
| `prefix-key` | Additional prefix of the cache key, can be set to start a new cache manually. | | | `prefix-key` | Additional prefix of the cache key, can be set to start a new cache manually. | |
| `github-token` | The `GITHUB_TOKEN` secret used to download precompiled binaries from GitHub API. If not specified, the [automatic GitHub token](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) provided to the workflow will be used. The token may be alternatively passed in an environment variable `GITHUB_TOKEN`. | `${{ github.token }}` | | `github-token` | The `GITHUB_TOKEN` secret used to download precompiled binaries from GitHub API. If not specified, the [automatic GitHub token](https://docs.github.com/en/actions/security-guides/automatic-token-authentication) provided to the workflow will be used. The token may be alternatively passed in an environment variable `GITHUB_TOKEN`. | `${{ github.token }}` |
@ -38,14 +39,23 @@ The action will work out-of-the-box if it is run inside the package root directo
# Use in workspaces with more than one crate # Use in workspaces with more than one crate
By default, if workspace contains multiple crates, all of them are checked for semver violations. You can specify a single crate to be checked instead using `package` or `manifest-path`. By default, if workspace contains multiple crates, all of them are checked for semver violations. You can specify one or more crates to be checked instead using `package`, `exclude` or `manifest-path`.
For example, this will check `my-crate`: For example, this will check `my-crate-api` and `my-crate-core`:
```yaml ```yaml
- name: Check semver for my-crate from the current workspace - name: Check semver for my-crate-api and my-crate-core
uses: obi1kenobi/cargo-semver-checks-action@v2 uses: obi1kenobi/cargo-semver-checks-action@v2
with: with:
package: my-crate package: my-crate-api, my-crate-core
- name: Publish my-crate to crates.io
run: # your `cargo publish` code here
```
And this will process all crates from the current workspace except `my-crate-tests`:
```yaml
- name: Check semver for all crates except my-crate-tests
uses: obi1kenobi/cargo-semver-checks-action@v2
with:
exclude: my-crate-tests
- name: Publish my-crate to crates.io - name: Publish my-crate to crates.io
run: # your `cargo publish` code here run: # your `cargo publish` code here
``` ```
@ -76,7 +86,7 @@ The two above might be also used together:
The action caches the baseline rustdoc for each package in the workspace. The keys used to distinguish the caches consist of four components: The action caches the baseline rustdoc for each package in the workspace. The keys used to distinguish the caches consist of four components:
- `prefix-key` input, which defaults to an empty string, - `prefix-key` input, which defaults to an empty string,
- `shared-key` input if provided, otherwise a concatenation of the value of environmental variable `GITHUB_JOB`, the value of `package` and the hashed value of the `manifest-path` variable (not the file it points to), - `shared-key` input if provided, otherwise the value of environmental variable `GITHUB_JOB` concatenated with a hash of the values of inputs `package`, `exclude` and `manifest-path` (we hash the path itself, not the file it points to),
- internal, unchangable component, being a concatenation of the runner OS, `rustc` version, `cargo-semver-checks` version and hash of all `Cargo.lock` files in the current workspace, - internal, unchangable component, being a concatenation of the runner OS, `rustc` version, `cargo-semver-checks` version and hash of all `Cargo.lock` files in the current workspace,
- constant suffix `"semver-checks-rustdoc"`. - constant suffix `"semver-checks-rustdoc"`.

View file

@ -5,7 +5,10 @@ branding:
color: 'green' color: 'green'
inputs: inputs:
package: package:
description: 'The package whose API to check for semver (in Package Id Specification format, see https://doc.rust-lang.org/cargo/reference/pkgid-spec.html for reference). If not set, all packages defined in the Cargo.toml file are processed.' description: 'Comma-separated list of the packages whose API to check for semver (in Package Id Specification format, see https://doc.rust-lang.org/cargo/reference/pkgid-spec.html for reference). If not set, all packages defined in the Cargo.toml file are processed.'
required: false
exclude:
description: 'Comma-separated list of the packages that will be excluded from being processed. Has effect only if the input `package` is not specified.'
required: false required: false
manifest-path: manifest-path:
description: 'Path to Cargo.toml of crate or workspace to check. If not specified, the action assumes the manifest is under the default `GITHUB_WORKSPACE` path.' description: 'Path to Cargo.toml of crate or workspace to check. If not specified, the action assumes the manifest is under the default `GITHUB_WORKSPACE` path.'
@ -19,7 +22,7 @@ inputs:
description: 'Rust toolchain name to use, e.g. `stable`, `nightly` or `1.68.0`. It will be installed if necessary and used regardless of local overrides and the `rust-toolchain.toml` file. However, if the input is set to `manual`, the action assumes some Rust toolchain is already installed and uses the default one.' description: 'Rust toolchain name to use, e.g. `stable`, `nightly` or `1.68.0`. It will be installed if necessary and used regardless of local overrides and the `rust-toolchain.toml` file. However, if the input is set to `manual`, the action assumes some Rust toolchain is already installed and uses the default one.'
default: 'stable' default: 'stable'
shared-key: shared-key:
description: 'A cache key that will be used instead of the automatic key based on the name of the GitHub job and values of the inputs `package` and `manifest-path`. Might be provided e.g. to share the cache between the jobs.' description: 'A cache key that will be used instead of the automatic key based on the name of the GitHub job and values of the inputs `package`, `exclude` and `manifest-path`. Might be provided e.g. to share the cache between the jobs.'
required: false required: false
default: '' default: ''
prefix-key: prefix-key:

2
dist/index.js vendored

File diff suppressed because one or more lines are too long

View file

@ -9,6 +9,7 @@ import {
getErrorMessage, getErrorMessage,
getPlatformMatchingTarget, getPlatformMatchingTarget,
getRustcVersion, getRustcVersion,
optionFromList,
optionIfValueProvided, optionIfValueProvided,
} from "./utils"; } from "./utils";
import { RustdocCache } from "./rustdoc-cache"; import { RustdocCache } from "./rustdoc-cache";
@ -17,7 +18,8 @@ const CARGO_TARGET_DIR = path.join("semver-checks", "target");
function getCheckReleaseArguments(): string[] { function getCheckReleaseArguments(): string[] {
return [ return [
optionIfValueProvided("--package", rustCore.input.getInput("package")), optionFromList("--package", rustCore.input.getInputList("package")),
optionFromList("--exclude", rustCore.input.getInputList("exclude")),
optionIfValueProvided("--manifest-path", rustCore.input.getInput("manifest-path")), optionIfValueProvided("--manifest-path", rustCore.input.getInput("manifest-path")),
rustCore.input.getInputBool("verbose") ? ["--verbose"] : [], rustCore.input.getInputBool("verbose") ? ["--verbose"] : [],
].flat(); ].flat();

View file

@ -1,16 +1,12 @@
import os = require("os"); import os = require("os");
import * as path from "path"; import * as path from "path";
import * as crypto from "crypto";
import * as cache from "@actions/cache"; import * as cache from "@actions/cache";
import * as core from "@actions/core"; import * as core from "@actions/core";
import * as rustCore from "@actions-rs/core"; import * as rustCore from "@actions-rs/core";
import { import { getCargoSemverChecksVersion, getRustcVersion, hashFilesOrEmpty } from "./utils";
getCargoSemverChecksVersion,
getRustcVersion,
hashFilesOrEmpty,
hashIfNotEmpty,
} from "./utils";
export class RustdocCache { export class RustdocCache {
private readonly cargo; private readonly cargo;
@ -71,11 +67,12 @@ export class RustdocCache {
} }
private getRunDependentKey(): string { private getRunDependentKey(): string {
return [ const hasher = crypto.createHash("md5");
process.env["GITHUB_JOB"] || "", hasher.update(JSON.stringify({ package: rustCore.input.getInputList("package").sort() }));
rustCore.input.getInput("package"), hasher.update(JSON.stringify({ exclude: rustCore.input.getInputList("exclude").sort() }));
hashIfNotEmpty(rustCore.input.getInput("manifest-path")), hasher.update(JSON.stringify({ manifest_path: rustCore.input.getInput("manifest-path") }));
].join("-");
return [process.env["GITHUB_JOB"] || "", hasher.digest("hex").substring(0, 16)].join("-");
} }
private getLocalCacheHash(): string { private getLocalCacheHash(): string {

View file

@ -3,7 +3,6 @@ import hashFiles = require("hash-files");
import * as exec from "@actions/exec"; import * as exec from "@actions/exec";
import * as rustCore from "@actions-rs/core"; import * as rustCore from "@actions-rs/core";
import { createHash } from "node:crypto";
export function getErrorMessage(error: unknown): string { export function getErrorMessage(error: unknown): string {
if (error instanceof Error) { if (error instanceof Error) {
@ -31,6 +30,10 @@ export function optionIfValueProvided(option: string, value?: string): string[]
return value ? [option, value] : []; return value ? [option, value] : [];
} }
export function optionFromList(option: string, values: string[]): string[] {
return values.map((value) => [option, value]).flat();
}
export function hashFilesOrEmpty(patterns: string[]): string { export function hashFilesOrEmpty(patterns: string[]): string {
try { try {
return hashFiles.sync({ files: patterns }); return hashFiles.sync({ files: patterns });
@ -39,10 +42,6 @@ export function hashFilesOrEmpty(patterns: string[]): string {
} }
} }
export function hashIfNotEmpty(str: string): string {
return createHash("md5").update(str).digest("hex");
}
function makeExecOptions(stdout: { s: string }): exec.ExecOptions { function makeExecOptions(stdout: { s: string }): exec.ExecOptions {
return { return {
listeners: { listeners: {