diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ddfa907..b66cfd20 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -97,7 +97,6 @@ jobs: - run: | cargo test --no-default-features --features zlib-ng-no-cmake-experimental-community-maintained || echo "::warning::failed to build libz-ng with --features zlib-ng-no-cmake-experimental-community-maintained" cargo run --manifest-path systest/Cargo.toml --no-default-features --features zlib-ng-no-cmake-experimental-community-maintained || echo "::warning::failed to run systest with --features zlib-ng-no-cmake-experimental-community-maintained" - # ensures packaging works package: @@ -106,10 +105,13 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 with: submodules: true - - run: | + - name: test bare packaging + run: | cargo package --all-features cargo package --no-default-features --features zlib-ng-no-cmake-experimental-community-maintained cargo package --no-default-features --features zlib-ng + - name: test packaging with release tool + run: cargo run -p maint -- publish linux: runs-on: ubuntu-latest @@ -158,7 +160,6 @@ jobs: - run: cargo build --features zlib-ng --no-default-features - run: cargo build --no-default-features --features zlib-ng-no-cmake-experimental-community-maintained || echo "::warning::failed to build libz-ng with --features zlib-ng-no-cmake-experimental-community-maintained" - strategy: fail-fast: false matrix: diff --git a/Cargo-zng.toml b/Cargo-zng.toml index 10e1552e..b6ee3d15 100644 --- a/Cargo-zng.toml +++ b/Cargo-zng.toml @@ -1,6 +1,6 @@ [package] name = "libz-ng-sys" -version = "1.1.27" +version = "1.1.28" authors = [ "Alex Crichton ", "Josh Triplett ", @@ -17,10 +17,12 @@ edition = "2018" exclude = [ "/.github", "/.gitmodules", + "/MAINTENANCE.md", "/README.md", "/build.rs", "/cargo-zng", "/ci", + "/maint", "/src/smoke.c", "/src/zlib", "/systest", @@ -39,4 +41,4 @@ libc = "0.2.43" cmake = "0.1.50" [lints.rust] -unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zng)'] } +unexpected_cfgs = { level = "warn", check-cfg = ['cfg(zng)', 'cfg(feature, values("libc"))'] } diff --git a/Cargo.toml b/Cargo.toml index 089b427d..31d6dcab 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libz-sys" -version = "1.1.27" +version = "1.1.28" authors = [ "Alex Crichton ", "Josh Triplett ", @@ -45,7 +45,7 @@ include = [ ] [workspace] -members = ["systest"] +members = ["maint", "systest"] [dependencies] # When this feature is disabled, zlib will be built in Z_SOLO mode which diff --git a/MAINTENANCE.md b/MAINTENANCE.md new file mode 100644 index 00000000..5ddcbaf4 --- /dev/null +++ b/MAINTENANCE.md @@ -0,0 +1,65 @@ +# Maintenance Guide + +This repository publishes two crates from the same sources: + +- `libz-sys`, from [`Cargo.toml`](Cargo.toml) +- `libz-ng-sys`, from [`Cargo-zng.toml`](Cargo-zng.toml) + +The supported release entrypoint is the maintenance tool in `maint/`. + +## Release Steps + +1. Update both crate versions so that `Cargo.toml` and `Cargo-zng.toml` have + the exact same `package.version`. +2. Make sure the release commit is checked out with submodules initialized. +3. Run the release verification in dry-run mode: + + ```bash + cargo run -p maint -- publish + ``` + + This verifies all release packaging and build combinations with + `cargo publish --dry-run`, checks that both manifests have the same version, + and rejects missing bundled source files or missing packaged source files. + +4. Perform the actual publish only after the dry-run passes: + + ```bash + cargo run -p maint -- publish --execute + ``` + + The tool verifies first, then publishes `libz-sys` and `libz-ng-sys`. + +5. Create and push the Git tag after both publishes succeed. `maint` provides instructions. + +## Safety Rules + +- `maint publish` defaults to dry-run mode. Real publishing requires + `--execute`. +- The tool never relies on a bare `cargo publish`. Verification always uses + `cargo publish --dry-run`, and the upload step uses `cargo publish --no-verify` + only after all dry-run checks succeed. +- The release path does not use `--allow-dirty`; Cargo should reject a dirty + worktree. +- Missing submodule checkouts are caught before publishing by requiring bundled + zlib and zlib-ng source files to exist in the worktree and in the packaged + crate contents. + +## Working on `libz-ng-sys` + +Use the maint tool to run Cargo as if the repository were checked out as the +`libz-ng-sys` crate: + +```bash +cargo run -p maint -- zng test +``` + +The compatibility wrapper still works: + +```bash +./cargo-zng test +``` + +If you invoke `publish` through the `zng` command, the tool forces +`--dry-run`. Real publishing is only supported through `maint publish +--execute`. diff --git a/README-zng.md b/README-zng.md index 60a87783..213dbf61 100644 --- a/README-zng.md +++ b/README-zng.md @@ -28,9 +28,13 @@ implementations. This crate is built from [the same sources as `libz-sys`](https://github.com/rust-lang/libz-sys). From within those sources, `Cargo.toml` is the manifest for `libz-sys`, and `Cargo-zng.toml` is the -manifest for `libz-ng-sys`. The script `./cargo-zng` invokes Cargo on a -temporary copy of the sources with `Cargo-zng.toml` replacing `Cargo.toml`; for -instance, use `./cargo-zng publish` to publish `libz-ng-sys`. +manifest for `libz-ng-sys`. Use `cargo run -p maint -- zng ` to +run Cargo against a temporary `libz-ng-sys` staging tree. The `./cargo-zng` +script remains as a compatibility wrapper around that command. + +Use `cargo run -p maint -- publish` to verify the release in dry-run mode, and +`cargo run -p maint -- publish --execute` to publish both crates. See +[`MAINTENANCE.md`](MAINTENANCE.md) for the full release process. # Minimum Supported Rust Version (MSRV) Policy diff --git a/build.rs b/build.rs index 4cd8291d..981b43ca 100644 --- a/build.rs +++ b/build.rs @@ -35,10 +35,11 @@ fn main() { // also don't run pkg-config on FreeBSD/DragonFly. That'll end up printing // `-L /usr/lib` which wreaks havoc with linking to an OpenSSL in /usr/local/lib // (Ports, etc.) - if !want_static && - !target.contains("msvc") && // pkg-config just never works here - !(host_and_target_contain("freebsd") || - host_and_target_contain("dragonfly")) + if !(want_static + // pkg-config just never works here + || target.contains("msvc") + || host_and_target_contain("freebsd") + || host_and_target_contain("dragonfly")) { // Don't print system lib dirs to cargo since this interferes with other // packages adding non-system search paths to link against libraries @@ -59,15 +60,16 @@ fn main() { } } Err(e) => { - println!("cargo:warning=Could not find zlib include paths via pkg-config: {}", e) + println!( + "cargo:warning=Could not find zlib include paths via pkg-config: {}", + e + ) } } } - if target.contains("windows") { - if try_vcpkg() { - return; - } + if target.contains("windows") && try_vcpkg() { + return; } let mut cfg = cc::Build::new(); @@ -77,10 +79,7 @@ fn main() { // - MSVC basically never has zlib preinstalled // - MinGW picks up a bunch of weird paths we don't like // - Explicit opt-in via `want_static` - if target.contains("msvc") - || target.contains("pc-windows-gnu") - || want_static - { + if target.contains("msvc") || target.contains("pc-windows-gnu") || want_static { return build_zlib(&mut cfg, &target); } @@ -117,7 +116,7 @@ fn build_zlib(cfg: &mut cc::Build, target: &str) { .file("src/zlib/uncompr.c") .file("src/zlib/zutil.c"); - if !cfg!(feature = "libc") || target.starts_with("wasm32") { + if target.starts_with("wasm32") { cfg.define("Z_SOLO", None); // zlib 1.3.2 uses `NULL` directly in compress.c/uncompr.c, but the // Z_SOLO config path doesn't pull in headers that always define it. @@ -243,15 +242,15 @@ fn zlib_installed(cfg: &mut cc::Build) -> bool { /// this enables the build environment to revert that preference via `LIBZ_SYS_STATIC=0`. /// The default is otherwise `false`. fn should_link_static() -> bool { - let has_static_env: Option<&'static str> = option_env!("LIBZ_SYS_STATIC"); - let has_static_cfg = cfg!(feature = "static"); + let has_static_env: Option<&'static str> = option_env!("LIBZ_SYS_STATIC"); + let has_static_cfg = cfg!(feature = "static"); - has_static_env - .and_then(|s: &str| s.parse::().ok()) - .and_then(|b| match b { - 0 => Some(false), - 1 => Some(true), - _ => None, - }) - .unwrap_or(has_static_cfg) + has_static_env + .and_then(|s: &str| s.parse::().ok()) + .and_then(|b| match b { + 0 => Some(false), + 1 => Some(true), + _ => None, + }) + .unwrap_or(has_static_cfg) } diff --git a/cargo-zng b/cargo-zng index 6b40bc63..f7f4d3b7 100755 --- a/cargo-zng +++ b/cargo-zng @@ -1,14 +1,3 @@ #!/bin/bash -set -eu -tempdir="$(mktemp -d)" -trap 'rm -rf "$tempdir"' 0 INT -cargo package -l --allow-dirty | - tr '\\' '/' | - grep -vxF -e Cargo.toml.orig -e .cargo_vcs_info.json -e Cargo.lock | - tar --files-from=- -cf - | - tar -C "$tempdir" -xf - -cp Cargo-zng.toml "$tempdir/Cargo.toml" -cp -a systest "$tempdir/systest" -mv "$tempdir/systest/Cargo-zng.toml" "$tempdir/systest/Cargo.toml" -cd "$tempdir" -cargo "$@" +set -euo pipefail +exec cargo run --quiet -p maint -- zng "$@" diff --git a/maint/Cargo.toml b/maint/Cargo.toml new file mode 100644 index 00000000..ed40f58c --- /dev/null +++ b/maint/Cargo.toml @@ -0,0 +1,10 @@ +[package] +name = "maint" +version = "0.1.0" +edition = "2024" +publish = false + +[dependencies] +anyhow = "1.0.100" +tempfile = "3.23.0" +toml = "0.9.7" diff --git a/maint/src/main.rs b/maint/src/main.rs new file mode 100644 index 00000000..55a2ddf9 --- /dev/null +++ b/maint/src/main.rs @@ -0,0 +1,631 @@ +use std::env; +use std::ffi::{OsStr, OsString}; +use std::fs; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; + +use anyhow::{Context, Result, anyhow, bail}; +use tempfile::TempDir; + +fn main() -> Result<()> { + let mut args = env::args_os(); + let program = args + .next() + .unwrap_or_else(|| OsString::from("maint")) + .to_string_lossy() + .into_owned(); + + let Some(subcommand) = args.next() else { + print_help(&program); + return Ok(()); + }; + + match subcommand.to_str() { + Some("publish") => run_cargo_publish(args), + Some("zng") => run_cargo_zng(args), + Some("-h") | Some("--help") | Some("help") => { + print_help(&program); + Ok(()) + } + Some(other) => bail!("unknown subcommand `{other}`"), + None => bail!("subcommand must be valid UTF-8"), + } +} + +fn print_help(program: &str) { + println!("Usage:\n {program} publish [--execute]\n {program} zng \n"); +} + +fn run_cargo_publish(args: impl IntoIterator) -> Result<()> { + let mut execute = false; + for arg in args { + match arg.to_str() { + Some("--execute") => execute = true, + Some("-h") | Some("--help") => { + println!("Usage: maint publish [--execute]"); + return Ok(()); + } + Some(other) => bail!("unexpected publish flag `{other}`"), + None => bail!("publish flags must be valid UTF-8"), + } + } + + let repo_root = env::current_dir().context("failed to determine current directory")?; + let root_manifest = read_manifest_info(&repo_root.join(ROOT_MANIFEST))?; + let zng_manifest = read_manifest_info(&repo_root.join(ZNG_MANIFEST))?; + + if root_manifest.name != ROOT_PACKAGE_NAME { + bail!( + "expected `{ROOT_PACKAGE_NAME}` in {}, found `{}`", + ROOT_MANIFEST, + root_manifest.name + ); + } + if zng_manifest.name != ZNG_PACKAGE_NAME { + bail!( + "expected `{ZNG_PACKAGE_NAME}` in {}, found `{}`", + ZNG_MANIFEST, + zng_manifest.name + ); + } + if root_manifest.version != zng_manifest.version { + bail!( + "crate versions must match before release: {} is {}, {} is {}", + ROOT_MANIFEST, + root_manifest.version, + ZNG_MANIFEST, + zng_manifest.version + ); + } + + ensure_paths_exist(&repo_root, REQUIRED_WORKTREE_PATHS, "worktree")?; + assert_package_contains( + &repo_root, + &root_manifest.name, + LIBZ_SYS_PACKAGE_SENTINELS, + "libz-sys package contents", + )?; + + let staged_zng = stage_zng(&repo_root, false)?; + assert_package_contains( + &staged_zng.root, + &zng_manifest.name, + LIBZ_NG_PACKAGE_SENTINELS, + "libz-ng-sys package contents", + )?; + + for feature_set in [ + FeatureSet::Default, + FeatureSet::Static, + FeatureSet::ZlibNg, + FeatureSet::ZlibNgNoCmake, + ] { + run_publish( + &repo_root, + &root_manifest.name, + PublishMode::DryRun, + Some(feature_set), + )?; + } + run_publish( + &staged_zng.root, + &zng_manifest.name, + PublishMode::DryRun, + None, + )?; + + if !execute { + println!( + "publish dry-run for {} {} completed; rerun with --execute to upload", + root_manifest.name, root_manifest.version + ); + return Ok(()); + } + + run_publish( + &repo_root, + &root_manifest.name, + PublishMode::UploadAfterVerification, + None, + )?; + run_publish( + &staged_zng.root, + &zng_manifest.name, + PublishMode::UploadAfterVerification, + None, + )?; + + println!( + "published {} and {} version {}", + root_manifest.name, zng_manifest.name, root_manifest.version + ); + println!("next steps:"); + println!( + " 1. Run `git tag -s {} -m \"{} {}\"`.", + root_manifest.version, root_manifest.name, root_manifest.version + ); + println!( + " 2. Run `git tag -s {}-zng -m \"{} {}\"`.", + zng_manifest.version, zng_manifest.name, zng_manifest.version + ); + println!(" 2. Run `git push origin HEAD --tags`."); + Ok(()) +} + +fn run_cargo_zng(args: impl IntoIterator) -> Result<()> { + let cargo_args: Vec = args.into_iter().collect(); + if cargo_args.is_empty() { + bail!("Usage: maint zng "); + } + + if matches!(cargo_args[0].to_str(), Some("-h" | "--help")) { + println!("Usage: maint zng "); + println!("Example: maint zng test"); + return Ok(()); + } + + let repo_root = env::current_dir().context("failed to determine current directory")?; + let staged_zng = stage_zng(&repo_root, true)?; + let forwarded_args = sanitize_zng_args(cargo_args)?; + run_cargo_in_dir(&staged_zng.root, &forwarded_args) +} + +fn sanitize_zng_args(mut cargo_args: Vec) -> Result> { + if !matches!( + cargo_args.first().and_then(|arg| arg.to_str()), + Some("publish") + ) { + return Ok(cargo_args); + } + + if cargo_args + .iter() + .any(|arg| arg == OsStr::new("--no-verify")) + { + bail!( + "refusing to run `cargo publish --no-verify` through `maint zng`; use `maint publish --execute` instead" + ); + } + + if !cargo_args + .iter() + .any(|arg| arg == OsStr::new("--dry-run") || arg == OsStr::new("-n")) + { + cargo_args.push(OsString::from("--dry-run")); + } + + Ok(cargo_args) +} + +fn stage_zng(repo_root: &Path, allow_dirty: bool) -> Result { + let package_files = package_file_list(repo_root, allow_dirty)?; + let tempdir = tempfile::tempdir().context("failed to create temporary staging directory")?; + let staging_root = tempdir.path().to_path_buf(); + + for relative_path in package_files { + if PACKAGE_LIST_EXCLUDE + .iter() + .any(|excluded| relative_path == Path::new(excluded)) + { + continue; + } + + let source = repo_root.join(&relative_path); + let destination = staging_root.join(&relative_path); + copy_path(&source, &destination) + .with_context(|| format!("failed to stage {}", relative_path.display()))?; + } + + copy_file( + &repo_root.join(ZNG_MANIFEST), + &staging_root.join(ROOT_MANIFEST), + )?; + copy_dir_recursive( + &repo_root.join(SYSTEST_DIR), + &staging_root.join(SYSTEST_DIR), + )?; + + let staged_systest_manifest = staging_root.join(SYSTEST_MANIFEST); + if staged_systest_manifest.exists() { + fs::remove_file(&staged_systest_manifest) + .with_context(|| format!("failed to remove {}", staged_systest_manifest.display()))?; + } + fs::rename( + staging_root.join(SYSTEST_ZNG_MANIFEST), + staging_root.join(SYSTEST_MANIFEST), + ) + .with_context(|| { + format!( + "failed to activate {} in staged systest workspace", + SYSTEST_ZNG_MANIFEST + ) + })?; + + Ok(StagedZng { + _tempdir: tempdir, + root: staging_root, + }) +} + +fn package_file_list(directory: &Path, allow_dirty: bool) -> Result> { + let mut command = Command::new("cargo"); + command.current_dir(directory).arg("package").arg("--list"); + if allow_dirty { + command.arg("--allow-dirty"); + } + let output = run_and_capture(&mut command)?; + + output + .lines() + .filter(|line| !line.trim().is_empty()) + .map(|line| Ok(PathBuf::from(line))) + .collect() +} + +fn ensure_paths_exist(root: &Path, required_paths: &[&str], label: &str) -> Result<()> { + for relative_path in required_paths { + let absolute_path = root.join(relative_path); + if !absolute_path.exists() { + bail!( + "missing required {label} path `{relative_path}`; check that all release submodules are initialized" + ); + } + } + Ok(()) +} + +fn assert_package_contains( + directory: &Path, + package_name: &str, + required_paths: &[&str], + label: &str, +) -> Result<()> { + let listed_files = package_file_list(directory, false)?; + for relative_path in required_paths { + let present = listed_files + .iter() + .any(|listed| listed == Path::new(relative_path)); + if !present { + bail!( + "missing `{relative_path}` in {label} for `{package_name}`; refusing to continue" + ); + } + } + Ok(()) +} + +fn run_publish( + directory: &Path, + package_name: &str, + mode: PublishMode, + feature_set: Option, +) -> Result<()> { + let mut command = Command::new("cargo"); + command + .current_dir(directory) + .arg("publish") + .arg("--package") + .arg(package_name); + mode.append_args(&mut command); + if let Some(feature_set) = feature_set { + feature_set.append_publish_args(&mut command); + eprintln!("verifying `{package_name}` with {}", feature_set.describe()); + } else { + match mode { + PublishMode::DryRun => eprintln!("verifying `{package_name}` with default features"), + PublishMode::UploadAfterVerification => eprintln!( + "uploading `{package_name}` with --no-verify after successful dry-run verification" + ), + } + } + run_command(&mut command) +} + +fn run_cargo_in_dir(directory: &Path, cargo_args: &[OsString]) -> Result<()> { + let mut command = Command::new("cargo"); + command.current_dir(directory).args(cargo_args); + run_command(&mut command) +} + +fn run_command(command: &mut Command) -> Result<()> { + eprintln!("+ {}", format_command(command)); + let status = command + .status() + .with_context(|| format!("failed to spawn {}", format_command(command)))?; + if status.success() { + Ok(()) + } else { + bail!("command failed: {} ({status})", format_command(command)); + } +} + +fn run_and_capture(command: &mut Command) -> Result { + eprintln!("+ {}", format_command(command)); + let output = command + .stdout(Stdio::piped()) + .stderr(Stdio::inherit()) + .output() + .with_context(|| format!("failed to spawn {}", format_command(command)))?; + if !output.status.success() { + bail!( + "command failed: {} ({})", + format_command(command), + output.status + ); + } + String::from_utf8(output.stdout).context("command output was not valid UTF-8") +} + +fn format_command(command: &Command) -> String { + let mut rendered = Vec::new(); + rendered.push(command.get_program().to_string_lossy().into_owned()); + rendered.extend( + command + .get_args() + .map(|arg| shell_escape(arg.to_string_lossy().as_ref())), + ); + rendered.join(" ") +} + +fn shell_escape(argument: &str) -> String { + if argument.is_empty() { + return "''".to_string(); + } + if argument + .chars() + .all(|ch| ch.is_ascii_alphanumeric() || matches!(ch, '/' | '.' | '_' | '-' | ':' | '=')) + { + return argument.to_string(); + } + format!("'{}'", argument.replace('\'', "'\"'\"'")) +} + +fn read_manifest_info(path: &Path) -> Result { + let contents = fs::read_to_string(path) + .with_context(|| format!("failed to read manifest {}", path.display()))?; + let document: toml::Value = + toml::from_str(&contents).with_context(|| format!("failed to parse {}", path.display()))?; + let package = document + .get("package") + .and_then(|value| value.as_table()) + .ok_or_else(|| anyhow!("{} is missing a [package] table", path.display()))?; + let name = package + .get("name") + .and_then(|value| value.as_str()) + .ok_or_else(|| anyhow!("{} is missing package.name", path.display()))?; + let version = package + .get("version") + .and_then(|value| value.as_str()) + .ok_or_else(|| anyhow!("{} is missing package.version", path.display()))?; + Ok(ManifestInfo { + name: name.to_owned(), + version: version.to_owned(), + }) +} + +fn copy_path(source: &Path, destination: &Path) -> Result<()> { + if source.is_dir() { + copy_dir_recursive(source, destination) + } else if source.is_file() { + copy_file(source, destination) + } else { + bail!("{} does not exist", source.display()); + } +} + +fn copy_dir_recursive(source: &Path, destination: &Path) -> Result<()> { + fs::create_dir_all(destination) + .with_context(|| format!("failed to create {}", destination.display()))?; + for entry in + fs::read_dir(source).with_context(|| format!("failed to read {}", source.display()))? + { + let entry = entry.with_context(|| format!("failed to read {}", source.display()))?; + let source_path = entry.path(); + let destination_path = destination.join(entry.file_name()); + copy_path(&source_path, &destination_path)?; + } + Ok(()) +} + +fn copy_file(source: &Path, destination: &Path) -> Result<()> { + if let Some(parent) = destination.parent() { + fs::create_dir_all(parent) + .with_context(|| format!("failed to create {}", parent.display()))?; + } + fs::copy(source, destination).with_context(|| { + format!( + "failed to copy {} to {}", + source.display(), + destination.display() + ) + })?; + let permissions = fs::metadata(source) + .with_context(|| format!("failed to read metadata for {}", source.display()))? + .permissions(); + fs::set_permissions(destination, permissions) + .with_context(|| format!("failed to set permissions for {}", destination.display()))?; + Ok(()) +} + +const ROOT_MANIFEST: &str = "Cargo.toml"; +const ZNG_MANIFEST: &str = "Cargo-zng.toml"; +const SYSTEST_DIR: &str = "systest"; +const SYSTEST_MANIFEST: &str = "systest/Cargo.toml"; +const SYSTEST_ZNG_MANIFEST: &str = "systest/Cargo-zng.toml"; +const ROOT_PACKAGE_NAME: &str = "libz-sys"; +const ZNG_PACKAGE_NAME: &str = "libz-ng-sys"; +const PACKAGE_LIST_EXCLUDE: &[&str] = &[".cargo_vcs_info.json", "Cargo.lock", "Cargo.toml.orig"]; +const REQUIRED_WORKTREE_PATHS: &[&str] = &[ + "src/zlib/adler32.c", + "src/zlib/zlib.h", + "src/zlib-ng/adler32.c", + "src/zlib-ng/CMakeLists.txt", +]; +const LIBZ_SYS_PACKAGE_SENTINELS: &[&str] = &[ + "build.rs", + "src/zlib/adler32.c", + "src/zlib/zlib.h", + "src/zlib-ng/adler32.c", + "src/zlib-ng/CMakeLists.txt", + "zng/cmake.rs", +]; +const LIBZ_NG_PACKAGE_SENTINELS: &[&str] = &[ + "README-zng.md", + "src/lib.rs", + "src/zlib-ng/adler32.c", + "src/zlib-ng/CMakeLists.txt", + "zng/cmake.rs", +]; + +#[derive(Clone, Debug, Eq, PartialEq)] +struct ManifestInfo { + name: String, + version: String, +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum FeatureSet { + Default, + Static, + ZlibNg, + ZlibNgNoCmake, +} + +impl FeatureSet { + fn describe(self) -> &'static str { + match self { + Self::Default => "default features", + Self::Static => "--features static", + Self::ZlibNg => "--no-default-features --features zlib-ng", + Self::ZlibNgNoCmake => { + "--no-default-features --features zlib-ng-no-cmake-experimental-community-maintained" + } + } + } + + fn append_publish_args(self, command: &mut Command) { + match self { + Self::Default => {} + Self::Static => { + command.arg("--features").arg("static"); + } + Self::ZlibNg => { + command + .arg("--no-default-features") + .arg("--features") + .arg("zlib-ng"); + } + Self::ZlibNgNoCmake => { + command + .arg("--no-default-features") + .arg("--features") + .arg("zlib-ng-no-cmake-experimental-community-maintained"); + } + } + } +} + +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +enum PublishMode { + DryRun, + /// The upload step intentionally uses `cargo publish --no-verify` because + /// this tool already verified the release with the full dry-run feature + /// matrix. Running Cargo's built-in verify again here would be narrower and + /// redundant. + UploadAfterVerification, +} + +impl PublishMode { + fn append_args(self, command: &mut Command) { + match self { + Self::DryRun => { + command.arg("--dry-run"); + } + Self::UploadAfterVerification => { + command.arg("--no-verify"); + } + } + } +} + +struct StagedZng { + _tempdir: TempDir, + root: PathBuf, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn publish_mode_appends_safe_flags() { + let mut command = Command::new("cargo"); + command.arg("publish"); + PublishMode::DryRun.append_args(&mut command); + assert!(command.get_args().any(|arg| arg == OsStr::new("--dry-run"))); + + let mut command = Command::new("cargo"); + command.arg("publish"); + PublishMode::UploadAfterVerification.append_args(&mut command); + assert!( + command + .get_args() + .any(|arg| arg == OsStr::new("--no-verify")) + ); + } + + #[test] + fn feature_sets_append_expected_publish_flags() { + let mut command = Command::new("cargo"); + command.arg("publish"); + FeatureSet::ZlibNg.append_publish_args(&mut command); + let args: Vec<_> = command.get_args().collect(); + assert_eq!( + args, + vec![ + OsStr::new("publish"), + OsStr::new("--no-default-features"), + OsStr::new("--features"), + OsStr::new("zlib-ng"), + ] + ); + } + + #[test] + fn zng_publish_defaults_to_dry_run() { + let sanitized = sanitize_zng_args(vec![OsString::from("publish")]).unwrap(); + assert_eq!( + sanitized, + vec![OsString::from("publish"), OsString::from("--dry-run")] + ); + } + + #[test] + fn zng_publish_rejects_no_verify() { + let error = sanitize_zng_args(vec![ + OsString::from("publish"), + OsString::from("--no-verify"), + ]) + .unwrap_err(); + assert!(error.to_string().contains("--no-verify")); + } + + #[test] + fn manifest_parser_reads_package_name_and_version() { + let tempdir = tempfile::tempdir().unwrap(); + let manifest_path = tempdir.path().join("Cargo.toml"); + fs::write( + &manifest_path, + "[package]\nname = \"example\"\nversion = \"1.2.3\"\n", + ) + .unwrap(); + let manifest = read_manifest_info(&manifest_path).unwrap(); + assert_eq!( + manifest, + ManifestInfo { + name: "example".to_string(), + version: "1.2.3".to_string(), + } + ); + } +} diff --git a/systest/build.rs b/systest/build.rs index 09b23a65..a7cebb22 100644 --- a/systest/build.rs +++ b/systest/build.rs @@ -1,6 +1,8 @@ use std::env; fn main() { + println!("cargo:rustc-check-cfg=cfg(zng)"); + let zng = env::var("CARGO_PKG_NAME").unwrap() == "systest-zng"; let mut cfg = ctest2::TestGenerator::new(); cfg.define("WITH_GZFILEOP", Some("ON")); @@ -44,12 +46,8 @@ fn main() { } else if n == "z_off_t" { return "z_off64_t".to_string(); } - } else { - if n == "z_size" { - return "unsigned long".to_string(); - } else if n == "z_checksum" { - return "unsigned long".to_string(); - } + } else if n == "z_size" || n == "z_checksum" { + return "unsigned long".to_string(); } if n == "internal_state" { format!("struct {}", n) @@ -57,11 +55,9 @@ fn main() { n.to_string() } }); - cfg.skip_signededness(|ty| match ty { + cfg.skip_signededness(|ty| matches!(ty, "gz_headerp" | "voidpf" | "voidcf" | "voidp" | "out_func" | "voidpc" | "gzFile" - | "in_func" | "free_func" | "alloc_func" | "z_streamp" => true, - _ => false, - }); + | "in_func" | "free_func" | "alloc_func" | "z_streamp")); cfg.skip_field_type(|s, field| s == "z_stream" && (field == "next_in" || field == "msg")); cfg.generate("../src/lib.rs", "all.rs"); } diff --git a/systest/src/main.rs b/systest/src/main.rs index 3d6cc09e..a69f1c9a 100644 --- a/systest/src/main.rs +++ b/systest/src/main.rs @@ -1,9 +1,27 @@ #![allow(bad_style, improper_ctypes)] use libc::*; -#[cfg(not(zng))] -use libz_sys::*; #[cfg(zng)] use libz_ng_sys::*; +#[cfg(not(zng))] +use libz_sys::*; + +mod generated { + #![allow( + missing_abi, + function_casts_as_integer, + clippy::all + )] + + use super::*; + + include!(concat!(env!("OUT_DIR"), "/all.rs")); + + pub(crate) fn run() { + main(); + } +} -include!(concat!(env!("OUT_DIR"), "/all.rs")); +fn main() { + generated::run(); +} diff --git a/zng/cc.rs b/zng/cc.rs index 367d207a..44a935b2 100644 --- a/zng/cc.rs +++ b/zng/cc.rs @@ -311,7 +311,7 @@ pub fn build_zlib_ng(target: &str, compat: bool) { // for arm, don't know if that is still true though if !cfg.is_msvc || is_aarch64 { cfg.define("ARM_ACLE", None).define("HAVE_ARM_ACLE_H", None); - cfg.append(Some("arch/arm"), &["crc32_acle"]); + cfg.append(Some("arch/arm"), &["crc32_armv8"]); // When targeting aarch64 we already need to specify +simd, so // we do that once later in this block if !is_aarch64 {