Skip to content

Commit

Permalink
feat(cargo-link): Implement command (#41)
Browse files Browse the repository at this point in the history
  • Loading branch information
kdy1 authored Aug 8, 2024
1 parent ff40b64 commit 64cbcab
Show file tree
Hide file tree
Showing 11 changed files with 511 additions and 36 deletions.
328 changes: 302 additions & 26 deletions Cargo.lock

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,10 @@ repository = "https://github.com/dudykr/ddbase.git"

[workspace.dependencies]
Inflector = { version = "0.11.4", default-features = false }
anyhow = "1.0.86"
arrayvec = "0.7.2"
cargo_metadata = "0.18.1"
clap = "4.5.13"
compact_str = "0.7.1"
criterion = "0.5.1"
hashbrown = { version = "0.14.3", default-features = false }
Expand All @@ -38,4 +41,5 @@ smartstring = "1.0.1"
smol_str = "0.2.0"
string_cache = "0.8.7"
syn = "2.0.39"
toml_edit = "0.22.20"
triomphe = "0.1.11"
6 changes: 5 additions & 1 deletion apps/cargo-link2/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,12 @@ edition = { workspace = true }
license = { workspace = true }
name = "cargo-link2"
repository = { workspace = true }
version = "0.1.0"
version = "0.1.1"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = { workspace = true }
cargo_metadata = { workspace = true }
clap = { workspace = true, features = ["derive"] }
toml_edit = { workspace = true }
195 changes: 193 additions & 2 deletions apps/cargo-link2/src/main.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,194 @@
fn main() {
println!("Hello, world!");
use std::{
collections::HashSet,
env::current_dir,
path::{Path, PathBuf},
};

use anyhow::{Context, Result};
use cargo_metadata::{Metadata, MetadataCommand};
use clap::Parser;

#[derive(Debug, Parser)]
struct CliArgs {
/// Changes the link location to <dir>.
///
/// Defaults to the current directory.
#[clap(short = 'C', long)]
dir: Option<PathBuf>,

/// The target directory to link to the current project.
///
/// If the target directory is a cargo workspace, all packages in the
/// workspace will be linked.
target_dir: PathBuf,
}

fn main() -> Result<()> {
let args = CliArgs::parse();

let working_dir = match args.dir {
Some(v) => v,
None => current_dir().context("failed to get current directory")?,
};

let link_candidates =
list_of_crates(&args.target_dir).context("failed to get candidates for linking")?;

let crate_names = add_patch_section(&working_dir, &link_candidates)
.context("failed to add patch section to Cargo.toml")?;

run_cargo_update(&working_dir, &crate_names)
.context("failed to run cargo update in the working directory")?;

Ok(())
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
struct PatchPkg {
name: String,
path: PathBuf,
}

fn list_of_crates(target_dir: &Path) -> Result<Vec<PatchPkg>> {
let md = MetadataCommand::new()
.no_deps()
.current_dir(target_dir)
.exec()
.with_context(|| format!("failed to run cargo metadata in '{}'", target_dir.display()))?;

let ws_members = md.workspace_members;

Ok(md
.packages
.into_iter()
.filter(|p| ws_members.contains(&p.id))
.map(|p| PatchPkg {
name: p.name,
path: PathBuf::from(p.manifest_path)
.parent()
.unwrap()
.to_path_buf(),
})
.collect())
}

fn add_patch_section(working_dir: &Path, link_candidates: &[PatchPkg]) -> Result<Vec<String>> {
let md = MetadataCommand::new()
.current_dir(working_dir)
.exec()
.with_context(|| {
format!(
"failed to run cargo metadata in '{}'",
working_dir.display()
)
})?;

let root_manifest_path = find_root_manifest_path(&md).with_context(|| {
format!(
"failed to find the root manifest for '{}'",
working_dir.display()
)
})?;

let toml = std::fs::read_to_string(&root_manifest_path)
.with_context(|| format!("failed to read '{}'", root_manifest_path.display()))?;

let mut doc = toml.parse::<toml_edit::DocumentMut>().with_context(|| {
format!(
"failed to parse Cargo.toml at '{}'",
root_manifest_path.display()
)
})?;

let (crates_to_link, all_deps) = find_used_crates(&md, link_candidates)
.with_context(|| format!("failed to find used crates in '{}'", working_dir.display()))?;

if doc.get("patch").is_none() {
doc["patch"] = toml_edit::table();
}

let patch = doc["patch"].as_table_mut().unwrap();
if patch.get("crates-io").is_none() {
patch["crates-io"] = toml_edit::table();
}

let crates_io = patch["crates-io"].as_table_mut().unwrap();

for PatchPkg { name, path } in &crates_to_link {
let mut v = toml_edit::table();
v["path"] = toml_edit::value(path.display().to_string());
crates_io[&**name] = v;
}

std::fs::write(&root_manifest_path, doc.to_string())
.with_context(|| format!("failed to write to '{}'", root_manifest_path.display()))?;

Ok(all_deps)
}

fn find_root_manifest_path(md: &Metadata) -> Result<PathBuf> {
if let Some(root) = md.root_package() {
Ok(root.manifest_path.clone().into())
} else {
Ok(PathBuf::from(md.workspace_root.clone()).join("Cargo.toml"))
}
}

/// `(direct, all)``
fn find_used_crates(
md: &Metadata,
link_candidates: &[PatchPkg],
) -> Result<(Vec<PatchPkg>, Vec<String>)> {
let mut direct_deps = HashSet::new();
let mut all_deps = HashSet::new();

let workspace_packages = md
.packages
.iter()
.filter(|p| md.workspace_members.contains(&p.id))
.map(|p| p.name.clone())
.collect::<HashSet<_>>();

for pkg in &md.packages {
for dep in &pkg.dependencies {
if workspace_packages.contains(&pkg.name) {
if let Some(linked) = link_candidates.iter().find(|c| c.name == dep.name) {
direct_deps.insert(linked.clone());
}
} else if link_candidates.iter().any(|c| c.name == dep.name) {
all_deps.insert(pkg.name.clone());
}
}
}

let mut direct_deps = direct_deps.into_iter().collect::<Vec<_>>();
direct_deps.sort();

let mut all_deps = all_deps.into_iter().collect::<Vec<_>>();
all_deps.sort();

Ok((direct_deps, all_deps))
}

fn run_cargo_update(dir: &PathBuf, crates: &[String]) -> Result<()> {
let mut cmd = std::process::Command::new(cargo_bin());
cmd.current_dir(dir);
cmd.arg("update");
for pkg in crates {
cmd.arg("--package");
cmd.arg(pkg);
}

eprintln!("Running: {:?}", cmd);
let status = cmd.status().context("failed to run cargo update")?;

if !status.success() {
anyhow::bail!("cargo update failed with status: {}", status);
}

Ok(())
}

fn cargo_bin() -> String {
std::env::var("CARGO").unwrap_or_else(|_| "cargo".to_string())
}
2 changes: 1 addition & 1 deletion crates/default-from-serde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = { workspace = true }
license = { workspace = true }
name = "default-from-serde"
repository = { workspace = true }
version = "0.1.2"
version = "0.1.3"

[features]
default = ["std"]
Expand Down
2 changes: 1 addition & 1 deletion crates/derive-default-from-serde/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = { workspace = true }
license = { workspace = true }
name = "derive-default-from-serde"
repository = { workspace = true }
version = "0.1.2"
version = "0.1.3"

[lib]
proc-macro = true
Expand Down
2 changes: 1 addition & 1 deletion crates/dudy-malloc/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = { workspace = true }
license = { workspace = true }
name = "dudy-malloc"
repository = { workspace = true }
version = "0.1.1"
version = "0.1.2"

[dependencies]

Expand Down
2 changes: 1 addition & 1 deletion crates/hstr/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = { workspace = true }
license = { workspace = true }
name = "hstr"
repository = { workspace = true }
version = "0.2.10"
version = "0.2.11"

[lib]
bench = false
Expand Down
2 changes: 1 addition & 1 deletion crates/is-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ edition = "2018"
license = { workspace = true }
name = "is-macro"
repository = { workspace = true }
version = "0.3.5"
version = "0.3.6"

[lib]
proc-macro = true
Expand Down
2 changes: 1 addition & 1 deletion crates/st-map/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2018"
license = { workspace = true }
name = "st-map"
repository = { workspace = true }
version = "0.2.3"
version = "0.2.4"

[lib]

Expand Down
2 changes: 1 addition & 1 deletion crates/static-map-macro/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ edition = "2018"
license = { workspace = true }
name = "static-map-macro"
repository = { workspace = true }
version = "0.3.3"
version = "0.3.4"

[lib]
proc-macro = true
Expand Down

0 comments on commit 64cbcab

Please sign in to comment.