├── .github └── workflows │ ├── lint.yml │ └── test.yml ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Changes.md ├── LICENSE ├── README.md ├── action.yml ├── determine-cargo-commands.sh ├── determine-cross-version.sh ├── git ├── hooks │ └── pre-commit.sh ├── setup.pl └── setup.py ├── install-cross-nix.sh ├── mise.toml ├── parse-and-set-rust-cache-parameters.py ├── perltidyrc ├── precious.toml ├── run-tests ├── Cargo.lock ├── Cargo.toml └── src │ └── main.rs ├── set-build-command.sh ├── set-cross-compile.py ├── set-cross-dir.sh ├── strip-binary.sh ├── test-project ├── Cargo.lock ├── Cargo.toml ├── bench │ ├── Cargo.lock │ ├── Cargo.toml │ ├── benches │ │ └── benchmark.rs │ └── lib.rs ├── src │ ├── bin1.rs │ └── bin2.rs └── subcrate │ ├── Cargo.lock │ ├── Cargo.toml │ └── src │ └── main.rs └── validate-inputs.py /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: Lint 2 | 3 | on: [push, pull_request] 4 | 5 | env: 6 | GITHUB_TOKEN: ${{ github.token }} 7 | 8 | jobs: 9 | lint: 10 | name: Check that code is lint clean using precious 11 | runs-on: ubuntu-24.04 12 | steps: 13 | - uses: actions/checkout@v4 14 | - name: Install mise 15 | run: | 16 | set -e 17 | set -x 18 | curl https://mise.run | sh 19 | mise install 20 | - name: Run precious 21 | run: | 22 | mise exec -- precious lint -a 23 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Self-test 2 | 3 | on: 4 | push: 5 | pull_request: 6 | 7 | jobs: 8 | test-action: 9 | name: Test action 10 | strategy: 11 | fail-fast: false 12 | matrix: 13 | platform: 14 | - name: FreeBSD-x86_64 15 | runs-on: ubuntu-24.04 16 | target: x86_64-unknown-freebsd 17 | cache-cross-binary: true 18 | expect-file-re: "x86-64.+FreeBSD" 19 | expect-cross: "--expect-cross" 20 | expect-stripped: "" 21 | can-execute: false 22 | 23 | - name: Linux-x86_64 24 | runs-on: ubuntu-24.04 25 | # It's important to use a musl target here to make sure that the step to install 26 | # musl-tools works. 27 | target: x86_64-unknown-linux-musl 28 | cache-cross-binary: true 29 | expect-file-re: "ELF.+x86-64" 30 | expect-cross: "" 31 | expect-stripped: "--expect-stripped" 32 | can-execute: true 33 | 34 | - name: Linux-x86_64 (beta) 35 | runs-on: ubuntu-24.04 36 | target: x86_64-unknown-linux-musl 37 | toolchain: beta 38 | cache-cross-binary: true 39 | expect-file-re: "ELF.+x86-64" 40 | expect-cross: "" 41 | expect-stripped: "--expect-stripped" 42 | can-execute: true 43 | 44 | - name: Linux-x86_64 (nightly) 45 | runs-on: ubuntu-24.04 46 | target: x86_64-unknown-linux-musl 47 | toolchain: nightly 48 | cache-cross-binary: true 49 | expect-file-re: "ELF.+x86-64" 50 | expect-cross: "" 51 | expect-stripped: "--expect-stripped" 52 | can-execute: true 53 | 54 | - name: Linux-x86_64 (force cross) 55 | runs-on: ubuntu-24.04 56 | target: x86_64-unknown-linux-gnu 57 | force-use-cross: true 58 | cache-cross-binary: true 59 | expect-file-re: "ELF.+x86-64" 60 | expect-cross: "--expect-cross" 61 | expect-stripped: "" 62 | can-execute: true 63 | 64 | - name: Linux-x86_64 (stable 8 months ago) 65 | runs-on: ubuntu-24.04 66 | target: x86_64-unknown-linux-gnu 67 | toolchain: "stable 8 months ago" 68 | cache-cross-binary: true 69 | expect-file-re: "ELF.+x86-64" 70 | expect-cross: "" 71 | expect-stripped: "--expect-stripped" 72 | can-execute: true 73 | 74 | - name: Linux-aarch64 75 | runs-on: ubuntu-24.04 76 | target: aarch64-unknown-linux-musl 77 | cache-cross-binary: true 78 | expect-file-re: "aarch64" 79 | expect-cross: "--expect-cross" 80 | expect-stripped: "" 81 | can-execute: true 82 | 83 | - name: Linux-aarch64 (no cache) 84 | runs-on: ubuntu-24.04 85 | target: aarch64-unknown-linux-gnu 86 | cache-cross-binary: false 87 | expect-file-re: "aarch64" 88 | expect-cross: "--expect-cross" 89 | expect-stripped: "" 90 | can-execute: true 91 | 92 | - name: Linux-aarch64 with cross v0.2.3 93 | runs-on: ubuntu-24.04 94 | target: aarch64-unknown-linux-gnu 95 | cross-version: "v0.2.3" 96 | cache-cross-binary: true 97 | expect-file-re: "aarch64" 98 | expect-cross: "--expect-cross" 99 | expect-cross-version: "0.2.3" 100 | expect-stripped: "" 101 | can-execute: true 102 | 103 | - name: Linux-aarch64 with cross v0.2.3 (no cache) 104 | runs-on: ubuntu-24.04 105 | target: aarch64-unknown-linux-gnu 106 | cross-version: "v0.2.3" 107 | cache-cross-binary: false 108 | expect-file-re: "aarch64" 109 | expect-cross: "--expect-cross" 110 | expect-cross-version: "0.2.3" 111 | expect-stripped: "" 112 | can-execute: true 113 | 114 | - name: Linux-aarch64 with cross 19be834 115 | runs-on: ubuntu-24.04 116 | target: aarch64-unknown-linux-gnu 117 | cross-version: "19be834" 118 | cache-cross-binary: true 119 | expect-file-re: "aarch64" 120 | expect-cross: "--expect-cross" 121 | expect-cross-version: "19be834" 122 | expect-stripped: "" 123 | can-execute: true 124 | 125 | - name: Linux-aarch64 with cross 19be834 (no cache) 126 | runs-on: ubuntu-24.04 127 | target: aarch64-unknown-linux-gnu 128 | cross-version: "19be834" 129 | cache-cross-binary: false 130 | expect-file-re: "aarch64" 131 | expect-cross: "--expect-cross" 132 | expect-cross-version: "19be834" 133 | expect-stripped: "" 134 | can-execute: true 135 | 136 | - name: Linux-aarch64 with arm64 host 137 | runs-on: ubuntu-24.04-arm 138 | target: aarch64-unknown-linux-gnu 139 | cache-cross-binary: true 140 | expect-file-re: "aarch64" 141 | expect-cross: "" 142 | expect-stripped: "--expect-stripped" 143 | can-execute: true 144 | 145 | # This fails because of some sort of weird bug in cross. See 146 | # https://github.com/cross-rs/cross/issues/1628 for more details. Until that's fixed 147 | # cross-compiling on Linux ARM won't work without a custom Docker image, which I don't 148 | # want to do just for the sake of testing this. 149 | # 150 | # - name: Linux-aarch64 with arm64 host compile to x86-64 151 | # runs-on: ubuntu-24.04-arm 152 | # target: x86_64-unknown-linux-gnu 153 | # # Until cross produces arm64 binary releases we _have_ to set this when running on 154 | # # arm64. 155 | # cross-version: "c7dee4d" 156 | # cache-cross-binary: true 157 | # expect-file-re: "ELF.+x86-64" 158 | # expect-cross: "--expect-cross" 159 | # expect-stripped: "" 160 | # can-execute: true 161 | 162 | - name: Linux-arm 163 | runs-on: ubuntu-24.04 164 | target: arm-unknown-linux-gnueabi 165 | cache-cross-binary: true 166 | expect-file-re: "32.+ARM" 167 | expect-cross: "--expect-cross" 168 | expect-stripped: "" 169 | can-execute: true 170 | 171 | - name: Linux-i586 172 | runs-on: ubuntu-24.04 173 | target: i586-unknown-linux-gnu 174 | cache-cross-binary: true 175 | expect-file-re: "ELF.+80386" 176 | expect-cross: "--expect-cross" 177 | expect-stripped: "" 178 | can-execute: true 179 | 180 | - name: Linux-i686 181 | runs-on: ubuntu-24.04 182 | target: i686-unknown-linux-gnu 183 | cache-cross-binary: true 184 | expect-file-re: "ELF.+80386" 185 | expect-cross: "--expect-cross" 186 | expect-stripped: "" 187 | can-execute: true 188 | 189 | - name: Linux-powerpc 190 | runs-on: ubuntu-24.04 191 | target: powerpc-unknown-linux-gnu 192 | cache-cross-binary: true 193 | expect-file-re: "32.+PowerPC" 194 | expect-cross: "--expect-cross" 195 | expect-stripped: "" 196 | can-execute: true 197 | 198 | - name: Linux-powerpc64 199 | runs-on: ubuntu-24.04 200 | target: powerpc64-unknown-linux-gnu 201 | cache-cross-binary: true 202 | expect-file-re: "64.+PowerPC" 203 | expect-cross: "--expect-cross" 204 | expect-stripped: "" 205 | can-execute: true 206 | 207 | - name: Linux-powerpc64le 208 | runs-on: ubuntu-24.04 209 | target: powerpc64le-unknown-linux-gnu 210 | cache-cross-binary: true 211 | expect-file-re: "64.+PowerPC" 212 | expect-cross: "--expect-cross" 213 | expect-stripped: "" 214 | can-execute: true 215 | 216 | - name: Linux-riscv64 217 | runs-on: ubuntu-24.04 218 | target: riscv64gc-unknown-linux-gnu 219 | cache-cross-binary: true 220 | expect-file-re: "64.+RISC-V" 221 | expect-cross: "--expect-cross" 222 | expect-stripped: "" 223 | can-execute: true 224 | 225 | - name: Linux-s390x 226 | runs-on: ubuntu-24.04 227 | target: s390x-unknown-linux-gnu 228 | cache-cross-binary: true 229 | expect-file-re: "64.+S/390" 230 | expect-cross: "--expect-cross" 231 | expect-stripped: "" 232 | can-execute: true 233 | 234 | - name: NetBSD-x86_64 235 | runs-on: ubuntu-24.04 236 | target: x86_64-unknown-netbsd 237 | cache-cross-binary: true 238 | expect-file-re: "x86-64.+NetBSD" 239 | expect-cross: "--expect-cross" 240 | expect-stripped: "" 241 | can-execute: false 242 | 243 | - name: Windows-aarch64 244 | runs-on: windows-latest 245 | target: aarch64-pc-windows-msvc 246 | cache-cross-binary: true 247 | expect-file-re: "Windows.+ARM64" 248 | expect-cross: "" 249 | expect-stripped: "" 250 | can-execute: false 251 | 252 | - name: Windows-i686 253 | runs-on: windows-latest 254 | target: i686-pc-windows-msvc 255 | cache-cross-binary: true 256 | expect-file-re: "Windows.+Intel i386" 257 | expect-cross: "" 258 | expect-stripped: "--expect-stripped" 259 | can-execute: true 260 | 261 | - name: Windows-x86_64 262 | runs-on: windows-latest 263 | target: x86_64-pc-windows-msvc 264 | cache-cross-binary: true 265 | expect-file-re: "Windows.+x86-64" 266 | expect-stripped: "--expect-stripped" 267 | can-execute: true 268 | 269 | - name: macOS-x86_64 270 | runs-on: macOS-latest 271 | target: x86_64-apple-darwin 272 | cache-cross-binary: true 273 | expect-file-re: "Mach-O.+x86_64" 274 | expect-cross: "" 275 | expect-stripped: "--expect-stripped" 276 | can-execute: true 277 | 278 | - name: macOS-aarch64 279 | runs-on: macOS-latest 280 | target: aarch64-apple-darwin 281 | cache-cross-binary: true 282 | expect-file-re: "Mach-O.+arm64" 283 | expect-cross: "" 284 | expect-stripped: "--expect-stripped" 285 | can-execute: false 286 | 287 | runs-on: ${{ matrix.platform.runs-on }} 288 | steps: 289 | - name: Checkout 290 | uses: actions/checkout@v4 291 | - name: Copy test project to root 292 | shell: bash 293 | run: | 294 | cp -a test-project/* . 295 | rm -fr test-project 296 | - name: Run both commands 297 | uses: ./ 298 | with: 299 | command: both 300 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 301 | cross-version: ${{ matrix.platform.cross-version }} 302 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 303 | target: ${{ matrix.platform.target }} 304 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 305 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 306 | if: ${{ matrix.platform.can-execute }} 307 | - name: Run test command 308 | uses: ./ 309 | with: 310 | command: test 311 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 312 | cross-version: ${{ matrix.platform.cross-version }} 313 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 314 | target: ${{ matrix.platform.target }} 315 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 316 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 317 | if: ${{ matrix.platform.can-execute }} 318 | - name: Run test command with args 319 | uses: ./ 320 | with: 321 | command: test 322 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 323 | cross-version: ${{ matrix.platform.cross-version }} 324 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 325 | target: ${{ matrix.platform.target }} 326 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 327 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 328 | args: "-- --ignored" 329 | if: ${{ matrix.platform.can-execute }} 330 | - name: Run build command 331 | uses: ./ 332 | with: 333 | command: build 334 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 335 | cross-version: ${{ matrix.platform.cross-version }} 336 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 337 | target: ${{ matrix.platform.target }} 338 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 339 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 340 | strip: true 341 | - name: Check binary and cross for main crate on ${{ matrix.platform.name }} 342 | shell: bash 343 | run: | 344 | set -e 345 | set -x 346 | cargo run --manifest-path ./run-tests/Cargo.toml -- \ 347 | --checkout-root "$PWD" \ 348 | --target "${{ matrix.platform.target }}" \ 349 | --expect-file-re "${{ matrix.platform.expect-file-re }}" \ 350 | --expect-cross-version "${{ matrix.platform.expect-cross-version }}" \ 351 | ${{ matrix.platform.expect-cross }} \ 352 | ${{ matrix.platform.expect-stripped }} 353 | - name: Run build command for subdir 354 | uses: ./ 355 | with: 356 | command: build 357 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 358 | cross-version: ${{ matrix.platform.cross-version }} 359 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 360 | working-directory: subcrate 361 | target: ${{ matrix.platform.target }} 362 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 363 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 364 | strip: true 365 | - name: Check binary and cross for subcrate on ${{ matrix.platform.name }} 366 | shell: bash 367 | run: | 368 | set -e 369 | set -x 370 | cargo run --manifest-path ./run-tests/Cargo.toml -- \ 371 | --checkout-root "$PWD" \ 372 | --target "${{ matrix.platform.target }}" \ 373 | --expect-file-re "${{ matrix.platform.expect-file-re }}" \ 374 | --expect-cross-version "${{ matrix.platform.expect-cross-version }}" \ 375 | ${{ matrix.platform.expect-cross }} \ 376 | ${{ matrix.platform.expect-stripped }} \ 377 | --is-subcrate 378 | - name: Run bench command 379 | uses: ./ 380 | with: 381 | command: bench 382 | force-use-cross: ${{ matrix.platform.force-use-cross || false }} 383 | cross-version: ${{ matrix.platform.cross-version }} 384 | cache-cross-binary: ${{ matrix.platform.cache-cross-binary }} 385 | target: ${{ matrix.platform.target }} 386 | working-directory: bench 387 | toolchain: ${{ matrix.platform.toolchain || 'stable' }} 388 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 389 | if: ${{ matrix.platform.can-execute }} 390 | - name: Upload build artifacts 391 | uses: actions/upload-artifact@v4 392 | with: 393 | name: build-artifacts-${{ matrix.platform.name }} 394 | path: | 395 | target/${{ matrix.platform.target }}/debug/bin* 396 | subcrate/target/${{ matrix.platform.target }}/debug/subcrate 397 | 398 | test-validate-inputs: 399 | name: Test validate-inputs 400 | runs-on: ubuntu-24.04 401 | steps: 402 | - name: Checkout 403 | uses: actions/checkout@v4 404 | - name: Run tests 405 | shell: bash 406 | run: ./validate-inputs.py --test 407 | 408 | test-other-cargo-commands: 409 | name: Test a "cargo foo" command 410 | runs-on: ubuntu-24.04 411 | steps: 412 | - name: Checkout 413 | uses: actions/checkout@v4 414 | - name: Copy test project to root 415 | shell: bash 416 | run: | 417 | cp -a test-project/* . 418 | rm -fr test-project 419 | - name: Create cargo-foo command 420 | shell: bash 421 | run: | 422 | set -e 423 | set -x 424 | 425 | dir="${{ github.workspace }}/bin" 426 | mkdir -p "$dir" 427 | 428 | bin="$dir/cargo-foo" 429 | 430 | echo "#!/bin/bash" >> "$bin" 431 | echo "echo 'This is cargo-foo!'" >> "$bin" 432 | echo "exit 0" >> "$bin" 433 | chmod 0755 "$bin" 434 | 435 | echo "PATH=$dir:$PATH" >> "$GITHUB_ENV" 436 | - name: Run cargo-foo command 437 | uses: ./ 438 | with: 439 | command: foo 440 | target: x86_64-unknown-linux-gnu 441 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /package-lock.json 3 | /package.json 4 | .\#* 5 | \#*\# 6 | run-tests/**/target/** 7 | test-project/**/target/** 8 | -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as contributors and maintainers 6 | pledge to making participation in our project and our community a harassment-free experience for 7 | everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level 8 | of experience, education, socio-economic status, nationality, personal appearance, race, religion, 9 | or sexual identity and orientation. 10 | 11 | ## Our Standards 12 | 13 | Examples of behavior that contributes to creating a positive environment include: 14 | 15 | - Using welcoming and inclusive language 16 | - Being respectful of differing viewpoints and experiences 17 | - Gracefully accepting constructive criticism 18 | - Focusing on what is best for the community 19 | - Showing empathy towards other community members 20 | 21 | Examples of unacceptable behavior by participants include: 22 | 23 | - The use of sexualized language or imagery and unwelcome sexual attention or advances 24 | - Trolling, insulting/derogatory comments, and personal or political attacks 25 | - Public or private harassment 26 | - Publishing others' private information, such as a physical or electronic address, without explicit 27 | permission 28 | - Other conduct which could reasonably be considered inappropriate in a professional setting 29 | 30 | ## Our Responsibilities 31 | 32 | Project maintainers are responsible for clarifying the standards of acceptable behavior and are 33 | expected to take appropriate and fair corrective action in response to any instances of unacceptable 34 | behavior. 35 | 36 | Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, 37 | code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or 38 | to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, 39 | threatening, offensive, or harmful. 40 | 41 | ## Scope 42 | 43 | This Code of Conduct applies both within project spaces and in public spaces when an individual is 44 | representing the project or its community. Examples of representing a project or community include 45 | using an official project e-mail address, posting via an official social media account, or acting as 46 | an appointed representative at an online or offline event. Representation of a project may be 47 | further defined and clarified by project maintainers. 48 | 49 | ## Enforcement 50 | 51 | Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting 52 | the project team at autarch@urth.org. All complaints will be reviewed and investigated and will 53 | result in a response that is deemed necessary and appropriate to the circumstances. The project team 54 | is obligated to maintain confidentiality with regard to the reporter of an incident. Further details 55 | of specific enforcement policies may be posted separately. 56 | 57 | Project maintainers who do not follow or enforce the Code of Conduct in good faith may face 58 | temporary or permanent repercussions as determined by other members of the project's leadership. 59 | 60 | ## Attribution 61 | 62 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at 63 | https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 64 | 65 | [homepage]: https://www.contributor-covenant.org 66 | -------------------------------------------------------------------------------- /Changes.md: -------------------------------------------------------------------------------- 1 | ## 1.0.4 - 2025-04-12 2 | 3 | - Removed validation for the `toolchain` input. The 4 | [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain) accepts a lot of different 5 | options that this action wasn't allowing. It's simpler and more flexible to just let that action 6 | handle validation. Requested by @axos88 (Akos Vandra-Meyer). GH #42. 7 | - Removed validation for the `command` input. This allows you to use this action with any `cargo` 8 | extension command, like `cargo-deb`. Setting `command` to `both` is still supported and will run 9 | the `build` and `test` commands. Requested by @bvaisvil (Benjamin Vaisvil). GH #43. 10 | 11 | ## 1.0.3 - 2025-02-17 12 | 13 | - Fixed a bug when running with a Linux ARM host where the action would use a cached `cross` 14 | download for x86-64 Linux (or vice versa). Now the cache key for the `cross` binary includes both 15 | the runner's architecture in addition to its OS. 16 | - This release partially support running on Linxu ARM, but see the `README.md` file for details on 17 | this. 18 | 19 | ## 1.0.2 - 2025-02-16 20 | 21 | - Added a new `force-use-cross` input, which does what it says. It will force the use of `cross` 22 | even when it is not required for given platform/target combination. Note that this only works on 23 | Linux hosts. 24 | 25 | ## 1.0.1 - 2025-01-20 26 | 27 | - Fixed a bug where this action would attempt to use `cross` when compiling for an ARM Linux target 28 | on an ARM Linux host. 29 | 30 | ## 1.0.0 - 2025-01-11 31 | 32 | The addition of caching is a significant behavior change for this action, so the version has been 33 | bumped to v1.0.0 because of this change. 34 | 35 | - This action will now configure and use `Swatinem/rust-cache` by default for you. It will include 36 | the `target` parameter as part of the cache key automatically, as well as the OS version when 37 | using `cargo` on Linux. Suggested by @jennydaman (Jennings Zhang). GH #23. 38 | - This action now validates its input and will exit early if they are not valid. GH #35. 39 | - When compiling for `musl` targets, this action will not try to reinstall the `musl-tools` package 40 | if it's already installed. 41 | 42 | The following changes were made since the 1.0.0-beta1 release: 43 | 44 | - The cache key includes information that causes the cache to not be re-used when the system running 45 | `cargo` or `cross` changes. When using `cargo` on Linux, this is the OS version, like "Ubuntu 46 | 22.04". When using `cross`, this is the hash of the `cross` binary itself. This is needed because 47 | the Docker images that `cross` uses can change when the binary is updated. This can include 48 | changing the underlying Docker image base OS, in which case it's quite likely the old cache 49 | contents would be incompatible with the the new image. 50 | 51 | ## 1.0.0-beta1 - 2024-12-21 52 | 53 | The addition of caching is a significant behavior change for this action, so the version has been 54 | bumped to v1.0.0 because of this change. 55 | 56 | - This action will now configure and use `Swatinem/rust-cache` by default for you. It will include 57 | the `target` parameter as part of the cache key automatically. Suggested by @jennydaman (Jennings 58 | Zhang). GH #23. 59 | - This action now validates its inputs and will exit early if they are not valid. GH #35. 60 | 61 | ## 0.0.17 - 2024-11-23 62 | 63 | - Added support for running `cargo bench` or `cross bench`. Implemented by @RaulTrombin (Raul Victor 64 | Trombin). GH #32. 65 | 66 | ## 0.0.16 - 2024-11-17 67 | 68 | - Arguments passed in the `args` parameter are now always last when executing `cargo`. This lets you 69 | pass arguments to test binaries like `-- --something`. First reported by @mateocabanal (Mateo 70 | Cabanal) as GH #12 and fully fixed by @donatello (Aditya Manthramurthy) in GH #30. 71 | 72 | ## 0.0.15 - 2024-09-21 73 | 74 | - The `musl` packages are only installed when not cross-compiling. 75 | 76 | ## 0.0.14 - 2024-08-25 77 | 78 | - When the given `target` includes the string `musl`, this action will install the `musl-tools` 79 | package. This allows crates with C or C++ code to compile properly. Fixes #20. Reported by Matteo 80 | Pietro Dazzi (@ilteoood). 81 | 82 | ## 0.0.13 - 2024-05-18 83 | 84 | - It's now possible to set `cross-version` to a git ref like a commit hash or `HEAD`. This will 85 | install `cross` from its git repo. 86 | 87 | ## 0.0.12 - 2024-02-25 88 | 89 | - Bumped the version of `actions/cache` used in this action to v4. The v3 version uses Node 16, 90 | which causes warnings when run. Implemented by @hms5232. GH #13. 91 | 92 | ## 0.0.11 - 2023-12-17 93 | 94 | - Use `cross` when compiling for 32-bit Linux targets. While in theory this should work without 95 | `cross`, compiling `openssl` with the `vendored` feature fails when we run 96 | `cargo build --target i686-unknown-linux-musl`. 97 | 98 | ## 0.0.10 - 2023-12-10 99 | 100 | - Fixed handling of crates with multiple binaries. Attempting to strip binaries for such a crate 101 | caused the build to fail. Reported by Tomaž Hribernik. GH #8 102 | - Added a new `cross-version` parameter. This can be specified to make this action use a specific 103 | version of `cross`. If this is not specified, the latest version will be used. 104 | 105 | ## 0.0.9 - 2023-09-10 106 | 107 | - Added a `working-directory` parameter. By default this is the current directory (`.`) but you can 108 | set it to something else to compile a single crate or workspace in a subdirectory of the repo. 109 | This allows you to use this action with monorepos with multiple crates. Based on GH #7 by 110 | @aaronvg. 111 | 112 | ## 0.0.8 - 2023-07-22 113 | 114 | - For builds that need the `cross` binary, this binary is now cached. A cache hit saves about 20 115 | seconds in my tests. Suggested by @timon-schelling. GH #4. 116 | 117 | ## 0.0.7 - 2023-04-21 118 | 119 | - The toolchain argument was (probably) not being respected with cross builds, though it's hard to 120 | be sure since none of the output from past CI runs I've looked at it includes the toolchain 121 | version in the output. But now the toolchain version is explicitly passed to all `cargo` and 122 | `cross` commands. 123 | 124 | ## 0.0.6 - 2023-04-21 125 | 126 | - When the `strip` parameter was true, stripping binaries could fail if there were both 127 | `target/*/debug` and `target/*/release` directories present and the `debug` directory didn't have 128 | a binary. Now it will strip all binaries it finds under `target`. 129 | 130 | ## 0.0.5 - 2023-03-19 131 | 132 | - Fix use of `dtolnay/rust-toolchain` action to allow passing a `toolchain` input. 133 | 134 | ## 0.0.4 - 2023-03-19 135 | 136 | - Added a new `toolchain` parameter to allow selecting a Rust toolchain other than stable. This 137 | supports picking on of "stable", "beta", or "nightly". 138 | - Fixed binary stripping to work in more situations. Previously it depended on a very specific setup 139 | plus expected to be run in the context of the matrix I use for my own projects. 140 | - Fixed a reference to a matrix variable that should have referenced an input variable. 141 | 142 | ## 0.0.3 - 2023-03-17 143 | 144 | - This action now supports running the `build` and `test` commands, or both, with a new input 145 | parameter, `command`. The default is `build`. 146 | 147 | ## 0.0.2 - 2023-03-05 148 | 149 | - Fixed some typos in the `README.md` documentation. 150 | 151 | ## 0.0.1 - 2023-03-05 152 | 153 | - First release upon an unsuspecting world. 154 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # GitHub Action to Cross Compile Rust Projects 2 | 3 | This action lets you easily cross-compile Rust projects using 4 | [cross](https://github.com/cross-rs/cross). 5 | 6 | Here's a simplified example from the test and release workflow for 7 | [my tool `ubi`](https://github.com/houseabsolute/ubi): 8 | 9 | ```yaml 10 | jobs: 11 | release: 12 | name: Release - ${{ matrix.platform.os-name }} 13 | strategy: 14 | matrix: 15 | platform: 16 | - os-name: FreeBSD-x86_64 17 | runs-on: ubuntu-24.04 18 | target: x86_64-unknown-freebsd 19 | 20 | - os-name: Linux-x86_64 21 | runs-on: ubuntu-24.04 22 | target: x86_64-unknown-linux-musl 23 | 24 | - os-name: Linux-aarch64 25 | runs-on: ubuntu-24.04 26 | target: aarch64-unknown-linux-musl 27 | 28 | - os-name: Linux-riscv64 29 | runs-on: ubuntu-24.04 30 | target: riscv64gc-unknown-linux-gnu 31 | 32 | - os-name: Windows-x86_64 33 | runs-on: windows-latest 34 | target: x86_64-pc-windows-msvc 35 | 36 | - os-name: macOS-x86_64 37 | runs-on: macOS-latest 38 | target: x86_64-apple-darwin 39 | 40 | # more targets here ... 41 | 42 | runs-on: ${{ matrix.platform.runs-on }} 43 | steps: 44 | - name: Checkout 45 | uses: actions/checkout@v4 46 | - name: Build binary 47 | uses: houseabsolute/actions-rust-cross@v1 48 | with: 49 | command: build 50 | target: ${{ matrix.platform.target }} 51 | args: "--locked --release" 52 | strip: true 53 | - name: Publish artifacts and release 54 | uses: houseabsolute/actions-rust-release@v0 55 | with: 56 | executable-name: ubi 57 | target: ${{ matrix.platform.target }} 58 | ``` 59 | 60 | Note that for Linux or BSD targets, you should always set the `runs-on` key to a Linux x86-64 61 | architecture runner. 62 | 63 | If you _only_ want to do native ARM compilation, for example using the `ubuntu-24.04-arm` runner, 64 | then there's no need to use this action. However, if you want to compile for _many_ platforms, 65 | including Linux ARM, using this action will simplify your config. This action is only tested on 66 | Ubuntu x86-64, Windows, and macOS runners. 67 | 68 | ## Input Parameters 69 | 70 | This action takes the following parameters: 71 | 72 | | Key | Type | Required? | Description | 73 | | ----------------------- | --------------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 74 | | `command` | string | no | The command(s) to run. The default is `build`. Running the `test` command will fail with \*BSD targets and non-x86 Windows. You can use any command supported by `cargo` and/or `cross`. For example, if you install `cargo-deb`, the command can be `deb`. Use the special string "both" to run both `build` and `test. | 75 | | `target` | string | yes | The target triple to compile for. This should be one of the targets found by running `rustup target list`. | 76 | | `working-directory` | string | no | The working directory in which to run the `cargo` or `cross` commands. Defaults to the current directory (`.`). | 77 | | `toolchain` | string) | no | The Rust toolchain version to install. This is passed directly to [dtolnay/rust-toolchain](https://github.com/dtolnay/rust-toolchain), which accepts many different options. See its documentation for more details. The default is `stable`. | 78 | | `GITHUB_TOKEN` | string | no | Defaults to the value of `${{ github.token }}`. | 79 | | `args` | string | no | A string-separated list of arguments to be passed to `cross build`, like `--release --locked`. | 80 | | `strip` | boolean (`true` or `false`) | no | If this is true, then the resulting binaries will be stripped if possible. This is only possible for binaries which weren't cross-compiled. | 81 | | `cross-version` | string | no | This can be used to set the version of `cross` to use. If specified, it should be a specific `cross` release tag (like `v0.2.3`) or a git ref (commit hash, `HEAD`, etc.). If this is not set then the latest released version will always be used. If this is set to a git ref then the version corresponding to that ref will be installed. | 82 | | `force-use-cross` | boolean (`true` or `false`) | no | If this is true, then the action will use `cross` even if it is not needed for the given target. If this is set to `true`, then the resulting binary will not be stripped, regardless of whether `strip` is `true` or not. This only works on Linux hosts. Forcing the use of `cross` on other hosts is not supported. | 83 | | `use-rust-cache` | boolean | no | Whether or not to use [the `Swatinem/rust-cache@v2` action](https://github.com/Swatinem/rust-cache). This defaults to true. | 84 | | `rust-cache-parameters` | string (containing JSON) | no | This must be a string containing valid JSON. The JSON should be an object where the keys are the parameters for [the `Swatinem/rust-cache@v2` action](https://github.com/Swatinem/rust-cache). | 85 | 86 | ### Setting Environment Variables 87 | 88 | By default, `cross` passes most rust-related environment variables through when it runs `cargo` in a 89 | Docker image. This means you can simply set an `env` key in the workflow step that uses this action. 90 | 91 | ``` 92 | - name: Run build command 93 | uses: houseabsolute/actions-rust-cross@v1 94 | env: 95 | CARGO_LOG: debug 96 | RUSTFLAGS: "-g" 97 | with: 98 | command: build 99 | ``` 100 | 101 | If you want to pass other environment variables through, you will need to configure `cross` to do 102 | see. See the 103 | [`cross` docs](https://github.com/cross-rs/cross/blob/main/docs/environment_variables.md#environment-variable-passthrough) 104 | for more details. 105 | 106 | ## How it Works 107 | 108 | Under the hood, this action will compile your binaries with either `cargo` or `cross`, depending on 109 | the host machine and target. For Linux builds, it will always use `cross` except for builds 110 | targeting an x86 architecture like `x86_64` or `i686`. 111 | 112 | On Windows and macOS, it's possible to compile for all supported targets out of the box, so `cross` 113 | will not be used on those platforms. 114 | 115 | If it needs to install `cross`, it will install the latest version by downloading a release using 116 | [my tool `ubi`](https://github.com/houseabsolute/ubi). This is much faster than using `cargo` to 117 | build `cross`. 118 | 119 | When compiling on Windows, it will do so in a Powershell environment, which can matter in some 120 | corner cases, like compiling the `openssl` crate with the `vendored` feature. 121 | 122 | When running `cargo` on a Linux system, it will also include the output of running 123 | `lsb_release --short --description` in the cache key. This is important for crates that link against 124 | system libraries. If those library versions change across OS versions (e.g. Ubuntu 20.04 to 22.04), 125 | then the cache will be broken for these cases. 126 | 127 | When running `cross`, the hash of the `cross` binary will be included in the cache key. This is done 128 | because the Docker images that `cross` uses can change when `cross` is updated. We want to make sure 129 | that we do not re-use the cache across changes when these images change. 130 | 131 | Finally, it will run `strip` to strip the binaries it builds if the `strip` parameter is true. This 132 | is only possible for builds that are not done via `cross`. In addition, Windows builds for `aarch64` 133 | cannot be stripped either. 134 | 135 | ### Caching 136 | 137 | By default, this action will use 138 | [the `Swatinem/rust-cache@v2` action](https://github.com/Swatinem/rust-cache) to cache compiled 139 | dependencies for a crate. Note that per the documentation for the `rust-cache` action, it has fairly 140 | limited value for crates without a `Cargo.lock` file. The `key` parameter passed to this action will 141 | always include the value of the `target` input. If you specify a `key` parameter in 142 | `rust-cache-parameters`, then the `target` input will be appended to the value you specify. 143 | 144 | #### Weird Caching Issue with Multiple Crates 145 | 146 | In my testing, it seemed like in some cases restoring the cache would delete existing files in a 147 | `target` directory. This manifested with this sequence of actions: 148 | 149 | 1. Run `actions-rust-cross` to compile a crate in a top-level directory. 150 | 2. Run `actions-rust-cross` to compile a crate in a subdirectory. 151 | 152 | After step 2, the compiled binaries from step 1 were no longer present, _sometimes_. I'm not sure 153 | exactly what's going on here, but my recommendation is to structure your workflows so that this 154 | cannot affect you. 155 | 156 | For example, if you have multiple crates, each of which builds a binary you want to release, then 157 | you can avoid this issue by structuring your workflow as follows: 158 | 159 | 1. Run `actions-rust-cross` to compile crate A. 160 | 2. Run the release steps for crate A. 161 | 3. Run `actions-rust-cross` to compile crate B. 162 | 4. Run the release steps for crate B. 163 | 164 | When structured this way, it does not matter if the output of crate A is deleted in step 3. 165 | 166 | ## Cross-Compiling from Linux ARM Runners 167 | 168 | In theory, this should work, and this action does implement some of the necessary work for this. 169 | However, there are a couple issues with this: 170 | 171 | 1. As of 2025-02-17, the `cross` project does not publish Linux ARM binary releases. That means that 172 | in order to use `cross` on a Linux ARM runner as part of this action, you must set 173 | `cross-version` to a more recent commit from the `cross` repo. 174 | 2. There is 175 | [a bug in `cross` that means you must use a custom Docker image](https://github.com/cross-rs/cross/issues/1628) 176 | when cross-compiling from a Linux ARM runner. See 177 | [this other `cross` issue](https://github.com/cross-rs/cross/issues/751) for more details. 178 | 179 | ## Linting and Tidying this Code 180 | 181 | The code in this repo is linted and tidied with 182 | [`precious`](https://github.com/houseabsolute/precious). This repo contains a `mise.toml` file. 183 | [Mise](https://mise.jdx.dev/) is a tool for managing dev tools with per-repo configuration. You can 184 | install `mise` and use it to run `precious` as follows: 185 | 186 | ``` 187 | # Installs mise 188 | curl https://mise.run | sh 189 | # Installs precious and other dev tools 190 | mise install 191 | ``` 192 | 193 | Once this is done, you can run `precious` via `mise`: 194 | 195 | ``` 196 | # Lints all code 197 | mise exec -- precious lint -a 198 | # Tidies all code 199 | mise exec -- precious tidy -a 200 | ``` 201 | 202 | If you want to use `mise` for other projects, see [its documentation](https://mise.jdx.dev/) for 203 | more details on how you can configure your shell to always activate `mise`. 204 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: "Build Rust Projects with Cross" 2 | author: "Dave Rolsky " 3 | 4 | branding: 5 | icon: home 6 | color: gray-dark 7 | 8 | description: | 9 | Cross compile your Rust projects with cross (https://github.com/cross-rs/cross). 10 | 11 | inputs: 12 | target: 13 | description: The target platform 14 | required: true 15 | 16 | command: 17 | description: | 18 | The commands to run. Use "both" to run both "build" and "test". 19 | default: build 20 | 21 | toolchain: 22 | description: | 23 | The target toolchain to use. 24 | default: stable 25 | 26 | working-directory: 27 | description: The working directory for each step 28 | default: "." 29 | 30 | GITHUB_TOKEN: 31 | description: | 32 | A GitHub token, available in the secrets.GITHUB_TOKEN working-directory variable. 33 | default: ${{ github.token }} 34 | 35 | args: 36 | description: | 37 | The arguments to be passed to cross or cargo when building, as a 38 | space-separated string. 39 | default: "" 40 | 41 | strip: 42 | description: Strip the compiled binary 43 | default: false 44 | 45 | cross-version: 46 | description: | 47 | The version of cross to use. If not specified, then the latest version 48 | will be used. 49 | 50 | cache-cross-binary: 51 | description: | 52 | Cache the cross binary if one is installed. This is primarily for use in 53 | tests of this action. 54 | default: true 55 | 56 | force-use-cross: 57 | description: | 58 | If this is true, the action will use cross even for targets where it is not needed. 59 | default: false 60 | 61 | use-rust-cache: 62 | description: | 63 | Use `Swatinem/rust-cache@v2`. Defaults to true. 64 | default: true 65 | 66 | rust-cache-parameters: 67 | description: | 68 | A JSON string containing parameters to pass to `Swatinem/rust-cache@v2`. You can use the 69 | `toJSON()` function in your action to make passing this easier. 70 | default: "{}" 71 | 72 | runs: 73 | using: composite 74 | steps: 75 | - name: Show inputs 76 | shell: bash 77 | run: | 78 | echo '${{ toJSON(inputs) }}' 79 | 80 | - name: Add this action's path to PATH 81 | shell: bash 82 | run: echo "${{ github.action_path }}" >> $GITHUB_PATH 83 | 84 | - name: Validate inputs 85 | shell: bash 86 | run: | 87 | "${{ github.action_path }}"/validate-inputs.py "${{ github.workspace }}" 88 | env: 89 | INPUTS_target: ${{ inputs.target }} 90 | INPUTS_command: ${{ inputs.command }} 91 | INPUTS_toolchain: ${{ inputs.toolchain }} 92 | INPUTS_working_directory: ${{ inputs.working-directory }} 93 | INPUTS_strip: ${{ inputs.strip }} 94 | INPUTS_cache_cross_binary: ${{ inputs.cache-cross-binary }} 95 | INPUTS_force_use_cross: ${{ inputs.force-use-cross }} 96 | INPUTS_use_rust_cache: ${{ inputs.use-rust-cache }} 97 | INPUTS_rust_cache_parameters: ${{ inputs.rust-cache-parameters }} 98 | 99 | - name: Determine whether we need to cross-compile 100 | id: determine-cross-compile 101 | shell: bash 102 | run: set-cross-compile.py ${{ inputs.target }} ${{ inputs.force-use-cross }} 103 | 104 | - name: Install toolchain 105 | uses: dtolnay/rust-toolchain@master 106 | with: 107 | targets: ${{ inputs.target }} 108 | toolchain: ${{ inputs.toolchain }} 109 | 110 | - name: Install qemu-user emulator binaries if cross-compiling on arm64 host 111 | shell: bash 112 | run: | 113 | set -e 114 | set -x 115 | 116 | docker run --privileged --rm tonistiigi/binfmt --install all 117 | if: steps.determine-cross-compile.outputs.needs-cross == 'true' && runner.os == 'Linux' && contains(runner.arch, 'ARM') 118 | 119 | - name: Determine cross version 120 | id: determine-cross-version 121 | shell: bash 122 | run: determine-cross-version.sh "${{ inputs.cross-version }}" 123 | env: 124 | GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} 125 | if: steps.determine-cross-compile.outputs.needs-cross == 'true' 126 | 127 | # We need to access this in both this YAML config and shell scripts. It 128 | # doesn't seem like using ${{ env.RUNNER_TEMP }} works in the YAML config. 129 | - name: Set directory for installing cross 130 | id: set-cross-dir 131 | shell: bash 132 | run: set-cross-dir.sh 133 | if: steps.determine-cross-compile.outputs.needs-cross == 'true' 134 | 135 | - name: Cache cross 136 | id: cache-cross 137 | uses: actions/cache@v4 138 | with: 139 | path: ${{ steps.set-cross-dir.outputs.cross-dir }}/cross 140 | key: ${{ runner.os }}-${{ runner.arch }}-${{ steps.determine-cross-version.outputs.cross-version }} 141 | if: steps.determine-cross-compile.outputs.needs-cross == 'true' && inputs.cache-cross-binary == 'true' 142 | 143 | - name: Install cross if cross-compiling (*nix) 144 | shell: bash 145 | run: install-cross-nix.sh ${{ steps.set-cross-dir.outputs.cross-dir }} ${{ steps.determine-cross-version.outputs.cross-version }} 146 | if: steps.determine-cross-compile.outputs.needs-cross == 'true' && steps.cache-cross.outputs.cache-hit != 'true' 147 | env: 148 | GITHUB_TOKEN: ${{ inputs.GITHUB_TOKEN }} 149 | 150 | - name: Install musl-tools on Linux if target includes "musl" 151 | shell: bash 152 | run: | 153 | if dpkg -l musl-tools | grep -q "^ii\s*musl-tools"; then 154 | exit 0 155 | fi 156 | sudo apt-get update --yes && \ 157 | sudo apt-get install --yes musl-tools 158 | if: steps.determine-cross-compile.outputs.needs-cross != 'true' && contains(inputs.target, 'musl') 159 | 160 | - name: Set build command 161 | id: set-build-command 162 | shell: bash 163 | run: set-build-command.sh ${{ steps.set-cross-dir.outputs.cross-dir }} 164 | 165 | - name: Determine which cargo commands to run 166 | id: determine-cargo-commands 167 | shell: bash 168 | run: determine-cargo-commands.sh ${{ inputs.command }} 169 | 170 | - name: Parse `rust-cache-parameters` and set inputs for `Swatinem/rust-cache@v2` 171 | id: parse-rust-cache-parameters 172 | shell: bash 173 | run: | 174 | set -e 175 | set -x 176 | set -o pipefail 177 | OS_VERSION="" 178 | if [ -x /usr/bin/lsb_release ]; then 179 | # This will be something like "Ubuntu 22.04.5 LTS" 180 | OS_VERSION="$( lsb_release --short --description )" 181 | fi 182 | # This will get the inputs JSON from the `RUST_CACHE_PARAMETERS` env var. This avoids 183 | # any string interpolation issues, since the inputs will contain quotes. 184 | parse-and-set-rust-cache-parameters.py "${{ inputs.target }}" "${{ steps.set-build-command.outputs.build-command }}" "$OS_VERSION" 185 | env: 186 | RUST_CACHE_PARAMETERS: ${{ inputs.rust-cache-parameters }} 187 | if: inputs.use-rust-cache == 'true' 188 | 189 | - name: Cache cargo & target directories 190 | uses: Swatinem/rust-cache@v2 191 | with: ${{ steps.parse-rust-cache-parameters.outputs }} 192 | if: inputs.use-rust-cache == 'true' 193 | 194 | - name: Run cargo test 195 | working-directory: ${{ inputs.working-directory }} 196 | # We want to run in Powershell on Windows to make sure we compile in a native Windows 197 | # environment. Some things won't compile properly under msys, notably OpenSSL, which is 198 | # compiled locally when using the `openssl` crate with the `vendored` feature. 199 | shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} 200 | run: | 201 | ${{ steps.set-build-command.outputs.build-command }} test --target ${{ inputs.target }} ${{ inputs.args }} 202 | if: steps.determine-cargo-commands.outputs.test == 'true' 203 | 204 | - name: Run cargo build 205 | working-directory: ${{ inputs.working-directory }} 206 | # We want to run in Powershell on Windows to make sure we compile in a native Windows 207 | # environment. Some things won't compile properly under msys, notably OpenSSL, which is 208 | # compiled locally when using the `openssl` crate with the `vendored` feature. 209 | shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} 210 | run: | 211 | ${{ steps.set-build-command.outputs.build-command }} build --target ${{ inputs.target }} ${{ inputs.args }} 212 | if: steps.determine-cargo-commands.outputs.build == 'true' 213 | 214 | - name: Run cargo ${{ steps.determine-cargo-commands.outputs.command }} 215 | working-directory: ${{ inputs.working-directory }} 216 | # We want to run in Powershell on Windows to make sure we compile in a native Windows 217 | # environment. Some things won't compile properly under msys, notably OpenSSL, which is 218 | # compiled locally when using the `openssl` crate with the `vendored` feature. 219 | shell: ${{ runner.os == 'Windows' && 'powershell' || 'bash' }} 220 | run: | 221 | ${{ steps.set-build-command.outputs.build-command }} ${{ steps.determine-cargo-commands.outputs.command }} --target ${{ inputs.target }} ${{ inputs.args }} 222 | if: steps.determine-cargo-commands.outputs.command != '' 223 | 224 | - name: Strip binary 225 | working-directory: ${{ inputs.working-directory }} 226 | shell: bash 227 | run: strip-binary.sh ${{ inputs.target }} 228 | # strip doesn't work with cross-arch binaries on Linux or Windows. 229 | if: inputs.command != 'test' && inputs.strip == 'true' && steps.determine-cross-compile.outputs.needs-cross == 'false' && inputs.target != 'aarch64-pc-windows-msvc' 230 | -------------------------------------------------------------------------------- /determine-cargo-commands.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | COMMAND="$1" 8 | if [ "$COMMAND" == 'both' ]; then 9 | echo "build=true" >>"$GITHUB_OUTPUT" 10 | echo "test=true" >>"$GITHUB_OUTPUT" 11 | else 12 | echo "command=$COMMAND" >>"$GITHUB_OUTPUT" 13 | fi 14 | -------------------------------------------------------------------------------- /determine-cross-version.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | VERSION=$1 8 | 9 | if [ -z "$VERSION" ]; then 10 | JSON=$(curl \ 11 | --request GET \ 12 | --header "Authorization: Bearer $GITHUB_TOKEN" \ 13 | https://api.github.com/repos/cross-rs/cross/releases/latest) 14 | VERSION=$(echo "$JSON" | jq -r ".tag_name") 15 | fi 16 | 17 | echo "cross-version=$VERSION" >>"$GITHUB_OUTPUT" 18 | -------------------------------------------------------------------------------- /git/hooks/pre-commit.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | declare -i status 4 | status=0 5 | 6 | PRECIOUS=$(which precious) 7 | if [[ -z $PRECIOUS ]]; then 8 | PRECIOUS=./bin/precious 9 | fi 10 | 11 | if ! "$PRECIOUS" lint -s; then 12 | status+=1 13 | fi 14 | 15 | exit $status 16 | -------------------------------------------------------------------------------- /git/setup.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env perl 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Cwd qw( abs_path ); 7 | 8 | symlink_hook('pre-commit'); 9 | 10 | sub symlink_hook { 11 | my $hook = shift; 12 | 13 | my $dot = ".git/hooks/$hook"; 14 | my $file = "git/hooks/$hook.sh"; 15 | my $link = "../../$file"; 16 | 17 | if ( -e $dot ) { 18 | if ( -l $dot ) { 19 | return if readlink $dot eq $link; 20 | } 21 | warn "You already have a hook at $dot!\n"; 22 | return; 23 | } 24 | 25 | symlink $link, $dot; 26 | } 27 | -------------------------------------------------------------------------------- /git/setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # This code was mostly written by Claude.ai. 4 | 5 | import os 6 | import sys 7 | 8 | 9 | def main() -> None: 10 | """ 11 | Main entry point to create a pre-commit hook symlink. 12 | """ 13 | symlink_hook("pre-commit") 14 | 15 | 16 | def symlink_hook(hook: str) -> None: 17 | """ 18 | Create a symlink for a Git hook if it doesn't already exist. 19 | 20 | Args: 21 | hook: Name of the Git hook (e.g., 'pre-commit') 22 | """ 23 | # Path to the hook in .git/hooks 24 | dot_hook_path = os.path.join(".git", "hooks", hook) 25 | 26 | # Path to the actual hook script 27 | file_hook_path = os.path.join("git", "hooks", f"{hook}.sh") 28 | 29 | # Relative symlink path 30 | link_path = os.path.join("..", "..", file_hook_path) 31 | 32 | # Check if the hook already exists 33 | if os.path.exists(dot_hook_path): 34 | # If it's already a symlink, check if it points to the correct location 35 | if os.path.islink(dot_hook_path): 36 | # If the existing symlink is correct, do nothing 37 | if os.readlink(dot_hook_path) == link_path: 38 | return 39 | 40 | # If a hook exists and is not the expected symlink, warn and exit 41 | print(f"You already have a hook at {dot_hook_path}!", file=sys.stderr) 42 | return 43 | 44 | # Create the symlink 45 | try: 46 | os.symlink(link_path, dot_hook_path) 47 | except OSError as e: 48 | print(f"Error creating symlink: {e}", file=sys.stderr) 49 | 50 | 51 | if __name__ == "__main__": 52 | main() 53 | -------------------------------------------------------------------------------- /install-cross-nix.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | CROSS_DIR="$1" 8 | VERSION="$2" 9 | 10 | cd "$CROSS_DIR" 11 | 12 | if [[ -n $VERSION ]] && ! [[ $VERSION =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then 13 | cargo install cross --git https://github.com/cross-rs/cross --rev "$VERSION" --force 14 | mv "$HOME/.cargo/bin/cross" . 15 | CROSS_NO_WARNINGS=0 ./cross --version 16 | exit 0 17 | fi 18 | 19 | VERSION_ARGS="" 20 | if [ -n "$VERSION" ]; then 21 | VERSION_ARGS="--tag $VERSION" 22 | fi 23 | 24 | export TARGET=. 25 | curl --silent --location \ 26 | https://raw.githubusercontent.com/houseabsolute/ubi/master/bootstrap/bootstrap-ubi.sh | 27 | sh 28 | # shellcheck disable=SC2086 29 | ./ubi --project cross-rs/cross --matching musl --in . $VERSION_ARGS 30 | CROSS_NO_WARNINGS=0 ./cross --version 31 | -------------------------------------------------------------------------------- /mise.toml: -------------------------------------------------------------------------------- 1 | [tools] 2 | node = "22.11.0" 3 | "npm:prettier" = "3.4.1" 4 | ruff = "0.8.3" 5 | shellcheck = "0.10.0" 6 | shfmt = "v3.10.0" 7 | taplo = "0.9.3" 8 | typos = "1.28.1" 9 | "ubi:houseabsolute/omegasort" = "v0.1.3" 10 | "ubi:houseabsolute/precious" = "v0.7.3" 11 | -------------------------------------------------------------------------------- /parse-and-set-rust-cache-parameters.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import json 4 | import os 5 | import sys 6 | import hashlib 7 | 8 | 9 | def main(): 10 | target = sys.argv[1] 11 | build_command = sys.argv[2] 12 | 13 | os_version = sys.argv[3] 14 | 15 | parameters = json.loads(os.environ["RUST_CACHE_PARAMETERS"]) 16 | if "key" not in parameters: 17 | parameters["key"] = target 18 | else: 19 | parameters["key"] += "-{}".format(target) 20 | 21 | if build_command == "cargo": 22 | # If we're running cargo, we need to add the OS version to the cache. Otherwise things that link 23 | # against system packages, like openssl, can break when we use the same cache across different 24 | # versions of the runner OS. For example, when going from Ubuntu 20.04 to 22.04, we move from 25 | # OpenSSL 1.1.x to 3.x. 26 | parameters["key"] += "-{}".format(os_version) 27 | else: 28 | # Otherwise we want to include the `cross` binary's hash. The Docker images that `cross` 29 | # uses can change when the binary is updated. This protects us from using the same cache 30 | # inside containers that have changed. 31 | parameters["key"] += "-cross-binary-hash-{}".format( 32 | get_file_hash(build_command) 33 | ) 34 | 35 | file = os.environ["GITHUB_OUTPUT"] 36 | with open(file, "w") as f: 37 | for key, value in parameters.items(): 38 | f.write(f"{key}={value}") 39 | 40 | 41 | def get_file_hash(build_command): 42 | with open(build_command, "rb") as f: 43 | file_hash = hashlib.sha256() 44 | while chunk := f.read(65536): 45 | file_hash.update(chunk) 46 | return file_hash.hexdigest() 47 | 48 | 49 | main() 50 | -------------------------------------------------------------------------------- /perltidyrc: -------------------------------------------------------------------------------- 1 | -l=78 2 | -i=4 3 | -ci=4 4 | -se 5 | -b 6 | -bar 7 | -boc 8 | -vt=0 9 | -vtc=0 10 | -cti=0 11 | -pt=1 12 | -bt=1 13 | -sbt=1 14 | -bbt=1 15 | -nolq 16 | -npro 17 | -nsfs 18 | --blank-lines-before-packages=0 19 | --opening-hash-brace-right 20 | --no-outdent-long-comments 21 | --iterations=2 22 | -wbb="% + - * / x != == >= <= =~ !~ < > | & >= < = **= += *= &= <<= &&= -= /= |= >>= ||= .= %= ^= x=" 23 | -------------------------------------------------------------------------------- /precious.toml: -------------------------------------------------------------------------------- 1 | exclude = [ 2 | "target", 3 | "tests/lib/**/*", 4 | ] 5 | 6 | [commands.clippy] 7 | type = "lint" 8 | include = "**/*.rs" 9 | invoke = "once" 10 | path-args = "none" 11 | working-dir.chdir-to = "run-tests" 12 | cmd = [ 13 | "cargo", 14 | "clippy", 15 | "--color", 16 | "always", 17 | "--locked", 18 | "--all-targets", 19 | "--all-features", 20 | "--", 21 | "-D", 22 | "clippy::pedantic", 23 | ] 24 | ok_exit_codes = 0 25 | lint_failure_exit_codes = 101 26 | expect_stderr = true 27 | labels = ["default"] 28 | 29 | [commands."clippy --fix"] 30 | type = "tidy" 31 | include = "**/*.rs" 32 | invoke = "once" 33 | path-args = "none" 34 | working-dir.chdir-to = "run-tests" 35 | cmd = [ 36 | "cargo", 37 | "clippy", 38 | "--fix", 39 | "--allow-dirty", 40 | "--locked", 41 | "--all-targets", 42 | "--all-features", 43 | "--", 44 | "-D", 45 | "clippy::pedantic", 46 | ] 47 | ok_exit_codes = 0 48 | lint_failure_exit_codes = 101 49 | expect_stderr = true 50 | labels = ["default"] 51 | 52 | [commands.rustfmt] 53 | type = "both" 54 | include = "**/*.rs" 55 | working-dir.chdir-to = "run-tests" 56 | cmd = ["rustfmt", "--edition", "2021"] 57 | lint_flags = "--check" 58 | ok_exit_codes = [0] 59 | lint_failure_exit_codes = [1] 60 | labels = ["default", "fast-tidy"] 61 | 62 | [commands."ruff for linting"] 63 | type = "both" 64 | include = [ "**/*.py" ] 65 | cmd = "ruff" 66 | lint_flags = [ "check" ] 67 | tidy_flags = [ "check", "--fix" ] 68 | ok_exit_codes = 0 69 | lint_failure_exit_codes = 1 70 | 71 | [commands."ruff for tidying"] 72 | type = "both" 73 | include = [ "**/*.py" ] 74 | cmd = "ruff" 75 | lint_flags = [ "format", "--check" ] 76 | tidy_flags = [ "format" ] 77 | ok_exit_codes = 0 78 | lint_failure_exit_codes = 1 79 | 80 | [commands.typos] 81 | type = "both" 82 | include = "**/*" 83 | exclude = "**/*.tar.gz" 84 | invoke = "once" 85 | cmd = "typos" 86 | tidy_flags = "--write-changes" 87 | ok-exit-codes = 0 88 | lint-failure-exit-codes = 2 89 | 90 | [commands.prettier-md] 91 | type = "both" 92 | include = [ "**/*.md" ] 93 | cmd = [ "prettier", "--no-config", "--print-width", "100", "--prose-wrap", "always" ] 94 | lint_flags = "--check" 95 | tidy_flags = "--write" 96 | ok_exit_codes = 0 97 | lint_failure_exit_codes = 1 98 | ignore_stderr = [ "Code style issues" ] 99 | 100 | [commands.prettier-yml] 101 | type = "both" 102 | include = [ "**/*.yml" ] 103 | cmd = [ "prettier", "--no-config" ] 104 | lint_flags = "--check" 105 | tidy_flags = "--write" 106 | ok_exit_codes = 0 107 | lint_failure_exit_codes = 1 108 | ignore_stderr = [ "Code style issues" ] 109 | 110 | [commands.omegasort-gitignore] 111 | type = "both" 112 | include = "**/.gitignore" 113 | cmd = [ "omegasort", "--sort", "path", "--unique" ] 114 | lint_flags = "--check" 115 | tidy_flags = "--in-place" 116 | ok_exit_codes = 0 117 | lint_failure_exit_codes = 1 118 | ignore_stderr = [ 119 | "The .+ file is not sorted", 120 | "The .+ file is not unique", 121 | ] 122 | 123 | [commands.shellcheck] 124 | type = "lint" 125 | include = "**/*.sh" 126 | cmd = "shellcheck" 127 | ok_exit_codes = 0 128 | lint_failure_exit_codes = 1 129 | 130 | [commands.shfmt] 131 | type = "both" 132 | include = "**/*.sh" 133 | cmd = ["shfmt", "--simplify", "--indent", "4"] 134 | lint_flags = "--diff" 135 | tidy_flags = "--write" 136 | ok_exit_codes = 0 137 | lint_failure_exit_codes = 1 138 | -------------------------------------------------------------------------------- /run-tests/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anstream" 16 | version = "0.6.18" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b" 19 | dependencies = [ 20 | "anstyle", 21 | "anstyle-parse", 22 | "anstyle-query", 23 | "anstyle-wincon", 24 | "colorchoice", 25 | "is_terminal_polyfill", 26 | "utf8parse", 27 | ] 28 | 29 | [[package]] 30 | name = "anstyle" 31 | version = "1.0.10" 32 | source = "registry+https://github.com/rust-lang/crates.io-index" 33 | checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9" 34 | 35 | [[package]] 36 | name = "anstyle-parse" 37 | version = "0.2.6" 38 | source = "registry+https://github.com/rust-lang/crates.io-index" 39 | checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9" 40 | dependencies = [ 41 | "utf8parse", 42 | ] 43 | 44 | [[package]] 45 | name = "anstyle-query" 46 | version = "1.1.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c" 49 | dependencies = [ 50 | "windows-sys", 51 | ] 52 | 53 | [[package]] 54 | name = "anstyle-wincon" 55 | version = "3.0.6" 56 | source = "registry+https://github.com/rust-lang/crates.io-index" 57 | checksum = "2109dbce0e72be3ec00bed26e6a7479ca384ad226efdd66db8fa2e3a38c83125" 58 | dependencies = [ 59 | "anstyle", 60 | "windows-sys", 61 | ] 62 | 63 | [[package]] 64 | name = "clap" 65 | version = "4.5.23" 66 | source = "registry+https://github.com/rust-lang/crates.io-index" 67 | checksum = "3135e7ec2ef7b10c6ed8950f0f792ed96ee093fa088608f1c76e569722700c84" 68 | dependencies = [ 69 | "clap_builder", 70 | "clap_derive", 71 | ] 72 | 73 | [[package]] 74 | name = "clap_builder" 75 | version = "4.5.23" 76 | source = "registry+https://github.com/rust-lang/crates.io-index" 77 | checksum = "30582fc632330df2bd26877bde0c1f4470d57c582bbc070376afcd04d8cb4838" 78 | dependencies = [ 79 | "anstream", 80 | "anstyle", 81 | "clap_lex", 82 | "strsim", 83 | ] 84 | 85 | [[package]] 86 | name = "clap_derive" 87 | version = "4.5.18" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "4ac6a0c7b1a9e9a5186361f67dfa1b88213572f427fb9ab038efb2bd8c582dab" 90 | dependencies = [ 91 | "heck", 92 | "proc-macro2", 93 | "quote", 94 | "syn", 95 | ] 96 | 97 | [[package]] 98 | name = "clap_lex" 99 | version = "0.7.4" 100 | source = "registry+https://github.com/rust-lang/crates.io-index" 101 | checksum = "f46ad14479a25103f283c0f10005961cf086d8dc42205bb44c46ac563475dca6" 102 | 103 | [[package]] 104 | name = "colorchoice" 105 | version = "1.0.3" 106 | source = "registry+https://github.com/rust-lang/crates.io-index" 107 | checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990" 108 | 109 | [[package]] 110 | name = "heck" 111 | version = "0.5.0" 112 | source = "registry+https://github.com/rust-lang/crates.io-index" 113 | checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" 114 | 115 | [[package]] 116 | name = "is_executable" 117 | version = "1.0.4" 118 | source = "registry+https://github.com/rust-lang/crates.io-index" 119 | checksum = "d4a1b5bad6f9072935961dfbf1cced2f3d129963d091b6f69f007fe04e758ae2" 120 | dependencies = [ 121 | "winapi", 122 | ] 123 | 124 | [[package]] 125 | name = "is_terminal_polyfill" 126 | version = "1.70.1" 127 | source = "registry+https://github.com/rust-lang/crates.io-index" 128 | checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" 129 | 130 | [[package]] 131 | name = "memchr" 132 | version = "2.7.4" 133 | source = "registry+https://github.com/rust-lang/crates.io-index" 134 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 135 | 136 | [[package]] 137 | name = "proc-macro2" 138 | version = "1.0.92" 139 | source = "registry+https://github.com/rust-lang/crates.io-index" 140 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 141 | dependencies = [ 142 | "unicode-ident", 143 | ] 144 | 145 | [[package]] 146 | name = "quote" 147 | version = "1.0.37" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 150 | dependencies = [ 151 | "proc-macro2", 152 | ] 153 | 154 | [[package]] 155 | name = "regex" 156 | version = "1.11.1" 157 | source = "registry+https://github.com/rust-lang/crates.io-index" 158 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 159 | dependencies = [ 160 | "aho-corasick", 161 | "memchr", 162 | "regex-automata", 163 | "regex-syntax", 164 | ] 165 | 166 | [[package]] 167 | name = "regex-automata" 168 | version = "0.4.9" 169 | source = "registry+https://github.com/rust-lang/crates.io-index" 170 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 171 | dependencies = [ 172 | "aho-corasick", 173 | "memchr", 174 | "regex-syntax", 175 | ] 176 | 177 | [[package]] 178 | name = "regex-syntax" 179 | version = "0.8.5" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 182 | 183 | [[package]] 184 | name = "run-tests" 185 | version = "0.1.0" 186 | dependencies = [ 187 | "clap", 188 | "is_executable", 189 | "regex", 190 | ] 191 | 192 | [[package]] 193 | name = "strsim" 194 | version = "0.11.1" 195 | source = "registry+https://github.com/rust-lang/crates.io-index" 196 | checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" 197 | 198 | [[package]] 199 | name = "syn" 200 | version = "2.0.90" 201 | source = "registry+https://github.com/rust-lang/crates.io-index" 202 | checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" 203 | dependencies = [ 204 | "proc-macro2", 205 | "quote", 206 | "unicode-ident", 207 | ] 208 | 209 | [[package]] 210 | name = "unicode-ident" 211 | version = "1.0.14" 212 | source = "registry+https://github.com/rust-lang/crates.io-index" 213 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 214 | 215 | [[package]] 216 | name = "utf8parse" 217 | version = "0.2.2" 218 | source = "registry+https://github.com/rust-lang/crates.io-index" 219 | checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" 220 | 221 | [[package]] 222 | name = "winapi" 223 | version = "0.3.9" 224 | source = "registry+https://github.com/rust-lang/crates.io-index" 225 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 226 | dependencies = [ 227 | "winapi-i686-pc-windows-gnu", 228 | "winapi-x86_64-pc-windows-gnu", 229 | ] 230 | 231 | [[package]] 232 | name = "winapi-i686-pc-windows-gnu" 233 | version = "0.4.0" 234 | source = "registry+https://github.com/rust-lang/crates.io-index" 235 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 236 | 237 | [[package]] 238 | name = "winapi-x86_64-pc-windows-gnu" 239 | version = "0.4.0" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 242 | 243 | [[package]] 244 | name = "windows-sys" 245 | version = "0.59.0" 246 | source = "registry+https://github.com/rust-lang/crates.io-index" 247 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 248 | dependencies = [ 249 | "windows-targets", 250 | ] 251 | 252 | [[package]] 253 | name = "windows-targets" 254 | version = "0.52.6" 255 | source = "registry+https://github.com/rust-lang/crates.io-index" 256 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 257 | dependencies = [ 258 | "windows_aarch64_gnullvm", 259 | "windows_aarch64_msvc", 260 | "windows_i686_gnu", 261 | "windows_i686_gnullvm", 262 | "windows_i686_msvc", 263 | "windows_x86_64_gnu", 264 | "windows_x86_64_gnullvm", 265 | "windows_x86_64_msvc", 266 | ] 267 | 268 | [[package]] 269 | name = "windows_aarch64_gnullvm" 270 | version = "0.52.6" 271 | source = "registry+https://github.com/rust-lang/crates.io-index" 272 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 273 | 274 | [[package]] 275 | name = "windows_aarch64_msvc" 276 | version = "0.52.6" 277 | source = "registry+https://github.com/rust-lang/crates.io-index" 278 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 279 | 280 | [[package]] 281 | name = "windows_i686_gnu" 282 | version = "0.52.6" 283 | source = "registry+https://github.com/rust-lang/crates.io-index" 284 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 285 | 286 | [[package]] 287 | name = "windows_i686_gnullvm" 288 | version = "0.52.6" 289 | source = "registry+https://github.com/rust-lang/crates.io-index" 290 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 291 | 292 | [[package]] 293 | name = "windows_i686_msvc" 294 | version = "0.52.6" 295 | source = "registry+https://github.com/rust-lang/crates.io-index" 296 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 297 | 298 | [[package]] 299 | name = "windows_x86_64_gnu" 300 | version = "0.52.6" 301 | source = "registry+https://github.com/rust-lang/crates.io-index" 302 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 303 | 304 | [[package]] 305 | name = "windows_x86_64_gnullvm" 306 | version = "0.52.6" 307 | source = "registry+https://github.com/rust-lang/crates.io-index" 308 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 309 | 310 | [[package]] 311 | name = "windows_x86_64_msvc" 312 | version = "0.52.6" 313 | source = "registry+https://github.com/rust-lang/crates.io-index" 314 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 315 | -------------------------------------------------------------------------------- /run-tests/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "run-tests" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dependencies] 7 | clap = { version = "4.5.23", features = ["derive"] } 8 | is_executable = "1.0.4" 9 | regex = "1.11.1" 10 | -------------------------------------------------------------------------------- /run-tests/src/main.rs: -------------------------------------------------------------------------------- 1 | // Mostly written by Claude.ai. 2 | 3 | use clap::Parser; 4 | use is_executable::is_executable; 5 | use regex::Regex; 6 | use std::{ 7 | env, 8 | path::{Path, PathBuf}, 9 | process::Command, 10 | }; 11 | 12 | #[derive(Parser, Debug)] 13 | #[command(version, about, long_about = None)] 14 | struct Args { 15 | #[arg(long)] 16 | checkout_root: String, 17 | #[arg(long)] 18 | target: String, 19 | #[arg(long)] 20 | expect_file_re: Option, 21 | #[arg(long)] 22 | expect_cross: bool, 23 | #[arg(long)] 24 | expect_cross_version: Option, 25 | #[arg(long)] 26 | expect_stripped: bool, 27 | #[arg(long)] 28 | is_subcrate: bool, 29 | } 30 | 31 | fn main() { 32 | let args = Args::parse(); 33 | 34 | let runner_temp = env::var("RUNNER_TEMP").expect("RUNNER_TEMP is not set"); 35 | let runner_temp_path = PathBuf::from(runner_temp); 36 | 37 | check_cross( 38 | &runner_temp_path, 39 | args.expect_cross, 40 | args.expect_cross_version.as_deref(), 41 | ); 42 | 43 | let checkout_root_path = PathBuf::from(args.checkout_root); 44 | let bin_paths = if args.is_subcrate { 45 | vec![checkout_root_path 46 | .join("subcrate") 47 | .join("target") 48 | .join(&args.target) 49 | .join("debug") 50 | .join("subcrate")] 51 | } else { 52 | vec![ 53 | checkout_root_path 54 | .join("target") 55 | .join(&args.target) 56 | .join("debug") 57 | .join("bin1"), 58 | checkout_root_path 59 | .join("target") 60 | .join(&args.target) 61 | .join("debug") 62 | .join("bin2"), 63 | ] 64 | }; 65 | 66 | for mut bin_path in bin_paths { 67 | if cfg!(windows) { 68 | bin_path.set_extension("exe"); 69 | } 70 | check_binary( 71 | &bin_path, 72 | args.expect_file_re.as_deref(), 73 | args.expect_stripped, 74 | ); 75 | } 76 | } 77 | 78 | fn check_cross(bin_dir: &Path, expect_cross: bool, expect_cross_version: Option<&str>) { 79 | let cross_path = bin_dir.join("cross"); 80 | 81 | if expect_cross { 82 | assert!( 83 | cross_path.is_file(), 84 | "`cross` exists at {}", 85 | cross_path.display() 86 | ); 87 | 88 | if let Some(expected_version) = expect_cross_version { 89 | let output = Command::new(&cross_path) 90 | .arg("--version") 91 | .output() 92 | .expect("Failed to execute `cross` command"); 93 | let version = String::from_utf8(output.stdout) 94 | .expect("`cross --version` stdout was not valid UTF-8"); 95 | assert!( 96 | version.contains(expected_version), 97 | "`cross` version matches expected version: {expected_version}", 98 | ); 99 | } 100 | } else { 101 | assert!(!cross_path.exists(), "`cross` was not downloaded"); 102 | } 103 | } 104 | 105 | fn check_binary(bin_path: &PathBuf, expect_file_re: Option<&str>, expect_stripped: bool) { 106 | assert!(bin_path.exists(), "Binary at {} exists", bin_path.display()); 107 | assert!( 108 | bin_path.is_file(), 109 | "Binary at {} is a file", 110 | bin_path.display() 111 | ); 112 | 113 | assert!( 114 | is_executable(bin_path), 115 | "Binary at {} is executable", 116 | bin_path.display() 117 | ); 118 | 119 | let output = Command::new("file") 120 | .arg("--brief") 121 | .arg(bin_path) 122 | .output() 123 | .expect("Failed to execute `file` command"); 124 | let file_output = String::from_utf8_lossy(&output.stdout); 125 | println!( 126 | "output from `file` for {}: {file_output}", 127 | bin_path.display(), 128 | ); 129 | 130 | if let Some(file_re) = expect_file_re { 131 | let re = Regex::new(file_re).expect("Invalid regex"); 132 | assert!( 133 | re.is_match(&file_output), 134 | "`file` output for {} matches `{file_re}`: `{file_output}`", 135 | bin_path.display(), 136 | ); 137 | } 138 | 139 | // `file` on macOS doesn't report if the binary is stripped. 140 | if cfg!(target_os = "macos") { 141 | return; 142 | } 143 | 144 | if expect_stripped { 145 | assert!( 146 | !file_output.contains("not stripped"), 147 | "`file` does not report {} as 'not stripped': `{file_output}`", 148 | bin_path.display(), 149 | ); 150 | assert!( 151 | file_output.contains("stripped"), 152 | "`file` reports {} as 'stripped': `{file_output}`", 153 | bin_path.display(), 154 | ); 155 | } else if cfg!(windows) { 156 | assert!( 157 | !file_output.contains("stripped"), 158 | "`file` does not report {} as 'stripped': `{file_output}`", 159 | bin_path.display(), 160 | ); 161 | } else { 162 | assert!( 163 | file_output.contains("not stripped"), 164 | "`file` reports {} as 'not stripped': `{file_output}`", 165 | bin_path.display(), 166 | ); 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /set-build-command.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | CROSS_DIR="$1" 8 | if [ -f "$CROSS_DIR/cross" ]; then 9 | echo "build-command=$CROSS_DIR/cross" >>"$GITHUB_OUTPUT" 10 | else 11 | echo "build-command=cargo" >>"$GITHUB_OUTPUT" 12 | fi 13 | -------------------------------------------------------------------------------- /set-cross-compile.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Written mostly by Claude.ai based on my original bash script. 4 | 5 | import sys 6 | import platform 7 | import re 8 | import os 9 | from subprocess import run, PIPE, CalledProcessError 10 | 11 | 12 | def main() -> int: 13 | """ 14 | Main function to determine cross-compilation requirements. 15 | 16 | Returns: 17 | Exit code (0 for success) 18 | """ 19 | if len(sys.argv) < 3: 20 | print( 21 | "Error: Target architecture and force cross arguments are required", 22 | file=sys.stderr, 23 | ) 24 | return 1 25 | 26 | target = sys.argv[1] 27 | force_use_cross = sys.argv[2] 28 | if force_use_cross == "true": 29 | needs_cross = True 30 | else: 31 | needs_cross = check_needs_cross(target) 32 | 33 | write_github_output(needs_cross) 34 | 35 | return 0 36 | 37 | 38 | def check_needs_cross(target: str) -> bool: 39 | """ 40 | Determine if cross-compilation is needed based on system and target. 41 | 42 | Args: 43 | target: Target architecture string 44 | 45 | Returns: 46 | Boolean indicating if cross-compilation is needed 47 | """ 48 | system_info = get_uname_info().lower() 49 | 50 | # Check if we're on macOS or Windows 51 | if any(os in system_info for os in ["darwin", "msys", "windows"]): 52 | return False 53 | 54 | target = target.lower() 55 | 56 | # Check for x86_64 Linux targets on x86_64 Linux host 57 | if ( 58 | re.search(r"x86_64.+linux-(?:gnu|musl)", target) 59 | and "x86_64" in system_info 60 | and "linux" in system_info 61 | ): 62 | return False 63 | 64 | # It's tempting to not use cross when the host is Linux x86-64 and we're compiling for Linux 65 | # i586 or i686. This sort of works, but if there's any C being compiled, things get weird, 66 | # because then we need 32-bit C headers, 32-bit C libs to link to, etc. 67 | 68 | # Check if both host and target are ARM Linux. I'm assuming here that for things like 69 | # "arm-linux-androideabi" or "armv7-unknown-linux-ohos" we'd still need cross. 70 | if ( 71 | re.search(r"(?:aarch64|arm).+linux-(?:gnu|musl)", target) 72 | and ("arm" in system_info or "aarch64" in system_info) 73 | and "linux" in system_info 74 | ): 75 | return False 76 | 77 | return True 78 | 79 | 80 | def get_uname_info() -> str: 81 | """ 82 | Get system information using uname command. 83 | 84 | Returns: 85 | String containing system information 86 | """ 87 | try: 88 | result = run(["uname", "-a"], check=True, text=True, stdout=PIPE) 89 | return result.stdout 90 | except (CalledProcessError, FileNotFoundError): 91 | # Fallback to platform.platform() if uname is not available 92 | return platform.platform() 93 | 94 | 95 | def write_github_output(needs_cross: bool) -> None: 96 | """ 97 | Write the needs-cross output to GITHUB_OUTPUT environment variable file. 98 | 99 | Args: 100 | needs_cross: Boolean indicating if cross-compilation is needed 101 | """ 102 | github_output = os.getenv("GITHUB_OUTPUT") 103 | if github_output: 104 | with open(github_output, "a") as f: 105 | f.write(f"needs-cross={str(needs_cross).lower()}\n") 106 | 107 | 108 | if __name__ == "__main__": 109 | sys.exit(main()) 110 | -------------------------------------------------------------------------------- /set-cross-dir.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | echo "cross-dir=$RUNNER_TEMP" >>"$GITHUB_OUTPUT" 8 | -------------------------------------------------------------------------------- /strip-binary.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | set -e 4 | set -x 5 | set -o pipefail 6 | 7 | TARGET=$1 8 | did_strip="" 9 | 10 | strip_binary() { 11 | if [[ $(uname -s) =~ "Darwin" ]]; then 12 | stripped=$( 13 | find "$1" -maxdepth 1 -type f -perm +111 | while read -r exe; do 14 | strip "$exe" 15 | echo "stripped $exe" 16 | done 17 | ) 18 | else 19 | stripped=$( 20 | find "$1" -maxdepth 1 -type f -executable | while read -r exe; do 21 | strip "$exe" 22 | echo "stripped $exe" 23 | done 24 | ) 25 | fi 26 | 27 | if [ -z "$stripped" ]; then 28 | echo "Could not find any binaries to strip in $1" 29 | else 30 | did_strip="true" 31 | fi 32 | } 33 | 34 | for type in debug release; do 35 | if [ -d "target/$TARGET/$type" ]; then 36 | strip_binary "target/$TARGET/$type" 37 | elif [ -d "target/$type" ]; then 38 | strip_binary "target/$type" 39 | fi 40 | done 41 | 42 | if [ -z "$did_strip" ]; then 43 | echo "No binaries were stripped" 44 | exit 1 45 | fi 46 | -------------------------------------------------------------------------------- /test-project/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "test-project" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /test-project/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "test-project" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # For testing it would be nice to create a binary with spaces in the name, but 7 | # right now the `name` value must be a valid crate name, and there's no 8 | # separate setting for the compiled executable's name. See 9 | # https://github.com/rust-lang/cargo/issues/9778. 10 | [[bin]] 11 | name = "bin1" 12 | path = "src/bin1.rs" 13 | 14 | [[bin]] 15 | name = "bin2" 16 | path = "src/bin2.rs" 17 | 18 | # workaround for https://github.com/cross-rs/cross/issues/1345 19 | [package.metadata.cross.target.x86_64-unknown-netbsd] 20 | pre-build = [ 21 | "mkdir -p /tmp/netbsd", 22 | "curl https://archive.netbsd.org/pub/NetBSD-archive/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", 23 | "tar -C /tmp/netbsd -xJf base.tar.xz", 24 | "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", 25 | "rm base.tar.xz", 26 | "rm -rf /tmp/netbsd", 27 | ] 28 | -------------------------------------------------------------------------------- /test-project/bench/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 4 4 | 5 | [[package]] 6 | name = "aho-corasick" 7 | version = "1.1.3" 8 | source = "registry+https://github.com/rust-lang/crates.io-index" 9 | checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" 10 | dependencies = [ 11 | "memchr", 12 | ] 13 | 14 | [[package]] 15 | name = "anes" 16 | version = "0.1.6" 17 | source = "registry+https://github.com/rust-lang/crates.io-index" 18 | checksum = "4b46cbb362ab8752921c97e041f5e366ee6297bd428a31275b9fcf1e380f7299" 19 | 20 | [[package]] 21 | name = "atty" 22 | version = "0.2.14" 23 | source = "registry+https://github.com/rust-lang/crates.io-index" 24 | checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8" 25 | dependencies = [ 26 | "hermit-abi", 27 | "libc", 28 | "winapi", 29 | ] 30 | 31 | [[package]] 32 | name = "autocfg" 33 | version = "1.4.0" 34 | source = "registry+https://github.com/rust-lang/crates.io-index" 35 | checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" 36 | 37 | [[package]] 38 | name = "bench" 39 | version = "0.1.0" 40 | dependencies = [ 41 | "criterion", 42 | ] 43 | 44 | [[package]] 45 | name = "bitflags" 46 | version = "1.3.2" 47 | source = "registry+https://github.com/rust-lang/crates.io-index" 48 | checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" 49 | 50 | [[package]] 51 | name = "bumpalo" 52 | version = "3.16.0" 53 | source = "registry+https://github.com/rust-lang/crates.io-index" 54 | checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" 55 | 56 | [[package]] 57 | name = "cast" 58 | version = "0.3.0" 59 | source = "registry+https://github.com/rust-lang/crates.io-index" 60 | checksum = "37b2a672a2cb129a2e41c10b1224bb368f9f37a2b16b612598138befd7b37eb5" 61 | 62 | [[package]] 63 | name = "cfg-if" 64 | version = "1.0.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" 67 | 68 | [[package]] 69 | name = "ciborium" 70 | version = "0.2.2" 71 | source = "registry+https://github.com/rust-lang/crates.io-index" 72 | checksum = "42e69ffd6f0917f5c029256a24d0161db17cea3997d185db0d35926308770f0e" 73 | dependencies = [ 74 | "ciborium-io", 75 | "ciborium-ll", 76 | "serde", 77 | ] 78 | 79 | [[package]] 80 | name = "ciborium-io" 81 | version = "0.2.2" 82 | source = "registry+https://github.com/rust-lang/crates.io-index" 83 | checksum = "05afea1e0a06c9be33d539b876f1ce3692f4afea2cb41f740e7743225ed1c757" 84 | 85 | [[package]] 86 | name = "ciborium-ll" 87 | version = "0.2.2" 88 | source = "registry+https://github.com/rust-lang/crates.io-index" 89 | checksum = "57663b653d948a338bfb3eeba9bb2fd5fcfaecb9e199e87e1eda4d9e8b240fd9" 90 | dependencies = [ 91 | "ciborium-io", 92 | "half", 93 | ] 94 | 95 | [[package]] 96 | name = "clap" 97 | version = "3.2.25" 98 | source = "registry+https://github.com/rust-lang/crates.io-index" 99 | checksum = "4ea181bf566f71cb9a5d17a59e1871af638180a18fb0035c92ae62b705207123" 100 | dependencies = [ 101 | "bitflags", 102 | "clap_lex", 103 | "indexmap", 104 | "textwrap", 105 | ] 106 | 107 | [[package]] 108 | name = "clap_lex" 109 | version = "0.2.4" 110 | source = "registry+https://github.com/rust-lang/crates.io-index" 111 | checksum = "2850f2f5a82cbf437dd5af4d49848fbdfc27c157c3d010345776f952765261c5" 112 | dependencies = [ 113 | "os_str_bytes", 114 | ] 115 | 116 | [[package]] 117 | name = "criterion" 118 | version = "0.4.0" 119 | source = "registry+https://github.com/rust-lang/crates.io-index" 120 | checksum = "e7c76e09c1aae2bc52b3d2f29e13c6572553b30c4aa1b8a49fd70de6412654cb" 121 | dependencies = [ 122 | "anes", 123 | "atty", 124 | "cast", 125 | "ciborium", 126 | "clap", 127 | "criterion-plot", 128 | "itertools", 129 | "lazy_static", 130 | "num-traits", 131 | "oorandom", 132 | "plotters", 133 | "rayon", 134 | "regex", 135 | "serde", 136 | "serde_derive", 137 | "serde_json", 138 | "tinytemplate", 139 | "walkdir", 140 | ] 141 | 142 | [[package]] 143 | name = "criterion-plot" 144 | version = "0.5.0" 145 | source = "registry+https://github.com/rust-lang/crates.io-index" 146 | checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" 147 | dependencies = [ 148 | "cast", 149 | "itertools", 150 | ] 151 | 152 | [[package]] 153 | name = "crossbeam-deque" 154 | version = "0.8.5" 155 | source = "registry+https://github.com/rust-lang/crates.io-index" 156 | checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d" 157 | dependencies = [ 158 | "crossbeam-epoch", 159 | "crossbeam-utils", 160 | ] 161 | 162 | [[package]] 163 | name = "crossbeam-epoch" 164 | version = "0.9.18" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e" 167 | dependencies = [ 168 | "crossbeam-utils", 169 | ] 170 | 171 | [[package]] 172 | name = "crossbeam-utils" 173 | version = "0.8.20" 174 | source = "registry+https://github.com/rust-lang/crates.io-index" 175 | checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" 176 | 177 | [[package]] 178 | name = "crunchy" 179 | version = "0.2.2" 180 | source = "registry+https://github.com/rust-lang/crates.io-index" 181 | checksum = "7a81dae078cea95a014a339291cec439d2f232ebe854a9d672b796c6afafa9b7" 182 | 183 | [[package]] 184 | name = "either" 185 | version = "1.13.0" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" 188 | 189 | [[package]] 190 | name = "half" 191 | version = "2.4.1" 192 | source = "registry+https://github.com/rust-lang/crates.io-index" 193 | checksum = "6dd08c532ae367adf81c312a4580bc67f1d0fe8bc9c460520283f4c0ff277888" 194 | dependencies = [ 195 | "cfg-if", 196 | "crunchy", 197 | ] 198 | 199 | [[package]] 200 | name = "hashbrown" 201 | version = "0.12.3" 202 | source = "registry+https://github.com/rust-lang/crates.io-index" 203 | checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" 204 | 205 | [[package]] 206 | name = "hermit-abi" 207 | version = "0.1.19" 208 | source = "registry+https://github.com/rust-lang/crates.io-index" 209 | checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33" 210 | dependencies = [ 211 | "libc", 212 | ] 213 | 214 | [[package]] 215 | name = "indexmap" 216 | version = "1.9.3" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" 219 | dependencies = [ 220 | "autocfg", 221 | "hashbrown", 222 | ] 223 | 224 | [[package]] 225 | name = "itertools" 226 | version = "0.10.5" 227 | source = "registry+https://github.com/rust-lang/crates.io-index" 228 | checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473" 229 | dependencies = [ 230 | "either", 231 | ] 232 | 233 | [[package]] 234 | name = "itoa" 235 | version = "1.0.13" 236 | source = "registry+https://github.com/rust-lang/crates.io-index" 237 | checksum = "540654e97a3f4470a492cd30ff187bc95d89557a903a2bbf112e2fae98104ef2" 238 | 239 | [[package]] 240 | name = "js-sys" 241 | version = "0.3.72" 242 | source = "registry+https://github.com/rust-lang/crates.io-index" 243 | checksum = "6a88f1bda2bd75b0452a14784937d796722fdebfe50df998aeb3f0b7603019a9" 244 | dependencies = [ 245 | "wasm-bindgen", 246 | ] 247 | 248 | [[package]] 249 | name = "lazy_static" 250 | version = "1.5.0" 251 | source = "registry+https://github.com/rust-lang/crates.io-index" 252 | checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" 253 | 254 | [[package]] 255 | name = "libc" 256 | version = "0.2.164" 257 | source = "registry+https://github.com/rust-lang/crates.io-index" 258 | checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" 259 | 260 | [[package]] 261 | name = "log" 262 | version = "0.4.22" 263 | source = "registry+https://github.com/rust-lang/crates.io-index" 264 | checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" 265 | 266 | [[package]] 267 | name = "memchr" 268 | version = "2.7.4" 269 | source = "registry+https://github.com/rust-lang/crates.io-index" 270 | checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" 271 | 272 | [[package]] 273 | name = "num-traits" 274 | version = "0.2.19" 275 | source = "registry+https://github.com/rust-lang/crates.io-index" 276 | checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" 277 | dependencies = [ 278 | "autocfg", 279 | ] 280 | 281 | [[package]] 282 | name = "once_cell" 283 | version = "1.20.2" 284 | source = "registry+https://github.com/rust-lang/crates.io-index" 285 | checksum = "1261fe7e33c73b354eab43b1273a57c8f967d0391e80353e51f764ac02cf6775" 286 | 287 | [[package]] 288 | name = "oorandom" 289 | version = "11.1.4" 290 | source = "registry+https://github.com/rust-lang/crates.io-index" 291 | checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" 292 | 293 | [[package]] 294 | name = "os_str_bytes" 295 | version = "6.6.1" 296 | source = "registry+https://github.com/rust-lang/crates.io-index" 297 | checksum = "e2355d85b9a3786f481747ced0e0ff2ba35213a1f9bd406ed906554d7af805a1" 298 | 299 | [[package]] 300 | name = "plotters" 301 | version = "0.3.7" 302 | source = "registry+https://github.com/rust-lang/crates.io-index" 303 | checksum = "5aeb6f403d7a4911efb1e33402027fc44f29b5bf6def3effcc22d7bb75f2b747" 304 | dependencies = [ 305 | "num-traits", 306 | "plotters-backend", 307 | "plotters-svg", 308 | "wasm-bindgen", 309 | "web-sys", 310 | ] 311 | 312 | [[package]] 313 | name = "plotters-backend" 314 | version = "0.3.7" 315 | source = "registry+https://github.com/rust-lang/crates.io-index" 316 | checksum = "df42e13c12958a16b3f7f4386b9ab1f3e7933914ecea48da7139435263a4172a" 317 | 318 | [[package]] 319 | name = "plotters-svg" 320 | version = "0.3.7" 321 | source = "registry+https://github.com/rust-lang/crates.io-index" 322 | checksum = "51bae2ac328883f7acdfea3d66a7c35751187f870bc81f94563733a154d7a670" 323 | dependencies = [ 324 | "plotters-backend", 325 | ] 326 | 327 | [[package]] 328 | name = "proc-macro2" 329 | version = "1.0.92" 330 | source = "registry+https://github.com/rust-lang/crates.io-index" 331 | checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" 332 | dependencies = [ 333 | "unicode-ident", 334 | ] 335 | 336 | [[package]] 337 | name = "quote" 338 | version = "1.0.37" 339 | source = "registry+https://github.com/rust-lang/crates.io-index" 340 | checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" 341 | dependencies = [ 342 | "proc-macro2", 343 | ] 344 | 345 | [[package]] 346 | name = "rayon" 347 | version = "1.10.0" 348 | source = "registry+https://github.com/rust-lang/crates.io-index" 349 | checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa" 350 | dependencies = [ 351 | "either", 352 | "rayon-core", 353 | ] 354 | 355 | [[package]] 356 | name = "rayon-core" 357 | version = "1.12.1" 358 | source = "registry+https://github.com/rust-lang/crates.io-index" 359 | checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2" 360 | dependencies = [ 361 | "crossbeam-deque", 362 | "crossbeam-utils", 363 | ] 364 | 365 | [[package]] 366 | name = "regex" 367 | version = "1.11.1" 368 | source = "registry+https://github.com/rust-lang/crates.io-index" 369 | checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" 370 | dependencies = [ 371 | "aho-corasick", 372 | "memchr", 373 | "regex-automata", 374 | "regex-syntax", 375 | ] 376 | 377 | [[package]] 378 | name = "regex-automata" 379 | version = "0.4.9" 380 | source = "registry+https://github.com/rust-lang/crates.io-index" 381 | checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" 382 | dependencies = [ 383 | "aho-corasick", 384 | "memchr", 385 | "regex-syntax", 386 | ] 387 | 388 | [[package]] 389 | name = "regex-syntax" 390 | version = "0.8.5" 391 | source = "registry+https://github.com/rust-lang/crates.io-index" 392 | checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" 393 | 394 | [[package]] 395 | name = "ryu" 396 | version = "1.0.18" 397 | source = "registry+https://github.com/rust-lang/crates.io-index" 398 | checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" 399 | 400 | [[package]] 401 | name = "same-file" 402 | version = "1.0.6" 403 | source = "registry+https://github.com/rust-lang/crates.io-index" 404 | checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" 405 | dependencies = [ 406 | "winapi-util", 407 | ] 408 | 409 | [[package]] 410 | name = "serde" 411 | version = "1.0.215" 412 | source = "registry+https://github.com/rust-lang/crates.io-index" 413 | checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" 414 | dependencies = [ 415 | "serde_derive", 416 | ] 417 | 418 | [[package]] 419 | name = "serde_derive" 420 | version = "1.0.215" 421 | source = "registry+https://github.com/rust-lang/crates.io-index" 422 | checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" 423 | dependencies = [ 424 | "proc-macro2", 425 | "quote", 426 | "syn", 427 | ] 428 | 429 | [[package]] 430 | name = "serde_json" 431 | version = "1.0.133" 432 | source = "registry+https://github.com/rust-lang/crates.io-index" 433 | checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" 434 | dependencies = [ 435 | "itoa", 436 | "memchr", 437 | "ryu", 438 | "serde", 439 | ] 440 | 441 | [[package]] 442 | name = "syn" 443 | version = "2.0.89" 444 | source = "registry+https://github.com/rust-lang/crates.io-index" 445 | checksum = "44d46482f1c1c87acd84dea20c1bf5ebff4c757009ed6bf19cfd36fb10e92c4e" 446 | dependencies = [ 447 | "proc-macro2", 448 | "quote", 449 | "unicode-ident", 450 | ] 451 | 452 | [[package]] 453 | name = "textwrap" 454 | version = "0.16.1" 455 | source = "registry+https://github.com/rust-lang/crates.io-index" 456 | checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" 457 | 458 | [[package]] 459 | name = "tinytemplate" 460 | version = "1.2.1" 461 | source = "registry+https://github.com/rust-lang/crates.io-index" 462 | checksum = "be4d6b5f19ff7664e8c98d03e2139cb510db9b0a60b55f8e8709b689d939b6bc" 463 | dependencies = [ 464 | "serde", 465 | "serde_json", 466 | ] 467 | 468 | [[package]] 469 | name = "unicode-ident" 470 | version = "1.0.14" 471 | source = "registry+https://github.com/rust-lang/crates.io-index" 472 | checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" 473 | 474 | [[package]] 475 | name = "walkdir" 476 | version = "2.5.0" 477 | source = "registry+https://github.com/rust-lang/crates.io-index" 478 | checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" 479 | dependencies = [ 480 | "same-file", 481 | "winapi-util", 482 | ] 483 | 484 | [[package]] 485 | name = "wasm-bindgen" 486 | version = "0.2.95" 487 | source = "registry+https://github.com/rust-lang/crates.io-index" 488 | checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e" 489 | dependencies = [ 490 | "cfg-if", 491 | "once_cell", 492 | "wasm-bindgen-macro", 493 | ] 494 | 495 | [[package]] 496 | name = "wasm-bindgen-backend" 497 | version = "0.2.95" 498 | source = "registry+https://github.com/rust-lang/crates.io-index" 499 | checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358" 500 | dependencies = [ 501 | "bumpalo", 502 | "log", 503 | "once_cell", 504 | "proc-macro2", 505 | "quote", 506 | "syn", 507 | "wasm-bindgen-shared", 508 | ] 509 | 510 | [[package]] 511 | name = "wasm-bindgen-macro" 512 | version = "0.2.95" 513 | source = "registry+https://github.com/rust-lang/crates.io-index" 514 | checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56" 515 | dependencies = [ 516 | "quote", 517 | "wasm-bindgen-macro-support", 518 | ] 519 | 520 | [[package]] 521 | name = "wasm-bindgen-macro-support" 522 | version = "0.2.95" 523 | source = "registry+https://github.com/rust-lang/crates.io-index" 524 | checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68" 525 | dependencies = [ 526 | "proc-macro2", 527 | "quote", 528 | "syn", 529 | "wasm-bindgen-backend", 530 | "wasm-bindgen-shared", 531 | ] 532 | 533 | [[package]] 534 | name = "wasm-bindgen-shared" 535 | version = "0.2.95" 536 | source = "registry+https://github.com/rust-lang/crates.io-index" 537 | checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d" 538 | 539 | [[package]] 540 | name = "web-sys" 541 | version = "0.3.72" 542 | source = "registry+https://github.com/rust-lang/crates.io-index" 543 | checksum = "f6488b90108c040df0fe62fa815cbdee25124641df01814dd7282749234c6112" 544 | dependencies = [ 545 | "js-sys", 546 | "wasm-bindgen", 547 | ] 548 | 549 | [[package]] 550 | name = "winapi" 551 | version = "0.3.9" 552 | source = "registry+https://github.com/rust-lang/crates.io-index" 553 | checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" 554 | dependencies = [ 555 | "winapi-i686-pc-windows-gnu", 556 | "winapi-x86_64-pc-windows-gnu", 557 | ] 558 | 559 | [[package]] 560 | name = "winapi-i686-pc-windows-gnu" 561 | version = "0.4.0" 562 | source = "registry+https://github.com/rust-lang/crates.io-index" 563 | checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" 564 | 565 | [[package]] 566 | name = "winapi-util" 567 | version = "0.1.9" 568 | source = "registry+https://github.com/rust-lang/crates.io-index" 569 | checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" 570 | dependencies = [ 571 | "windows-sys", 572 | ] 573 | 574 | [[package]] 575 | name = "winapi-x86_64-pc-windows-gnu" 576 | version = "0.4.0" 577 | source = "registry+https://github.com/rust-lang/crates.io-index" 578 | checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" 579 | 580 | [[package]] 581 | name = "windows-sys" 582 | version = "0.59.0" 583 | source = "registry+https://github.com/rust-lang/crates.io-index" 584 | checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" 585 | dependencies = [ 586 | "windows-targets", 587 | ] 588 | 589 | [[package]] 590 | name = "windows-targets" 591 | version = "0.52.6" 592 | source = "registry+https://github.com/rust-lang/crates.io-index" 593 | checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" 594 | dependencies = [ 595 | "windows_aarch64_gnullvm", 596 | "windows_aarch64_msvc", 597 | "windows_i686_gnu", 598 | "windows_i686_gnullvm", 599 | "windows_i686_msvc", 600 | "windows_x86_64_gnu", 601 | "windows_x86_64_gnullvm", 602 | "windows_x86_64_msvc", 603 | ] 604 | 605 | [[package]] 606 | name = "windows_aarch64_gnullvm" 607 | version = "0.52.6" 608 | source = "registry+https://github.com/rust-lang/crates.io-index" 609 | checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" 610 | 611 | [[package]] 612 | name = "windows_aarch64_msvc" 613 | version = "0.52.6" 614 | source = "registry+https://github.com/rust-lang/crates.io-index" 615 | checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" 616 | 617 | [[package]] 618 | name = "windows_i686_gnu" 619 | version = "0.52.6" 620 | source = "registry+https://github.com/rust-lang/crates.io-index" 621 | checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" 622 | 623 | [[package]] 624 | name = "windows_i686_gnullvm" 625 | version = "0.52.6" 626 | source = "registry+https://github.com/rust-lang/crates.io-index" 627 | checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" 628 | 629 | [[package]] 630 | name = "windows_i686_msvc" 631 | version = "0.52.6" 632 | source = "registry+https://github.com/rust-lang/crates.io-index" 633 | checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" 634 | 635 | [[package]] 636 | name = "windows_x86_64_gnu" 637 | version = "0.52.6" 638 | source = "registry+https://github.com/rust-lang/crates.io-index" 639 | checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" 640 | 641 | [[package]] 642 | name = "windows_x86_64_gnullvm" 643 | version = "0.52.6" 644 | source = "registry+https://github.com/rust-lang/crates.io-index" 645 | checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" 646 | 647 | [[package]] 648 | name = "windows_x86_64_msvc" 649 | version = "0.52.6" 650 | source = "registry+https://github.com/rust-lang/crates.io-index" 651 | checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" 652 | -------------------------------------------------------------------------------- /test-project/bench/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "bench" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | [dev-dependencies] 7 | criterion = "0.4" 8 | 9 | [[bench]] 10 | name = "benchmark" 11 | harness = false 12 | -------------------------------------------------------------------------------- /test-project/bench/benches/benchmark.rs: -------------------------------------------------------------------------------- 1 | use criterion::{black_box, criterion_group, criterion_main, Criterion}; 2 | 3 | fn fibonacci(n: u64) -> u64 { 4 | match n { 5 | 0 => 1, 6 | 1 => 1, 7 | n => fibonacci(n - 1) + fibonacci(n - 2), 8 | } 9 | } 10 | 11 | fn criterion_benchmark(c: &mut Criterion) { 12 | c.bench_function("fib 20", |b| b.iter(|| fibonacci(black_box(20)))); 13 | } 14 | 15 | criterion_group!(benches, criterion_benchmark); 16 | criterion_main!(benches); 17 | -------------------------------------------------------------------------------- /test-project/bench/lib.rs: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /test-project/src/bin1.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | 5 | #[cfg(test)] 6 | mod test { 7 | #[test] 8 | fn test_something() { 9 | assert_eq!(1, 1); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /test-project/src/bin2.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world!"); 3 | } 4 | 5 | #[cfg(test)] 6 | mod test { 7 | #[test] 8 | fn test_something() { 9 | assert_eq!(1, 1); 10 | } 11 | 12 | #[test] 13 | #[ignore] 14 | fn test_something_ignored() { 15 | assert_eq!(2, 2); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /test-project/subcrate/Cargo.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Cargo. 2 | # It is not intended for manual editing. 3 | version = 3 4 | 5 | [[package]] 6 | name = "subcrate" 7 | version = "0.1.0" 8 | -------------------------------------------------------------------------------- /test-project/subcrate/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "subcrate" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # workaround for https://github.com/cross-rs/cross/issues/1345 7 | [package.metadata.cross.target.x86_64-unknown-netbsd] 8 | pre-build = [ 9 | "mkdir -p /tmp/netbsd", 10 | "curl https://archive.netbsd.org/pub/NetBSD-archive/NetBSD-9.2/amd64/binary/sets/base.tar.xz -O", 11 | "tar -C /tmp/netbsd -xJf base.tar.xz", 12 | "cp /tmp/netbsd/usr/lib/libexecinfo.so /usr/local/x86_64-unknown-netbsd/lib", 13 | "rm base.tar.xz", 14 | "rm -rf /tmp/netbsd", 15 | ] 16 | -------------------------------------------------------------------------------- /test-project/subcrate/src/main.rs: -------------------------------------------------------------------------------- 1 | fn main() { 2 | println!("Hello, world (subcrate)!"); 3 | } 4 | -------------------------------------------------------------------------------- /validate-inputs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | # Written by Claude.ai 4 | 5 | import os 6 | import json 7 | from pathlib import Path 8 | from typing import Dict, List, Union 9 | import tempfile 10 | import unittest 11 | 12 | boolean_flags = {"cache_cross_binary", "force_use_cross", "strip", "use_rust_cache"} 13 | 14 | 15 | class InputValidator: 16 | """Validate inputs for a GitHub Action.""" 17 | 18 | def __init__(self, repo_root: Union[str, Path]): 19 | """ 20 | Create a new InputValidator by collecting environment variables. 21 | 22 | Args: 23 | repo_root: Path to the repository root 24 | """ 25 | self.repo_root = Path(repo_root) 26 | self.inputs: Dict[str, str] = { 27 | key.replace("INPUTS_", "").lower(): value 28 | for key, value in os.environ.items() 29 | if key.startswith("INPUTS_") 30 | } 31 | 32 | def validate(self) -> List[str]: 33 | """ 34 | Validate all inputs according to specifications. 35 | 36 | Returns: 37 | List of validation errors. Empty list means all inputs are valid. 38 | """ 39 | validation_errors: List[str] = [] 40 | 41 | # Check for required 'target' parameter 42 | if "target" not in self.inputs: 43 | validation_errors.append("'target' is a required parameter") 44 | 45 | # Validate working directory if present 46 | if "working_directory" in self.inputs: 47 | path = Path(self.inputs["working_directory"]) 48 | if not path.is_absolute(): 49 | path = self.repo_root / path 50 | 51 | if not path.exists(): 52 | validation_errors.append( 53 | f"'working-directory' does not exist: {self.inputs['working_directory']}" 54 | ) 55 | elif not path.is_dir(): 56 | validation_errors.append( 57 | f"'working-directory' is not a directory: {self.inputs['working_directory']}" 58 | ) 59 | 60 | # Validate boolean flags 61 | for flag in boolean_flags: 62 | if flag in self.inputs and self.inputs[flag] not in {"true", "false"}: 63 | # Turn the underscores into dashes 64 | dashes = flag.replace("_", "-") 65 | validation_errors.append(f"'{dashes}' must be either 'true' or 'false'") 66 | 67 | # Validate rust-cache-parameters JSON if present 68 | if "rust_cache_parameters" in self.inputs: 69 | try: 70 | json.loads(self.inputs["rust_cache_parameters"]) 71 | except json.JSONDecodeError: 72 | validation_errors.append("'rust-cache-parameters' must be valid JSON") 73 | 74 | return validation_errors 75 | 76 | 77 | def main() -> None: 78 | """Main function for running the validator.""" 79 | import sys 80 | 81 | validator = InputValidator(sys.argv[1]) 82 | errors = validator.validate() 83 | 84 | if not errors: 85 | print("All inputs are valid.") 86 | sys.exit(0) 87 | else: 88 | for error in errors: 89 | print(error, file=sys.stderr) 90 | sys.exit(1) 91 | 92 | 93 | class TestInputValidator(unittest.TestCase): 94 | """Unit tests for the InputValidator.""" 95 | 96 | def setUp(self) -> None: 97 | """Set up test environment.""" 98 | # Clear existing INPUTS_ environment variables 99 | for key in list(os.environ.keys()): 100 | if key.startswith("INPUTS_"): 101 | del os.environ[key] 102 | 103 | def setup_env(self, inputs: Dict[str, str]) -> None: 104 | """Helper function to set up environment variables for testing.""" 105 | for key, value in inputs.items(): 106 | env_key = f"INPUTS_{key.upper().replace('-', '_')}" 107 | os.environ[env_key] = value 108 | 109 | def test_get_inputs_from_env(self) -> None: 110 | """Test getting inputs from environment variables.""" 111 | inputs = { 112 | "target": "x86_64-unknown-linux-gnu", 113 | "command": "build", 114 | "toolchain": "stable", 115 | "use-rust-cache": "true", 116 | } 117 | self.setup_env(inputs) 118 | 119 | validator = InputValidator("/root") 120 | for key, value in validator.inputs.items(): 121 | self.assertEqual(value, inputs[key.replace("_", "-")]) 122 | 123 | def test_validate_missing_target(self) -> None: 124 | """Test validation with missing target.""" 125 | self.setup_env({}) 126 | validator = InputValidator("/root") 127 | errors = validator.validate() 128 | self.assertTrue(errors) 129 | 130 | def test_validate_valid_toolchain(self) -> None: 131 | """Test validation of valid toolchains.""" 132 | valid_toolchains = [ 133 | "stable", 134 | "beta", 135 | "nightly", 136 | "nightly-2025-03-17", 137 | "stable 8 weeks ago", 138 | ] 139 | 140 | for toolchain in valid_toolchains: 141 | self.setup_env( 142 | {"target": "x86_64-unknown-linux-gnu", "toolchain": toolchain} 143 | ) 144 | validator = InputValidator("/root") 145 | errors = validator.validate() 146 | self.assertFalse(errors, f"Toolchain '{toolchain}' should be valid") 147 | 148 | def test_validate_working_directory(self) -> None: 149 | """Test validation of working directory.""" 150 | with tempfile.TemporaryDirectory() as temp_dir: 151 | # Test with valid directory 152 | self.setup_env( 153 | {"target": "x86_64-unknown-linux-gnu", "working-directory": temp_dir} 154 | ) 155 | validator = InputValidator("/root") 156 | errors = validator.validate() 157 | self.assertFalse(errors) 158 | 159 | # Test with non-existent directory 160 | self.setup_env( 161 | { 162 | "target": "x86_64-unknown-linux-gnu", 163 | "working-directory": "/path/to/nonexistent/directory", 164 | } 165 | ) 166 | validator = InputValidator("/root") 167 | errors = validator.validate() 168 | self.assertTrue(errors) 169 | 170 | # Test with file instead of directory 171 | with tempfile.NamedTemporaryFile() as temp_file: 172 | self.setup_env( 173 | { 174 | "target": "x86_64-unknown-linux-gnu", 175 | "working-directory": temp_file.name, 176 | } 177 | ) 178 | validator = InputValidator("/root") 179 | errors = validator.validate() 180 | self.assertTrue(errors) 181 | 182 | def test_validate_boolean_flags(self) -> None: 183 | """Test validation of boolean flags.""" 184 | 185 | # Test valid boolean values 186 | for flag in boolean_flags: 187 | for value in ["true", "false"]: 188 | self.setup_env({"target": "x86_64-unknown-linux-gnu", flag: value}) 189 | validator = InputValidator("/root") 190 | errors = validator.validate() 191 | self.assertFalse(errors, f"'{flag}' with '{value}' should be valid") 192 | 193 | # Test invalid boolean values 194 | for flag in boolean_flags: 195 | self.setup_env({"target": "x86_64-unknown-linux-gnu", flag: "invalid"}) 196 | validator = InputValidator("/root") 197 | errors = validator.validate() 198 | self.assertTrue(errors, f"'{flag}' with 'invalid' should be invalid") 199 | 200 | def test_validate_rust_cache_parameters(self) -> None: 201 | """Test validation of rust cache parameters.""" 202 | # Valid JSON 203 | self.setup_env( 204 | { 205 | "target": "x86_64-unknown-linux-gnu", 206 | "rust-cache-parameters": '{"key1":"value1","key2":"value2"}', 207 | } 208 | ) 209 | validator = InputValidator("/root") 210 | errors = validator.validate() 211 | self.assertFalse(errors) 212 | 213 | # Invalid JSON 214 | self.setup_env( 215 | { 216 | "target": "x86_64-unknown-linux-gnu", 217 | "rust-cache-parameters": "{invalid json", 218 | } 219 | ) 220 | validator = InputValidator("/root") 221 | errors = validator.validate() 222 | self.assertTrue(errors) 223 | 224 | 225 | if __name__ == "__main__": 226 | if len(os.sys.argv) > 1 and os.sys.argv[1] == "--test": 227 | unittest.main(argv=["unittest"]) 228 | else: 229 | main() 230 | --------------------------------------------------------------------------------