diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69a3978..358f2f7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -75,17 +75,17 @@ jobs: # unless cargo-3ds actually runs them as separate commands. See # https://github.com/rust3ds/cargo-3ds/issues/44 for more details - name: Build lib and integration tests - run: cargo 3ds test --no-run --tests --package ctru-rs + run: cargo 3ds test --no-run --tests - name: Run lib and integration tests uses: rust3ds/test-runner/run-tests@v1 with: - args: --tests --package ctru-rs + args: --tests - name: Build and run doc tests uses: rust3ds/test-runner/run-tests@v1 with: - args: --doc --package ctru-rs + args: --doc - name: Upload citra logs and capture videos uses: actions/upload-artifact@v3 diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 0000000..0487a66 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,62 @@ +name: GitHub Pages + +on: + push: + branches: + - master + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + container: devkitpro/devkitarm + steps: + - name: Checkout branch + uses: actions/checkout@v4 + + - name: Setup Pages + uses: actions/configure-pages@v3 + + - name: Build with Jekyll + uses: actions/jekyll-build-pages@v1 + + - uses: rust3ds/test-runner/setup@v1 + with: + toolchain: nightly + + - name: Build workspace docs + run: cargo 3ds --verbose doc --verbose --no-deps --workspace + env: + RUSTDOCFLAGS: --enable-index-page + + # https://github.com/actions/upload-pages-artifact#file-permissions + - name: Fix file permissions + run: | + chmod -c -R +rX "target/armv6k-nintendo-3ds/doc" | while read line; do + echo "::warning title=Invalid file permissions automatically fixed::$line" + done + + - name: Copy generated docs to _site + # Note: this won't include proc-macro crate, but macros are re-exported + # by the crate docs so there will still be some documentation. + run: cp -R ./target/armv6k-nintendo-3ds/doc ./_site/crates + + - name: Upload docs + uses: actions/upload-pages-artifact@v2 + + deploy: + runs-on: ubuntu-latest + needs: build + permissions: + pages: write + id-token: write + + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 + diff --git a/README.md b/README.md index 8a061b4..ae14fd7 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,20 @@ This repository is home of the `ctru-rs` project, which aims to bring full contr This repository is organized as follows: - * [`ctru-rs`](./ctru-rs/) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys/). - * [`ctru-sys`](./ctru-sys/) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru). + * [`ctru-rs`](./ctru-rs) - Safe, idiomatic wrapper around [`ctru-sys`](./ctru-sys). + * [`ctru-sys`](./ctru-sys) - Low-level, unsafe bindings to [`libctru`](https://github.com/devkitPro/libctru). ## Getting Started Specific information about how to use the crates is present in the individual README for each package. Have a look at `ctru-rs`' [README.md](./ctru-rs/README.md) for a broad overview. +## Documentation + +Cargo-generated [documentation](https://rust3ds.github.io/ctru-rs/crates) is available +via GitHub Pages, because the build environment does not have `libctru` +installed. + ## Original version This project is based on the efforts of the original authors: diff --git a/_config.yml b/_config.yml new file mode 100644 index 0000000..4688e93 --- /dev/null +++ b/_config.yml @@ -0,0 +1,2 @@ +# Configuration for GitHub Pages (Jekyll) +theme: jekyll-theme-midnight diff --git a/assets/css/style.scss b/assets/css/style.scss new file mode 100644 index 0000000..1efa0fc --- /dev/null +++ b/assets/css/style.scss @@ -0,0 +1,15 @@ +--- +--- + +// https://github.com/pages-themes/midnight#stylesheet +@import "{{ site.theme }}"; + +// Give code links a color that matches regular links +a code { + color: inherit; +} + +// Remove weird extra padding and spaces from inline code blocks +code { + padding: 0; +} diff --git a/ctru-rs/Cargo.toml b/ctru-rs/Cargo.toml index ac10586..12ead22 100644 --- a/ctru-rs/Cargo.toml +++ b/ctru-rs/Cargo.toml @@ -4,6 +4,7 @@ version = "0.7.1" authors = ["Rust3DS Org", "Ronald Kinard "] description = "A safe wrapper around libctru" repository = "https://github.com/rust3ds/ctru-rs" +documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru" keywords = ["3ds", "libctru"] categories = ["os", "api-bindings", "hardware-support"] exclude = ["examples"] @@ -17,7 +18,7 @@ name = "ctru" [dependencies] cfg-if = "1.0" -ctru-sys = { path = "../ctru-sys", version = "22.2" } +ctru-sys = { path = "../ctru-sys", version = "0.5.0" } const-zero = "0.1.0" shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } pthread-3ds = { git = "https://github.com/rust3ds/pthread-3ds.git" } diff --git a/ctru-rs/README.md b/ctru-rs/README.md index 0cc8a79..121e9d4 100644 --- a/ctru-rs/README.md +++ b/ctru-rs/README.md @@ -2,6 +2,8 @@ Safe and idiomatic Rust wrapper around [`libctru`](https://github.com/devkitPro/libctru). +Documentation for the `master` branch can be found [here](https://rust3ds.github.io/ctru-rs/crates/ctru). + ## Getting Started Thoroughly read the [`ctru-rs` wiki](https://github.com/rust3ds/ctru-rs/wiki/Getting-Started) to meet the requirements diff --git a/ctru-rs/src/error.rs b/ctru-rs/src/error.rs index 9e01a8e..87170ad 100644 --- a/ctru-rs/src/error.rs +++ b/ctru-rs/src/error.rs @@ -263,7 +263,13 @@ fn result_code_description_str(result: ctru_sys::Result) -> Cow<'static, str> { RD_NOT_AUTHORIZED => "not_authorized", RD_TOO_LARGE => "too_large", RD_INVALID_SELECTION => "invalid_selection", - code => return Cow::Owned(format!("(unknown description: {code:#x})")), + code => { + let error = unsafe { CStr::from_ptr(ctru_sys::osStrError(result)) }.to_str(); + match error { + Ok(err) => err, + Err(_) => return Cow::Owned(format!("(unknown description: {code:#x})")), + } + } }) } diff --git a/ctru-rs/src/lib.rs b/ctru-rs/src/lib.rs index 58537ee..e8c6483 100644 --- a/ctru-rs/src/lib.rs +++ b/ctru-rs/src/lib.rs @@ -28,6 +28,7 @@ #![doc( html_logo_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" )] +#![doc(html_root_url = "https://rust3ds.github.io/ctru-rs/crates")] // Nothing is imported from these crates but their inclusion here assures correct linking of the missing implementations. extern crate pthread_3ds; diff --git a/ctru-sys/Cargo.toml b/ctru-sys/Cargo.toml index eba9db0..1e2c303 100644 --- a/ctru-sys/Cargo.toml +++ b/ctru-sys/Cargo.toml @@ -1,9 +1,10 @@ [package] name = "ctru-sys" -version = "22.2.0+2.2.2-1" +version = "0.5.0" authors = ["Rust3DS Org", "Ronald Kinard "] description = "Raw bindings to libctru" repository = "https://github.com/rust3ds/ctru-rs" +documentation = "https://rust3ds.github.io/ctru-rs/crates/ctru_sys" keywords = ["3ds", "libctru"] categories = ["os", "external-ffi-bindings", "no-std", "hardware-support"] exclude = ["src/.gitattributes"] @@ -18,8 +19,13 @@ libc = { version = "0.2.121", default-features = false } bindgen = { version = "0.65.1", features = ["experimental"] } cc = "1.0" doxygen-rs = "0.4.2" +itertools = "0.11.0" which = "4.4.0" +[dev-dependencies] +shim-3ds = { git = "https://github.com/rust3ds/shim-3ds.git" } +test-runner = { git = "https://github.com/rust3ds/test-runner.git" } + [package.metadata.docs.rs] default-target = "armv6k-nintendo-3ds" targets = [] diff --git a/ctru-sys/README.md b/ctru-sys/README.md index 280fd83..93d392c 100644 --- a/ctru-sys/README.md +++ b/ctru-sys/README.md @@ -2,6 +2,10 @@ Raw Rust bindings over the [`libctru`](https://github.com/devkitPro/libctru) C library. +Documentation for the latest devkitPro release +[on Docker Hub](https://hub.docker.com/r/devkitpro/devkitarm/) +can be found [here](https://rust3ds.github.io/ctru-rs/crates/ctru_sys). + ## Requirements To use the bindings provided by this crate you will need to link against the [`libctru`](https://github.com/devkitPro/libctru) library. @@ -10,23 +14,17 @@ to use this library. ## Version -This crate's version changes according to the version of `libctru` -used to generate the bindings, with the following convention: - - * [`libctru`](https://github.com/devkitPro/libctru) version `X.Y.Z-W` - * `ctru-sys` version `XY.Z.P+X.Y.Z-W` - - where `P` is usually 0 but may be incremented for fixes in e.g. - binding generation, `libc` dependency bump, etc. - -It may be possible to build this crate against a different version of [`libctru`](https://github.com/devkitPro/libctru), -but you may encounter linker errors or ABI issues. A build-time Cargo warning -(displayed when built with `-vv`) will be issued if the build script detects -a mismatch or is unable to check the installed [`libctru`](https://github.com/devkitPro/libctru) version. - -## Generating bindings - -Bindings of new versions of [`libctru`](https://github.com/devkitPro/libctru) can be built using the integrated [`bindgen.sh`](./bindgen.sh) script. +Crate bindings are generated at build time, so the available APIs will depend on the +installed version of `libctru` when the crate is built. If you want to check +what version of `libctru` is being built, you can examine these environment +variables from your crate's build script via to its +[`links` variables](https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key): + +* `DEP_CTRU_VERSION`: full version string (e.g. `"2.3.1-4"`) +* `DEP_CTRU_MAJOR_VERSION`: major version (e.g. `"2"` for version `2.3.1-4`) +* `DEP_CTRU_MINOR_VERSION`: minor version (e.g. `"3"` for version `2.3.1-4`) +* `DEP_CTRU_PATCH_VERSION`: patch version (e.g. `"1"` for version `2.3.1-4`) +* `DEP_CTRU_RELEASE`: release version (e.g. `"4"` for version `2.3.1-4`) ## License diff --git a/ctru-sys/build.rs b/ctru-sys/build.rs index b1a48b3..5e71ae0 100644 --- a/ctru-sys/build.rs +++ b/ctru-sys/build.rs @@ -1,5 +1,6 @@ use bindgen::callbacks::ParseCallbacks; use bindgen::{Builder, RustTarget}; +use itertools::Itertools; use std::env; use std::error::Error; @@ -18,33 +19,39 @@ impl ParseCallbacks for CustomCallbacks { fn main() { let devkitpro = env::var("DEVKITPRO").unwrap(); let devkitarm = env::var("DEVKITARM").unwrap(); - let profile = env::var("PROFILE").unwrap(); let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap()); println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=DEVKITPRO"); println!("cargo:rustc-link-search=native={devkitpro}/libctru/lib"); - println!( - "cargo:rustc-link-lib=static={}", - match profile.as_str() { - "debug" => "ctrud", - _ => "ctru", - } - ); - - match check_libctru_version() { - Ok((maj, min, patch)) => { - eprintln!("using libctru version {maj}.{min}.{patch}"); - - // These are accessible by the crate during build with `env!()`. - // We might consider exporting some public constants or something. - println!("cargo:rustc-env=LIBCTRU_VERSION={maj}.{min}.{patch}"); - println!("cargo:rustc-env=LIBCTRU_MAJOR={maj}"); - println!("cargo:rustc-env=LIBCTRU_MINOR={min}"); - println!("cargo:rustc-env=LIBCTRU_PATCH={patch}"); + + // https://github.com/rust3ds/cargo-3ds/issues/14#issuecomment-1783991872 + // To link properly, this must be the same as the library linked by cargo-3ds when building + // the standard library, so if `-lctru[d]` is found in RUSTFLAGS we always defer to that + // https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-build-scripts + let cargo_rustflags = env::var("CARGO_ENCODED_RUSTFLAGS").unwrap(); + let rustflags_libctru = cargo_rustflags + .split('\x1F') + // Technically this could also be `-l ctru`, or `-lstatic=ctru` etc. + // but for now we'll just rely on cargo-3ds implementation to pass it like this + .find(|flag| flag.starts_with("-lctru")) + .and_then(|flag| flag.strip_prefix("-l")); + + let linked_libctru = rustflags_libctru.unwrap_or_else(|| { + let debuginfo = env::var("DEBUG").unwrap(); + match debuginfo.as_str() { + // Normally this should just be "true" or "false", but just in case, + // we don't support all the different options documented in + // https://doc.rust-lang.org/cargo/reference/profiles.html#debug + // so just default to linking with debuginfo if it wasn't disabled + "0" | "false" | "none" => "ctru", + _ => "ctrud", } - Err(err) => println!("cargo:warning=failed to check libctru version: {err}"), - } + }); + + println!("cargo:rustc-link-lib=static={linked_libctru}"); + + detect_and_track_libctru(); let gcc_version = get_gcc_version(PathBuf::from(&devkitarm).join("bin/arm-none-eabi-gcc")); @@ -135,22 +142,40 @@ fn get_gcc_version(path_to_gcc: PathBuf) -> String { .to_string() } -fn parse_libctru_version(version: &str) -> Result<(String, String, String), &str> { - let versions: Vec<_> = version - .split(|c| c == '.' || c == '-') - .map(String::from) - .collect(); +fn detect_and_track_libctru() { + let pacman = match which::which("dkp-pacman") + .or_else(|err1| which::which("pacman").map_err(|err2| format!("{err1}; {err2}"))) + { + Ok(pacman) => pacman, + Err(err) => { + println!("cargo:warning=unable to find `pacman` or `dkp-pacman`: {err}"); + return; + } + }; + + match get_libctru_version(&pacman) { + Ok((maj, min, patch, rel)) => { + let version = format!("{maj}.{min}.{patch}-{rel}"); + eprintln!("using libctru version {version}"); + // These are exported as build script output variables, accessible + // via `env::var("DEP_CTRU_")` in other crates' build scripts. + // https://doc.rust-lang.org/cargo/reference/build-scripts.html#the-links-manifest-key + println!("cargo:VERSION={version}"); + println!("cargo:MAJOR_VERSION={maj}"); + println!("cargo:MINOR_VERSION={min}"); + println!("cargo:PATCH_VERSION={patch}"); + println!("cargo:RELEASE={rel}"); + } + Err(err) => println!("cargo:warning=unknown libctru version: {err}"), + } - match &versions[..] { - [major, minor, patch, _build] => Ok((major.clone(), minor.clone(), patch.clone())), - _ => Err("unexpected number of version segments"), + if let Err(err) = track_libctru_files(&pacman) { + println!("cargo:warning=unable to track `libctru` files for changes: {err}"); } } -fn check_libctru_version() -> Result<(String, String, String), Box> { - let pacman = which::which("dkp-pacman").or_else(|_| which::which("pacman"))?; - - let Output { stdout, .. } = Command::new(&pacman) +fn get_libctru_version(pacman: &Path) -> Result<(String, String, String, String), Box> { + let Output { stdout, .. } = Command::new(pacman) .args(["--query", "libctru"]) .stderr(Stdio::inherit()) .output()?; @@ -159,35 +184,44 @@ fn check_libctru_version() -> Result<(String, String, String), Box> { let (_pkg, lib_version) = output_str .split_once(char::is_whitespace) - .ok_or("unexpected pacman output format")?; + .ok_or_else(|| format!("unexpected pacman output format: {output_str:?}"))?; let lib_version = lib_version.trim(); - let cargo_pkg_version = env::var("CARGO_PKG_VERSION").unwrap(); - let (_, crate_built_version) = cargo_pkg_version - .split_once('+') - .expect("crate version should have '+' delimeter"); + parse_libctru_version(lib_version).map_err(Into::into) +} - if lib_version != crate_built_version { - return Err(format!( - "libctru version is {lib_version} but this crate was built for {crate_built_version}" - ) - .into()); - } +fn parse_libctru_version(version: &str) -> Result<(String, String, String, String), String> { + version + .split(|c| c == '.' || c == '-') + .map(String::from) + .collect_tuple() + .ok_or_else(|| format!("unexpected number of version segments: {version:?}")) +} - let Output { stdout, .. } = Command::new(pacman) +fn track_libctru_files(pacman: &Path) -> Result<(), String> { + let stdout = match Command::new(pacman) .args(["--query", "--list", "libctru"]) .stderr(Stdio::inherit()) - .output()?; + .output() + { + Ok(Output { stdout, status, .. }) if status.success() => stdout, + Ok(Output { status, .. }) => { + return Err(format!("pacman query failed with status {status}")); + } + Err(err) => { + return Err(format!("pacman query failed: {err}")); + } + }; - for line in String::from_utf8_lossy(&stdout).split('\n') { + for line in String::from_utf8_lossy(&stdout).trim().split('\n') { let Some((_pkg, file)) = line.split_once(char::is_whitespace) else { + println!("cargo:warning=unexpected line from pacman query: {line:?}"); continue; }; println!("cargo:rerun-if-changed={file}"); } - let (lib_major, lib_minor, lib_patch) = parse_libctru_version(lib_version)?; - Ok((lib_major, lib_minor, lib_patch)) + Ok(()) } diff --git a/ctru-sys/src/lib.rs b/ctru-sys/src/lib.rs index 32252e6..b9f71de 100644 --- a/ctru-sys/src/lib.rs +++ b/ctru-sys/src/lib.rs @@ -3,6 +3,15 @@ #![allow(non_camel_case_types)] #![allow(non_snake_case)] #![allow(clippy::all)] +#![cfg_attr(test, feature(custom_test_frameworks))] +#![cfg_attr(test, test_runner(test_runner::run_gdb))] +#![doc( + html_favicon_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" +)] +#![doc( + html_logo_url = "https://user-images.githubusercontent.com/11131775/225929072-2fa1741c-93ae-4b47-9bdf-af70f3d59910.png" +)] +#![doc(html_root_url = "https://rust3ds.github.io/ctru-rs/crates")] pub mod result; pub use result::*; @@ -15,10 +24,6 @@ pub unsafe fn errno() -> s32 { *__errno() } -// TODO: not sure if there's a better way to do this, but I have gotten myself -// with this a couple times so having the hint seems nice to have. +// Prevent linking errors from the standard `test` library when running `cargo 3ds test --lib`. #[cfg(test)] -compile_error!(concat!( - "ctru-sys doesn't have tests and its lib test will fail to build at link time. ", - "Try specifying `--package ctru-rs` to build those tests.", -)); +extern crate shim_3ds;