xtask/
main.rs

1use std::{
2    env::{current_dir, var_os},
3    path::PathBuf,
4    process,
5};
6
7use anyhow::Result;
8use clap::{Command, arg};
9
10mod command;
11mod nft_bench;
12mod publish;
13mod summarize_bench;
14mod visualize_bundler_bench;
15
16use nft_bench::show_result;
17use publish::{publish_workspace, run_bump, run_publish};
18use rustc_hash::{FxHashMap, FxHashSet};
19
20fn cli() -> Command {
21    Command::new("xtask")
22        .about("turbo-tooling cargo tasks")
23        .subcommand_required(true)
24        .arg_required_else_help(true)
25        .allow_external_subcommands(true)
26        .subcommand(
27            Command::new("npm")
28                .about("Publish binaries to npm")
29                .arg(arg!(<NAME> "the package to publish"))
30                .arg_required_else_help(true),
31        )
32        .subcommand(
33            Command::new("workspace")
34                .arg(arg!(--publish "publish npm packages in pnpm workspace"))
35                .arg(arg!(--bump "bump new version for npm package in pnpm workspace"))
36                .arg(arg!(--"dry-run" "dry run all operations"))
37                .arg(arg!([NAME] "the package to bump"))
38                .about("Manage packages in pnpm workspaces"),
39        )
40        .subcommand(
41            Command::new("nft-bench-result")
42                .about("Print node-file-trace benchmark result against @vercel/nft"),
43        )
44        .subcommand(
45            Command::new("upgrade-swc").about("Upgrade all SWC dependencies to the latest version"),
46        )
47        .subcommand(
48            Command::new("summarize-benchmarks")
49                .about(
50                    "Normalize all raw data based on similar benchmarks, average data by \
51                     system+sha and compute latest by system",
52                )
53                .arg(arg!(<PATH> "the path to the benchmark data directory")),
54        )
55        .subcommand(
56            Command::new("visualize-bundler-benchmarks")
57                .about("Generate visualizations of bundler benchmarks")
58                .long_about(
59                    "Generates visualizations of bundler benchmarks. Currently supports:
60    * Scaling: shows how each bundler scales with varying module counts
61
62To generate the summary json file:
63    * Check out this repository at the `benchmark-data` branch. An additional shallow clone or git \
64                     worktree is recommended.
65    * Run `cargo xtask summarize-benchmarks path/to/repo/data`
66    * A summary file is generated within the data dir, e.g.  \
67                     path/to/repo/data/ubuntu-latest-16-core.json
68
69Visualizations generated by this command will appear in a sibling directory to the summary data \
70                     file.",
71                )
72                .arg(arg!(<PATH_TO_SUMMARY_JSON> "the path to the benchmark summary json file"))
73                .arg(arg!(--bundlers <BUNDLERS> "comma separated list of bundlers to include in the visualization")),
74        )
75}
76
77fn main() -> Result<()> {
78    let matches = cli().get_matches();
79    match matches.subcommand() {
80        Some(("npm", sub_matches)) => {
81            let name = sub_matches
82                .get_one::<String>("NAME")
83                .expect("NAME is required");
84            run_publish(name);
85            Ok(())
86        }
87        Some(("workspace", sub_matches)) => {
88            let is_bump = sub_matches.get_flag("bump");
89            let is_publish = sub_matches.get_flag("publish");
90            let dry_run = sub_matches.get_flag("dry-run");
91            if is_bump {
92                let names = sub_matches
93                    .get_many::<String>("NAME")
94                    .map(|names| names.cloned().collect::<FxHashSet<_>>())
95                    .unwrap_or_default();
96                run_bump(names, dry_run);
97            }
98            if is_publish {
99                publish_workspace(dry_run);
100            }
101            Ok(())
102        }
103        Some(("nft-bench-result", _)) => {
104            show_result();
105            Ok(())
106        }
107        Some(("upgrade-swc", _)) => {
108            let workspace_dir = var_os("CARGO_WORKSPACE_DIR")
109                .map(PathBuf::from)
110                .unwrap_or_else(|| current_dir().unwrap());
111            let cargo_lock_path = workspace_dir.join("../../Cargo.lock");
112            let lock = cargo_lock::Lockfile::load(cargo_lock_path).unwrap();
113            let swc_packages = lock
114                .packages
115                .iter()
116                .filter(|p| {
117                    p.name.as_str().starts_with("swc_")
118                        || p.name.as_str() == "swc"
119                        || p.name.as_str() == "testing"
120                })
121                .collect::<Vec<_>>();
122            let only_swc_set = swc_packages
123                .iter()
124                .map(|p| p.name.as_str())
125                .collect::<FxHashSet<_>>();
126            let packages = lock
127                .packages
128                .iter()
129                .map(|p| (format!("{}@{}", p.name, p.version), p))
130                .collect::<FxHashMap<_, _>>();
131            let mut queue = swc_packages.clone();
132            let mut set = FxHashSet::default();
133            while let Some(package) = queue.pop() {
134                for dep in package.dependencies.iter() {
135                    let ident = format!("{}@{}", dep.name, dep.version);
136                    let package = *packages.get(&ident).unwrap();
137                    if set.insert(ident) {
138                        queue.push(package);
139                    }
140                }
141            }
142            let status = process::Command::new("cargo")
143                .arg("upgrade")
144                .arg("--workspace")
145                .args(only_swc_set)
146                .current_dir(&workspace_dir)
147                .stdout(process::Stdio::inherit())
148                .stderr(process::Stdio::inherit())
149                .status()
150                .expect("Running cargo upgrade failed");
151            assert!(status.success());
152            let status = process::Command::new("cargo")
153                .arg("update")
154                .args(set.iter().flat_map(|p| ["-p", p]))
155                .current_dir(&workspace_dir)
156                .stdout(process::Stdio::inherit())
157                .stderr(process::Stdio::inherit())
158                .status()
159                .expect("Running cargo update failed");
160            assert!(status.success());
161            Ok(())
162        }
163        Some(("summarize-benchmarks", sub_matches)) => {
164            let path = sub_matches
165                .get_one::<String>("PATH")
166                .expect("PATH is required");
167            let path = PathBuf::from(path);
168            let path = path.canonicalize().unwrap();
169            summarize_bench::process_all(path);
170            Ok(())
171        }
172        Some(("visualize-bundler-benchmarks", sub_matches)) => {
173            let path = sub_matches
174                .get_one::<String>("PATH_TO_SUMMARY_JSON")
175                .expect("PATH_TO_SUMMARY_JSON is required");
176            let bundlers: Option<FxHashSet<&str>> = sub_matches
177                .get_one::<String>("bundlers")
178                .map(|s| s.split(',').collect());
179
180            let path = PathBuf::from(path);
181            let path = path.canonicalize().unwrap();
182            visualize_bundler_bench::generate(path, bundlers)
183        }
184        _ => {
185            anyhow::bail!("Unknown command {:?}", matches.subcommand().map(|c| c.0));
186        }
187    }
188}