Skip to content

Commit 2adf7ee

Browse files
authored
Merge pull request #2091 from topecongiro/issue-2056
Add local dependencies to targets
2 parents 90b3222 + d10df70 commit 2adf7ee

File tree

1 file changed

+149
-93
lines changed

1 file changed

+149
-93
lines changed

src/bin/cargo-fmt.rs

+149-93
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ extern crate getopts;
1717
extern crate serde_json as json;
1818

1919
use std::env;
20-
use std::io::Write;
20+
use std::io::{self, Write};
2121
use std::path::PathBuf;
2222
use std::process::{Command, ExitStatus};
2323
use std::str;
@@ -126,7 +126,7 @@ pub enum Verbosity {
126126
fn format_crate(
127127
verbosity: Verbosity,
128128
workspace_hitlist: &WorkspaceHitlist,
129-
) -> Result<ExitStatus, std::io::Error> {
129+
) -> Result<ExitStatus, io::Error> {
130130
let targets = get_targets(workspace_hitlist)?;
131131

132132
// Currently only bin and lib files get formatted
@@ -180,6 +180,29 @@ pub struct Target {
180180
kind: TargetKind,
181181
}
182182

183+
impl Target {
184+
pub fn from_json(json_val: &Value) -> Option<Self> {
185+
let jtarget = json_val.as_object()?;
186+
let path = PathBuf::from(jtarget.get("src_path")?.as_str()?);
187+
let kinds = jtarget.get("kind")?.as_array()?;
188+
let kind = match kinds[0].as_str()? {
189+
"bin" => TargetKind::Bin,
190+
"lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
191+
"test" => TargetKind::Test,
192+
"example" => TargetKind::Example,
193+
"bench" => TargetKind::Bench,
194+
"custom-build" => TargetKind::CustomBuild,
195+
"proc-macro" => TargetKind::ProcMacro,
196+
_ => TargetKind::Other,
197+
};
198+
199+
Some(Target {
200+
path: path,
201+
kind: kind,
202+
})
203+
}
204+
}
205+
183206
#[derive(Debug, PartialEq, Eq)]
184207
pub enum WorkspaceHitlist {
185208
All,
@@ -205,121 +228,154 @@ impl WorkspaceHitlist {
205228
}
206229
}
207230

208-
// Returns a vector of all compile targets of a crate
209-
fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<Vec<Target>, std::io::Error> {
231+
fn get_cargo_metadata_from_utf8(v: &[u8]) -> Option<Value> {
232+
json::from_str(str::from_utf8(v).ok()?).ok()
233+
}
234+
235+
fn get_json_array_with<'a>(v: &'a Value, key: &str) -> Option<&'a Vec<Value>> {
236+
v.as_object()?.get(key)?.as_array()
237+
}
238+
239+
// `cargo metadata --no-deps | jq '.["packages"]'`
240+
fn get_packages(v: &[u8]) -> Result<Vec<Value>, io::Error> {
241+
let e = io::Error::new(
242+
io::ErrorKind::NotFound,
243+
String::from("`cargo metadata` returned json without a 'packages' key"),
244+
);
245+
match get_cargo_metadata_from_utf8(v) {
246+
Some(ref json_obj) => get_json_array_with(json_obj, "packages").cloned().ok_or(e),
247+
None => Err(e),
248+
}
249+
}
250+
251+
fn extract_target_from_package(package: &Value) -> Option<Vec<Target>> {
252+
let jtargets = get_json_array_with(package, "targets")?;
210253
let mut targets: Vec<Target> = vec![];
211-
if *workspace_hitlist == WorkspaceHitlist::None {
212-
let output = Command::new("cargo").arg("read-manifest").output()?;
213-
if output.status.success() {
214-
// None of the unwraps should fail if output of `cargo read-manifest` is correct
215-
let data = &String::from_utf8(output.stdout).unwrap();
216-
let json: Value = json::from_str(data).unwrap();
217-
let json_obj = json.as_object().unwrap();
218-
let jtargets = json_obj.get("targets").unwrap().as_array().unwrap();
219-
for jtarget in jtargets {
220-
targets.push(target_from_json(jtarget));
221-
}
254+
for jtarget in jtargets {
255+
targets.push(Target::from_json(&jtarget)?);
256+
}
257+
Some(targets)
258+
}
222259

223-
return Ok(targets);
260+
fn filter_packages_with_hitlist<'a>(
261+
packages: Vec<Value>,
262+
workspace_hitlist: &'a WorkspaceHitlist,
263+
) -> Result<Vec<Value>, &'a String> {
264+
if *workspace_hitlist == WorkspaceHitlist::All {
265+
return Ok(packages);
266+
}
267+
let mut hitlist: HashSet<&String> = workspace_hitlist
268+
.get_some()
269+
.map_or(HashSet::new(), HashSet::from_iter);
270+
let members: Vec<Value> = packages
271+
.into_iter()
272+
.filter(|member| {
273+
member
274+
.as_object()
275+
.and_then(|member_obj| {
276+
member_obj
277+
.get("name")
278+
.and_then(Value::as_str)
279+
.map(|member_name| {
280+
hitlist.take(&member_name.to_string()).is_some()
281+
})
282+
})
283+
.unwrap_or(false)
284+
})
285+
.collect();
286+
if hitlist.is_empty() {
287+
Ok(members)
288+
} else {
289+
Err(hitlist.into_iter().next().unwrap())
290+
}
291+
}
292+
293+
fn get_dependencies_from_package(package: &Value) -> Option<Vec<PathBuf>> {
294+
let jdependencies = get_json_array_with(package, "dependencies")?;
295+
let root_path = env::current_dir().ok()?;
296+
let mut dependencies: Vec<PathBuf> = vec![];
297+
for jdep in jdependencies {
298+
let jdependency = jdep.as_object()?;
299+
if !jdependency.get("source")?.is_null() {
300+
continue;
224301
}
225-
return Err(std::io::Error::new(
226-
std::io::ErrorKind::NotFound,
227-
str::from_utf8(&output.stderr).unwrap(),
228-
));
302+
let name = jdependency.get("name")?.as_str()?;
303+
let mut path = root_path.clone();
304+
path.push(&name);
305+
dependencies.push(path);
229306
}
230-
// This happens when cargo-fmt is not used inside a crate or
231-
// is used inside a workspace.
232-
// To ensure backward compatability, we only use `cargo metadata` for workspaces.
233-
// TODO: Is it possible only use metadata or read-manifest
307+
Some(dependencies)
308+
}
309+
310+
// Returns a vector of local dependencies under this crate
311+
fn get_path_to_local_dependencies(packages: &[Value]) -> Vec<PathBuf> {
312+
let mut local_dependencies: Vec<PathBuf> = vec![];
313+
for package in packages {
314+
if let Some(mut d) = get_dependencies_from_package(package) {
315+
local_dependencies.append(&mut d);
316+
}
317+
}
318+
local_dependencies
319+
}
320+
321+
// Returns a vector of all compile targets of a crate
322+
fn get_targets(workspace_hitlist: &WorkspaceHitlist) -> Result<Vec<Target>, io::Error> {
234323
let output = Command::new("cargo")
235-
.arg("metadata")
236-
.arg("--no-deps")
324+
.args(&["metadata", "--no-deps", "--format-version=1"])
237325
.output()?;
238326
if output.status.success() {
239-
let data = &String::from_utf8(output.stdout).unwrap();
240-
let json: Value = json::from_str(data).unwrap();
241-
let json_obj = json.as_object().unwrap();
242-
let mut hitlist: HashSet<&String> = if *workspace_hitlist != WorkspaceHitlist::All {
243-
HashSet::from_iter(workspace_hitlist.get_some().unwrap())
244-
} else {
245-
HashSet::new() // Unused
246-
};
247-
let members: Vec<&Value> = json_obj
248-
.get("packages")
249-
.unwrap()
250-
.as_array()
251-
.unwrap()
252-
.into_iter()
253-
.filter(|member| if *workspace_hitlist == WorkspaceHitlist::All {
254-
true
255-
} else {
256-
let member_obj = member.as_object().unwrap();
257-
let member_name = member_obj.get("name").unwrap().as_str().unwrap();
258-
hitlist.take(&member_name.to_string()).is_some()
259-
})
260-
.collect();
261-
if !hitlist.is_empty() {
262-
// Mimick cargo of only outputting one <package> spec.
263-
return Err(std::io::Error::new(
264-
std::io::ErrorKind::InvalidInput,
265-
format!(
266-
"package `{}` is not a member of the workspace",
267-
hitlist.iter().next().unwrap()
268-
),
269-
));
327+
let cur_dir = env::current_dir()?;
328+
let mut targets: Vec<Target> = vec![];
329+
let packages = get_packages(&output.stdout)?;
330+
331+
// If we can find any local dependencies, we will try to get targets from those as well.
332+
for path in get_path_to_local_dependencies(&packages) {
333+
env::set_current_dir(path)?;
334+
targets.append(&mut get_targets(workspace_hitlist)?);
270335
}
271-
for member in members {
272-
let member_obj = member.as_object().unwrap();
273-
let jtargets = member_obj.get("targets").unwrap().as_array().unwrap();
274-
for jtarget in jtargets {
275-
targets.push(target_from_json(jtarget));
336+
337+
env::set_current_dir(cur_dir)?;
338+
match filter_packages_with_hitlist(packages, workspace_hitlist) {
339+
Ok(packages) => {
340+
for package in packages {
341+
if let Some(mut target) = extract_target_from_package(&package) {
342+
targets.append(&mut target);
343+
}
344+
}
345+
Ok(targets)
346+
}
347+
Err(package) => {
348+
// Mimick cargo of only outputting one <package> spec.
349+
Err(io::Error::new(
350+
io::ErrorKind::InvalidInput,
351+
format!("package `{}` is not a member of the workspace", package),
352+
))
276353
}
277354
}
278-
return Ok(targets);
279-
}
280-
Err(std::io::Error::new(
281-
std::io::ErrorKind::NotFound,
282-
str::from_utf8(&output.stderr).unwrap(),
283-
))
284-
}
285-
286-
fn target_from_json(jtarget: &Value) -> Target {
287-
let jtarget = jtarget.as_object().unwrap();
288-
let path = PathBuf::from(jtarget.get("src_path").unwrap().as_str().unwrap());
289-
let kinds = jtarget.get("kind").unwrap().as_array().unwrap();
290-
let kind = match kinds[0].as_str().unwrap() {
291-
"bin" => TargetKind::Bin,
292-
"lib" | "dylib" | "staticlib" | "cdylib" | "rlib" => TargetKind::Lib,
293-
"test" => TargetKind::Test,
294-
"example" => TargetKind::Example,
295-
"bench" => TargetKind::Bench,
296-
"custom-build" => TargetKind::CustomBuild,
297-
"proc-macro" => TargetKind::ProcMacro,
298-
_ => TargetKind::Other,
299-
};
300-
301-
Target {
302-
path: path,
303-
kind: kind,
355+
} else {
356+
Err(io::Error::new(
357+
io::ErrorKind::NotFound,
358+
str::from_utf8(&output.stderr).unwrap(),
359+
))
304360
}
305361
}
306362

307363
fn format_files(
308364
files: &[PathBuf],
309365
fmt_args: &[String],
310366
verbosity: Verbosity,
311-
) -> Result<ExitStatus, std::io::Error> {
367+
) -> Result<ExitStatus, io::Error> {
312368
let stdout = if verbosity == Verbosity::Quiet {
313369
std::process::Stdio::null()
314370
} else {
315371
std::process::Stdio::inherit()
316372
};
317373
if verbosity == Verbosity::Verbose {
318374
print!("rustfmt");
319-
for a in fmt_args.iter() {
375+
for a in fmt_args {
320376
print!(" {}", a);
321377
}
322-
for f in files.iter() {
378+
for f in files {
323379
print!(" {}", f.display());
324380
}
325381
println!("");
@@ -330,8 +386,8 @@ fn format_files(
330386
.args(fmt_args)
331387
.spawn()
332388
.map_err(|e| match e.kind() {
333-
std::io::ErrorKind::NotFound => std::io::Error::new(
334-
std::io::ErrorKind::Other,
389+
io::ErrorKind::NotFound => io::Error::new(
390+
io::ErrorKind::Other,
335391
"Could not run rustfmt, please make sure it is in your PATH.",
336392
),
337393
_ => e,

0 commit comments

Comments
 (0)