1 use std::collections::HashMap;
2 use std::env::{self, Args};
3 use std::fs::{create_dir_all, File};
4 use std::io::{Read, Write};
5 use std::path::{Path, PathBuf};
6 use std::process::{self, exit, Command};
7 use std::str::FromStr;
8 
9 static USAGE_STRING: &'static str = r#"Tools for jsparagus + SmooshMonkey development
10 
11 USAGE:
12     cargo run --bin smoosh_tools [COMMAND] [OPTIONS]
13 
14 COMMAND:
15     build [--opt] [MOZILLA_CENTRAL]
16         Build SpiderMonkey JS shell with SmooshMonkey enabled, using this
17         jsparagus clone instead of vendored one
18     shell [--opt] [MOZILLA_CENTRAL]
19         Run SpiderMonkey JS shell binary built by "build" command
20     test [--opt] [MOZILLA_CENTRAL]
21         Run jstests/jit-test with SpiderMonkey JS shell binary built by
22         "build" command
23     bench [--opt] [--samples-dir=REAL_JS_SAMPLES/DATE] [MOZILLA_CENTRAL]
24         Compare SpiderMonkey parser performance against SmooshMonkey on a
25         collection of JavaScript files, using the JS shell binary built by
26         "build" command.
27     bump [MOZILLA_CENTRAL]
28         Bump jsparagus version referred by mozilla-central to the latest
29         "ci_generated" branch HEAD, and re-vendor jsparagus
30     try [--remote=REMOTE] [MOZILLA_CENTRAL]
31         Push to try with current jsparagus branch
32         This pushes current jsparagus branch to "generated" branch, and
33         modifies the reference in mozilla-central to it, and pushes to try
34         This requires L1 Commit Access for hg.mozilla.org,
35         and mozilla-central should be a Git repository
36     gen [--remote=REMOTE]
37         Push current jsparagus branch to "generated" branch, with generated
38         files included, to refer from mozilla-central
39 
40 OPTIONS:
41     MOZILLA_CENTRAL Path to mozilla-central or mozilla-unified clone
42                     This can be omitted if mozilla-central or mozilla-unified
43                     is placed next to jsparagus clone directory
44     --opt           Use optimized build configuration, instead of debug build
45     --remote=REMOTE The name of remote to push the generated branch to
46                     Defaults to "origin"
47     --concat-mozconfig For building mozilla-central, concatenates the content
48                     of the MOZCONFIG environment variable with the content of
49                     smoosh_tools mozconfig.
50     --samples-dir=DIR Directory containing thousands of JavaScripts to be used
51                     for measuring the performance of SmooshMonkey.
52 "#;
53 
54 macro_rules! try_finally {
55     ({$($t: tt)*} {$($f: tt)*}) => {
56         let result = (|| -> Result<(), Error> {
57             $($t)*
58             Ok(())
59         })();
60         $($f)*
61         result?
62     }
63 }
64 
65 /// Simple wrapper for logging.
66 ///
67 /// Do not use env_logger etc, to avoid adding extra dependency to library.
68 /// See https://github.com/rust-lang/rfcs/pull/2887
69 macro_rules! log_info {
70     ($($t: tt)*) => {
71         print!("[INFO] ");
72         println!($($t)*);
73     }
74 }
75 
76 #[derive(Debug)]
77 enum Error {
78     Generic(String),
79     SubProcessError(String, Option<i32>),
80     IO(String, std::io::Error),
81     Encode(String, std::str::Utf8Error),
82     EnvVar(&'static str, std::env::VarError),
83 }
84 
85 impl Error {
dump(&self)86     fn dump(&self) {
87         match self {
88             Error::Generic(message) => {
89                 println!("{}", message);
90             }
91             Error::SubProcessError(message, code) => {
92                 println!("{}", message);
93                 match code {
94                     Some(code) => println!("Subprocess exit with exit status: {}", code),
95                     None => println!("Subprocess terminated by signal"),
96                 }
97             }
98             Error::IO(message, e) => {
99                 println!("{}", message);
100                 println!("{}", e);
101             }
102             Error::Encode(message, e) => {
103                 println!("{}", message);
104                 println!("{}", e);
105             }
106             Error::EnvVar(var, e) => {
107                 println!("Error while reading {}:", var);
108                 println!("{}", e);
109             }
110         }
111     }
112 }
113 
114 #[derive(Debug, Copy, Clone)]
115 enum CommandType {
116     Build,
117     Shell,
118     Test,
119     Bench,
120     Bump,
121     Gen,
122     Try,
123 }
124 
125 #[derive(Debug, Copy, Clone)]
126 enum BuildType {
127     Opt,
128     Debug,
129 }
130 
131 /// Parse command line arguments.
132 ///
133 /// Do not use `clap` here, to avoid adding extra dependency to library.
134 /// See https://github.com/rust-lang/rfcs/pull/2887
135 #[derive(Debug)]
136 struct SimpleArgs {
137     command: CommandType,
138     build_type: BuildType,
139     moz_path: String,
140     realjs_path: String,
141     remote: String,
142     concat_mozconfig: bool,
143 }
144 
145 impl SimpleArgs {
parse(mut args: Args) -> Self146     fn parse(mut args: Args) -> Self {
147         // Skip binary path.
148         let _ = args.next().unwrap();
149 
150         let command = match args.next() {
151             Some(command) => match command.as_str() {
152                 "build" => CommandType::Build,
153                 "test" => CommandType::Test,
154                 "shell" => CommandType::Shell,
155                 "bench" => CommandType::Bench,
156                 "bump" => CommandType::Bump,
157                 "gen" => CommandType::Gen,
158                 "try" => CommandType::Try,
159                 _ => Self::show_usage(),
160             },
161             None => Self::show_usage(),
162         };
163 
164         let mut plain_args = Vec::new();
165 
166         let mut remote = "origin".to_string();
167         let mut moz_path = Self::guess_moz();
168         let mut realjs_path = Self::guess_realjs();
169         let mut build_type = BuildType::Debug;
170         let mut concat_mozconfig = false;
171 
172         for arg in args {
173             if arg.starts_with("-") {
174                 if arg.contains("=") {
175                     let mut split = arg.split("=");
176                     let name = match split.next() {
177                         Some(s) => s,
178                         None => Self::show_usage(),
179                     };
180                     let value = match split.next() {
181                         Some(s) => s,
182                         None => Self::show_usage(),
183                     };
184 
185                     match name {
186                         "--remote" => {
187                             remote = value.to_string();
188                         }
189                         "--samples-dir" => {
190                             realjs_path = value.to_string();
191                         }
192                         _ => {
193                             Self::show_usage();
194                         }
195                     }
196                 } else {
197                     match arg.as_str() {
198                         "--opt" => {
199                             build_type = BuildType::Opt;
200                         }
201                         "--concat-mozconfig" => {
202                             concat_mozconfig = true;
203                         }
204                         _ => {
205                             Self::show_usage();
206                         }
207                     }
208                 }
209             } else {
210                 plain_args.push(arg);
211             }
212         }
213 
214         if !plain_args.is_empty() {
215             moz_path = plain_args.remove(0);
216         }
217 
218         if !plain_args.is_empty() {
219             Self::show_usage();
220         }
221 
222         SimpleArgs {
223             command,
224             build_type,
225             moz_path,
226             realjs_path,
227             remote,
228             concat_mozconfig,
229         }
230     }
231 
show_usage() -> !232     fn show_usage() -> ! {
233         print!("{}", USAGE_STRING);
234         process::exit(-1)
235     }
236 
guess_moz() -> String237     fn guess_moz() -> String {
238         let cwd = match env::current_dir() {
239             Ok(cwd) => cwd,
240             _ => return "../mozilla-central".to_string(),
241         };
242 
243         for path in vec!["../mozilla-central", "../mozilla-unified"] {
244             let topsrcdir = Path::new(&cwd).join(path);
245             if topsrcdir.exists() {
246                 return path.to_string();
247             }
248         }
249 
250         return "../mozilla-central".to_string();
251     }
252 
guess_realjs() -> String253     fn guess_realjs() -> String {
254         return "../real-js-samples/20190416".to_string();
255     }
256 }
257 
258 #[derive(Debug)]
259 struct MozillaTree {
260     topsrcdir: PathBuf,
261     smoosh_cargo: PathBuf,
262 }
263 
264 impl MozillaTree {
try_new(path: &String) -> Result<Self, Error>265     fn try_new(path: &String) -> Result<Self, Error> {
266         let rel_topsrcdir = Path::new(path);
267         let cwd = env::current_dir().unwrap();
268         let topsrcdir = Path::new(&cwd).join(rel_topsrcdir);
269         if !topsrcdir.exists() {
270             return Err(Error::Generic(format!(
271                 "{:?} doesn't exist. Please specify a path to mozilla-central\n
272 For more information, see https://github.com/mozilla-spidermonkey/jsparagus/wiki/SpiderMonkey",
273                 topsrcdir
274             )));
275         }
276         let topsrcdir = topsrcdir.canonicalize().unwrap();
277         let cargo = topsrcdir
278             .join("js")
279             .join("src")
280             .join("frontend")
281             .join("smoosh")
282             .join("Cargo.toml");
283         if !cargo.exists() {
284             return Err(Error::Generic(format!(
285                 "{:?} doesn't exist. Please specify a path to mozilla-central",
286                 cargo
287             )));
288         }
289 
290         Ok(Self {
291             topsrcdir: topsrcdir.to_path_buf(),
292             smoosh_cargo: cargo.to_path_buf(),
293         })
294     }
295 }
296 
297 #[derive(Debug)]
298 struct JsparagusTree {
299     topsrcdir: PathBuf,
300     mozconfigs: PathBuf,
301 }
302 
303 impl JsparagusTree {
try_new() -> Result<Self, Error>304     fn try_new() -> Result<Self, Error> {
305         let cwd = env::current_dir().unwrap();
306         let topsrcdir = Path::new(&cwd);
307         let cargo = topsrcdir.join("Cargo.toml");
308         if !cargo.exists() {
309             return Err(Error::Generic(format!(
310                 "{:?} doesn't exist. Please run smoosh_tools in jsparagus top level directory",
311                 cargo
312             )));
313         }
314 
315         let mozconfigs = topsrcdir.join("mozconfigs");
316         if !mozconfigs.exists() {
317             return Err(Error::Generic(format!(
318                 "{:?} doesn't exist. Please run smoosh_tools in jsparagus top level directory",
319                 mozconfigs
320             )));
321         }
322 
323         Ok(Self {
324             topsrcdir: topsrcdir.to_path_buf(),
325             mozconfigs: mozconfigs.to_path_buf(),
326         })
327     }
328 
mozconfig(&self, build_type: BuildType) -> PathBuf329     fn mozconfig(&self, build_type: BuildType) -> PathBuf {
330         self.mozconfigs.join(match build_type {
331             BuildType::Opt => "smoosh-opt",
332             BuildType::Debug => "smoosh-debug",
333         })
334     }
335 
compare_parsers_js(&self) -> PathBuf336     fn compare_parsers_js(&self) -> PathBuf {
337         self.topsrcdir
338             .join("benchmarks")
339             .join("compare-spidermonkey-parsers.js")
340     }
341 }
342 
343 struct ObjDir(String);
344 impl FromStr for ObjDir {
345     type Err = Error;
346 
from_str(s: &str) -> Result<Self, Self::Err>347     fn from_str(s: &str) -> Result<Self, Self::Err> {
348         let header = "mk_add_options";
349         let s = match s.starts_with(header) {
350             true => &s[header.len()..],
351             false => return Err(Error::Generic("unexpected start".into())),
352         };
353         if Some(0) != s.find(char::is_whitespace) {
354             return Err(Error::Generic(
355                 "expected whitespace after mk_add_options".into(),
356             ));
357         }
358         let s = s.trim_start();
359         let eq_idx = s.find('=').ok_or(Error::Generic(
360             "equal sign not found after mk_add_option".into(),
361         ))?;
362         let var_name = &s[..eq_idx];
363         if var_name != "MOZ_OBJDIR" {
364             return Err(Error::Generic(format!(
365                 "{}: unexpected variable, expected MOZ_OBJDIR",
366                 var_name
367             )));
368         }
369         let s = &s[(eq_idx + 1)..];
370         let s = s.trim();
371 
372         Ok(ObjDir(s.into()))
373     }
374 }
375 
376 #[derive(Debug)]
377 struct BuildTree {
378     moz: MozillaTree,
379     jsp: JsparagusTree,
380     mozconfig: PathBuf,
381 }
382 
383 impl BuildTree {
try_new(args: &SimpleArgs) -> Result<Self, Error>384     fn try_new(args: &SimpleArgs) -> Result<Self, Error> {
385         let moz = MozillaTree::try_new(&args.moz_path)?;
386         let jsp = JsparagusTree::try_new()?;
387 
388         let jsp_mozconfig = jsp.mozconfig(args.build_type);
389         let mozconfig = if args.concat_mozconfig {
390             // Create a MOZCONFIG file which concatenate the content of the
391             // environmenet variable with the content provided by jsparagus.
392             // This is useful to add additional compilation variants for
393             // mozilla-central.
394             let env = env::var("MOZCONFIG").map_err(|e| Error::EnvVar("MOZCONFIG", e))?;
395             let env_config = read_file(&env.into())?;
396             let jsp_config = read_file(&jsp_mozconfig)?;
397             let config = env_config + &jsp_config;
398 
399             // Extract the object directory, in which the mozconfig file would
400             // be created.
401             let mut objdir = None;
402             for line in config.lines() {
403                 match line.parse() {
404                     Ok(ObjDir(meta_path)) => objdir = Some(meta_path),
405                     Err(_error) => (),
406                 }
407             }
408             let objdir = objdir.ok_or(Error::Generic("MOZ_OBJDIR must exists".into()))?;
409             let topsrcdir = moz
410                 .topsrcdir
411                 .to_str()
412                 .ok_or(())
413                 .map_err(|_| Error::Generic("topsrcdir cannot be encoded in UTF-8.".into()))?;
414             let objdir = objdir.replace("@TOPSRCDIR@", topsrcdir);
415 
416             // Create the object direcotry.
417             let objdir: PathBuf = objdir.into();
418             if !objdir.is_dir() {
419                 create_dir_all(&objdir).map_err(|e| {
420                     Error::IO(format!("Failed to create directory {:?}", objdir), e)
421                 })?;
422             }
423 
424             // Create MOZCONFIG file.
425             let mozconfig = objdir.join("mozconfig");
426             write_file(&mozconfig, config)?;
427 
428             mozconfig
429         } else {
430             jsp_mozconfig
431         };
432 
433         Ok(Self {
434             moz,
435             jsp,
436             mozconfig,
437         })
438     }
439 }
440 
441 /// Run `command`, and check if the exit code is successful.
442 /// Returns Err if failed to run the command, or the exit code is non-zero.
check_command(command: &mut Command) -> Result<(), Error>443 fn check_command(command: &mut Command) -> Result<(), Error> {
444     log_info!("$ {:?}", command);
445     let status = command
446         .status()
447         .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?;
448     if !status.success() {
449         return Err(Error::SubProcessError(
450             format!("Failed to run {:?}", command),
451             status.code(),
452         ));
453     }
454 
455     Ok(())
456 }
457 
458 /// Run `command`, and returns its status code.
459 /// Returns Err if failed to run the command, or the subprocess is terminated
460 /// by signal.
get_retcode(command: &mut Command) -> Result<i32, Error>461 fn get_retcode(command: &mut Command) -> Result<i32, Error> {
462     log_info!("$ {:?}", command);
463     let status = command
464         .status()
465         .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?;
466     if !status.success() {
467         match status.code() {
468             Some(code) => return Ok(code),
469             None => {
470                 return Err(Error::SubProcessError(
471                     format!("Failed to run {:?}", command),
472                     None,
473                 ))
474             }
475         }
476     }
477 
478     Ok(0)
479 }
480 
481 /// Run `command`, and returns its stdout
482 /// Returns Err if failed to run the command.
get_output(command: &mut Command) -> Result<String, Error>483 fn get_output(command: &mut Command) -> Result<String, Error> {
484     log_info!("$ {:?}", command);
485     let output = command
486         .output()
487         .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?;
488     let stdout = std::str::from_utf8(output.stdout.as_slice())
489         .map_err(|e| Error::Encode(format!("Failed to decode the output of {:?}", command), e))?
490         .to_string();
491     Ok(stdout)
492 }
493 
494 struct GitRepository {
495     topsrcdir: PathBuf,
496 }
497 
498 impl GitRepository {
try_new(topsrcdir: PathBuf) -> Result<Self, Error>499     fn try_new(topsrcdir: PathBuf) -> Result<Self, Error> {
500         if !topsrcdir.join(".git").as_path().exists() {
501             return Err(Error::Generic(format!(
502                 "{:?} is not Git repository",
503                 topsrcdir
504             )));
505         }
506 
507         Ok(Self { topsrcdir })
508     }
509 
run(&self, args: &[&str]) -> Result<(), Error>510     fn run(&self, args: &[&str]) -> Result<(), Error> {
511         check_command(
512             Command::new("git")
513                 .args(args)
514                 .current_dir(self.topsrcdir.clone()),
515         )
516     }
517 
get_retcode(&self, args: &[&str]) -> Result<i32, Error>518     fn get_retcode(&self, args: &[&str]) -> Result<i32, Error> {
519         get_retcode(
520             Command::new("git")
521                 .args(args)
522                 .current_dir(self.topsrcdir.clone()),
523         )
524     }
525 
get_output(&self, args: &[&str]) -> Result<String, Error>526     fn get_output(&self, args: &[&str]) -> Result<String, Error> {
527         get_output(
528             Command::new("git")
529                 .args(args)
530                 .current_dir(self.topsrcdir.clone()),
531         )
532     }
533 
534     /// Checks if there's no uncommitted changes.
assert_clean(&self) -> Result<(), Error>535     fn assert_clean(&self) -> Result<(), Error> {
536         log_info!("Checking {} is clean", self.topsrcdir.to_str().unwrap());
537         let code = self.get_retcode(&["diff-index", "--quiet", "HEAD", "--"])?;
538         if code != 0 {
539             return Err(Error::Generic(format!(
540                 "Uncommitted changes found in {}",
541                 self.topsrcdir.to_str().unwrap()
542             )));
543         }
544 
545         let code = self.get_retcode(&["diff-index", "--cached", "--quiet", "HEAD", "--"])?;
546         if code != 0 {
547             return Err(Error::Generic(format!(
548                 "Uncommitted changes found in {}",
549                 self.topsrcdir.to_str().unwrap()
550             )));
551         }
552 
553         Ok(())
554     }
555 
556     /// Returns the current branch, or "HEAD" if it's detached head..
branch(&self) -> Result<String, Error>557     fn branch(&self) -> Result<String, Error> {
558         Ok(self
559             .get_output(&["rev-parse", "--abbrev-ref", "HEAD"])?
560             .trim()
561             .to_string())
562     }
563 
564     /// Ensure a remote with `name` exists.
565     /// If it doesn't exist, add remote with `name` and `url`.
ensure_remote(&self, name: &'static str, url: &'static str) -> Result<(), Error>566     fn ensure_remote(&self, name: &'static str, url: &'static str) -> Result<(), Error> {
567         for line in self.get_output(&["remote"])?.split("\n") {
568             if line == name {
569                 return Ok(());
570             }
571         }
572 
573         self.run(&["remote", "add", name, url])?;
574 
575         Ok(())
576     }
577 
578     /// Returns a map of remote branches.
ls_remote(&self, remote: &'static str) -> Result<HashMap<String, String>, Error>579     fn ls_remote(&self, remote: &'static str) -> Result<HashMap<String, String>, Error> {
580         let mut map = HashMap::new();
581         for line in self.get_output(&["ls-remote", remote])?.split("\n") {
582             let mut split = line.split("\t");
583             let sha = match split.next() {
584                 Some(s) => s,
585                 None => continue,
586             };
587             let ref_name = match split.next() {
588                 Some(s) => s,
589                 None => continue,
590             };
591             map.insert(ref_name.to_string(), sha.to_string());
592         }
593 
594         Ok(map)
595     }
596 }
597 
598 /// Trait for replacing dependencies in Cargo.toml.
599 trait DependencyLineReplacer {
600     /// Receives `line` for official jsparagus reference,
601     /// and adds modified jsparagus reference to `lines`.
on_official(&self, line: &str, lines: &mut Vec<String>)602     fn on_official(&self, line: &str, lines: &mut Vec<String>);
603 }
604 
605 /// Replace jsparagus reference to `sha` in official ci_generated branch.
606 struct OfficialDependencyLineReplacer {
607     sha: String,
608 }
609 
610 impl DependencyLineReplacer for OfficialDependencyLineReplacer {
on_official(&self, _line: &str, lines: &mut Vec<String>)611     fn on_official(&self, _line: &str, lines: &mut Vec<String>) {
612         let newline = format!("jsparagus = {{ git = \"https://github.com/mozilla-spidermonkey/jsparagus\", rev = \"{}\" }}", self.sha);
613         log_info!("Rewriting jsparagus reference: {}", newline);
614         lines.push(newline);
615     }
616 }
617 
618 /// Replace jsparagus reference to local clone.
619 struct LocalDependencyLineReplacer {
620     jsparagus: PathBuf,
621 }
622 
623 impl DependencyLineReplacer for LocalDependencyLineReplacer {
on_official(&self, line: &str, lines: &mut Vec<String>)624     fn on_official(&self, line: &str, lines: &mut Vec<String>) {
625         lines.push(format!("# {}", line));
626         let newline = format!(
627             "jsparagus = {{ path = \"{}\" }}",
628             self.jsparagus.to_str().unwrap()
629         );
630         log_info!("Rewriting jsparagus reference: {}", newline);
631         lines.push(newline);
632     }
633 }
634 
635 /// Replace jsparagus reference to a remote branch in forked repository.
636 struct ForkDependencyLineReplacer {
637     github_user: String,
638     branch: String,
639 }
640 
641 impl DependencyLineReplacer for ForkDependencyLineReplacer {
on_official(&self, line: &str, lines: &mut Vec<String>)642     fn on_official(&self, line: &str, lines: &mut Vec<String>) {
643         lines.push(format!("# {}", line));
644         let newline = format!(
645             "jsparagus = {{ git = \"https://github.com/{}/jsparagus\", branch = \"{}\" }}",
646             self.github_user, self.branch
647         );
648         log_info!("Rewriting jsparagus reference: {}", newline);
649         lines.push(newline);
650     }
651 }
652 
read_file(path: &PathBuf) -> Result<String, Error>653 fn read_file(path: &PathBuf) -> Result<String, Error> {
654     let mut file = File::open(path.as_path())
655         .map_err(|e| Error::IO(format!("Couldn't open {}", path.to_str().unwrap()), e))?;
656     let mut content = String::new();
657     file.read_to_string(&mut content)
658         .map_err(|e| Error::IO(format!("Couldn't read {}", path.to_str().unwrap()), e))?;
659 
660     Ok(content)
661 }
662 
write_file(path: &PathBuf, content: String) -> Result<(), Error>663 fn write_file(path: &PathBuf, content: String) -> Result<(), Error> {
664     let mut file = File::create(path.as_path()).map_err(|e| {
665         Error::IO(
666             format!("Couldn't open {} in write mode", path.to_str().unwrap()),
667             e,
668         )
669     })?;
670     file.write_all(content.as_bytes())
671         .map_err(|e| Error::IO(format!("Couldn't write {}", path.to_str().unwrap()), e))?;
672 
673     Ok(())
674 }
675 
update_cargo<T>(cargo: &PathBuf, replacer: T) -> Result<(), Error> where T: DependencyLineReplacer,676 fn update_cargo<T>(cargo: &PathBuf, replacer: T) -> Result<(), Error>
677 where
678     T: DependencyLineReplacer,
679 {
680     let content = read_file(cargo)?;
681     let mut filtered_lines = Vec::new();
682     for line in content.split("\n") {
683         if line.starts_with(
684             "# jsparagus = { git = \"https://github.com/mozilla-spidermonkey/jsparagus\",",
685         ) || line.starts_with(
686             "jsparagus = { git = \"https://github.com/mozilla-spidermonkey/jsparagus\",",
687         ) {
688             let orig_line = if line.starts_with("# ") {
689                 &line[2..]
690             } else {
691                 line
692             };
693             replacer.on_official(orig_line, &mut filtered_lines)
694         } else if line.starts_with("jsparagus = ") {
695         } else {
696             filtered_lines.push(line.to_string());
697         }
698     }
699     write_file(cargo, filtered_lines.join("\n"))
700 }
701 
run_mach(command_args: &[&str], args: &SimpleArgs) -> Result<(), Error>702 fn run_mach(command_args: &[&str], args: &SimpleArgs) -> Result<(), Error> {
703     let build = BuildTree::try_new(args)?;
704 
705     check_command(
706         Command::new(build.moz.topsrcdir.join("mach").to_str().unwrap())
707             .args(command_args)
708             .current_dir(build.moz.topsrcdir)
709             .env("MOZCONFIG", build.mozconfig.to_str().unwrap()),
710     )
711 }
712 
build(args: &SimpleArgs) -> Result<(), Error>713 fn build(args: &SimpleArgs) -> Result<(), Error> {
714     let moz = MozillaTree::try_new(&args.moz_path)?;
715     let jsparagus = JsparagusTree::try_new()?;
716 
717     update_cargo(
718         &moz.smoosh_cargo,
719         LocalDependencyLineReplacer {
720             jsparagus: jsparagus.topsrcdir,
721         },
722     )?;
723 
724     run_mach(&["build"], args)
725 }
726 
shell(args: &SimpleArgs) -> Result<(), Error>727 fn shell(args: &SimpleArgs) -> Result<(), Error> {
728     run_mach(&["run", "--smoosh"], args)
729 }
730 
bench(args: &SimpleArgs) -> Result<(), Error>731 fn bench(args: &SimpleArgs) -> Result<(), Error> {
732     let jsparagus = JsparagusTree::try_new()?;
733     let cmp_parsers = jsparagus.compare_parsers_js();
734     let cmp_parsers: &str = cmp_parsers.to_str().ok_or(Error::Generic(
735         "Unable to serialize benchmark script path".into(),
736     ))?;
737     let realjs_path = jsparagus.topsrcdir.join(&args.realjs_path);
738     let realjs_path: &str = realjs_path.to_str().ok_or(Error::Generic(
739         "Unable to serialize benchmark script path".into(),
740     ))?;
741 
742     run_mach(
743         &["run", "-f", cmp_parsers, "--", "--", "--dir", realjs_path],
744         args,
745     )
746 }
747 
test(args: &SimpleArgs) -> Result<(), Error>748 fn test(args: &SimpleArgs) -> Result<(), Error> {
749     run_mach(&["jstests", "--args=--smoosh"], args)?;
750     run_mach(&["jit-test", "--args=--smoosh"], args)
751 }
752 
vendor(moz: &MozillaTree) -> Result<(), Error>753 fn vendor(moz: &MozillaTree) -> Result<(), Error> {
754     check_command(
755         Command::new(moz.topsrcdir.join("mach").to_str().unwrap())
756             .arg("vendor")
757             .arg("rust")
758             .current_dir(moz.topsrcdir.clone()),
759     )
760 }
761 
bump(args: &SimpleArgs) -> Result<(), Error>762 fn bump(args: &SimpleArgs) -> Result<(), Error> {
763     let moz = MozillaTree::try_new(&args.moz_path)?;
764     let jsparagus = JsparagusTree::try_new()?;
765 
766     let jsparagus_repo = GitRepository::try_new(jsparagus.topsrcdir.clone())?;
767 
768     log_info!("Checking ci_generated branch HEAD");
769 
770     let remotes =
771         jsparagus_repo.ls_remote("https://github.com/mozilla-spidermonkey/jsparagus.git")?;
772 
773     let branch = "refs/heads/ci_generated";
774 
775     let ci_generated_sha = match remotes.get(branch) {
776         Some(sha) => sha,
777         None => {
778             return Err(Error::Generic(format!("{} not found in upstream", branch)));
779         }
780     };
781 
782     log_info!("ci_generated branch HEAD = {}", ci_generated_sha);
783 
784     update_cargo(
785         &moz.smoosh_cargo,
786         OfficialDependencyLineReplacer {
787             sha: ci_generated_sha.clone(),
788         },
789     )?;
790 
791     vendor(&moz)?;
792 
793     log_info!("Please add updated files and commit them.");
794 
795     Ok(())
796 }
797 
798 /// Parse remote string and get GitHub username.
799 /// Currently this supports only SSH format.
parse_github_username(remote: String) -> Result<String, Error>800 fn parse_github_username(remote: String) -> Result<String, Error> {
801     let git_prefix = "git@github.com:";
802     let git_suffix = "/jsparagus.git";
803 
804     if remote.starts_with(git_prefix) && remote.ends_with(git_suffix) {
805         return Ok(remote.replace(git_prefix, "").replace(git_suffix, ""));
806     }
807 
808     Err(Error::Generic(format!(
809         "Failed to get GitHub username: {}",
810         remote
811     )))
812 }
813 
814 struct BranchInfo {
815     github_user: String,
816     branch: String,
817 }
818 
819 /// Create "generated" branch and push to remote, and returns
820 /// GitHub username and branch name.
push_to_gen_branch(args: &SimpleArgs) -> Result<BranchInfo, Error>821 fn push_to_gen_branch(args: &SimpleArgs) -> Result<BranchInfo, Error> {
822     let jsparagus = JsparagusTree::try_new()?;
823 
824     let jsparagus_repo = GitRepository::try_new(jsparagus.topsrcdir.clone())?;
825     jsparagus_repo.assert_clean()?;
826 
827     log_info!("Getting GitHub username and current branch");
828 
829     let origin = jsparagus_repo
830         .get_output(&["remote", "get-url", args.remote.as_str()])?
831         .trim()
832         .to_string();
833     let github_user = parse_github_username(origin)?;
834 
835     let branch = jsparagus_repo.branch()?;
836     if branch == "HEAD" {
837         return Err(Error::Generic(format!(
838             "Detached HEAD is not supported. Please checkout a branch"
839         )));
840     }
841 
842     let gen_branch = format!("{}-generated-branch", branch);
843 
844     log_info!("Creating {} branch", gen_branch);
845 
846     jsparagus_repo.run(&["checkout", "-b", gen_branch.as_str()])?;
847 
848     try_finally!({
849         log_info!("Updating generated files");
850 
851         check_command(
852             Command::new("make")
853                 .arg("all")
854                 .current_dir(jsparagus.topsrcdir.clone()),
855         )?;
856 
857         log_info!("Committing generated files");
858 
859         jsparagus_repo.run(&["add", "--force", "*_generated.rs"])?;
860 
861         try_finally!({
862             jsparagus_repo.run(&["commit", "-m", "Add generated files"])?;
863 
864             try_finally!({
865                 log_info!("Pushing to {}", gen_branch);
866                 jsparagus_repo.run(&["push", "-f", args.remote.as_str(), gen_branch.as_str()])?;
867             } {
868                 // Revert the commit, wihtout removing *_generated.rs.
869                 jsparagus_repo.run(&["reset", "--soft", "HEAD^"])?;
870             });
871         } {
872             // Forget *_generated.rs files.
873             jsparagus_repo.run(&["reset"])?;
874         });
875     } {
876         jsparagus_repo.run(&["checkout", branch.as_str()])?;
877         jsparagus_repo.run(&["branch", "-D", gen_branch.as_str()])?;
878     });
879 
880     Ok(BranchInfo {
881         github_user,
882         branch: gen_branch,
883     })
884 }
885 
gen_branch(args: &SimpleArgs) -> Result<(), Error>886 fn gen_branch(args: &SimpleArgs) -> Result<(), Error> {
887     push_to_gen_branch(args)?;
888 
889     Ok(())
890 }
891 
push_try(args: &SimpleArgs) -> Result<(), Error>892 fn push_try(args: &SimpleArgs) -> Result<(), Error> {
893     let moz = MozillaTree::try_new(&args.moz_path)?;
894 
895     let moz_repo = GitRepository::try_new(moz.topsrcdir.clone())?;
896     moz_repo.assert_clean()?;
897 
898     let branch_info = push_to_gen_branch(args)?;
899 
900     moz_repo.ensure_remote("try", "hg::https://hg.mozilla.org/try")?;
901 
902     update_cargo(
903         &moz.smoosh_cargo,
904         ForkDependencyLineReplacer {
905             github_user: branch_info.github_user,
906             branch: branch_info.branch,
907         },
908     )?;
909 
910     vendor(&moz)?;
911 
912     moz_repo.run(&["add", "."])?;
913     moz_repo.run(&["commit", "-m", "Update vendored crates for jsparagus"])?;
914     try_finally!({
915         let syntax = "try: -b do -p sm-smoosh-linux64,sm-nonunified-linux64 -u none -t none";
916         moz_repo.run(&["commit", "--allow-empty", "-m", syntax])?;
917         try_finally!({
918             moz_repo.run(&["push", "try"])?;
919         } {
920             moz_repo.run(&["reset", "--hard", "HEAD^"])?;
921         });
922     } {
923         moz_repo.run(&["reset", "--hard", "HEAD^"])?;
924     });
925 
926     Ok(())
927 }
928 
main()929 fn main() {
930     let args = SimpleArgs::parse(env::args());
931 
932     let result = match args.command {
933         CommandType::Build => build(&args),
934         CommandType::Shell => shell(&args),
935         CommandType::Test => test(&args),
936         CommandType::Bench => bench(&args),
937         CommandType::Bump => bump(&args),
938         CommandType::Gen => gen_branch(&args),
939         CommandType::Try => push_try(&args),
940     };
941 
942     match result {
943         Ok(_) => {}
944         Err(e) => {
945             e.dump();
946             exit(1)
947         }
948     }
949 }
950