@@ -4,7 +4,8 @@ mod config;
4
4
mod dep_collector;
5
5
6
6
use crate :: dep_collector:: DepCollector ;
7
- use anyhow:: Context ;
7
+ use anyhow:: { bail, Context } ;
8
+ use error_chain:: ChainedError ;
8
9
use std:: {
9
10
path:: { Path , PathBuf } ,
10
11
process:: { Command , ExitCode } ,
@@ -13,8 +14,8 @@ use structopt::StructOpt;
13
14
14
15
#[ derive( StructOpt ) ]
15
16
struct Options {
16
- /// Spec files dir
17
- spec : PathBuf ,
17
+ /// Template files dir
18
+ tpls_dir : PathBuf ,
18
19
/// Out dir
19
20
out : PathBuf ,
20
21
/// Trace log,
@@ -41,14 +42,18 @@ struct Options {
41
42
print : Option < PathBuf > ,
42
43
}
43
44
44
- fn run_under_trace ( script_path : & Path , data_path : & Path ) -> anyhow:: Result < Vec < u8 > > {
45
+ fn run_under_trace (
46
+ script_path : & Path ,
47
+ data_path : & Path ,
48
+ detect_out : & DetectScriptOutput ,
49
+ ) -> anyhow:: Result < Vec < u8 > > {
45
50
let current_dir = tempfile:: TempDir :: new ( ) . context ( "failed to create temp dir" ) ?;
46
51
println ! ( "running in {}" , current_dir. path( ) . display( ) ) ;
47
52
let log_out_file = current_dir. path ( ) . join ( "__jjs_trace.json" ) ;
48
53
let data_path = data_path. canonicalize ( ) . context ( "data dir not exists" ) ?;
49
54
println ! ( "script will use data from {}" , data_path. display( ) ) ;
50
- let status = Command :: new ( "lxtrace" )
51
- . current_dir ( current_dir. path ( ) )
55
+ let mut cmd = Command :: new ( "lxtrace" ) ;
56
+ cmd . current_dir ( current_dir. path ( ) )
52
57
// machine-readable
53
58
. arg ( "--json" )
54
59
// redirect to file, so it will not mix with script output
@@ -58,35 +63,214 @@ fn run_under_trace(script_path: &Path, data_path: &Path) -> anyhow::Result<Vec<u
58
63
. arg ( "--" )
59
64
. arg ( "bash" )
60
65
. arg ( script_path. canonicalize ( ) . context ( "script not exists" ) ?)
61
- . env ( "DATA" , data_path)
62
- . status ( )
63
- . context ( "failed to start ktrace" ) ?;
66
+ . env ( "DATA" , data_path) ;
67
+ for ( k, v) in & detect_out. env {
68
+ cmd. env ( k, v) ;
69
+ }
70
+ let status = cmd. status ( ) . context ( "failed to start ktrace" ) ?;
64
71
if !status. success ( ) {
65
72
anyhow:: bail!( "ktrace returned error" ) ;
66
73
}
67
74
Ok ( std:: fs:: read ( & log_out_file) . context ( "failed to read trace log" ) ?)
68
75
}
69
76
70
- fn process_toolchain (
71
- dir : & Path ,
77
+ #[ derive( Clone ) ]
78
+ struct TemplateInfo {
79
+ dir : PathBuf ,
80
+ name : String ,
81
+ cfg : config:: ToolchainConfig ,
82
+ }
83
+ mod tpl_info_impls {
84
+ use super :: * ;
85
+ use std:: { cmp:: * , hash:: * } ;
86
+
87
+ impl Hash for TemplateInfo {
88
+ fn hash < H : Hasher > ( & self , hasher : & mut H ) {
89
+ self . name . hash ( hasher) ;
90
+ }
91
+ }
92
+
93
+ impl PartialEq for TemplateInfo {
94
+ fn eq ( & self , that : & TemplateInfo ) -> bool {
95
+ self . name == that. name
96
+ }
97
+ }
98
+
99
+ impl Eq for TemplateInfo { }
100
+ }
101
+
102
+ fn list_templates ( dir : & Path ) -> anyhow:: Result < Vec < TemplateInfo > > {
103
+ let content = std:: fs:: read_dir ( dir) . context ( "failed to read toolchain templates dir" ) ?;
104
+ let mut out = Vec :: new ( ) ;
105
+ for item in content {
106
+ let item = item. context ( "failed to stat toolchain template dir" ) ?;
107
+ let cfg = item. path ( ) . join ( "config.toml" ) ;
108
+ let cfg = std:: fs:: read_to_string ( cfg) . context ( "failed to open manifest" ) ?;
109
+ let cfg = toml:: from_str ( & cfg) . context ( "failed to parse manifest" ) ?;
110
+ let name = item
111
+ . path ( )
112
+ . file_name ( )
113
+ . unwrap ( )
114
+ . to_str ( )
115
+ . context ( "toolchain name is not utf8" ) ?
116
+ . to_string ( ) ;
117
+ out. push ( TemplateInfo {
118
+ dir : item. path ( ) ,
119
+ name,
120
+ cfg,
121
+ } ) ;
122
+ }
123
+ Ok ( out)
124
+ }
125
+
126
+ fn select_templates (
127
+ tpls : impl Iterator < Item = TemplateInfo > ,
128
+ opt : & Options ,
129
+ ) -> anyhow:: Result < impl Iterator < Item = TemplateInfo > > {
130
+ let filter: Box < dyn FnMut ( & TemplateInfo ) -> bool > = if !opt. only . is_empty ( ) {
131
+ Box :: new ( |tpl| opt. only . contains ( & tpl. name ) || tpl. cfg . auto )
132
+ } else if !opt. skip . is_empty ( ) {
133
+ Box :: new ( |tpl| !opt. skip . contains ( & tpl. name ) )
134
+ } else {
135
+ Box :: new ( |_tpl| true )
136
+ } ;
137
+ let tpls: Vec < _ > = tpls. collect ( ) ;
138
+ let roots: Vec < _ > = tpls. clone ( ) . into_iter ( ) . filter ( filter) . collect ( ) ;
139
+ let mut q = std:: collections:: HashSet :: new ( ) ;
140
+ let mut used = std:: collections:: HashSet :: new ( ) ;
141
+ q. extend ( roots. into_iter ( ) ) ;
142
+
143
+ while let Some ( head) = q. iter ( ) . next ( ) {
144
+ let tpl = head. clone ( ) ;
145
+ q. remove ( & tpl) ;
146
+ used. insert ( tpl. clone ( ) ) ;
147
+ for dep_name in & tpl. cfg . depends {
148
+ let dep = tpls
149
+ . iter ( )
150
+ . find ( |d| d. name . as_str ( ) == dep_name)
151
+ . context ( "dependency not found" ) ?
152
+ . clone ( ) ;
153
+ if !used. contains ( & dep) {
154
+ q. insert ( dep) ;
155
+ }
156
+ }
157
+ }
158
+ Ok ( used. into_iter ( ) )
159
+ }
160
+
161
+ struct DetectScriptOutput {
162
+ env : std:: collections:: HashMap < String , String > ,
163
+ }
164
+
165
+ impl std:: str:: FromStr for DetectScriptOutput {
166
+ type Err = anyhow:: Error ;
167
+
168
+ fn from_str ( s : & str ) -> anyhow:: Result < Self > {
169
+ let mut this = Self {
170
+ env : std:: collections:: HashMap :: new ( ) ,
171
+ } ;
172
+ for line in s. lines ( ) {
173
+ if line. starts_with ( "set-env:" ) {
174
+ let cmd = line. trim_start_matches ( "set-env:" ) ;
175
+ let parts: Vec < _ > = cmd. splitn ( 2 , '=' ) . collect ( ) ;
176
+ if parts. len ( ) != 2 {
177
+ bail ! ( "set-env command does not look like var_name=var_value" ) ;
178
+ }
179
+ this. env . insert ( parts[ 0 ] . to_string ( ) , parts[ 1 ] . to_string ( ) ) ;
180
+ } else {
181
+ bail ! ( "unknown command: {}" , line) ;
182
+ }
183
+ }
184
+ Ok ( this)
185
+ }
186
+ }
187
+
188
+ fn run_detect_script ( tpl : & TemplateInfo ) -> anyhow:: Result < Option < DetectScriptOutput > > {
189
+ let detect_script_path = tpl. dir . join ( "detect.sh" ) ;
190
+ if !detect_script_path. exists ( ) {
191
+ bail ! ( "detect.sh script missing" ) ;
192
+ }
193
+ let out_file_path = tempfile:: NamedTempFile :: new ( ) . context ( "failed to allocate temp file" ) ?;
194
+
195
+ let status = std:: process:: Command :: new ( "bash" )
196
+ . arg ( & detect_script_path)
197
+ . arg ( out_file_path. path ( ) )
198
+ . status ( )
199
+ . context ( "failed to execute detect.sh script" ) ?;
200
+ let script_out =
201
+ std:: fs:: read_to_string ( out_file_path. path ( ) ) . context ( "failed to read detect.sh output" ) ?;
202
+ println ! ( "--- script control output ---" ) ;
203
+ print ! ( "{}" , & script_out) ;
204
+ println ! ( "--- end script control output ---" ) ;
205
+ let script_out = script_out
206
+ . parse ( )
207
+ . context ( "failed to parse detect.sh output" ) ?;
208
+ let script_out = if status. success ( ) {
209
+ Some ( script_out)
210
+ } else {
211
+ None
212
+ } ;
213
+ Ok ( script_out)
214
+ }
215
+
216
+ fn process_toolchain_invoke_conf (
217
+ tpl : & TemplateInfo ,
218
+ out_dir : & Path ,
219
+ detect_out : & DetectScriptOutput ,
220
+ ) -> anyhow:: Result < ( ) > {
221
+ let out_file = out_dir
222
+ . join ( "etc/toolchains" )
223
+ . join ( format ! ( "{}.toml" , & tpl. name) ) ;
224
+ let in_file = tpl. dir . join ( "invoke-conf.toml" ) ;
225
+ if !in_file. exists ( ) {
226
+ return Ok ( ( ) ) ;
227
+ }
228
+ let in_file = std:: fs:: read_to_string ( & in_file) . context ( "failed to read invoke-conf.toml" ) ?;
229
+
230
+ let mut render_ctx = std:: collections:: HashMap :: new ( ) ;
231
+ for ( k, v) in & detect_out. env {
232
+ let k = format ! ( "env_{}" , k) ;
233
+ render_ctx. insert ( k, v. to_string ( ) ) ;
234
+ }
235
+ let output = tera:: Tera :: one_off ( & in_file, & render_ctx, false )
236
+ . map_err ( |tera_err| {
237
+ // unfortunately, tera uses error_chain for error handling, which does not have `Sync` bound on its errors
238
+ // to workaround it, we render error to string
239
+ anyhow:: anyhow!( "{:#}" , tera_err. display_chain( ) )
240
+ } )
241
+ . context ( "failed to render invoke config file" ) ?;
242
+
243
+ std:: fs:: create_dir_all ( out_file. parent ( ) . unwrap ( ) ) . ok ( ) ;
244
+
245
+ std:: fs:: write ( & out_file, & output) . context ( "failed to create config file" ) ?;
246
+
247
+ Ok ( ( ) )
248
+ }
249
+
250
+ fn process_toolchain_template (
251
+ tpl : TemplateInfo ,
72
252
collector : & mut DepCollector ,
73
253
mut event_log : Option < & mut dyn std:: io:: Write > ,
254
+ out_dir : & Path ,
74
255
) -> anyhow:: Result < ( ) > {
75
- let manifest_path = dir. join ( "config.toml" ) ;
76
- let manifest =
77
- std:: fs:: read_to_string ( manifest_path) . context ( "config.toml not found or not readable" ) ?;
78
- let _manifest: config:: Config = toml:: from_str ( & manifest) . context ( "failed to parse config" ) ?;
79
- // TODO: look at config
80
- let scripts: anyhow:: Result < Vec < _ > > = dir
81
- . join ( "scripts" )
256
+ let detect_out = match run_detect_script ( & tpl) ? {
257
+ Some ( dso) => dso,
258
+ None => {
259
+ println ! ( "Skipping toolchain {}: not available" , & tpl. name) ;
260
+ return Ok ( ( ) ) ;
261
+ }
262
+ } ;
263
+ let scripts: anyhow:: Result < Vec < _ > > = tpl
264
+ . dir
265
+ . join ( "use" )
82
266
. read_dir ( ) ?
83
267
. map ( |item| item. map_err ( |err| anyhow:: Error :: new ( err) . context ( "failed to read script" ) ) )
84
268
. collect ( ) ;
85
- let current_dir = dir. join ( "data" ) ;
269
+ let current_dir = tpl . dir . join ( "data" ) ;
86
270
for script in scripts? {
87
271
println ! ( "running {}" , script. path( ) . display( ) ) ;
88
- let out =
89
- run_under_trace ( & script . path ( ) , & current_dir ) . context ( "failed to collect trace" ) ?;
272
+ let out = run_under_trace ( & script . path ( ) , & current_dir , & detect_out )
273
+ . context ( "failed to collect trace" ) ?;
90
274
let scanner = serde_json:: Deserializer :: from_slice ( & out) . into_iter ( ) ;
91
275
let mut cnt = 0 ;
92
276
let mut cnt_items = 0 ;
@@ -124,6 +308,7 @@ fn process_toolchain(
124
308
cnt_items, cnt, cnt_errors
125
309
) ;
126
310
}
311
+ process_toolchain_invoke_conf ( & tpl, & out_dir, & detect_out) ?;
127
312
Ok ( ( ) )
128
313
}
129
314
@@ -138,32 +323,16 @@ fn main_inner() -> anyhow::Result<()> {
138
323
}
139
324
None => None ,
140
325
} ;
141
- for item in std:: fs:: read_dir ( & opt. spec ) . context ( "failed read spec dir" ) ? {
142
- let item = item. context ( "failed read spec dir" ) ?;
143
- let title = item
144
- . path ( )
145
- . file_name ( )
146
- . unwrap ( )
147
- . to_str ( )
148
- . context ( "toolchain name is not utf8" ) ?
149
- . to_string ( ) ;
326
+ let templates = list_templates ( & opt. tpls_dir ) ?;
327
+ let templates = select_templates ( templates. into_iter ( ) , & opt) ?;
328
+ for tpl in templates {
329
+ println ! ( "------ processing {} ------" , & tpl. name) ;
150
330
151
- let mut ok = true ;
152
- if !opt. only . is_empty ( ) {
153
- ok = opt. only . contains ( & title) ;
154
- } else if opt. skip . contains ( & title) {
155
- ok = false ;
156
- }
157
- if ok {
158
- println ! ( "processing {}" , & title) ;
159
- } else {
160
- println ! ( "skipping {}" , & title) ;
161
- continue ;
162
- }
163
- if let Err ( e) = process_toolchain (
164
- & item. path ( ) ,
331
+ if let Err ( e) = process_toolchain_template (
332
+ tpl,
165
333
& mut collector,
166
334
log_file. as_mut ( ) . map ( |x| x as _ ) ,
335
+ & opt. out ,
167
336
) {
168
337
util:: print_error ( & * e) ;
169
338
}
@@ -172,6 +341,7 @@ fn main_inner() -> anyhow::Result<()> {
172
341
"all toolchains processed: {} files found" ,
173
342
collector. count( )
174
343
) ;
344
+ let toolchain_files_output_dir = opt. out . join ( "opt" ) ;
175
345
let mut process_file: Box < dyn FnMut ( & str ) > = if let Some ( path) = & opt. print {
176
346
let out_file = std:: fs:: File :: create ( & path) . context ( "failed to open output file" ) ?;
177
347
let mut out_file = std:: io:: BufWriter :: new ( out_file) ;
@@ -185,7 +355,9 @@ fn main_inner() -> anyhow::Result<()> {
185
355
if file. is_dir ( ) && !opt. copy_dirs {
186
356
return ;
187
357
}
188
- if let Err ( e) = copy_ln:: copy ( & opt. out , file, true , !opt. copy_symlinks ) {
358
+ if let Err ( e) =
359
+ copy_ln:: copy ( & toolchain_files_output_dir, file, true , !opt. copy_symlinks )
360
+ {
189
361
eprintln ! ( "{:?}" , e) ;
190
362
}
191
363
} )
0 commit comments