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(®istry_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(®istry_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