From a1ce44861a9bdaa3f11efe4153e46b2c9ece484b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Misty=20De=20M=C3=A9o?= Date: Thu, 14 Sep 2023 12:26:20 -0700 Subject: [PATCH] feat: add a linkage checker subcommand --- Cargo.lock | 66 +++++ cargo-dist/Cargo.toml | 2 + cargo-dist/src/cli.rs | 18 ++ cargo-dist/src/errors.rs | 15 ++ cargo-dist/src/lib.rs | 229 ++++++++++++++++++ cargo-dist/src/main.rs | 24 +- cargo-dist/src/tasks.rs | 12 +- cargo-dist/templates/ci/github_ci.yml.j2 | 5 + .../tests/snapshots/akaikatana_basic.snap | 5 + .../akaikatana_repo_with_dot_git.snap | 5 + .../tests/snapshots/axolotlsay_basic.snap | 5 + .../snapshots/axolotlsay_edit_existing.snap | 5 + .../axolotlsay_no_homebrew_publish.snap | 5 + .../axolotlsay_ssldotcom_windows_sign.snap | 5 + ...xolotlsay_ssldotcom_windows_sign_prod.snap | 5 + .../axolotlsay_user_publish_job.snap | 5 + cargo-dist/tests/snapshots/long-help.snap | 1 + cargo-dist/tests/snapshots/markdown-help.snap | 25 ++ cargo-dist/tests/snapshots/short-help.snap | 1 + 19 files changed, 431 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 6f1aa5e3c..a9d0b8862 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -305,10 +305,12 @@ dependencies = [ "cruet", "dialoguer", "flate2", + "goblin", "guppy", "include_dir", "insta", "itertools 0.11.0", + "mach_object", "miette", "minijinja", "newline-converter", @@ -827,6 +829,17 @@ version = "0.28.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6fb8d784f27acf97159b40fc4db5ecd8aa23b9ad5ef69cdd136d3bc80665f0c0" +[[package]] +name = "goblin" +version = "0.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27c1b4369c2cd341b5de549380158b105a04c331be5db9110eef7b6d2742134" +dependencies = [ + "log 0.4.20", + "plain", + "scroll", +] + [[package]] name = "guppy" version = "0.15.2" @@ -1219,6 +1232,22 @@ dependencies = [ "pkg-config", ] +[[package]] +name = "mach_object" +version = "0.1.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6f2d7176b94027af58085a2c9d27c4e416586caba409c314569213901d6068" +dependencies = [ + "bitflags 1.3.2", + "byteorder", + "lazy_static", + "libc", + "log 0.4.20", + "thiserror", + "time", + "uuid", +] + [[package]] name = "memchr" version = "2.6.3" @@ -1482,6 +1511,12 @@ version = "0.3.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + [[package]] name = "proc-macro2" version = "1.0.66" @@ -1702,6 +1737,26 @@ dependencies = [ "syn 1.0.109", ] +[[package]] +name = "scroll" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "04c565b551bafbef4157586fa379538366e4385d42082f255bfd96e4fe8519da" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1db149f81d46d2deba7cd3c50772474707729550221e69588478ebf9ada425ae" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.31", +] + [[package]] name = "sct" version = "0.7.0" @@ -2068,8 +2123,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17f6bb557fd245c28e6411aa56b6403c689ad95061f50e4be16c274e70a17e48" dependencies = [ "deranged", + "itoa", "serde", "time-core", + "time-macros", ] [[package]] @@ -2078,6 +2135,15 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7300fbefb4dadc1af235a9cef3737cea692a9d97e1b9cbcd4ebdae6f8868e6fb" +[[package]] +name = "time-macros" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a942f44339478ef67935ab2bbaec2fb0322496cf3cbe84b261e06ac3814c572" +dependencies = [ + "time-core", +] + [[package]] name = "tinyvec" version = "1.6.0" diff --git a/cargo-dist/Cargo.toml b/cargo-dist/Cargo.toml index 3bd192ced..f8bfe53e6 100644 --- a/cargo-dist/Cargo.toml +++ b/cargo-dist/Cargo.toml @@ -53,6 +53,8 @@ include_dir = "0.7.3" itertools = "0.11.0" cargo-wix = "0.3.7" uuid = { version = "1", features = ["v4"] } +mach_object = "0.1" +goblin = "0.7.1" [dev-dependencies] insta = { version = "1.26.0", features = ["filters"] } diff --git a/cargo-dist/src/cli.rs b/cargo-dist/src/cli.rs index 17eb50222..1936f5043 100644 --- a/cargo-dist/src/cli.rs +++ b/cargo-dist/src/cli.rs @@ -128,6 +128,9 @@ pub enum Commands { #[clap(disable_version_flag = true)] #[clap(hide = true)] GenerateCi(GenerateCiArgs), + /// Report on the dynamic libraries used by the built artifacts. + #[clap(disable_version_flag = true)] + Linkage(LinkageArgs), /// Generate the final build manifest without running any builds. /// /// This command is designed to match the exact behaviour of @@ -278,6 +281,21 @@ pub struct GenerateCiArgs { #[clap(default_value_t = false)] pub check: bool, } +#[derive(Args, Clone, Debug)] +pub struct LinkageArgs { + /// Print human-readable output + #[clap(long)] + #[clap(default_value_t = false)] + pub print_output: bool, + /// Print output as JSON + #[clap(long)] + #[clap(default_value_t = false)] + pub print_json: bool, + #[clap(long)] + #[clap(hide = true)] + #[clap(default_value = "")] + pub artifacts: String, +} #[derive(Args, Clone, Debug)] pub struct HelpMarkdownArgs {} diff --git a/cargo-dist/src/errors.rs b/cargo-dist/src/errors.rs index 234d70e14..24b28ca79 100644 --- a/cargo-dist/src/errors.rs +++ b/cargo-dist/src/errors.rs @@ -240,6 +240,21 @@ pub enum DistError { /// Name of the msi style: String, }, + /// Linkage report can't be run for this combination of OS and target + #[error("unable to run linkage report for {target} on {host}")] + LinkageCheckInvalidOS { + /// The OS the check was run on + host: String, + /// The OS being checked + target: String, + }, + /// Linkage report can't be run for this target + #[error("unable to run linkage report for this type of binary")] + LinkageCheckUnsupportedBinary {}, + + /// random i/o error + #[error(transparent)] + Goblin(#[from] goblin::error::Error), } impl From for DistError { diff --git a/cargo-dist/src/lib.rs b/cargo-dist/src/lib.rs index d0e6ed2e9..1f9ab10db 100644 --- a/cargo-dist/src/lib.rs +++ b/cargo-dist/src/lib.rs @@ -12,6 +12,8 @@ use std::{ collections::{BTreeMap, HashMap}, + fs::File, + io::{Cursor, Read}, process::Command, }; @@ -26,7 +28,10 @@ use cargo_dist_schema::{Asset, AssetKind, DistManifest, ExecutableAsset}; use config::{ ArtifactMode, ChecksumStyle, CompressionImpl, Config, DirtyMode, GenerateMode, ZipStyle, }; +use goblin::Object; +use mach_object::{LoadCommand, OFile}; use semver::Version; +use serde::Serialize; use tracing::{info, warn}; use errors::*; @@ -621,6 +626,15 @@ pub struct GenerateArgs { pub modes: Vec, } +/// Arguments for `cargo dist linkage` ([`do_linkage][]) +#[derive(Debug)] +pub struct LinkageArgs { + /// Print human-readable output + pub print_output: bool, + /// Print output as JSON + pub print_json: bool, +} + fn do_generate_preflight_checks(dist: &DistGraph) -> Result<()> { // Enforce cargo-dist-version, unless... // @@ -713,6 +727,221 @@ pub fn run_generate(dist: &DistGraph, args: &GenerateArgs) -> Result<()> { Ok(()) } +/// Determinage dynamic linkage of built artifacts (impl of `cargo dist linkage`) +pub fn do_linkage(cfg: &Config, args: &LinkageArgs) -> Result<()> { + let dist = gather_work(cfg)?; + + let mut reports = vec![]; + + for target in cfg.targets.clone() { + let artifacts: Vec = dist + .artifacts + .clone() + .into_iter() + .filter(|r| r.target_triples.contains(&target)) + .collect(); + + if artifacts.is_empty() { + eprintln!("No matching artifact for target {target}"); + continue; + } + + for artifact in artifacts { + let path = Utf8PathBuf::from(&dist.dist_dir).join(format!("{}-{target}", artifact.id)); + + for (_, binary) in artifact.required_binaries { + let bin_path = path.join(binary); + if !bin_path.exists() { + eprintln!("Binary {bin_path} missing; skipping check"); + } else { + reports.push(determine_linkage(&bin_path, &target)?); + } + } + } + } + + if args.print_output { + for report in &reports { + eprintln!("{}", report.report()); + } + } + if args.print_json { + let j = serde_json::to_string(&reports).unwrap(); + println!("{}", j); + } + + Ok(()) +} + +/// Information about dynamic libraries used by a binary +#[derive(Debug, Serialize)] +pub struct Linkage { + /// The filename of the binary + pub binary: String, + /// The target triple for which the binary was built + pub target: String, + /// Libraries included with the operating system + pub system: Vec, + /// Libraries provided by the Homebrew package manager + pub homebrew: Vec, + /// Public libraries not provided by the system and not managed by any package manager + pub public_unmanaged: Vec, + /// Libraries which don't fall into any other categories + pub other: Vec, + /// Frameworks, only used on macOS + pub frameworks: Vec, +} + +impl Linkage { + /// Formatted human-readable output + pub fn report(&self) -> String { + let s = format!( + r#"{} ({}): + +System: {} +Homebrew: {} +Public (unmanaged): {} +Frameworks: {} +Other: {}"#, + self.binary, + self.target, + self.system.join(" "), + self.homebrew.join(" "), + self.public_unmanaged.join(" "), + self.frameworks.join(" "), + self.other.join(" "), + ); + + s.to_owned() + } +} + +fn do_otool(path: &Utf8PathBuf) -> DistResult> { + let mut libraries = vec![]; + + let mut f = File::open(path)?; + let mut buf = vec![]; + let size = f.read_to_end(&mut buf).unwrap(); + let mut cur = Cursor::new(&buf[..size]); + if let OFile::MachFile { + header: _, + commands, + } = OFile::parse(&mut cur).unwrap() + { + let commands = commands + .iter() + .map(|load| load.command()) + .cloned() + .collect::>(); + + for command in commands { + match command { + LoadCommand::IdDyLib(ref dylib) + | LoadCommand::LoadDyLib(ref dylib) + | LoadCommand::LoadWeakDyLib(ref dylib) + | LoadCommand::ReexportDyLib(ref dylib) + | LoadCommand::LoadUpwardDylib(ref dylib) + | LoadCommand::LazyLoadDylib(ref dylib) => { + libraries.push(dylib.name.to_string()); + } + _ => {} + } + } + } + + Ok(libraries) +} + +fn do_ldd(path: &Utf8PathBuf) -> DistResult> { + let mut libraries = vec![]; + + let output = Command::new("ldd") + .arg(path) + .output() + .expect("Unable to run ldd"); + + let result = String::from_utf8_lossy(&output.stdout).to_string(); + let lines = result.trim_end().split('\n'); + + for line in lines { + let line = line.trim(); + // Not a library that actually concerns us + if line.starts_with("linux-vdso") { + continue; + } + + // Format: libname.so.1 => /path/to/libname.so.1 (address) + if let Some(path) = line.split("=>").nth(1) { + libraries.push((path.split(' ').next().unwrap()).to_owned()); + } else { + continue; + } + } + + Ok(libraries) +} + +fn do_pe(path: &Utf8PathBuf) -> DistResult> { + let buf = std::fs::read(path)?; + match Object::parse(&buf)? { + Object::PE(pe) => Ok(pe.libraries.into_iter().map(|s| s.to_owned()).collect()), + _ => Err(DistError::LinkageCheckUnsupportedBinary {}), + } +} + +fn determine_linkage(path: &Utf8PathBuf, target: &str) -> DistResult { + let libraries = match target { + // Can be run on any OS + "i686-apple-darwin" | "x86_64-apple-darwin" | "aarch64-apple-darwin" => do_otool(path)?, + "i686-unknown-linux-gnu" | "x86_64-unknown-linux-gnu" | "aarch64-unknown-linux-gnu" => { + // Currently can only be run on Linux + if std::env::consts::OS != "linux" { + return Err(DistError::LinkageCheckInvalidOS { + host: std::env::consts::OS.to_owned(), + target: target.to_owned(), + }); + } + do_ldd(path)? + } + // Can be run on any OS + "i686-pc-windows-msvc" | "x86_64-pc-windows-msvc" | "aarch64-pc-windows-msvc" => { + do_pe(path)? + } + _ => return Err(DistError::LinkageCheckUnsupportedBinary {}), + }; + + let mut linkage = Linkage { + binary: path.file_name().unwrap().to_owned(), + target: target.to_owned(), + system: vec![], + homebrew: vec![], + public_unmanaged: vec![], + frameworks: vec![], + other: vec![], + }; + for library in libraries { + if library.starts_with("/opt/homebrew") { + linkage.homebrew.push(library.clone()); + } else if library.starts_with("/usr/lib") || library.starts_with("/lib") { + linkage.system.push(library.clone()); + } else if library.starts_with("/System/Library/Frameworks") + || library.starts_with("/Library/Frameworks") + { + linkage.frameworks.push(library.clone()); + } else if library.starts_with("/usr/local") { + if std::fs::canonicalize(&library)?.starts_with("/usr/local/Cellar") { + linkage.homebrew.push(library.clone()); + } else { + linkage.public_unmanaged.push(library.clone()); + } + } else { + linkage.other.push(library.clone()); + } + } + + Ok(linkage) +} + /// Run any necessary integrity checks for "primary" commands like build/plan /// /// (This is currently equivalent to `cargo dist generate --check`) diff --git a/cargo-dist/src/main.rs b/cargo-dist/src/main.rs index 2556d2bf5..7620e9d68 100644 --- a/cargo-dist/src/main.rs +++ b/cargo-dist/src/main.rs @@ -15,7 +15,7 @@ use cli::{ use console::Term; use miette::IntoDiagnostic; -use crate::cli::{BuildArgs, GenerateArgs, GenerateCiArgs, InitArgs}; +use crate::cli::{BuildArgs, GenerateArgs, GenerateCiArgs, InitArgs, LinkageArgs}; mod cli; @@ -33,6 +33,7 @@ fn real_main(cli: &axocli::CliApp) -> Result<(), miette::Report> { Commands::Init(args) => cmd_init(config, args), Commands::Generate(args) => cmd_generate(config, args), Commands::GenerateCi(args) => cmd_generate_ci(config, args), + Commands::Linkage(args) => cmd_linkage(config, args), Commands::Manifest(args) => cmd_manifest(config, args), Commands::Plan(args) => cmd_plan(config, args), Commands::HelpMarkdown(args) => cmd_help_md(config, args), @@ -238,6 +239,27 @@ fn cmd_generate(cli: &Cli, args: &GenerateArgs) -> Result<(), miette::Report> { do_generate(&config, &args) } +fn cmd_linkage(cli: &Cli, args: &LinkageArgs) -> Result<(), miette::Report> { + let config = cargo_dist::config::Config { + needs_coherent_announcement_tag: false, + artifact_mode: cargo_dist::config::ArtifactMode::All, + no_local_paths: cli.no_local_paths, + allow_all_dirty: cli.allow_dirty, + targets: cli.target.clone(), + ci: cli.ci.iter().map(|ci| ci.to_lib()).collect(), + installers: cli.installer.iter().map(|ins| ins.to_lib()).collect(), + announcement_tag: cli.tag.clone(), + }; + let mut options = cargo_dist::LinkageArgs { + print_output: args.print_output, + print_json: args.print_json, + }; + if !args.print_output && !args.print_json { + options.print_output = true; + } + do_linkage(&config, &options) +} + fn cmd_generate_ci(cli: &Cli, args: &GenerateCiArgs) -> Result<(), miette::Report> { cmd_generate( cli, diff --git a/cargo-dist/src/tasks.rs b/cargo-dist/src/tasks.rs index bbeba2daa..ccee36974 100644 --- a/cargo-dist/src/tasks.rs +++ b/cargo-dist/src/tasks.rs @@ -385,7 +385,7 @@ impl SymbolKind { } /// A distributable artifact we want to build -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Artifact { /// Unique id for the Artifact (its file name) /// @@ -417,7 +417,7 @@ pub struct Artifact { /// Info about an archive (zip/tarball) that should be made. Currently this is always part /// of an Artifact, and the final output will be [`Artifact::file_path`][]. -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Archive { /// An optional prefix path to nest all the archive contents under /// If None then all the archive's contents will be placed in the root @@ -435,7 +435,7 @@ pub struct Archive { } /// A kind of artifact (more specific fields) -#[derive(Debug)] +#[derive(Clone, Debug)] #[allow(clippy::large_enum_variant)] pub enum ArtifactKind { /// An Archive containing binaries (aka ExecutableZip) @@ -449,20 +449,20 @@ pub enum ArtifactKind { } /// An Archive containing binaries (aka ExecutableZip) -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct ExecutableZip { // everything important is already part of Artifact } /// A Symbols/Debuginfo Artifact -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Symbols { /// The kind of symbols this is kind: SymbolKind, } /// A logical release of an application that artifacts are grouped under -#[derive(Debug)] +#[derive(Clone, Debug)] pub struct Release { /// The name of the app pub app_name: String, diff --git a/cargo-dist/templates/ci/github_ci.yml.j2 b/cargo-dist/templates/ci/github_ci.yml.j2 index af9835657..02bbfa385 100644 --- a/cargo-dist/templates/ci/github_ci.yml.j2 +++ b/cargo-dist/templates/ci/github_ci.yml.j2 @@ -130,6 +130,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/akaikatana_basic.snap b/cargo-dist/tests/snapshots/akaikatana_basic.snap index 67ed0db4a..2c5ee47c5 100644 --- a/cargo-dist/tests/snapshots/akaikatana_basic.snap +++ b/cargo-dist/tests/snapshots/akaikatana_basic.snap @@ -1389,6 +1389,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/akaikatana_repo_with_dot_git.snap b/cargo-dist/tests/snapshots/akaikatana_repo_with_dot_git.snap index 67ed0db4a..2c5ee47c5 100644 --- a/cargo-dist/tests/snapshots/akaikatana_repo_with_dot_git.snap +++ b/cargo-dist/tests/snapshots/akaikatana_repo_with_dot_git.snap @@ -1389,6 +1389,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_basic.snap b/cargo-dist/tests/snapshots/axolotlsay_basic.snap index 09ae86248..3cc1c8f46 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_basic.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_basic.snap @@ -2284,6 +2284,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap b/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap index a77d3f92b..86f41b9d9 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_edit_existing.snap @@ -2259,6 +2259,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap b/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap index b2647a506..148bb5486 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_no_homebrew_publish.snap @@ -2259,6 +2259,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign.snap b/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign.snap index 7f7f41a46..ffb7b41ec 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign.snap @@ -1377,6 +1377,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign_prod.snap b/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign_prod.snap index ad77b0e24..47b2dee2b 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign_prod.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_ssldotcom_windows_sign_prod.snap @@ -1377,6 +1377,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap b/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap index e1121ff15..8177916c1 100644 --- a/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap +++ b/cargo-dist/tests/snapshots/axolotlsay_user_publish_job.snap @@ -2259,6 +2259,11 @@ jobs: echo "paths<> "$GITHUB_OUTPUT" jq --raw-output ".artifacts[]?.path | select( . != null )" dist-manifest.json >> "$GITHUB_OUTPUT" echo "EOF" >> "$GITHUB_OUTPUT" + - name: "Linkage report" + run: | + cargo dist linkage ${{ matrix.dist_args }} --print-output --print-json > linkage.json + + echo "linkage=$(cat linkage.json)" >> "${GITHUB_OUTPUT}" - name: "Upload artifacts" uses: actions/upload-artifact@v3 with: diff --git a/cargo-dist/tests/snapshots/long-help.snap b/cargo-dist/tests/snapshots/long-help.snap index cca421d25..db8efdd7c 100644 --- a/cargo-dist/tests/snapshots/long-help.snap +++ b/cargo-dist/tests/snapshots/long-help.snap @@ -14,6 +14,7 @@ Commands: build Build artifacts init Setup or update cargo-dist generate Generate one or more pieces of configuration + linkage Report on the dynamic libraries used by the built artifacts manifest Generate the final build manifest without running any builds plan Get a plan of what to build (and check project status) help Print this message or the help of the given subcommand(s) diff --git a/cargo-dist/tests/snapshots/markdown-help.snap b/cargo-dist/tests/snapshots/markdown-help.snap index f0851b4e2..47bb19579 100644 --- a/cargo-dist/tests/snapshots/markdown-help.snap +++ b/cargo-dist/tests/snapshots/markdown-help.snap @@ -22,6 +22,7 @@ cargo dist * [build](#cargo-dist-build): Build artifacts * [init](#cargo-dist-init): Setup or update cargo-dist * [generate](#cargo-dist-generate): Generate one or more pieces of configuration +* [linkage](#cargo-dist-linkage): Report on the dynamic libraries used by the built artifacts * [manifest](#cargo-dist-manifest): Generate the final build manifest without running any builds * [plan](#cargo-dist-plan): Get a plan of what to build (and check project status) * [help](#cargo-dist-help): Print this message or the help of the given subcommand(s) @@ -188,6 +189,29 @@ Print help (see a summary with '-h') ### GLOBAL OPTIONS This subcommand accepts all the [global options](#global-options) +


+## cargo dist linkage +Report on the dynamic libraries used by the built artifacts + +### Usage + +```text +cargo dist linkage [OPTIONS] +``` + +### Options +#### `--print-output` +Print human-readable output + +#### `--print-json` +Print output as JSON + +#### `-h, --help` +Print help (see a summary with '-h') + +### GLOBAL OPTIONS +This subcommand accepts all the [global options](#global-options) +


## cargo dist manifest Generate the final build manifest without running any builds. @@ -267,6 +291,7 @@ cargo dist help [COMMAND] * [build](#cargo-dist-build): Build artifacts * [init](#cargo-dist-init): Setup or update cargo-dist * [generate](#cargo-dist-generate): Generate one or more pieces of configuration +* [linkage](#cargo-dist-linkage): Report on the dynamic libraries used by the built artifacts * [manifest](#cargo-dist-manifest): Generate the final build manifest without running any builds * [plan](#cargo-dist-plan): Get a plan of what to build (and check project status) * [help](#cargo-dist-help): Print this message or the help of the given subcommand(s) diff --git a/cargo-dist/tests/snapshots/short-help.snap b/cargo-dist/tests/snapshots/short-help.snap index 1707e02fd..6e6b540e4 100644 --- a/cargo-dist/tests/snapshots/short-help.snap +++ b/cargo-dist/tests/snapshots/short-help.snap @@ -12,6 +12,7 @@ Commands: build Build artifacts init Setup or update cargo-dist generate Generate one or more pieces of configuration + linkage Report on the dynamic libraries used by the built artifacts manifest Generate the final build manifest without running any builds plan Get a plan of what to build (and check project status) help Print this message or the help of the given subcommand(s)