cargo/src/cargo/ops/registry.rs

774 lines
25 KiB
Rust
Raw Normal View History

use std::collections::BTreeMap;
use std::fs::{self, File};
2018-12-20 01:40:01 +00:00
use std::io::{self, BufRead};
2015-02-06 07:27:53 +00:00
use std::iter::repeat;
use std::str;
2016-05-18 17:01:40 +00:00
use std::time::Duration;
use std::{cmp, env};
use crates_io::{NewCrate, NewCrateDependency, Registry};
use curl::easy::{Easy, InfoType, SslOpt};
use log::{log, Level};
use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
2018-12-06 19:17:36 +00:00
use crate::core::dependency::Kind;
use crate::core::manifest::ManifestMetadata;
use crate::core::source::Source;
use crate::core::{Package, SourceId, Workspace};
use crate::ops;
use crate::sources::{RegistrySource, SourceConfigMap};
use crate::util::config::{self, Config};
use crate::util::errors::{CargoResult, CargoResultExt};
use crate::util::important_paths::find_root_manifest_for_wd;
use crate::util::ToUrl;
use crate::util::{paths, validate_package_name};
2018-12-06 19:17:36 +00:00
use crate::version;
pub struct RegistryConfig {
pub index: Option<String>,
pub token: Option<String>,
}
pub struct PublishOpts<'cfg> {
pub config: &'cfg Config,
pub token: Option<String>,
pub index: Option<String>,
pub verify: bool,
pub allow_dirty: bool,
2016-07-13 08:38:22 +00:00
pub jobs: Option<u32>,
pub target: Option<String>,
Add --dry-run option to publish sub-command Squashed commit of the following: commit deed1d7b99c1cd142f7782d3b3b782d949e1f71f Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:35:01 2016 +1000 Remove --dry-run and --no-verify mutual exclusion commit 8a91fcf2a1aa3ba682fee67bb5b3e7c2c2cce8ef Merge: 0c0d057 970535d Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:30:38 2016 +1000 Merge remote-tracking branch 'upstream/master' into publish_dry_run commit 0c0d0572533599b3c0e42797a6014edf480f1dc2 Author: Wesley Moore <wes@wezm.net> Date: Tue Jul 12 08:03:15 2016 +1000 Improve grammar in --dry-run option commit a17c1bf6f41f016cafdcb8cfc58ccbe34d54fbb8 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:17:41 2016 +1000 Add test for passing no-verify and dry-run to publish commit 284810cca5df3268596f18700c0247de2f621c98 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:51:38 2016 +1000 Add test for publish --dry-run commit 8514e47fbce61c20b227815887a377c25d17d004 Merge: 2b061c5 ef07b81 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 08:27:10 2016 +1000 Merge branch 'publish_dry_run' of github.com:JustAPerson/cargo into publish_dry_run commit ef07b81617df855328c34365b28049cd9742946c Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 23:11:51 2015 -0500 Improve publish `--dry-run` Catch a few more errors by aborting midway through transmit(). commit 0686fb0bf92a09bcbd41e15e23ff03a0763c5d08 Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 14:38:58 2015 -0500 Teach publish the `--dry-run` flag Closes #1332
2016-07-17 23:43:57 +00:00
pub dry_run: bool,
pub registry: Option<String>,
pub features: Vec<String>,
pub all_features: bool,
pub no_default_features: bool,
}
pub fn publish(ws: &Workspace<'_>, opts: &PublishOpts<'_>) -> CargoResult<()> {
let pkg = ws.current()?;
2018-02-27 02:02:39 +00:00
if let Some(ref allowed_registries) = *pkg.publish() {
2017-11-08 05:42:48 +00:00
if !match opts.registry {
Some(ref registry) => allowed_registries.contains(registry),
None => false,
} {
2018-12-12 21:20:44 +00:00
failure::bail!(
2018-03-14 15:17:44 +00:00
"some crates cannot be published.\n\
`{}` is marked as unpublishable",
pkg.name()
);
}
}
2017-09-24 14:26:37 +00:00
if !pkg.manifest().patch().is_empty() {
2018-12-12 21:20:44 +00:00
failure::bail!("published crates cannot contain [patch] sections");
}
2018-03-14 15:17:44 +00:00
let (mut registry, reg_id) = registry(
opts.config,
opts.token.clone(),
opts.index.clone(),
opts.registry.clone(),
2018-12-20 01:40:01 +00:00
true,
2018-03-14 15:17:44 +00:00
)?;
verify_dependencies(pkg, &registry, reg_id)?;
// Prepare a tarball, with a non-surpressable warning if metadata
// is missing since this is being put online.
2018-03-14 15:17:44 +00:00
let tarball = ops::package(
ws,
&ops::PackageOpts {
config: opts.config,
verify: opts.verify,
list: false,
check_metadata: true,
allow_dirty: opts.allow_dirty,
target: opts.target.clone(),
jobs: opts.jobs,
features: opts.features.clone(),
all_features: opts.all_features,
no_default_features: opts.no_default_features,
2018-03-14 15:17:44 +00:00
},
)?
.unwrap();
// Upload said tarball to the specified destination
2018-03-14 15:17:44 +00:00
opts.config
.shell()
.status("Uploading", pkg.package_id().to_string())?;
transmit(
opts.config,
pkg,
tarball.file(),
&mut registry,
reg_id,
2018-03-14 15:17:44 +00:00
opts.dry_run,
)?;
Ok(())
}
fn verify_dependencies(
pkg: &Package,
registry: &Registry,
registry_src: SourceId,
) -> CargoResult<()> {
for dep in pkg.dependencies().iter() {
if dep.source_id().is_path() {
if !dep.specified_req() {
2018-12-12 21:20:44 +00:00
failure::bail!(
2018-03-14 15:17:44 +00:00
"all path dependencies must have a version specified \
when publishing.\ndependency `{}` does not specify \
a version",
dep.package_name()
2018-03-14 15:17:44 +00:00
)
}
} else if dep.source_id() != registry_src {
if dep.source_id().is_registry() {
// Block requests to send to crates.io with alt-registry deps.
// This extra hostname check is mostly to assist with testing,
// but also prevents someone using `--index` to specify
// something that points to crates.io.
let is_crates_io = registry
.host()
.to_url()
.map(|u| u.host_str() == Some("crates.io"))
.unwrap_or(false);
if registry_src.is_default_registry() || is_crates_io {
2018-12-12 21:20:44 +00:00
failure::bail!("crates cannot be published to crates.io with dependencies sourced from other\n\
registries either publish `{}` on crates.io or pull it into this repository\n\
and specify it with a path and version\n\
(crate `{}` is pulled from {})",
dep.package_name(),
dep.package_name(),
dep.source_id());
}
} else {
2018-12-12 21:20:44 +00:00
failure::bail!(
"crates cannot be published with dependencies sourced from \
a repository\neither publish `{}` as its own crate and \
specify a version as a dependency or pull it into this \
2018-03-14 15:17:44 +00:00
repository and specify it with a path and version\n(crate `{}` has \
repository path `{}`)",
dep.package_name(),
dep.package_name(),
2018-03-14 15:17:44 +00:00
dep.source_id()
);
}
}
}
Ok(())
}
2018-03-14 15:17:44 +00:00
fn transmit(
config: &Config,
pkg: &Package,
tarball: &File,
registry: &mut Registry,
registry_id: SourceId,
2018-03-14 15:17:44 +00:00
dry_run: bool,
) -> CargoResult<()> {
let deps = pkg
.dependencies()
2018-03-14 15:17:44 +00:00
.iter()
.map(|dep| {
// If the dependency is from a different registry, then include the
// registry in the dependency.
let dep_registry_id = match dep.registry_id() {
Some(id) => id,
None => SourceId::crates_io(config)?,
2018-03-14 15:17:44 +00:00
};
// In the index and Web API, None means "from the same registry"
// whereas in Cargo.toml, it means "from crates.io".
2018-03-14 15:17:44 +00:00
let dep_registry = if dep_registry_id != registry_id {
Some(dep_registry_id.url().to_string())
} else {
None
};
Ok(NewCrateDependency {
optional: dep.is_optional(),
default_features: dep.uses_default_features(),
name: dep.package_name().to_string(),
2018-04-04 17:42:18 +00:00
features: dep.features().iter().map(|s| s.to_string()).collect(),
2018-03-14 15:17:44 +00:00
version_req: dep.version_req().to_string(),
target: dep.platform().map(|s| s.to_string()),
kind: match dep.kind() {
Kind::Normal => "normal",
Kind::Build => "build",
Kind::Development => "dev",
}
.to_string(),
2018-03-14 15:17:44 +00:00
registry: dep_registry,
Fix publishing renamed dependencies to crates.io This commit fixes publishing crates which contain locally renamed dependencies to crates.io. Previously this lack of information meant that although we could resolve the crate graph correctly it wouldn't work well with respect to optional features and optional dependencies. The fix here is to persist this information into the registry about the crate being renamed in `Cargo.toml`, allowing Cargo to correctly deduce feature names as it does when it has `Cargo.toml` locally. A dual side of this commit is to publish this information to crates.io. We'll want to merge the associated PR (link to come soon) on crates.io first and make sure that's deployed as well before we stabilize the crate renaming feature. The index format is updated as well as part of this change. The `name` key for dependencies is now unconditionally what was written in `Cargo.toml` as the left-hand-side of the dependency specification. In other words this is the raw crate name, but only for the local crate. A new key, `package`, is added to dependencies (and it can be `None`). This key indicates the crates.io package is being linked against, an represents the `package` key in `Cargo.toml`. It's important to consider the interaction with older Cargo implementations which don't support the `package` key in the index. In these situations older Cargo binaries will likely fail to resolve entirely as the renamed name is unlikely to exist on crates.io. For example the `futures` crate now has an optional dependency with the name `futures01` which depends on an older version of `futures` on crates.io. The string `futures01` will be listed in the index under the `"name"` key, but no `futures01` crate exists on crates.io so older Cargo will generate an error. If the crate does exist on crates.io, then even weirder error messages will likely result. Closes #5962
2018-09-07 16:37:06 +00:00
explicit_name_in_toml: dep.explicit_name_in_toml().map(|s| s.to_string()),
2018-03-14 15:17:44 +00:00
})
})
2018-03-14 15:17:44 +00:00
.collect::<CargoResult<Vec<NewCrateDependency>>>()?;
let manifest = pkg.manifest();
let ManifestMetadata {
2018-03-14 15:17:44 +00:00
ref authors,
ref description,
ref homepage,
ref documentation,
ref keywords,
ref readme,
ref repository,
ref license,
ref license_file,
ref categories,
ref badges,
ref links,
} = *manifest.metadata();
let readme_content = match *readme {
Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?),
None => None,
};
2017-02-18 12:01:10 +00:00
if let Some(ref file) = *license_file {
if fs::metadata(&pkg.root().join(file)).is_err() {
2018-12-12 21:20:44 +00:00
failure::bail!("the license file `{}` does not exist", file)
}
}
Add --dry-run option to publish sub-command Squashed commit of the following: commit deed1d7b99c1cd142f7782d3b3b782d949e1f71f Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:35:01 2016 +1000 Remove --dry-run and --no-verify mutual exclusion commit 8a91fcf2a1aa3ba682fee67bb5b3e7c2c2cce8ef Merge: 0c0d057 970535d Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:30:38 2016 +1000 Merge remote-tracking branch 'upstream/master' into publish_dry_run commit 0c0d0572533599b3c0e42797a6014edf480f1dc2 Author: Wesley Moore <wes@wezm.net> Date: Tue Jul 12 08:03:15 2016 +1000 Improve grammar in --dry-run option commit a17c1bf6f41f016cafdcb8cfc58ccbe34d54fbb8 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:17:41 2016 +1000 Add test for passing no-verify and dry-run to publish commit 284810cca5df3268596f18700c0247de2f621c98 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:51:38 2016 +1000 Add test for publish --dry-run commit 8514e47fbce61c20b227815887a377c25d17d004 Merge: 2b061c5 ef07b81 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 08:27:10 2016 +1000 Merge branch 'publish_dry_run' of github.com:JustAPerson/cargo into publish_dry_run commit ef07b81617df855328c34365b28049cd9742946c Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 23:11:51 2015 -0500 Improve publish `--dry-run` Catch a few more errors by aborting midway through transmit(). commit 0686fb0bf92a09bcbd41e15e23ff03a0763c5d08 Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 14:38:58 2015 -0500 Teach publish the `--dry-run` flag Closes #1332
2016-07-17 23:43:57 +00:00
// Do not upload if performing a dry run
if dry_run {
config.shell().warn("aborting upload due to dry run")?;
Add --dry-run option to publish sub-command Squashed commit of the following: commit deed1d7b99c1cd142f7782d3b3b782d949e1f71f Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:35:01 2016 +1000 Remove --dry-run and --no-verify mutual exclusion commit 8a91fcf2a1aa3ba682fee67bb5b3e7c2c2cce8ef Merge: 0c0d057 970535d Author: Wesley Moore <wes@wezm.net> Date: Fri Jul 15 13:30:38 2016 +1000 Merge remote-tracking branch 'upstream/master' into publish_dry_run commit 0c0d0572533599b3c0e42797a6014edf480f1dc2 Author: Wesley Moore <wes@wezm.net> Date: Tue Jul 12 08:03:15 2016 +1000 Improve grammar in --dry-run option commit a17c1bf6f41f016cafdcb8cfc58ccbe34d54fbb8 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:17:41 2016 +1000 Add test for passing no-verify and dry-run to publish commit 284810cca5df3268596f18700c0247de2f621c98 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 14:51:38 2016 +1000 Add test for publish --dry-run commit 8514e47fbce61c20b227815887a377c25d17d004 Merge: 2b061c5 ef07b81 Author: Wesley Moore <wes@wezm.net> Date: Mon Jul 11 08:27:10 2016 +1000 Merge branch 'publish_dry_run' of github.com:JustAPerson/cargo into publish_dry_run commit ef07b81617df855328c34365b28049cd9742946c Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 23:11:51 2015 -0500 Improve publish `--dry-run` Catch a few more errors by aborting midway through transmit(). commit 0686fb0bf92a09bcbd41e15e23ff03a0763c5d08 Author: Jason Priest <jpriest128@gmail.com> Date: Tue Jun 9 14:38:58 2015 -0500 Teach publish the `--dry-run` flag Closes #1332
2016-07-17 23:43:57 +00:00
return Ok(());
}
let summary = pkg.summary();
let string_features = summary
.features()
.iter()
.map(|(feat, values)| {
(
feat.to_string(),
values.iter().map(|fv| fv.to_string(&summary)).collect(),
)
})
.collect::<BTreeMap<String, Vec<String>>>();
2018-03-14 15:17:44 +00:00
let publish = registry.publish(
&NewCrate {
name: pkg.name().to_string(),
vers: pkg.version().to_string(),
deps,
features: string_features,
2018-03-14 15:17:44 +00:00
authors: authors.clone(),
description: description.clone(),
homepage: homepage.clone(),
documentation: documentation.clone(),
keywords: keywords.clone(),
categories: categories.clone(),
readme: readme_content,
readme_file: readme.clone(),
repository: repository.clone(),
license: license.clone(),
license_file: license_file.clone(),
badges: badges.clone(),
links: links.clone(),
},
tarball,
);
match publish {
Ok(warnings) => {
if !warnings.invalid_categories.is_empty() {
2018-03-14 15:17:44 +00:00
let msg = format!(
"\
the following are not valid category slugs and were \
ignored: {}. Please see https://crates.io/category_slugs \
for the list of all category slugs. \
",
warnings.invalid_categories.join(", ")
);
config.shell().warn(&msg)?;
}
if !warnings.invalid_badges.is_empty() {
2018-03-14 15:17:44 +00:00
let msg = format!(
"\
the following are not valid badges and were ignored: {}. \
Either the badge type specified is unknown or a required \
attribute is missing. Please see \
2019-01-30 20:34:37 +00:00
https://doc.rust-lang.org/cargo/reference/manifest.html \
2018-03-14 15:17:44 +00:00
for valid badge types and their required attributes.",
warnings.invalid_badges.join(", ")
);
config.shell().warn(&msg)?;
}
if !warnings.other.is_empty() {
for msg in warnings.other {
config.shell().warn(&msg)?;
}
}
Ok(())
2018-03-14 15:17:44 +00:00
}
2018-02-06 21:49:50 +00:00
Err(e) => Err(e),
}
}
2018-03-14 15:17:44 +00:00
pub fn registry_configuration(
config: &Config,
registry: Option<String>,
) -> CargoResult<RegistryConfig> {
let (index, token) = match registry {
Some(registry) => {
validate_package_name(&registry, "registry name", "")?;
(
Some(config.get_registry_index(&registry)?.to_string()),
config
.get_string(&format!("registries.{}.token", registry))?
.map(|p| p.val),
)
}
None => {
// Checking out for default index and token
2018-03-14 15:17:44 +00:00
(
config.get_string("registry.index")?.map(|p| p.val),
config.get_string("registry.token")?.map(|p| p.val),
)
}
};
2018-03-14 15:17:44 +00:00
Ok(RegistryConfig { index, token })
}
2018-03-14 15:17:44 +00:00
pub fn registry(
config: &Config,
token: Option<String>,
index: Option<String>,
registry: Option<String>,
2018-12-20 01:40:01 +00:00
force_update: bool,
2018-03-14 15:17:44 +00:00
) -> CargoResult<(Registry, SourceId)> {
// Parse all configuration options
let RegistryConfig {
token: token_config,
index: index_config,
} = registry_configuration(config, registry.clone())?;
let token = token.or(token_config);
let sid = get_source_id(config, index_config.or(index), registry)?;
let api_host = {
let mut src = RegistrySource::remote(sid, config);
2018-12-20 01:40:01 +00:00
// Only update the index if the config is not available or `force` is set.
let cfg = src.config();
let cfg = if force_update || cfg.is_err() {
src.update()
.chain_err(|| format!("failed to update {}", sid))?;
cfg.or_else(|_| src.config())?
} else {
cfg.unwrap()
};
cfg.and_then(|cfg| cfg.api)
.ok_or_else(|| failure::format_err!("{} does not support API commands", sid))?
};
let handle = http_handle(config)?;
Ok((Registry::new_handle(api_host, token, handle), sid))
}
/// Create a new HTTP handle with appropriate global configuration for cargo.
2016-05-18 17:01:40 +00:00
pub fn http_handle(config: &Config) -> CargoResult<Easy> {
let (mut handle, timeout) = http_handle_and_timeout(config)?;
timeout.configure(&mut handle)?;
Ok(handle)
}
pub fn http_handle_and_timeout(config: &Config) -> CargoResult<(Easy, HttpTimeout)> {
if config.frozen() {
2018-12-12 21:20:44 +00:00
failure::bail!(
2018-03-14 15:17:44 +00:00
"attempting to make an HTTP request, but --frozen was \
specified"
)
Add flags to assert lock/cache behavior to Cargo If a lock file is generated and some equivalent of `cargo fetch` is run then Cargo shouldn't ever touch the network or modify `Cargo.lock` until any `Cargo.toml` later changes, but this often wants to be asserted in some build environments where it's a programmer error if Cargo attempts to access the network. The `--locked` flag added here will assert that `Cargo.lock` does not need to change to proceed. That is, if `Cargo.lock` would be modified (as it automatically is by default) this is turned into a hard error instead. This `--frozen` will not only assert that `Cargo.lock` doesn't change (the same behavior as `--locked`), but it will also will manually prevent Cargo from touching the network by ensuring that all network requests return an error. These flags can be used in environments where it is *expected* that no network access happens (or no lockfile changes happen) because it has been pre-arranged for Cargo to not happen. Examples of this include: * CI for projects want to pass `--locked` to ensure that `Cargo.lock` is up to date before changes are checked in. * Environments with vendored dependencies want to pass `--frozen` as touching the network indicates a programmer error that something wasn't vendored correctly. A crucial property of these two flags is that **they do not change the behavior of Cargo**. They are simply assertions at a few locations in Cargo to ensure that actions expected to not happen indeed don't happen. Some documentation has also been added to this effect. Closes #2111
2016-06-28 17:39:46 +00:00
}
if !config.network_allowed() {
2018-12-12 21:20:44 +00:00
failure::bail!("can't make HTTP request in the offline mode")
}
Add flags to assert lock/cache behavior to Cargo If a lock file is generated and some equivalent of `cargo fetch` is run then Cargo shouldn't ever touch the network or modify `Cargo.lock` until any `Cargo.toml` later changes, but this often wants to be asserted in some build environments where it's a programmer error if Cargo attempts to access the network. The `--locked` flag added here will assert that `Cargo.lock` does not need to change to proceed. That is, if `Cargo.lock` would be modified (as it automatically is by default) this is turned into a hard error instead. This `--frozen` will not only assert that `Cargo.lock` doesn't change (the same behavior as `--locked`), but it will also will manually prevent Cargo from touching the network by ensuring that all network requests return an error. These flags can be used in environments where it is *expected* that no network access happens (or no lockfile changes happen) because it has been pre-arranged for Cargo to not happen. Examples of this include: * CI for projects want to pass `--locked` to ensure that `Cargo.lock` is up to date before changes are checked in. * Environments with vendored dependencies want to pass `--frozen` as touching the network indicates a programmer error that something wasn't vendored correctly. A crucial property of these two flags is that **they do not change the behavior of Cargo**. They are simply assertions at a few locations in Cargo to ensure that actions expected to not happen indeed don't happen. Some documentation has also been added to this effect. Closes #2111
2016-06-28 17:39:46 +00:00
// The timeout option for libcurl by default times out the entire transfer,
// but we probably don't want this. Instead we only set timeouts for the
// connect phase as well as a "low speed" timeout so if we don't receive
// many bytes in a large-ish period of time then we time out.
2016-05-18 17:01:40 +00:00
let mut handle = Easy::new();
let timeout = configure_http_handle(config, &mut handle)?;
Ok((handle, timeout))
}
pub fn needs_custom_http_transport(config: &Config) -> CargoResult<bool> {
let proxy_exists = http_proxy_exists(config)?;
let timeout = HttpTimeout::new(config)?.is_non_default();
let cainfo = config.get_path("http.cainfo")?;
let check_revoke = config.get_bool("http.check-revoke")?;
let user_agent = config.get_string("http.user-agent")?;
Ok(proxy_exists
|| timeout
|| cainfo.is_some()
|| check_revoke.is_some()
|| user_agent.is_some())
}
/// Configure a libcurl http handle with the defaults options for Cargo
pub fn configure_http_handle(config: &Config, handle: &mut Easy) -> CargoResult<HttpTimeout> {
if let Some(proxy) = http_proxy(config)? {
handle.proxy(&proxy)?;
2016-05-18 17:01:40 +00:00
}
if let Some(cainfo) = config.get_path("http.cainfo")? {
handle.cainfo(&cainfo.val)?;
}
if let Some(check) = config.get_bool("http.check-revoke")? {
handle.ssl_options(SslOpt::new().no_revoke(!check.val))?;
}
if let Some(user_agent) = config.get_string("http.user-agent")? {
handle.useragent(&user_agent.val)?;
} else {
handle.useragent(&version().to_string())?;
}
if let Some(true) = config.get::<Option<bool>>("http.debug")? {
handle.verbose(true)?;
handle.debug_function(|kind, data| {
let (prefix, level) = match kind {
InfoType::Text => ("*", Level::Debug),
InfoType::HeaderIn => ("<", Level::Debug),
InfoType::HeaderOut => (">", Level::Debug),
InfoType::DataIn => ("{", Level::Trace),
InfoType::DataOut => ("}", Level::Trace),
InfoType::SslDataIn | InfoType::SslDataOut => return,
_ => return,
};
match str::from_utf8(data) {
Ok(s) => {
for line in s.lines() {
log!(level, "http-debug: {} {}", prefix, line);
}
}
Err(_) => {
log!(
level,
"http-debug: {} ({} bytes of data)",
prefix,
data.len()
);
}
}
})?;
}
HttpTimeout::new(config)
}
#[must_use]
pub struct HttpTimeout {
pub dur: Duration,
pub low_speed_limit: u32,
}
impl HttpTimeout {
pub fn new(config: &Config) -> CargoResult<HttpTimeout> {
let low_speed_limit = config
.get::<Option<u32>>("http.low-speed-limit")?
.unwrap_or(10);
let seconds = config
.get::<Option<u64>>("http.timeout")?
.or_else(|| env::var("HTTP_TIMEOUT").ok().and_then(|s| s.parse().ok()))
.unwrap_or(30);
Ok(HttpTimeout {
dur: Duration::new(seconds, 0),
low_speed_limit,
})
}
fn is_non_default(&self) -> bool {
self.dur != Duration::new(30, 0) || self.low_speed_limit != 10
}
pub fn configure(&self, handle: &mut Easy) -> CargoResult<()> {
// The timeout option for libcurl by default times out the entire
// transfer, but we probably don't want this. Instead we only set
// timeouts for the connect phase as well as a "low speed" timeout so
// if we don't receive many bytes in a large-ish period of time then we
// time out.
handle.connect_timeout(self.dur)?;
handle.low_speed_time(self.dur)?;
handle.low_speed_limit(self.low_speed_limit)?;
Ok(())
}
}
/// Find an explicit HTTP proxy if one is available.
///
/// Favor cargo's `http.proxy`, then git's `http.proxy`. Proxies specified
/// via environment variables are picked up by libcurl.
fn http_proxy(config: &Config) -> CargoResult<Option<String>> {
2017-02-18 12:01:10 +00:00
if let Some(s) = config.get_string("http.proxy")? {
2018-03-14 15:17:44 +00:00
return Ok(Some(s.val));
}
2017-02-18 12:01:10 +00:00
if let Ok(cfg) = git2::Config::open_default() {
if let Ok(s) = cfg.get_str("http.proxy") {
2018-03-14 15:17:44 +00:00
return Ok(Some(s.to_string()));
}
}
Ok(None)
}
/// Determine if an http proxy exists.
///
/// Checks the following for existence, in order:
///
/// * cargo's `http.proxy`
/// * git's `http.proxy`
2017-09-24 14:26:37 +00:00
/// * `http_proxy` env var
/// * `HTTP_PROXY` env var
/// * `https_proxy` env var
/// * `HTTPS_PROXY` env var
fn http_proxy_exists(config: &Config) -> CargoResult<bool> {
if http_proxy(config)?.is_some() {
Ok(true)
} else {
2018-03-14 15:17:44 +00:00
Ok(["http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY"]
.iter()
.any(|v| env::var(v).is_ok()))
}
}
2018-12-20 01:40:01 +00:00
pub fn registry_login(
config: &Config,
token: Option<String>,
reg: Option<String>,
) -> CargoResult<()> {
let (registry, _) = registry(config, token.clone(), None, reg.clone(), false)?;
let token = match token {
Some(token) => token,
None => {
println!(
"please visit {}/me and paste the API Token below",
registry.host()
);
let mut line = String::new();
let input = io::stdin();
input
.lock()
.read_line(&mut line)
.chain_err(|| "failed to read stdin")
.map_err(failure::Error::from)?;
line.trim().to_string()
}
};
let RegistryConfig {
2018-03-14 15:17:44 +00:00
token: old_token, ..
2018-12-20 01:40:01 +00:00
} = registry_configuration(config, reg.clone())?;
if let Some(old_token) = old_token {
if old_token == token {
2018-12-20 01:40:01 +00:00
config.shell().status("Login", "already logged in")?;
return Ok(());
}
}
2018-12-20 01:40:01 +00:00
config::save_credentials(config, token, reg.clone())?;
config.shell().status(
"Login",
format!(
"token for `{}` saved",
reg.as_ref().map_or("crates.io", String::as_str)
),
)?;
Ok(())
}
pub struct OwnersOptions {
pub krate: Option<String>,
pub token: Option<String>,
pub index: Option<String>,
pub to_add: Option<Vec<String>>,
pub to_remove: Option<Vec<String>>,
pub list: bool,
pub registry: Option<String>,
}
pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> {
let name = match opts.krate {
Some(ref name) => name.clone(),
None => {
let manifest_path = find_root_manifest_for_wd(config.cwd())?;
let ws = Workspace::new(&manifest_path, config)?;
ws.current()?.package_id().name().to_string()
}
};
2018-03-14 15:17:44 +00:00
let (mut registry, _) = registry(
config,
opts.token.clone(),
opts.index.clone(),
opts.registry.clone(),
2018-12-20 01:40:01 +00:00
true,
2018-03-14 15:17:44 +00:00
)?;
2017-02-18 12:01:10 +00:00
if let Some(ref v) = opts.to_add {
let v = v.iter().map(|s| &s[..]).collect::<Vec<_>>();
2018-12-12 21:20:44 +00:00
let msg = registry.add_owners(&name, &v).map_err(|e| {
failure::format_err!("failed to invite owners to crate {}: {}", name, e)
})?;
config.shell().status("Owner", msg)?;
}
2017-02-18 12:01:10 +00:00
if let Some(ref v) = opts.to_remove {
let v = v.iter().map(|s| &s[..]).collect::<Vec<_>>();
2018-03-14 15:17:44 +00:00
config
.shell()
.status("Owner", format!("removing {:?} from crate {}", v, name))?;
registry
.remove_owners(&name, &v)
.chain_err(|| format!("failed to remove owners from crate {}", name))?;
}
if opts.list {
2018-03-14 15:17:44 +00:00
let owners = registry
.list_owners(&name)
.chain_err(|| format!("failed to list owners of crate {}", name))?;
for owner in owners.iter() {
print!("{}", owner.login);
match (owner.name.as_ref(), owner.email.as_ref()) {
(Some(name), Some(email)) => println!(" ({} <{}>)", name, email),
2018-03-14 15:17:44 +00:00
(Some(s), None) | (None, Some(s)) => println!(" ({})", s),
2018-02-06 21:49:50 +00:00
(None, None) => println!(),
}
}
}
Ok(())
}
2018-03-14 15:17:44 +00:00
pub fn yank(
config: &Config,
krate: Option<String>,
version: Option<String>,
token: Option<String>,
index: Option<String>,
undo: bool,
reg: Option<String>,
) -> CargoResult<()> {
let name = match krate {
Some(name) => name,
None => {
let manifest_path = find_root_manifest_for_wd(config.cwd())?;
let ws = Workspace::new(&manifest_path, config)?;
ws.current()?.package_id().name().to_string()
}
};
let version = match version {
Some(v) => v,
2018-12-12 21:20:44 +00:00
None => failure::bail!("a version must be specified to yank"),
};
2018-12-20 01:40:01 +00:00
let (mut registry, _) = registry(config, token, index, reg, true)?;
if undo {
2018-03-14 15:17:44 +00:00
config
.shell()
.status("Unyank", format!("{}:{}", name, version))?;
registry
.unyank(&name, &version)
.chain_err(|| "failed to undo a yank")?;
} else {
2018-03-14 15:17:44 +00:00
config
.shell()
.status("Yank", format!("{}:{}", name, version))?;
registry
.yank(&name, &version)
.chain_err(|| "failed to yank")?;
}
Ok(())
}
2014-11-22 14:36:14 +00:00
fn get_source_id(
config: &Config,
index: Option<String>,
reg: Option<String>,
) -> CargoResult<SourceId> {
match (reg, index) {
(Some(r), _) => SourceId::alt_registry(config, &r),
(_, Some(i)) => SourceId::for_registry(&i.to_url()?),
_ => {
let map = SourceConfigMap::new(config)?;
let src = map.load(SourceId::crates_io(config)?)?;
Ok(src.replaced_source_id())
}
}
}
2018-03-14 15:17:44 +00:00
pub fn search(
query: &str,
config: &Config,
index: Option<String>,
limit: u32,
reg: Option<String>,
) -> CargoResult<()> {
fn truncate_with_ellipsis(s: &str, max_width: usize) -> String {
// We should truncate at grapheme-boundary and compute character-widths,
// yet the dependencies on unicode-segmentation and unicode-width are
// not worth it.
let mut chars = s.chars();
let mut prefix = (&mut chars).take(max_width - 1).collect::<String>();
if chars.next().is_some() {
prefix.push('…');
2014-11-22 14:36:14 +00:00
}
prefix
2014-11-22 14:36:14 +00:00
}
2018-12-20 01:40:01 +00:00
let (mut registry, _) = registry(config, None, index, reg, false)?;
2018-03-14 15:17:44 +00:00
let (crates, total_crates) = registry
.search(query, limit)
.chain_err(|| "failed to retrieve search results from the registry")?;
2014-11-22 14:36:14 +00:00
2018-03-14 15:17:44 +00:00
let names = crates
.iter()
.map(|krate| format!("{} = \"{}\"", krate.name, krate.max_version))
.collect::<Vec<String>>();
2018-03-14 15:17:44 +00:00
let description_margin = names.iter().map(|s| s.len() + 4).max().unwrap_or_default();
let description_length = cmp::max(80, 128 - description_margin);
2018-03-14 15:17:44 +00:00
let descriptions = crates.iter().map(|krate| {
krate
.description
.as_ref()
.map(|desc| truncate_with_ellipsis(&desc.replace("\n", " "), description_length))
});
2014-11-22 14:36:14 +00:00
for (name, description) in names.into_iter().zip(descriptions) {
2014-11-22 14:36:14 +00:00
let line = match description {
Some(desc) => {
2018-03-14 15:17:44 +00:00
let space = repeat(' ')
.take(description_margin - name.len())
.collect::<String>();
name + &space + "# " + &desc
2014-11-22 14:36:14 +00:00
}
2018-03-14 15:17:44 +00:00
None => name,
2014-11-22 14:36:14 +00:00
};
println!("{}", line);
2014-11-22 14:36:14 +00:00
}
let search_max_limit = 100;
if total_crates > limit && limit < search_max_limit {
2018-03-14 15:17:44 +00:00
println!(
"... and {} crates more (use --limit N to see more)",
total_crates - limit
2018-03-14 15:17:44 +00:00
);
} else if total_crates > limit && limit >= search_max_limit {
2018-03-14 15:17:44 +00:00
println!(
2019-01-30 20:34:37 +00:00
"... and {} crates more (go to https://crates.io/search?q={} to see more)",
total_crates - limit,
2018-03-14 15:17:44 +00:00
percent_encode(query.as_bytes(), QUERY_ENCODE_SET)
);
}
2014-11-22 14:36:14 +00:00
Ok(())
}