From 1df1817b1ffb88d75a74279e9fcbd2f6082de129 Mon Sep 17 00:00:00 2001 From: AP Ljungquist Date: Thu, 9 Jul 2026 09:56:24 +0200 Subject: [PATCH 1/2] Replace acap-build crate with rs4a-eap from git Remove the in-tree `acap-build` crate and depend on the `rs4a-eap` crate from apljungquist/rs4acap instead. This has benefits in two categories: - Clarifies the purpose of this repo as facilitating the use of Rust in ACAP app implementations - `rs4a-eap` has several implemented and planned improvements, notably the option to not depend on external programs allowing it to be fully installed with cargo, and more robust testing. We drop all use of the reference implementation, which rs4a does not support and instead rely on the testing done by the library maintainer (me). The approach was flawed anyway since bugs could creep in when staging files in a temporary directory. --- .github/workflows/CI.yml | 2 - .github/workflows/on-host-workflow.yml | 7 +- Cargo.lock | 406 ++++++++++----- Cargo.toml | 2 +- crates/acap-build/Cargo.toml | 18 - crates/acap-build/src/bin/acap-build.rs | 96 ---- crates/acap-build/src/command_utils.rs | 61 --- crates/acap-build/src/files.rs | 5 - crates/acap-build/src/files/cgi_conf.rs | 66 --- crates/acap-build/src/files/manifest.rs | 132 ----- crates/acap-build/src/files/package_conf.rs | 342 ------------- crates/acap-build/src/files/param_conf.rs | 68 --- crates/acap-build/src/json_ext.rs | 127 ----- crates/acap-build/src/lib.rs | 477 ------------------ crates/cargo-acap-build/Cargo.toml | 4 +- crates/cargo-acap-build/src/cargo_acap.rs | 9 +- crates/cargo-acap-build/src/lib.rs | 13 +- crates/cargo-acap-build/src/main.rs | 12 +- crates/cargo-acap-sdk/Cargo.toml | 1 + .../src/commands/build_command.rs | 3 +- .../src/commands/install_command.rs | 3 +- .../src/commands/run_command.rs | 3 +- .../src/commands/test_command.rs | 3 +- crates/cargo-acap-sdk/src/main.rs | 17 + 24 files changed, 352 insertions(+), 1525 deletions(-) delete mode 100644 crates/acap-build/Cargo.toml delete mode 100644 crates/acap-build/src/bin/acap-build.rs delete mode 100644 crates/acap-build/src/command_utils.rs delete mode 100644 crates/acap-build/src/files.rs delete mode 100644 crates/acap-build/src/files/cgi_conf.rs delete mode 100644 crates/acap-build/src/files/manifest.rs delete mode 100644 crates/acap-build/src/files/package_conf.rs delete mode 100644 crates/acap-build/src/files/param_conf.rs delete mode 100644 crates/acap-build/src/json_ext.rs delete mode 100644 crates/acap-build/src/lib.rs diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 24bec34c..36dffc08 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -17,8 +17,6 @@ jobs: run: devcontainer up --workspace-folder . - name: Update generated files run: devcontainer exec --workspace-folder . make --always-make check_generated_files check_generated_files_container - - name: Update generated without acap-build - run: devcontainer exec --remote-env ACAP_BUILD_IMPL=equivalent --workspace-folder . make check_generated_files_container - name: Run other checks run: devcontainer exec --workspace-folder . make check_other - name: Run miri check diff --git a/.github/workflows/on-host-workflow.yml b/.github/workflows/on-host-workflow.yml index d0ac35df..9660ff0d 100644 --- a/.github/workflows/on-host-workflow.yml +++ b/.github/workflows/on-host-workflow.yml @@ -49,16 +49,14 @@ jobs: - name: Shell setup # This step typically needs to be done once per shell session. # To verify that the procedure has worked, some checks are run as well. - # TODO: Simplify shell session setup; exporting two variables seems excessive run: | export ACAP_SDK_LOCATION=$HOME/axis/ source ./init_env.sh - export ACAP_BUILD_IMPL=equivalent make check_generated_files debian_and_ubuntu_no_sdk: runs-on: ubuntu-24.04 - # If the sysroots from the ACAP Native SDK are not needed, then the Rust implementation of - # `acap-build` can be used for an easier setup: + # If the sysroots from the ACAP Native SDK are not needed, then the native EAP builder + # (`rs4a-eap`, used by `cargo-acap-build`) can be used for an easier setup: steps: - name: Checkout uses: actions/checkout@v4 @@ -76,6 +74,5 @@ jobs: run: | cargo-acap-build -- --package hello_world env: - ACAP_BUILD_IMPL: equivalent CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc CARGO_TARGET_THUMBV7NEON_UNKNOWN_LINUX_GNUEABIHF_LINKER: arm-linux-gnueabihf-gcc diff --git a/Cargo.lock b/Cargo.lock index a2b2082f..06a22b03 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,24 +2,6 @@ # It is not intended for manual editing. version = 4 -[[package]] -name = "acap-build" -version = "0.0.0" -dependencies = [ - "anyhow", - "clap", - "dirs", - "env_logger", - "glob", - "log", - "regex", - "semver", - "serde", - "serde_json", - "tempdir", - "tempfile", -] - [[package]] name = "acap-logging" version = "0.1.1" @@ -85,6 +67,20 @@ version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627" +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.2", + "once_cell", + "serde", + "version_check", + "zerocopy", +] + [[package]] name = "aho-corasick" version = "1.1.3" @@ -458,6 +454,21 @@ dependencies = [ "which", ] +[[package]] +name = "bit-set" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08807e080ed7f9d5433fa9b275196cfc35414f66a0c79d864dc51a0d825231a3" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5e764a1d40d510daf35e07be9eb06e75770908c27d411ee6c92109c9840eaaf7" + [[package]] name = "bitflags" version = "1.3.2" @@ -479,6 +490,12 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borrow-or-share" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc0b364ead1874514c8c2855ab558056ebfeb775653e7ae45ff72f28f8f3166c" + [[package]] name = "bounding_box_example" version = "0.0.0" @@ -495,6 +512,12 @@ version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" +[[package]] +name = "bytecount" +version = "0.6.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175812e0be2bccb6abe50bb8d566126198344f707e304f45c648fd8f2cc0365e" + [[package]] name = "byteorder" version = "1.5.0" @@ -532,13 +555,13 @@ dependencies = [ name = "cargo-acap-build" version = "0.1.0" dependencies = [ - "acap-build", "anyhow", "clap", "cli-version", "dirs", "env_logger", "log", + "rs4a-eap", "serde", "serde_json", "tempfile", @@ -559,6 +582,7 @@ dependencies = [ "env_logger", "log", "reqwest", + "rs4a-eap", "tokio", "url", ] @@ -762,7 +786,7 @@ dependencies = [ "digest", "hex", "md-5", - "rand 0.8.5", + "rand", "sha2", ] @@ -821,6 +845,15 @@ version = "1.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a47c1c47d2f5964e29c61246e81db715514cd532db6b5116a25ea3c03d6780a2" +[[package]] +name = "email_address" +version = "0.2.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e079f19b08ca6239f47f8ba8509c11cf3ea30095831f7fed61441475edd8c449" +dependencies = [ + "serde", +] + [[package]] name = "embedded_web_page" version = "0.0.0" @@ -916,6 +949,17 @@ dependencies = [ "once_cell", ] +[[package]] +name = "fancy-regex" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e24cb5a94bcae1e5408b0effca5cd7172ea3c5755049c5f3af4cd283a165298" +dependencies = [ + "bit-set", + "regex-automata", + "regex-syntax", +] + [[package]] name = "fastrand" version = "2.3.0" @@ -948,6 +992,17 @@ dependencies = [ "miniz_oxide 0.8.5", ] +[[package]] +name = "fluent-uri" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1918b65d96df47d3591bed19c5cca17e3fa5d0707318e4b5ef2eae01764df7e5" +dependencies = [ + "borrow-or-share", + "ref-cast", + "serde", +] + [[package]] name = "fnv" version = "1.0.7" @@ -979,10 +1034,14 @@ dependencies = [ ] [[package]] -name = "fuchsia-cprng" -version = "0.1.1" +name = "fraction" +version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" +checksum = "e076045bb43dac435333ed5f04caf35c7463631d0dae2deb2638d94dd0a5b872" +dependencies = [ + "lazy_static", + "num", +] [[package]] name = "futures" @@ -1578,6 +1637,30 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "jsonschema" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "161c33c3ec738cfea3288c5c53dfcdb32fd4fc2954de86ea06f71b5a1a40bfcd" +dependencies = [ + "ahash", + "base64 0.22.1", + "bytecount", + "email_address", + "fancy-regex", + "fraction", + "idna", + "itoa", + "num-cmp", + "once_cell", + "percent-encoding", + "referencing", + "regex-syntax", + "serde", + "serde_json", + "uuid-simd", +] + [[package]] name = "larod-sys" version = "0.0.0" @@ -1824,6 +1907,84 @@ dependencies = [ "minimal-lexical", ] +[[package]] +name = "num" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35bd024e8b2ff75562e5f34e7f4905839deb4b22955ef5e73d2fea1b9813cb23" +dependencies = [ + "num-bigint", + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-bigint" +version = "0.4.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c89e69e7e0f03bea5ef08013795c25018e101932225a656383bd384495ecc367" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-cmp" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63335b2e2c34fae2fb0aa2cecfd9f0832a1e24b3b32ecec612c3426d46dc8aaa" + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c92800bd69a1eac91786bcfe9da64a897eb72911b8dc3095decbd07429e8048b" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + [[package]] name = "object" version = "0.36.1" @@ -1845,9 +2006,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.19.0" +version = "1.21.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" [[package]] name = "openssl" @@ -1898,6 +2059,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "outref" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a80800c0488c3a21695ea981a54918fbb37abf04f4d0720c453632255e2ff0e" + [[package]] name = "parking" version = "2.2.1" @@ -2026,19 +2193,6 @@ version = "5.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74765f6d916ee2faa39bc8e68e4f3ed8949b48cccdac59983d287a7cb71ce9c5" -[[package]] -name = "rand" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "552840b97013b1a26992c11eac34bdd778e464601a4c2054b5f0bff7c6761293" -dependencies = [ - "fuchsia-cprng", - "libc", - "rand_core 0.3.1", - "rdrand", - "winapi", -] - [[package]] name = "rand" version = "0.8.5" @@ -2047,7 +2201,7 @@ checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", "rand_chacha", - "rand_core 0.6.4", + "rand_core", ] [[package]] @@ -2057,24 +2211,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core 0.6.4", -] - -[[package]] -name = "rand_core" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a6fdeb83b075e8266dcc8762c22776f6877a63111121f5f8c7411e5be7eed4b" -dependencies = [ - "rand_core 0.4.2", + "rand_core", ] -[[package]] -name = "rand_core" -version = "0.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c33a3c44ca05fa6f1807d8e6743f3824e8509beca625669633be0acbdf509dc" - [[package]] name = "rand_core" version = "0.6.4" @@ -2084,15 +2223,6 @@ dependencies = [ "getrandom 0.2.15", ] -[[package]] -name = "rdrand" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "678054eb77286b51581ba43620cc911abf02758c91f93f479767aed0f90458b2" -dependencies = [ - "rand_core 0.3.1", -] - [[package]] name = "redox_syscall" version = "0.4.1" @@ -2122,11 +2252,45 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "ref-cast" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f354300ae66f76f1c85c5f84693f0ce81d747e2c3f21a45fef496d89c960bf7d" +dependencies = [ + "ref-cast-impl", +] + +[[package]] +name = "ref-cast-impl" +version = "1.0.25" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7186006dcb21920990093f30e3dea63b7d6e977bf1256be20c3563a5db070da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + +[[package]] +name = "referencing" +version = "0.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a64b3a635fad9000648b4d8a59c8710c523ab61a23d392a7d91d47683f5adc" +dependencies = [ + "ahash", + "fluent-uri", + "once_cell", + "parking_lot", + "percent-encoding", + "serde_json", +] + [[package]] name = "regex" -version = "1.11.1" +version = "1.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +checksum = "f1292b7759ae1cb9ec195452d1390a074f0cd8541ab7a5a8c31cd6db45d4a6ba" dependencies = [ "aho-corasick", "memchr", @@ -2136,9 +2300,9 @@ dependencies = [ [[package]] name = "regex-automata" -version = "0.4.9" +version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f" dependencies = [ "aho-corasick", "memchr", @@ -2147,18 +2311,9 @@ dependencies = [ [[package]] name = "regex-syntax" -version = "0.8.5" +version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "remove_dir_all" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3acd125665422973a33ac9d3dd2df85edad0f4ae9b00dafb1a05e43a9f5ef8e7" -dependencies = [ - "winapi", -] +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" [[package]] name = "reqwest" @@ -2248,6 +2403,20 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "rs4a-eap" +version = "0.1.0" +source = "git+https://github.com/apljungquist/rs4acap.git?rev=694c2066cca141b2b064ce988d4f5bb887a892b0#694c2066cca141b2b064ce988d4f5bb887a892b0" +dependencies = [ + "anyhow", + "jsonschema", + "log", + "regex", + "semver", + "serde", + "serde_json", +] + [[package]] name = "rustc-demangle" version = "0.1.24" @@ -2654,16 +2823,6 @@ version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" -[[package]] -name = "tempdir" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15f2b5fb00ccdf689e0149d1b1b3c03fead81c2b37735d812fa8bddbbf41b6d8" -dependencies = [ - "rand 0.4.6", - "remove_dir_all", -] - [[package]] name = "tempfile" version = "3.20.0" @@ -2951,7 +3110,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha1", "thiserror 1.0.69", "url", @@ -2970,7 +3129,7 @@ dependencies = [ "http", "httparse", "log", - "rand 0.8.5", + "rand", "sha1", "thiserror 1.0.69", "utf-8", @@ -3046,6 +3205,23 @@ version = "0.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a" +[[package]] +name = "uuid" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "458f7a779bf54acc9f347480ac654f68407d3aab21269a6e3c9f922acd9e2da9" + +[[package]] +name = "uuid-simd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b082222b4f6619906941c17eb2297fff4c2fb96cb60164170522942a200bd8" +dependencies = [ + "outref", + "uuid", + "vsimd", +] + [[package]] name = "vapix_access" version = "0.0.0" @@ -3111,6 +3287,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "vsimd" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c3082ca00d5a5ef149bb8b555a72ae84c9c59f7250f013ac822ac2e49b19c64" + [[package]] name = "want" version = "0.3.1" @@ -3223,28 +3405,6 @@ dependencies = [ "rustix 0.38.34", ] -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - [[package]] name = "windows-registry" version = "0.2.0" @@ -3488,6 +3648,26 @@ dependencies = [ "synstructure", ] +[[package]] +name = "zerocopy" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0894878a5fa3edfd6da3f88c4805f4c8558e2b996227a3d864f47fe11e38282c" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "88d2b8d9c68ad2b9e4340d7832716a4d21a22a1154777ad56ea55c51a9cf3831" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.100", +] + [[package]] name = "zerofrom" version = "0.1.6" diff --git a/Cargo.toml b/Cargo.toml index a36ea870..aa753252 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,7 +44,6 @@ tokio = "1.38.1" tower-http = "0.5.2" url = "2.5.2" -acap-build = { path = "crates/acap-build" } acap-logging = { path = "crates/acap-logging", default-features = false } acap-ssh-utils = { path = "crates/acap-ssh-utils" } acap-vapix = { path = "crates/acap-vapix" } @@ -65,6 +64,7 @@ licensekey = { path = "crates/licensekey" } licensekey-sys = { path = "crates/licensekey-sys" } mdb = { path = "crates/mdb" } mdb-sys = { path = "crates/mdb-sys" } +rs4a-eap = { git = "https://github.com/apljungquist/rs4acap.git", rev = "694c2066cca141b2b064ce988d4f5bb887a892b0" } vdo = { path = "crates/vdo" } vdo-sys = { path = "crates/vdo-sys" } diff --git a/crates/acap-build/Cargo.toml b/crates/acap-build/Cargo.toml deleted file mode 100644 index a34eb279..00000000 --- a/crates/acap-build/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] -name = "acap-build" -version = "0.0.0" -edition = "2021" - -[dependencies] -anyhow = { workspace = true } -clap = { workspace = true, features = ["derive", "env"] } -dirs = { workspace = true } -env_logger = { workspace = true } -glob = { workspace = true } -log = { workspace = true } -semver = { workspace = true } -serde = { workspace = true, features = ["derive"] } -serde_json = { workspace = true, features = ["preserve_order"] } -tempfile = { workspace = true } -regex = { workspace = true } -tempdir = { workspace = true } diff --git a/crates/acap-build/src/bin/acap-build.rs b/crates/acap-build/src/bin/acap-build.rs deleted file mode 100644 index c79e7f3c..00000000 --- a/crates/acap-build/src/bin/acap-build.rs +++ /dev/null @@ -1,96 +0,0 @@ -//! A drop-in replacement for the acap-build python script -use std::{ - env, - fmt::{Display, Formatter}, - fs, - path::PathBuf, - process::Command, -}; - -use acap_build::{AppBuilder, Architecture}; -use anyhow::Context; -use clap::{Parser, ValueEnum}; -use log::debug; -use tempdir::TempDir; - -#[derive(Clone, Copy, Debug, Default, Eq, Hash, PartialEq, ValueEnum)] -#[clap(rename_all = "kebab-case")] -enum BuildOption { - #[default] - Make, - NoBuild, -} - -impl Display for BuildOption { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Self::Make => write!(f, "make"), - Self::NoBuild => write!(f, "no-build"), - } - } -} - -#[derive(Clone, Debug, Parser)] -struct Cli { - path: PathBuf, - /// Build tool, if any, to run before packaging. - #[clap(default_value_t, long, short)] - build: BuildOption, - #[clap(long, short)] - manifest: Option, - /// Note: can be used more than once. - #[clap(long, short)] - additional_file: Vec, -} - -fn main() -> anyhow::Result<()> { - env_logger::init(); - - let Cli { - path, - build, - manifest, - additional_file, - } = Cli::parse(); - match build { - BuildOption::Make => assert!(Command::new("make") - .status() - .context("subprocess make failed")? - .success()), - BuildOption::NoBuild => { - debug!("no build"); - } - } - - let arch: Architecture = env::var("OECORE_TARGET_ARCH")?.parse()?; - let manifest = match manifest { - None => path.join("manifest.json"), - Some(m) => path.join(m), - }; - - let staging_dir = TempDir::new_in(&path, "acap-build")?; - let mut builder = AppBuilder::new(true, staging_dir.path(), &manifest, arch)?; - - for name in builder.mandatory_files() { - builder.add(&path.join(name))?; - } - - for name in builder.optional_files() { - let file = path.join(name); - if file.symlink_metadata().is_ok() { - builder.add(&file)?; - } - } - - for additional_file in additional_file { - builder.add(&path.join(additional_file))?; - } - - let eap_file_name = builder.build()?; - fs::copy( - staging_dir.path().join(&eap_file_name), - path.join(&eap_file_name), - )?; - - Ok(()) -} diff --git a/crates/acap-build/src/command_utils.rs b/crates/acap-build/src/command_utils.rs deleted file mode 100644 index 882a3224..00000000 --- a/crates/acap-build/src/command_utils.rs +++ /dev/null @@ -1,61 +0,0 @@ -use std::io::{BufRead, BufReader}; - -use anyhow::Context; -use log::debug; - -pub trait RunWith { - fn run_with_processed_stdout( - self, - func: impl FnMut(std::io::Result) -> anyhow::Result<()>, - ) -> anyhow::Result<()>; - fn run_with_logged_stdout(self) -> anyhow::Result<()>; -} - -fn spawn(mut cmd: std::process::Command) -> anyhow::Result { - match cmd.spawn() { - Ok(t) => Ok(t), - Err(e) if e.kind() == std::io::ErrorKind::NotFound => { - let program = cmd.get_program().to_string_lossy().to_string(); - Err(e).context(format!( - "{program} not found, perhaps it must be installed." - )) - } - Err(e) => Err(e.into()), - } -} - -impl RunWith for std::process::Command { - fn run_with_processed_stdout( - mut self, - mut func: impl FnMut(std::io::Result) -> anyhow::Result<()>, - ) -> anyhow::Result<()> { - self.stdout(std::process::Stdio::piped()); - debug!("Spawning child {self:#?}..."); - let mut child = spawn(self)?; - let stdout = child - .stdout - .take() - .expect("not previously taken by this function"); - - let lines = BufReader::new(stdout).lines(); - for line in lines { - func(line)?; - } - - debug!("Waiting for child..."); - let status = child.wait()?; - if !status.success() { - anyhow::bail!("Child failed: {status}"); - } - Ok(()) - } - fn run_with_logged_stdout(self) -> anyhow::Result<()> { - self.run_with_processed_stdout(|line| { - let line = line?; - if !line.is_empty() { - debug!("Child said {line:?}."); - }; - Ok(()) - }) - } -} diff --git a/crates/acap-build/src/files.rs b/crates/acap-build/src/files.rs deleted file mode 100644 index 9c5c35cb..00000000 --- a/crates/acap-build/src/files.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! Files that are parsed or created in the course of creating an EAP. -pub(crate) mod cgi_conf; -pub(crate) mod manifest; -pub(crate) mod package_conf; -pub(crate) mod param_conf; diff --git a/crates/acap-build/src/files/cgi_conf.rs b/crates/acap-build/src/files/cgi_conf.rs deleted file mode 100644 index 8dc08d4a..00000000 --- a/crates/acap-build/src/files/cgi_conf.rs +++ /dev/null @@ -1,66 +0,0 @@ -//! Code for populating the `cgi.conf` file -use std::fmt::{Display, Formatter}; - -use log::debug; - -use crate::{ - files::manifest::Manifest, - json_ext, - json_ext::{MapExt, ValueExt}, -}; - -#[derive(Debug)] -enum Entry { - Fast { access: String, name: String }, - Other { access: String, name: String }, -} - -#[derive(Debug)] -pub(crate) struct CgiConf(Vec); - -impl CgiConf { - pub(crate) fn new(manifest: &Manifest) -> anyhow::Result> { - let conf = match manifest.try_find_http_config() { - Ok(v) => v, - Err(json_ext::Error::KeyNotFound(_)) => return Ok(None), - Err(e) => return Err(e.into()), - }; - - let mut entries = Vec::new(); - for obj in conf.iter() { - let obj = obj.try_to_object()?; - - let kind = obj.try_get_str("type")?; - if kind == "directory" { - debug!("Skipping httpConfig of type directory"); - continue; - } - - let name = obj.try_get_str("name")?.trim_start_matches('/').to_string(); - - let access = match obj.try_get_str("access")? { - "admin" => "administrator", - access => access, - } - .to_string(); - - entries.push(match kind { - "fastCgi" => Entry::Fast { access, name }, - _ => Entry::Other { access, name }, - }) - } - Ok(Some(Self(entries))) - } -} - -impl Display for CgiConf { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for cgi in &self.0 { - match &cgi { - Entry::Fast { access, name } => writeln!(f, "{access} /{name} fastCgi")?, - Entry::Other { access, name } => writeln!(f, "{access} /{name}")?, - } - } - Ok(()) - } -} diff --git a/crates/acap-build/src/files/manifest.rs b/crates/acap-build/src/files/manifest.rs deleted file mode 100644 index 34d4c5b7..00000000 --- a/crates/acap-build/src/files/manifest.rs +++ /dev/null @@ -1,132 +0,0 @@ -use anyhow::bail; -use log::debug; -use serde::Serialize; -use serde_json::{ser::PrettyFormatter, Map, Serializer, Value}; - -use crate::{ - json_ext, - json_ext::{MapExt, ValueExt}, - Architecture, -}; - -#[derive(Debug)] -pub(crate) struct Manifest(Value); - -impl Manifest { - pub(crate) fn new(manifest: Value, architecture: Architecture) -> anyhow::Result { - let mut manifest = Self(manifest); - let mut schema_version = manifest - .as_object()? - .try_get_str("schemaVersion")? - .to_string(); - - // Make it valid semver - for _ in 0..(2 - schema_version.chars().filter(|&c| c == '.').count()) { - schema_version.push_str(".0"); - } - let schema_version = semver::Version::parse(&schema_version)?; - if schema_version > semver::Version::new(1, 3, 0) { - let setup = manifest.try_find_setup_mut()?; - if let Some(a) = setup.get("architecture") { - if a != "all" && a != architecture.nickname() { - bail!( - "Architecture in manifest ({a}) is not compatible with built target ({:?})", - architecture - ); - } - } else { - debug!( - "Architecture not set in manifest, using {:?}", - &architecture - ); - setup.insert( - "architecture".to_string(), - Value::String(architecture.nickname().to_string()), - ); - } - } - Ok(manifest) - } - - pub(crate) fn as_object(&self) -> json_ext::Result<&Map> { - self.0.try_to_object() - } - - pub(crate) fn as_object_mut(&mut self) -> json_ext::Result<&mut Map> { - self.0.try_to_object_mut() - } - - // TODO: Consider generalizing this to something like `try_get_as_str(&self, path: &[&str])` - pub(crate) fn try_find_app_name(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("setup")? - .try_get_str("appName") - } - - pub(crate) fn try_find_architecture(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("setup")? - .try_get_str("architecture") - } - - pub(crate) fn try_find_friendly_name(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("setup")? - .try_get_str("friendlyName") - } - - pub(crate) fn try_find_http_config(&self) -> json_ext::Result<&Vec> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("configuration")? - .try_get_array("httpConfig") - } - - pub(crate) fn try_find_param_config(&self) -> json_ext::Result<&Vec> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("configuration")? - .try_get_array("paramConfig") - } - - pub(crate) fn try_find_post_install_script(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("installation")? - .try_get_str("postInstallScript") - } - - pub(crate) fn try_find_pre_uninstall_script(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("uninstallation")? - .try_get_str("preUninstallScript") - } - - pub(crate) fn try_find_version(&self) -> json_ext::Result<&str> { - self.as_object()? - .try_get_object("acapPackageConf")? - .try_get_object("setup")? - .try_get_str("version") - } - - pub(crate) fn try_find_setup_mut(&mut self) -> json_ext::Result<&mut Map> { - self.as_object_mut()? - .try_get_object_mut("acapPackageConf")? - .try_get_object_mut("setup") - } - - pub(crate) fn try_to_string(&self) -> anyhow::Result { - // This file is included in the EAP, so for as long as we want bit-exact output, we must - // take care to serialize the manifest the same way as the python implementation. - // let mut writer = BufWriter::new(String::new()); - let mut data = Vec::new(); - let mut serializer = - Serializer::with_formatter(&mut data, PrettyFormatter::with_indent(b" ")); - self.0.serialize(&mut serializer)?; - Ok(String::from_utf8(data)?) - } -} diff --git a/crates/acap-build/src/files/package_conf.rs b/crates/acap-build/src/files/package_conf.rs deleted file mode 100644 index 575bd290..00000000 --- a/crates/acap-build/src/files/package_conf.rs +++ /dev/null @@ -1,342 +0,0 @@ -//! Code for populating the `package.conf` file -use std::{ - collections::HashMap, - fmt::{Display, Formatter}, -}; - -use anyhow::{bail, Context}; -use log::debug; -use regex::Regex; -use semver::Version; -use serde_json::{Map, Value}; - -use crate::{files::manifest::Manifest, Architecture}; - -#[derive(Clone, Debug)] -pub(crate) struct PackageConf(HashMap<&'static str, String>); - -impl PackageConf { - pub(crate) fn new( - manifest: &Manifest, - other_files: &[&str], - default_arch: Architecture, - ) -> anyhow::Result { - let mut package_conf = Self(HashMap::new()); - package_conf.set_custom_from_manifest(manifest)?; - package_conf.set_custom_from_other_files(other_files)?; - package_conf.set_defaults(default_arch); - Ok(package_conf) - } - - fn set_custom_from_manifest(&mut self, manifest: &Manifest) -> anyhow::Result<()> { - let parameters: HashMap<_, _> = PARAMETERS - .iter() - .flat_map(|p| p.source.map(|s| (s, p.name))) - .collect(); - - let flat_manifest = flattened(manifest.as_object()?.clone()); - let valid_vendor_url = - Regex::new("(?:(?:http|https)://)?(.+)").expect("Hard-coded regex is valid"); - for (path, value) in flat_manifest { - match path.as_str() { - "acapPackageConf.setup.version" => { - let v = value - .as_str() - .context("acapPackageConf.setup.version is not a string")?; - let v = Version::parse(v)?; - self.0.insert("APPMAJORVERSION", v.major.to_string()); - self.0.insert("APPMINORVERSION", v.minor.to_string()); - self.0.insert("APPMICROVERSION", v.patch.to_string()); - } - "acapPackageConf.setup.vendorUrl" => { - let v = value - .as_str() - .context("acapPackageConf.setup.vendorUrl is not a string")?; - let Some(caps) = valid_vendor_url.captures(v) else { - bail!("Expected vendor url to match regex {:?}", valid_vendor_url) - }; - let domain_name = caps - .get(1) - .expect("Hard coded regex as exactly one capture group") - .as_str(); - - self.0.insert( - "VENDORHOMEPAGELINK", - format!(r#"{domain_name}"#), - ); - } - "acapPackageConf.configuration.httpConfig" => { - let v = value - .as_array() - .context("acapPackageConf.configuration.httpConfig is not an array")?; - if !v.is_empty() { - self.0.insert("HTTPCGIPATHS", "cgi.conf".to_string()); - } - } - path => { - if let Some(name) = parameters.get(path) { - let v = value - .as_str() - .with_context(|| format!("{path} is not a string"))? - .to_string(); - self.0.insert(name, v); - } else { - debug!("{path} skipped, no corresponding parameter in package.conf") - } - } - } - } - Ok(()) - } - - fn set_custom_from_other_files(&mut self, other_files: &[&str]) -> anyhow::Result<()> { - if !other_files.is_empty() { - self.0.insert("OTHERFILES", other_files.join(" ")); - } - Ok(()) - } - - fn set_defaults(&mut self, arch: Architecture) { - // If not set from the manifest, eap-create.sh would try to infer it. - self.0 - .entry("APPTYPE") - .or_insert(arch.nickname().to_string()); - - // If not set from the manifest, eap-create.sh would try to infer it from the exe. - // But we know that it must be set for the manifest to be valid. - // TODO: Fail explicitly if app name is not set. - let app_name = self.0.get("APPNAME").cloned().unwrap_or_default(); - // If not set from the manifest, eap-create.sh would fall back on the app name - self.0.entry("PACKAGENAME").or_insert(app_name); - - for Parameter { name, default, .. } in PARAMETERS { - if let Some(v) = default { - self.0.entry(name).or_insert(v.to_string()); - } - } - } -} - -impl Display for PackageConf { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for Parameter { name, .. } in PARAMETERS { - if let Some(value) = self.0.get(name) { - if name == "VENDORHOMEPAGELINK" { - writeln!(f, r#"{name}='{value}'"#)?; - } else { - writeln!(f, r#"{name}="{value}""#)?; - } - } - } - Ok(()) - } -} - -struct Parameter { - name: &'static str, - source: Option<&'static str>, - default: Option<&'static str>, -} - -// TODO: Consider generating this (semi-) automatically from its sources: -// - conversion.py -// - package-conf-parameters.cfg -const PARAMETERS: [Parameter; 25] = [ - Parameter { - name: "PACKAGENAME", - source: Some("acapPackageConf.setup.friendlyName"), - default: Some(""), - }, - // Not supported. The name of the ACAP will be taken from PACKAGENAME instead - Parameter { - name: "MENUNAME", - source: None, - default: None, - }, - Parameter { - name: "APPTYPE", - source: Some("acapPackageConf.setup.architecture"), - default: Some(""), - }, - Parameter { - name: "APPNAME", - source: Some("acapPackageConf.setup.appName"), - default: Some(""), - }, - Parameter { - name: "APPID", - source: Some("acapPackageConf.setup.appId"), - default: Some(""), - }, - // Not supported by manifest ACAP applications - Parameter { - name: "LICENSENAME", - source: None, - default: Some("Available"), - }, - Parameter { - name: "LICENSEPAGE", - source: Some("acapPackageConf.copyProtection.method"), - default: Some("none"), - }, - // If the application uses a custom licensing solution but still want to be able to use the - // standard list.cgi and WebUI to display license status. - // Use this option to allow the application to hook into the AXIS API. - Parameter { - name: "LICENSE_CHECK_ARGS", - source: Some("acapPackageConf.copyProtection.customOptions"), - default: None, - }, - Parameter { - name: "VENDOR", - source: Some("acapPackageConf.setup.vendor"), - default: Some("-"), - }, - Parameter { - name: "REQEMBDEVVERSION", - source: Some("acapPackageConf.setup.embeddedSdkVersion"), - default: Some("2.0"), - }, - Parameter { - name: "APPMAJORVERSION", - source: None, // Treated specially - default: Some("1"), - }, - Parameter { - name: "APPMINORVERSION", - source: None, // Treated specially - default: Some("0"), - }, - Parameter { - name: "APPMICROVERSION", - source: None, // Treated specially - default: Some("0"), - }, - Parameter { - name: "APPGRP", - source: Some("acapPackageConf.setup.user.group"), - default: Some("sdk"), - }, - Parameter { - name: "APPUSR", - source: Some("acapPackageConf.setup.user.username"), - default: Some("sdk"), - }, - Parameter { - name: "APPOPTS", - source: Some("acapPackageConf.setup.runOptions"), - default: Some(""), - }, - Parameter { - name: "OTHERFILES", - source: None, - default: Some(""), - }, - Parameter { - name: "SETTINGSPAGEFILE", - source: Some("acapPackageConf.configuration.settingPage"), - default: Some(""), - }, - // Special name on the link to the settings page is not supported - Parameter { - name: "SETTINGSPAGETEXT", - source: None, - default: Some(""), - }, - Parameter { - name: "VENDORHOMEPAGELINK", - source: None, // Treated specially - default: Some(""), - }, - // Pre-upgrade scripts are not supported - Parameter { - name: "PREUPGRADESCRIPT", - source: None, - default: Some(""), - }, - Parameter { - name: "POSTINSTALLSCRIPT", - source: Some("acapPackageConf.installation.postInstallScript"), - default: Some(""), - }, - Parameter { - name: "STARTMODE", - source: Some("acapPackageConf.setup.runMode"), - default: Some("never"), - }, - Parameter { - name: "HTTPCGIPATHS", - source: None, // Treated specially - default: Some(""), - }, - // Only supported for pre-installed applications - Parameter { - name: "AUTOSTART", - source: None, - default: None, - }, -]; - -fn flattened(root: Map) -> Vec<(String, Value)> { - let mut output = Vec::new(); - let mut values = vec![(String::new(), Value::Object(root))]; - while let Some((path, value)) = values.pop() { - match value { - Value::Null => output.push((path, Value::Null)), - Value::Bool(b) => output.push((path, Value::Bool(b))), - Value::Number(n) => output.push((path, Value::Number(n))), - Value::String(s) => output.push((path, Value::String(s.clone()))), - Value::Array(a) => output.push((path, Value::Array(a.clone()))), - Value::Object(o) => { - for (k, v) in o.into_iter().rev() { - debug_assert!(!k.contains('.')); - let mut p = path.clone(); - if !p.is_empty() { - p.push('.'); - } - p.push_str(&k.to_string()); - values.push((p, v)); - } - } - } - } - output -} - -#[cfg(test)] -mod tests { - use serde_json::{json, Value}; - - use super::*; - - #[test] - fn stringify_works_on_example() { - let value = json!( - { - "a": 1, - "b": [2, 3], - "c": [{"i":4}], - "d": {"j": 5}, - "e": { - "k": 6, - "l": {"x": 7}, - "m": 8, - }, - } - ); - let Value::Object(object) = value else { - panic!("expected object"); - }; - let actual = flattened(object); - let expected = vec![ - ("a".to_string(), json!(1)), - ("b".to_string(), json!([2, 3])), - ("c".to_string(), json!([{"i": 4}])), - ("d.j".to_string(), json!(5)), - ("e.k".to_string(), json!(6)), - ("e.l.x".to_string(), json!(7)), - ("e.m".to_string(), json!(8)), - ]; - assert_eq!(actual, expected); - } -} diff --git a/crates/acap-build/src/files/param_conf.rs b/crates/acap-build/src/files/param_conf.rs deleted file mode 100644 index df5349a0..00000000 --- a/crates/acap-build/src/files/param_conf.rs +++ /dev/null @@ -1,68 +0,0 @@ -//! Code for populating the `param.conf` file -use std::fmt::{Display, Formatter}; - -use crate::{ - files::manifest::Manifest, - json_ext, - json_ext::{MapExt, ValueExt}, -}; - -#[derive(Debug)] -enum Entry { - Typed { - name: String, - default: String, - kind: String, - }, - Untyped { - name: String, - default: String, - }, -} - -#[derive(Debug)] -pub(crate) struct ParamConf(Vec); - -impl ParamConf { - pub(crate) fn new(manifest: &Manifest) -> anyhow::Result> { - let param_config = match manifest.try_find_param_config() { - Ok(v) => v, - Err(json_ext::Error::KeyNotFound(_)) => return Ok(None), - Err(e) => return Err(e.into()), - }; - - let mut entries = Vec::new(); - for obj in param_config.iter() { - let obj = obj.try_to_object()?; - let name = obj.try_get_str("name")?.to_string(); - let default = obj.try_get_str("default")?.to_string(); - let kind = obj.try_get_str("type")?.to_string(); - - entries.push(match kind.is_empty() { - false => Entry::Typed { - name, - default, - kind, - }, - true => Entry::Untyped { name, default }, - }) - } - Ok(Some(Self(entries))) - } -} - -impl Display for ParamConf { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - for param in &self.0 { - match param { - Entry::Typed { - name, - default, - kind, - } => writeln!(f, r#"{name}="{default}" type="{kind}""#)?, - Entry::Untyped { name, default } => writeln!(f, "{name}={default}")?, - } - } - Ok(()) - } -} diff --git a/crates/acap-build/src/json_ext.rs b/crates/acap-build/src/json_ext.rs deleted file mode 100644 index 93d9a57a..00000000 --- a/crates/acap-build/src/json_ext.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Additional methods for [`serde_json`] types. -use std::{ - any::type_name_of_val, - fmt::{Debug, Display, Formatter}, -}; - -use serde_json::{Map, Value}; - -#[derive(Debug)] -pub(crate) enum Error { - KeyNotFound(&'static str), - WrongType { - key: &'static str, - expected: &'static str, - found: &'static str, - }, -} - -impl Display for Error { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - Error::KeyNotFound(key) => { - write!(f, "Expected key {key}") - } - Error::WrongType { - key, - expected, - found, - } => { - write!(f, "Expected {key} to be {expected}, but found {found}") - } - } - } -} - -impl std::error::Error for Error {} - -pub(crate) type Result = core::result::Result; -pub(crate) trait MapExt { - fn to_object(&self) -> &Map; - fn to_object_mut(&mut self) -> &mut Map; - - fn try_get_array(&self, key: &'static str) -> Result<&Vec> { - match self.to_object().get(key) { - Some(Value::Array(a)) => Ok(a), - Some(v) => Err(Error::WrongType { - key, - expected: "array", - found: type_name_of_val(v), - }), - None => Err(Error::KeyNotFound(key)), - } - } - - fn try_get_object(&self, key: &'static str) -> Result<&Map> { - match self.to_object().get(key) { - Some(Value::Object(o)) => Ok(o), - Some(v) => Err(Error::WrongType { - key, - expected: "object", - found: type_name_of_val(v), - }), - None => Err(Error::KeyNotFound(key)), - } - } - - fn try_get_object_mut(&mut self, key: &'static str) -> Result<&mut Map> { - match self.to_object_mut().get_mut(key) { - Some(Value::Object(o)) => Ok(o), - Some(v) => Err(Error::WrongType { - key, - expected: "object", - found: type_name_of_val(v), - }), - None => Err(Error::KeyNotFound(key)), - } - } - - fn try_get_str(&self, key: &'static str) -> Result<&str> { - match self.to_object().get(key) { - Some(Value::String(s)) => Ok(s), - Some(v) => Err(Error::WrongType { - key, - expected: "string", - found: type_name_of_val(v), - }), - None => Err(Error::KeyNotFound(key)), - } - } -} - -impl MapExt for Map { - fn to_object(&self) -> &Map { - self - } - fn to_object_mut(&mut self) -> &mut Map { - self - } -} - -pub(crate) trait ValueExt { - fn try_to_object(&self) -> Result<&Map>; - fn try_to_object_mut(&mut self) -> Result<&mut Map>; -} - -impl ValueExt for Value { - fn try_to_object(&self) -> Result<&Map> { - match self { - Value::Object(o) => Ok(o), - v => Err(Error::WrongType { - key: "?", - expected: "object", - found: type_name_of_val(v), - }), - } - } - fn try_to_object_mut(&mut self) -> Result<&mut Map> { - match self { - Value::Object(o) => Ok(o), - v => Err(Error::WrongType { - key: "?", - expected: "object", - found: type_name_of_val(v), - }), - } - } -} diff --git a/crates/acap-build/src/lib.rs b/crates/acap-build/src/lib.rs deleted file mode 100644 index 16c7f387..00000000 --- a/crates/acap-build/src/lib.rs +++ /dev/null @@ -1,477 +0,0 @@ -#![forbid(unsafe_code)] -//! Library for creating Embedded Application Packages (EAPs). -use std::{ - collections::HashSet, - env, - ffi::OsString, - fs, - io::Write, - os::unix::fs::PermissionsExt, - path::{Path, PathBuf}, - process::Command, - str::FromStr, -}; - -use anyhow::{anyhow, bail, Context}; -use command_utils::RunWith; -use log::{debug, info}; -use semver::Version; -use serde_json::Value; - -use crate::files::{ - cgi_conf::CgiConf, manifest::Manifest, package_conf::PackageConf, param_conf::ParamConf, -}; - -mod command_utils; -mod json_ext; - -mod files; - -// TODO: Find a better way to support reproducible builds -fn copy, Q: AsRef>( - src: P, - dst: Q, - copy_permissions: bool, -) -> anyhow::Result<()> { - let src = src.as_ref(); - let dst = dst.as_ref(); - if dst.symlink_metadata().is_ok() { - bail!("Path already exists {dst:?}"); - } - if src.is_symlink() { - // FIXME: Copy symlink in Rust - let mut cp = Command::new("cp"); - - if copy_permissions { - cp.arg("--preserve=mode"); - } - - cp.arg("-dn").arg(src.as_os_str()).arg(dst.as_os_str()); - - if !cp.status()?.success() { - bail!("Failed to copy symlink: {}", src.display()); - } - } else if copy_permissions { - fs::copy(src, dst)?; - } else { - let mut src = fs::File::open(src)?; - let mut dst = fs::File::create(dst)?; - std::io::copy(&mut src, &mut dst)?; - } - Ok(()) -} - -fn copy_recursively(src: &Path, dst: &Path, copy_permissions: bool) -> anyhow::Result<()> { - if !src.is_dir() { - copy(src, dst, copy_permissions)?; - debug!("Created reg {dst:?}"); - return Ok(()); - } - match fs::create_dir(dst) { - Ok(()) => { - debug!("Created dir {dst:?}"); - Ok(()) - } - Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => Ok(()), - Err(e) => Err(e), - }?; - for entry in fs::read_dir(src)? { - let entry = entry?; - copy_recursively( - &entry.path(), - &dst.join(entry.file_name()), - copy_permissions, - )?; - } - Ok(()) -} - -enum AcapBuildImpl { - Reference, - Equivalent, -} - -impl AcapBuildImpl { - fn from_env_or_default() -> anyhow::Result { - Ok(match env::var_os("ACAP_BUILD_IMPL") { - Some(v) if v.to_string_lossy() == "reference" => Self::Reference, - Some(v) if v.to_string_lossy() == "equivalent" => Self::Equivalent, - Some(v) => { - bail!("Expected ACAP_BUILD_IMPL to be 'reference' or 'equivalent', but found {v:?}") - } - None => Self::Reference, - }) - } -} - -pub struct AppBuilder<'a> { - preserve_permissions: bool, - staging_dir: &'a Path, - manifest: Manifest, - files: Vec, - default_architecture: Architecture, - app_name: String, -} - -impl<'a> AppBuilder<'a> { - pub fn new( - preserve_permissions: bool, - staging_dir: &'a Path, - manifest: &Path, - default_architecture: Architecture, - ) -> anyhow::Result { - let manifest: Value = serde_json::from_reader(fs::File::open(manifest)?)?; - let manifest = Manifest::new(manifest, default_architecture)?; - let app_name = manifest.try_find_app_name()?.to_string(); - Ok(Self { - preserve_permissions, - staging_dir, - manifest, - app_name, - files: Vec::new(), - default_architecture, - }) - } - - /// Add a file to the EAP. - pub fn add(&mut self, path: &Path) -> anyhow::Result<&mut Self> { - let name = path - .file_name() - .context("file has no name")? - .to_str() - .context("file name is not a string")?; - self.add_as(path, name)?; - Ok(self) - } - - /// Add all files in a directory to the EAP. - pub fn add_from(&mut self, dir: &Path) -> anyhow::Result<&mut Self> { - let mut entries = fs::read_dir(dir)? - .map(|res| res.map(|e| e.path())) - .collect::>>()?; - entries.sort(); - for entry in entries { - let name = entry - .file_name() - .context("file has no name")? - .to_str() - .context("file name is not a string")?; - self.add_as(&entry, name)?; - } - Ok(self) - } - - // TODO: Remove the file system copy - pub fn add_as(&mut self, path: &Path, name: &str) -> anyhow::Result { - let dst = self.staging_dir.join(name); - if dst.symlink_metadata().is_ok() { - bail!("Cannot add {path:?} because {name} already exists"); - } - copy_recursively(path, &dst, self.preserve_permissions)?; - self.files.push(name.to_string()); - if name == self.app_name && !self.preserve_permissions { - let mut permissions = fs::metadata(&dst)?.permissions(); - let mode = permissions.mode(); - permissions.set_mode(mode | 0o111); - fs::set_permissions(&dst, permissions)?; - } - debug!("Added {name} from {path:?}"); - Ok(dst) - } - - /// Add the **mandatory** executable to the EAP. - pub fn add_exe(&mut self, reg: &Path) -> anyhow::Result<&mut Self> { - // TODO: Consider refactoring or changing to avoid cloning. - let app_name = self.app_name.clone(); - self.add_as(reg, &app_name)?; - Ok(self) - } - - /// Build the EAP and return its path. - pub fn build(self) -> anyhow::Result { - match AcapBuildImpl::from_env_or_default()? { - AcapBuildImpl::Reference => { - debug!("Using acap-build"); - self.build_foreign() - } - AcapBuildImpl::Equivalent => { - // TODO: Implement validation. - info!("Bypassing acap-build, manifest will not be validated"); - self.build_native() - } - } - } - - fn build_foreign(self) -> anyhow::Result { - let Self { - staging_dir, - default_architecture, - manifest, - .. - } = &self; - - fs::File::create_new(staging_dir.join("manifest.json")) - .context("creating manifest.json")? - .write_all(manifest.try_to_string()?.as_bytes())?; - - let mut acap_build = Command::new("acap-build"); - acap_build.args(["--build", "no-build"]); - for file in self.additional_files() { - acap_build.args(["--additional-file", file]); - } - acap_build.arg("."); - - let mut sh = Command::new("sh"); - sh.current_dir(staging_dir); - - let env_setup = match default_architecture { - Architecture::Aarch64 => "environment-setup-cortexa53-crypto-poky-linux", - Architecture::Armv7hf => "environment-setup-cortexa9hf-neon-poky-linux-gnueabi", - }; - sh.args([ - "-c", - &format!(". /opt/axis/acapsdk/{env_setup} && {acap_build:?}"), - ]); - sh.run_with_logged_stdout()?; - - let mut apps = Vec::new(); - for entry in fs::read_dir(staging_dir)? { - let entry = entry?; - let path = entry.path(); - if let Some(extension) = path.extension() { - if extension.to_str() == Some("eap") { - apps.push(path); - } - } - } - let mut apps = apps.into_iter(); - let app = apps.next().context("Expected at least one artifact")?; - if let Some(second) = apps.next() { - bail!("Built at least one unexpected .eap file {second:?}") - } - Ok(app.file_name().context("file has no name")?.to_os_string()) - } - - fn build_native(self) -> anyhow::Result { - let Self { - staging_dir, - manifest, - - default_architecture, - app_name, - .. - } = &self; - let mtime = match env::var_os("SOURCE_DATE_EPOCH") { - Some(v) => v.into_string().map_err(|e| anyhow!("{e:?}"))?, - None => String::from_utf8(Command::new("date").arg("+%s").output()?.stdout)?, - }; - - // Compute file name - let package_name = match manifest.try_find_friendly_name() { - Ok(v) => v, - Err(json_ext::Error::KeyNotFound(_)) => app_name.as_str(), - Err(e) => return Err(e.into()), - } - .replace(' ', "_"); - let Version { - major, - minor, - patch, - .. - } = manifest.try_find_version().context("no version")?.parse()?; - - let arch = match manifest.try_find_architecture() { - Ok(v) => v, - Err(json_ext::Error::KeyNotFound(_)) => default_architecture.nickname(), - Err(e) => return Err(e.into()), - }; - let eap_file_name = format!("{package_name}_{major}_{minor}_{patch}_{arch}.eap"); - - // Generate derived files - let package_conf = - PackageConf::new(manifest, &self.other_files(), *default_architecture)?.to_string(); - fs::File::create_new(staging_dir.join("package.conf"))? - .write_all(package_conf.as_bytes())?; - - let param_conf = match ParamConf::new(manifest)? { - None => { - // If there is no param.conf, `eap-create.sh` creates one - debug!("Creating empty param.conf"); - String::new() - } - Some(v) => v.to_string(), - }; - fs::File::create_new(staging_dir.join("param.conf"))?.write_all(param_conf.as_bytes())?; - - match CgiConf::new(manifest)? { - None => { - debug!("Skipping cgi.conf") - } - Some(cgi_conf) => { - fs::File::create_new(staging_dir.join("cgi.conf"))? - .write_all(cgi_conf.to_string().as_bytes())?; - } - } - - // This file is included in the EAP, so for as long as we want bit-exact output, we must - // take care to serialize the manifest the same way as the python implementation. - let manifest_file = staging_dir.join("manifest.json"); - fs::File::create_new(&manifest_file)?.write_all(manifest.try_to_string()?.as_bytes())?; - // Replicate the permissions that temporary files get by default. - let mut permissions = fs::metadata(&manifest_file)?.permissions(); - permissions.set_mode(0o600); - fs::set_permissions(&manifest_file, permissions)?; - - // Create the archive - let mut tar = Command::new("tar"); - tar.args(["--exclude", "*~"]) - .args(["--file", &eap_file_name]) - .args(["--format", "gnu"]) - .args(["--group", "0"]) - .args(["--mtime", &format!("@{mtime}")]) - .args(["--owner", "0"]) - .args(["--sort", "name"]) - .args(["--use-compress-program", "gzip --no-name -9"]) - .arg("--create") - .arg("--numeric-owner") - .arg("--exclude-vcs"); - - for name in self.section_1_files() { - if staging_dir.join(name).symlink_metadata().is_ok() { - tar.arg(name); - } - } - - tar.args(self.other_files()); - - // TODO: Consider implementing support for `httpd.conf.local.*` and `mime.types.local.*`. - - for name in self.section_4_files() { - if staging_dir.join(name).symlink_metadata().is_ok() { - tar.arg(name); - } - } - - tar.arg("--verbose"); - tar.current_dir(staging_dir); - tar.run_with_logged_stdout()?; - - Ok(OsString::from(eap_file_name)) - } - - // These sections are probably relevant only for the equivalent and reference implementations; - // Once unpacked on device the order of files or the reason they were included is not important - // (even though some files are nonetheless treated specially). - // The sections don't have any semantics, they are just partitions that can be composed to - // create meaningful or useful lists of names. - - fn section_1_files(&self) -> Vec<&str> { - [ - Some(self.app_name.as_str()), - Some("package.conf"), - Some("param.conf"), - Some("LICENSE"), - Some("manifest.json"), - self.manifest.try_find_post_install_script().ok(), - ] - .into_iter() - .flatten() - .collect() - } - - fn section_2_files(&self) -> Vec<&str> { - let known_files: HashSet<_> = [ - self.section_1_files(), - self.section_3_files(), - self.section_4_files(), - ] - .into_iter() - .flatten() - .collect(); - - self.files - .iter() - .map(String::as_str) - .filter(|f| !known_files.contains(f)) - .collect() - } - - fn section_3_files(&self) -> Vec<&str> { - [self.manifest.try_find_pre_uninstall_script().ok()] - .into_iter() - .flatten() - .collect() - } - - fn section_4_files(&self) -> Vec<&str> { - ["html", "declarations", "lib", "cgi.conf"] - .into_iter() - .collect() - } - - /// Additional files for the reference implementation. - fn additional_files(&self) -> Vec<&str> { - self.section_2_files() - } - - /// Other files for the `package.conf` file. - fn other_files(&self) -> Vec<&str> { - [self.section_2_files(), self.section_3_files()].concat() - } - - /// Return the name of files that must be added using [`Self::add`]. - pub fn mandatory_files(&self) -> Vec { - [ - Some(self.app_name.as_str()), - Some("LICENSE"), - self.manifest.try_find_post_install_script().ok(), - self.manifest.try_find_pre_uninstall_script().ok(), - ] - .into_iter() - .flatten() - .map(str::to_string) - .collect() - } - - /// Return the name of files that should be added using [`Self::add`]. - pub fn optional_files(&self) -> Vec { - ["html", "declarations", "lib"] - .into_iter() - .map(str::to_string) - .collect() - } -} - -#[derive(Clone, Copy, Debug)] -pub enum Architecture { - Aarch64, - Armv7hf, -} - -impl Architecture { - pub fn triple(&self) -> &'static str { - match self { - Architecture::Aarch64 => "aarch64-unknown-linux-gnu", - Architecture::Armv7hf => "thumbv7neon-unknown-linux-gnueabihf", - } - } - - pub fn nickname(&self) -> &'static str { - match self { - Self::Aarch64 => "aarch64", - Self::Armv7hf => "armv7hf", - } - } -} - -impl FromStr for Architecture { - type Err = anyhow::Error; - - fn from_str(s: &str) -> Result { - match s { - "aarch64" => Ok(Self::Aarch64), - "arm" => Ok(Self::Armv7hf), - _ => Err(anyhow::anyhow!("Unrecognized variant {s}")), - } - } -} diff --git a/crates/cargo-acap-build/Cargo.toml b/crates/cargo-acap-build/Cargo.toml index 24a8e4a6..53a1bf7b 100644 --- a/crates/cargo-acap-build/Cargo.toml +++ b/crates/cargo-acap-build/Cargo.toml @@ -5,7 +5,7 @@ edition = "2021" [dependencies] anyhow = { workspace = true } -clap = { workspace = true, features = ["derive"] } +clap = { workspace = true, features = ["derive", "env"] } dirs = { workspace = true } env_logger = { workspace = true, default-features = true } log = { workspace = true } @@ -13,5 +13,5 @@ serde = { workspace = true, features = ["derive"] } serde_json = { workspace = true } tempfile = { workspace = true } -acap-build = { workspace = true } +rs4a-eap = { workspace = true } cli-version = { workspace = true } diff --git a/crates/cargo-acap-build/src/cargo_acap.rs b/crates/cargo-acap-build/src/cargo_acap.rs index 4d2100c0..15a88f69 100644 --- a/crates/cargo-acap-build/src/cargo_acap.rs +++ b/crates/cargo-acap-build/src/cargo_acap.rs @@ -1,13 +1,13 @@ -/// This module bridges the gap between `cargo` and `acap-build` using the application structure +/// This module bridges the gap between `cargo` and `rs4a-eap` using the application structure /// conventions detailed in [`crate`]. use std::{ collections::HashMap, path::{Path, PathBuf}, }; -use acap_build::AppBuilder; use anyhow::{bail, Context}; use log::{debug, error, warn}; +use rs4a_eap::{AppBuilder, Mtime}; use crate::{ cargo::{cargo_command, get_cargo_metadata, json_message::JsonMessage}, @@ -21,10 +21,12 @@ pub enum Artifact { Eap { path: PathBuf, name: String }, Exe { path: PathBuf }, } + pub fn build_and_pack( arch: Architecture, args: &[&str], manifest_path: Option<&Path>, + mtime: Mtime, ) -> anyhow::Result> { // If user supplies a target we lose track of which target is currently being built assert!(!args.contains(&"--target")); @@ -78,6 +80,7 @@ pub fn build_and_pack( manifest_path, executable, out_dir, + mtime, )?, name: target.name, }); @@ -114,6 +117,7 @@ fn pack( manifest_path: PathBuf, executable: PathBuf, out_dir: Option, + mtime: Mtime, ) -> anyhow::Result { let mut staging_dir = cargo_target_dir.join(arch.nickname()); if !staging_dir.is_dir() { @@ -138,6 +142,7 @@ fn pack( debug!("Creating app builder"); let mut app_builder = AppBuilder::new(false, &staging_dir, &manifest, arch)?; + app_builder.mtime(mtime); app_builder.add_exe(&executable)?; // TODO: Consider providing defaults for more files. diff --git a/crates/cargo-acap-build/src/lib.rs b/crates/cargo-acap-build/src/lib.rs index 539b2679..08823abd 100644 --- a/crates/cargo-acap-build/src/lib.rs +++ b/crates/cargo-acap-build/src/lib.rs @@ -7,10 +7,12 @@ use std::{ path::{Path, PathBuf}, }; -pub use acap_build::Architecture; pub use cargo::get_cargo_metadata; pub use cargo_acap::Artifact; use log::debug; +pub use rs4a_eap::Architecture; +use rs4a_eap::Mtime; + mod cargo; mod cargo_acap; mod command_utils; @@ -91,12 +93,17 @@ impl AppBuilder { self } - pub fn execute(&mut self) -> anyhow::Result> { + pub fn execute(&mut self, mtime: Mtime) -> anyhow::Result> { let args: Vec<_> = self.args.iter().map(String::as_str).collect(); let manifest_path = self.manifest_path.as_deref(); let mut artifacts = Vec::new(); for target in &self.targets { - artifacts.extend(cargo_acap::build_and_pack(*target, &args, manifest_path)?); + artifacts.extend(cargo_acap::build_and_pack( + *target, + &args, + manifest_path, + mtime, + )?); } if let Some(artifact_dir) = self.artifact_dir.as_deref() { copy_final_artifacts(&artifacts, artifact_dir)?; diff --git a/crates/cargo-acap-build/src/main.rs b/crates/cargo-acap-build/src/main.rs index 29fc7bcc..dce32f11 100644 --- a/crates/cargo-acap-build/src/main.rs +++ b/crates/cargo-acap-build/src/main.rs @@ -5,6 +5,7 @@ use cargo_acap_build::{get_cargo_metadata, AppBuilder, Architecture}; use clap::{Parser, ValueEnum}; use cli_version::version_with_commit_id; use log::debug; +use rs4a_eap::Mtime; // TODO: Figure out what to call this. // This is sometimes called just "architecture" but in other contexts arch refers to the first @@ -24,6 +25,10 @@ impl From for Architecture { } } +fn parse_mtime(s: &str) -> anyhow::Result { + s.trim().parse::()?.try_into() +} + /// Build app using cargo /// /// Some defaults deviate from Cargo: @@ -37,6 +42,11 @@ struct Cli { /// Can be used multiple times. #[arg(long)] target: Vec, + /// Time to stamp on every archive member, in seconds after the Unix epoch. + /// + /// Defaults to the current time. + #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] + source_date_epoch: Option, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented. @@ -68,7 +78,7 @@ fn build_and_copy(cli: Cli) -> anyhow::Result<()> { AppBuilder::from_targets(cli.targets()) .args(args) .artifact_dir(get_cargo_metadata(None)?.target_directory.join("acap")) - .execute()?; + .execute(cli.source_date_epoch.unwrap_or_default())?; Ok(()) } diff --git a/crates/cargo-acap-sdk/Cargo.toml b/crates/cargo-acap-sdk/Cargo.toml index 30a9fb76..31611813 100644 --- a/crates/cargo-acap-sdk/Cargo.toml +++ b/crates/cargo-acap-sdk/Cargo.toml @@ -11,6 +11,7 @@ dirs = { workspace = true } env_logger = { workspace = true, default-features = true } log = { workspace = true } reqwest = { workspace = true, default-features = true, features = ["default-tls"] } +rs4a-eap = { workspace = true } tokio = { workspace = true, features = ["full"] } url = { workspace = true } diff --git a/crates/cargo-acap-sdk/src/commands/build_command.rs b/crates/cargo-acap-sdk/src/commands/build_command.rs index baf8ced5..6c9f042d 100644 --- a/crates/cargo-acap-sdk/src/commands/build_command.rs +++ b/crates/cargo-acap-sdk/src/commands/build_command.rs @@ -16,6 +16,7 @@ impl BuildCommand { ResolvedBuildOptions { target, manifest_path, + source_date_epoch, mut args, }, } = self; @@ -41,7 +42,7 @@ impl BuildCommand { .target_directory .join("acap"), ) - .execute()?; + .execute(source_date_epoch.unwrap_or_default())?; Ok(()) } } diff --git a/crates/cargo-acap-sdk/src/commands/install_command.rs b/crates/cargo-acap-sdk/src/commands/install_command.rs index 80a26f78..94576973 100644 --- a/crates/cargo-acap-sdk/src/commands/install_command.rs +++ b/crates/cargo-acap-sdk/src/commands/install_command.rs @@ -25,6 +25,7 @@ impl InstallCommand { let ResolvedBuildOptions { target, manifest_path, + source_date_epoch, mut args, } = build_options.resolve(&deploy_options).await?; @@ -43,7 +44,7 @@ impl InstallCommand { if let Some(ref path) = manifest_path { builder.manifest_path(path); } - let artifacts = builder.execute()?; + let artifacts = builder.execute(source_date_epoch.unwrap_or_default())?; // TODO: Handle the case where multiple artifacts of the same kind have the same name. for artifact in artifacts { diff --git a/crates/cargo-acap-sdk/src/commands/run_command.rs b/crates/cargo-acap-sdk/src/commands/run_command.rs index 6f62582d..ae93afe7 100644 --- a/crates/cargo-acap-sdk/src/commands/run_command.rs +++ b/crates/cargo-acap-sdk/src/commands/run_command.rs @@ -21,6 +21,7 @@ impl RunCommand { let ResolvedBuildOptions { target, manifest_path, + source_date_epoch, args, } = build_options.resolve(&deploy_options).await?; @@ -39,7 +40,7 @@ impl RunCommand { if let Some(ref path) = manifest_path { builder.manifest_path(path); } - let artifacts = builder.execute()?; + let artifacts = builder.execute(source_date_epoch.unwrap_or_default())?; for artifact in artifacts { let envs = vec![("RUST_LOG", "debug"), ("RUST_LOG_STYLE", "always")] .into_iter() diff --git a/crates/cargo-acap-sdk/src/commands/test_command.rs b/crates/cargo-acap-sdk/src/commands/test_command.rs index c9c43818..19d51938 100644 --- a/crates/cargo-acap-sdk/src/commands/test_command.rs +++ b/crates/cargo-acap-sdk/src/commands/test_command.rs @@ -21,6 +21,7 @@ impl TestCommand { let ResolvedBuildOptions { target, manifest_path, + source_date_epoch, args: mut build_args, } = build_options.resolve(&deploy_options).await?; @@ -41,7 +42,7 @@ impl TestCommand { if let Some(ref path) = manifest_path { builder.manifest_path(path); } - let artifacts = builder.execute()?; + let artifacts = builder.execute(source_date_epoch.unwrap_or_default())?; for artifact in artifacts { debug!("Running {:?}", artifact); diff --git a/crates/cargo-acap-sdk/src/main.rs b/crates/cargo-acap-sdk/src/main.rs index 2b5c6e44..3e12e98c 100644 --- a/crates/cargo-acap-sdk/src/main.rs +++ b/crates/cargo-acap-sdk/src/main.rs @@ -6,6 +6,7 @@ use cargo_acap_build::Architecture; use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; use cli_version::version_with_commit_id; use log::debug; +use rs4a_eap::Mtime; use url::Host; use crate::commands::{ @@ -69,12 +70,21 @@ enum Commands { Completions(CompletionsCommand), } +fn parse_mtime(s: &str) -> anyhow::Result { + s.trim().parse::()?.try_into() +} + // TODO: Include package selection for better completions and help messages. #[derive(clap::Args, Debug, Clone)] struct BuildOptions { /// Path to Cargo.toml. #[arg(long)] manifest_path: Option, + /// Time to stamp on every archive member, in seconds after the Unix epoch. + /// + /// Defaults to the current time. + #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] + source_date_epoch: Option, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented. @@ -85,6 +95,7 @@ impl BuildOptions { async fn resolve(self, deploy_options: &DeployOptions) -> anyhow::Result { let Self { manifest_path, + source_date_epoch, args, } = self; // TODO: Consider using `get_properties` instead. @@ -99,6 +110,7 @@ impl BuildOptions { Ok(ResolvedBuildOptions { target, manifest_path, + source_date_epoch, args, }) } @@ -112,6 +124,11 @@ pub struct ResolvedBuildOptions { /// Path to Cargo.toml. #[arg(long)] manifest_path: Option, + /// Time to stamp on every archive member, in seconds after the Unix epoch. + /// + /// Defaults to the current time. + #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] + source_date_epoch: Option, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented. From cf57c34fdc0ea717e95962f41a293bdd1883033c Mon Sep 17 00:00:00 2001 From: AP Ljungquist Date: Thu, 23 Jul 2026 07:23:52 +0200 Subject: [PATCH 2/2] Upgrade rs4a-eap version --- Cargo.lock | 26 ++++++++++--------- Cargo.toml | 2 +- crates/cargo-acap-build/src/cargo_acap.rs | 18 ++++++++++--- crates/cargo-acap-build/src/lib.rs | 13 +++++++++- crates/cargo-acap-build/src/main.rs | 6 ++++- .../src/commands/build_command.rs | 2 ++ .../src/commands/install_command.rs | 2 ++ .../src/commands/run_command.rs | 2 ++ .../src/commands/test_command.rs | 2 ++ crates/cargo-acap-sdk/src/main.rs | 10 ++++++- 10 files changed, 64 insertions(+), 19 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 06a22b03..582ad859 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -106,9 +106,9 @@ dependencies = [ [[package]] name = "anstyle" -version = "1.0.6" +version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" +checksum = "940b3a0ca603d1eade50a4846a2afffd5ef57a9feac2c0e2ec2e14f9ead76000" [[package]] name = "anstyle-parse" @@ -635,9 +635,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.13" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" +checksum = "2797f34da339ce31042b27d23607e051786132987f595b02ba4f6a6dffb7030a" dependencies = [ "clap_builder", "clap_derive", @@ -645,9 +645,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.13" +version = "4.5.60" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" +checksum = "24a241312cea5059b13574bb9b3861cabf758b879c15190b37b6d6fd63ab6876" dependencies = [ "anstream", "anstyle", @@ -666,9 +666,9 @@ dependencies = [ [[package]] name = "clap_derive" -version = "4.5.13" +version = "4.5.55" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" +checksum = "a92793da1a46a5f2a02a6f4c46c6496b28c43638adea8306fcb0caa1634f24e5" dependencies = [ "heck", "proc-macro2", @@ -678,9 +678,9 @@ dependencies = [ [[package]] name = "clap_lex" -version = "0.7.1" +version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b82cf0babdbd58558212896d1a4272303a57bdb245c2bf1147185fb45640e70" +checksum = "c8d4a3bb8b1e0c1050499d1815f5ab16d04f0959b233085fb31653fbfc9d98f9" [[package]] name = "cli-version" @@ -1694,7 +1694,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" dependencies = [ "cfg-if", - "windows-targets 0.52.6", + "windows-targets 0.48.5", ] [[package]] @@ -2406,9 +2406,11 @@ dependencies = [ [[package]] name = "rs4a-eap" version = "0.1.0" -source = "git+https://github.com/apljungquist/rs4acap.git?rev=694c2066cca141b2b064ce988d4f5bb887a892b0#694c2066cca141b2b064ce988d4f5bb887a892b0" +source = "git+https://github.com/apljungquist/rs4acap.git?rev=5a8b7d5627e29608e94da0280c6b25fa1d8aefe5#5a8b7d5627e29608e94da0280c6b25fa1d8aefe5" dependencies = [ "anyhow", + "clap", + "flate2", "jsonschema", "log", "regex", diff --git a/Cargo.toml b/Cargo.toml index aa753252..7a02a482 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,7 +64,7 @@ licensekey = { path = "crates/licensekey" } licensekey-sys = { path = "crates/licensekey-sys" } mdb = { path = "crates/mdb" } mdb-sys = { path = "crates/mdb-sys" } -rs4a-eap = { git = "https://github.com/apljungquist/rs4acap.git", rev = "694c2066cca141b2b064ce988d4f5bb887a892b0" } +rs4a-eap = { git = "https://github.com/apljungquist/rs4acap.git", rev = "5a8b7d5627e29608e94da0280c6b25fa1d8aefe5", features = ["clap"] } vdo = { path = "crates/vdo" } vdo-sys = { path = "crates/vdo-sys" } diff --git a/crates/cargo-acap-build/src/cargo_acap.rs b/crates/cargo-acap-build/src/cargo_acap.rs index 15a88f69..6393053e 100644 --- a/crates/cargo-acap-build/src/cargo_acap.rs +++ b/crates/cargo-acap-build/src/cargo_acap.rs @@ -13,7 +13,7 @@ use crate::{ cargo::{cargo_command, get_cargo_metadata, json_message::JsonMessage}, command_utils::RunWith, files::license, - Architecture, + AcapBuildImpl, Architecture, }; #[derive(Debug)] @@ -22,8 +22,17 @@ pub enum Artifact { Exe { path: PathBuf }, } +/// The Rust target triple used to build for the given architecture. +fn target_triple(arch: Architecture) -> &'static str { + match arch { + Architecture::Aarch64 => "aarch64-unknown-linux-gnu", + Architecture::Armv7hf => "thumbv7neon-unknown-linux-gnueabihf", + } +} + pub fn build_and_pack( arch: Architecture, + acap_build_impl: AcapBuildImpl, args: &[&str], manifest_path: Option<&Path>, mtime: Mtime, @@ -33,7 +42,7 @@ pub fn build_and_pack( let mut cargo = cargo_command(manifest_path); cargo.arg("build"); - cargo.args(["--target", arch.triple()]); + cargo.args(["--target", target_triple(arch)]); cargo.args(["--message-format", "json-render-diagnostics"]); @@ -77,6 +86,7 @@ pub fn build_and_pack( path: pack( &cargo_target_directory, arch, + acap_build_impl, manifest_path, executable, out_dir, @@ -114,12 +124,13 @@ pub fn build_and_pack( fn pack( cargo_target_dir: &Path, arch: Architecture, + acap_build_impl: AcapBuildImpl, manifest_path: PathBuf, executable: PathBuf, out_dir: Option, mtime: Mtime, ) -> anyhow::Result { - let mut staging_dir = cargo_target_dir.join(arch.nickname()); + let mut staging_dir = cargo_target_dir.join(arch.as_str()); if !staging_dir.is_dir() { std::fs::create_dir(&staging_dir)?; } @@ -142,6 +153,7 @@ fn pack( debug!("Creating app builder"); let mut app_builder = AppBuilder::new(false, &staging_dir, &manifest, arch)?; + app_builder.implementation(acap_build_impl); app_builder.mtime(mtime); app_builder.add_exe(&executable)?; diff --git a/crates/cargo-acap-build/src/lib.rs b/crates/cargo-acap-build/src/lib.rs index 08823abd..aa581bf3 100644 --- a/crates/cargo-acap-build/src/lib.rs +++ b/crates/cargo-acap-build/src/lib.rs @@ -10,7 +10,7 @@ use std::{ pub use cargo::get_cargo_metadata; pub use cargo_acap::Artifact; use log::debug; -pub use rs4a_eap::Architecture; +pub use rs4a_eap::{AcapBuildImpl, Architecture}; use rs4a_eap::Mtime; mod cargo; @@ -23,6 +23,7 @@ pub struct AppBuilder { args: Vec, artifact_dir: Option, manifest_path: Option, + acap_build_impl: AcapBuildImpl, } impl AppBuilder { @@ -36,9 +37,18 @@ impl AppBuilder { args: Vec::new(), artifact_dir: None, manifest_path: None, + acap_build_impl: AcapBuildImpl::Compatible, } } + /// Select the implementation used to package the EAP. + /// + /// Defaults to [`AcapBuildImpl::Compatible`]. + pub fn implementation(&mut self, acap_build_impl: AcapBuildImpl) -> &mut Self { + self.acap_build_impl = acap_build_impl; + self + } + /// Add arguments that will be passed through to cargo. /// /// # Panics @@ -100,6 +110,7 @@ impl AppBuilder { for target in &self.targets { artifacts.extend(cargo_acap::build_and_pack( *target, + self.acap_build_impl, &args, manifest_path, mtime, diff --git a/crates/cargo-acap-build/src/main.rs b/crates/cargo-acap-build/src/main.rs index dce32f11..cf9bd1b1 100644 --- a/crates/cargo-acap-build/src/main.rs +++ b/crates/cargo-acap-build/src/main.rs @@ -1,7 +1,7 @@ #![forbid(unsafe_code)] use std::fs::File; -use cargo_acap_build::{get_cargo_metadata, AppBuilder, Architecture}; +use cargo_acap_build::{get_cargo_metadata, AcapBuildImpl, AppBuilder, Architecture}; use clap::{Parser, ValueEnum}; use cli_version::version_with_commit_id; use log::debug; @@ -47,6 +47,9 @@ struct Cli { /// Defaults to the current time. #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] source_date_epoch: Option, + /// Implementation used to package the EAP. + #[clap(long, env = "ACAP_BUILD_IMPL", default_value_t = AcapBuildImpl::Compatible)] + acap_build_impl: AcapBuildImpl, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented. @@ -77,6 +80,7 @@ fn build_and_copy(cli: Cli) -> anyhow::Result<()> { AppBuilder::from_targets(cli.targets()) .args(args) + .implementation(cli.acap_build_impl) .artifact_dir(get_cargo_metadata(None)?.target_directory.join("acap")) .execute(cli.source_date_epoch.unwrap_or_default())?; Ok(()) diff --git a/crates/cargo-acap-sdk/src/commands/build_command.rs b/crates/cargo-acap-sdk/src/commands/build_command.rs index 6c9f042d..ca6508f9 100644 --- a/crates/cargo-acap-sdk/src/commands/build_command.rs +++ b/crates/cargo-acap-sdk/src/commands/build_command.rs @@ -17,6 +17,7 @@ impl BuildCommand { target, manifest_path, source_date_epoch, + acap_build_impl, mut args, }, } = self; @@ -33,6 +34,7 @@ impl BuildCommand { let mut builder = AppBuilder::from_targets([Architecture::from(target)]); builder.args(args); + builder.implementation(acap_build_impl); if let Some(ref path) = manifest_path { builder.manifest_path(path); } diff --git a/crates/cargo-acap-sdk/src/commands/install_command.rs b/crates/cargo-acap-sdk/src/commands/install_command.rs index 94576973..4af75434 100644 --- a/crates/cargo-acap-sdk/src/commands/install_command.rs +++ b/crates/cargo-acap-sdk/src/commands/install_command.rs @@ -26,6 +26,7 @@ impl InstallCommand { target, manifest_path, source_date_epoch, + acap_build_impl, mut args, } = build_options.resolve(&deploy_options).await?; @@ -41,6 +42,7 @@ impl InstallCommand { let mut builder = AppBuilder::from_targets([Architecture::from(target)]); builder.args(args); + builder.implementation(acap_build_impl); if let Some(ref path) = manifest_path { builder.manifest_path(path); } diff --git a/crates/cargo-acap-sdk/src/commands/run_command.rs b/crates/cargo-acap-sdk/src/commands/run_command.rs index ae93afe7..32c36c02 100644 --- a/crates/cargo-acap-sdk/src/commands/run_command.rs +++ b/crates/cargo-acap-sdk/src/commands/run_command.rs @@ -22,6 +22,7 @@ impl RunCommand { target, manifest_path, source_date_epoch, + acap_build_impl, args, } = build_options.resolve(&deploy_options).await?; @@ -37,6 +38,7 @@ impl RunCommand { let mut builder = AppBuilder::from_targets([Architecture::from(target)]); builder.args(args); + builder.implementation(acap_build_impl); if let Some(ref path) = manifest_path { builder.manifest_path(path); } diff --git a/crates/cargo-acap-sdk/src/commands/test_command.rs b/crates/cargo-acap-sdk/src/commands/test_command.rs index 19d51938..45341408 100644 --- a/crates/cargo-acap-sdk/src/commands/test_command.rs +++ b/crates/cargo-acap-sdk/src/commands/test_command.rs @@ -22,6 +22,7 @@ impl TestCommand { target, manifest_path, source_date_epoch, + acap_build_impl, args: mut build_args, } = build_options.resolve(&deploy_options).await?; @@ -39,6 +40,7 @@ impl TestCommand { let mut builder = AppBuilder::from_targets([Architecture::from(target)]); builder.args(build_args); + builder.implementation(acap_build_impl); if let Some(ref path) = manifest_path { builder.manifest_path(path); } diff --git a/crates/cargo-acap-sdk/src/main.rs b/crates/cargo-acap-sdk/src/main.rs index 3e12e98c..5ddacf91 100644 --- a/crates/cargo-acap-sdk/src/main.rs +++ b/crates/cargo-acap-sdk/src/main.rs @@ -2,7 +2,7 @@ use std::{ffi::OsString, fs::File, path::PathBuf, str::FromStr}; use acap_vapix::{applications_control, basic_device_info, HttpClient}; -use cargo_acap_build::Architecture; +use cargo_acap_build::{AcapBuildImpl, Architecture}; use clap::{CommandFactory, Parser, Subcommand, ValueEnum}; use cli_version::version_with_commit_id; use log::debug; @@ -85,6 +85,9 @@ struct BuildOptions { /// Defaults to the current time. #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] source_date_epoch: Option, + /// Implementation used to package the EAP. + #[clap(long, env = "ACAP_BUILD_IMPL", default_value_t = AcapBuildImpl::Compatible)] + acap_build_impl: AcapBuildImpl, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented. @@ -96,6 +99,7 @@ impl BuildOptions { let Self { manifest_path, source_date_epoch, + acap_build_impl, args, } = self; // TODO: Consider using `get_properties` instead. @@ -111,6 +115,7 @@ impl BuildOptions { target, manifest_path, source_date_epoch, + acap_build_impl, args, }) } @@ -129,6 +134,9 @@ pub struct ResolvedBuildOptions { /// Defaults to the current time. #[clap(long, env = "SOURCE_DATE_EPOCH", value_parser = parse_mtime)] source_date_epoch: Option, + /// Implementation used to package the EAP. + #[clap(long, env = "ACAP_BUILD_IMPL", default_value_t = AcapBuildImpl::Compatible)] + acap_build_impl: AcapBuildImpl, /// Pass additional arguments to `cargo build`. /// /// Beware that not all incompatible arguments have been documented.