Compare commits
81 Commits
fix/option
...
main
Author | SHA1 | Date |
---|---|---|
xenua | 8f00290b74 | 9 months ago |
Meziu | af2d8bfd79 | 11 months ago |
Ian Chamberlain | c6943cf953 | 11 months ago |
Ian Chamberlain | f6b9c6dc95 | 1 year ago |
Ian Chamberlain | 7d6ddb2ef8 | 1 year ago |
Ian Chamberlain | 2baf9cfc78 | 1 year ago |
Ian Chamberlain | 85ebef86aa | 1 year ago |
Ian Chamberlain | e1cf233cde | 1 year ago |
Ian Chamberlain | 545d853a80 | 1 year ago |
Ian Chamberlain | 2276df9d26 | 1 year ago |
Ian Chamberlain | 2931e5609e | 1 year ago |
Meziu | aa0d754d79 | 1 year ago |
Ian Chamberlain | 6b4fd53f20 | 1 year ago |
Ian Chamberlain | 0db0ad3b96 | 1 year ago |
Ian Chamberlain | ab30da9ee7 | 1 year ago |
Ian Chamberlain | cda49e09b6 | 1 year ago |
Ian Chamberlain | 48aa7933a4 | 1 year ago |
Ian Chamberlain | d374e7016d | 1 year ago |
Andrea Ciliberti | 268c45ae8f | 1 year ago |
Meziu | 1372661285 | 1 year ago |
Andrea Ciliberti | d2f50fcc84 | 1 year ago |
Andrea Ciliberti | 50530b112f | 1 year ago |
Meziu | 246753119d | 2 years ago |
Andrea Ciliberti | 757ce04e98 | 2 years ago |
Meziu | 3429e3ed9c | 2 years ago |
Andrea Ciliberti | c1f5e121f1 | 2 years ago |
Meziu | afc9046365 | 2 years ago |
Andrea Ciliberti | 70d5ec1843 | 2 years ago |
Andrea Ciliberti | 4560716fcc | 2 years ago |
Andrea Ciliberti | f5bee9a4e5 | 2 years ago |
Andrea Ciliberti | 17ce4002e8 | 2 years ago |
Andrea Ciliberti | ea28cbc788 | 2 years ago |
Andrea Ciliberti | 66eb19ed1f | 2 years ago |
Andrea Ciliberti | 7e16b8f123 | 2 years ago |
Meziu | 6c22e506af | 2 years ago |
Andrea Ciliberti | dd5ed8a480 | 2 years ago |
Andrea Ciliberti | abe088a78c | 2 years ago |
Andrea Ciliberti | a0fcbbc57e | 2 years ago |
Andrea Ciliberti | 9b483a8589 | 2 years ago |
Andrea Ciliberti | eaa93126f4 | 2 years ago |
Andrea Ciliberti | b899ff9d7e | 2 years ago |
Andrea Ciliberti | 9b6b79dff2 | 2 years ago |
Ian Chamberlain | 78a652fdfb | 2 years ago |
Ian Chamberlain | 6f4b74355a | 2 years ago |
Ian Chamberlain | 3e7665fb63 | 2 years ago |
Meziu | 804f63d0bc | 2 years ago |
Meziu | a1ca4f649a | 2 years ago |
Andrea Ciliberti | 318320200b | 2 years ago |
Andrea Ciliberti | 657eeccd1e | 2 years ago |
Ian Chamberlain | 5a06e64307 | 2 years ago |
Ian Chamberlain | e8535a130d | 2 years ago |
Meziu | 83032c6a71 | 2 years ago |
Ian Chamberlain | 329cb00d74 | 2 years ago |
Ian Chamberlain | dd8ae47296 | 2 years ago |
Ian Chamberlain | dbf1595def | 2 years ago |
Ian Chamberlain | 583fc67fc6 | 2 years ago |
Ian Chamberlain | c2560622e8 | 2 years ago |
Ian Chamberlain | cef69644e6 | 2 years ago |
Meziu | 7b70b6b26c | 2 years ago |
Mark Drobnak | cb3a9a19ff | 2 years ago |
Mark Drobnak | 7e99e1a9a5 | 2 years ago |
Mark Drobnak | 7723d93227 | 2 years ago |
Steve Cook | c7b6482f9c | 2 years ago |
Steve Cook | 84ac10371b | 3 years ago |
Steve Cook | 1a562d0c1a | 3 years ago |
Steve Cook | eb1d4345b7 | 3 years ago |
Steve Cook | 8d350a8c6b | 3 years ago |
Steve Cook | 9aa632622c | 3 years ago |
Steve Cook | 4c5929715c | 3 years ago |
Steve Cook | ef6a2f6724 | 3 years ago |
Steve Cook | 8eac835c4c | 3 years ago |
Steve Cook | 06bdf9f289 | 3 years ago |
Steve Cook | 239556abf8 | 3 years ago |
Steve Cook | 51917f8fcf | 3 years ago |
Steve Cook | f05b57f5ac | 3 years ago |
Steve Cook | 11855472df | 3 years ago |
Steve Cook | 3cb1169dcb | 3 years ago |
Steve Cook | cc03d78ba5 | 3 years ago |
Meziu | 9136456653 | 3 years ago |
Meziu | 7c8d450a4a | 3 years ago |
Mateo Cabanal | 880661157c | 3 years ago |
13 changed files with 2192 additions and 503 deletions
@ -0,0 +1,36 @@ |
|||||||
|
name: Setup |
||||||
|
description: Set up CI environment for Rust + 3DS development |
||||||
|
|
||||||
|
inputs: |
||||||
|
toolchain: |
||||||
|
description: The Rust toolchain to use for the steps |
||||||
|
required: true |
||||||
|
default: nightly |
||||||
|
|
||||||
|
runs: |
||||||
|
using: composite |
||||||
|
steps: |
||||||
|
# https://github.com/nektos/act/issues/917#issuecomment-1074421318 |
||||||
|
- if: ${{ env.ACT }} |
||||||
|
shell: bash |
||||||
|
name: Hack container for local development |
||||||
|
run: | |
||||||
|
curl -fsSL https://deb.nodesource.com/setup_lts.x | sudo -E bash - |
||||||
|
sudo apt-get install -y nodejs |
||||||
|
|
||||||
|
- name: Setup default Rust toolchain |
||||||
|
# Use this helper action so we get matcher support |
||||||
|
# https://github.com/actions-rust-lang/setup-rust-toolchain/pull/15 |
||||||
|
uses: actions-rust-lang/setup-rust-toolchain@v1 |
||||||
|
with: |
||||||
|
components: clippy, rustfmt, rust-src |
||||||
|
toolchain: ${{ inputs.toolchain }} |
||||||
|
|
||||||
|
- name: Install build tools for host |
||||||
|
shell: bash |
||||||
|
run: sudo apt-get update && sudo apt-get install -y build-essential |
||||||
|
|
||||||
|
- name: Set PATH to include devkitARM |
||||||
|
shell: bash |
||||||
|
# For some reason devkitARM/bin is not part of the default PATH in the container |
||||||
|
run: echo "${DEVKITARM}/bin" >> $GITHUB_PATH |
@ -0,0 +1,71 @@ |
|||||||
|
name: CI |
||||||
|
|
||||||
|
on: |
||||||
|
push: |
||||||
|
branches: |
||||||
|
- master |
||||||
|
pull_request: |
||||||
|
branches: |
||||||
|
- master |
||||||
|
workflow_dispatch: |
||||||
|
|
||||||
|
env: |
||||||
|
# https://blog.rust-lang.org/2022/06/22/sparse-registry-testing.html |
||||||
|
CARGO_UNSTABLE_SPARSE_REGISTRY: "true" |
||||||
|
# actions-rust-lang/setup-rust-toolchain sets some default RUSTFLAGS |
||||||
|
RUSTFLAGS: "" |
||||||
|
|
||||||
|
jobs: |
||||||
|
lint: |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
toolchain: |
||||||
|
- stable |
||||||
|
|
||||||
|
runs-on: ubuntu-latest |
||||||
|
container: devkitpro/devkitarm |
||||||
|
steps: |
||||||
|
- name: Checkout branch |
||||||
|
uses: actions/checkout@v2 |
||||||
|
|
||||||
|
- uses: ./.github/actions/setup |
||||||
|
with: |
||||||
|
toolchain: ${{ matrix.toolchain }} |
||||||
|
|
||||||
|
- name: Check formatting |
||||||
|
run: cargo fmt --all --verbose -- --check |
||||||
|
|
||||||
|
- name: Cargo check |
||||||
|
run: cargo clippy --color=always --verbose --all-targets |
||||||
|
|
||||||
|
project-build: |
||||||
|
strategy: |
||||||
|
matrix: |
||||||
|
toolchain: |
||||||
|
# Oldest supported nightly |
||||||
|
- nightly-2023-06-01 |
||||||
|
- nightly |
||||||
|
|
||||||
|
continue-on-error: ${{ matrix.toolchain == 'nightly' }} |
||||||
|
runs-on: ubuntu-latest |
||||||
|
container: devkitpro/devkitarm |
||||||
|
steps: |
||||||
|
- name: Checkout branch |
||||||
|
uses: actions/checkout@v3 |
||||||
|
|
||||||
|
- uses: ./.github/actions/setup |
||||||
|
with: |
||||||
|
toolchain: ${{ matrix.toolchain }} |
||||||
|
|
||||||
|
- name: Install cargo-3ds |
||||||
|
uses: actions-rs/cargo@v1 |
||||||
|
with: |
||||||
|
command: install |
||||||
|
args: --locked --path . |
||||||
|
|
||||||
|
- name: Create new project |
||||||
|
run: cargo 3ds new app --bin |
||||||
|
|
||||||
|
- name: Build project |
||||||
|
working-directory: ./app |
||||||
|
run: cargo 3ds build --release |
@ -0,0 +1,462 @@ |
|||||||
|
# This file is automatically @generated by Cargo. |
||||||
|
# It is not intended for manual editing. |
||||||
|
version = 3 |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "anstream" |
||||||
|
version = "0.6.4" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "2ab91ebe16eb252986481c5b62f6098f3b698a45e34b5b98200cf20dd2484a44" |
||||||
|
dependencies = [ |
||||||
|
"anstyle", |
||||||
|
"anstyle-parse", |
||||||
|
"anstyle-query", |
||||||
|
"anstyle-wincon", |
||||||
|
"colorchoice", |
||||||
|
"utf8parse", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "anstyle" |
||||||
|
version = "1.0.4" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "7079075b41f533b8c61d2a4d073c4676e1f8b249ff94a393b0595db304e0dd87" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "anstyle-parse" |
||||||
|
version = "0.2.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "317b9a89c1868f5ea6ff1d9539a69f45dffc21ce321ac1fd1160dfa48c8e2140" |
||||||
|
dependencies = [ |
||||||
|
"utf8parse", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "anstyle-query" |
||||||
|
version = "1.0.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "5ca11d4be1bab0c8bc8734a9aa7bf4ee8316d462a08c6ac5052f888fef5b494b" |
||||||
|
dependencies = [ |
||||||
|
"windows-sys 0.48.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "anstyle-wincon" |
||||||
|
version = "3.0.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "f0699d10d2f4d628a98ee7b57b289abbc98ff3bad977cb3152709d4bf2330628" |
||||||
|
dependencies = [ |
||||||
|
"anstyle", |
||||||
|
"windows-sys 0.48.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "bitflags" |
||||||
|
version = "2.4.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "327762f6e5a765692301e5bb513e0d9fef63be86bbc14528052b1cd3e6f03e07" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "camino" |
||||||
|
version = "1.1.6" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "c59e92b5a388f549b863a7bea62612c09f24c8393560709a54558a9abdfb3b9c" |
||||||
|
dependencies = [ |
||||||
|
"serde", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cargo-3ds" |
||||||
|
version = "0.1.2" |
||||||
|
dependencies = [ |
||||||
|
"cargo_metadata", |
||||||
|
"clap", |
||||||
|
"rustc_version", |
||||||
|
"semver", |
||||||
|
"serde", |
||||||
|
"serde_json", |
||||||
|
"shlex", |
||||||
|
"tee", |
||||||
|
"toml", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cargo-platform" |
||||||
|
version = "0.1.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "e34637b3140142bdf929fb439e8aa4ebad7651ebf7b1080b3930aa16ac1459ff" |
||||||
|
dependencies = [ |
||||||
|
"serde", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "cargo_metadata" |
||||||
|
version = "0.14.2" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "4acbb09d9ee8e23699b9634375c72795d095bf268439da88562cf9b501f181fa" |
||||||
|
dependencies = [ |
||||||
|
"camino", |
||||||
|
"cargo-platform", |
||||||
|
"semver", |
||||||
|
"serde", |
||||||
|
"serde_json", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "clap" |
||||||
|
version = "4.4.10" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "41fffed7514f420abec6d183b1d3acfd9099c79c3a10a06ade4f8203f1411272" |
||||||
|
dependencies = [ |
||||||
|
"clap_builder", |
||||||
|
"clap_derive", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "clap_builder" |
||||||
|
version = "4.4.9" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "63361bae7eef3771745f02d8d892bec2fee5f6e34af316ba556e7f97a7069ff1" |
||||||
|
dependencies = [ |
||||||
|
"anstream", |
||||||
|
"anstyle", |
||||||
|
"clap_lex", |
||||||
|
"strsim", |
||||||
|
"terminal_size", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "clap_derive" |
||||||
|
version = "4.4.7" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442" |
||||||
|
dependencies = [ |
||||||
|
"heck", |
||||||
|
"proc-macro2", |
||||||
|
"quote", |
||||||
|
"syn", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "clap_lex" |
||||||
|
version = "0.6.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "colorchoice" |
||||||
|
version = "1.0.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "errno" |
||||||
|
version = "0.3.8" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" |
||||||
|
dependencies = [ |
||||||
|
"libc", |
||||||
|
"windows-sys 0.52.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "heck" |
||||||
|
version = "0.4.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "itoa" |
||||||
|
version = "1.0.9" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "af150ab688ff2122fcef229be89cb50dd66af9e01a4ff320cc137eecc9bacc38" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "libc" |
||||||
|
version = "0.2.150" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "linux-raw-sys" |
||||||
|
version = "0.4.11" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "969488b55f8ac402214f3f5fd243ebb7206cf82de60d3172994707a4bcc2b829" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "proc-macro2" |
||||||
|
version = "1.0.70" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "39278fbbf5fb4f646ce651690877f89d1c5811a3d4acb27700c1cb3cdb78fd3b" |
||||||
|
dependencies = [ |
||||||
|
"unicode-ident", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "quote" |
||||||
|
version = "1.0.33" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rustc_version" |
||||||
|
version = "0.4.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" |
||||||
|
dependencies = [ |
||||||
|
"semver", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "rustix" |
||||||
|
version = "0.38.25" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "dc99bc2d4f1fed22595588a013687477aedf3cdcfb26558c559edb67b4d9b22e" |
||||||
|
dependencies = [ |
||||||
|
"bitflags", |
||||||
|
"errno", |
||||||
|
"libc", |
||||||
|
"linux-raw-sys", |
||||||
|
"windows-sys 0.48.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "ryu" |
||||||
|
version = "1.0.15" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "semver" |
||||||
|
version = "1.0.20" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" |
||||||
|
dependencies = [ |
||||||
|
"serde", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "serde" |
||||||
|
version = "1.0.193" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "25dd9975e68d0cb5aa1120c288333fc98731bd1dd12f561e468ea4728c042b89" |
||||||
|
dependencies = [ |
||||||
|
"serde_derive", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "serde_derive" |
||||||
|
version = "1.0.193" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "43576ca501357b9b071ac53cdc7da8ef0cbd9493d8df094cd821777ea6e894d3" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
"quote", |
||||||
|
"syn", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "serde_json" |
||||||
|
version = "1.0.108" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3d1c7e3eac408d115102c4c24ad393e0821bb3a5df4d506a80f85f7a742a526b" |
||||||
|
dependencies = [ |
||||||
|
"itoa", |
||||||
|
"ryu", |
||||||
|
"serde", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "shlex" |
||||||
|
version = "1.2.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "a7cee0529a6d40f580e7a5e6c495c8fbfe21b7b52795ed4bb5e62cdf92bc6380" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "strsim" |
||||||
|
version = "0.10.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "syn" |
||||||
|
version = "2.0.39" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "23e78b90f2fcf45d3e842032ce32e3f2d1545ba6636271dcbf24fa306d87be7a" |
||||||
|
dependencies = [ |
||||||
|
"proc-macro2", |
||||||
|
"quote", |
||||||
|
"unicode-ident", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "tee" |
||||||
|
version = "0.1.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "37c12559dba7383625faaff75be24becf35bfc885044375bcab931111799a3da" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "terminal_size" |
||||||
|
version = "0.3.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "21bebf2b7c9e0a515f6e0f8c51dc0f8e4696391e6f1ff30379559f8365fb0df7" |
||||||
|
dependencies = [ |
||||||
|
"rustix", |
||||||
|
"windows-sys 0.48.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "toml" |
||||||
|
version = "0.5.11" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" |
||||||
|
dependencies = [ |
||||||
|
"serde", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "unicode-ident" |
||||||
|
version = "1.0.12" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "utf8parse" |
||||||
|
version = "0.2.1" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows-sys" |
||||||
|
version = "0.48.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" |
||||||
|
dependencies = [ |
||||||
|
"windows-targets 0.48.5", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows-sys" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" |
||||||
|
dependencies = [ |
||||||
|
"windows-targets 0.52.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows-targets" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" |
||||||
|
dependencies = [ |
||||||
|
"windows_aarch64_gnullvm 0.48.5", |
||||||
|
"windows_aarch64_msvc 0.48.5", |
||||||
|
"windows_i686_gnu 0.48.5", |
||||||
|
"windows_i686_msvc 0.48.5", |
||||||
|
"windows_x86_64_gnu 0.48.5", |
||||||
|
"windows_x86_64_gnullvm 0.48.5", |
||||||
|
"windows_x86_64_msvc 0.48.5", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows-targets" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd" |
||||||
|
dependencies = [ |
||||||
|
"windows_aarch64_gnullvm 0.52.0", |
||||||
|
"windows_aarch64_msvc 0.52.0", |
||||||
|
"windows_i686_gnu 0.52.0", |
||||||
|
"windows_i686_msvc 0.52.0", |
||||||
|
"windows_x86_64_gnu 0.52.0", |
||||||
|
"windows_x86_64_gnullvm 0.52.0", |
||||||
|
"windows_x86_64_msvc 0.52.0", |
||||||
|
] |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_aarch64_gnullvm" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_aarch64_gnullvm" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_aarch64_msvc" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_aarch64_msvc" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_i686_gnu" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_i686_gnu" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_i686_msvc" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_i686_msvc" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_gnu" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_gnu" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_gnullvm" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_gnullvm" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_msvc" |
||||||
|
version = "0.48.5" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" |
||||||
|
|
||||||
|
[[package]] |
||||||
|
name = "windows_x86_64_msvc" |
||||||
|
version = "0.52.0" |
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index" |
||||||
|
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04" |
@ -1,17 +1,22 @@ |
|||||||
[package] |
[package] |
||||||
name = "cargo-3ds" |
name = "cargo-3ds" |
||||||
version = "0.1.0" |
version = "0.1.2" |
||||||
description = "Cargo wrapper for developing Rust-based Nintendo 3DS homebrew apps" |
authors = ["Rust3DS Org", "Andrea Ciliberti <meziu210@icloud.com>"] |
||||||
repository = "https://github.com/Meziu/cargo-3ds" |
description = "Cargo wrapper for developing Nintendo 3DS homebrew apps" |
||||||
license = "MIT" |
repository = "https://git.xenua.me/rust3ds/cargo-3ds" |
||||||
authors = ["Andrea Ciliberti <meziu210@icloud.com>"] |
keywords = ["3ds", "homebrew"] |
||||||
|
categories = ["command-line-utilities", "development-tools::cargo-plugins"] |
||||||
|
exclude = [".github"] |
||||||
|
license = "MIT OR Apache-2.0" |
||||||
edition = "2021" |
edition = "2021" |
||||||
|
|
||||||
[dependencies] |
[dependencies] |
||||||
cargo_metadata = "0.14.0" |
cargo_metadata = "0.14.0" |
||||||
rustc_version = "0.4.0" |
rustc_version = "0.4.0" |
||||||
semver = "1.0.10" |
semver = "1.0.10" |
||||||
serde = "1.0.111" |
serde = { version = "1.0.139", features = ["derive"] } |
||||||
serde_derive = "1.0.111" |
|
||||||
tee = "0.1.0" |
tee = "0.1.0" |
||||||
toml = "0.5.6" |
toml = "0.5.6" |
||||||
|
clap = { version = "4.0.15", features = ["derive", "wrap_help"] } |
||||||
|
shlex = "1.1.0" |
||||||
|
serde_json = "1.0.108" |
||||||
|
@ -0,0 +1,176 @@ |
|||||||
|
Apache License |
||||||
|
Version 2.0, January 2004 |
||||||
|
http://www.apache.org/licenses/ |
||||||
|
|
||||||
|
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION |
||||||
|
|
||||||
|
1. Definitions. |
||||||
|
|
||||||
|
"License" shall mean the terms and conditions for use, reproduction, |
||||||
|
and distribution as defined by Sections 1 through 9 of this document. |
||||||
|
|
||||||
|
"Licensor" shall mean the copyright owner or entity authorized by |
||||||
|
the copyright owner that is granting the License. |
||||||
|
|
||||||
|
"Legal Entity" shall mean the union of the acting entity and all |
||||||
|
other entities that control, are controlled by, or are under common |
||||||
|
control with that entity. For the purposes of this definition, |
||||||
|
"control" means (i) the power, direct or indirect, to cause the |
||||||
|
direction or management of such entity, whether by contract or |
||||||
|
otherwise, or (ii) ownership of fifty percent (50%) or more of the |
||||||
|
outstanding shares, or (iii) beneficial ownership of such entity. |
||||||
|
|
||||||
|
"You" (or "Your") shall mean an individual or Legal Entity |
||||||
|
exercising permissions granted by this License. |
||||||
|
|
||||||
|
"Source" form shall mean the preferred form for making modifications, |
||||||
|
including but not limited to software source code, documentation |
||||||
|
source, and configuration files. |
||||||
|
|
||||||
|
"Object" form shall mean any form resulting from mechanical |
||||||
|
transformation or translation of a Source form, including but |
||||||
|
not limited to compiled object code, generated documentation, |
||||||
|
and conversions to other media types. |
||||||
|
|
||||||
|
"Work" shall mean the work of authorship, whether in Source or |
||||||
|
Object form, made available under the License, as indicated by a |
||||||
|
copyright notice that is included in or attached to the work |
||||||
|
(an example is provided in the Appendix below). |
||||||
|
|
||||||
|
"Derivative Works" shall mean any work, whether in Source or Object |
||||||
|
form, that is based on (or derived from) the Work and for which the |
||||||
|
editorial revisions, annotations, elaborations, or other modifications |
||||||
|
represent, as a whole, an original work of authorship. For the purposes |
||||||
|
of this License, Derivative Works shall not include works that remain |
||||||
|
separable from, or merely link (or bind by name) to the interfaces of, |
||||||
|
the Work and Derivative Works thereof. |
||||||
|
|
||||||
|
"Contribution" shall mean any work of authorship, including |
||||||
|
the original version of the Work and any modifications or additions |
||||||
|
to that Work or Derivative Works thereof, that is intentionally |
||||||
|
submitted to Licensor for inclusion in the Work by the copyright owner |
||||||
|
or by an individual or Legal Entity authorized to submit on behalf of |
||||||
|
the copyright owner. For the purposes of this definition, "submitted" |
||||||
|
means any form of electronic, verbal, or written communication sent |
||||||
|
to the Licensor or its representatives, including but not limited to |
||||||
|
communication on electronic mailing lists, source code control systems, |
||||||
|
and issue tracking systems that are managed by, or on behalf of, the |
||||||
|
Licensor for the purpose of discussing and improving the Work, but |
||||||
|
excluding communication that is conspicuously marked or otherwise |
||||||
|
designated in writing by the copyright owner as "Not a Contribution." |
||||||
|
|
||||||
|
"Contributor" shall mean Licensor and any individual or Legal Entity |
||||||
|
on behalf of whom a Contribution has been received by Licensor and |
||||||
|
subsequently incorporated within the Work. |
||||||
|
|
||||||
|
2. Grant of Copyright License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
copyright license to reproduce, prepare Derivative Works of, |
||||||
|
publicly display, publicly perform, sublicense, and distribute the |
||||||
|
Work and such Derivative Works in Source or Object form. |
||||||
|
|
||||||
|
3. Grant of Patent License. Subject to the terms and conditions of |
||||||
|
this License, each Contributor hereby grants to You a perpetual, |
||||||
|
worldwide, non-exclusive, no-charge, royalty-free, irrevocable |
||||||
|
(except as stated in this section) patent license to make, have made, |
||||||
|
use, offer to sell, sell, import, and otherwise transfer the Work, |
||||||
|
where such license applies only to those patent claims licensable |
||||||
|
by such Contributor that are necessarily infringed by their |
||||||
|
Contribution(s) alone or by combination of their Contribution(s) |
||||||
|
with the Work to which such Contribution(s) was submitted. If You |
||||||
|
institute patent litigation against any entity (including a |
||||||
|
cross-claim or counterclaim in a lawsuit) alleging that the Work |
||||||
|
or a Contribution incorporated within the Work constitutes direct |
||||||
|
or contributory patent infringement, then any patent licenses |
||||||
|
granted to You under this License for that Work shall terminate |
||||||
|
as of the date such litigation is filed. |
||||||
|
|
||||||
|
4. Redistribution. You may reproduce and distribute copies of the |
||||||
|
Work or Derivative Works thereof in any medium, with or without |
||||||
|
modifications, and in Source or Object form, provided that You |
||||||
|
meet the following conditions: |
||||||
|
|
||||||
|
(a) You must give any other recipients of the Work or |
||||||
|
Derivative Works a copy of this License; and |
||||||
|
|
||||||
|
(b) You must cause any modified files to carry prominent notices |
||||||
|
stating that You changed the files; and |
||||||
|
|
||||||
|
(c) You must retain, in the Source form of any Derivative Works |
||||||
|
that You distribute, all copyright, patent, trademark, and |
||||||
|
attribution notices from the Source form of the Work, |
||||||
|
excluding those notices that do not pertain to any part of |
||||||
|
the Derivative Works; and |
||||||
|
|
||||||
|
(d) If the Work includes a "NOTICE" text file as part of its |
||||||
|
distribution, then any Derivative Works that You distribute must |
||||||
|
include a readable copy of the attribution notices contained |
||||||
|
within such NOTICE file, excluding those notices that do not |
||||||
|
pertain to any part of the Derivative Works, in at least one |
||||||
|
of the following places: within a NOTICE text file distributed |
||||||
|
as part of the Derivative Works; within the Source form or |
||||||
|
documentation, if provided along with the Derivative Works; or, |
||||||
|
within a display generated by the Derivative Works, if and |
||||||
|
wherever such third-party notices normally appear. The contents |
||||||
|
of the NOTICE file are for informational purposes only and |
||||||
|
do not modify the License. You may add Your own attribution |
||||||
|
notices within Derivative Works that You distribute, alongside |
||||||
|
or as an addendum to the NOTICE text from the Work, provided |
||||||
|
that such additional attribution notices cannot be construed |
||||||
|
as modifying the License. |
||||||
|
|
||||||
|
You may add Your own copyright statement to Your modifications and |
||||||
|
may provide additional or different license terms and conditions |
||||||
|
for use, reproduction, or distribution of Your modifications, or |
||||||
|
for any such Derivative Works as a whole, provided Your use, |
||||||
|
reproduction, and distribution of the Work otherwise complies with |
||||||
|
the conditions stated in this License. |
||||||
|
|
||||||
|
5. Submission of Contributions. Unless You explicitly state otherwise, |
||||||
|
any Contribution intentionally submitted for inclusion in the Work |
||||||
|
by You to the Licensor shall be under the terms and conditions of |
||||||
|
this License, without any additional terms or conditions. |
||||||
|
Notwithstanding the above, nothing herein shall supersede or modify |
||||||
|
the terms of any separate license agreement you may have executed |
||||||
|
with Licensor regarding such Contributions. |
||||||
|
|
||||||
|
6. Trademarks. This License does not grant permission to use the trade |
||||||
|
names, trademarks, service marks, or product names of the Licensor, |
||||||
|
except as required for reasonable and customary use in describing the |
||||||
|
origin of the Work and reproducing the content of the NOTICE file. |
||||||
|
|
||||||
|
7. Disclaimer of Warranty. Unless required by applicable law or |
||||||
|
agreed to in writing, Licensor provides the Work (and each |
||||||
|
Contributor provides its Contributions) on an "AS IS" BASIS, |
||||||
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or |
||||||
|
implied, including, without limitation, any warranties or conditions |
||||||
|
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE. You are solely responsible for determining the |
||||||
|
appropriateness of using or redistributing the Work and assume any |
||||||
|
risks associated with Your exercise of permissions under this License. |
||||||
|
|
||||||
|
8. Limitation of Liability. In no event and under no legal theory, |
||||||
|
whether in tort (including negligence), contract, or otherwise, |
||||||
|
unless required by applicable law (such as deliberate and grossly |
||||||
|
negligent acts) or agreed to in writing, shall any Contributor be |
||||||
|
liable to You for damages, including any direct, indirect, special, |
||||||
|
incidental, or consequential damages of any character arising as a |
||||||
|
result of this License or out of the use or inability to use the |
||||||
|
Work (including but not limited to damages for loss of goodwill, |
||||||
|
work stoppage, computer failure or malfunction, or any and all |
||||||
|
other commercial damages or losses), even if such Contributor |
||||||
|
has been advised of the possibility of such damages. |
||||||
|
|
||||||
|
9. Accepting Warranty or Additional Liability. While redistributing |
||||||
|
the Work or Derivative Works thereof, You may choose to offer, |
||||||
|
and charge a fee for, acceptance of support, warranty, indemnity, |
||||||
|
or other liability obligations and/or rights consistent with this |
||||||
|
License. However, in accepting such obligations, You may act only |
||||||
|
on Your own behalf and on Your sole responsibility, not on behalf |
||||||
|
of any other Contributor, and only if You agree to indemnify, |
||||||
|
defend, and hold each Contributor harmless for any liability |
||||||
|
incurred by, or claims asserted against, such Contributor by reason |
||||||
|
of your accepting any such warranty or additional liability. |
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS |
@ -0,0 +1,23 @@ |
|||||||
|
Permission is hereby granted, free of charge, to any |
||||||
|
person obtaining a copy of this software and associated |
||||||
|
documentation files (the "Software"), to deal in the |
||||||
|
Software without restriction, including without |
||||||
|
limitation the rights to use, copy, modify, merge, |
||||||
|
publish, distribute, sublicense, and/or sell copies of |
||||||
|
the Software, and to permit persons to whom the Software |
||||||
|
is furnished to do so, subject to the following |
||||||
|
conditions: |
||||||
|
|
||||||
|
The above copyright notice and this permission notice |
||||||
|
shall be included in all copies or substantial portions |
||||||
|
of the Software. |
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF |
||||||
|
ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED |
||||||
|
TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A |
||||||
|
PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT |
||||||
|
SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY |
||||||
|
CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION |
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR |
||||||
|
IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER |
||||||
|
DEALINGS IN THE SOFTWARE. |
@ -1,15 +1,130 @@ |
|||||||
# cargo-3ds |
# cargo-3ds |
||||||
|
|
||||||
Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp. |
Cargo command to work with Nintendo 3DS project binaries. Based on cargo-psp. |
||||||
|
|
||||||
# Usage |
## Installation |
||||||
While you can set the nightly version of Rust as default for the project you're working on (`rustup override nightly`), my suggested method is: |
|
||||||
`cargo +nightly 3ds`. \ |
To install the latest release on <https://crates.io>: |
||||||
The commands are the same as cargo ("run" also uses 3dslink, so you can directly use `run` to compile and run on your system). |
|
||||||
|
```sh |
||||||
|
cargo install --locked cargo-3ds |
||||||
|
``` |
||||||
|
|
||||||
|
To install the current `master` version of `cargo-3ds`: |
||||||
|
|
||||||
|
```sh |
||||||
|
cargo install --locked --git https://github.com/rust3ds/cargo-3ds |
||||||
|
``` |
||||||
|
|
||||||
|
## Usage |
||||||
|
|
||||||
|
Use the nightly toolchain to build 3DS apps (either by using `rustup override nightly` for the project directory or by adding `+nightly` in the `cargo` invocation). |
||||||
|
|
||||||
|
```txt |
||||||
|
Commands: |
||||||
|
build |
||||||
|
Builds an executable suitable to run on a 3DS (3dsx) |
||||||
|
run |
||||||
|
Builds an executable and sends it to a device with `3dslink` |
||||||
|
test |
||||||
|
Builds a test executable and sends it to a device with `3dslink` |
||||||
|
new |
||||||
|
Sets up a new cargo project suitable to run on a 3DS |
||||||
|
help |
||||||
|
Print this message or the help of the given subcommand(s) |
||||||
|
|
||||||
|
Options: |
||||||
|
-h, --help |
||||||
|
Print help information (use `-h` for a summary) |
||||||
|
|
||||||
|
-V, --version |
||||||
|
Print version information |
||||||
|
``` |
||||||
|
|
||||||
|
Additional arguments will be passed through to the given subcommand. |
||||||
|
See [passthrough arguments](#passthrough-arguments) for more details. |
||||||
|
|
||||||
|
It is also possible to pass any other `cargo` command (e.g. `doc`, `check`), |
||||||
|
and all its arguments will be passed through directly to `cargo` unmodified, |
||||||
|
with the proper `--target armv6k-nintendo-3ds` set. |
||||||
|
|
||||||
|
### Basic Examples |
||||||
|
|
||||||
|
* `cargo 3ds build` |
||||||
|
* `cargo 3ds check --verbose` |
||||||
|
* `cargo 3ds run --release --example foo` |
||||||
|
* `cargo 3ds test --no-run` |
||||||
|
* `cargo 3ds new my-new-project --edition 2021` |
||||||
|
|
||||||
|
### Running executables |
||||||
|
|
||||||
|
`cargo 3ds test` and `cargo 3ds run` use the `3dslink` tool to send built |
||||||
|
executables to a device, and thus accept specific related arguments that correspond |
||||||
|
to `3dslink` arguments: |
||||||
|
|
||||||
|
```txt |
||||||
|
-a, --address <ADDRESS> |
||||||
|
Specify the IP address of the device to send the executable to. |
||||||
|
|
||||||
|
Corresponds to 3dslink's `--address` arg, which defaults to automatically finding the device. |
||||||
|
|
||||||
|
-0, --argv0 <ARGV0> |
||||||
|
Set the 0th argument of the executable when running it. Corresponds to 3dslink's `--argv0` argument |
||||||
|
|
||||||
|
-s, --server |
||||||
|
Start the 3dslink server after sending the executable. Corresponds to 3dslink's `--server` argument |
||||||
|
|
||||||
|
--retries <RETRIES> |
||||||
|
Set the number of tries when connecting to the device to send the executable. Corresponds to 3dslink's `--retries` argument |
||||||
|
``` |
||||||
|
|
||||||
|
### Passthrough Arguments |
||||||
|
|
||||||
|
Due to the way `cargo-3ds`, `cargo`, and `3dslink` parse arguments, there is |
||||||
|
unfortunately some complexity required when invoking an executable with arguments. |
||||||
|
|
||||||
|
All unrecognized arguments beginning with a dash (e.g. `--release`, `--features`, |
||||||
|
etc.) will be passed through to `cargo` directly. |
||||||
|
|
||||||
|
> **NOTE:** arguments for [running executables](#running-executables) must be |
||||||
|
> specified *before* other unrecognized `cargo` arguments. Otherwise they will |
||||||
|
> be treated as passthrough arguments which will most likely fail the resulting |
||||||
|
> `cargo` command. |
||||||
|
|
||||||
|
An optional `--` may be used to explicitly pass subsequent args to `cargo`, including |
||||||
|
arguments to pass to the executable itself. To separate `cargo` arguments from |
||||||
|
executable arguments, *another* `--` can be used. For example: |
||||||
|
|
||||||
|
* `cargo 3ds run -- -- xyz` |
||||||
|
|
||||||
|
Builds an executable and send it to a device to run it with the argument `xyz`. |
||||||
|
|
||||||
|
* `cargo 3ds test --address 192.168.0.2 -- -- --test-arg 1` |
||||||
|
|
||||||
|
Builds a test executable and attempts to send it to a device with the |
||||||
|
address `192.168.0.2` and run it using the arguments `["--test-arg", "1"]`. |
||||||
|
|
||||||
|
* `cargo 3ds test --address 192.168.0.2 --verbose -- --test-arg 1` |
||||||
|
|
||||||
|
Build a test executable with `cargo test --verbose`, and attempts to send |
||||||
|
it to a device with the address `192.168.0.2` and run it using the arguments |
||||||
|
`["--test-arg", "1"]`. |
||||||
|
|
||||||
|
This works without two `--` instances because `--verbose` begins the set of |
||||||
|
`cargo` arguments and ends the set of 3DS-specific arguments. |
||||||
|
|
||||||
|
### Caveats |
||||||
|
|
||||||
|
Due to the fact that only one executable at a time can be sent with `3dslink`, |
||||||
|
by default only the "last" executable built will be used. If a `test` or `run` |
||||||
|
command builds more than one binary, you may need to filter it in order to run |
||||||
|
the executable you want. |
||||||
|
|
||||||
# Examples: |
Doc tests sort of work, but `cargo-3ds` uses a number of unstable cargo and |
||||||
`cargo +nightly 3ds build` \ |
rustdoc features to make them work, so the output won't be as pretty and will |
||||||
`cargo +nightly 3ds run --release` |
require some manual workarounds to actually run the tests and see output from them. |
||||||
|
For now, `cargo 3ds test --doc` will not build a 3dsx file or use `3dslink` at all. |
||||||
|
|
||||||
You can pass or not `--release` to build with debug symbols or not, and this works for both `build` and `run`. |
## License |
||||||
|
|
||||||
Any other parameters you pass after the command will be passed during the compiling stage to `cargo`. |
This project is distributed under the MIT license or the Apache-2.0 license. |
||||||
|
@ -0,0 +1,694 @@ |
|||||||
|
use std::fs; |
||||||
|
use std::io::Read; |
||||||
|
use std::process::Stdio; |
||||||
|
use std::sync::OnceLock; |
||||||
|
|
||||||
|
use cargo_metadata::Message; |
||||||
|
use clap::{Args, Parser, Subcommand}; |
||||||
|
|
||||||
|
use crate::{build_3dsx, build_smdh, cargo, get_metadata, link, print_command, CTRConfig}; |
||||||
|
|
||||||
|
#[derive(Parser, Debug)] |
||||||
|
#[command(name = "cargo", bin_name = "cargo")] |
||||||
|
pub enum Cargo { |
||||||
|
#[command(name = "3ds")] |
||||||
|
Input(Input), |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
#[command(version, about)] |
||||||
|
pub struct Input { |
||||||
|
#[command(subcommand)] |
||||||
|
pub cmd: CargoCmd, |
||||||
|
|
||||||
|
/// Print the exact commands `cargo-3ds` is running. Note that this does not
|
||||||
|
/// set the verbose flag for cargo itself. To set cargo's verbosity flag, add
|
||||||
|
/// `-- -v` to the end of the command line.
|
||||||
|
#[arg(long, short = 'v', global = true)] |
||||||
|
pub verbose: bool, |
||||||
|
|
||||||
|
/// Set cargo configuration on the command line. This is equivalent to
|
||||||
|
/// cargo's `--config` option.
|
||||||
|
#[arg(long, global = true)] |
||||||
|
pub config: Vec<String>, |
||||||
|
} |
||||||
|
|
||||||
|
/// Run a cargo command. COMMAND will be forwarded to the real
|
||||||
|
/// `cargo` with the appropriate arguments for the 3DS target.
|
||||||
|
///
|
||||||
|
/// If an unrecognized COMMAND is used, it will be passed through unmodified
|
||||||
|
/// to `cargo` with the appropriate flags set for the 3DS target.
|
||||||
|
#[derive(Subcommand, Debug)] |
||||||
|
#[command(allow_external_subcommands = true)] |
||||||
|
pub enum CargoCmd { |
||||||
|
/// Builds an executable suitable to run on a 3DS (3dsx).
|
||||||
|
Build(Build), |
||||||
|
|
||||||
|
/// Builds an executable and sends it to a device with `3dslink`.
|
||||||
|
Run(Run), |
||||||
|
|
||||||
|
/// Builds a test executable and sends it to a device with `3dslink`.
|
||||||
|
///
|
||||||
|
/// This can be used with `--test` for integration tests, or `--lib` for
|
||||||
|
/// unit tests (which require a custom test runner).
|
||||||
|
Test(Test), |
||||||
|
|
||||||
|
/// Sets up a new cargo project suitable to run on a 3DS.
|
||||||
|
New(New), |
||||||
|
|
||||||
|
// NOTE: it seems docstring + name for external subcommands are not rendered
|
||||||
|
// in help, but we might as well set them here in case a future version of clap
|
||||||
|
// does include them in help text.
|
||||||
|
/// Run any other `cargo` command with custom building tailored for the 3DS.
|
||||||
|
#[command(external_subcommand, name = "COMMAND")] |
||||||
|
Passthrough(Vec<String>), |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
pub struct RemainingArgs { |
||||||
|
/// Pass additional options through to the `cargo` command.
|
||||||
|
///
|
||||||
|
/// All arguments after the first `--`, or starting with the first unrecognized
|
||||||
|
/// option, will be passed through to `cargo` unmodified.
|
||||||
|
///
|
||||||
|
/// To pass arguments to an executable being run, a *second* `--` must be
|
||||||
|
/// used to disambiguate cargo arguments from executable arguments.
|
||||||
|
/// For example, `cargo 3ds run -- -- xyz` runs an executable with the argument
|
||||||
|
/// `xyz`.
|
||||||
|
#[arg(
|
||||||
|
trailing_var_arg = true, |
||||||
|
allow_hyphen_values = true, |
||||||
|
value_name = "CARGO_ARGS" |
||||||
|
)] |
||||||
|
args: Vec<String>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
pub struct Build { |
||||||
|
#[arg(from_global)] |
||||||
|
pub verbose: bool, |
||||||
|
|
||||||
|
// Passthrough cargo options.
|
||||||
|
#[command(flatten)] |
||||||
|
pub passthrough: RemainingArgs, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
pub struct Run { |
||||||
|
/// Specify the IP address of the device to send the executable to.
|
||||||
|
///
|
||||||
|
/// Corresponds to 3dslink's `--address` arg, which defaults to automatically
|
||||||
|
/// finding the device.
|
||||||
|
#[arg(long, short = 'a')] |
||||||
|
pub address: Option<std::net::Ipv4Addr>, |
||||||
|
|
||||||
|
/// Set the 0th argument of the executable when running it. Corresponds to
|
||||||
|
/// 3dslink's `--argv0` argument.
|
||||||
|
#[arg(long, short = '0')] |
||||||
|
pub argv0: Option<String>, |
||||||
|
|
||||||
|
/// Start the 3dslink server after sending the executable. Corresponds to
|
||||||
|
/// 3dslink's `--server` argument.
|
||||||
|
#[arg(long, short = 's', default_value_t = false)] |
||||||
|
pub server: bool, |
||||||
|
|
||||||
|
/// Set the number of tries when connecting to the device to send the executable.
|
||||||
|
/// Corresponds to 3dslink's `--retries` argument.
|
||||||
|
// Can't use `short = 'r'` because that would conflict with cargo's `--release/-r`
|
||||||
|
#[arg(long)] |
||||||
|
pub retries: Option<usize>, |
||||||
|
|
||||||
|
// Passthrough `cargo build` options.
|
||||||
|
#[command(flatten)] |
||||||
|
pub build_args: Build, |
||||||
|
|
||||||
|
#[arg(from_global)] |
||||||
|
config: Vec<String>, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
pub struct Test { |
||||||
|
/// If set, the built executable will not be sent to the device to run it.
|
||||||
|
#[arg(long)] |
||||||
|
pub no_run: bool, |
||||||
|
|
||||||
|
/// If set, documentation tests will be built instead of unit tests.
|
||||||
|
/// This implies `--no-run`, unless Cargo's `target.armv6k-nintendo-3ds.runner`
|
||||||
|
/// is configured.
|
||||||
|
#[arg(long)] |
||||||
|
pub doc: bool, |
||||||
|
|
||||||
|
// The test command uses a superset of the same arguments as Run.
|
||||||
|
#[command(flatten)] |
||||||
|
pub run_args: Run, |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Args, Debug)] |
||||||
|
pub struct New { |
||||||
|
/// Path of the new project.
|
||||||
|
#[arg(required = true)] |
||||||
|
pub path: String, |
||||||
|
|
||||||
|
// The test command uses a superset of the same arguments as Run.
|
||||||
|
#[command(flatten)] |
||||||
|
pub cargo_args: RemainingArgs, |
||||||
|
} |
||||||
|
|
||||||
|
impl CargoCmd { |
||||||
|
/// Returns the additional arguments run by the "official" cargo subcommand.
|
||||||
|
pub fn cargo_args(&self) -> Vec<String> { |
||||||
|
match self { |
||||||
|
CargoCmd::Build(build) => build.passthrough.cargo_args(), |
||||||
|
CargoCmd::Run(run) => run.build_args.passthrough.cargo_args(), |
||||||
|
CargoCmd::Test(test) => test.cargo_args(), |
||||||
|
CargoCmd::New(new) => { |
||||||
|
// We push the original path in the new command (we captured it in [`New`] to learn about the context)
|
||||||
|
let mut cargo_args = new.cargo_args.cargo_args(); |
||||||
|
cargo_args.push(new.path.clone()); |
||||||
|
|
||||||
|
cargo_args |
||||||
|
} |
||||||
|
CargoCmd::Passthrough(other) => other.clone().split_off(1), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns the cargo subcommand run by `cargo-3ds` when handling a [`CargoCmd`].
|
||||||
|
///
|
||||||
|
/// # Notes
|
||||||
|
///
|
||||||
|
/// This is not equivalent to the lowercase name of the [`CargoCmd`] variant.
|
||||||
|
/// Commands may use different commands under the hood to function (e.g. [`CargoCmd::Run`] uses `build`
|
||||||
|
/// if no custom runner is configured).
|
||||||
|
pub fn subcommand_name(&self) -> &str { |
||||||
|
match self { |
||||||
|
CargoCmd::Build(_) => "build", |
||||||
|
CargoCmd::Run(run) => { |
||||||
|
if run.use_custom_runner() { |
||||||
|
"run" |
||||||
|
} else { |
||||||
|
"build" |
||||||
|
} |
||||||
|
} |
||||||
|
CargoCmd::Test(_) => "test", |
||||||
|
CargoCmd::New(_) => "new", |
||||||
|
CargoCmd::Passthrough(cmd) => &cmd[0], |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Whether or not this command should compile any code, and thus needs import the custom environment configuration (e.g. target spec).
|
||||||
|
pub fn should_compile(&self) -> bool { |
||||||
|
matches!( |
||||||
|
self, |
||||||
|
Self::Build(_) | Self::Run(_) | Self::Test(_) | Self::Passthrough(_) |
||||||
|
) |
||||||
|
} |
||||||
|
|
||||||
|
/// Whether or not this command should build a 3DSX executable file.
|
||||||
|
pub fn should_build_3dsx(&self) -> bool { |
||||||
|
match self { |
||||||
|
Self::Build(_) | CargoCmd::Run(_) => true, |
||||||
|
&Self::Test(Test { doc, .. }) => { |
||||||
|
if doc { |
||||||
|
eprintln!("Documentation tests requested, no 3dsx will be built"); |
||||||
|
false |
||||||
|
} else { |
||||||
|
true |
||||||
|
} |
||||||
|
} |
||||||
|
_ => false, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Whether or not the resulting executable should be sent to the 3DS with
|
||||||
|
/// `3dslink`.
|
||||||
|
pub fn should_link_to_device(&self) -> bool { |
||||||
|
match self { |
||||||
|
Self::Test(Test { no_run: true, .. }) => false, |
||||||
|
Self::Run(run) | Self::Test(Test { run_args: run, .. }) => !run.use_custom_runner(), |
||||||
|
_ => false, |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
pub const DEFAULT_MESSAGE_FORMAT: &'static str = "json-render-diagnostics"; |
||||||
|
|
||||||
|
pub fn extract_message_format(&mut self) -> Result<Option<String>, String> { |
||||||
|
let cargo_args = match self { |
||||||
|
Self::Build(build) => &mut build.passthrough.args, |
||||||
|
Self::Run(run) => &mut run.build_args.passthrough.args, |
||||||
|
Self::New(new) => &mut new.cargo_args.args, |
||||||
|
Self::Test(test) => &mut test.run_args.build_args.passthrough.args, |
||||||
|
Self::Passthrough(args) => args, |
||||||
|
}; |
||||||
|
|
||||||
|
let format = Self::extract_message_format_from_args(cargo_args)?; |
||||||
|
if format.is_some() { |
||||||
|
return Ok(format); |
||||||
|
} |
||||||
|
|
||||||
|
if let Self::Test(Test { doc: true, .. }) = self { |
||||||
|
// We don't care about JSON output for doctests since we're not
|
||||||
|
// building any 3dsx etc. Just use the default output as it's more
|
||||||
|
// readable compared to DEFAULT_MESSAGE_FORMAT
|
||||||
|
Ok(Some(String::from("human"))) |
||||||
|
} else { |
||||||
|
Ok(None) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn extract_message_format_from_args( |
||||||
|
cargo_args: &mut Vec<String>, |
||||||
|
) -> Result<Option<String>, String> { |
||||||
|
// Checks for a position within the args where '--message-format' is located
|
||||||
|
if let Some(pos) = cargo_args |
||||||
|
.iter() |
||||||
|
.position(|s| s.starts_with("--message-format")) |
||||||
|
{ |
||||||
|
// Remove the arg from list so we don't pass anything twice by accident
|
||||||
|
let arg = cargo_args.remove(pos); |
||||||
|
|
||||||
|
// Allows for usage of '--message-format=<format>' and also using space separation.
|
||||||
|
// Check for a '=' delimiter and use the second half of the split as the format,
|
||||||
|
// otherwise remove next arg which is now at the same position as the original flag.
|
||||||
|
let format = if let Some((_, format)) = arg.split_once('=') { |
||||||
|
format.to_string() |
||||||
|
} else { |
||||||
|
// Also need to remove the argument to the --message-format option
|
||||||
|
cargo_args.remove(pos) |
||||||
|
}; |
||||||
|
|
||||||
|
// Non-json formats are not supported so the executable exits.
|
||||||
|
if format.starts_with("json") { |
||||||
|
Ok(Some(format)) |
||||||
|
} else { |
||||||
|
Err(String::from( |
||||||
|
"error: non-JSON `message-format` is not supported", |
||||||
|
)) |
||||||
|
} |
||||||
|
} else { |
||||||
|
Ok(None) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Runs the custom callback *after* the cargo command, depending on the type of command launched.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// - `cargo 3ds build` and other "build" commands will use their callbacks to build the final `.3dsx` file and link it.
|
||||||
|
/// - `cargo 3ds new` and other generic commands will use their callbacks to make 3ds-specific changes to the environment.
|
||||||
|
pub fn run_callback(&self, messages: &[Message]) { |
||||||
|
// Process the metadata only for commands that have it/use it
|
||||||
|
let config = if self.should_build_3dsx() { |
||||||
|
eprintln!("Getting metadata"); |
||||||
|
|
||||||
|
Some(get_metadata(messages)) |
||||||
|
} else { |
||||||
|
None |
||||||
|
}; |
||||||
|
|
||||||
|
// Run callback only for commands that use it
|
||||||
|
match self { |
||||||
|
Self::Build(cmd) => cmd.callback(&config), |
||||||
|
Self::Run(cmd) => cmd.callback(&config), |
||||||
|
Self::Test(cmd) => cmd.callback(&config), |
||||||
|
Self::New(cmd) => cmd.callback(), |
||||||
|
_ => (), |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl RemainingArgs { |
||||||
|
/// Get the args to be passed to `cargo`.
|
||||||
|
pub fn cargo_args(&self) -> Vec<String> { |
||||||
|
self.split_args().0 |
||||||
|
} |
||||||
|
|
||||||
|
/// Get the args to be passed to the executable itself (not `cargo`).
|
||||||
|
pub fn exe_args(&self) -> Vec<String> { |
||||||
|
self.split_args().1 |
||||||
|
} |
||||||
|
|
||||||
|
fn split_args(&self) -> (Vec<String>, Vec<String>) { |
||||||
|
let mut args = self.args.clone(); |
||||||
|
|
||||||
|
if let Some(split) = args.iter().position(|s| s == "--") { |
||||||
|
let second_half = args.split_off(split + 1); |
||||||
|
// take off the "--" arg we found, we'll add one later if needed
|
||||||
|
args.pop(); |
||||||
|
|
||||||
|
(args, second_half) |
||||||
|
} else { |
||||||
|
(args, Vec::new()) |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Build { |
||||||
|
/// Callback for `cargo 3ds build`.
|
||||||
|
///
|
||||||
|
/// This callback handles building the application as a `.3dsx` file.
|
||||||
|
fn callback(&self, config: &Option<CTRConfig>) { |
||||||
|
if let Some(config) = config { |
||||||
|
eprintln!("Building smdh: {}", config.path_smdh().display()); |
||||||
|
build_smdh(config, self.verbose); |
||||||
|
|
||||||
|
eprintln!("Building 3dsx: {}", config.path_3dsx().display()); |
||||||
|
build_3dsx(config, self.verbose); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Run { |
||||||
|
/// Get the args to pass to `3dslink` based on these options.
|
||||||
|
pub fn get_3dslink_args(&self) -> Vec<String> { |
||||||
|
let mut args = Vec::new(); |
||||||
|
|
||||||
|
if let Some(address) = self.address { |
||||||
|
args.extend(["--address".to_string(), address.to_string()]); |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(argv0) = &self.argv0 { |
||||||
|
args.extend(["--arg0".to_string(), argv0.clone()]); |
||||||
|
} |
||||||
|
|
||||||
|
if let Some(retries) = self.retries { |
||||||
|
args.extend(["--retries".to_string(), retries.to_string()]); |
||||||
|
} |
||||||
|
|
||||||
|
if self.server { |
||||||
|
args.push("--server".to_string()); |
||||||
|
} |
||||||
|
|
||||||
|
let exe_args = self.build_args.passthrough.exe_args(); |
||||||
|
if !exe_args.is_empty() { |
||||||
|
// For some reason 3dslink seems to want 2 instances of `--`, one
|
||||||
|
// in front of all of the args like this...
|
||||||
|
args.extend(["--args".to_string(), "--".to_string()]); |
||||||
|
|
||||||
|
let mut escaped = false; |
||||||
|
for arg in exe_args.iter().cloned() { |
||||||
|
if arg.starts_with('-') && !escaped { |
||||||
|
// And one before the first `-` arg that is passed in.
|
||||||
|
args.extend(["--".to_string(), arg]); |
||||||
|
escaped = true; |
||||||
|
} else { |
||||||
|
args.push(arg); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
args |
||||||
|
} |
||||||
|
|
||||||
|
/// Callback for `cargo 3ds run`.
|
||||||
|
///
|
||||||
|
/// This callback handles launching the application via `3dslink`.
|
||||||
|
fn callback(&self, config: &Option<CTRConfig>) { |
||||||
|
// Run the normal "build" callback
|
||||||
|
self.build_args.callback(config); |
||||||
|
|
||||||
|
if !self.use_custom_runner() { |
||||||
|
if let Some(cfg) = config { |
||||||
|
eprintln!("Running 3dslink"); |
||||||
|
link(cfg, self, self.build_args.verbose); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Returns whether the cargo environment has `target.armv6k-nintendo-3ds.runner`
|
||||||
|
/// configured. This will only be checked once during the lifetime of the program,
|
||||||
|
/// and takes into account the usual ways Cargo looks for its
|
||||||
|
/// [configuration](https://doc.rust-lang.org/cargo/reference/config.html):
|
||||||
|
///
|
||||||
|
/// - `.cargo/config.toml`
|
||||||
|
/// - Environment variables
|
||||||
|
/// - Command-line `--config` overrides
|
||||||
|
pub fn use_custom_runner(&self) -> bool { |
||||||
|
static HAS_RUNNER: OnceLock<bool> = OnceLock::new(); |
||||||
|
|
||||||
|
let &custom_runner_configured = HAS_RUNNER.get_or_init(|| { |
||||||
|
let mut cmd = cargo(&self.config); |
||||||
|
cmd.args([ |
||||||
|
// https://github.com/rust-lang/cargo/issues/9301
|
||||||
|
"-Z", |
||||||
|
"unstable-options", |
||||||
|
"config", |
||||||
|
"get", |
||||||
|
"target.armv6k-nintendo-3ds.runner", |
||||||
|
]) |
||||||
|
.stdout(Stdio::null()) |
||||||
|
.stderr(Stdio::null()); |
||||||
|
|
||||||
|
if self.build_args.verbose { |
||||||
|
print_command(&cmd); |
||||||
|
} |
||||||
|
|
||||||
|
// `cargo config get` exits zero if the config exists, or nonzero otherwise
|
||||||
|
cmd.status().map_or(false, |status| status.success()) |
||||||
|
}); |
||||||
|
|
||||||
|
if self.build_args.verbose { |
||||||
|
eprintln!( |
||||||
|
"Custom runner is {}configured", |
||||||
|
if custom_runner_configured { "" } else { "not " } |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
custom_runner_configured |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl Test { |
||||||
|
/// Callback for `cargo 3ds test`.
|
||||||
|
///
|
||||||
|
/// This callback handles launching the application via `3dslink`.
|
||||||
|
fn callback(&self, config: &Option<CTRConfig>) { |
||||||
|
if self.no_run { |
||||||
|
// If the tests don't have to run, use the "build" callback
|
||||||
|
self.run_args.build_args.callback(config); |
||||||
|
} else { |
||||||
|
// If the tests have to run, use the "run" callback
|
||||||
|
self.run_args.callback(config); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
fn should_run(&self) -> bool { |
||||||
|
self.run_args.use_custom_runner() && !self.no_run |
||||||
|
} |
||||||
|
|
||||||
|
/// The args to pass to the underlying `cargo test` command.
|
||||||
|
fn cargo_args(&self) -> Vec<String> { |
||||||
|
let mut cargo_args = self.run_args.build_args.passthrough.cargo_args(); |
||||||
|
|
||||||
|
// We can't run 3DS executables on the host, but we want to respect
|
||||||
|
// the user's "runner" configuration if set.
|
||||||
|
//
|
||||||
|
// If doctests were requested, `--no-run` will be rejected on the
|
||||||
|
// command line and must be set with RUSTDOCFLAGS instead:
|
||||||
|
// https://github.com/rust-lang/rust/issues/87022
|
||||||
|
|
||||||
|
if self.doc { |
||||||
|
cargo_args.extend([ |
||||||
|
"--doc".into(), |
||||||
|
// https://github.com/rust-lang/cargo/issues/7040
|
||||||
|
"-Z".into(), |
||||||
|
"doctest-xcompile".into(), |
||||||
|
]); |
||||||
|
} else if !self.should_run() { |
||||||
|
cargo_args.push("--no-run".into()); |
||||||
|
} |
||||||
|
|
||||||
|
cargo_args |
||||||
|
} |
||||||
|
|
||||||
|
/// Flags to pass to rustdoc via RUSTDOCFLAGS
|
||||||
|
pub(crate) fn rustdocflags(&self) -> &'static str { |
||||||
|
if self.should_run() { |
||||||
|
"" |
||||||
|
} else { |
||||||
|
// We don't support running doctests by default, but cargo doesn't like
|
||||||
|
// --no-run for doctests, so we have to plumb it in via RUSTDOCFLAGS
|
||||||
|
" --no-run" |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const TOML_CHANGES: &str = r#"ctru-rs = { git = "https://git.xenua.me/rust3ds/ctru-rs" } |
||||||
|
|
||||||
|
[package.metadata.cargo-3ds] |
||||||
|
romfs_dir = "romfs" |
||||||
|
"#; |
||||||
|
|
||||||
|
const CUSTOM_MAIN_RS: &str = r#"use ctru::prelude::*; |
||||||
|
|
||||||
|
fn main() { |
||||||
|
let apt = Apt::new().unwrap(); |
||||||
|
let mut hid = Hid::new().unwrap(); |
||||||
|
let gfx = Gfx::new().unwrap(); |
||||||
|
let _console = Console::new(gfx.top_screen.borrow_mut()); |
||||||
|
|
||||||
|
println!("Hello, World!"); |
||||||
|
println!("\x1b[29;16HPress Start to exit"); |
||||||
|
|
||||||
|
while apt.main_loop() { |
||||||
|
gfx.wait_for_vblank(); |
||||||
|
|
||||||
|
hid.scan_input(); |
||||||
|
if hid.keys_down().contains(KeyPad::START) { |
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
"#; |
||||||
|
|
||||||
|
impl New { |
||||||
|
/// Callback for `cargo 3ds new`.
|
||||||
|
///
|
||||||
|
/// This callback handles the custom environment modifications when creating a new 3DS project.
|
||||||
|
fn callback(&self) { |
||||||
|
// Commmit changes to the project only if is meant to be a binary
|
||||||
|
if self.cargo_args.args.contains(&"--lib".to_string()) { |
||||||
|
return; |
||||||
|
} |
||||||
|
|
||||||
|
// Attain a canonicalised path for the new project and it's TOML manifest
|
||||||
|
let project_path = fs::canonicalize(&self.path).unwrap(); |
||||||
|
let toml_path = project_path.join("Cargo.toml"); |
||||||
|
let romfs_path = project_path.join("romfs"); |
||||||
|
let main_rs_path = project_path.join("src/main.rs"); |
||||||
|
|
||||||
|
// Create the "romfs" directory
|
||||||
|
fs::create_dir(romfs_path).unwrap(); |
||||||
|
|
||||||
|
// Read the contents of `Cargo.toml` to a string
|
||||||
|
let mut buf = String::new(); |
||||||
|
fs::File::open(&toml_path) |
||||||
|
.unwrap() |
||||||
|
.read_to_string(&mut buf) |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
// Add the custom changes to the TOML
|
||||||
|
let buf = buf + TOML_CHANGES; |
||||||
|
fs::write(&toml_path, buf).unwrap(); |
||||||
|
|
||||||
|
// Add the custom changes to the main.rs file
|
||||||
|
fs::write(main_rs_path, CUSTOM_MAIN_RS).unwrap(); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[cfg(test)] |
||||||
|
mod tests { |
||||||
|
use clap::CommandFactory; |
||||||
|
|
||||||
|
use super::*; |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn verify_app() { |
||||||
|
Cargo::command().debug_assert(); |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn extract_format() { |
||||||
|
const CASES: &[(&[&str], Option<&str>)] = &[ |
||||||
|
(&["--foo", "--message-format=json", "bar"], Some("json")), |
||||||
|
(&["--foo", "--message-format", "json", "bar"], Some("json")), |
||||||
|
( |
||||||
|
&[ |
||||||
|
"--foo", |
||||||
|
"--message-format", |
||||||
|
"json-render-diagnostics", |
||||||
|
"bar", |
||||||
|
], |
||||||
|
Some("json-render-diagnostics"), |
||||||
|
), |
||||||
|
( |
||||||
|
&["--foo", "--message-format=json-render-diagnostics", "bar"], |
||||||
|
Some("json-render-diagnostics"), |
||||||
|
), |
||||||
|
(&["--foo", "bar"], None), |
||||||
|
]; |
||||||
|
|
||||||
|
for (args, expected) in CASES { |
||||||
|
let mut cmd = CargoCmd::Build(Build { |
||||||
|
passthrough: RemainingArgs { |
||||||
|
args: args.iter().map(ToString::to_string).collect(), |
||||||
|
}, |
||||||
|
verbose: false, |
||||||
|
}); |
||||||
|
|
||||||
|
assert_eq!( |
||||||
|
cmd.extract_message_format().unwrap(), |
||||||
|
expected.map(ToString::to_string) |
||||||
|
); |
||||||
|
|
||||||
|
if let CargoCmd::Build(build) = cmd { |
||||||
|
assert_eq!(build.passthrough.args, vec!["--foo", "bar"]); |
||||||
|
} else { |
||||||
|
unreachable!(); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn extract_format_err() { |
||||||
|
for args in [&["--message-format=foo"][..], &["--message-format", "foo"]] { |
||||||
|
let mut cmd = CargoCmd::Build(Build { |
||||||
|
passthrough: RemainingArgs { |
||||||
|
args: args.iter().map(ToString::to_string).collect(), |
||||||
|
}, |
||||||
|
verbose: false, |
||||||
|
}); |
||||||
|
|
||||||
|
assert!(cmd.extract_message_format().is_err()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[test] |
||||||
|
fn split_run_args() { |
||||||
|
struct TestParam { |
||||||
|
input: &'static [&'static str], |
||||||
|
expected_cargo: &'static [&'static str], |
||||||
|
expected_exe: &'static [&'static str], |
||||||
|
} |
||||||
|
|
||||||
|
for param in [ |
||||||
|
TestParam { |
||||||
|
input: &["--example", "hello-world", "--no-default-features"], |
||||||
|
expected_cargo: &["--example", "hello-world", "--no-default-features"], |
||||||
|
expected_exe: &[], |
||||||
|
}, |
||||||
|
TestParam { |
||||||
|
input: &["--example", "hello-world", "--", "--do-stuff", "foo"], |
||||||
|
expected_cargo: &["--example", "hello-world"], |
||||||
|
expected_exe: &["--do-stuff", "foo"], |
||||||
|
}, |
||||||
|
TestParam { |
||||||
|
input: &["--lib", "--", "foo"], |
||||||
|
expected_cargo: &["--lib"], |
||||||
|
expected_exe: &["foo"], |
||||||
|
}, |
||||||
|
TestParam { |
||||||
|
input: &["foo", "--", "bar"], |
||||||
|
expected_cargo: &["foo"], |
||||||
|
expected_exe: &["bar"], |
||||||
|
}, |
||||||
|
] { |
||||||
|
let input: Vec<&str> = ["cargo", "3ds", "run"] |
||||||
|
.iter() |
||||||
|
.chain(param.input) |
||||||
|
.copied() |
||||||
|
.collect(); |
||||||
|
|
||||||
|
dbg!(&input); |
||||||
|
let Cargo::Input(Input { |
||||||
|
cmd: CargoCmd::Run(Run { build_args, .. }), |
||||||
|
.. |
||||||
|
}) = Cargo::try_parse_from(input).unwrap_or_else(|e| panic!("{e}")) |
||||||
|
else { |
||||||
|
panic!("parsed as something other than `run` subcommand") |
||||||
|
}; |
||||||
|
|
||||||
|
assert_eq!(build_args.passthrough.cargo_args(), param.expected_cargo); |
||||||
|
assert_eq!(build_args.passthrough.exe_args(), param.expected_exe); |
||||||
|
} |
||||||
|
} |
||||||
|
} |
@ -0,0 +1,90 @@ |
|||||||
|
use std::error::Error; |
||||||
|
use std::io::Read; |
||||||
|
use std::process::{Command, Stdio}; |
||||||
|
|
||||||
|
use cargo_metadata::Target; |
||||||
|
use serde::Deserialize; |
||||||
|
|
||||||
|
use crate::print_command; |
||||||
|
|
||||||
|
/// In lieu of <https://github.com/oli-obk/cargo_metadata/issues/107>
|
||||||
|
/// and to avoid pulling in the real `cargo`
|
||||||
|
/// [data structures](https://docs.rs/cargo/latest/cargo/core/compiler/unit_graph/type.UnitGraph.html)
|
||||||
|
/// as a dependency, we define the subset of the build graph we care about.
|
||||||
|
#[derive(Deserialize)] |
||||||
|
pub struct UnitGraph { |
||||||
|
pub version: i32, |
||||||
|
pub units: Vec<Unit>, |
||||||
|
} |
||||||
|
|
||||||
|
impl UnitGraph { |
||||||
|
/// Collect the unit graph via Cargo's `--unit-graph` flag.
|
||||||
|
/// This runs the same command as the actual build, except nothing is actually
|
||||||
|
/// build and the graph is output instead.
|
||||||
|
///
|
||||||
|
/// See <https://doc.rust-lang.org/nightly/cargo/reference/unstable.html#unit-graph>.
|
||||||
|
pub fn from_cargo(cargo_cmd: &Command, verbose: bool) -> Result<Self, Box<dyn Error>> { |
||||||
|
// Since Command isn't Clone, copy it "by hand", by copying its args and envs
|
||||||
|
let mut cmd = Command::new(cargo_cmd.get_program()); |
||||||
|
|
||||||
|
let mut args = cargo_cmd.get_args(); |
||||||
|
cmd.args(args.next()) |
||||||
|
// These options must be added before any possible `--`, so the best
|
||||||
|
// place is to just stick them immediately after the first arg (subcommand)
|
||||||
|
.args(["-Z", "unstable-options", "--unit-graph"]) |
||||||
|
.args(args) |
||||||
|
.envs(cargo_cmd.get_envs().filter_map(|(k, v)| Some((k, v?)))) |
||||||
|
.stdout(Stdio::piped()) |
||||||
|
.stderr(Stdio::piped()); |
||||||
|
|
||||||
|
if verbose { |
||||||
|
print_command(&cmd); |
||||||
|
} |
||||||
|
|
||||||
|
let mut proc = cmd.spawn()?; |
||||||
|
let stdout = proc.stdout.take().unwrap(); |
||||||
|
let mut stderr = proc.stderr.take().unwrap(); |
||||||
|
|
||||||
|
let result: Self = serde_json::from_reader(stdout).map_err(|err| { |
||||||
|
let mut stderr_str = String::new(); |
||||||
|
let _ = stderr.read_to_string(&mut stderr_str); |
||||||
|
|
||||||
|
let _ = proc.wait(); |
||||||
|
format!("unable to parse `--unit-graph` json: {err}\nstderr: `{stderr_str}`") |
||||||
|
})?; |
||||||
|
|
||||||
|
let _status = proc.wait()?; |
||||||
|
// TODO: with cargo 1.74.0-nightly (b4ddf95ad 2023-09-18),
|
||||||
|
// `cargo run --unit-graph` panics at src/cargo/ops/cargo_run.rs:83:5
|
||||||
|
// It seems to have been fixed as of cargo 1.76.0-nightly (71cd3a926 2023-11-20)
|
||||||
|
// so maybe we can stop ignoring it once we bump the minimum toolchain version,
|
||||||
|
// and certainly we should once `--unit-graph` is ever stabilized.
|
||||||
|
//
|
||||||
|
// if !status.success() {
|
||||||
|
// return Err(format!("`cargo --unit-graph` exited with status {status:?}").into());
|
||||||
|
// }
|
||||||
|
|
||||||
|
if result.version == 1 { |
||||||
|
Ok(result) |
||||||
|
} else { |
||||||
|
Err(format!( |
||||||
|
"unknown `cargo --unit-graph` output version {}", |
||||||
|
result.version |
||||||
|
))? |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Deserialize)] |
||||||
|
pub struct Unit { |
||||||
|
pub target: Target, |
||||||
|
pub profile: Profile, |
||||||
|
} |
||||||
|
|
||||||
|
/// This struct is very similar to [`cargo_metadata::ArtifactProfile`], but seems
|
||||||
|
/// to have some slight differences so we define a different version. We only
|
||||||
|
/// really care about `debuginfo` anyway.
|
||||||
|
#[derive(Deserialize)] |
||||||
|
pub struct Profile { |
||||||
|
pub debuginfo: Option<u32>, |
||||||
|
} |
@ -0,0 +1,490 @@ |
|||||||
|
pub mod command; |
||||||
|
mod graph; |
||||||
|
|
||||||
|
use core::fmt; |
||||||
|
use std::ffi::OsStr; |
||||||
|
use std::io::{BufRead, BufReader}; |
||||||
|
use std::path::{Path, PathBuf}; |
||||||
|
use std::process::{Command, ExitStatus, Stdio}; |
||||||
|
use std::{env, io, process}; |
||||||
|
|
||||||
|
use cargo_metadata::{Message, MetadataCommand}; |
||||||
|
use command::{Input, Test}; |
||||||
|
use rustc_version::Channel; |
||||||
|
use semver::Version; |
||||||
|
use tee::TeeReader; |
||||||
|
|
||||||
|
use crate::command::{CargoCmd, Run}; |
||||||
|
use crate::graph::UnitGraph; |
||||||
|
|
||||||
|
/// Build a command using [`make_cargo_build_command`] and execute it,
|
||||||
|
/// parsing and returning the messages from the spawned process.
|
||||||
|
///
|
||||||
|
/// For commands that produce an executable output, this function will build the
|
||||||
|
/// `.elf` binary that can be used to create other 3ds files.
|
||||||
|
pub fn run_cargo(input: &Input, message_format: Option<String>) -> (ExitStatus, Vec<Message>) { |
||||||
|
let mut command = make_cargo_command(input, &message_format); |
||||||
|
|
||||||
|
if input.cmd.should_compile() { |
||||||
|
let libctru = if should_use_ctru_debuginfo(&command, input.verbose) { |
||||||
|
"ctrud" |
||||||
|
} else { |
||||||
|
"ctru" |
||||||
|
}; |
||||||
|
|
||||||
|
let rustflags = command |
||||||
|
.get_envs() |
||||||
|
.find(|(var, _)| var == &OsStr::new("RUSTFLAGS")) |
||||||
|
.and_then(|(_, flags)| flags) |
||||||
|
.unwrap_or_default() |
||||||
|
.to_string_lossy(); |
||||||
|
|
||||||
|
let rustflags = format!("{rustflags} -l{libctru}"); |
||||||
|
|
||||||
|
command.env("RUSTFLAGS", rustflags); |
||||||
|
} |
||||||
|
|
||||||
|
if input.verbose { |
||||||
|
print_command(&command); |
||||||
|
} |
||||||
|
|
||||||
|
let mut process = command.spawn().unwrap(); |
||||||
|
let command_stdout = process.stdout.take().unwrap(); |
||||||
|
|
||||||
|
let mut tee_reader; |
||||||
|
let mut stdout_reader; |
||||||
|
|
||||||
|
let buf_reader: &mut dyn BufRead = match (message_format, &input.cmd) { |
||||||
|
// The user presumably cares about the message format if set, so we should
|
||||||
|
// copy stuff to stdout like they expect. We can still extract the executable
|
||||||
|
// information out of it that we need for 3dsxtool etc.
|
||||||
|
(Some(_), _) | |
||||||
|
// Rustdoc unfortunately prints to stdout for compile errors, so
|
||||||
|
// we also use a tee when building doc tests too.
|
||||||
|
// Possibly related: https://github.com/rust-lang/rust/issues/75135
|
||||||
|
(None, CargoCmd::Test(Test { doc: true, .. })) => { |
||||||
|
tee_reader = BufReader::new(TeeReader::new(command_stdout, io::stdout())); |
||||||
|
&mut tee_reader |
||||||
|
} |
||||||
|
_ => { |
||||||
|
stdout_reader = BufReader::new(command_stdout); |
||||||
|
&mut stdout_reader |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
let messages = Message::parse_stream(buf_reader) |
||||||
|
.collect::<io::Result<_>>() |
||||||
|
.unwrap(); |
||||||
|
|
||||||
|
(process.wait().unwrap(), messages) |
||||||
|
} |
||||||
|
|
||||||
|
/// Ensure that we use the same `-lctru[d]` flag that `ctru-sys` is using in its build.
|
||||||
|
fn should_use_ctru_debuginfo(cargo_cmd: &Command, verbose: bool) -> bool { |
||||||
|
match UnitGraph::from_cargo(cargo_cmd, verbose) { |
||||||
|
Ok(unit_graph) => { |
||||||
|
let Some(unit) = unit_graph |
||||||
|
.units |
||||||
|
.iter() |
||||||
|
.find(|unit| unit.target.name == "ctru-sys") |
||||||
|
else { |
||||||
|
eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: `ctru-sys` not found"); |
||||||
|
return false; |
||||||
|
}; |
||||||
|
|
||||||
|
let debuginfo = unit.profile.debuginfo.unwrap_or(0); |
||||||
|
debuginfo > 0 |
||||||
|
} |
||||||
|
Err(err) => { |
||||||
|
eprintln!("Warning: unable to check if `ctru` debuginfo should be linked: {err}"); |
||||||
|
false |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Create a cargo command based on the context.
|
||||||
|
///
|
||||||
|
/// For "build" commands (which compile code, such as `cargo 3ds build` or `cargo 3ds clippy`),
|
||||||
|
/// if there is no pre-built std detected in the sysroot, `build-std` will be used instead.
|
||||||
|
pub fn make_cargo_command(input: &Input, message_format: &Option<String>) -> Command { |
||||||
|
let devkitpro = |
||||||
|
env::var("DEVKITPRO").expect("DEVKITPRO is not defined as an environment variable"); |
||||||
|
// TODO: should we actually prepend the user's RUSTFLAGS for linking order? not sure
|
||||||
|
let rustflags = |
||||||
|
env::var("RUSTFLAGS").unwrap_or_default() + &format!(" -L{devkitpro}/libctru/lib"); |
||||||
|
|
||||||
|
let cargo_cmd = &input.cmd; |
||||||
|
|
||||||
|
let mut command = cargo(&input.config); |
||||||
|
command |
||||||
|
.arg(cargo_cmd.subcommand_name()) |
||||||
|
.env("RUSTFLAGS", rustflags); |
||||||
|
|
||||||
|
// Any command that needs to compile code will run under this environment.
|
||||||
|
// Even `clippy` and `check` need this kind of context, so we'll just assume any other `Passthrough` command uses it too.
|
||||||
|
if cargo_cmd.should_compile() { |
||||||
|
command |
||||||
|
.arg("--target") |
||||||
|
.arg("armv6k-nintendo-3ds") |
||||||
|
.arg("--message-format") |
||||||
|
.arg( |
||||||
|
message_format |
||||||
|
.as_deref() |
||||||
|
.unwrap_or(CargoCmd::DEFAULT_MESSAGE_FORMAT), |
||||||
|
); |
||||||
|
|
||||||
|
let sysroot = find_sysroot(); |
||||||
|
if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { |
||||||
|
eprintln!("No pre-build std found, using build-std"); |
||||||
|
// Always building the test crate is not ideal, but we don't know if the
|
||||||
|
// crate being built uses #![feature(test)], so we build it just in case.
|
||||||
|
command.arg("-Z").arg("build-std=std,test"); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
if let CargoCmd::Test(test) = cargo_cmd { |
||||||
|
// RUSTDOCFLAGS is simply ignored if --doc wasn't passed, so we always set it.
|
||||||
|
let rustdoc_flags = std::env::var("RUSTDOCFLAGS").unwrap_or_default() + test.rustdocflags(); |
||||||
|
command.env("RUSTDOCFLAGS", rustdoc_flags); |
||||||
|
} |
||||||
|
|
||||||
|
command.args(cargo_cmd.cargo_args()); |
||||||
|
|
||||||
|
if let CargoCmd::Run(run) | CargoCmd::Test(Test { run_args: run, .. }) = &cargo_cmd { |
||||||
|
if run.use_custom_runner() { |
||||||
|
command |
||||||
|
.arg("--") |
||||||
|
.args(run.build_args.passthrough.exe_args()); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
command |
||||||
|
.stdout(Stdio::piped()) |
||||||
|
.stdin(Stdio::inherit()) |
||||||
|
.stderr(Stdio::inherit()); |
||||||
|
|
||||||
|
command |
||||||
|
} |
||||||
|
|
||||||
|
/// Build a `cargo` command with the given `--config` flags.
|
||||||
|
fn cargo(config: &[String]) -> Command { |
||||||
|
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); |
||||||
|
let mut cmd = Command::new(cargo); |
||||||
|
cmd.args(config.iter().map(|cfg| format!("--config={cfg}"))); |
||||||
|
cmd |
||||||
|
} |
||||||
|
|
||||||
|
fn print_command(command: &Command) { |
||||||
|
let mut cmd_str = vec![command.get_program().to_string_lossy().to_string()]; |
||||||
|
cmd_str.extend(command.get_args().map(|s| s.to_string_lossy().to_string())); |
||||||
|
|
||||||
|
eprintln!("Running command:"); |
||||||
|
for (k, v) in command.get_envs() { |
||||||
|
let v = v.map(|v| v.to_string_lossy().to_string()); |
||||||
|
eprintln!( |
||||||
|
" {}={} \\", |
||||||
|
k.to_string_lossy(), |
||||||
|
v.map_or_else(String::new, |s| shlex::quote(&s).to_string()) |
||||||
|
); |
||||||
|
} |
||||||
|
eprintln!(" {}\n", shlex::join(cmd_str.iter().map(String::as_str))); |
||||||
|
} |
||||||
|
|
||||||
|
/// Finds the sysroot path of the current toolchain
|
||||||
|
pub fn find_sysroot() -> PathBuf { |
||||||
|
let sysroot = env::var("SYSROOT").ok().unwrap_or_else(|| { |
||||||
|
let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); |
||||||
|
|
||||||
|
let output = Command::new(&rustc) |
||||||
|
.arg("--print") |
||||||
|
.arg("sysroot") |
||||||
|
.output() |
||||||
|
.unwrap_or_else(|_| panic!("Failed to run `{rustc} -- print sysroot`")); |
||||||
|
String::from_utf8(output.stdout).expect("Failed to parse sysroot path into a UTF-8 string") |
||||||
|
}); |
||||||
|
|
||||||
|
PathBuf::from(sysroot.trim()) |
||||||
|
} |
||||||
|
|
||||||
|
/// Checks the current rust version and channel.
|
||||||
|
/// Exits if the minimum requirement is not met.
|
||||||
|
pub fn check_rust_version() { |
||||||
|
let rustc_version = rustc_version::version_meta().unwrap(); |
||||||
|
|
||||||
|
if rustc_version.channel > Channel::Nightly { |
||||||
|
eprintln!("cargo-3ds requires a nightly rustc version."); |
||||||
|
eprintln!( |
||||||
|
"Please run `rustup override set nightly` to use nightly in the \ |
||||||
|
current directory, or use `cargo +nightly 3ds` to use it for a \ |
||||||
|
single invocation." |
||||||
|
); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
let old_version = MINIMUM_RUSTC_VERSION |
||||||
|
> Version { |
||||||
|
// Remove `-nightly` pre-release tag for comparison.
|
||||||
|
pre: semver::Prerelease::EMPTY, |
||||||
|
..rustc_version.semver.clone() |
||||||
|
}; |
||||||
|
|
||||||
|
let old_commit = match rustc_version.commit_date { |
||||||
|
None => false, |
||||||
|
Some(date) => { |
||||||
|
MINIMUM_COMMIT_DATE |
||||||
|
> CommitDate::parse(&date).expect("could not parse `rustc --version` commit date") |
||||||
|
} |
||||||
|
}; |
||||||
|
|
||||||
|
if old_version || old_commit { |
||||||
|
eprintln!("cargo-3ds requires rustc nightly version >= {MINIMUM_COMMIT_DATE}"); |
||||||
|
eprintln!("Please run `rustup update nightly` to upgrade your nightly version"); |
||||||
|
|
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Parses messages returned by "build" cargo commands (such as `cargo 3ds build` or `cargo 3ds run`).
|
||||||
|
/// The returned [`CTRConfig`] is then used for further building in and execution
|
||||||
|
/// in [`build_smdh`], [`build_3dsx`], and [`link`].
|
||||||
|
pub fn get_metadata(messages: &[Message]) -> CTRConfig { |
||||||
|
let metadata = MetadataCommand::new() |
||||||
|
.no_deps() |
||||||
|
.exec() |
||||||
|
.expect("Failed to get cargo metadata"); |
||||||
|
|
||||||
|
let mut package = None; |
||||||
|
let mut artifact = None; |
||||||
|
|
||||||
|
// Extract the final built executable. We may want to fail in cases where
|
||||||
|
// multiple executables, or none, were built?
|
||||||
|
for message in messages.iter().rev() { |
||||||
|
if let Message::CompilerArtifact(art) = message { |
||||||
|
if art.executable.is_some() { |
||||||
|
package = Some(metadata[&art.package_id].clone()); |
||||||
|
artifact = Some(art.clone()); |
||||||
|
|
||||||
|
break; |
||||||
|
} |
||||||
|
} |
||||||
|
} |
||||||
|
if package.is_none() || artifact.is_none() { |
||||||
|
eprintln!("No executable found from build command output!"); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
let (package, artifact) = (package.unwrap(), artifact.unwrap()); |
||||||
|
|
||||||
|
let mut icon = String::from("./icon.png"); |
||||||
|
|
||||||
|
if !Path::new(&icon).exists() { |
||||||
|
icon = format!( |
||||||
|
"{}/libctru/default_icon.png", |
||||||
|
env::var("DEVKITPRO").unwrap() |
||||||
|
); |
||||||
|
} |
||||||
|
|
||||||
|
// for now assume a single "kind" since we only support one output artifact
|
||||||
|
let name = match artifact.target.kind[0].as_ref() { |
||||||
|
"bin" | "lib" | "rlib" | "dylib" if artifact.target.test => { |
||||||
|
format!("{} tests", artifact.target.name) |
||||||
|
} |
||||||
|
"example" => { |
||||||
|
format!("{} - {} example", artifact.target.name, package.name) |
||||||
|
} |
||||||
|
_ => artifact.target.name, |
||||||
|
}; |
||||||
|
|
||||||
|
let author = match package.authors.as_slice() { |
||||||
|
[name, ..] => name.clone(), |
||||||
|
[] => String::from("Unspecified Author"), // as standard with the devkitPRO toolchain
|
||||||
|
}; |
||||||
|
|
||||||
|
CTRConfig { |
||||||
|
name, |
||||||
|
author, |
||||||
|
description: package |
||||||
|
.description |
||||||
|
.clone() |
||||||
|
.unwrap_or_else(|| String::from("Homebrew Application")), |
||||||
|
icon, |
||||||
|
target_path: artifact.executable.unwrap().into(), |
||||||
|
cargo_manifest_path: package.manifest_path.into(), |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Builds the smdh using `smdhtool`.
|
||||||
|
/// This will fail if `smdhtool` is not within the running directory or in a directory found in $PATH
|
||||||
|
pub fn build_smdh(config: &CTRConfig, verbose: bool) { |
||||||
|
let mut command = Command::new("smdhtool"); |
||||||
|
command |
||||||
|
.arg("--create") |
||||||
|
.arg(&config.name) |
||||||
|
.arg(&config.description) |
||||||
|
.arg(&config.author) |
||||||
|
.arg(&config.icon) |
||||||
|
.arg(config.path_smdh()) |
||||||
|
.stdin(Stdio::inherit()) |
||||||
|
.stdout(Stdio::inherit()) |
||||||
|
.stderr(Stdio::inherit()); |
||||||
|
|
||||||
|
if verbose { |
||||||
|
print_command(&command); |
||||||
|
} |
||||||
|
|
||||||
|
let mut process = command |
||||||
|
.spawn() |
||||||
|
.expect("smdhtool command failed, most likely due to 'smdhtool' not being in $PATH"); |
||||||
|
|
||||||
|
let status = process.wait().unwrap(); |
||||||
|
|
||||||
|
if !status.success() { |
||||||
|
process::exit(status.code().unwrap_or(1)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Builds the 3dsx using `3dsxtool`.
|
||||||
|
/// This will fail if `3dsxtool` is not within the running directory or in a directory found in $PATH
|
||||||
|
pub fn build_3dsx(config: &CTRConfig, verbose: bool) { |
||||||
|
let mut command = Command::new("3dsxtool"); |
||||||
|
command |
||||||
|
.arg(&config.target_path) |
||||||
|
.arg(config.path_3dsx()) |
||||||
|
.arg(format!("--smdh={}", config.path_smdh().to_string_lossy())); |
||||||
|
|
||||||
|
// If romfs directory exists, automatically include it
|
||||||
|
let (romfs_path, is_default_romfs) = get_romfs_path(config); |
||||||
|
if romfs_path.is_dir() { |
||||||
|
eprintln!("Adding RomFS from {}", romfs_path.display()); |
||||||
|
command.arg(format!("--romfs={}", romfs_path.to_string_lossy())); |
||||||
|
} else if !is_default_romfs { |
||||||
|
eprintln!( |
||||||
|
"Could not find configured RomFS dir: {}", |
||||||
|
romfs_path.display() |
||||||
|
); |
||||||
|
process::exit(1); |
||||||
|
} |
||||||
|
|
||||||
|
if verbose { |
||||||
|
print_command(&command); |
||||||
|
} |
||||||
|
|
||||||
|
let mut process = command |
||||||
|
.stdin(Stdio::inherit()) |
||||||
|
.stdout(Stdio::inherit()) |
||||||
|
.stderr(Stdio::inherit()) |
||||||
|
.spawn() |
||||||
|
.expect("3dsxtool command failed, most likely due to '3dsxtool' not being in $PATH"); |
||||||
|
|
||||||
|
let status = process.wait().unwrap(); |
||||||
|
|
||||||
|
if !status.success() { |
||||||
|
process::exit(status.code().unwrap_or(1)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Link the generated 3dsx to a 3ds to execute and test using `3dslink`.
|
||||||
|
/// This will fail if `3dslink` is not within the running directory or in a directory found in $PATH
|
||||||
|
pub fn link(config: &CTRConfig, run_args: &Run, verbose: bool) { |
||||||
|
let mut command = Command::new("3dslink"); |
||||||
|
command |
||||||
|
.arg(config.path_3dsx()) |
||||||
|
.args(run_args.get_3dslink_args()) |
||||||
|
.stdin(Stdio::inherit()) |
||||||
|
.stdout(Stdio::inherit()) |
||||||
|
.stderr(Stdio::inherit()); |
||||||
|
|
||||||
|
if verbose { |
||||||
|
print_command(&command); |
||||||
|
} |
||||||
|
|
||||||
|
let status = command.spawn().unwrap().wait().unwrap(); |
||||||
|
|
||||||
|
if !status.success() { |
||||||
|
process::exit(status.code().unwrap_or(1)); |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
/// Read the `RomFS` path from the Cargo manifest. If it's unset, use the default.
|
||||||
|
/// The returned boolean is true when the default is used.
|
||||||
|
pub fn get_romfs_path(config: &CTRConfig) -> (PathBuf, bool) { |
||||||
|
let manifest_path = &config.cargo_manifest_path; |
||||||
|
let manifest_str = std::fs::read_to_string(manifest_path) |
||||||
|
.unwrap_or_else(|e| panic!("Could not open {}: {e}", manifest_path.display())); |
||||||
|
let manifest_data: toml::Value = |
||||||
|
toml::de::from_str(&manifest_str).expect("Could not parse Cargo manifest as TOML"); |
||||||
|
|
||||||
|
// Find the romfs setting and compute the path
|
||||||
|
let mut is_default = false; |
||||||
|
let romfs_dir_setting = manifest_data |
||||||
|
.as_table() |
||||||
|
.and_then(|table| table.get("package")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("metadata")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("cargo-3ds")) |
||||||
|
.and_then(toml::Value::as_table) |
||||||
|
.and_then(|table| table.get("romfs_dir")) |
||||||
|
.and_then(toml::Value::as_str) |
||||||
|
.unwrap_or_else(|| { |
||||||
|
is_default = true; |
||||||
|
"romfs" |
||||||
|
}); |
||||||
|
let mut romfs_path = manifest_path.clone(); |
||||||
|
romfs_path.pop(); // Pop Cargo.toml
|
||||||
|
romfs_path.push(romfs_dir_setting); |
||||||
|
|
||||||
|
(romfs_path, is_default) |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Default)] |
||||||
|
pub struct CTRConfig { |
||||||
|
name: String, |
||||||
|
author: String, |
||||||
|
description: String, |
||||||
|
icon: String, |
||||||
|
target_path: PathBuf, |
||||||
|
cargo_manifest_path: PathBuf, |
||||||
|
} |
||||||
|
|
||||||
|
impl CTRConfig { |
||||||
|
pub fn path_3dsx(&self) -> PathBuf { |
||||||
|
self.target_path.with_extension("3dsx") |
||||||
|
} |
||||||
|
|
||||||
|
pub fn path_smdh(&self) -> PathBuf { |
||||||
|
self.target_path.with_extension("smdh") |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
#[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] |
||||||
|
pub struct CommitDate { |
||||||
|
year: i32, |
||||||
|
month: i32, |
||||||
|
day: i32, |
||||||
|
} |
||||||
|
|
||||||
|
impl CommitDate { |
||||||
|
fn parse(date: &str) -> Option<Self> { |
||||||
|
let mut iter = date.split('-'); |
||||||
|
|
||||||
|
let year = iter.next()?.parse().ok()?; |
||||||
|
let month = iter.next()?.parse().ok()?; |
||||||
|
let day = iter.next()?.parse().ok()?; |
||||||
|
|
||||||
|
Some(Self { year, month, day }) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
impl fmt::Display for CommitDate { |
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
||||||
|
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day) |
||||||
|
} |
||||||
|
} |
||||||
|
|
||||||
|
const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { |
||||||
|
year: 2023, |
||||||
|
month: 5, |
||||||
|
day: 31, |
||||||
|
}; |
||||||
|
const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 70, 0); |
@ -1,501 +1,27 @@ |
|||||||
use cargo_metadata::{Message, MetadataCommand}; |
use std::process; |
||||||
use rustc_version::{Channel, Version}; |
|
||||||
use std::io::{BufRead, BufReader}; |
|
||||||
use std::path::{Path, PathBuf}; |
|
||||||
use std::process::ExitStatus; |
|
||||||
use std::{ |
|
||||||
env, fmt, io, |
|
||||||
process::{self, Command, Stdio}, |
|
||||||
}; |
|
||||||
use tee::TeeReader; |
|
||||||
|
|
||||||
#[derive(serde_derive::Deserialize, Default)] |
use cargo_3ds::command::Cargo; |
||||||
struct CTRConfig { |
use cargo_3ds::{check_rust_version, run_cargo}; |
||||||
name: String, |
use clap::Parser; |
||||||
author: String, |
|
||||||
description: String, |
|
||||||
icon: String, |
|
||||||
target_path: PathBuf, |
|
||||||
cargo_manifest_path: PathBuf, |
|
||||||
} |
|
||||||
|
|
||||||
impl CTRConfig { |
|
||||||
fn path_3dsx(&self) -> PathBuf { |
|
||||||
self.target_path.with_extension("3dsx") |
|
||||||
} |
|
||||||
|
|
||||||
fn path_smdh(&self) -> PathBuf { |
|
||||||
self.target_path.with_extension("smdh") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
#[derive(Ord, PartialOrd, PartialEq, Eq, Debug)] |
|
||||||
struct CommitDate { |
|
||||||
year: i32, |
|
||||||
month: i32, |
|
||||||
day: i32, |
|
||||||
} |
|
||||||
|
|
||||||
impl CommitDate { |
|
||||||
fn parse(date: &str) -> Option<Self> { |
|
||||||
let mut iter = date.split('-'); |
|
||||||
|
|
||||||
let year = iter.next()?.parse().ok()?; |
|
||||||
let month = iter.next()?.parse().ok()?; |
|
||||||
let day = iter.next()?.parse().ok()?; |
|
||||||
|
|
||||||
Some(Self { year, month, day }) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
impl fmt::Display for CommitDate { |
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { |
|
||||||
write!(f, "{:04}-{:02}-{:02}", self.year, self.month, self.day) |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
const MINIMUM_COMMIT_DATE: CommitDate = CommitDate { |
|
||||||
year: 2022, |
|
||||||
month: 6, |
|
||||||
day: 15, |
|
||||||
}; |
|
||||||
const MINIMUM_RUSTC_VERSION: Version = Version::new(1, 63, 0); |
|
||||||
|
|
||||||
fn main() { |
fn main() { |
||||||
check_rust_version(); |
check_rust_version(); |
||||||
|
|
||||||
if env::args().any(|arg| arg == "--help" || arg == "-h") { |
let Cargo::Input(mut input) = Cargo::parse(); |
||||||
print_usage(&mut io::stdout()); |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
// Get the command and collect the remaining arguments
|
|
||||||
let cargo_command = CargoCommand::from_args().unwrap_or_else(|| { |
|
||||||
print_usage(&mut io::stderr()); |
|
||||||
process::exit(2) |
|
||||||
}); |
|
||||||
|
|
||||||
eprintln!("Running Cargo"); |
|
||||||
let (status, messages) = cargo_command.build_elf(); |
|
||||||
if !status.success() { |
|
||||||
process::exit(status.code().unwrap_or(1)); |
|
||||||
} |
|
||||||
|
|
||||||
if !cargo_command.should_build_3dsx() { |
|
||||||
return; |
|
||||||
} |
|
||||||
|
|
||||||
eprintln!("Getting metadata"); |
|
||||||
let app_conf = get_metadata(&messages); |
|
||||||
|
|
||||||
eprintln!("Building smdh:{}", app_conf.path_smdh().display()); |
|
||||||
build_smdh(&app_conf); |
|
||||||
|
|
||||||
eprintln!("Building 3dsx: {}", app_conf.path_3dsx().display()); |
|
||||||
build_3dsx(&app_conf); |
|
||||||
|
|
||||||
if cargo_command.should_link { |
|
||||||
eprintln!("Running 3dslink"); |
|
||||||
link(&app_conf); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
struct CargoCommand { |
|
||||||
command: String, |
|
||||||
should_link: bool, |
|
||||||
args: Vec<String>, |
|
||||||
message_format: String, |
|
||||||
} |
|
||||||
|
|
||||||
impl CargoCommand { |
|
||||||
const DEFAULT_MESSAGE_FORMAT: &'static str = "json-render-diagnostics"; |
|
||||||
|
|
||||||
fn from_args() -> Option<Self> { |
|
||||||
// Skip `cargo 3ds`. `cargo-3ds` isn't supported for now
|
|
||||||
let mut args = env::args().skip(2); |
|
||||||
|
|
||||||
let command = args.next()?; |
let message_format = match input.cmd.extract_message_format() { |
||||||
let mut remaining_args: Vec<String> = args.collect(); |
Ok(fmt) => fmt, |
||||||
|
Err(msg) => { |
||||||
let (command, should_link) = match command.as_str() { |
eprintln!("{msg}"); |
||||||
"run" => ("build".to_string(), true), |
process::exit(1) |
||||||
"test" => { |
|
||||||
let no_run = String::from("--no-run"); |
|
||||||
|
|
||||||
if remaining_args.contains(&no_run) { |
|
||||||
(command, false) |
|
||||||
} else { |
|
||||||
remaining_args.push(no_run); |
|
||||||
(command, true) |
|
||||||
} |
|
||||||
} |
|
||||||
_ => (command, false), |
|
||||||
}; |
|
||||||
|
|
||||||
let message_format = match Self::extract_message_format(&mut remaining_args) { |
|
||||||
Some(format) => { |
|
||||||
if !format.starts_with("json") { |
|
||||||
eprintln!("error: non-JSON `message-format` is not supported"); |
|
||||||
process::exit(1); |
|
||||||
} |
|
||||||
format |
|
||||||
} |
|
||||||
None => Self::DEFAULT_MESSAGE_FORMAT.to_string(), |
|
||||||
}; |
|
||||||
|
|
||||||
Some(Self { |
|
||||||
command, |
|
||||||
should_link, |
|
||||||
args: remaining_args, |
|
||||||
message_format, |
|
||||||
}) |
|
||||||
} |
|
||||||
|
|
||||||
fn extract_message_format(args: &mut Vec<String>) -> Option<String> { |
|
||||||
for (i, arg) in args.iter().enumerate() { |
|
||||||
if arg.starts_with("--message-format") { |
|
||||||
return { |
|
||||||
let arg = args.remove(i); |
|
||||||
|
|
||||||
if let Some((_, format)) = arg.split_once('=') { |
|
||||||
Some(format.to_string()) |
|
||||||
} else { |
|
||||||
Some(args.remove(i)) |
|
||||||
} |
} |
||||||
}; |
}; |
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
None |
|
||||||
} |
|
||||||
|
|
||||||
fn build_elf(&self) -> (ExitStatus, Vec<Message>) { |
|
||||||
let mut command = self.make_cargo_build_command(); |
|
||||||
let mut process = command.spawn().unwrap(); |
|
||||||
let command_stdout = process.stdout.take().unwrap(); |
|
||||||
|
|
||||||
let mut tee_reader; |
let (status, messages) = run_cargo(&input, message_format); |
||||||
let mut stdout_reader; |
|
||||||
|
|
||||||
let buf_reader: &mut dyn BufRead = if self.message_format == Self::DEFAULT_MESSAGE_FORMAT { |
|
||||||
stdout_reader = BufReader::new(command_stdout); |
|
||||||
&mut stdout_reader |
|
||||||
} else { |
|
||||||
// The user presumably cares about the message format, so we should
|
|
||||||
// copy stuff to stdout like they expect. We can still extract the executable
|
|
||||||
// information out of it that we need for 3dsxtool etc.
|
|
||||||
tee_reader = BufReader::new(TeeReader::new(command_stdout, io::stdout())); |
|
||||||
&mut tee_reader |
|
||||||
}; |
|
||||||
|
|
||||||
let messages = Message::parse_stream(buf_reader) |
|
||||||
.collect::<io::Result<_>>() |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
(process.wait().unwrap(), messages) |
|
||||||
} |
|
||||||
|
|
||||||
/// Create the cargo build command, but don't execute it.
|
|
||||||
/// If there is no pre-built std detected in the sysroot, `build-std` is used.
|
|
||||||
fn make_cargo_build_command(&self) -> Command { |
|
||||||
let rustflags = env::var("RUSTFLAGS").unwrap_or_default() |
|
||||||
+ &format!(" -L{}/libctru/lib -lctru", env::var("DEVKITPRO").unwrap()); |
|
||||||
let cargo = env::var("CARGO").unwrap_or_else(|_| "cargo".to_string()); |
|
||||||
let sysroot = Self::find_sysroot(); |
|
||||||
let mut command = Command::new(cargo); |
|
||||||
|
|
||||||
if !sysroot.join("lib/rustlib/armv6k-nintendo-3ds").exists() { |
|
||||||
eprintln!("No pre-built std found, using build-std"); |
|
||||||
command.arg("-Z").arg("build-std"); |
|
||||||
} |
|
||||||
|
|
||||||
command |
|
||||||
.env("RUSTFLAGS", rustflags) |
|
||||||
.arg(&self.command) |
|
||||||
.arg("--target") |
|
||||||
.arg("armv6k-nintendo-3ds") |
|
||||||
.arg("--message-format") |
|
||||||
.arg(&self.message_format) |
|
||||||
.args(&self.args) |
|
||||||
.stdout(Stdio::piped()) |
|
||||||
.stdin(Stdio::inherit()) |
|
||||||
.stderr(Stdio::inherit()); |
|
||||||
|
|
||||||
command |
|
||||||
} |
|
||||||
|
|
||||||
/// Get the compiler's sysroot path
|
|
||||||
fn find_sysroot() -> PathBuf { |
|
||||||
let sysroot = env::var("SYSROOT").ok().unwrap_or_else(|| { |
|
||||||
// Get sysroot from rustc
|
|
||||||
let rustc = env::var("RUSTC").unwrap_or_else(|_| "rustc".to_string()); |
|
||||||
|
|
||||||
let output = Command::new(&rustc) |
|
||||||
.arg("--print") |
|
||||||
.arg("sysroot") |
|
||||||
.output() |
|
||||||
.unwrap_or_else(|_| panic!("Failed to run `{rustc} --print sysroot`")); |
|
||||||
|
|
||||||
String::from_utf8(output.stdout) |
|
||||||
.expect("Failed to parse sysroot path into a UTF-8 string") |
|
||||||
}); |
|
||||||
|
|
||||||
PathBuf::from(sysroot.trim()) |
|
||||||
} |
|
||||||
|
|
||||||
fn should_build_3dsx(&self) -> bool { |
|
||||||
matches!(self.command.as_str(), "build" | "run" | "test") |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn print_usage(f: &mut impl io::Write) { |
|
||||||
let invocation = { |
|
||||||
let mut args = env::args(); |
|
||||||
|
|
||||||
// We do this to properly display `cargo-3ds` if invoked that way
|
|
||||||
let bin = args.next().unwrap(); |
|
||||||
if let Some("3ds") = args.next().as_deref() { |
|
||||||
"cargo 3ds".to_string() |
|
||||||
} else { |
|
||||||
bin |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
writeln!( |
|
||||||
f, |
|
||||||
"{name}: {description}. |
|
||||||
|
|
||||||
Usage: |
|
||||||
{invocation} build [CARGO_OPTS...] |
|
||||||
{invocation} run [CARGO_OPTS...] |
|
||||||
{invocation} test [CARGO_OPTS...] |
|
||||||
{invocation} <cargo-command> [CARGO_OPTS...] |
|
||||||
{invocation} -h | --help |
|
||||||
|
|
||||||
Commands: |
|
||||||
build build a 3dsx executable. |
|
||||||
run build a 3dsx executable and send it to a device with 3dslink. |
|
||||||
test build a 3dsx executable from unit/integration tests and send it to a device. |
|
||||||
<cargo-command> execute some other Cargo command with 3ds options configured (ex. check or clippy). |
|
||||||
|
|
||||||
Options: |
|
||||||
-h --help Show this screen. |
|
||||||
|
|
||||||
Additional arguments will be passed through to `<cargo-command>`. Some that are supported include: |
|
||||||
|
|
||||||
[build | run | test] --release |
|
||||||
test --no-run |
|
||||||
|
|
||||||
Other flags may work, but haven't been tested. |
|
||||||
", |
|
||||||
name = env!("CARGO_BIN_NAME"), |
|
||||||
description = env!("CARGO_PKG_DESCRIPTION"), |
|
||||||
invocation = invocation, |
|
||||||
) |
|
||||||
.unwrap(); |
|
||||||
} |
|
||||||
|
|
||||||
fn check_rust_version() { |
|
||||||
let rustc_version = rustc_version::version_meta().unwrap(); |
|
||||||
|
|
||||||
if rustc_version.channel > Channel::Nightly { |
|
||||||
eprintln!("cargo-3ds requires a nightly rustc version."); |
|
||||||
eprintln!( |
|
||||||
"Please run `rustup override set nightly` to use nightly in the \ |
|
||||||
current directory." |
|
||||||
); |
|
||||||
process::exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
let old_version = MINIMUM_RUSTC_VERSION > Version { |
|
||||||
// Remove `-nightly` pre-release tag for comparison.
|
|
||||||
pre: semver::Prerelease::EMPTY, |
|
||||||
..rustc_version.semver.clone() |
|
||||||
}; |
|
||||||
|
|
||||||
let old_commit = match rustc_version.commit_date { |
|
||||||
None => false, |
|
||||||
Some(date) => { |
|
||||||
MINIMUM_COMMIT_DATE |
|
||||||
> CommitDate::parse(&date).expect("could not parse `rustc --version` commit date") |
|
||||||
} |
|
||||||
}; |
|
||||||
|
|
||||||
if old_version || old_commit { |
|
||||||
eprintln!( |
|
||||||
"cargo-3ds requires rustc nightly version >= {}", |
|
||||||
MINIMUM_COMMIT_DATE, |
|
||||||
); |
|
||||||
eprintln!("Please run `rustup update nightly` to upgrade your nightly version"); |
|
||||||
|
|
||||||
process::exit(1); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn get_metadata(messages: &[Message]) -> CTRConfig { |
|
||||||
let metadata = MetadataCommand::new() |
|
||||||
.exec() |
|
||||||
.expect("Failed to get cargo metadata"); |
|
||||||
|
|
||||||
let mut package = None; |
|
||||||
let mut artifact = None; |
|
||||||
|
|
||||||
// Extract the final built executable. We may want to fail in cases where
|
|
||||||
// multiple executables, or none, were built?
|
|
||||||
for message in messages.iter().rev() { |
|
||||||
if let Message::CompilerArtifact(art) = message { |
|
||||||
if art.executable.is_some() { |
|
||||||
package = Some(metadata[&art.package_id].clone()); |
|
||||||
artifact = Some(art.clone()); |
|
||||||
|
|
||||||
break; |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
||||||
if package.is_none() || artifact.is_none() { |
|
||||||
eprintln!("No executable found from build command output!"); |
|
||||||
process::exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
let (package, artifact) = (package.unwrap(), artifact.unwrap()); |
|
||||||
|
|
||||||
let mut icon = String::from("./icon.png"); |
|
||||||
|
|
||||||
if !Path::new(&icon).exists() { |
|
||||||
icon = format!( |
|
||||||
"{}/libctru/default_icon.png", |
|
||||||
env::var("DEVKITPRO").unwrap() |
|
||||||
); |
|
||||||
} |
|
||||||
|
|
||||||
// for now assume a single "kind" since we only support one output artifact
|
|
||||||
let name = match artifact.target.kind[0].as_ref() { |
|
||||||
"bin" | "lib" | "rlib" | "dylib" if artifact.target.test => { |
|
||||||
format!("{} tests", artifact.target.name) |
|
||||||
} |
|
||||||
"example" => { |
|
||||||
format!("{} - {} example", artifact.target.name, package.name) |
|
||||||
} |
|
||||||
_ => artifact.target.name, |
|
||||||
}; |
|
||||||
|
|
||||||
let author = match package.authors.as_slice() { |
|
||||||
[name, ..] => name.to_owned(), |
|
||||||
[] => String::from("Unspecified Author"), // as standard with the devkitPRO toolchain
|
|
||||||
}; |
|
||||||
|
|
||||||
CTRConfig { |
|
||||||
name, |
|
||||||
author, |
|
||||||
description: package |
|
||||||
.description |
|
||||||
.clone() |
|
||||||
.unwrap_or_else(|| String::from("Homebrew Application")), |
|
||||||
icon, |
|
||||||
target_path: artifact.executable.unwrap().into(), |
|
||||||
cargo_manifest_path: package.manifest_path.into(), |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn build_smdh(config: &CTRConfig) { |
|
||||||
let mut process = Command::new("smdhtool") |
|
||||||
.arg("--create") |
|
||||||
.arg(&config.name) |
|
||||||
.arg(&config.description) |
|
||||||
.arg(&config.author) |
|
||||||
.arg(&config.icon) |
|
||||||
.arg(config.path_smdh()) |
|
||||||
.stdin(Stdio::inherit()) |
|
||||||
.stdout(Stdio::inherit()) |
|
||||||
.stderr(Stdio::inherit()) |
|
||||||
.spawn() |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
let status = process.wait().unwrap(); |
|
||||||
|
|
||||||
if !status.success() { |
|
||||||
process::exit(status.code().unwrap_or(1)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
fn build_3dsx(config: &CTRConfig) { |
|
||||||
let mut command = Command::new("3dsxtool"); |
|
||||||
let mut process = command |
|
||||||
.arg(&config.target_path) |
|
||||||
.arg(config.path_3dsx()) |
|
||||||
.arg(format!("--smdh={}", config.path_smdh().to_string_lossy())); |
|
||||||
|
|
||||||
// If romfs directory exists, automatically include it
|
|
||||||
let (romfs_path, is_default_romfs) = get_romfs_path(config); |
|
||||||
if romfs_path.is_dir() { |
|
||||||
eprintln!("Adding RomFS from {}", romfs_path.display()); |
|
||||||
process = process.arg(format!("--romfs={}", romfs_path.to_string_lossy())); |
|
||||||
} else if !is_default_romfs { |
|
||||||
eprintln!( |
|
||||||
"Could not find configured RomFS dir: {}", |
|
||||||
romfs_path.display() |
|
||||||
); |
|
||||||
process::exit(1); |
|
||||||
} |
|
||||||
|
|
||||||
let mut process = process |
|
||||||
.stdin(Stdio::inherit()) |
|
||||||
.stdout(Stdio::inherit()) |
|
||||||
.stderr(Stdio::inherit()) |
|
||||||
.spawn() |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
let status = process.wait().unwrap(); |
|
||||||
|
|
||||||
if !status.success() { |
if !status.success() { |
||||||
process::exit(status.code().unwrap_or(1)); |
process::exit(status.code().unwrap_or(1)); |
||||||
} |
} |
||||||
} |
|
||||||
|
|
||||||
fn link(config: &CTRConfig) { |
|
||||||
let mut process = Command::new("3dslink") |
|
||||||
.arg(config.path_3dsx()) |
|
||||||
.stdin(Stdio::inherit()) |
|
||||||
.stdout(Stdio::inherit()) |
|
||||||
.stderr(Stdio::inherit()) |
|
||||||
.spawn() |
|
||||||
.unwrap(); |
|
||||||
|
|
||||||
let status = process.wait().unwrap(); |
|
||||||
|
|
||||||
if !status.success() { |
|
||||||
process::exit(status.code().unwrap_or(1)); |
|
||||||
} |
|
||||||
} |
|
||||||
|
|
||||||
/// Read the `RomFS` path from the Cargo manifest. If it's unset, use the default.
|
|
||||||
/// The returned boolean is true when the default is used.
|
|
||||||
fn get_romfs_path(config: &CTRConfig) -> (PathBuf, bool) { |
|
||||||
let manifest_path = &config.cargo_manifest_path; |
|
||||||
let manifest_str = std::fs::read_to_string(manifest_path) |
|
||||||
.unwrap_or_else(|e| panic!("Could not open {}: {e}", manifest_path.display())); |
|
||||||
let manifest_data: toml::Value = |
|
||||||
toml::de::from_str(&manifest_str).expect("Could not parse Cargo manifest as TOML"); |
|
||||||
|
|
||||||
// Find the romfs setting and compute the path
|
|
||||||
let mut is_default = false; |
|
||||||
let romfs_dir_setting = manifest_data |
|
||||||
.as_table() |
|
||||||
.and_then(|table| table.get("package")) |
|
||||||
.and_then(toml::Value::as_table) |
|
||||||
.and_then(|table| table.get("metadata")) |
|
||||||
.and_then(toml::Value::as_table) |
|
||||||
.and_then(|table| table.get("cargo-3ds")) |
|
||||||
.and_then(toml::Value::as_table) |
|
||||||
.and_then(|table| table.get("romfs_dir")) |
|
||||||
.and_then(toml::Value::as_str) |
|
||||||
.unwrap_or_else(|| { |
|
||||||
is_default = true; |
|
||||||
"romfs" |
|
||||||
}); |
|
||||||
let mut romfs_path = manifest_path.clone(); |
|
||||||
romfs_path.pop(); // Pop Cargo.toml
|
|
||||||
romfs_path.push(romfs_dir_setting); |
|
||||||
|
|
||||||
(romfs_path, is_default) |
input.cmd.run_callback(&messages); |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue