diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..26f927c --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,5 @@ +[net] +retry = 2 + +[http] +timeout = 30 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 337c2cd..7a97c62 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -15,10 +15,10 @@ concurrency: jobs: verify: - runs-on: ubuntu-latest + runs-on: ubuntu-24.04 timeout-minutes: 20 steps: - - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7 + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 with: persist-credentials: false @@ -32,7 +32,7 @@ jobs: toolchain: "1.97.0" components: rustfmt, clippy - - name: Verify Zed and Cargo metadata agree + - name: Verify Cargo and Zed ownership boundaries shell: bash run: | set -euo pipefail @@ -41,24 +41,60 @@ jobs: from pathlib import Path with Path("Cargo.toml").open("rb") as handle: - cargo = tomllib.load(handle)["package"] + cargo = tomllib.load(handle) with Path(".zpkg.toml").open("rb") as handle: - zed = tomllib.load(handle)["package"] + zed = tomllib.load(handle) - assert cargo["name"] == zed["name"] - assert cargo["version"] == zed["version"] - assert cargo["description"] == zed["description"] - assert cargo["license"] == zed["license"] - assert cargo["repository"] == zed["repository"]["url"] - assert cargo["readme"] == "README.md" - assert zed["org"] == "zed-pkg-test" + cargo_package = cargo["package"] + zed_package = zed["package"] + assert cargo_package["name"] == zed_package["name"] + assert cargo_package["version"] == zed_package["version"] == "1.0.1" + assert cargo_package["description"] == zed_package["description"] + assert cargo_package["license"] == zed_package["license"] + assert cargo_package["repository"] == zed["package"]["repository"]["url"] + assert cargo_package["readme"] == "README.md" + assert cargo_package["rust-version"] == "1.88" + assert zed_package["org"] == "zed-pkg-test" + + # crates.io dependencies and Cargo features stay native to Cargo. + assert cargo["dependencies"] == {"itoa": "=1.0.18"} + assert cargo["features"] == {"default": [], "uppercase": []} + assert "dependencies" not in zed + assert zed["publish"]["exclude"] == ["Cargo.lock"] PY - - name: Format, lint, and test + - name: Format, lint, and test both feature sets + shell: bash run: | + set -euo pipefail cargo fmt --all -- --check cargo clippy --all-targets -- -D warnings + cargo clippy --all-targets --all-features -- -D warnings cargo test --all-targets + cargo test --all-targets --all-features + + - name: Verify Cargo resolves registry provenance independently + shell: bash + run: | + set -euo pipefail + cargo metadata --locked --format-version 1 > "$RUNNER_TEMP/cargo-metadata.json" + python3 - "$RUNNER_TEMP/cargo-metadata.json" <<'PY' + import json + import sys + from pathlib import Path + + metadata = json.loads(Path(sys.argv[1]).read_text()) + packages = {package["name"]: package for package in metadata["packages"]} + assert packages["rust-lib"]["source"] is None + assert packages["rust-lib"]["version"] == "1.0.1" + assert packages["itoa"]["version"] == "1.0.18" + assert packages["itoa"]["source"].startswith("registry+") + resolve = metadata["resolve"] + assert resolve is not None + root = next(node for node in resolve["nodes"] if node["id"] == resolve["root"]) + assert {dep["name"] for dep in root["deps"]} == {"itoa"} + PY + test ! -e .zpkg.lock - name: Audit Cargo package contents shell: bash @@ -72,13 +108,17 @@ jobs: cat "$RUNNER_TEMP/package-files.txt" grep -Fxq Cargo.toml "$RUNNER_TEMP/package-files.txt" grep -Fxq README.md "$RUNNER_TEMP/package-files.txt" + grep -Fxq scripts/zed-smoke.sh "$RUNNER_TEMP/package-files.txt" grep -Fxq src/lib.rs "$RUNNER_TEMP/package-files.txt" grep -Fxq Cargo.lock "$RUNNER_TEMP/package-files.txt" - if grep -Eq '(^|/)(target|\.zed)/' "$RUNNER_TEMP/package-files.txt"; then + if grep -Eq '(^|/)(target|\.zed|\.vendor)/' "$RUNNER_TEMP/package-files.txt"; then echo "Cargo package contains build or local Zed state" >&2 exit 1 fi cargo package --allow-dirty + test ! -e .zpkg.lock + rm -f Cargo.lock + test -z "$(git status --porcelain --untracked-files=all)" - name: Upload package-content diagnostics if: ${{ always() }} diff --git a/.zpkg.toml b/.zpkg.toml index f237a45..aa20471 100644 --- a/.zpkg.toml +++ b/.zpkg.toml @@ -1,13 +1,17 @@ [package] org = "zed-pkg-test" name = "rust-lib" -version = "1.0.0" +version = "1.0.1" description = "A Rust crate sourced via zed (complements cargo/crates.io)." license = "MIT" + [package.repository] vcs = "git" url = "https://github.com/zed-pkg-test/rust-lib" +# Cargo-native dependencies remain in Cargo.toml/Cargo.lock. A Zed +# [dependencies] table is intentionally absent because it names Zed packages, +# not crates.io packages. [publish] exclude = ["Cargo.lock"] smoke_test = 'sh "$ZED_PKG_TEST_TARGET/scripts/zed-smoke.sh"' diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8d1ce3e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,9 @@ +# Changelog + +## 1.0.1 + +- add the exact Cargo registry dependency `itoa = 1.0.18`; +- add the optional `uppercase` Cargo feature; +- certify default/all-feature builds and Cargo package contents; +- certify a separate locked/offline Cargo consumer installed through Zed; +- document the `.zpkg.toml` versus `Cargo.toml` ownership boundary. diff --git a/Cargo.toml b/Cargo.toml index 78affd5..f774b9e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,20 @@ [package] name = "rust-lib" -version = "1.0.0" +version = "1.0.1" edition = "2021" +rust-version = "1.88" description = "A Rust crate sourced via zed (complements cargo/crates.io)." license = "MIT" repository = "https://github.com/zed-pkg-test/rust-lib" readme = "README.md" +[features] +default = [] +uppercase = [] + +[dependencies] +itoa = "=1.0.18" + [lib] name = "rust_lib" path = "src/lib.rs" diff --git a/README.md b/README.md index 220f500..5476322 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,45 @@ -# rust-lib (zed-pkg-test/rust-lib) +# rust-lib (`zed-pkg-test/rust-lib`) -A Rust crate published to the zed registry. Consumers add it as a cargo **path** dependency pointing at their `[install].dir` (`.vendor/.zed/zed-pkg-test/rust-lib`), sourced via zed; crates.io keeps owning everything else. +A real Rust crate published through the Zed package registry while retaining +ordinary Cargo semantics. + +## Interoperability contract + +- Zed owns distribution and materializes the crate under the consumer's + `[install].dir`, normally `.vendor/.zed/zed-pkg-test/rust-lib`. +- Cargo consumes that materialization as a normal `path` dependency. +- `Cargo.toml` owns crates.io dependencies and Cargo features. +- `.zpkg.toml` owns Zed package identity and Zed-to-Zed dependencies; crates.io + packages are deliberately not repeated there. +- Library source does not track `Cargo.lock`, matching standard Cargo library + practice. Binary consumers do track their own lockfile. + +Version `1.0.1` depends on the exact crates.io package `itoa = 1.0.18`. The +`format_count` API proves that dependency is present after Zed publication and +installation. The optional `uppercase` feature is used by the companion +`zed-pkg-test/rust-app` fixture to prove Cargo resolver-v2 feature separation +between target and build dependencies. + +A consumer uses both package managers explicitly: + +```toml +# Cargo.toml +[dependencies] +rust-lib = { + path = ".vendor/.zed/zed-pkg-test/rust-lib", + features = ["uppercase"] +} +``` + +```toml +# .zpkg.toml +[install] +dir = ".vendor/.zed" + +[dependencies] +"zed-pkg-test/rust-lib" = "^1.0.0" +``` + +The Zed publish smoke test builds a separate Cargo application with this path +dependency plus an independent crates.io dependency, locks it, runs it, and +replays it offline without creating a Zed lockfile in the Cargo-only consumer. diff --git a/docs/cargo-interop.md b/docs/cargo-interop.md new file mode 100644 index 0000000..f4ef982 --- /dev/null +++ b/docs/cargo-interop.md @@ -0,0 +1,31 @@ +# Cargo interoperability contract + +This fixture proves that Zed package distribution and Cargo dependency +resolution remain separate, composable layers. + +## Ownership + +- `.zpkg.toml` identifies the Zed package and any Zed package dependencies. +- `Cargo.toml` identifies crates.io, Git, and Cargo path dependencies plus Cargo + features. +- library source does not track `Cargo.lock`; +- a consuming binary owns its exact Cargo lock; +- `.zpkg.lock` and `Cargo.lock` may coexist and neither package manager rewrites + the other's lockfile. + +## Certified scenario + +`rust-lib` is published and installed through Zed. Its Cargo manifest depends on +`itoa = 1.0.18`. The publish smoke test then creates an external binary that: + +1. points Cargo at the installed Zed package using a path dependency; +2. enables the `uppercase` Cargo feature; +3. declares its own independent `unicode-ident = 1.0.24` crates.io dependency; +4. generates and retains a Cargo lock; +5. verifies Cargo metadata contains one path package and two registry packages; +6. runs with `--locked`; and +7. replays with `--locked --offline` without creating `.zpkg.lock`. + +The companion `rust-app` fixture extends this with normal/build dependency +feature separation, Zed symlink/copy modes, dual-lock immutability, and +cross-platform Cargo execution. diff --git a/scripts/zed-smoke.sh b/scripts/zed-smoke.sh index e87e41d..56b7479 100755 --- a/scripts/zed-smoke.sh +++ b/scripts/zed-smoke.sh @@ -11,20 +11,38 @@ cat >"$smoke_root/Cargo.toml" <"$smoke_root/src/main.rs" <<'EOF' fn main() { assert_eq!( rust_lib::greet("r2g"), - "hello r2g from zed-pkg-test/rust-lib" + "HELLO R2G FROM ZED-PKG-TEST/RUST-LIB" ); + assert_eq!(rust_lib::format_count(1_000_001), "1000001"); + assert!(unicode_ident::is_xid_start('Δ')); } EOF -CARGO_TARGET_DIR="$smoke_root/target" cargo run \ - --quiet \ - --manifest-path "$smoke_root/Cargo.toml" +cd "$smoke_root" +CARGO_TARGET_DIR="$smoke_root/target" cargo generate-lockfile +cp Cargo.lock Cargo.lock.expected +CARGO_TARGET_DIR="$smoke_root/target" cargo metadata \ + --locked \ + --format-version 1 >metadata.json + +grep -Fq '"name":"rust-lib"' metadata.json +grep -Fq '"name":"itoa"' metadata.json +grep -Fq '"name":"unicode-ident"' metadata.json +grep -Fq 'registry+https://github.com/rust-lang/crates.io-index' Cargo.lock + +test ! -e .zpkg.lock +CARGO_TARGET_DIR="$smoke_root/target" cargo run --locked --quiet +CARGO_TARGET_DIR="$smoke_root/target" cargo run --locked --offline --quiet +cmp Cargo.lock.expected Cargo.lock +test ! -e .zpkg.lock diff --git a/src/lib.rs b/src/lib.rs index 5212728..0c4b7e9 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,17 +1,47 @@ -//! The one thing this zed-sourced crate exports. +//! Cargo crate fixture distributed through the Zed package registry. + +/// Return a greeting that identifies the immutable Zed package namespace. +/// +/// The optional `uppercase` Cargo feature intentionally changes only the +/// consumer-facing target build. Cargo resolver v2 can therefore prove that a +/// normal dependency and a build dependency may use separate feature sets even +/// when both point at the same Zed-materialized crate. pub fn greet(who: &str) -> String { - format!("hello {who} from zed-pkg-test/rust-lib") + let message = format!("hello {who} from zed-pkg-test/rust-lib"); + #[cfg(feature = "uppercase")] + let message = message.to_uppercase(); + message +} + +/// Format an integer through a real crates.io dependency. +/// +/// This keeps the fixture small while proving that a crate installed by Zed can +/// retain ordinary Cargo registry dependencies without duplicating them in +/// `.zpkg.toml`. +pub fn format_count(value: u64) -> String { + let mut buffer = itoa::Buffer::new(); + buffer.format(value).to_owned() } #[cfg(test)] mod tests { - use super::greet; + use super::{format_count, greet}; #[test] fn greet_identifies_the_immutable_zed_package_namespace() { - assert_eq!( - greet("consumer"), + let expected = if cfg!(feature = "uppercase") { + "HELLO CONSUMER FROM ZED-PKG-TEST/RUST-LIB" + } else { "hello consumer from zed-pkg-test/rust-lib" + }; + assert_eq!(greet("consumer"), expected); + } + + #[test] + fn cargo_registry_dependency_is_exercised() { + assert_eq!( + format_count(18_446_744_073_709_551_615), + "18446744073709551615" ); } } diff --git a/tests/feature_contract.rs b/tests/feature_contract.rs new file mode 100644 index 0000000..015f0c4 --- /dev/null +++ b/tests/feature_contract.rs @@ -0,0 +1,10 @@ +#[test] +fn cargo_feature_and_registry_dependency_are_publicly_usable() { + let expected = if cfg!(feature = "uppercase") { + "HELLO INTEGRATION FROM ZED-PKG-TEST/RUST-LIB" + } else { + "hello integration from zed-pkg-test/rust-lib" + }; + assert_eq!(rust_lib::greet("integration"), expected); + assert_eq!(rust_lib::format_count(4_294_967_296), "4294967296"); +}