1 //! Helper script to publish the wasm-bindgen suite of crates
2 //!
3 //! Usage:
4 //!
5 //! * First, compile this script
6 //! * Next, set cwd to the root of the wasm-bindgen repository
7 //! * Execute `./publish bump` to bump versions
8 //! * Send a PR
9 //! * Merge when green
10 //! * Execute `./publish publish` to publish crates
11
12 use std::collections::HashMap;
13 use std::env;
14 use std::fs;
15 use std::io;
16 use std::path::{Path, PathBuf};
17 use std::process::Command;
18
19 // note that this list must be topologically sorted by dependencies
20 const CRATES_TO_PUBLISH: &[&str] = &[
21 "wasm-bindgen-shared",
22 "wasm-bindgen-backend",
23 "wasm-bindgen-macro-support",
24 "wasm-bindgen-macro",
25 "wasm-bindgen-test-macro",
26 "wasm-bindgen-test",
27 "wasm-bindgen-wasm-interpreter",
28 "wasm-bindgen-webidl",
29 "wasm-bindgen-wasm-conventions",
30 "wasm-bindgen-threads-xform",
31 "wasm-bindgen-multi-value-xform",
32 "wasm-bindgen-externref-xform",
33 "wasm-bindgen-cli-support",
34 "wasm-bindgen-cli",
35 "wasm-bindgen",
36 "wasm-bindgen-futures",
37 "js-sys",
38 "web-sys",
39 ];
40
41 const CRATES_TO_AVOID_PUBLISH: &[&str] = &[
42 // We'll publish these when they're ready one day
43 "wasm-bindgen-typescript",
44 // These are internal crates, unlikely to ever be published
45 "ui-tests",
46 "sample",
47 "webidl-tests",
48 "typescript-tests",
49 ];
50
51 struct Crate {
52 manifest: PathBuf,
53 name: String,
54 version: String,
55 next_version: String,
56 }
57
main()58 fn main() {
59 let mut crates = Vec::new();
60 crates.push(read_crate("./Cargo.toml".as_ref()));
61 find_crates("crates".as_ref(), &mut crates);
62 find_crates("examples".as_ref(), &mut crates);
63
64 let pos = CRATES_TO_PUBLISH
65 .iter()
66 .chain(CRATES_TO_AVOID_PUBLISH)
67 .enumerate()
68 .map(|(i, c)| (*c, i))
69 .collect::<HashMap<_, _>>();
70 crates.sort_by_key(|krate| pos.get(&krate.name[..]));
71
72 match &env::args().nth(1).expect("must have one argument")[..] {
73 "bump" => {
74 for krate in crates.iter() {
75 bump_version(&krate, &crates);
76 }
77 }
78
79 "publish" => {
80 for krate in crates.iter() {
81 publish(&krate);
82 }
83 }
84
85 s => panic!("unknown command: {}", s),
86 }
87 }
88
find_crates(dir: &Path, dst: &mut Vec<Crate>)89 fn find_crates(dir: &Path, dst: &mut Vec<Crate>) {
90 if dir.join("Cargo.toml").exists() {
91 let krate = read_crate(&dir.join("Cargo.toml"));
92 if CRATES_TO_PUBLISH
93 .iter()
94 .chain(CRATES_TO_AVOID_PUBLISH)
95 .any(|c| krate.name == *c)
96 {
97 dst.push(krate);
98 } else if dir.iter().any(|s| s == "examples") {
99 dst.push(krate);
100 } else {
101 panic!("failed to find {:?} in whitelist or blacklist", krate.name);
102 }
103 }
104
105 for entry in dir.read_dir().unwrap() {
106 let entry = entry.unwrap();
107 if entry.file_type().unwrap().is_dir() {
108 find_crates(&entry.path(), dst);
109 }
110 }
111 }
112
read_crate(manifest: &Path) -> Crate113 fn read_crate(manifest: &Path) -> Crate {
114 let mut name = None;
115 let mut version = None;
116 for line in fs::read_to_string(manifest).unwrap().lines() {
117 if name.is_none() && line.starts_with("name = \"") {
118 name = Some(
119 line.replace("name = \"", "")
120 .replace("\"", "")
121 .trim()
122 .to_string(),
123 );
124 }
125 if version.is_none() && line.starts_with("version = \"") {
126 version = Some(
127 line.replace("version = \"", "")
128 .replace("\"", "")
129 .trim()
130 .to_string(),
131 );
132 }
133 }
134 let name = name.unwrap();
135 let version = version.unwrap();
136 let next_version = if CRATES_TO_PUBLISH.contains(&&name[..]) {
137 bump(&version)
138 } else {
139 version.clone()
140 };
141 Crate {
142 manifest: manifest.to_path_buf(),
143 name,
144 version,
145 next_version,
146 }
147 }
148
bump_version(krate: &Crate, crates: &[Crate])149 fn bump_version(krate: &Crate, crates: &[Crate]) {
150 let contents = fs::read_to_string(&krate.manifest).unwrap();
151
152 let mut new_manifest = String::new();
153 let mut is_deps = false;
154 for line in contents.lines() {
155 let mut rewritten = false;
156 if line.starts_with("version =") {
157 if CRATES_TO_PUBLISH.contains(&&krate.name[..]) {
158 println!(
159 "bump `{}` {} => {}",
160 krate.name, krate.version, krate.next_version
161 );
162 new_manifest.push_str(&line.replace(&krate.version, &krate.next_version));
163 rewritten = true;
164 }
165 }
166
167 is_deps = if line.starts_with("[") {
168 line.contains("dependencies")
169 } else {
170 is_deps
171 };
172
173 for other in crates {
174 if !is_deps || !line.starts_with(&format!("{} ", other.name)) {
175 continue;
176 }
177 if !line.contains(&other.version) {
178 if !line.contains("version =") {
179 continue;
180 }
181 panic!(
182 "{:?} has a dep on {} but doesn't list version {}",
183 krate.manifest, other.name, other.version
184 );
185 }
186 rewritten = true;
187 new_manifest.push_str(&line.replace(&other.version, &other.next_version));
188 break;
189 }
190 if !rewritten {
191 new_manifest.push_str(line);
192 }
193 new_manifest.push_str("\n");
194 }
195 fs::write(&krate.manifest, new_manifest).unwrap();
196 }
197
bump(version: &str) -> String198 fn bump(version: &str) -> String {
199 let mut iter = version.split('.').map(|s| s.parse::<u32>().unwrap());
200 let major = iter.next().expect("major version");
201 let minor = iter.next().expect("minor version");
202 let patch = iter.next().expect("patch version");
203 format!("{}.{}.{}", major, minor, patch + 1)
204 }
205
publish(krate: &Crate)206 fn publish(krate: &Crate) {
207 if !CRATES_TO_PUBLISH.iter().any(|s| *s == krate.name) {
208 return;
209 }
210 let status = Command::new("cargo")
211 .arg("publish")
212 .current_dir(krate.manifest.parent().unwrap())
213 .arg("--no-verify")
214 .arg("--allow-dirty")
215 .status()
216 .expect("failed to run cargo");
217 if !status.success() {
218 println!("FAIL: failed to publish `{}`: {}", krate.name, status);
219 }
220 }
221