diff --git a/CHANGELOG.md b/CHANGELOG.md index 3930edd423..612b8e850b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,8 @@ Detailed changelog for Perry. See CLAUDE.md for concise summaries. +## v0.5.909 — feat: add `libDirs` to native library manifest (#762). New optional `targets..libDirs` field in `package.json`'s `perry.nativeLibrary` block — array of linker search paths emitted ahead of the `libs` list. **Surface.** Documented in `docs/src/native-libraries/manifest-v1.md` alongside the existing `libs` / `frameworks` / `pkgConfig` entries. Use it when a wrapper ships a vendored `.a`/`.dylib` outside the cargo crate (the standard `lib_name` lookup probes `target//release/`); previously the only escape hatch was an absolute path glued into `libs` via a workaround. **Plumbing.** Three edits — (1) `TargetNativeConfig.lib_dirs: Vec` field added in `crates/perry/src/commands/compile.rs`; (2) `parse_native_library_manifest` parses `libDirs` and anchors relative entries to `package_dir` via `package_dir.join(p)` — mirrors the existing `swift_sources` / `metal_sources` pattern so a `"./vendor/lib"` in the manifest resolves against the wrapper's directory, not the user's cwd (absolute entries pass through unchanged since `PathBuf::join` ignores the base on an absolute right-hand side); (3) `build_and_run_link` in `crates/perry/src/commands/compile/link.rs` emits each entry between the `frameworks` loop and the `libs` loop — `-L` on every non-Windows target, `/LIBPATH:` when `is_windows` (mirrors the `libs` loop's existing `{lib}.lib` vs `-l{lib}` MSVC/Unix split, so a `targets.windows.libDirs` entry actually resolves the `{lib}.lib` lookups instead of being a silent no-op against link.exe). **Validation.** Two new unit tests in `crates/perry/src/commands/compile/resolve.rs::manifest_parse_tests` — `lib_dirs_relative_paths_anchored_to_package_dir` round-trips a manifest with both `"vendor/lib"` and `"/abs/path"` and asserts the package_dir-anchored / pass-through behavior; `lib_dirs_defaults_to_empty_when_absent` confirms the field is optional. `cargo build --release` clean; `cargo test -p perry --lib` green. **Version-bump note.** Renumbered v0.5.908 → v0.5.909 because #760 (test: argon2 + ethers parity) landed on main mid-review and took the v0.5.908 slot. **Contributor.** Implementation by @Lebei2046; MSVC `/LIBPATH:` branch + `package_dir` anchoring + tests + manifest doc entry + this changelog folded in at merge time. + ## v0.5.908 — test: #698 — behavioral parity tests for argon2 + ethers integrations (#760). Follow-up to #694, addressing #698. Adds two new parity fixtures and a `@covers` block on `test_parity_crypto.ts`, moving 18 FFI entries out of `test_ffi_surface_stdlib_integrations.ts` (156 → 138 unique names) into behavioral coverage. **`test_parity_argon2.ts`** — round-trips `argon2.hash` / `argon2.verify` against the `perry-ext-argon2` wrapper; non-deterministic salt is handled by shape-checking the `$argon2id$` prefix and asserting verify round-trips against a freshly produced hash. Only the async path is in `NativeModSig`, so this covers the two FFI entries (`js_argon2_hash`, `js_argon2_verify`) reachable end-to-end. **`test_parity_ethers.ts`** — pure deterministic helpers from `perry-ext-ethers`: `getAddress` (EIP-55 checksum on lower- and upper-case input), `parseEther`/`formatEther` round-trip on 1.5 ETH, `parseUnits`/`formatUnits` at 6- (USDC) and 9-decimal (gwei) positions. Covers the five `js_ethers_*` helpers. Both new fixtures use the `test-parity/expected/` mechanism — Perry routes the npm-style imports to the bundled `perry-ext-*` wrappers, but Node can't resolve the same names without `node_modules`, so they fall through to stored expected-output comparison. **`test_parity_crypto.ts`** — adds a `@covers` block declaring the crypto/webcrypto/crypto_e2e FFI surface it already exercises via `node:crypto` and `crypto.subtle` (digest, hash, pbkdf2/hkdf, subtle.sign/verify); no behavioral change. **Out of scope (each gets its own follow-up).** `jsonwebtoken.sign` — `NativeModSig` passes `payload`/`secret` as `NA_F64` (NaN-boxed) but `js_jwt_sign` reads them as `*const StringHeader`, producing garbage tokens (calling-convention mismatch, same shape as the #591 argon2 fix). `bcrypt.hash` — return value reports `typeof === "object"` in user code; `compare` round-trips correctly but `.startsWith(...)` on the hash fails. `cheerio` — `$.select(...)` returns a bare-number handle, so `.length()` / `.first()` etc. fail with `(number).method is not a function` at the prototype check. Service-backed integrations (MongoDB, MySQL, PG, ioredis, Nodemailer, Sharp) also remain in the inventory pending the local-container / opt-in test setup described in #698. **Audit.** `./test-coverage/audit.sh --markdown` keeps TS-side and combined FFI coverage at **1791/1791 (100.0%)**. `test_ffi_surface_stdlib_integrations.ts` re-regenerated via `regen_ts_surface_inventory.py` to fold in the new `@covers` blocks. Comment-only changes; no behavioral or runtime impact. Contributor: @TheHypnoo. ## v0.5.907 — test(stdlib): #697 — move stdlib_io FFI coverage into behavioral tests (#758). Follow-up to #694: relocates the 155 inventory entries from `test_ffi_surface_stdlib_io.ts` into `@covers` annotations attached to the existing behavioral fixtures for each surface — `fetch.rs` (43 names) → `test_gap_fetch_response.ts`; `streams.rs` (37) → `test_parity_stream_web.ts`; `http.rs` (13) → `test_parity_http.ts`; `net/mod.rs` (10) → `test_net_socket.ts`; `readline.rs` (5) → `test_parity_readline.ts`; `worker_threads.rs` (6) → `test_parity_worker_threads.ts`; `ws.rs` (11) → `test_node_http_ws_upgrade.ts`; `framework/{multipart,request,response,server}.rs` (28) → `test_fastify_integration.ts`. Each receiving fixture already exercises real behavior on its surface (local loopback servers / deterministic in-memory probes / Headers + Blob + Response API roundtrips) — no external services added. **Mechanism.** `regen_ts_surface_inventory.py` excludes the six `test_ffi_surface_*.ts` inventory files when scanning for `@covers` references, so any FFI name that appears in a non-inventory fixture's `@covers` block is dropped from its inventory file on the next regen. After this PR, `test_ffi_surface_stdlib_io.ts` shrinks from **155 → 0** unique FFI names. **Audit.** `./test-coverage/audit.sh --markdown` keeps TypeScript and combined FFI coverage at **1791/1791 (100.0%)**. `test_ffi_surface_runtime_core.ts` re-regenerated against current `main` post-rebase — the inventory drift visible in the diff (added `js_array_flat_depth`, `js_template_raw`, `js_path_matches_glob`, `js_async_first_call`, the 7 new `webassembly.rs` exports; removed `js_mul`, `js_net_socket_destroy/end/write`, the 9 `js_ws_*` stubs from `stdlib_stubs.rs`) reflects upstream Rust refactors, not hand edits. Comment-only changes; no behavioral or runtime impact. Contributor: @TheHypnoo. diff --git a/CLAUDE.md b/CLAUDE.md index 9d2bcdbe5c..c2955582ab 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -8,7 +8,7 @@ This file provides guidance to Claude Code (claude.ai/code) when working with co Perry is a native TypeScript compiler written in Rust that compiles TypeScript source code directly to native executables. It uses SWC for TypeScript parsing and LLVM for code generation. -**Current Version:** 0.5.908 +**Current Version:** 0.5.909 ## TypeScript Parity Status diff --git a/Cargo.lock b/Cargo.lock index 9ed2bdbd3e..b3ea2a3e96 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4790,7 +4790,7 @@ checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" [[package]] name = "perry" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "base64", @@ -4845,14 +4845,14 @@ dependencies = [ [[package]] name = "perry-api-manifest" -version = "0.5.908" +version = "0.5.909" dependencies = [ "serde", ] [[package]] name = "perry-codegen" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "log", @@ -4865,7 +4865,7 @@ dependencies = [ [[package]] name = "perry-codegen-arkts" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-hir", @@ -4874,7 +4874,7 @@ dependencies = [ [[package]] name = "perry-codegen-glance" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-hir", @@ -4882,7 +4882,7 @@ dependencies = [ [[package]] name = "perry-codegen-js" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-dispatch", @@ -4892,7 +4892,7 @@ dependencies = [ [[package]] name = "perry-codegen-swiftui" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-hir", @@ -4901,7 +4901,7 @@ dependencies = [ [[package]] name = "perry-codegen-wasm" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "base64", @@ -4914,7 +4914,7 @@ dependencies = [ [[package]] name = "perry-codegen-wear-tiles" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-hir", @@ -4922,7 +4922,7 @@ dependencies = [ [[package]] name = "perry-diagnostics" -version = "0.5.908" +version = "0.5.909" dependencies = [ "serde", "serde_json", @@ -4930,7 +4930,7 @@ dependencies = [ [[package]] name = "perry-dispatch" -version = "0.5.908" +version = "0.5.909" [[package]] name = "perry-doc-fixture-my-bindings" @@ -4941,7 +4941,7 @@ dependencies = [ [[package]] name = "perry-doc-tests" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "clap", @@ -4956,7 +4956,7 @@ dependencies = [ [[package]] name = "perry-ext-argon2" -version = "0.5.908" +version = "0.5.909" dependencies = [ "argon2", "perry-ffi", @@ -4964,7 +4964,7 @@ dependencies = [ [[package]] name = "perry-ext-axios" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "reqwest", @@ -4973,7 +4973,7 @@ dependencies = [ [[package]] name = "perry-ext-bcrypt" -version = "0.5.908" +version = "0.5.909" dependencies = [ "bcrypt", "perry-ffi", @@ -4981,7 +4981,7 @@ dependencies = [ [[package]] name = "perry-ext-better-sqlite3" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "rusqlite", @@ -4989,7 +4989,7 @@ dependencies = [ [[package]] name = "perry-ext-cheerio" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "scraper", @@ -4997,14 +4997,14 @@ dependencies = [ [[package]] name = "perry-ext-commander" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-cron" -version = "0.5.908" +version = "0.5.909" dependencies = [ "chrono", "cron", @@ -5013,7 +5013,7 @@ dependencies = [ [[package]] name = "perry-ext-dayjs" -version = "0.5.908" +version = "0.5.909" dependencies = [ "chrono", "perry-ffi", @@ -5021,7 +5021,7 @@ dependencies = [ [[package]] name = "perry-ext-decimal" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "rust_decimal", @@ -5029,7 +5029,7 @@ dependencies = [ [[package]] name = "perry-ext-dotenv" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "serde_json", @@ -5037,7 +5037,7 @@ dependencies = [ [[package]] name = "perry-ext-ethers" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "rand 0.8.6", @@ -5045,21 +5045,21 @@ dependencies = [ [[package]] name = "perry-ext-events" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-exponential-backoff" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-fastify" -version = "0.5.908" +version = "0.5.909" dependencies = [ "bytes", "http-body-util", @@ -5073,7 +5073,7 @@ dependencies = [ [[package]] name = "perry-ext-fetch" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lazy_static", "perry-ffi", @@ -5084,7 +5084,7 @@ dependencies = [ [[package]] name = "perry-ext-http" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lazy_static", "perry-ext-http-server", @@ -5096,7 +5096,7 @@ dependencies = [ [[package]] name = "perry-ext-http-server" -version = "0.5.908" +version = "0.5.909" dependencies = [ "bytes", "http-body-util", @@ -5115,7 +5115,7 @@ dependencies = [ [[package]] name = "perry-ext-ioredis" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lazy_static", "perry-ffi", @@ -5125,7 +5125,7 @@ dependencies = [ [[package]] name = "perry-ext-jsonwebtoken" -version = "0.5.908" +version = "0.5.909" dependencies = [ "base64", "jsonwebtoken", @@ -5136,7 +5136,7 @@ dependencies = [ [[package]] name = "perry-ext-lru-cache" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lru", "perry-ffi", @@ -5144,7 +5144,7 @@ dependencies = [ [[package]] name = "perry-ext-moment" -version = "0.5.908" +version = "0.5.909" dependencies = [ "chrono", "perry-ffi", @@ -5152,7 +5152,7 @@ dependencies = [ [[package]] name = "perry-ext-mongodb" -version = "0.5.908" +version = "0.5.909" dependencies = [ "bson", "futures-util", @@ -5164,7 +5164,7 @@ dependencies = [ [[package]] name = "perry-ext-mysql2" -version = "0.5.908" +version = "0.5.909" dependencies = [ "chrono", "perry-ffi", @@ -5174,7 +5174,7 @@ dependencies = [ [[package]] name = "perry-ext-nanoid" -version = "0.5.908" +version = "0.5.909" dependencies = [ "nanoid", "perry-ffi", @@ -5183,7 +5183,7 @@ dependencies = [ [[package]] name = "perry-ext-net" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "rustls", @@ -5194,7 +5194,7 @@ dependencies = [ [[package]] name = "perry-ext-nodemailer" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lettre", "perry-ffi", @@ -5204,7 +5204,7 @@ dependencies = [ [[package]] name = "perry-ext-pg" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "sqlx", @@ -5213,7 +5213,7 @@ dependencies = [ [[package]] name = "perry-ext-ratelimit" -version = "0.5.908" +version = "0.5.909" dependencies = [ "governor", "perry-ffi", @@ -5221,7 +5221,7 @@ dependencies = [ [[package]] name = "perry-ext-sharp" -version = "0.5.908" +version = "0.5.909" dependencies = [ "base64", "image", @@ -5230,14 +5230,14 @@ dependencies = [ [[package]] name = "perry-ext-slugify" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", ] [[package]] name = "perry-ext-streams" -version = "0.5.908" +version = "0.5.909" dependencies = [ "lazy_static", "perry-ffi", @@ -5245,7 +5245,7 @@ dependencies = [ [[package]] name = "perry-ext-uuid" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "uuid", @@ -5253,7 +5253,7 @@ dependencies = [ [[package]] name = "perry-ext-validator" -version = "0.5.908" +version = "0.5.909" dependencies = [ "perry-ffi", "regex", @@ -5263,7 +5263,7 @@ dependencies = [ [[package]] name = "perry-ext-ws" -version = "0.5.908" +version = "0.5.909" dependencies = [ "futures-util", "lazy_static", @@ -5274,7 +5274,7 @@ dependencies = [ [[package]] name = "perry-ext-zlib" -version = "0.5.908" +version = "0.5.909" dependencies = [ "flate2", "perry-ffi", @@ -5282,7 +5282,7 @@ dependencies = [ [[package]] name = "perry-ffi" -version = "0.5.908" +version = "0.5.909" dependencies = [ "dashmap 6.1.0", "once_cell", @@ -5291,7 +5291,7 @@ dependencies = [ [[package]] name = "perry-hir" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-api-manifest", @@ -5305,7 +5305,7 @@ dependencies = [ [[package]] name = "perry-jsruntime" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "deno_core", @@ -5325,7 +5325,7 @@ dependencies = [ [[package]] name = "perry-parser" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-diagnostics", @@ -5337,7 +5337,7 @@ dependencies = [ [[package]] name = "perry-runtime" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "base64", @@ -5361,7 +5361,7 @@ dependencies = [ [[package]] name = "perry-stdlib" -version = "0.5.908" +version = "0.5.909" dependencies = [ "aes", "aes-gcm", @@ -5429,7 +5429,7 @@ dependencies = [ [[package]] name = "perry-transform" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "perry-hir", @@ -5439,7 +5439,7 @@ dependencies = [ [[package]] name = "perry-types" -version = "0.5.908" +version = "0.5.909" dependencies = [ "anyhow", "thiserror 1.0.69", @@ -5447,11 +5447,11 @@ dependencies = [ [[package]] name = "perry-ui" -version = "0.5.908" +version = "0.5.909" [[package]] name = "perry-ui-android" -version = "0.5.908" +version = "0.5.909" dependencies = [ "itoa", "jni", @@ -5466,7 +5466,7 @@ dependencies = [ [[package]] name = "perry-ui-geisterhand" -version = "0.5.908" +version = "0.5.909" dependencies = [ "rand 0.8.6", "serde", @@ -5476,7 +5476,7 @@ dependencies = [ [[package]] name = "perry-ui-gtk4" -version = "0.5.908" +version = "0.5.909" dependencies = [ "cairo-rs", "dirs 5.0.1", @@ -5495,7 +5495,7 @@ dependencies = [ [[package]] name = "perry-ui-ios" -version = "0.5.908" +version = "0.5.909" dependencies = [ "block2", "libc", @@ -5510,7 +5510,7 @@ dependencies = [ [[package]] name = "perry-ui-macos" -version = "0.5.908" +version = "0.5.909" dependencies = [ "block2", "libc", @@ -5528,11 +5528,11 @@ version = "0.1.0" [[package]] name = "perry-ui-testkit" -version = "0.5.908" +version = "0.5.909" [[package]] name = "perry-ui-tvos" -version = "0.5.908" +version = "0.5.909" dependencies = [ "block2", "libc", @@ -5547,7 +5547,7 @@ dependencies = [ [[package]] name = "perry-ui-visionos" -version = "0.5.908" +version = "0.5.909" dependencies = [ "block2", "libc", @@ -5562,7 +5562,7 @@ dependencies = [ [[package]] name = "perry-ui-watchos" -version = "0.5.908" +version = "0.5.909" dependencies = [ "block2", "libc", @@ -5575,7 +5575,7 @@ dependencies = [ [[package]] name = "perry-ui-windows" -version = "0.5.908" +version = "0.5.909" dependencies = [ "libc", "perry-runtime", @@ -5589,7 +5589,7 @@ dependencies = [ [[package]] name = "perry-updater" -version = "0.5.908" +version = "0.5.909" dependencies = [ "base64", "ed25519-dalek", @@ -5603,7 +5603,7 @@ dependencies = [ [[package]] name = "perry-wasm-host" -version = "0.5.908" +version = "0.5.909" dependencies = [ "wasmi", ] diff --git a/Cargo.toml b/Cargo.toml index eddb8a1c5f..fd17d85535 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -190,7 +190,7 @@ opt-level = "s" # Optimize for size in stdlib opt-level = 3 [workspace.package] -version = "0.5.908" +version = "0.5.909" edition = "2021" license = "MIT" repository = "https://github.com/PerryTS/perry" diff --git a/crates/perry/src/commands/compile.rs b/crates/perry/src/commands/compile.rs index f6246ce0d3..87445dc387 100644 --- a/crates/perry/src/commands/compile.rs +++ b/crates/perry/src/commands/compile.rs @@ -555,6 +555,11 @@ pub struct TargetNativeConfig { pub lib_name: String, pub frameworks: Vec, pub libs: Vec, + /// Extra `-L`/`/LIBPATH:` search paths to hand the linker before the + /// `libs` entries are resolved. Anchored to the manifest's + /// `package_dir`, so relative entries in `package.json` resolve + /// against the package, not the user's cwd. + pub lib_dirs: Vec, pub pkg_config: Vec, /// Swift sources (absolute paths) to compile via swiftc and link into the /// final binary. Used by `--features watchos-swift-app` so a native lib diff --git a/crates/perry/src/commands/compile/link.rs b/crates/perry/src/commands/compile/link.rs index 70b934bcbf..3f74472a9b 100644 --- a/crates/perry/src/commands/compile/link.rs +++ b/crates/perry/src/commands/compile/link.rs @@ -1858,6 +1858,20 @@ pub(super) fn build_and_run_link( cmd.arg("-framework").arg(framework); } + // Add library search paths. MSVC link.exe takes `/LIBPATH:`; + // every other linker we drive (clang/ld on Apple, gcc/ld on + // Linux/Android/HarmonyOS) understands `-L`. Mirror the + // `target_config.libs` branch immediately below so a + // `targets.windows.libDirs` entry actually resolves the + // `{lib}.lib` lookups instead of being a silent no-op. + for lib_dir in &target_config.lib_dirs { + if is_windows { + cmd.arg(format!("/LIBPATH:{}", lib_dir.display())); + } else { + cmd.arg(format!("-L{}", lib_dir.display())); + } + } + // Add platform libraries for lib in &target_config.libs { if is_windows { diff --git a/crates/perry/src/commands/compile/resolve.rs b/crates/perry/src/commands/compile/resolve.rs index 292c77e30a..b5894e0e22 100644 --- a/crates/perry/src/commands/compile/resolve.rs +++ b/crates/perry/src/commands/compile/resolve.rs @@ -205,6 +205,15 @@ pub(super) fn parse_native_library_manifest( .collect() }) .unwrap_or_default(), + lib_dirs: tc + .get("libDirs") + .and_then(|l| l.as_array()) + .map(|a| { + a.iter() + .filter_map(|v| v.as_str().map(|p| package_dir.join(p))) + .collect() + }) + .unwrap_or_default(), pkg_config: tc .get("pkgConfig") .and_then(|p| p.as_array()) @@ -369,6 +378,75 @@ mod abi_validation_tests { } } +#[cfg(test)] +mod manifest_parse_tests { + use super::*; + + /// Relative `libDirs` entries must resolve against the package's + /// own directory, not the user's cwd — otherwise a wrapper that + /// ships a `vendor/lib/` alongside its `package.json` would only + /// link when invoked from one specific directory. Absolute entries + /// pass through unchanged (`PathBuf::join` ignores the base when + /// the right-hand side is absolute). + #[test] + fn lib_dirs_relative_paths_anchored_to_package_dir() { + let dir = tempfile::tempdir().expect("tempdir"); + let pkg_dir = dir.path(); + let manifest = serde_json::json!({ + "perry": { + "nativeLibrary": { + "functions": [], + "targets": { + "macos": { + "crate": "rust", + "lib": "demo", + "libDirs": ["vendor/lib", "/abs/path"] + } + } + } + } + }); + std::fs::write( + pkg_dir.join("package.json"), + serde_json::to_string(&manifest).unwrap(), + ) + .expect("write package.json"); + + let parsed = + parse_native_library_manifest(pkg_dir, "demo", Some("macos")).expect("parsed manifest"); + let tc = parsed.target_config.expect("target_config"); + assert_eq!(tc.lib_dirs.len(), 2); + assert_eq!(tc.lib_dirs[0], pkg_dir.join("vendor/lib")); + assert_eq!(tc.lib_dirs[1], PathBuf::from("/abs/path")); + } + + /// Omitted `libDirs` must default to an empty list, not error — + /// it's an optional field on every existing wrapper. + #[test] + fn lib_dirs_defaults_to_empty_when_absent() { + let dir = tempfile::tempdir().expect("tempdir"); + let pkg_dir = dir.path(); + let manifest = serde_json::json!({ + "perry": { + "nativeLibrary": { + "functions": [], + "targets": { "macos": { "crate": "rust", "lib": "demo" } } + } + } + }); + std::fs::write( + pkg_dir.join("package.json"), + serde_json::to_string(&manifest).unwrap(), + ) + .expect("write package.json"); + + let parsed = + parse_native_library_manifest(pkg_dir, "demo", Some("macos")).expect("parsed manifest"); + let tc = parsed.target_config.expect("target_config"); + assert!(tc.lib_dirs.is_empty()); + } +} + /// Packages that Perry provides built-in native extensions for. /// These must never be loaded into V8 — Perry's codegen intercepts all imports /// from these packages and replaces them with native calls. diff --git a/docs/src/native-libraries/manifest-v1.md b/docs/src/native-libraries/manifest-v1.md index 4266c7a58e..34dbe3c889 100644 --- a/docs/src/native-libraries/manifest-v1.md +++ b/docs/src/native-libraries/manifest-v1.md @@ -154,6 +154,7 @@ as their device counterpart (`ios` covers both `ios-simulator` and | `lib` | string | yes\* | Library name (without the `lib` prefix or `.a` extension). Required when `prebuilt` is absent. | | `frameworks` | array of string | no | Apple-only — frameworks to pass to `clang -framework`. | | `libs` | array of string | no | System libraries to pass to the linker (`-lcurl`, etc.). | +| `libDirs` | array of paths | no | Extra linker search paths. Emitted before `libs` as `-L` (or `/LIBPATH:` on Windows MSVC). Relative entries resolve against `package.json`. | | `pkgConfig` | array of string | no | pkg-config package names. The compiler runs `pkg-config --libs` and forwards the output. | | `swift_sources` | array of paths | no | Swift sources to compile via `swiftc` and link in. Used by SwiftUI wrappers. | | `metal_sources` | array of paths | no | Metal shader sources to compile via `xcrun metal` into `.app/default.metallib`. |