@@ -8,15 +8,16 @@ use std::time::{Duration, Instant};
88
99use anyhow:: { anyhow, ensure, Context , Result } ;
1010use camino:: { Utf8Path , Utf8PathBuf } ;
11+ use cargo_metadata:: Metadata ;
1112use itertools:: Itertools ;
1213use serde_json:: Value ;
1314use tracing:: debug_span;
1415#[ allow( unused_imports) ]
1516use tracing:: { debug, error, info, span, trace, warn, Level } ;
1617
1718use crate :: outcome:: PhaseResult ;
19+ use crate :: package:: Package ;
1820use crate :: process:: { get_command_output, Process } ;
19- use crate :: source:: Package ;
2021use crate :: * ;
2122
2223/// Run cargo build, check, or test.
@@ -50,105 +51,20 @@ pub fn run_cargo(
5051 } )
5152}
5253
53- /// Return the path of the workspace directory enclosing a given directory.
54- pub fn find_workspace ( path : & Utf8Path ) -> Result < Utf8PathBuf > {
55- ensure ! ( path. is_dir( ) , "{path:?} is not a directory" ) ;
56- let cargo_bin = cargo_bin ( ) ; // needed for lifetime
57- let argv: Vec < & str > = vec ! [ & cargo_bin, "locate-project" , "--workspace" ] ;
58- let stdout = get_command_output ( & argv, path)
59- . with_context ( || format ! ( "run cargo locate-project in {path:?}" ) ) ?;
60- let val: Value = serde_json:: from_str ( & stdout) . context ( "parse cargo locate-project output" ) ?;
61- let cargo_toml_path: Utf8PathBuf = val[ "root" ]
62- . as_str ( )
63- . with_context ( || format ! ( "cargo locate-project output has no root: {stdout:?}" ) ) ?
64- . to_owned ( )
65- . into ( ) ;
66- debug ! ( ?cargo_toml_path, "Found workspace root manifest" ) ;
67- ensure ! (
68- cargo_toml_path. is_file( ) ,
69- "cargo locate-project root {cargo_toml_path:?} is not a file"
70- ) ;
71- let root = cargo_toml_path
72- . parent ( )
73- . ok_or_else ( || anyhow ! ( "cargo locate-project root {cargo_toml_path:?} has no parent" ) ) ?
74- . to_owned ( ) ;
75- ensure ! (
76- root. is_dir( ) ,
77- "apparent project root directory {root:?} is not a directory"
78- ) ;
79- Ok ( root)
80- }
81-
82- /// Find the root files for each relevant package in the source tree.
83- ///
84- /// A source tree might include multiple packages (e.g. in a Cargo workspace),
85- /// and each package might have multiple targets (e.g. a bin and lib). Test targets
86- /// are excluded here: we run them, but we don't mutate them.
87- ///
88- /// Each target has one root file, typically but not necessarily called `src/lib.rs`
89- /// or `src/main.rs`. This function returns a list of all those files.
90- ///
91- /// After this, there is one more level of discovery, by walking those root files
92- /// to find `mod` statements, and then recursively walking those files to find
93- /// all source files.
94- ///
95- /// Packages are only included if their name is in `include_packages`.
96- pub fn top_source_files (
97- workspace_dir : & Utf8Path ,
98- include_packages : & [ String ] ,
99- ) -> Result < Vec < Arc < SourceFile > > > {
54+ pub fn run_cargo_metadata ( workspace_dir : & Utf8Path ) -> Result < Metadata > {
10055 let cargo_toml_path = workspace_dir. join ( "Cargo.toml" ) ;
101- debug ! ( ?cargo_toml_path, ?workspace_dir, "Find root files " ) ;
56+ debug ! ( ?cargo_toml_path, ?workspace_dir, "run cargo metadata " ) ;
10257 check_interrupted ( ) ?;
10358 let metadata = cargo_metadata:: MetadataCommand :: new ( )
10459 . manifest_path ( & cargo_toml_path)
10560 . exec ( )
10661 . context ( "run cargo metadata" ) ?;
107-
108- let mut r = Vec :: new ( ) ;
109- // cargo-metadata output is not obviously ordered so make it deterministic.
110- for package_metadata in metadata
111- . workspace_packages ( )
112- . iter ( )
113- . filter ( |p| include_packages. is_empty ( ) || include_packages. contains ( & p. name ) )
114- . sorted_by_key ( |p| & p. name )
115- {
116- check_interrupted ( ) ?;
117- let _span = debug_span ! ( "package" , name = %package_metadata. name) . entered ( ) ;
118- let manifest_path = & package_metadata. manifest_path ;
119- debug ! ( %manifest_path, "walk package" ) ;
120- let relative_manifest_path = manifest_path
121- . strip_prefix ( workspace_dir)
122- . map_err ( |_| {
123- anyhow ! (
124- "manifest path {manifest_path:?} for package {name:?} is not within the detected source root path {workspace_dir:?}" ,
125- name = package_metadata. name
126- )
127- } ) ?
128- . to_owned ( ) ;
129- let package = Arc :: new ( Package {
130- name : package_metadata. name . clone ( ) ,
131- relative_manifest_path,
132- } ) ;
133- for source_path in direct_package_sources ( workspace_dir, package_metadata) ? {
134- check_interrupted ( ) ?;
135- r. push ( Arc :: new ( SourceFile :: new (
136- workspace_dir,
137- source_path,
138- & package,
139- ) ?) ) ;
140- }
141- }
142- for p in include_packages {
143- if !r. iter ( ) . any ( |sf| sf. package . name == * p) {
144- warn ! ( "package {p} not found in source tree" ) ;
145- }
146- }
147- Ok ( r)
62+ check_interrupted ( ) ?;
63+ Ok ( metadata)
14864}
14965
15066/// Return the name of the cargo binary.
151- fn cargo_bin ( ) -> String {
67+ pub fn cargo_bin ( ) -> String {
15268 // When run as a Cargo subcommand, which is the usual/intended case,
15369 // $CARGO tells us the right way to call back into it, so that we get
15470 // the matching toolchain etc.
@@ -222,46 +138,6 @@ fn rustflags() -> String {
222138 rustflags. join ( "\x1f " )
223139}
224140
225- /// Find all the files that are named in the `path` of targets in a Cargo manifest that should be tested.
226- ///
227- /// These are the starting points for discovering source files.
228- fn direct_package_sources (
229- workspace_root : & Utf8Path ,
230- package_metadata : & cargo_metadata:: Package ,
231- ) -> Result < Vec < Utf8PathBuf > > {
232- let mut found = Vec :: new ( ) ;
233- let pkg_dir = package_metadata. manifest_path . parent ( ) . unwrap ( ) ;
234- for target in & package_metadata. targets {
235- if should_mutate_target ( target) {
236- if let Ok ( relpath) = target
237- . src_path
238- . strip_prefix ( workspace_root)
239- . map ( ToOwned :: to_owned)
240- {
241- debug ! (
242- "found mutation target {} of kind {:?}" ,
243- relpath, target. kind
244- ) ;
245- found. push ( relpath) ;
246- } else {
247- warn ! ( "{:?} is not in {:?}" , target. src_path, pkg_dir) ;
248- }
249- } else {
250- debug ! (
251- "skipping target {:?} of kinds {:?}" ,
252- target. name, target. kind
253- ) ;
254- }
255- }
256- found. sort ( ) ;
257- found. dedup ( ) ;
258- Ok ( found)
259- }
260-
261- fn should_mutate_target ( target : & cargo_metadata:: Target ) -> bool {
262- target. kind . iter ( ) . any ( |k| k. ends_with ( "lib" ) || k == "bin" )
263- }
264-
265141#[ cfg( test) ]
266142mod test {
267143 use std:: ffi:: OsStr ;
@@ -364,88 +240,4 @@ mod test {
364240 ]
365241 ) ;
366242 }
367-
368- #[ test]
369- fn error_opening_outside_of_crate ( ) {
370- cargo:: find_workspace ( Utf8Path :: new ( "/" ) ) . unwrap_err ( ) ;
371- }
372-
373- #[ test]
374- fn open_subdirectory_of_crate_opens_the_crate ( ) {
375- let root = cargo:: find_workspace ( Utf8Path :: new ( "testdata/tree/factorial/src" ) )
376- . expect ( "open source tree from subdirectory" ) ;
377- assert ! ( root. is_dir( ) ) ;
378- assert ! ( root. join( "Cargo.toml" ) . is_file( ) ) ;
379- assert ! ( root. join( "src/bin/factorial.rs" ) . is_file( ) ) ;
380- assert_eq ! ( root. file_name( ) . unwrap( ) , OsStr :: new( "factorial" ) ) ;
381- }
382-
383- #[ test]
384- fn find_root_from_subdirectory_of_workspace_finds_the_workspace_root ( ) {
385- let root = cargo:: find_workspace ( Utf8Path :: new ( "testdata/tree/workspace/main" ) )
386- . expect ( "Find root from within workspace/main" ) ;
387- assert_eq ! ( root. file_name( ) , Some ( "workspace" ) , "Wrong root: {root:?}" ) ;
388- }
389-
390- #[ test]
391- fn find_top_source_files_from_subdirectory_of_workspace ( ) {
392- let root_dir = cargo:: find_workspace ( Utf8Path :: new ( "testdata/tree/workspace/main" ) )
393- . expect ( "Find workspace root" ) ;
394- let top_source_files = top_source_files ( & root_dir, & [ ] ) . expect ( "Find root files" ) ;
395- println ! ( "{top_source_files:#?}" ) ;
396- let paths = top_source_files
397- . iter ( )
398- . map ( |sf| sf. tree_relative_path . to_slash_path ( ) )
399- . collect_vec ( ) ;
400- // The order here might look strange, but they're actually deterministically
401- // sorted by the package name, not the path name.
402- assert_eq ! (
403- paths,
404- [ "utils/src/lib.rs" , "main/src/main.rs" , "main2/src/main.rs" ]
405- ) ;
406- }
407-
408- #[ test]
409- fn filter_by_single_package ( ) {
410- let root_dir = cargo:: find_workspace ( Utf8Path :: new ( "testdata/tree/workspace/main" ) )
411- . expect ( "Find workspace root" ) ;
412- assert_eq ! (
413- root_dir. file_name( ) ,
414- Some ( "workspace" ) ,
415- "found the workspace root"
416- ) ;
417- let top_source_files =
418- top_source_files ( & root_dir, & [ "main" . to_owned ( ) ] ) . expect ( "Find root files" ) ;
419- println ! ( "{top_source_files:#?}" ) ;
420- assert_eq ! ( top_source_files. len( ) , 1 ) ;
421- assert_eq ! (
422- top_source_files
423- . iter( )
424- . map( |sf| sf. tree_relative_path. clone( ) )
425- . collect_vec( ) ,
426- [ "main/src/main.rs" ]
427- ) ;
428- }
429-
430- #[ test]
431- fn filter_by_multiple_packages ( ) {
432- let root_dir = cargo:: find_workspace ( Utf8Path :: new ( "testdata/tree/workspace/main" ) )
433- . expect ( "Find workspace root" ) ;
434- assert_eq ! (
435- root_dir. file_name( ) ,
436- Some ( "workspace" ) ,
437- "found the workspace root"
438- ) ;
439- let top_source_files =
440- top_source_files ( & root_dir, & [ "main" . to_owned ( ) , "main2" . to_owned ( ) ] )
441- . expect ( "Find root files" ) ;
442- println ! ( "{top_source_files:#?}" ) ;
443- assert_eq ! (
444- top_source_files
445- . iter( )
446- . map( |sf| sf. tree_relative_path. clone( ) )
447- . collect_vec( ) ,
448- [ "main/src/main.rs" , "main2/src/main.rs" ]
449- ) ;
450- }
451243}
0 commit comments