1 use crate::git::repo;
2 use crate::paths;
3 use cargo_util::{registry::make_dep_path, Sha256};
4 use flate2::write::GzEncoder;
5 use flate2::Compression;
6 use std::collections::BTreeMap;
7 use std::fmt::Write as _;
8 use std::fs::{self, File};
9 use std::io::{BufRead, BufReader, Write};
10 use std::net::TcpListener;
11 use std::path::{Path, PathBuf};
12 use std::thread;
13 use tar::{Builder, Header};
14 use url::Url;
15 
16 /// Gets the path to the local index pretending to be crates.io. This is a Git repo
17 /// initialized with a `config.json` file pointing to `dl_path` for downloads
18 /// and `api_path` for uploads.
registry_path() -> PathBuf19 pub fn registry_path() -> PathBuf {
20     generate_path("registry")
21 }
registry_url() -> Url22 pub fn registry_url() -> Url {
23     generate_url("registry")
24 }
25 /// Gets the path for local web API uploads. Cargo will place the contents of a web API
26 /// request here. For example, `api/v1/crates/new` is the result of publishing a crate.
api_path() -> PathBuf27 pub fn api_path() -> PathBuf {
28     generate_path("api")
29 }
api_url() -> Url30 pub fn api_url() -> Url {
31     generate_url("api")
32 }
33 /// Gets the path where crates can be downloaded using the web API endpoint. Crates
34 /// should be organized as `{name}/{version}/download` to match the web API
35 /// endpoint. This is rarely used and must be manually set up.
dl_path() -> PathBuf36 pub fn dl_path() -> PathBuf {
37     generate_path("dl")
38 }
dl_url() -> Url39 pub fn dl_url() -> Url {
40     generate_url("dl")
41 }
42 /// Gets the alternative-registry version of `registry_path`.
alt_registry_path() -> PathBuf43 pub fn alt_registry_path() -> PathBuf {
44     generate_path("alternative-registry")
45 }
alt_registry_url() -> Url46 pub fn alt_registry_url() -> Url {
47     generate_url("alternative-registry")
48 }
49 /// Gets the alternative-registry version of `dl_path`.
alt_dl_path() -> PathBuf50 pub fn alt_dl_path() -> PathBuf {
51     generate_path("alt_dl")
52 }
alt_dl_url() -> String53 pub fn alt_dl_url() -> String {
54     generate_alt_dl_url("alt_dl")
55 }
56 /// Gets the alternative-registry version of `api_path`.
alt_api_path() -> PathBuf57 pub fn alt_api_path() -> PathBuf {
58     generate_path("alt_api")
59 }
alt_api_url() -> Url60 pub fn alt_api_url() -> Url {
61     generate_url("alt_api")
62 }
63 
generate_path(name: &str) -> PathBuf64 pub fn generate_path(name: &str) -> PathBuf {
65     paths::root().join(name)
66 }
generate_url(name: &str) -> Url67 pub fn generate_url(name: &str) -> Url {
68     Url::from_file_path(generate_path(name)).ok().unwrap()
69 }
generate_alt_dl_url(name: &str) -> String70 pub fn generate_alt_dl_url(name: &str) -> String {
71     let base = Url::from_file_path(generate_path(name)).ok().unwrap();
72     format!("{}/{{crate}}/{{version}}/{{crate}}-{{version}}.crate", base)
73 }
74 
75 /// A builder for initializing registries.
76 pub struct RegistryBuilder {
77     /// If `true`, adds source replacement for crates.io to a registry on the filesystem.
78     replace_crates_io: bool,
79     /// If `true`, configures a registry named "alternative".
80     alternative: bool,
81     /// If set, sets the API url for the "alternative" registry.
82     /// This defaults to a directory on the filesystem.
83     alt_api_url: Option<String>,
84     /// If `true`, configures `.cargo/credentials` with some tokens.
85     add_tokens: bool,
86 }
87 
88 impl RegistryBuilder {
new() -> RegistryBuilder89     pub fn new() -> RegistryBuilder {
90         RegistryBuilder {
91             replace_crates_io: true,
92             alternative: false,
93             alt_api_url: None,
94             add_tokens: true,
95         }
96     }
97 
98     /// Sets whether or not to replace crates.io with a registry on the filesystem.
99     /// Default is `true`.
replace_crates_io(&mut self, replace: bool) -> &mut Self100     pub fn replace_crates_io(&mut self, replace: bool) -> &mut Self {
101         self.replace_crates_io = replace;
102         self
103     }
104 
105     /// Sets whether or not to initialize an alternative registry named "alternative".
106     /// Default is `false`.
alternative(&mut self, alt: bool) -> &mut Self107     pub fn alternative(&mut self, alt: bool) -> &mut Self {
108         self.alternative = alt;
109         self
110     }
111 
112     /// Sets the API url for the "alternative" registry.
113     /// Defaults to a path on the filesystem ([`alt_api_path`]).
alternative_api_url(&mut self, url: &str) -> &mut Self114     pub fn alternative_api_url(&mut self, url: &str) -> &mut Self {
115         self.alternative = true;
116         self.alt_api_url = Some(url.to_string());
117         self
118     }
119 
120     /// Sets whether or not to initialize `.cargo/credentials` with some tokens.
121     /// Defaults to `true`.
add_tokens(&mut self, add: bool) -> &mut Self122     pub fn add_tokens(&mut self, add: bool) -> &mut Self {
123         self.add_tokens = add;
124         self
125     }
126 
127     /// Initializes the registries.
build(&self)128     pub fn build(&self) {
129         let config_path = paths::home().join(".cargo/config");
130         if config_path.exists() {
131             panic!(
132                 "{} already exists, the registry may only be initialized once, \
133                 and must be done before the config file is created",
134                 config_path.display()
135             );
136         }
137         t!(fs::create_dir_all(config_path.parent().unwrap()));
138         let mut config = String::new();
139         if self.replace_crates_io {
140             write!(
141                 &mut config,
142                 "
143                     [source.crates-io]
144                     replace-with = 'dummy-registry'
145 
146                     [source.dummy-registry]
147                     registry = '{}'
148                 ",
149                 registry_url()
150             )
151             .unwrap();
152         }
153         if self.alternative {
154             write!(
155                 config,
156                 "
157                     [registries.alternative]
158                     index = '{}'
159                 ",
160                 alt_registry_url()
161             )
162             .unwrap();
163         }
164         t!(fs::write(&config_path, config));
165 
166         if self.add_tokens {
167             let credentials = paths::home().join(".cargo/credentials");
168             t!(fs::write(
169                 &credentials,
170                 r#"
171                     [registry]
172                     token = "api-token"
173 
174                     [registries.alternative]
175                     token = "api-token"
176                 "#
177             ));
178         }
179 
180         if self.replace_crates_io {
181             init_registry(registry_path(), dl_url().into(), api_url(), api_path());
182         }
183 
184         if self.alternative {
185             init_registry(
186                 alt_registry_path(),
187                 alt_dl_url(),
188                 self.alt_api_url
189                     .as_ref()
190                     .map_or_else(alt_api_url, |url| Url::parse(url).expect("valid url")),
191                 alt_api_path(),
192             );
193         }
194     }
195 
196     /// Initializes the registries, and sets up an HTTP server for the
197     /// "alternative" registry.
198     ///
199     /// The given callback takes a `Vec` of headers when a request comes in.
200     /// The first entry should be the HTTP command, such as
201     /// `PUT /api/v1/crates/new HTTP/1.1`.
202     ///
203     /// The callback should return the HTTP code for the response, and the
204     /// response body.
205     ///
206     /// This method returns a `JoinHandle` which you should call
207     /// `.join().unwrap()` on before exiting the test.
build_api_server<'a>( &mut self, handler: &'static (dyn (Fn(Vec<String>) -> (u32, &'a dyn AsRef<[u8]>)) + Sync), ) -> thread::JoinHandle<()>208     pub fn build_api_server<'a>(
209         &mut self,
210         handler: &'static (dyn (Fn(Vec<String>) -> (u32, &'a dyn AsRef<[u8]>)) + Sync),
211     ) -> thread::JoinHandle<()> {
212         let server = TcpListener::bind("127.0.0.1:0").unwrap();
213         let addr = server.local_addr().unwrap();
214         let api_url = format!("http://{}", addr);
215 
216         self.replace_crates_io(false)
217             .alternative_api_url(&api_url)
218             .build();
219 
220         let t = thread::spawn(move || {
221             let mut conn = BufReader::new(server.accept().unwrap().0);
222             let headers: Vec<_> = (&mut conn)
223                 .lines()
224                 .map(|s| s.unwrap())
225                 .take_while(|s| s.len() > 2)
226                 .map(|s| s.trim().to_string())
227                 .collect();
228             let (code, response) = handler(headers);
229             let response = response.as_ref();
230             let stream = conn.get_mut();
231             write!(
232                 stream,
233                 "HTTP/1.1 {}\r\n\
234                   Content-Length: {}\r\n\
235                   \r\n",
236                 code,
237                 response.len()
238             )
239             .unwrap();
240             stream.write_all(response).unwrap();
241         });
242 
243         t
244     }
245 }
246 
247 /// A builder for creating a new package in a registry.
248 ///
249 /// This uses "source replacement" using an automatically generated
250 /// `.cargo/config` file to ensure that dependencies will use these packages
251 /// instead of contacting crates.io. See `source-replacement.md` for more
252 /// details on how source replacement works.
253 ///
254 /// Call `publish` to finalize and create the package.
255 ///
256 /// If no files are specified, an empty `lib.rs` file is automatically created.
257 ///
258 /// The `Cargo.toml` file is automatically generated based on the methods
259 /// called on `Package` (for example, calling `dep()` will add to the
260 /// `[dependencies]` automatically). You may also specify a `Cargo.toml` file
261 /// to override the generated one.
262 ///
263 /// This supports different registry types:
264 /// - Regular source replacement that replaces `crates.io` (the default).
265 /// - A "local registry" which is a subset for vendoring (see
266 ///   `Package::local`).
267 /// - An "alternative registry" which requires specifying the registry name
268 ///   (see `Package::alternative`).
269 ///
270 /// This does not support "directory sources". See `directory.rs` for
271 /// `VendorPackage` which implements directory sources.
272 ///
273 /// # Example
274 /// ```
275 /// // Publish package "a" depending on "b".
276 /// Package::new("a", "1.0.0")
277 ///     .dep("b", "1.0.0")
278 ///     .file("src/lib.rs", r#"
279 ///         extern crate b;
280 ///         pub fn f() -> i32 { b::f() * 2 }
281 ///     "#)
282 ///     .publish();
283 ///
284 /// // Publish package "b".
285 /// Package::new("b", "1.0.0")
286 ///     .file("src/lib.rs", r#"
287 ///         pub fn f() -> i32 { 12 }
288 ///     "#)
289 ///     .publish();
290 ///
291 /// // Create a project that uses package "a".
292 /// let p = project()
293 ///     .file("Cargo.toml", r#"
294 ///         [package]
295 ///         name = "foo"
296 ///         version = "0.0.1"
297 ///
298 ///         [dependencies]
299 ///         a = "1.0"
300 ///     "#)
301 ///     .file("src/main.rs", r#"
302 ///         extern crate a;
303 ///         fn main() { println!("{}", a::f()); }
304 ///     "#)
305 ///     .build();
306 ///
307 /// p.cargo("run").with_stdout("24").run();
308 /// ```
309 #[must_use]
310 pub struct Package {
311     name: String,
312     vers: String,
313     deps: Vec<Dependency>,
314     files: Vec<PackageFile>,
315     yanked: bool,
316     features: FeatureMap,
317     local: bool,
318     alternative: bool,
319     invalid_json: bool,
320     proc_macro: bool,
321     links: Option<String>,
322     rust_version: Option<String>,
323     cargo_features: Vec<String>,
324     v: Option<u32>,
325 }
326 
327 type FeatureMap = BTreeMap<String, Vec<String>>;
328 
329 #[derive(Clone)]
330 pub struct Dependency {
331     name: String,
332     vers: String,
333     kind: String,
334     target: Option<String>,
335     features: Vec<String>,
336     registry: Option<String>,
337     package: Option<String>,
338     optional: bool,
339 }
340 
341 /// A file to be created in a package.
342 struct PackageFile {
343     path: String,
344     contents: String,
345     /// The Unix mode for the file. Note that when extracted on Windows, this
346     /// is mostly ignored since it doesn't have the same style of permissions.
347     mode: u32,
348     /// If `true`, the file is created in the root of the tarfile, used for
349     /// testing invalid packages.
350     extra: bool,
351 }
352 
353 const DEFAULT_MODE: u32 = 0o644;
354 
355 /// Initializes the on-disk registry and sets up the config so that crates.io
356 /// is replaced with the one on disk.
init()357 pub fn init() {
358     let config = paths::home().join(".cargo/config");
359     if config.exists() {
360         return;
361     }
362     RegistryBuilder::new().build();
363 }
364 
365 /// Variant of `init` that initializes the "alternative" registry.
alt_init()366 pub fn alt_init() {
367     RegistryBuilder::new().alternative(true).build();
368 }
369 
370 /// Creates a new on-disk registry.
init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf)371 pub fn init_registry(registry_path: PathBuf, dl_url: String, api_url: Url, api_path: PathBuf) {
372     // Initialize a new registry.
373     repo(&registry_path)
374         .file(
375             "config.json",
376             &format!(r#"{{"dl":"{}","api":"{}"}}"#, dl_url, api_url),
377         )
378         .build();
379     fs::create_dir_all(api_path.join("api/v1/crates")).unwrap();
380 }
381 
382 impl Package {
383     /// Creates a new package builder.
384     /// Call `publish()` to finalize and build the package.
new(name: &str, vers: &str) -> Package385     pub fn new(name: &str, vers: &str) -> Package {
386         init();
387         Package {
388             name: name.to_string(),
389             vers: vers.to_string(),
390             deps: Vec::new(),
391             files: Vec::new(),
392             yanked: false,
393             features: BTreeMap::new(),
394             local: false,
395             alternative: false,
396             invalid_json: false,
397             proc_macro: false,
398             links: None,
399             rust_version: None,
400             cargo_features: Vec::new(),
401             v: None,
402         }
403     }
404 
405     /// Call with `true` to publish in a "local registry".
406     ///
407     /// See `source-replacement.html#local-registry-sources` for more details
408     /// on local registries. See `local_registry.rs` for the tests that use
409     /// this.
local(&mut self, local: bool) -> &mut Package410     pub fn local(&mut self, local: bool) -> &mut Package {
411         self.local = local;
412         self
413     }
414 
415     /// Call with `true` to publish in an "alternative registry".
416     ///
417     /// The name of the alternative registry is called "alternative".
418     ///
419     /// See `src/doc/src/reference/registries.md` for more details on
420     /// alternative registries. See `alt_registry.rs` for the tests that use
421     /// this.
alternative(&mut self, alternative: bool) -> &mut Package422     pub fn alternative(&mut self, alternative: bool) -> &mut Package {
423         self.alternative = alternative;
424         self
425     }
426 
427     /// Adds a file to the package.
file(&mut self, name: &str, contents: &str) -> &mut Package428     pub fn file(&mut self, name: &str, contents: &str) -> &mut Package {
429         self.file_with_mode(name, DEFAULT_MODE, contents)
430     }
431 
432     /// Adds a file with a specific Unix mode.
file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package433     pub fn file_with_mode(&mut self, path: &str, mode: u32, contents: &str) -> &mut Package {
434         self.files.push(PackageFile {
435             path: path.to_string(),
436             contents: contents.to_string(),
437             mode,
438             extra: false,
439         });
440         self
441     }
442 
443     /// Adds an "extra" file that is not rooted within the package.
444     ///
445     /// Normal files are automatically placed within a directory named
446     /// `$PACKAGE-$VERSION`. This allows you to override that behavior,
447     /// typically for testing invalid behavior.
extra_file(&mut self, path: &str, contents: &str) -> &mut Package448     pub fn extra_file(&mut self, path: &str, contents: &str) -> &mut Package {
449         self.files.push(PackageFile {
450             path: path.to_string(),
451             contents: contents.to_string(),
452             mode: DEFAULT_MODE,
453             extra: true,
454         });
455         self
456     }
457 
458     /// Adds a normal dependency. Example:
459     /// ```
460     /// [dependencies]
461     /// foo = {version = "1.0"}
462     /// ```
dep(&mut self, name: &str, vers: &str) -> &mut Package463     pub fn dep(&mut self, name: &str, vers: &str) -> &mut Package {
464         self.add_dep(&Dependency::new(name, vers))
465     }
466 
467     /// Adds a dependency with the given feature. Example:
468     /// ```
469     /// [dependencies]
470     /// foo = {version = "1.0", "features": ["feat1", "feat2"]}
471     /// ```
feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package472     pub fn feature_dep(&mut self, name: &str, vers: &str, features: &[&str]) -> &mut Package {
473         self.add_dep(Dependency::new(name, vers).enable_features(features))
474     }
475 
476     /// Adds a platform-specific dependency. Example:
477     /// ```
478     /// [target.'cfg(windows)'.dependencies]
479     /// foo = {version = "1.0"}
480     /// ```
target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package481     pub fn target_dep(&mut self, name: &str, vers: &str, target: &str) -> &mut Package {
482         self.add_dep(Dependency::new(name, vers).target(target))
483     }
484 
485     /// Adds a dependency to the alternative registry.
registry_dep(&mut self, name: &str, vers: &str) -> &mut Package486     pub fn registry_dep(&mut self, name: &str, vers: &str) -> &mut Package {
487         self.add_dep(Dependency::new(name, vers).registry("alternative"))
488     }
489 
490     /// Adds a dev-dependency. Example:
491     /// ```
492     /// [dev-dependencies]
493     /// foo = {version = "1.0"}
494     /// ```
dev_dep(&mut self, name: &str, vers: &str) -> &mut Package495     pub fn dev_dep(&mut self, name: &str, vers: &str) -> &mut Package {
496         self.add_dep(Dependency::new(name, vers).dev())
497     }
498 
499     /// Adds a build-dependency. Example:
500     /// ```
501     /// [build-dependencies]
502     /// foo = {version = "1.0"}
503     /// ```
build_dep(&mut self, name: &str, vers: &str) -> &mut Package504     pub fn build_dep(&mut self, name: &str, vers: &str) -> &mut Package {
505         self.add_dep(Dependency::new(name, vers).build())
506     }
507 
add_dep(&mut self, dep: &Dependency) -> &mut Package508     pub fn add_dep(&mut self, dep: &Dependency) -> &mut Package {
509         self.deps.push(dep.clone());
510         self
511     }
512 
513     /// Specifies whether or not the package is "yanked".
yanked(&mut self, yanked: bool) -> &mut Package514     pub fn yanked(&mut self, yanked: bool) -> &mut Package {
515         self.yanked = yanked;
516         self
517     }
518 
519     /// Specifies whether or not this is a proc macro.
proc_macro(&mut self, proc_macro: bool) -> &mut Package520     pub fn proc_macro(&mut self, proc_macro: bool) -> &mut Package {
521         self.proc_macro = proc_macro;
522         self
523     }
524 
525     /// Adds an entry in the `[features]` section.
feature(&mut self, name: &str, deps: &[&str]) -> &mut Package526     pub fn feature(&mut self, name: &str, deps: &[&str]) -> &mut Package {
527         let deps = deps.iter().map(|s| s.to_string()).collect();
528         self.features.insert(name.to_string(), deps);
529         self
530     }
531 
532     /// Specify a minimal Rust version.
rust_version(&mut self, rust_version: &str) -> &mut Package533     pub fn rust_version(&mut self, rust_version: &str) -> &mut Package {
534         self.rust_version = Some(rust_version.into());
535         self
536     }
537 
538     /// Causes the JSON line emitted in the index to be invalid, presumably
539     /// causing Cargo to skip over this version.
invalid_json(&mut self, invalid: bool) -> &mut Package540     pub fn invalid_json(&mut self, invalid: bool) -> &mut Package {
541         self.invalid_json = invalid;
542         self
543     }
544 
links(&mut self, links: &str) -> &mut Package545     pub fn links(&mut self, links: &str) -> &mut Package {
546         self.links = Some(links.to_string());
547         self
548     }
549 
cargo_feature(&mut self, feature: &str) -> &mut Package550     pub fn cargo_feature(&mut self, feature: &str) -> &mut Package {
551         self.cargo_features.push(feature.to_owned());
552         self
553     }
554 
555     /// Sets the index schema version for this package.
556     ///
557     /// See `cargo::sources::registry::RegistryPackage` for more information.
schema_version(&mut self, version: u32) -> &mut Package558     pub fn schema_version(&mut self, version: u32) -> &mut Package {
559         self.v = Some(version);
560         self
561     }
562 
563     /// Creates the package and place it in the registry.
564     ///
565     /// This does not actually use Cargo's publishing system, but instead
566     /// manually creates the entry in the registry on the filesystem.
567     ///
568     /// Returns the checksum for the package.
publish(&self) -> String569     pub fn publish(&self) -> String {
570         self.make_archive();
571 
572         // Figure out what we're going to write into the index.
573         let deps = self
574             .deps
575             .iter()
576             .map(|dep| {
577                 // In the index, the `registry` is null if it is from the same registry.
578                 // In Cargo.toml, it is None if it is from crates.io.
579                 let registry_url = match (self.alternative, dep.registry.as_deref()) {
580                     (false, None) => None,
581                     (false, Some("alternative")) => Some(alt_registry_url().to_string()),
582                     (true, None) => {
583                         Some("https://github.com/rust-lang/crates.io-index".to_string())
584                     }
585                     (true, Some("alternative")) => None,
586                     _ => panic!("registry_dep currently only supports `alternative`"),
587                 };
588                 serde_json::json!({
589                     "name": dep.name,
590                     "req": dep.vers,
591                     "features": dep.features,
592                     "default_features": true,
593                     "target": dep.target,
594                     "optional": dep.optional,
595                     "kind": dep.kind,
596                     "registry": registry_url,
597                     "package": dep.package,
598                 })
599             })
600             .collect::<Vec<_>>();
601         let cksum = {
602             let c = t!(fs::read(&self.archive_dst()));
603             cksum(&c)
604         };
605         let name = if self.invalid_json {
606             serde_json::json!(1)
607         } else {
608             serde_json::json!(self.name)
609         };
610         // This emulates what crates.io may do in the future.
611         let (features, features2) = split_index_features(self.features.clone());
612         let mut json = serde_json::json!({
613             "name": name,
614             "vers": self.vers,
615             "deps": deps,
616             "cksum": cksum,
617             "features": features,
618             "yanked": self.yanked,
619             "links": self.links,
620         });
621         if let Some(f2) = &features2 {
622             json["features2"] = serde_json::json!(f2);
623             json["v"] = serde_json::json!(2);
624         }
625         if let Some(v) = self.v {
626             json["v"] = serde_json::json!(v);
627         }
628         let line = json.to_string();
629 
630         let file = make_dep_path(&self.name, false);
631 
632         let registry_path = if self.alternative {
633             alt_registry_path()
634         } else {
635             registry_path()
636         };
637 
638         // Write file/line in the index.
639         let dst = if self.local {
640             registry_path.join("index").join(&file)
641         } else {
642             registry_path.join(&file)
643         };
644         let prev = fs::read_to_string(&dst).unwrap_or_default();
645         t!(fs::create_dir_all(dst.parent().unwrap()));
646         t!(fs::write(&dst, prev + &line[..] + "\n"));
647 
648         // Add the new file to the index.
649         if !self.local {
650             let repo = t!(git2::Repository::open(&registry_path));
651             let mut index = t!(repo.index());
652             t!(index.add_path(Path::new(&file)));
653             t!(index.write());
654             let id = t!(index.write_tree());
655 
656             // Commit this change.
657             let tree = t!(repo.find_tree(id));
658             let sig = t!(repo.signature());
659             let parent = t!(repo.refname_to_id("refs/heads/master"));
660             let parent = t!(repo.find_commit(parent));
661             t!(repo.commit(
662                 Some("HEAD"),
663                 &sig,
664                 &sig,
665                 "Another commit",
666                 &tree,
667                 &[&parent]
668             ));
669         }
670 
671         cksum
672     }
673 
make_archive(&self)674     fn make_archive(&self) {
675         let dst = self.archive_dst();
676         t!(fs::create_dir_all(dst.parent().unwrap()));
677         let f = t!(File::create(&dst));
678         let mut a = Builder::new(GzEncoder::new(f, Compression::default()));
679 
680         if !self
681             .files
682             .iter()
683             .any(|PackageFile { path, .. }| path == "Cargo.toml")
684         {
685             self.append_manifest(&mut a);
686         }
687         if self.files.is_empty() {
688             self.append(&mut a, "src/lib.rs", DEFAULT_MODE, "");
689         } else {
690             for PackageFile {
691                 path,
692                 contents,
693                 mode,
694                 extra,
695             } in &self.files
696             {
697                 if *extra {
698                     self.append_raw(&mut a, path, *mode, contents);
699                 } else {
700                     self.append(&mut a, path, *mode, contents);
701                 }
702             }
703         }
704     }
705 
append_manifest<W: Write>(&self, ar: &mut Builder<W>)706     fn append_manifest<W: Write>(&self, ar: &mut Builder<W>) {
707         let mut manifest = String::new();
708 
709         if !self.cargo_features.is_empty() {
710             manifest.push_str(&format!(
711                 "cargo-features = {}\n\n",
712                 toml::to_string(&self.cargo_features).unwrap()
713             ));
714         }
715 
716         manifest.push_str(&format!(
717             r#"
718             [package]
719             name = "{}"
720             version = "{}"
721             authors = []
722         "#,
723             self.name, self.vers
724         ));
725 
726         if let Some(version) = &self.rust_version {
727             manifest.push_str(&format!("rust-version = \"{}\"", version));
728         }
729 
730         for dep in self.deps.iter() {
731             let target = match dep.target {
732                 None => String::new(),
733                 Some(ref s) => format!("target.'{}'.", s),
734             };
735             let kind = match &dep.kind[..] {
736                 "build" => "build-",
737                 "dev" => "dev-",
738                 _ => "",
739             };
740             manifest.push_str(&format!(
741                 r#"
742                 [{}{}dependencies.{}]
743                 version = "{}"
744             "#,
745                 target, kind, dep.name, dep.vers
746             ));
747             if let Some(registry) = &dep.registry {
748                 assert_eq!(registry, "alternative");
749                 manifest.push_str(&format!("registry-index = \"{}\"", alt_registry_url()));
750             }
751         }
752         if self.proc_macro {
753             manifest.push_str("[lib]\nproc-macro = true\n");
754         }
755 
756         self.append(ar, "Cargo.toml", DEFAULT_MODE, &manifest);
757     }
758 
append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &str)759     fn append<W: Write>(&self, ar: &mut Builder<W>, file: &str, mode: u32, contents: &str) {
760         self.append_raw(
761             ar,
762             &format!("{}-{}/{}", self.name, self.vers, file),
763             mode,
764             contents,
765         );
766     }
767 
append_raw<W: Write>(&self, ar: &mut Builder<W>, path: &str, mode: u32, contents: &str)768     fn append_raw<W: Write>(&self, ar: &mut Builder<W>, path: &str, mode: u32, contents: &str) {
769         let mut header = Header::new_ustar();
770         header.set_size(contents.len() as u64);
771         t!(header.set_path(path));
772         header.set_mode(mode);
773         header.set_cksum();
774         t!(ar.append(&header, contents.as_bytes()));
775     }
776 
777     /// Returns the path to the compressed package file.
archive_dst(&self) -> PathBuf778     pub fn archive_dst(&self) -> PathBuf {
779         if self.local {
780             registry_path().join(format!("{}-{}.crate", self.name, self.vers))
781         } else if self.alternative {
782             alt_dl_path()
783                 .join(&self.name)
784                 .join(&self.vers)
785                 .join(&format!("{}-{}.crate", self.name, self.vers))
786         } else {
787             dl_path().join(&self.name).join(&self.vers).join("download")
788         }
789     }
790 }
791 
cksum(s: &[u8]) -> String792 pub fn cksum(s: &[u8]) -> String {
793     Sha256::new().update(s).finish_hex()
794 }
795 
796 impl Dependency {
new(name: &str, vers: &str) -> Dependency797     pub fn new(name: &str, vers: &str) -> Dependency {
798         Dependency {
799             name: name.to_string(),
800             vers: vers.to_string(),
801             kind: "normal".to_string(),
802             target: None,
803             features: Vec::new(),
804             package: None,
805             optional: false,
806             registry: None,
807         }
808     }
809 
810     /// Changes this to `[build-dependencies]`.
build(&mut self) -> &mut Self811     pub fn build(&mut self) -> &mut Self {
812         self.kind = "build".to_string();
813         self
814     }
815 
816     /// Changes this to `[dev-dependencies]`.
dev(&mut self) -> &mut Self817     pub fn dev(&mut self) -> &mut Self {
818         self.kind = "dev".to_string();
819         self
820     }
821 
822     /// Changes this to `[target.$target.dependencies]`.
target(&mut self, target: &str) -> &mut Self823     pub fn target(&mut self, target: &str) -> &mut Self {
824         self.target = Some(target.to_string());
825         self
826     }
827 
828     /// Adds `registry = $registry` to this dependency.
registry(&mut self, registry: &str) -> &mut Self829     pub fn registry(&mut self, registry: &str) -> &mut Self {
830         self.registry = Some(registry.to_string());
831         self
832     }
833 
834     /// Adds `features = [ ... ]` to this dependency.
enable_features(&mut self, features: &[&str]) -> &mut Self835     pub fn enable_features(&mut self, features: &[&str]) -> &mut Self {
836         self.features.extend(features.iter().map(|s| s.to_string()));
837         self
838     }
839 
840     /// Adds `package = ...` to this dependency.
package(&mut self, pkg: &str) -> &mut Self841     pub fn package(&mut self, pkg: &str) -> &mut Self {
842         self.package = Some(pkg.to_string());
843         self
844     }
845 
846     /// Changes this to an optional dependency.
optional(&mut self, optional: bool) -> &mut Self847     pub fn optional(&mut self, optional: bool) -> &mut Self {
848         self.optional = optional;
849         self
850     }
851 }
852 
split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>)853 fn split_index_features(mut features: FeatureMap) -> (FeatureMap, Option<FeatureMap>) {
854     let mut features2 = FeatureMap::new();
855     for (feat, values) in features.iter_mut() {
856         if values
857             .iter()
858             .any(|value| value.starts_with("dep:") || value.contains("?/"))
859         {
860             let new_values = values.drain(..).collect();
861             features2.insert(feat.clone(), new_values);
862         }
863     }
864     if features2.is_empty() {
865         (features, None)
866     } else {
867         (features, Some(features2))
868     }
869 }
870