mirror of
https://github.com/moonrepo/setup-rust.git
synced 2025-04-20 09:38:43 +00:00
Automatic compilation
This commit is contained in:
parent
52e0bc3392
commit
ede6de059f
19 changed files with 192278 additions and 4567 deletions
|
@ -1,6 +0,0 @@
|
|||
coverage/
|
||||
dist/
|
||||
node_modules/
|
||||
*.min.js
|
||||
*.map
|
||||
*.snap
|
13
.eslintrc.js
13
.eslintrc.js
|
@ -1,13 +0,0 @@
|
|||
/* eslint-disable sort-keys */
|
||||
|
||||
module.exports = {
|
||||
root: true,
|
||||
extends: ['moon', 'moon/node'],
|
||||
parserOptions: {
|
||||
project: 'tsconfig.json',
|
||||
tsconfigRootDir: __dirname,
|
||||
},
|
||||
rules: {
|
||||
'unicorn/prefer-top-level-await': 'off',
|
||||
},
|
||||
};
|
50
.github/workflows/ci.yml
vendored
50
.github/workflows/ci.yml
vendored
|
@ -1,50 +0,0 @@
|
|||
name: 'Pipeline'
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'master'
|
||||
pull_request:
|
||||
jobs:
|
||||
ci:
|
||||
name: 'CI'
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm run check
|
||||
action-default:
|
||||
name: 'Action - Default'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm run build
|
||||
- uses: ./ # self
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
action-warmup:
|
||||
name: 'Action - Cache warmup'
|
||||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm run build
|
||||
- uses: ./ # self
|
||||
with:
|
||||
cache-base: master
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
20
.github/workflows/publish.yml
vendored
20
.github/workflows/publish.yml
vendored
|
@ -1,20 +0,0 @@
|
|||
name: Publish
|
||||
on:
|
||||
release:
|
||||
types: [published, edited]
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
ref: ${{ github.event.release.tag_name }}
|
||||
- uses: actions/setup-node@v4
|
||||
- run: npm install -g pnpm
|
||||
- run: pnpm install
|
||||
- run: pnpm run build
|
||||
- uses: aboutte/build-and-tag-action@v2
|
||||
with:
|
||||
additional_files: 'dist/post/index.js'
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ github.token }}
|
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -1,2 +0,0 @@
|
|||
node_modules/
|
||||
dist/
|
54
CHANGELOG.md
54
CHANGELOG.md
|
@ -1,54 +0,0 @@
|
|||
# 1.2.2
|
||||
|
||||
- Updated dependencies.
|
||||
|
||||
# 1.2.1
|
||||
|
||||
- Pinned the `cargo-binstall` version to v1.8 to work around the 404 errors in CI.
|
||||
- Added support for the `CARGO_BINSTALL_VERSION` environment variable.
|
||||
|
||||
# 1.2.0
|
||||
|
||||
- Added a `target-dirs` input, allowing the target folders to be specified. Can now cache multiple
|
||||
target folders.
|
||||
- Updated to skip caching a directory if it does not exist, instead of failing.
|
||||
- Updated dependencies.
|
||||
|
||||
# 1.1.0
|
||||
|
||||
- Added a `cache-base` input. When provided, will only save cache on this branch/ref, but will
|
||||
restore cache on all branches/refs.
|
||||
- Updated dependencies.
|
||||
|
||||
# 1.0.3
|
||||
|
||||
- Include `GITHUB_WORKFLOW` in cache key.
|
||||
- Updated dependencies.
|
||||
|
||||
# 1.0.2
|
||||
|
||||
- Switch to Node.js v20.
|
||||
|
||||
# 1.0.1
|
||||
|
||||
- Fixed an issue where a module was missing from the build.
|
||||
|
||||
# 1.0.0
|
||||
|
||||
- Will now install `rustup` if it does not exist in the environment.
|
||||
- Added musl support to `cargo-binstall`.
|
||||
|
||||
# 0.6.0
|
||||
|
||||
- Breaking: Cargo `bins` must provide the `cargo-` crate prefix manually. This change allows
|
||||
non-crate globals to be installed.
|
||||
- Added a `cache-target` input, to customize which target profile is cached. Defaults to `debug`.
|
||||
|
||||
# 0.5.0
|
||||
|
||||
- Added `inherit-toolchain` input to inherit all settings from `rust-toolchain.toml`, and not just
|
||||
`channel`.
|
||||
|
||||
# 0.1.0
|
||||
|
||||
- Initial release.
|
197
README.md
197
README.md
|
@ -1,197 +0,0 @@
|
|||
# Setup Rust and Cargo
|
||||
|
||||
A one-stop-shop GitHub action for setting up Rust and Cargo. Will automatically setup the Rust
|
||||
toolchain with `rustup`, cache the `~/.cargo/registry` and `/target/debug` directories, and install
|
||||
Cargo binaries (when applicable).
|
||||
|
||||
```yaml
|
||||
jobs:
|
||||
ci:
|
||||
name: CI
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
# ...
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
- run: cargo test
|
||||
```
|
||||
|
||||
## Inputs
|
||||
|
||||
The following inputs are available for the action, and can be passed via `with`. All inputs are
|
||||
optional.
|
||||
|
||||
- `bins` - Comma-separated list of global binaries to install into Cargo.
|
||||
- `cache` - Toggle caching of directories. Defaults to `true`.
|
||||
- `cache-base` - Base branch/ref to save a warmup cache on. Other branches/refs will restore from
|
||||
this base.
|
||||
- `cache-target` - Name of the target profile to cache. Defaults to `debug`.
|
||||
- `channel` - Toolchain specification/channel to explicitly install.
|
||||
- `components` - Comma-separated list of additional components to install.
|
||||
- `inherit-toolchain` - Inherit all toolchain settings from the `rust-toolchain.toml` file. Defaults
|
||||
to `false`.
|
||||
- `targets` - Comma-separated list of additional targets to install.
|
||||
- `target-dirs` - Comma-separated list of target folder paths, relative from the repository root.
|
||||
Defaults to `target`.
|
||||
- `profile` - Profile to install. Defaults to `minimal`.
|
||||
|
||||
## Configuring the Rust toolchain
|
||||
|
||||
This action will automatically install the appropriate toolchain with `rustup` by inspecting the
|
||||
`RUSTUP_TOOLCHAIN` environment variable or the `rust-toolchain.toml` (preferred) or `rust-toolchain`
|
||||
configuration files. If no toolchain found, will default to `stable`.
|
||||
|
||||
```toml
|
||||
# rust-toolchain.toml
|
||||
[toolchain]
|
||||
channel = "1.68.0"
|
||||
```
|
||||
|
||||
> When loading a configuration file, only the `channel` field is used, while the other fields are
|
||||
> ignored. We chose this approach, as those other fields are typically for develop/release
|
||||
> workflows, but not for CI, which requires a minimal/granular setup. However, this can be
|
||||
> overwritten with the `inherit-toolchain` input.
|
||||
|
||||
The toolchain/channel can also be explicitly configured with the `channel` input, which takes
|
||||
highest precedence.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
channel: '1.65.0'
|
||||
```
|
||||
|
||||
### Profile, components, and targets
|
||||
|
||||
Furthermore, this action supports rustup profiles, components, and targets, which can be configured
|
||||
with the `profile`, `components`, and `targets` inputs respectively. When not defined, profile
|
||||
defaults to `minimal`.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
profile: complete
|
||||
```
|
||||
|
||||
When using components, the input requires a comma separated list of component names.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
components: clippy
|
||||
- run: cargo clippy --workspace
|
||||
```
|
||||
|
||||
When using targets, the input requires a comma separated list of target triples.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
targets: 'x86_64-pc-windows-msvc,x86_64-pc-windows-gnu'
|
||||
```
|
||||
|
||||
## Installing Cargo binaries
|
||||
|
||||
If you require `cargo-make`, `cargo-nextest`, or other global binaries, this action supports
|
||||
installing Cargo binaries through the `bins` input, which requires a comma-separated list of crate
|
||||
names.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
bins: cargo-nextest, cargo-insta@1.28.0
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
```
|
||||
|
||||
> Binaries are installed with [`cargo-binstall`](https://crates.io/crates/cargo-binstall) under the
|
||||
> hood. We suggest setting `GITHUB_TOKEN` to avoid rate limiting.
|
||||
|
||||
## Caching in CI
|
||||
|
||||
By default this action will cache the `~/.cargo/registry` and `/target/debug` directories to improve
|
||||
CI times. To disable caching, set the `cache` input to `false`. Furthermore, the target profile can
|
||||
be changed with the `cache-target` input, which defaults to `debug`.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
cache: false
|
||||
cache-target: release
|
||||
```
|
||||
|
||||
The following optimizations and considerations are taken into account when caching:
|
||||
|
||||
- `~/.cargo`
|
||||
- The `/bin` directory is not cached as we manage binary installation in this action via the
|
||||
`bins` input.
|
||||
- The `/git` directory is not cached as it's not necessary for CI. When required by Cargo or a
|
||||
crate, a checkout will be performed on-demand.
|
||||
- The `/registry` directory is _cleaned_ before saving the cache. This includes removing `src`,
|
||||
`.cache`, and any other unnecessary files.
|
||||
- `/target`
|
||||
- Only the `/debug` profile is cached, as this profile is typically used for formatting, linting,
|
||||
and testing. This can be changed with the `cache-target` input.
|
||||
- The `/examples` and `/incremental` sub-directories are not cached as they are not necessary for
|
||||
CI.
|
||||
- All dep-info (`*.d`) files are removed, as they're meant for build systems and signaling
|
||||
re-executions.
|
||||
|
||||
> The following sources are hashed for the generated cache key: `$GITHUB_JOB`, `Cargo.lock`, Rust
|
||||
> version, Rust commit hash, and OS.
|
||||
|
||||
### Warmup strategy
|
||||
|
||||
Another strategy that we support is called a warmup cache, where a base branch/ref is used to
|
||||
generate and save the cache (like master), and all other branches/refs will _only_ restore this
|
||||
cache (and not save).
|
||||
|
||||
This can be enabled with the `cache-base` input, which requires a branch/ref name. This input also
|
||||
supports regex.
|
||||
|
||||
```yaml
|
||||
- uses: moonrepo/setup-rust@v1
|
||||
with:
|
||||
cache-base: master
|
||||
# With regex
|
||||
cache-base: (master|main|develop)
|
||||
```
|
||||
|
||||
## Compared to
|
||||
|
||||
### `actions-rs/*`
|
||||
|
||||
The "official" actions have served their purpose, but after 3 years of no updates, severe lack of
|
||||
maintenance, and being full of deprecation warnings, it was time to create something new.
|
||||
|
||||
Outside of being evergreen, this action also supports the following features:
|
||||
|
||||
- Automatically caches.
|
||||
- Installs Cargo bins.
|
||||
- Assumes `rustup`, `cargo`, and other commands are available globally. This allows you to use them
|
||||
directly in a `run` command, without having to use `actions-rs/cargo`.
|
||||
|
||||
### `dtolnay/rust-toolchain`
|
||||
|
||||
Our action is very similar to theirs, which was a great implementation reference, but our action
|
||||
also supports the following features:
|
||||
|
||||
- Extracts the toolchain/channel from `rust-toolchain.toml` and `rust-toolchain` configuration
|
||||
files.
|
||||
- Automatically caches.
|
||||
- Installs Cargo bins.
|
||||
|
||||
### `Swatinem/rust-cache`
|
||||
|
||||
Their action only caches for Rust/Cargo, it doesn't actually setup Rust/Cargo. Our action is meant
|
||||
to do _everything_, but it's not as configurable as the alternatives.
|
||||
|
||||
Here are the key differences between the two:
|
||||
|
||||
- Theirs caches the entire `~/.cargo` directory, while our action only caches `~/.cargo/registry`.
|
||||
[View the reasoning above](#caching-in-ci).
|
||||
- Our action also avoids an array of `~/.cargo/bin` issues that theirs currently has.
|
||||
- Theirs includes compiler specific environment variables in the cache key, while our action
|
||||
currently does not.
|
||||
- Theirs supports a handful of inputs for controlling the cache key, while ours does not.
|
||||
- Theirs is a bit more smart in what it caches, while ours is a bit more brute force. We simply
|
||||
cache specific directories as-is after cleaning.
|
96156
dist/index.js
vendored
Normal file
96156
dist/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
96122
dist/post/index.js
vendored
Normal file
96122
dist/post/index.js
vendored
Normal file
File diff suppressed because one or more lines are too long
62
index.ts
62
index.ts
|
@ -1,62 +0,0 @@
|
|||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as io from '@actions/io';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import { CARGO_HOME } from './src/cache';
|
||||
import { installBins, restoreCache } from './src/cargo';
|
||||
import { installToolchain } from './src/rust';
|
||||
|
||||
export async function installRustup() {
|
||||
try {
|
||||
await io.which('rustup', true);
|
||||
return;
|
||||
} catch {
|
||||
// Doesn't exist
|
||||
}
|
||||
|
||||
core.info('rustup does not exist, attempting to install');
|
||||
|
||||
const script = await tc.downloadTool(
|
||||
process.platform === 'win32' ? 'https://win.rustup.rs' : 'https://sh.rustup.rs',
|
||||
path.join(os.tmpdir(), 'rustup-init'),
|
||||
);
|
||||
|
||||
core.info(`Downloaded installation script to ${script}`);
|
||||
|
||||
// eslint-disable-next-line no-magic-numbers
|
||||
await fs.promises.chmod(script, 0o755);
|
||||
|
||||
await exec.exec(script, ['--default-toolchain', 'none', '-y']);
|
||||
|
||||
core.info('Installed rustup');
|
||||
}
|
||||
|
||||
async function run() {
|
||||
core.info('Setting cargo environment variables');
|
||||
|
||||
core.exportVariable('CARGO_INCREMENTAL', '0');
|
||||
core.exportVariable('CARGO_TERM_COLOR', 'always');
|
||||
|
||||
core.info('Adding ~/.cargo/bin to PATH');
|
||||
|
||||
core.addPath(path.join(CARGO_HOME, 'bin'));
|
||||
|
||||
try {
|
||||
await installRustup();
|
||||
await installToolchain();
|
||||
await installBins();
|
||||
|
||||
// Restore cache after the toolchain has been installed,
|
||||
// as we use the rust version and commit hashes in the cache key!
|
||||
await restoreCache();
|
||||
} catch (error: unknown) {
|
||||
core.setFailed((error as Error).message);
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
void run();
|
41
package.json
41
package.json
|
@ -1,41 +0,0 @@
|
|||
{
|
||||
"name": "@moonrepo/setup-rust",
|
||||
"version": "1.2.2",
|
||||
"description": "A GitHub action for setting up Rust and Cargo.",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "ncc build ./index.ts && ncc build ./post.ts --out ./dist/post",
|
||||
"check": "pnpm run lint && pnpm run typecheck",
|
||||
"lint": "eslint --ext .ts,.js --fix .",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/moonrepo/setup-rust"
|
||||
},
|
||||
"author": "Miles Johnson",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@actions/cache": "^4.0.0",
|
||||
"@actions/core": "^1.10.1",
|
||||
"@actions/exec": "^1.1.1",
|
||||
"@actions/glob": "^0.5.0",
|
||||
"@actions/io": "^1.1.3",
|
||||
"@actions/tool-cache": "^2.0.1",
|
||||
"@ltd/j-toml": "^1.38.0",
|
||||
"detect-libc": "^2.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^20.13.4",
|
||||
"@vercel/ncc": "^0.38.1",
|
||||
"eslint": "^8.53.0",
|
||||
"eslint-config-moon": "^2.0.11",
|
||||
"prettier": "^3.2.5",
|
||||
"prettier-config-moon": "^1.1.2",
|
||||
"tsconfig-moon": "^1.3.0",
|
||||
"typescript": "^5.4.5"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=20.0.0"
|
||||
}
|
||||
}
|
3576
pnpm-lock.yaml
generated
3576
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load diff
19
post.ts
19
post.ts
|
@ -1,19 +0,0 @@
|
|||
import * as core from '@actions/core';
|
||||
import { saveCache } from './src/cargo';
|
||||
|
||||
async function run() {
|
||||
try {
|
||||
const base = core.getInput('cache-base');
|
||||
|
||||
// Only save the cache for the following 2 scenarios:
|
||||
// - If not using the base warmup strategy.
|
||||
// - If using the base warmup strategy, and the current ref matches.
|
||||
if (!base || (base && !!(process.env.GITHUB_REF_NAME ?? '').match(base))) {
|
||||
await saveCache();
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
core.setFailed((error as Error).message);
|
||||
}
|
||||
}
|
||||
|
||||
void run();
|
|
@ -1 +0,0 @@
|
|||
module.exports = 'prettier-config-moon';
|
102
src/cache.ts
102
src/cache.ts
|
@ -1,102 +0,0 @@
|
|||
import crypto from 'node:crypto';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as glob from '@actions/glob';
|
||||
import { RUST_HASH, RUST_VERSION } from './rust';
|
||||
|
||||
export const CARGO_HOME = process.env.CARGO_HOME ?? path.join(os.homedir(), '.cargo');
|
||||
|
||||
export const WORKSPACE_ROOT = process.env.GITHUB_WORKSPACE ?? process.cwd();
|
||||
|
||||
export function isCacheEnabled(): boolean {
|
||||
return core.getBooleanInput('cache') && cache.isFeatureAvailable();
|
||||
}
|
||||
|
||||
export function getCacheTarget(): string {
|
||||
return core.getInput('cache-target') || 'debug';
|
||||
}
|
||||
|
||||
export function getTargetPaths(): string[] {
|
||||
const profile = getCacheTarget();
|
||||
const dirs = core.getInput('target-dirs', { required: true }).split(',');
|
||||
|
||||
return dirs
|
||||
.map((dir) => dir.trim())
|
||||
.filter(Boolean)
|
||||
.map((dir) => path.join(WORKSPACE_ROOT, dir, profile));
|
||||
}
|
||||
|
||||
export function getCachePaths(): string[] {
|
||||
return [
|
||||
// ~/.cargo/registry
|
||||
path.join(CARGO_HOME, 'registry'),
|
||||
// /workspace/target/debug
|
||||
...getTargetPaths(),
|
||||
];
|
||||
}
|
||||
|
||||
export function getCachePrefixes(): string[] {
|
||||
return [`setup-rustcargo-v1-${process.platform}`, 'setup-rustcargo-v1'];
|
||||
}
|
||||
|
||||
export async function getPrimaryCacheKey() {
|
||||
const hasher = crypto.createHash('sha1');
|
||||
|
||||
core.info('Generating cache key');
|
||||
|
||||
core.debug(`Hashing Rust version = ${RUST_VERSION}`);
|
||||
hasher.update(RUST_VERSION);
|
||||
|
||||
core.debug(`Hashing Rust commit hash = ${RUST_HASH}`);
|
||||
hasher.update(RUST_HASH);
|
||||
|
||||
const cacheTarget = getCacheTarget();
|
||||
|
||||
core.debug(`Hashing target profile = ${cacheTarget}`);
|
||||
hasher.update(cacheTarget);
|
||||
|
||||
// When warming up, loosen the cache key to allow for more cache hits
|
||||
if (core.getInput('cache-base')) {
|
||||
core.debug('Using warmup strategy, not hashing Cargo.lock, GITHUB_WORKFLOW, or GITHUB_JOB');
|
||||
hasher.update('warmup');
|
||||
|
||||
const baseRef = process.env.GITHUB_BASE_REF ?? '';
|
||||
|
||||
if (
|
||||
baseRef === 'master' ||
|
||||
baseRef === 'main' ||
|
||||
baseRef === 'trunk' ||
|
||||
baseRef.startsWith('develop') ||
|
||||
baseRef.startsWith('release')
|
||||
) {
|
||||
core.debug(`Hashing GITHUB_BASE_REF = ${baseRef}`);
|
||||
hasher.update(baseRef);
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, these add far too much granularity to the cache key
|
||||
else {
|
||||
const lockHash = await glob.hashFiles('Cargo.lock');
|
||||
|
||||
core.debug(`Hashing Cargo.lock = ${lockHash}`);
|
||||
hasher.update(lockHash);
|
||||
|
||||
const workflow = process.env.GITHUB_WORKFLOW;
|
||||
|
||||
if (workflow) {
|
||||
core.debug(`Hashing GITHUB_WORKFLOW = ${workflow}`);
|
||||
hasher.update(workflow);
|
||||
}
|
||||
|
||||
const job = process.env.GITHUB_JOB;
|
||||
|
||||
if (job) {
|
||||
core.debug(`Hashing GITHUB_JOB = ${job}`);
|
||||
hasher.update(job);
|
||||
}
|
||||
}
|
||||
|
||||
return `${getCachePrefixes()[0]}-${hasher.digest('hex')}`;
|
||||
}
|
230
src/cargo.ts
230
src/cargo.ts
|
@ -1,230 +0,0 @@
|
|||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { family } from 'detect-libc';
|
||||
import * as cache from '@actions/cache';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import * as glob from '@actions/glob';
|
||||
import * as tc from '@actions/tool-cache';
|
||||
import {
|
||||
CARGO_HOME,
|
||||
getCachePaths,
|
||||
getCachePrefixes,
|
||||
getCacheTarget,
|
||||
getPrimaryCacheKey,
|
||||
isCacheEnabled,
|
||||
WORKSPACE_ROOT,
|
||||
} from './cache';
|
||||
import { rmrf } from './fs';
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export async function downloadAndInstallBinstall(binDir: string) {
|
||||
core.info('cargo-binstall does not exist, attempting to install');
|
||||
|
||||
let arch;
|
||||
let file;
|
||||
|
||||
switch (process.arch) {
|
||||
case 'x64':
|
||||
arch = 'x86_64';
|
||||
break;
|
||||
case 'arm':
|
||||
arch = 'armv7';
|
||||
break;
|
||||
case 'arm64':
|
||||
arch = 'aarch64';
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported architecture: ${process.arch}`);
|
||||
}
|
||||
|
||||
switch (process.platform) {
|
||||
case 'linux': {
|
||||
let lib = 'gnu';
|
||||
|
||||
if ((await family()) === 'musl') {
|
||||
lib = 'musl';
|
||||
}
|
||||
|
||||
if (process.arch === 'arm') {
|
||||
lib += 'eabihf';
|
||||
}
|
||||
|
||||
file = `${arch}-unknown-linux-${lib}.tgz`;
|
||||
break;
|
||||
}
|
||||
case 'darwin':
|
||||
file = `${arch}-apple-darwin.zip`;
|
||||
break;
|
||||
case 'win32':
|
||||
file = `${arch}-pc-windows-msvc.zip`;
|
||||
break;
|
||||
default:
|
||||
throw new Error(`Unsupported platform: ${process.platform}`);
|
||||
}
|
||||
|
||||
// https://github.com/cargo-bins/cargo-binstall/issues/1864
|
||||
const version = process.env.CARGO_BINSTALL_VERSION ?? 'v1.8.0';
|
||||
const url = version === 'latest'
|
||||
? `https://github.com/cargo-bins/cargo-binstall/releases/latest/download/cargo-binstall-${file}`
|
||||
: `https://github.com/cargo-bins/cargo-binstall/releases/download/${version}/cargo-binstall-${file}`;
|
||||
const dlPath = await tc.downloadTool(url);
|
||||
|
||||
if (url.endsWith('.zip')) {
|
||||
await tc.extractZip(dlPath, binDir);
|
||||
} else if (url.endsWith('.tgz')) {
|
||||
await tc.extractTar(dlPath, binDir);
|
||||
}
|
||||
}
|
||||
|
||||
export async function installBins() {
|
||||
const bins = core
|
||||
.getInput('bins')
|
||||
.split(',')
|
||||
.map((bin) => bin.trim())
|
||||
.filter(Boolean);
|
||||
|
||||
if (isCacheEnabled()) {
|
||||
bins.push('cargo-cache');
|
||||
}
|
||||
|
||||
if (bins.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
core.info('Installing additional binaries');
|
||||
|
||||
const binDir = path.join(CARGO_HOME, 'bin');
|
||||
|
||||
if (!fs.existsSync(path.join(binDir, 'cargo-binstall'))) {
|
||||
await downloadAndInstallBinstall(binDir);
|
||||
}
|
||||
|
||||
await exec.exec('cargo', ['binstall', '--no-confirm', '--log-level', 'info', ...bins]);
|
||||
}
|
||||
|
||||
export async function cleanCargoRegistry() {
|
||||
core.info('Cleaning ~/.cargo before saving');
|
||||
|
||||
const registryDir = path.join(CARGO_HOME, 'registry');
|
||||
|
||||
// .cargo/registry/src - Delete entirely
|
||||
await exec.exec('cargo', ['cache', '--autoclean']);
|
||||
|
||||
// .cargo/registry/index - Delete .cache directories
|
||||
const indexDir = path.join(registryDir, 'index');
|
||||
|
||||
if (fs.existsSync(indexDir)) {
|
||||
await Promise.all(
|
||||
fs.readdirSync(indexDir).map(async (index) => {
|
||||
if (fs.existsSync(path.join(indexDir, index, '.git'))) {
|
||||
await rmrf(path.join(indexDir, index, '.cache'));
|
||||
}
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
// .cargo/registry/cache - Do nothing?
|
||||
}
|
||||
|
||||
// https://doc.rust-lang.org/cargo/guide/build-cache.html
|
||||
export async function cleanTargetProfile() {
|
||||
const targetProfile = getCacheTarget();
|
||||
|
||||
core.info(`Cleaning target/${targetProfile} before saving`);
|
||||
|
||||
const targetDir = path.join(WORKSPACE_ROOT, 'target', targetProfile);
|
||||
|
||||
// target/*/{examples,incremental} - Not required in CI
|
||||
core.info('Removing examples and incremental directories');
|
||||
|
||||
await Promise.all(
|
||||
['examples', 'incremental'].map(async (dirName) => {
|
||||
const dir = path.join(targetDir, dirName);
|
||||
|
||||
if (fs.existsSync(dir)) {
|
||||
await rmrf(dir);
|
||||
}
|
||||
}),
|
||||
);
|
||||
|
||||
// target/**/*.d - Not required in CI?
|
||||
core.info('Removing dep-info files (*.d)');
|
||||
|
||||
const globber = await glob.create(path.join(targetDir, '**/*.d'));
|
||||
const files = await globber.glob();
|
||||
|
||||
await Promise.all(files.map(rmrf));
|
||||
}
|
||||
|
||||
export async function saveCache() {
|
||||
if (!isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const primaryKey = await getPrimaryCacheKey();
|
||||
const cacheHitKey = core.getState('cache-hit-key');
|
||||
|
||||
if (cacheHitKey === primaryKey) {
|
||||
core.info(`Cache hit occured on the key ${cacheHitKey}, not saving cache`);
|
||||
return;
|
||||
}
|
||||
|
||||
const cachePaths = getCachePaths().filter((cachePath) => {
|
||||
if (!fs.existsSync(cachePath)) {
|
||||
core.info(`Cache path ${cachePath} does not exist, skipping`);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
if (cachePaths.length === 0) {
|
||||
core.info('No paths to cache, skipping save entirely');
|
||||
return;
|
||||
}
|
||||
|
||||
await cleanCargoRegistry();
|
||||
await cleanTargetProfile();
|
||||
|
||||
core.info(`Saving cache with key ${primaryKey}`);
|
||||
|
||||
core.debug(`Cache key: ${primaryKey}`);
|
||||
core.debug('Cache paths:');
|
||||
|
||||
for (const cachePath of cachePaths) {
|
||||
core.debug(`- ${cachePath}`);
|
||||
}
|
||||
|
||||
await cache.saveCache(cachePaths, primaryKey);
|
||||
}
|
||||
|
||||
export async function restoreCache() {
|
||||
if (!isCacheEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
core.info('Attempting to restore cache');
|
||||
|
||||
const primaryKey = await getPrimaryCacheKey();
|
||||
const cachePaths = getCachePaths();
|
||||
|
||||
core.debug(`Cache key: ${primaryKey}`);
|
||||
core.debug('Cache paths:');
|
||||
|
||||
for (const cachePath of cachePaths) {
|
||||
core.debug(`- ${cachePath}`);
|
||||
}
|
||||
|
||||
const cacheKey = await cache.restoreCache(getCachePaths(), primaryKey, getCachePrefixes());
|
||||
|
||||
if (cacheKey) {
|
||||
core.saveState('cache-hit-key', cacheKey);
|
||||
core.info(`Cache restored using key ${primaryKey}`);
|
||||
} else {
|
||||
core.info(`Cache does not exist using key ${primaryKey}`);
|
||||
}
|
||||
|
||||
core.setOutput('cache-key', cacheKey ?? primaryKey);
|
||||
core.setOutput('cache-hit', !!cacheKey);
|
||||
}
|
12
src/fs.ts
12
src/fs.ts
|
@ -1,12 +0,0 @@
|
|||
import * as core from '@actions/core';
|
||||
import * as io from '@actions/io';
|
||||
|
||||
export async function rmrf(dir: string) {
|
||||
core.debug(`Deleting ${dir}`);
|
||||
|
||||
try {
|
||||
await io.rmRF(dir);
|
||||
} catch (error: unknown) {
|
||||
core.warning(`Failed to delete ${dir}: ${error}`);
|
||||
}
|
||||
}
|
166
src/rust.ts
166
src/rust.ts
|
@ -1,166 +0,0 @@
|
|||
/* eslint-disable import/no-mutable-exports */
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import * as core from '@actions/core';
|
||||
import * as exec from '@actions/exec';
|
||||
import TOML from '@ltd/j-toml';
|
||||
|
||||
interface Toolchain {
|
||||
channel: string;
|
||||
components: string[];
|
||||
targets: string[];
|
||||
profile: string;
|
||||
}
|
||||
|
||||
interface ToolchainConfig {
|
||||
toolchain: Partial<Toolchain>;
|
||||
}
|
||||
|
||||
const DEFAULT_TOOLCHAIN: Toolchain = {
|
||||
channel: 'stable',
|
||||
components: [],
|
||||
profile: 'minimal',
|
||||
targets: [],
|
||||
};
|
||||
|
||||
export let RUST_VERSION = core.getState('rust-version');
|
||||
export let RUST_HASH = core.getState('rust-hash');
|
||||
|
||||
export async function extractRustVersion(toolchain: string) {
|
||||
let out = '';
|
||||
|
||||
await exec.exec('rustc', [`+${toolchain}`, '--version', '--verbose'], {
|
||||
listeners: {
|
||||
stdout(data: Buffer) {
|
||||
out += data.toString();
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const extract = (key: string, line: string) => {
|
||||
const value = line.split(':')[1].trim();
|
||||
|
||||
core.setOutput(key, value);
|
||||
core.saveState(key, value);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
out.split('\n').forEach((line) => {
|
||||
if (line.startsWith('commit-hash')) {
|
||||
RUST_HASH = extract('rust-hash', line);
|
||||
|
||||
// version
|
||||
} else if (line.startsWith('release')) {
|
||||
RUST_VERSION = extract('rust-version', line);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function parseConfig(configPath: string): Partial<Toolchain> {
|
||||
const contents = fs.readFileSync(configPath, 'utf8').trim();
|
||||
|
||||
if (!contents.includes('[toolchain]')) {
|
||||
core.debug('No [toolchain] section found, assuming legacy format');
|
||||
|
||||
return { channel: contents };
|
||||
}
|
||||
|
||||
const config = TOML.parse(contents) as unknown as ToolchainConfig;
|
||||
|
||||
if (config.toolchain) {
|
||||
if (core.getBooleanInput('inherit-toolchain')) {
|
||||
core.debug('Inheriting entire [toolchain] section');
|
||||
|
||||
return { ...config.toolchain };
|
||||
}
|
||||
|
||||
if (config.toolchain.channel) {
|
||||
core.debug('Found channel in [toolchain] section');
|
||||
|
||||
return { channel: config.toolchain.channel };
|
||||
}
|
||||
}
|
||||
|
||||
core.debug('No channel found in [toolchain] section');
|
||||
|
||||
return {};
|
||||
}
|
||||
|
||||
// https://rust-lang.github.io/rustup/overrides.html
|
||||
export function detectToolchain(): Toolchain {
|
||||
core.info('Detecting toolchain');
|
||||
|
||||
const toolchain = { ...DEFAULT_TOOLCHAIN };
|
||||
|
||||
if (process.env.RUSTUP_TOOLCHAIN) {
|
||||
core.info('Using toolchain from RUSTUP_TOOLCHAIN environment variable');
|
||||
|
||||
Object.assign(toolchain, {
|
||||
channel: process.env.RUSTUP_TOOLCHAIN,
|
||||
});
|
||||
} else {
|
||||
core.info('Loading rust-toolchain.toml or rust-toolchain file');
|
||||
|
||||
for (const configName of ['rust-toolchain.toml', 'rust-toolchain']) {
|
||||
const configPath = path.join(process.cwd(), configName);
|
||||
|
||||
if (fs.existsSync(configPath)) {
|
||||
core.debug(`Found ${configName}, parsing TOML`);
|
||||
|
||||
Object.assign(toolchain, parseConfig(configPath));
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
core.info('Inheriting toolchain settings from inputs');
|
||||
|
||||
(Object.keys(DEFAULT_TOOLCHAIN) as (keyof typeof DEFAULT_TOOLCHAIN)[]).forEach((key) => {
|
||||
const input = core.getInput(key);
|
||||
|
||||
if (input) {
|
||||
core.debug(`Found input for ${key}: ${input}`);
|
||||
|
||||
if (key === 'components' || key === 'targets') {
|
||||
input.split(',').forEach((part) => {
|
||||
toolchain[key].push(part.trim());
|
||||
});
|
||||
} else {
|
||||
toolchain[key] = input;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return toolchain;
|
||||
}
|
||||
|
||||
export async function installToolchain() {
|
||||
const toolchain = detectToolchain();
|
||||
|
||||
core.info('Installing toolchain with rustup');
|
||||
|
||||
const args = ['toolchain', 'install', toolchain.channel, '--profile', toolchain.profile];
|
||||
|
||||
toolchain.targets.forEach((target) => {
|
||||
args.push('--target', target);
|
||||
});
|
||||
|
||||
toolchain.components.forEach((component) => {
|
||||
args.push('--component', component);
|
||||
});
|
||||
|
||||
if (toolchain.channel === 'nightly' && toolchain.components.length > 0) {
|
||||
args.push('--allow-downgrade');
|
||||
}
|
||||
|
||||
args.push('--no-self-update');
|
||||
|
||||
await exec.exec('rustup', args);
|
||||
await exec.exec('rustup', ['default', toolchain.channel]);
|
||||
|
||||
core.info('Logging installed toolchain versions');
|
||||
|
||||
await extractRustVersion(toolchain.channel);
|
||||
}
|
|
@ -1,16 +0,0 @@
|
|||
{
|
||||
"extends": "tsconfig-moon/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"allowJs": true,
|
||||
"module": "NodeNext",
|
||||
"moduleResolution": "NodeNext",
|
||||
"noEmit": true,
|
||||
"verbatimModuleSyntax": false
|
||||
},
|
||||
"include": [
|
||||
".eslintrc.js",
|
||||
"*.js",
|
||||
"*.ts",
|
||||
"tests/*.ts"
|
||||
]
|
||||
}
|
Loading…
Add table
Reference in a new issue