diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-07 19:33:14 +0000 |
commit | 36d22d82aa202bb199967e9512281e9a53db42c9 (patch) | |
tree | 105e8c98ddea1c1e4784a60a5a6410fa416be2de /third_party/rust/jsparagus/src | |
parent | Initial commit. (diff) | |
download | firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.tar.xz firefox-esr-36d22d82aa202bb199967e9512281e9a53db42c9.zip |
Adding upstream version 115.7.0esr.upstream/115.7.0esrupstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'third_party/rust/jsparagus/src')
-rw-r--r-- | third_party/rust/jsparagus/src/bin/smoosh_tools.rs | 964 | ||||
-rw-r--r-- | third_party/rust/jsparagus/src/lib.rs | 24 |
2 files changed, 988 insertions, 0 deletions
diff --git a/third_party/rust/jsparagus/src/bin/smoosh_tools.rs b/third_party/rust/jsparagus/src/bin/smoosh_tools.rs new file mode 100644 index 0000000000..3e814237e3 --- /dev/null +++ b/third_party/rust/jsparagus/src/bin/smoosh_tools.rs @@ -0,0 +1,964 @@ +use std::collections::HashMap; +use std::env::{self, Args}; +use std::fs::{create_dir_all, File}; +use std::io::{Read, Write}; +use std::path::{Path, PathBuf}; +use std::process::{self, exit, Command}; +use std::str::FromStr; + +static USAGE_STRING: &'static str = r#"Tools for jsparagus + SmooshMonkey development + +USAGE: + cargo run --bin smoosh_tools [COMMAND] [OPTIONS] + +COMMAND: + build [--opt] [MOZILLA_CENTRAL] + Build SpiderMonkey JS shell with SmooshMonkey enabled, using this + jsparagus clone instead of vendored one + shell [--opt] [MOZILLA_CENTRAL] + Run SpiderMonkey JS shell binary built by "build" command + test [--opt] [MOZILLA_CENTRAL] + Run jstests/jit-test with SpiderMonkey JS shell binary built by + "build" command + bench [--opt] [--samples-dir=REAL_JS_SAMPLES/DATE] [MOZILLA_CENTRAL] + Compare SpiderMonkey parser performance against SmooshMonkey on a + collection of JavaScript files, using the JS shell binary built by + "build" command. + bump [MOZILLA_CENTRAL] + Bump jsparagus version referred by mozilla-central to the latest + "ci_generated" branch HEAD, and re-vendor jsparagus + try [--remote=REMOTE] [MOZILLA_CENTRAL] + Push to try with current jsparagus branch + This pushes current jsparagus branch to "generated" branch, and + modifies the reference in mozilla-central to it, and pushes to try + This requires L1 Commit Access for hg.mozilla.org, + and mozilla-central should be a Git repository + gen [--remote=REMOTE] + Push current jsparagus branch to "generated" branch, with generated + files included, to refer from mozilla-central + +OPTIONS: + MOZILLA_CENTRAL Path to mozilla-central or mozilla-unified clone + This can be omitted if mozilla-central or mozilla-unified + is placed next to jsparagus clone directory + --opt Use optimized build configuration, instead of debug build + --remote=REMOTE The name of remote to push the generated branch to + Defaults to "origin" + --concat-mozconfig For building mozilla-central, concatenates the content + of the MOZCONFIG environment variable with the content of + smoosh_tools mozconfig. + --samples-dir=DIR Directory containing thousands of JavaScripts to be used + for measuring the performance of SmooshMonkey. +"#; + +macro_rules! try_finally { + ({$($t: tt)*} {$($f: tt)*}) => { + let result = (|| -> Result<(), Error> { + $($t)* + Ok(()) + })(); + $($f)* + result? + } +} + +/// Simple wrapper for logging. +/// +/// Do not use env_logger etc, to avoid adding extra dependency to library. +/// See https://github.com/rust-lang/rfcs/pull/2887 +macro_rules! log_info { + ($($t: tt)*) => { + print!("[INFO] "); + println!($($t)*); + } +} + +#[derive(Debug)] +enum Error { + Generic(String), + SubProcessError(String, Option<i32>), + IO(String, std::io::Error), + Encode(String, std::str::Utf8Error), + EnvVar(&'static str, std::env::VarError), +} + +impl Error { + fn dump(&self) { + match self { + Error::Generic(message) => { + println!("{}", message); + } + Error::SubProcessError(message, code) => { + println!("{}", message); + match code { + Some(code) => println!("Subprocess exit with exit status: {}", code), + None => println!("Subprocess terminated by signal"), + } + } + Error::IO(message, e) => { + println!("{}", message); + println!("{}", e); + } + Error::Encode(message, e) => { + println!("{}", message); + println!("{}", e); + } + Error::EnvVar(var, e) => { + println!("Error while reading {}:", var); + println!("{}", e); + } + } + } +} + +#[derive(Debug, Copy, Clone)] +enum CommandType { + Build, + Shell, + Test, + Bench, + Bump, + Gen, + Try, +} + +#[derive(Debug, Copy, Clone)] +enum BuildType { + Opt, + Debug, +} + +/// Parse command line arguments. +/// +/// Do not use `clap` here, to avoid adding extra dependency to library. +/// See https://github.com/rust-lang/rfcs/pull/2887 +#[derive(Debug)] +struct SimpleArgs { + command: CommandType, + build_type: BuildType, + moz_path: String, + realjs_path: String, + remote: String, + concat_mozconfig: bool, +} + +impl SimpleArgs { + fn parse(mut args: Args) -> Self { + // Skip binary path. + let _ = args.next().unwrap(); + + let command = match args.next() { + Some(command) => match command.as_str() { + "build" => CommandType::Build, + "test" => CommandType::Test, + "shell" => CommandType::Shell, + "bench" => CommandType::Bench, + "bump" => CommandType::Bump, + "gen" => CommandType::Gen, + "try" => CommandType::Try, + _ => Self::show_usage(), + }, + None => Self::show_usage(), + }; + + let mut plain_args = Vec::new(); + + let mut remote = "origin".to_string(); + let mut moz_path = Self::guess_moz(); + let mut realjs_path = Self::guess_realjs(); + let mut build_type = BuildType::Debug; + let mut concat_mozconfig = false; + + for arg in args { + if arg.starts_with("-") { + if arg.contains("=") { + let mut split = arg.split("="); + let name = match split.next() { + Some(s) => s, + None => Self::show_usage(), + }; + let value = match split.next() { + Some(s) => s, + None => Self::show_usage(), + }; + + match name { + "--remote" => { + remote = value.to_string(); + } + "--samples-dir" => { + realjs_path = value.to_string(); + } + _ => { + Self::show_usage(); + } + } + } else { + match arg.as_str() { + "--opt" => { + build_type = BuildType::Opt; + } + "--concat-mozconfig" => { + concat_mozconfig = true; + } + _ => { + Self::show_usage(); + } + } + } + } else { + plain_args.push(arg); + } + } + + if !plain_args.is_empty() { + moz_path = plain_args.remove(0); + } + + if !plain_args.is_empty() { + Self::show_usage(); + } + + SimpleArgs { + command, + build_type, + moz_path, + realjs_path, + remote, + concat_mozconfig, + } + } + + fn show_usage() -> ! { + print!("{}", USAGE_STRING); + process::exit(-1) + } + + fn guess_moz() -> String { + let cwd = match env::current_dir() { + Ok(cwd) => cwd, + _ => return "../mozilla-central".to_string(), + }; + + for path in vec!["../mozilla-central", "../mozilla-unified"] { + let topsrcdir = Path::new(&cwd).join(path); + if topsrcdir.exists() { + return path.to_string(); + } + } + + return "../mozilla-central".to_string(); + } + + fn guess_realjs() -> String { + return "../real-js-samples/20190416".to_string(); + } +} + +#[derive(Debug)] +struct MozillaTree { + topsrcdir: PathBuf, + smoosh_cargo: PathBuf, +} + +impl MozillaTree { + fn try_new(path: &String) -> Result<Self, Error> { + let rel_topsrcdir = Path::new(path); + let cwd = env::current_dir().unwrap(); + let topsrcdir = Path::new(&cwd).join(rel_topsrcdir); + if !topsrcdir.exists() { + return Err(Error::Generic(format!( + "{:?} doesn't exist. Please specify a path to mozilla-central\n +For more information, see https://github.com/mozilla-spidermonkey/jsparagus/wiki/SpiderMonkey", + topsrcdir + ))); + } + let topsrcdir = topsrcdir.canonicalize().unwrap(); + let cargo = topsrcdir + .join("js") + .join("src") + .join("frontend") + .join("smoosh") + .join("Cargo.toml"); + if !cargo.exists() { + return Err(Error::Generic(format!( + "{:?} doesn't exist. Please specify a path to mozilla-central", + cargo + ))); + } + + Ok(Self { + topsrcdir: topsrcdir.to_path_buf(), + smoosh_cargo: cargo.to_path_buf(), + }) + } +} + +#[derive(Debug)] +struct JsparagusTree { + topsrcdir: PathBuf, + mozconfigs: PathBuf, +} + +impl JsparagusTree { + fn try_new() -> Result<Self, Error> { + let cwd = env::current_dir().unwrap(); + let topsrcdir = Path::new(&cwd); + let cargo = topsrcdir.join("Cargo.toml"); + if !cargo.exists() { + return Err(Error::Generic(format!( + "{:?} doesn't exist. Please run smoosh_tools in jsparagus top level directory", + cargo + ))); + } + + let mozconfigs = topsrcdir.join("mozconfigs"); + if !mozconfigs.exists() { + return Err(Error::Generic(format!( + "{:?} doesn't exist. Please run smoosh_tools in jsparagus top level directory", + mozconfigs + ))); + } + + Ok(Self { + topsrcdir: topsrcdir.to_path_buf(), + mozconfigs: mozconfigs.to_path_buf(), + }) + } + + fn mozconfig(&self, build_type: BuildType) -> PathBuf { + self.mozconfigs.join(match build_type { + BuildType::Opt => "smoosh-opt", + BuildType::Debug => "smoosh-debug", + }) + } + + fn compare_parsers_js(&self) -> PathBuf { + self.topsrcdir + .join("benchmarks") + .join("compare-spidermonkey-parsers.js") + } +} + +struct ObjDir(String); +impl FromStr for ObjDir { + type Err = Error; + + fn from_str(s: &str) -> Result<Self, Self::Err> { + let header = "mk_add_options"; + let s = match s.starts_with(header) { + true => &s[header.len()..], + false => return Err(Error::Generic("unexpected start".into())), + }; + if Some(0) != s.find(char::is_whitespace) { + return Err(Error::Generic( + "expected whitespace after mk_add_options".into(), + )); + } + let s = s.trim_start(); + let eq_idx = s.find('=').ok_or(Error::Generic( + "equal sign not found after mk_add_option".into(), + ))?; + let var_name = &s[..eq_idx]; + if var_name != "MOZ_OBJDIR" { + return Err(Error::Generic(format!( + "{}: unexpected variable, expected MOZ_OBJDIR", + var_name + ))); + } + let s = &s[(eq_idx + 1)..]; + let s = s.trim(); + + Ok(ObjDir(s.into())) + } +} + +#[derive(Debug)] +struct BuildTree { + moz: MozillaTree, + #[allow(dead_code)] + jsp: JsparagusTree, + mozconfig: PathBuf, +} + +impl BuildTree { + fn try_new(args: &SimpleArgs) -> Result<Self, Error> { + let moz = MozillaTree::try_new(&args.moz_path)?; + let jsp = JsparagusTree::try_new()?; + + let jsp_mozconfig = jsp.mozconfig(args.build_type); + let mozconfig = if args.concat_mozconfig { + // Create a MOZCONFIG file which concatenate the content of the + // environmenet variable with the content provided by jsparagus. + // This is useful to add additional compilation variants for + // mozilla-central. + let env = env::var("MOZCONFIG").map_err(|e| Error::EnvVar("MOZCONFIG", e))?; + let env_config = read_file(&env.into())?; + let jsp_config = read_file(&jsp_mozconfig)?; + let config = env_config + &jsp_config; + + // Extract the object directory, in which the mozconfig file would + // be created. + let mut objdir = None; + for line in config.lines() { + match line.parse() { + Ok(ObjDir(meta_path)) => objdir = Some(meta_path), + Err(_error) => (), + } + } + let objdir = objdir.ok_or(Error::Generic("MOZ_OBJDIR must exists".into()))?; + let topsrcdir = moz + .topsrcdir + .to_str() + .ok_or(()) + .map_err(|_| Error::Generic("topsrcdir cannot be encoded in UTF-8.".into()))?; + let objdir = objdir.replace("@TOPSRCDIR@", topsrcdir); + + // Create the object direcotry. + let objdir: PathBuf = objdir.into(); + if !objdir.is_dir() { + create_dir_all(&objdir).map_err(|e| { + Error::IO(format!("Failed to create directory {:?}", objdir), e) + })?; + } + + // Create MOZCONFIG file. + let mozconfig = objdir.join("mozconfig"); + write_file(&mozconfig, config)?; + + mozconfig + } else { + jsp_mozconfig + }; + + Ok(Self { + moz, + jsp, + mozconfig, + }) + } +} + +/// Run `command`, and check if the exit code is successful. +/// Returns Err if failed to run the command, or the exit code is non-zero. +fn check_command(command: &mut Command) -> Result<(), Error> { + log_info!("$ {:?}", command); + let status = command + .status() + .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?; + if !status.success() { + return Err(Error::SubProcessError( + format!("Failed to run {:?}", command), + status.code(), + )); + } + + Ok(()) +} + +/// Run `command`, and returns its status code. +/// Returns Err if failed to run the command, or the subprocess is terminated +/// by signal. +fn get_retcode(command: &mut Command) -> Result<i32, Error> { + log_info!("$ {:?}", command); + let status = command + .status() + .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?; + if !status.success() { + match status.code() { + Some(code) => return Ok(code), + None => { + return Err(Error::SubProcessError( + format!("Failed to run {:?}", command), + None, + )) + } + } + } + + Ok(0) +} + +/// Run `command`, and returns its stdout +/// Returns Err if failed to run the command. +fn get_output(command: &mut Command) -> Result<String, Error> { + log_info!("$ {:?}", command); + let output = command + .output() + .map_err(|e| Error::IO(format!("Failed to run {:?}", command), e))?; + let stdout = std::str::from_utf8(output.stdout.as_slice()) + .map_err(|e| Error::Encode(format!("Failed to decode the output of {:?}", command), e))? + .to_string(); + Ok(stdout) +} + +struct GitRepository { + topsrcdir: PathBuf, +} + +impl GitRepository { + fn try_new(topsrcdir: PathBuf) -> Result<Self, Error> { + if !topsrcdir.join(".git").as_path().exists() { + return Err(Error::Generic(format!( + "{:?} is not Git repository", + topsrcdir + ))); + } + + Ok(Self { topsrcdir }) + } + + fn run(&self, args: &[&str]) -> Result<(), Error> { + check_command( + Command::new("git") + .args(args) + .current_dir(self.topsrcdir.clone()), + ) + } + + fn get_retcode(&self, args: &[&str]) -> Result<i32, Error> { + get_retcode( + Command::new("git") + .args(args) + .current_dir(self.topsrcdir.clone()), + ) + } + + fn get_output(&self, args: &[&str]) -> Result<String, Error> { + get_output( + Command::new("git") + .args(args) + .current_dir(self.topsrcdir.clone()), + ) + } + + /// Checks if there's no uncommitted changes. + fn assert_clean(&self) -> Result<(), Error> { + log_info!("Checking {} is clean", self.topsrcdir.to_str().unwrap()); + let code = self.get_retcode(&["diff-index", "--quiet", "HEAD", "--"])?; + if code != 0 { + return Err(Error::Generic(format!( + "Uncommitted changes found in {}", + self.topsrcdir.to_str().unwrap() + ))); + } + + let code = self.get_retcode(&["diff-index", "--cached", "--quiet", "HEAD", "--"])?; + if code != 0 { + return Err(Error::Generic(format!( + "Uncommitted changes found in {}", + self.topsrcdir.to_str().unwrap() + ))); + } + + Ok(()) + } + + /// Returns the current branch, or "HEAD" if it's detached head.. + fn branch(&self) -> Result<String, Error> { + Ok(self + .get_output(&["rev-parse", "--abbrev-ref", "HEAD"])? + .trim() + .to_string()) + } + + /// Ensure a remote with `name` exists. + /// If it doesn't exist, add remote with `name` and `url`. + fn ensure_remote(&self, name: &'static str, url: &'static str) -> Result<(), Error> { + for line in self.get_output(&["remote"])?.split("\n") { + if line == name { + return Ok(()); + } + } + + self.run(&["remote", "add", name, url])?; + + Ok(()) + } + + /// Returns a map of remote branches. + fn ls_remote(&self, remote: &'static str) -> Result<HashMap<String, String>, Error> { + let mut map = HashMap::new(); + for line in self.get_output(&["ls-remote", remote])?.split("\n") { + let mut split = line.split("\t"); + let sha = match split.next() { + Some(s) => s, + None => continue, + }; + let ref_name = match split.next() { + Some(s) => s, + None => continue, + }; + map.insert(ref_name.to_string(), sha.to_string()); + } + + Ok(map) + } +} + +/// Trait for replacing dependencies in Cargo.toml. +trait DependencyLineReplacer { + /// Receives `line` for official jsparagus reference, + /// and adds modified jsparagus reference to `lines`. + fn on_official(&self, line: &str, lines: &mut Vec<String>); +} + +/// Replace jsparagus reference to `sha` in official ci_generated branch. +struct OfficialDependencyLineReplacer { + sha: String, +} + +impl DependencyLineReplacer for OfficialDependencyLineReplacer { + fn on_official(&self, _line: &str, lines: &mut Vec<String>) { + let newline = format!("jsparagus = {{ git = \"https://github.com/mozilla-spidermonkey/jsparagus\", rev = \"{}\" }}", self.sha); + log_info!("Rewriting jsparagus reference: {}", newline); + lines.push(newline); + } +} + +/// Replace jsparagus reference to local clone. +struct LocalDependencyLineReplacer { + jsparagus: PathBuf, +} + +impl DependencyLineReplacer for LocalDependencyLineReplacer { + fn on_official(&self, line: &str, lines: &mut Vec<String>) { + lines.push(format!("# {}", line)); + let newline = format!( + "jsparagus = {{ path = \"{}\" }}", + self.jsparagus.to_str().unwrap() + ); + log_info!("Rewriting jsparagus reference: {}", newline); + lines.push(newline); + } +} + +/// Replace jsparagus reference to a remote branch in forked repository. +struct ForkDependencyLineReplacer { + github_user: String, + branch: String, +} + +impl DependencyLineReplacer for ForkDependencyLineReplacer { + fn on_official(&self, line: &str, lines: &mut Vec<String>) { + lines.push(format!("# {}", line)); + let newline = format!( + "jsparagus = {{ git = \"https://github.com/{}/jsparagus\", branch = \"{}\" }}", + self.github_user, self.branch + ); + log_info!("Rewriting jsparagus reference: {}", newline); + lines.push(newline); + } +} + +fn read_file(path: &PathBuf) -> Result<String, Error> { + let mut file = File::open(path.as_path()) + .map_err(|e| Error::IO(format!("Couldn't open {}", path.to_str().unwrap()), e))?; + let mut content = String::new(); + file.read_to_string(&mut content) + .map_err(|e| Error::IO(format!("Couldn't read {}", path.to_str().unwrap()), e))?; + + Ok(content) +} + +fn write_file(path: &PathBuf, content: String) -> Result<(), Error> { + let mut file = File::create(path.as_path()).map_err(|e| { + Error::IO( + format!("Couldn't open {} in write mode", path.to_str().unwrap()), + e, + ) + })?; + file.write_all(content.as_bytes()) + .map_err(|e| Error::IO(format!("Couldn't write {}", path.to_str().unwrap()), e))?; + + Ok(()) +} + +fn update_cargo<T>(cargo: &PathBuf, replacer: T) -> Result<(), Error> +where + T: DependencyLineReplacer, +{ + let content = read_file(cargo)?; + let mut filtered_lines = Vec::new(); + for line in content.split("\n") { + if line.starts_with( + "# jsparagus = { git = \"https://github.com/mozilla-spidermonkey/jsparagus\",", + ) || line.starts_with( + "jsparagus = { git = \"https://github.com/mozilla-spidermonkey/jsparagus\",", + ) { + let orig_line = if line.starts_with("# ") { + &line[2..] + } else { + line + }; + replacer.on_official(orig_line, &mut filtered_lines) + } else if line.starts_with("jsparagus = ") { + } else { + filtered_lines.push(line.to_string()); + } + } + write_file(cargo, filtered_lines.join("\n")) +} + +fn update_gkrust(args: &SimpleArgs) -> Result<(), Error> { + log_info!("Updating gkrust-shared"); + + let build = BuildTree::try_new(args)?; + + check_command( + Command::new("cargo") + .args(["update", "-p", "gkrust-shared"]) + .current_dir(build.moz.topsrcdir), + ) +} + +fn run_mach(command_args: &[&str], args: &SimpleArgs) -> Result<(), Error> { + let build = BuildTree::try_new(args)?; + + check_command( + Command::new(build.moz.topsrcdir.join("mach").to_str().unwrap()) + .args(command_args) + .current_dir(build.moz.topsrcdir) + .env("MOZCONFIG", build.mozconfig.to_str().unwrap()), + ) +} + +fn build(args: &SimpleArgs) -> Result<(), Error> { + let moz = MozillaTree::try_new(&args.moz_path)?; + let jsparagus = JsparagusTree::try_new()?; + + update_cargo( + &moz.smoosh_cargo, + LocalDependencyLineReplacer { + jsparagus: jsparagus.topsrcdir, + }, + )?; + + update_gkrust(args)?; + + run_mach(&["build"], args) +} + +fn shell(args: &SimpleArgs) -> Result<(), Error> { + run_mach(&["run", "--smoosh"], args) +} + +fn bench(args: &SimpleArgs) -> Result<(), Error> { + let jsparagus = JsparagusTree::try_new()?; + let cmp_parsers = jsparagus.compare_parsers_js(); + let cmp_parsers: &str = cmp_parsers.to_str().ok_or(Error::Generic( + "Unable to serialize benchmark script path".into(), + ))?; + let realjs_path = jsparagus.topsrcdir.join(&args.realjs_path); + let realjs_path: &str = realjs_path.to_str().ok_or(Error::Generic( + "Unable to serialize benchmark script path".into(), + ))?; + + run_mach( + &["run", "-f", cmp_parsers, "--", "--", "--dir", realjs_path], + args, + ) +} + +fn test(args: &SimpleArgs) -> Result<(), Error> { + run_mach(&["jstests", "--args=--smoosh"], args)?; + run_mach(&["jit-test", "--args=--smoosh"], args) +} + +fn vendor(moz: &MozillaTree) -> Result<(), Error> { + check_command( + Command::new(moz.topsrcdir.join("mach").to_str().unwrap()) + .arg("vendor") + .arg("rust") + .current_dir(moz.topsrcdir.clone()), + ) +} + +fn bump(args: &SimpleArgs) -> Result<(), Error> { + let moz = MozillaTree::try_new(&args.moz_path)?; + let jsparagus = JsparagusTree::try_new()?; + + let jsparagus_repo = GitRepository::try_new(jsparagus.topsrcdir.clone())?; + + log_info!("Checking ci_generated branch HEAD"); + + let remotes = + jsparagus_repo.ls_remote("https://github.com/mozilla-spidermonkey/jsparagus.git")?; + + let branch = "refs/heads/ci_generated"; + + let ci_generated_sha = match remotes.get(branch) { + Some(sha) => sha, + None => { + return Err(Error::Generic(format!("{} not found in upstream", branch))); + } + }; + + log_info!("ci_generated branch HEAD = {}", ci_generated_sha); + + update_cargo( + &moz.smoosh_cargo, + OfficialDependencyLineReplacer { + sha: ci_generated_sha.clone(), + }, + )?; + + vendor(&moz)?; + + log_info!("Please add updated files and commit them."); + + Ok(()) +} + +/// Parse remote string and get GitHub username. +/// Currently this supports only SSH format. +fn parse_github_username(remote: String) -> Result<String, Error> { + let git_prefix = "git@github.com:"; + let git_suffix = "/jsparagus.git"; + + if remote.starts_with(git_prefix) && remote.ends_with(git_suffix) { + return Ok(remote.replace(git_prefix, "").replace(git_suffix, "")); + } + + Err(Error::Generic(format!( + "Failed to get GitHub username: {}", + remote + ))) +} + +struct BranchInfo { + github_user: String, + branch: String, +} + +/// Create "generated" branch and push to remote, and returns +/// GitHub username and branch name. +fn push_to_gen_branch(args: &SimpleArgs) -> Result<BranchInfo, Error> { + let jsparagus = JsparagusTree::try_new()?; + + let jsparagus_repo = GitRepository::try_new(jsparagus.topsrcdir.clone())?; + jsparagus_repo.assert_clean()?; + + log_info!("Getting GitHub username and current branch"); + + let origin = jsparagus_repo + .get_output(&["remote", "get-url", args.remote.as_str()])? + .trim() + .to_string(); + let github_user = parse_github_username(origin)?; + + let branch = jsparagus_repo.branch()?; + if branch == "HEAD" { + return Err(Error::Generic(format!( + "Detached HEAD is not supported. Please checkout a branch" + ))); + } + + let gen_branch = format!("{}-generated-branch", branch); + + log_info!("Creating {} branch", gen_branch); + + jsparagus_repo.run(&["checkout", "-b", gen_branch.as_str()])?; + + try_finally!({ + log_info!("Updating generated files"); + + check_command( + Command::new("make") + .arg("all") + .current_dir(jsparagus.topsrcdir.clone()), + )?; + + log_info!("Committing generated files"); + + jsparagus_repo.run(&["add", "--force", "*_generated.rs"])?; + + try_finally!({ + jsparagus_repo.run(&["commit", "-m", "Add generated files"])?; + + try_finally!({ + log_info!("Pushing to {}", gen_branch); + jsparagus_repo.run(&["push", "-f", args.remote.as_str(), gen_branch.as_str()])?; + } { + // Revert the commit, wihtout removing *_generated.rs. + jsparagus_repo.run(&["reset", "--soft", "HEAD^"])?; + }); + } { + // Forget *_generated.rs files. + jsparagus_repo.run(&["reset"])?; + }); + } { + jsparagus_repo.run(&["checkout", branch.as_str()])?; + jsparagus_repo.run(&["branch", "-D", gen_branch.as_str()])?; + }); + + Ok(BranchInfo { + github_user, + branch: gen_branch, + }) +} + +fn gen_branch(args: &SimpleArgs) -> Result<(), Error> { + push_to_gen_branch(args)?; + + Ok(()) +} + +fn push_try(args: &SimpleArgs) -> Result<(), Error> { + let moz = MozillaTree::try_new(&args.moz_path)?; + + let moz_repo = GitRepository::try_new(moz.topsrcdir.clone())?; + moz_repo.assert_clean()?; + + let branch_info = push_to_gen_branch(args)?; + + moz_repo.ensure_remote("try", "hg::https://hg.mozilla.org/try")?; + + update_cargo( + &moz.smoosh_cargo, + ForkDependencyLineReplacer { + github_user: branch_info.github_user, + branch: branch_info.branch, + }, + )?; + + vendor(&moz)?; + + moz_repo.run(&["add", "."])?; + moz_repo.run(&["commit", "-m", "Update vendored crates for jsparagus"])?; + try_finally!({ + let syntax = "try: -b do -p sm-smoosh-linux64,sm-nonunified-linux64 -u none -t none"; + moz_repo.run(&["commit", "--allow-empty", "-m", syntax])?; + try_finally!({ + moz_repo.run(&["push", "try"])?; + } { + moz_repo.run(&["reset", "--hard", "HEAD^"])?; + }); + } { + moz_repo.run(&["reset", "--hard", "HEAD^"])?; + }); + + Ok(()) +} + +fn main() { + let args = SimpleArgs::parse(env::args()); + + let result = match args.command { + CommandType::Build => build(&args), + CommandType::Shell => shell(&args), + CommandType::Test => test(&args), + CommandType::Bench => bench(&args), + CommandType::Bump => bump(&args), + CommandType::Gen => gen_branch(&args), + CommandType::Try => push_try(&args), + }; + + match result { + Ok(_) => {} + Err(e) => { + e.dump(); + exit(1) + } + } +} diff --git a/third_party/rust/jsparagus/src/lib.rs b/third_party/rust/jsparagus/src/lib.rs new file mode 100644 index 0000000000..abaa91b7a0 --- /dev/null +++ b/third_party/rust/jsparagus/src/lib.rs @@ -0,0 +1,24 @@ +pub mod ast { + extern crate jsparagus_ast; + pub use self::jsparagus_ast::*; +} + +pub mod emitter { + extern crate jsparagus_emitter; + pub use self::jsparagus_emitter::*; +} + +pub mod parser { + extern crate jsparagus_parser; + pub use self::jsparagus_parser::*; +} + +pub mod scope { + extern crate jsparagus_scope; + pub use self::jsparagus_scope::*; +} + +pub mod stencil { + extern crate jsparagus_stencil; + pub use self::jsparagus_stencil::*; +} |