]> git.proxmox.com Git - cargo.git/blame - src/cargo/ops/registry.rs
More lint cleaning
[cargo.git] / src / cargo / ops / registry.rs
CommitLineData
ee5e24ff 1use std::env;
a9fd1c2c 2use std::fs::{self, File};
ee5e24ff 3use std::iter::repeat;
f7d213e7 4use std::time::Duration;
9fba127e 5
923f21c3 6use curl::easy::{Easy, SslOpt};
9fba127e
AC
7use git2;
8use registry::{Registry, NewCrate, NewCrateDependency};
9
134edb20
JE
10use url::percent_encoding::{percent_encode, QUERY_ENCODE_SET};
11
ae2b5980 12use version;
9fba127e 13use core::source::Source;
58ddb28a 14use core::{Package, SourceId, Workspace};
3b5994e7 15use core::dependency::Kind;
9fba127e
AC
16use core::manifest::ManifestMetadata;
17use ops;
0d9eac6e 18use sources::{RegistrySource};
30cc3784 19use util::config::{self, Config};
8524efb0 20use util::paths;
c7de4859 21use util::ToUrl;
c7de4859 22use util::errors::{CargoError, CargoResult, CargoResultExt};
09d62a65 23use util::important_paths::find_root_manifest_for_wd;
9fba127e
AC
24
25pub struct RegistryConfig {
26 pub index: Option<String>,
27 pub token: Option<String>,
28}
29
088d14ad
AC
30pub struct PublishOpts<'cfg> {
31 pub config: &'cfg Config,
32 pub token: Option<String>,
33 pub index: Option<String>,
34 pub verify: bool,
35 pub allow_dirty: bool,
f8ee283a 36 pub jobs: Option<u32>,
1c593879 37 pub target: Option<&'cfg str>,
c05a5b4a 38 pub dry_run: bool,
088d14ad
AC
39}
40
58ddb28a 41pub fn publish(ws: &Workspace, opts: &PublishOpts) -> CargoResult<()> {
82655b46 42 let pkg = ws.current()?;
9fba127e 43
d28f8bb0
JL
44 if !pkg.publish() {
45 bail!("some crates cannot be published.\n\
46 `{}` is marked as unpublishable", pkg.name());
47 }
23591fe5 48 if !pkg.manifest().patch().is_empty() {
61a3c68b
AC
49 bail!("published crates cannot contain [patch] sections");
50 }
d28f8bb0 51
82655b46 52 let (mut registry, reg_id) = registry(opts.config,
61a3c68b
AC
53 opts.token.clone(),
54 opts.index.clone())?;
c5611a32 55 verify_dependencies(pkg, &reg_id)?;
9fba127e 56
5db1316a
HW
57 // Prepare a tarball, with a non-surpressable warning if metadata
58 // is missing since this is being put online.
82655b46 59 let tarball = ops::package(ws, &ops::PackageOpts {
088d14ad
AC
60 config: opts.config,
61 verify: opts.verify,
62 list: false,
63 check_metadata: true,
64 allow_dirty: opts.allow_dirty,
1c593879 65 target: opts.target,
f8ee283a 66 jobs: opts.jobs,
82655b46 67 })?.unwrap();
9fba127e
AC
68
69 // Upload said tarball to the specified destination
82655b46 70 opts.config.shell().status("Uploading", pkg.package_id().to_string())?;
c5611a32 71 transmit(opts.config, pkg, tarball.file(), &mut registry, opts.dry_run)?;
9fba127e
AC
72
73 Ok(())
74}
75
76fn verify_dependencies(pkg: &Package, registry_src: &SourceId)
77 -> CargoResult<()> {
7a2facba
AC
78 for dep in pkg.dependencies().iter() {
79 if dep.source_id().is_path() {
0d7b5cc4 80 if !dep.specified_req() {
7ab18e3a
AC
81 bail!("all path dependencies must have a version specified \
82 when publishing.\ndependency `{}` does not specify \
83 a version", dep.name())
9fba127e 84 }
7a2facba 85 } else if dep.source_id() != registry_src {
450da94e
BM
86 bail!("crates cannot be published to crates.io with dependencies sourced from \
87 a repository\neither publish `{}` as its own crate on crates.io and \
88 specify a crates.io version as a dependency or pull it into this \
89 repository and specify it with a path and version\n(crate `{}` has \
90 repository path `{}`)", dep.name(), dep.name(), dep.source_id());
9fba127e
AC
91 }
92 }
93 Ok(())
94}
95
c05a5b4a
WM
96fn transmit(config: &Config,
97 pkg: &Package,
98 tarball: &File,
99 registry: &mut Registry,
100 dry_run: bool) -> CargoResult<()> {
7a2facba 101 let deps = pkg.dependencies().iter().map(|dep| {
9fba127e
AC
102 NewCrateDependency {
103 optional: dep.is_optional(),
104 default_features: dep.uses_default_features(),
7a2facba
AC
105 name: dep.name().to_string(),
106 features: dep.features().to_vec(),
107 version_req: dep.version_req().to_string(),
f5d786e0 108 target: dep.platform().map(|s| s.to_string()),
7a2facba 109 kind: match dep.kind() {
3b5994e7
AC
110 Kind::Normal => "normal",
111 Kind::Build => "build",
112 Kind::Development => "dev",
113 }.to_string(),
9fba127e
AC
114 }
115 }).collect::<Vec<NewCrateDependency>>();
7a2facba 116 let manifest = pkg.manifest();
9fba127e
AC
117 let ManifestMetadata {
118 ref authors, ref description, ref homepage, ref documentation,
5acb5f56 119 ref keywords, ref readme, ref repository, ref license, ref license_file,
f5f4c417 120 ref categories, ref badges,
7a2facba 121 } = *manifest.metadata();
9fba127e 122 let readme = match *readme {
82655b46 123 Some(ref readme) => Some(paths::read(&pkg.root().join(readme))?),
9fba127e
AC
124 None => None,
125 };
c5611a32
AB
126 if let Some(ref file) = *license_file {
127 if fs::metadata(&pkg.root().join(file)).is_err() {
128 bail!("the license file `{}` does not exist", file)
5acb5f56 129 }
5acb5f56 130 }
c05a5b4a
WM
131
132 // Do not upload if performing a dry run
133 if dry_run {
82655b46 134 config.shell().warn("aborting upload due to dry run")?;
c05a5b4a
WM
135 return Ok(());
136 }
137
154cc0aa 138 let publish = registry.publish(&NewCrate {
7a2facba
AC
139 name: pkg.name().to_string(),
140 vers: pkg.version().to_string(),
9fba127e 141 deps: deps,
7a2facba 142 features: pkg.summary().features().clone(),
9fba127e
AC
143 authors: authors.clone(),
144 description: description.clone(),
145 homepage: homepage.clone(),
146 documentation: documentation.clone(),
147 keywords: keywords.clone(),
0f01d9bd 148 categories: categories.clone(),
9fba127e
AC
149 readme: readme,
150 repository: repository.clone(),
151 license: license.clone(),
5acb5f56 152 license_file: license_file.clone(),
f5f4c417 153 badges: badges.clone(),
154cc0aa
CNG
154 }, tarball);
155
156 match publish {
f697b8c6
CNG
157 Ok(warnings) => {
158 if !warnings.invalid_categories.is_empty() {
154cc0aa
CNG
159 let msg = format!("\
160 the following are not valid category slugs and were \
161 ignored: {}. Please see https://crates.io/category_slugs \
162 for the list of all category slugs. \
f697b8c6 163 ", warnings.invalid_categories.join(", "));
154cc0aa
CNG
164 config.shell().warn(&msg)?;
165 }
f5f4c417
JG
166
167 if !warnings.invalid_badges.is_empty() {
168 let msg = format!("\
169 the following are not valid badges and were ignored: {}. \
170 Either the badge type specified is unknown or a required \
171 attribute is missing. Please see \
172 http://doc.crates.io/manifest.html#package-metadata \
173 for valid badge types and their required attributes.",
174 warnings.invalid_badges.join(", "));
175 config.shell().warn(&msg)?;
176 }
177
154cc0aa
CNG
178 Ok(())
179 },
c7de4859 180 Err(e) => Err(e.into()),
154cc0aa 181 }
9fba127e
AC
182}
183
aa17b61e
ED
184pub fn registry_configuration(config: &Config) -> CargoResult<RegistryConfig> {
185 let index = config.get_string("registry.index")?.map(|p| p.val);
186 let token = config.get_string("registry.token")?.map(|p| p.val);
187 Ok(RegistryConfig { index: index, token: token })
9fba127e
AC
188}
189
5d0cb3f2 190pub fn registry(config: &Config,
9fba127e
AC
191 token: Option<String>,
192 index: Option<String>) -> CargoResult<(Registry, SourceId)> {
193 // Parse all configuration options
aa17b61e
ED
194 let RegistryConfig {
195 token: token_config,
196 index: _index_config,
197 } = registry_configuration(config)?;
198 let token = token.or(token_config);
8214bb95 199 let sid = match index {
dc7422b6 200 Some(index) => SourceId::for_registry(&index.to_url()?)?,
82655b46 201 None => SourceId::crates_io(config)?,
8214bb95 202 };
9fba127e 203 let api_host = {
f1e26ed3 204 let mut src = RegistrySource::remote(&sid, config);
e95044e3 205 src.update().chain_err(|| {
c7de4859 206 format!("failed to update {}", sid)
82655b46
SG
207 })?;
208 (src.config()?).unwrap().api
9fba127e 209 };
82655b46 210 let handle = http_handle(config)?;
9fba127e
AC
211 Ok((Registry::new_handle(api_host, token, handle), sid))
212}
213
214/// Create a new HTTP handle with appropriate global configuration for cargo.
f7d213e7 215pub fn http_handle(config: &Config) -> CargoResult<Easy> {
a504f480
AC
216 if !config.network_allowed() {
217 bail!("attempting to make an HTTP request, but --frozen was \
218 specified")
219 }
220
923c2f2d
AC
221 // The timeout option for libcurl by default times out the entire transfer,
222 // but we probably don't want this. Instead we only set timeouts for the
223 // connect phase as well as a "low speed" timeout so if we don't receive
224 // many bytes in a large-ish period of time then we time out.
f7d213e7 225 let mut handle = Easy::new();
82655b46
SG
226 handle.connect_timeout(Duration::new(30, 0))?;
227 handle.low_speed_limit(10 /* bytes per second */)?;
228 handle.low_speed_time(Duration::new(30, 0))?;
775c900e 229 handle.useragent(&version().to_string())?;
82655b46
SG
230 if let Some(proxy) = http_proxy(config)? {
231 handle.proxy(&proxy)?;
f7d213e7 232 }
82655b46
SG
233 if let Some(cainfo) = config.get_path("http.cainfo")? {
234 handle.cainfo(&cainfo.val)?;
8e8a2924 235 }
923f21c3
AC
236 if let Some(check) = config.get_bool("http.check-revoke")? {
237 handle.ssl_options(SslOpt::new().no_revoke(!check.val))?;
238 }
82655b46
SG
239 if let Some(timeout) = http_timeout(config)? {
240 handle.connect_timeout(Duration::new(timeout as u64, 0))?;
241 handle.low_speed_time(Duration::new(timeout as u64, 0))?;
f7d213e7 242 }
0602940f 243 Ok(handle)
9fba127e
AC
244}
245
8eb28ad6 246/// Find an explicit HTTP proxy if one is available.
9fba127e 247///
8eb28ad6 248/// Favor cargo's `http.proxy`, then git's `http.proxy`. Proxies specified
249/// via environment variables are picked up by libcurl.
250fn http_proxy(config: &Config) -> CargoResult<Option<String>> {
c5611a32
AB
251 if let Some(s) = config.get_string("http.proxy")? {
252 return Ok(Some(s.val))
9fba127e 253 }
c5611a32
AB
254 if let Ok(cfg) = git2::Config::open_default() {
255 if let Ok(s) = cfg.get_str("http.proxy") {
256 return Ok(Some(s.to_string()))
9fba127e 257 }
9fba127e 258 }
8eb28ad6 259 Ok(None)
260}
261
262/// Determine if an http proxy exists.
263///
264/// Checks the following for existence, in order:
265///
266/// * cargo's `http.proxy`
267/// * git's `http.proxy`
23591fe5
LL
268/// * `http_proxy` env var
269/// * `HTTP_PROXY` env var
270/// * `https_proxy` env var
271/// * `HTTPS_PROXY` env var
8eb28ad6 272pub fn http_proxy_exists(config: &Config) -> CargoResult<bool> {
82655b46 273 if http_proxy(config)?.is_some() {
8eb28ad6 274 Ok(true)
275 } else {
276 Ok(["http_proxy", "HTTP_PROXY",
277 "https_proxy", "HTTPS_PROXY"].iter().any(|v| env::var(v).is_ok()))
278 }
9fba127e
AC
279}
280
0602940f 281pub fn http_timeout(config: &Config) -> CargoResult<Option<i64>> {
c5611a32
AB
282 if let Some(s) = config.get_i64("http.timeout")? {
283 return Ok(Some(s.val))
0602940f 284 }
1384050e 285 Ok(env::var("HTTP_TIMEOUT").ok().and_then(|s| s.parse().ok()))
0602940f
AC
286}
287
aa17b61e 288pub fn registry_login(config: &Config, token: String) -> CargoResult<()> {
23591fe5 289 let RegistryConfig { token: old_token, .. } = registry_configuration(config)?;
30cc3784
ED
290 if let Some(old_token) = old_token {
291 if old_token == token {
292 return Ok(());
293 }
9fba127e 294 }
9fba127e 295
aa17b61e 296 config::save_credentials(config, token)
9fba127e
AC
297}
298
a3538e25
AC
299pub struct OwnersOptions {
300 pub krate: Option<String>,
301 pub token: Option<String>,
302 pub index: Option<String>,
303 pub to_add: Option<Vec<String>>,
304 pub to_remove: Option<Vec<String>>,
305 pub list: bool,
306}
307
5d0cb3f2 308pub fn modify_owners(config: &Config, opts: &OwnersOptions) -> CargoResult<()> {
a3538e25
AC
309 let name = match opts.krate {
310 Some(ref name) => name.clone(),
9fba127e 311 None => {
82655b46
SG
312 let manifest_path = find_root_manifest_for_wd(None, config.cwd())?;
313 let pkg = Package::for_path(&manifest_path, config)?;
7a2facba 314 pkg.name().to_string()
9fba127e
AC
315 }
316 };
317
82655b46
SG
318 let (mut registry, _) = registry(config, opts.token.clone(),
319 opts.index.clone())?;
9fba127e 320
c5611a32
AB
321 if let Some(ref v) = opts.to_add {
322 let v = v.iter().map(|s| &s[..]).collect::<Vec<_>>();
323 config.shell().status("Owner", format!("adding {:?} to crate {}",
324 v, name))?;
325 registry.add_owners(&name, &v).map_err(|e| {
c7de4859 326 CargoError::from(format!("failed to add owners to crate {}: {}", name, e))
c5611a32 327 })?;
9fba127e
AC
328 }
329
c5611a32
AB
330 if let Some(ref v) = opts.to_remove {
331 let v = v.iter().map(|s| &s[..]).collect::<Vec<_>>();
332 config.shell().status("Owner", format!("removing {:?} from crate {}",
333 v, name))?;
334 registry.remove_owners(&name, &v).map_err(|e| {
c7de4859 335 CargoError::from(format!("failed to remove owners from crate {}: {}", name, e))
c5611a32 336 })?;
9fba127e
AC
337 }
338
a3538e25 339 if opts.list {
82655b46 340 let owners = registry.list_owners(&name).map_err(|e| {
c7de4859 341 CargoError::from(format!("failed to list owners of crate {}: {}", name, e))
82655b46 342 })?;
a3538e25
AC
343 for owner in owners.iter() {
344 print!("{}", owner.login);
345 match (owner.name.as_ref(), owner.email.as_ref()) {
346 (Some(name), Some(email)) => println!(" ({} <{}>)", name, email),
347 (Some(s), None) |
348 (None, Some(s)) => println!(" ({})", s),
349 (None, None) => println!(""),
350 }
351 }
352 }
353
9fba127e
AC
354 Ok(())
355}
356
5d0cb3f2 357pub fn yank(config: &Config,
9fba127e
AC
358 krate: Option<String>,
359 version: Option<String>,
360 token: Option<String>,
361 index: Option<String>,
362 undo: bool) -> CargoResult<()> {
363 let name = match krate {
364 Some(name) => name,
365 None => {
82655b46
SG
366 let manifest_path = find_root_manifest_for_wd(None, config.cwd())?;
367 let pkg = Package::for_path(&manifest_path, config)?;
7a2facba 368 pkg.name().to_string()
9fba127e
AC
369 }
370 };
371 let version = match version {
372 Some(v) => v,
7ab18e3a 373 None => bail!("a version must be specified to yank")
9fba127e
AC
374 };
375
82655b46 376 let (mut registry, _) = registry(config, token, index)?;
9fba127e
AC
377
378 if undo {
82655b46
SG
379 config.shell().status("Unyank", format!("{}:{}", name, version))?;
380 registry.unyank(&name, &version).map_err(|e| {
c7de4859 381 CargoError::from(format!("failed to undo a yank: {}", e))
82655b46 382 })?;
9fba127e 383 } else {
82655b46
SG
384 config.shell().status("Yank", format!("{}:{}", name, version))?;
385 registry.yank(&name, &version).map_err(|e| {
c7de4859 386 CargoError::from(format!("failed to yank: {}", e))
82655b46 387 })?;
9fba127e
AC
388 }
389
390 Ok(())
391}
0c25226b 392
53c9374c
JE
393pub fn search(query: &str,
394 config: &Config,
395 index: Option<String>,
396 limit: u8) -> CargoResult<()> {
55321111 397 fn truncate_with_ellipsis(s: &str, max_length: usize) -> String {
0c25226b
JB
398 if s.len() < max_length {
399 s.to_string()
400 } else {
55321111 401 format!("{}…", &s[..max_length - 1])
0c25226b
JB
402 }
403 }
404
82655b46
SG
405 let (mut registry, _) = registry(config, None, index)?;
406 let (crates, total_crates) = registry.search(query, limit).map_err(|e| {
c7de4859 407 CargoError::from(format!("failed to retrieve search results from the registry: {}", e))
82655b46 408 })?;
0c25226b
JB
409
410 let list_items = crates.iter()
411 .map(|krate| (
94aa4ae2 412 format!("{} = \"{}\"", krate.name, krate.max_version),
0c25226b 413 krate.description.as_ref().map(|desc|
25e537aa 414 truncate_with_ellipsis(&desc.replace("\n", " "), 128))
0c25226b
JB
415 ))
416 .collect::<Vec<_>>();
417 let description_margin = list_items.iter()
418 .map(|&(ref left, _)| left.len() + 4)
419 .max()
420 .unwrap_or(0);
421
422 for (name, description) in list_items.into_iter() {
423 let line = match description {
424 Some(desc) => {
31534136
AC
425 let space = repeat(' ').take(description_margin - name.len())
426 .collect::<String>();
8af9e95d 427 name + &space + "# " + &desc
0c25226b
JB
428 }
429 None => name
430 };
deda31fd 431 println!("{}", line);
0c25226b
JB
432 }
433
134edb20 434 let search_max_limit = 100;
23591fe5 435 if total_crates > u32::from(limit) && limit < search_max_limit {
deda31fd 436 println!("... and {} crates more (use --limit N to see more)",
23591fe5
LL
437 total_crates - u32::from(limit));
438 } else if total_crates > u32::from(limit) && limit >= search_max_limit {
deda31fd 439 println!("... and {} crates more (go to http://crates.io/search?q={} to see more)",
23591fe5 440 total_crates - u32::from(limit),
deda31fd 441 percent_encode(query.as_bytes(), QUERY_ENCODE_SET));
134edb20
JE
442 }
443
0c25226b
JB
444 Ok(())
445}