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