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