Compare commits
No commits in common. 'main' and 'fix/optional-authors' have entirely different histories.
main
...
fix/option
13 changed files with 503 additions and 2192 deletions
@ -1,36 +0,0 @@ |
|||||||
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 |
|
@ -1,71 +0,0 @@ |
|||||||
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 |
|
@ -1,462 +0,0 @@ |
|||||||
# 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,176 +0,0 @@ |
|||||||
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 |
|
@ -1,23 +0,0 @@ |
|||||||
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,130 +1,15 @@ |
|||||||
# 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. |
||||||
|
|
||||||
## Installation |
# Usage |
||||||
|
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: |
||||||
To install the latest release on <https://crates.io>: |
`cargo +nightly 3ds`. \ |
||||||
|
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. |
|
||||||
|
|
||||||
Doc tests sort of work, but `cargo-3ds` uses a number of unstable cargo and |
# Examples: |
||||||
rustdoc features to make them work, so the output won't be as pretty and will |
`cargo +nightly 3ds build` \ |
||||||
require some manual workarounds to actually run the tests and see output from them. |
`cargo +nightly 3ds run --release` |
||||||
For now, `cargo 3ds test --doc` will not build a 3dsx file or use `3dslink` at all. |
|
||||||
|
|
||||||
## License |
You can pass or not `--release` to build with debug symbols or not, and this works for both `build` and `run`. |
||||||
|
|
||||||
This project is distributed under the MIT license or the Apache-2.0 license. |
Any other parameters you pass after the command will be passed during the compiling stage to `cargo`. |
||||||
|
@ -1,694 +0,0 @@ |
|||||||
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); |
|
||||||
} |
|
||||||
} |
|
||||||
} |
|
@ -1,90 +0,0 @@ |
|||||||
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>, |
|
||||||
} |
|
@ -1,490 +0,0 @@ |
|||||||
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,27 +1,501 @@ |
|||||||
use std::process; |
use cargo_metadata::{Message, MetadataCommand}; |
||||||
|
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; |
||||||
|
|
||||||
use cargo_3ds::command::Cargo; |
#[derive(serde_derive::Deserialize, Default)] |
||||||
use cargo_3ds::{check_rust_version, run_cargo}; |
struct CTRConfig { |
||||||
use clap::Parser; |
name: String, |
||||||
|
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(); |
||||||
|
|
||||||
let Cargo::Input(mut input) = Cargo::parse(); |
if env::args().any(|arg| arg == "--help" || arg == "-h") { |
||||||
|
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 mut remaining_args: Vec<String> = args.collect(); |
||||||
|
|
||||||
|
let (command, should_link) = match command.as_str() { |
||||||
|
"run" => ("build".to_string(), true), |
||||||
|
"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 mut stdout_reader; |
||||||
|
|
||||||
let message_format = match input.cmd.extract_message_format() { |
let buf_reader: &mut dyn BufRead = if self.message_format == Self::DEFAULT_MESSAGE_FORMAT { |
||||||
Ok(fmt) => fmt, |
stdout_reader = BufReader::new(command_stdout); |
||||||
Err(msg) => { |
&mut stdout_reader |
||||||
eprintln!("{msg}"); |
} else { |
||||||
process::exit(1) |
// 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; |
||||||
|
|
||||||
let (status, messages) = run_cargo(&input, message_format); |
// 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() { |
if !status.success() { |
||||||
process::exit(status.code().unwrap_or(1)); |
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() { |
||||||
|
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); |
||||||
|
|
||||||
input.cmd.run_callback(&messages); |
(romfs_path, is_default) |
||||||
} |
} |
||||||
|
Loading…
Reference in new issue