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}