@@ -5,23 +5,20 @@ use std::{
5
5
io:: BufReader ,
6
6
path:: { Path , PathBuf } ,
7
7
process:: { Command , Stdio } ,
8
+ sync:: Arc ,
8
9
} ;
9
10
10
11
use anyhow:: Result ;
11
- use cargo_metadata:: { BuildScript , Message , Package , PackageId } ;
12
+ use cargo_metadata:: { BuildScript , Message } ;
12
13
use itertools:: Itertools ;
13
14
use paths:: { AbsPath , AbsPathBuf } ;
14
15
use rustc_hash:: FxHashMap ;
15
16
use stdx:: JodChild ;
16
17
17
18
use crate :: { cfg_flag:: CfgFlag , CargoConfig } ;
18
19
19
- #[ derive( Debug , Clone , Default ) ]
20
- pub ( crate ) struct BuildDataMap {
21
- data : FxHashMap < PackageId , BuildData > ,
22
- }
23
20
#[ derive( Debug , Clone , Default , PartialEq , Eq ) ]
24
- pub struct BuildData {
21
+ pub ( crate ) struct BuildData {
25
22
/// List of config flags defined by this package's build script
26
23
pub cfgs : Vec < CfgFlag > ,
27
24
/// List of cargo-related environment variables with their value
@@ -35,131 +32,171 @@ pub struct BuildData {
35
32
pub proc_macro_dylib_path : Option < AbsPathBuf > ,
36
33
}
37
34
38
- impl BuildDataMap {
39
- pub ( crate ) fn new (
40
- cargo_toml : & AbsPath ,
41
- cargo_features : & CargoConfig ,
42
- packages : & Vec < Package > ,
43
- progress : & dyn Fn ( String ) ,
44
- ) -> Result < BuildDataMap > {
45
- let mut cmd = Command :: new ( toolchain:: cargo ( ) ) ;
46
- cmd. args ( & [ "check" , "--workspace" , "--message-format=json" , "--manifest-path" ] )
47
- . arg ( cargo_toml. as_ref ( ) ) ;
48
-
49
- // --all-targets includes tests, benches and examples in addition to the
50
- // default lib and bins. This is an independent concept from the --targets
51
- // flag below.
52
- cmd. arg ( "--all-targets" ) ;
53
-
54
- if let Some ( target) = & cargo_features. target {
55
- cmd. args ( & [ "--target" , target] ) ;
56
- }
35
+ #[ derive( Clone , Debug ) ]
36
+ pub ( crate ) struct BuildDataConfig {
37
+ pub ( crate ) workspace_root : AbsPathBuf ,
38
+ pub ( crate ) cargo_toml : AbsPathBuf ,
39
+ pub ( crate ) cargo_features : CargoConfig ,
40
+ pub ( crate ) packages : Arc < Vec < cargo_metadata:: Package > > ,
41
+ }
57
42
58
- if cargo_features. all_features {
59
- cmd. arg ( "--all-features" ) ;
60
- } else {
61
- if cargo_features. no_default_features {
62
- // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
63
- // https://github.com/oli-obk/cargo_metadata/issues/79
64
- cmd. arg ( "--no-default-features" ) ;
65
- }
66
- if !cargo_features. features . is_empty ( ) {
67
- cmd. arg ( "--features" ) ;
68
- cmd. arg ( cargo_features. features . join ( " " ) ) ;
69
- }
70
- }
43
+ impl PartialEq for BuildDataConfig {
44
+ fn eq ( & self , other : & Self ) -> bool {
45
+ Arc :: ptr_eq ( & self . packages , & other. packages )
46
+ }
47
+ }
71
48
72
- cmd. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: null ( ) ) . stdin ( Stdio :: null ( ) ) ;
73
-
74
- let mut child = cmd. spawn ( ) . map ( JodChild ) ?;
75
- let child_stdout = child. stdout . take ( ) . unwrap ( ) ;
76
- let stdout = BufReader :: new ( child_stdout) ;
77
-
78
- let mut res = BuildDataMap :: default ( ) ;
79
- for message in cargo_metadata:: Message :: parse_stream ( stdout) {
80
- if let Ok ( message) = message {
81
- match message {
82
- Message :: BuildScriptExecuted ( BuildScript {
83
- package_id,
84
- out_dir,
85
- cfgs,
86
- env,
87
- ..
88
- } ) => {
89
- let cfgs = {
90
- let mut acc = Vec :: new ( ) ;
91
- for cfg in cfgs {
92
- match cfg. parse :: < CfgFlag > ( ) {
93
- Ok ( it) => acc. push ( it) ,
94
- Err ( err) => {
95
- anyhow:: bail!( "invalid cfg from cargo-metadata: {}" , err)
96
- }
97
- } ;
98
- }
99
- acc
100
- } ;
101
- let res = res. data . entry ( package_id. clone ( ) ) . or_default ( ) ;
102
- // cargo_metadata crate returns default (empty) path for
103
- // older cargos, which is not absolute, so work around that.
104
- if out_dir != PathBuf :: default ( ) {
105
- let out_dir = AbsPathBuf :: assert ( out_dir) ;
106
- res. out_dir = Some ( out_dir) ;
107
- res. cfgs = cfgs;
108
- }
49
+ impl Eq for BuildDataConfig { }
109
50
110
- res. envs = env;
111
- }
112
- Message :: CompilerArtifact ( message) => {
113
- progress ( format ! ( "metadata {}" , message. target. name) ) ;
114
-
115
- if message. target . kind . contains ( & "proc-macro" . to_string ( ) ) {
116
- let package_id = message. package_id ;
117
- // Skip rmeta file
118
- if let Some ( filename) =
119
- message. filenames . iter ( ) . find ( |name| is_dylib ( name) )
120
- {
121
- let filename = AbsPathBuf :: assert ( filename. clone ( ) ) ;
122
- let res = res. data . entry ( package_id. clone ( ) ) . or_default ( ) ;
123
- res. proc_macro_dylib_path = Some ( filename) ;
124
- }
125
- }
126
- }
127
- Message :: CompilerMessage ( message) => {
128
- progress ( message. target . name . clone ( ) ) ;
129
- }
130
- Message :: Unknown => ( ) ,
131
- Message :: BuildFinished ( _) => { }
132
- Message :: TextLine ( _) => { }
133
- }
134
- }
51
+ #[ derive( Debug , Default ) ]
52
+ pub struct BuildDataCollector {
53
+ configs : FxHashMap < AbsPathBuf , BuildDataConfig > ,
54
+ }
55
+
56
+ #[ derive( Debug , Default ) ]
57
+ pub struct BuildDataResult {
58
+ data : FxHashMap < AbsPathBuf , BuildDataMap > ,
59
+ }
60
+
61
+ pub ( crate ) type BuildDataMap = FxHashMap < String , BuildData > ;
62
+
63
+ impl BuildDataCollector {
64
+ pub ( crate ) fn add_config ( & mut self , config : BuildDataConfig ) {
65
+ self . configs . insert ( config. workspace_root . to_path_buf ( ) . clone ( ) , config) ;
66
+ }
67
+
68
+ pub fn collect ( & mut self , progress : & dyn Fn ( String ) ) -> Result < BuildDataResult > {
69
+ let mut res = BuildDataResult :: default ( ) ;
70
+ for ( path, config) in self . configs . iter ( ) {
71
+ res. data . insert (
72
+ path. clone ( ) ,
73
+ collect_from_workspace (
74
+ & config. cargo_toml ,
75
+ & config. cargo_features ,
76
+ & config. packages ,
77
+ progress,
78
+ ) ?,
79
+ ) ;
135
80
}
136
- res. inject_cargo_env ( packages) ;
137
81
Ok ( res)
138
82
}
83
+ }
84
+
85
+ impl BuildDataResult {
86
+ pub ( crate ) fn get ( & self , root : & AbsPath ) -> Option < & BuildDataMap > {
87
+ self . data . get ( & root. to_path_buf ( ) )
88
+ }
89
+ }
139
90
140
- pub ( crate ) fn with_cargo_env ( packages : & Vec < Package > ) -> Self {
141
- let mut res = Self :: default ( ) ;
142
- res. inject_cargo_env ( packages) ;
143
- res
91
+ fn collect_from_workspace (
92
+ cargo_toml : & AbsPath ,
93
+ cargo_features : & CargoConfig ,
94
+ packages : & Vec < cargo_metadata:: Package > ,
95
+ progress : & dyn Fn ( String ) ,
96
+ ) -> Result < BuildDataMap > {
97
+ let mut cmd = Command :: new ( toolchain:: cargo ( ) ) ;
98
+ cmd. args ( & [ "check" , "--workspace" , "--message-format=json" , "--manifest-path" ] )
99
+ . arg ( cargo_toml. as_ref ( ) ) ;
100
+
101
+ // --all-targets includes tests, benches and examples in addition to the
102
+ // default lib and bins. This is an independent concept from the --targets
103
+ // flag below.
104
+ cmd. arg ( "--all-targets" ) ;
105
+
106
+ if let Some ( target) = & cargo_features. target {
107
+ cmd. args ( & [ "--target" , target] ) ;
144
108
}
145
109
146
- pub ( crate ) fn get ( & self , id : & PackageId ) -> Option < & BuildData > {
147
- self . data . get ( id)
110
+ if cargo_features. all_features {
111
+ cmd. arg ( "--all-features" ) ;
112
+ } else {
113
+ if cargo_features. no_default_features {
114
+ // FIXME: `NoDefaultFeatures` is mutual exclusive with `SomeFeatures`
115
+ // https://github.com/oli-obk/cargo_metadata/issues/79
116
+ cmd. arg ( "--no-default-features" ) ;
117
+ }
118
+ if !cargo_features. features . is_empty ( ) {
119
+ cmd. arg ( "--features" ) ;
120
+ cmd. arg ( cargo_features. features . join ( " " ) ) ;
121
+ }
148
122
}
149
123
150
- fn inject_cargo_env ( & mut self , packages : & Vec < Package > ) {
151
- for meta_pkg in packages {
152
- let resource = self . data . entry ( meta_pkg. id . clone ( ) ) . or_default ( ) ;
153
- inject_cargo_env ( meta_pkg, & mut resource. envs ) ;
124
+ cmd. stdout ( Stdio :: piped ( ) ) . stderr ( Stdio :: null ( ) ) . stdin ( Stdio :: null ( ) ) ;
125
+
126
+ let mut child = cmd. spawn ( ) . map ( JodChild ) ?;
127
+ let child_stdout = child. stdout . take ( ) . unwrap ( ) ;
128
+ let stdout = BufReader :: new ( child_stdout) ;
129
+
130
+ let mut res = BuildDataMap :: default ( ) ;
131
+ for message in cargo_metadata:: Message :: parse_stream ( stdout) {
132
+ if let Ok ( message) = message {
133
+ match message {
134
+ Message :: BuildScriptExecuted ( BuildScript {
135
+ package_id,
136
+ out_dir,
137
+ cfgs,
138
+ env,
139
+ ..
140
+ } ) => {
141
+ let cfgs = {
142
+ let mut acc = Vec :: new ( ) ;
143
+ for cfg in cfgs {
144
+ match cfg. parse :: < CfgFlag > ( ) {
145
+ Ok ( it) => acc. push ( it) ,
146
+ Err ( err) => {
147
+ anyhow:: bail!( "invalid cfg from cargo-metadata: {}" , err)
148
+ }
149
+ } ;
150
+ }
151
+ acc
152
+ } ;
153
+ let res = res. entry ( package_id. repr . clone ( ) ) . or_default ( ) ;
154
+ // cargo_metadata crate returns default (empty) path for
155
+ // older cargos, which is not absolute, so work around that.
156
+ if out_dir != PathBuf :: default ( ) {
157
+ let out_dir = AbsPathBuf :: assert ( out_dir) ;
158
+ res. out_dir = Some ( out_dir) ;
159
+ res. cfgs = cfgs;
160
+ }
154
161
155
- if let Some ( out_dir) = & resource. out_dir {
156
- // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
157
- if let Some ( out_dir) = out_dir. to_str ( ) . map ( |s| s. to_owned ( ) ) {
158
- resource. envs . push ( ( "OUT_DIR" . to_string ( ) , out_dir) ) ;
162
+ res. envs = env;
159
163
}
164
+ Message :: CompilerArtifact ( message) => {
165
+ progress ( format ! ( "metadata {}" , message. target. name) ) ;
166
+
167
+ if message. target . kind . contains ( & "proc-macro" . to_string ( ) ) {
168
+ let package_id = message. package_id ;
169
+ // Skip rmeta file
170
+ if let Some ( filename) = message. filenames . iter ( ) . find ( |name| is_dylib ( name) )
171
+ {
172
+ let filename = AbsPathBuf :: assert ( filename. clone ( ) ) ;
173
+ let res = res. entry ( package_id. repr . clone ( ) ) . or_default ( ) ;
174
+ res. proc_macro_dylib_path = Some ( filename) ;
175
+ }
176
+ }
177
+ }
178
+ Message :: CompilerMessage ( message) => {
179
+ progress ( message. target . name . clone ( ) ) ;
180
+ }
181
+ Message :: Unknown => ( ) ,
182
+ Message :: BuildFinished ( _) => { }
183
+ Message :: TextLine ( _) => { }
184
+ }
185
+ }
186
+ }
187
+
188
+ for package in packages {
189
+ let build_data = res. entry ( package. id . repr . clone ( ) ) . or_default ( ) ;
190
+ inject_cargo_env ( package, build_data) ;
191
+ if let Some ( out_dir) = & build_data. out_dir {
192
+ // NOTE: cargo and rustc seem to hide non-UTF-8 strings from env! and option_env!()
193
+ if let Some ( out_dir) = out_dir. to_str ( ) . map ( |s| s. to_owned ( ) ) {
194
+ build_data. envs . push ( ( "OUT_DIR" . to_string ( ) , out_dir) ) ;
160
195
}
161
196
}
162
197
}
198
+
199
+ Ok ( res)
163
200
}
164
201
165
202
// FIXME: File a better way to know if it is a dylib
@@ -173,7 +210,9 @@ fn is_dylib(path: &Path) -> bool {
173
210
/// Recreates the compile-time environment variables that Cargo sets.
174
211
///
175
212
/// Should be synced with <https://doc.rust-lang.org/cargo/reference/environment-variables.html#environment-variables-cargo-sets-for-crates>
176
- fn inject_cargo_env ( package : & cargo_metadata:: Package , env : & mut Vec < ( String , String ) > ) {
213
+ fn inject_cargo_env ( package : & cargo_metadata:: Package , build_data : & mut BuildData ) {
214
+ let env = & mut build_data. envs ;
215
+
177
216
// FIXME: Missing variables:
178
217
// CARGO_PKG_HOMEPAGE, CARGO_CRATE_NAME, CARGO_BIN_NAME, CARGO_BIN_EXE_<name>
179
218
0 commit comments