1 //! Tests for weak-dep-features.
2
3 use super::features2::switch_to_resolver_2;
4 use cargo_test_support::paths::CargoPathExt;
5 use cargo_test_support::registry::{Dependency, Package};
6 use cargo_test_support::{project, publish};
7 use std::fmt::Write;
8
9 // Helper to create lib.rs files that check features.
require(enabled_features: &[&str], disabled_features: &[&str]) -> String10 fn require(enabled_features: &[&str], disabled_features: &[&str]) -> String {
11 let mut s = String::new();
12 for feature in enabled_features {
13 write!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");\n",
14 feature=feature).unwrap();
15 }
16 for feature in disabled_features {
17 write!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");\n",
18 feature=feature).unwrap();
19 }
20 s
21 }
22
23 #[cargo_test]
gated()24 fn gated() {
25 // Need -Z weak-dep-features to enable.
26 Package::new("bar", "1.0.0").feature("feat", &[]).publish();
27 let p = project()
28 .file(
29 "Cargo.toml",
30 r#"
31 [package]
32 name = "foo"
33 version = "0.1.0"
34
35 [dependencies]
36 bar = { version = "1.0", optional = true }
37
38 [features]
39 f1 = ["bar?/feat"]
40 "#,
41 )
42 .file("src/lib.rs", "")
43 .build();
44 p.cargo("check")
45 .with_status(101)
46 .with_stderr(
47 "\
48 error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
49
50 Caused by:
51 optional dependency features with `?` syntax are only allowed on the nightly \
52 channel and requires the `-Z weak-dep-features` flag on the command line
53 Feature `f1` had feature value `bar?/feat`.
54 ",
55 )
56 .run();
57 }
58
59 #[cargo_test]
dependency_gate_ignored()60 fn dependency_gate_ignored() {
61 // Dependencies with ? features in the registry are ignored in the
62 // registry if not on nightly.
63 Package::new("baz", "1.0.0").feature("feat", &[]).publish();
64 Package::new("bar", "1.0.0")
65 .add_dep(Dependency::new("baz", "1.0").optional(true))
66 .feature("feat", &["baz?/feat"])
67 .publish();
68 let p = project()
69 .file(
70 "Cargo.toml",
71 r#"
72 [package]
73 name = "foo"
74 version = "0.1.0"
75
76 [dependencies]
77 bar = "1.0"
78 "#,
79 )
80 .file("src/lib.rs", "")
81 .build();
82
83 p.cargo("check")
84 .masquerade_as_nightly_cargo()
85 .with_status(101)
86 .with_stderr(
87 "\
88 [UPDATING] [..]
89 [ERROR] no matching package named `bar` found
90 location searched: registry `https://github.com/rust-lang/crates.io-index`
91 required by package `foo v0.1.0 ([..]/foo)`
92 ",
93 )
94 .run();
95
96 // Publish a version without the ? feature, it should ignore 1.0.0
97 // an use this instead.
98 Package::new("bar", "1.0.1")
99 .add_dep(Dependency::new("baz", "1.0").optional(true))
100 .feature("feat", &["baz"])
101 .publish();
102 p.cargo("check")
103 .masquerade_as_nightly_cargo()
104 .with_stderr(
105 "\
106 [UPDATING] [..]
107 [DOWNLOADING] crates ...
108 [DOWNLOADED] bar [..]
109 [CHECKING] bar v1.0.1
110 [CHECKING] foo v0.1.0 [..]
111 [FINISHED] [..]
112 ",
113 )
114 .run();
115 }
116
117 #[cargo_test]
simple()118 fn simple() {
119 Package::new("bar", "1.0.0")
120 .feature("feat", &[])
121 .file("src/lib.rs", &require(&["feat"], &[]))
122 .publish();
123 let p = project()
124 .file(
125 "Cargo.toml",
126 r#"
127 [package]
128 name = "foo"
129 version = "0.1.0"
130
131 [dependencies]
132 bar = { version = "1.0", optional = true }
133
134 [features]
135 f1 = ["bar?/feat"]
136 "#,
137 )
138 .file("src/lib.rs", &require(&["f1"], &[]))
139 .build();
140
141 // It's a bit unfortunate that this has to download `bar`, but avoiding
142 // that is extremely difficult.
143 p.cargo("check -Z weak-dep-features --features f1")
144 .masquerade_as_nightly_cargo()
145 .with_stderr(
146 "\
147 [UPDATING] [..]
148 [DOWNLOADING] crates ...
149 [DOWNLOADED] bar v1.0.0 [..]
150 [CHECKING] foo v0.1.0 [..]
151 [FINISHED] [..]
152 ",
153 )
154 .run();
155
156 p.cargo("check -Z weak-dep-features --features f1,bar")
157 .masquerade_as_nightly_cargo()
158 .with_stderr(
159 "\
160 [CHECKING] bar v1.0.0
161 [CHECKING] foo v0.1.0 [..]
162 [FINISHED] [..]
163 ",
164 )
165 .run();
166 }
167
168 #[cargo_test]
deferred()169 fn deferred() {
170 // A complex chain that requires deferring enabling the feature due to
171 // another dependency getting enabled.
172 Package::new("bar", "1.0.0")
173 .feature("feat", &[])
174 .file("src/lib.rs", &require(&["feat"], &[]))
175 .publish();
176 Package::new("dep", "1.0.0")
177 .add_dep(Dependency::new("bar", "1.0").optional(true))
178 .feature("feat", &["bar?/feat"])
179 .publish();
180 Package::new("bar_activator", "1.0.0")
181 .feature_dep("dep", "1.0", &["bar"])
182 .publish();
183 let p = project()
184 .file(
185 "Cargo.toml",
186 r#"
187 [package]
188 name = "foo"
189 version = "0.1.0"
190
191 [dependencies]
192 dep = { version = "1.0", features = ["feat"] }
193 bar_activator = "1.0"
194 "#,
195 )
196 .file("src/lib.rs", "")
197 .build();
198
199 p.cargo("check -Z weak-dep-features")
200 .masquerade_as_nightly_cargo()
201 .with_stderr(
202 "\
203 [UPDATING] [..]
204 [DOWNLOADING] crates ...
205 [DOWNLOADED] dep v1.0.0 [..]
206 [DOWNLOADED] bar_activator v1.0.0 [..]
207 [DOWNLOADED] bar v1.0.0 [..]
208 [CHECKING] bar v1.0.0
209 [CHECKING] dep v1.0.0
210 [CHECKING] bar_activator v1.0.0
211 [CHECKING] foo v0.1.0 [..]
212 [FINISHED] [..]
213 ",
214 )
215 .run();
216 }
217
218 #[cargo_test]
not_optional_dep()219 fn not_optional_dep() {
220 // Attempt to use dep_name?/feat where dep_name is not optional.
221 Package::new("dep", "1.0.0").feature("feat", &[]).publish();
222
223 let p = project()
224 .file(
225 "Cargo.toml",
226 r#"
227 [package]
228 name = "foo"
229 version = "0.1.0"
230
231 [dependencies]
232 dep = "1.0"
233
234 [features]
235 feat = ["dep?/feat"]
236 "#,
237 )
238 .file("src/lib.rs", "")
239 .build();
240
241 p.cargo("check -Z weak-dep-features")
242 .masquerade_as_nightly_cargo()
243 .with_status(101)
244 .with_stderr("\
245 error: failed to parse manifest at `[ROOT]/foo/Cargo.toml`
246
247 Caused by:
248 feature `feat` includes `dep?/feat` with a `?`, but `dep` is not an optional dependency
249 A non-optional dependency of the same name is defined; consider removing the `?` or changing the dependency to be optional
250 ")
251 .run();
252 }
253
254 #[cargo_test]
optional_cli_syntax()255 fn optional_cli_syntax() {
256 // --features bar?/feat
257 Package::new("bar", "1.0.0")
258 .feature("feat", &[])
259 .file("src/lib.rs", &require(&["feat"], &[]))
260 .publish();
261
262 let p = project()
263 .file(
264 "Cargo.toml",
265 r#"
266 [package]
267 name = "foo"
268 version = "0.1.0"
269
270 [dependencies]
271 bar = { version = "1.0", optional = true }
272 "#,
273 )
274 .file("src/lib.rs", "")
275 .build();
276
277 // Does not build bar.
278 p.cargo("check --features bar?/feat -Z weak-dep-features")
279 .masquerade_as_nightly_cargo()
280 .with_stderr(
281 "\
282 [UPDATING] [..]
283 [DOWNLOADING] crates ...
284 [DOWNLOADED] bar v1.0.0 [..]
285 [CHECKING] foo v0.1.0 [..]
286 [FINISHED] [..]
287 ",
288 )
289 .run();
290
291 // Builds bar.
292 p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
293 .masquerade_as_nightly_cargo()
294 .with_stderr(
295 "\
296 [CHECKING] bar v1.0.0
297 [CHECKING] foo v0.1.0 [..]
298 [FINISHED] [..]
299 ",
300 )
301 .run();
302
303 eprintln!("check V2 resolver");
304 switch_to_resolver_2(&p);
305 p.build_dir().rm_rf();
306 // Does not build bar.
307 p.cargo("check --features bar?/feat -Z weak-dep-features")
308 .masquerade_as_nightly_cargo()
309 .with_stderr(
310 "\
311 [CHECKING] foo v0.1.0 [..]
312 [FINISHED] [..]
313 ",
314 )
315 .run();
316
317 // Builds bar.
318 p.cargo("check --features bar?/feat,bar -Z weak-dep-features")
319 .masquerade_as_nightly_cargo()
320 .with_stderr(
321 "\
322 [CHECKING] bar v1.0.0
323 [CHECKING] foo v0.1.0 [..]
324 [FINISHED] [..]
325 ",
326 )
327 .run();
328 }
329
330 #[cargo_test]
required_features()331 fn required_features() {
332 // required-features doesn't allow ?
333 Package::new("bar", "1.0.0").feature("feat", &[]).publish();
334
335 let p = project()
336 .file(
337 "Cargo.toml",
338 r#"
339 [package]
340 name = "foo"
341 version = "0.1.0"
342
343 [dependencies]
344 bar = { version = "1.0", optional = true }
345
346 [[bin]]
347 name = "foo"
348 required-features = ["bar?/feat"]
349 "#,
350 )
351 .file("src/main.rs", "fn main() {}")
352 .build();
353
354 p.cargo("check -Z weak-dep-features")
355 .masquerade_as_nightly_cargo()
356 .with_status(101)
357 .with_stderr(
358 "\
359 [UPDATING] [..]
360 [ERROR] invalid feature `bar?/feat` in required-features of target `foo`: \
361 optional dependency with `?` is not allowed in required-features
362 ",
363 )
364 .run();
365 }
366
367 #[cargo_test]
weak_with_host_decouple()368 fn weak_with_host_decouple() {
369 // -Z weak-opt-features with new resolver
370 //
371 // foo v0.1.0
372 // └── common v1.0.0
373 // └── bar v1.0.0 <-- does not have `feat` enabled
374 // [build-dependencies]
375 // └── bar_activator v1.0.0
376 // └── common v1.0.0
377 // └── bar v1.0.0 <-- does have `feat` enabled
378 Package::new("bar", "1.0.0")
379 .feature("feat", &[])
380 .file(
381 "src/lib.rs",
382 r#"
383 pub fn feat() -> bool {
384 cfg!(feature = "feat")
385 }
386 "#,
387 )
388 .publish();
389
390 Package::new("common", "1.0.0")
391 .add_dep(Dependency::new("bar", "1.0").optional(true))
392 .feature("feat", &["bar?/feat"])
393 .file(
394 "src/lib.rs",
395 r#"
396 #[cfg(feature = "bar")]
397 pub fn feat() -> bool { bar::feat() }
398 #[cfg(not(feature = "bar"))]
399 pub fn feat() -> bool { false }
400 "#,
401 )
402 .publish();
403
404 Package::new("bar_activator", "1.0.0")
405 .feature_dep("common", "1.0", &["bar", "feat"])
406 .file(
407 "src/lib.rs",
408 r#"
409 pub fn feat() -> bool {
410 common::feat()
411 }
412 "#,
413 )
414 .publish();
415
416 let p = project()
417 .file(
418 "Cargo.toml",
419 r#"
420 [package]
421 name = "foo"
422 version = "0.1.0"
423 resolver = "2"
424
425 [dependencies]
426 common = { version = "1.0", features = ["feat"] }
427
428 [build-dependencies]
429 bar_activator = "1.0"
430 "#,
431 )
432 .file(
433 "src/main.rs",
434 r#"
435 fn main() {
436 assert!(!common::feat());
437 }
438 "#,
439 )
440 .file(
441 "build.rs",
442 r#"
443 fn main() {
444 assert!(bar_activator::feat());
445 }
446 "#,
447 )
448 .build();
449
450 p.cargo("run -Z weak-dep-features")
451 .masquerade_as_nightly_cargo()
452 .with_stderr(
453 "\
454 [UPDATING] [..]
455 [DOWNLOADING] crates ...
456 [DOWNLOADED] [..]
457 [DOWNLOADED] [..]
458 [DOWNLOADED] [..]
459 [COMPILING] bar v1.0.0
460 [COMPILING] common v1.0.0
461 [COMPILING] bar_activator v1.0.0
462 [COMPILING] foo v0.1.0 [..]
463 [FINISHED] [..]
464 [RUNNING] `target/debug/foo[EXE]`
465 ",
466 )
467 .run();
468 }
469
470 #[cargo_test]
deferred_with_namespaced()471 fn deferred_with_namespaced() {
472 // Interaction with -Z namespaced-features using dep: syntax.
473 //
474 // `bar` is deferred with bar?/feat
475 // `bar2` is deferred with dep:bar2?/feat
476 Package::new("bar", "1.0.0")
477 .feature("feat", &[])
478 .file("src/lib.rs", &require(&["feat"], &[]))
479 .publish();
480 Package::new("bar2", "1.0.0")
481 .feature("feat", &[])
482 .file("src/lib.rs", &require(&["feat"], &[]))
483 .publish();
484 Package::new("bar_includer", "1.0.0")
485 .add_dep(Dependency::new("bar", "1.0").optional(true))
486 .add_dep(Dependency::new("bar2", "1.0").optional(true))
487 .feature("feat", &["bar?/feat", "dep:bar2?/feat"])
488 .feature("feat2", &["dep:bar2"])
489 .file("src/lib.rs", &require(&["bar"], &["bar2"]))
490 .publish();
491 Package::new("bar_activator", "1.0.0")
492 .feature_dep("bar_includer", "1.0", &["bar", "feat2"])
493 .publish();
494 let p = project()
495 .file(
496 "Cargo.toml",
497 r#"
498 [package]
499 name = "foo"
500 version = "0.1.0"
501
502 [dependencies]
503 bar_includer = { version = "1.0", features = ["feat"] }
504 bar_activator = "1.0"
505 "#,
506 )
507 .file("src/lib.rs", "")
508 .build();
509
510 p.cargo("check -Z weak-dep-features -Z namespaced-features")
511 .masquerade_as_nightly_cargo()
512 .with_stderr_unordered(
513 "\
514 [UPDATING] [..]
515 [DOWNLOADING] crates ...
516 [DOWNLOADED] [..]
517 [DOWNLOADED] [..]
518 [DOWNLOADED] [..]
519 [DOWNLOADED] [..]
520 [CHECKING] bar v1.0.0
521 [CHECKING] bar2 v1.0.0
522 [CHECKING] bar_includer v1.0.0
523 [CHECKING] bar_activator v1.0.0
524 [CHECKING] foo v0.1.0 [..]
525 [FINISHED] [..]
526 ",
527 )
528 .run();
529 }
530
531 #[cargo_test]
tree()532 fn tree() {
533 Package::new("bar", "1.0.0")
534 .feature("feat", &[])
535 .file("src/lib.rs", &require(&["feat"], &[]))
536 .publish();
537 let p = project()
538 .file(
539 "Cargo.toml",
540 r#"
541 [package]
542 name = "foo"
543 version = "0.1.0"
544
545 [dependencies]
546 bar = { version = "1.0", optional = true }
547
548 [features]
549 f1 = ["bar?/feat"]
550 "#,
551 )
552 .file("src/lib.rs", &require(&["f1"], &[]))
553 .build();
554
555 p.cargo("tree -Z weak-dep-features --features f1")
556 .masquerade_as_nightly_cargo()
557 .with_stdout("foo v0.1.0 ([ROOT]/foo)")
558 .run();
559
560 p.cargo("tree -Z weak-dep-features --features f1,bar")
561 .masquerade_as_nightly_cargo()
562 .with_stdout(
563 "\
564 foo v0.1.0 ([ROOT]/foo)
565 └── bar v1.0.0
566 ",
567 )
568 .run();
569
570 p.cargo("tree -Z weak-dep-features --features f1,bar -e features")
571 .masquerade_as_nightly_cargo()
572 .with_stdout(
573 "\
574 foo v0.1.0 ([ROOT]/foo)
575 └── bar feature \"default\"
576 └── bar v1.0.0
577 ",
578 )
579 .run();
580
581 p.cargo("tree -Z weak-dep-features --features f1,bar -e features -i bar")
582 .masquerade_as_nightly_cargo()
583 .with_stdout(
584 "\
585 bar v1.0.0
586 ├── bar feature \"default\"
587 │ └── foo v0.1.0 ([ROOT]/foo)
588 │ ├── foo feature \"bar\" (command-line)
589 │ │ └── foo feature \"f1\" (command-line)
590 │ ├── foo feature \"default\" (command-line)
591 │ └── foo feature \"f1\" (command-line)
592 └── bar feature \"feat\"
593 └── foo feature \"f1\" (command-line)
594 ",
595 )
596 .run();
597
598 p.cargo("tree -Z weak-dep-features -e features --features bar?/feat")
599 .masquerade_as_nightly_cargo()
600 .with_stdout("foo v0.1.0 ([ROOT]/foo)")
601 .run();
602
603 // This is a little strange in that it produces no output.
604 // Maybe `cargo tree` should print a note about why?
605 p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat")
606 .masquerade_as_nightly_cargo()
607 .with_stdout("")
608 .run();
609
610 p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat,bar")
611 .masquerade_as_nightly_cargo()
612 .with_stdout(
613 "\
614 bar v1.0.0
615 ├── bar feature \"default\"
616 │ └── foo v0.1.0 ([ROOT]/foo)
617 │ ├── foo feature \"bar\" (command-line)
618 │ └── foo feature \"default\" (command-line)
619 └── bar feature \"feat\" (command-line)
620 ",
621 )
622 .run();
623 }
624
625 #[cargo_test]
publish()626 fn publish() {
627 // Publish behavior with /? syntax.
628 Package::new("bar", "1.0.0").feature("feat", &[]).publish();
629 let p = project()
630 .file(
631 "Cargo.toml",
632 r#"
633 [package]
634 name = "foo"
635 version = "0.1.0"
636 description = "foo"
637 license = "MIT"
638 homepage = "https://example.com/"
639
640 [dependencies]
641 bar = { version = "1.0", optional = true }
642
643 [features]
644 feat1 = []
645 feat2 = ["bar?/feat"]
646 "#,
647 )
648 .file("src/lib.rs", "")
649 .build();
650
651 p.cargo("publish --token sekrit -Z weak-dep-features")
652 .masquerade_as_nightly_cargo()
653 .with_stderr(
654 "\
655 [UPDATING] [..]
656 [PACKAGING] foo v0.1.0 [..]
657 [VERIFYING] foo v0.1.0 [..]
658 [COMPILING] foo v0.1.0 [..]
659 [FINISHED] [..]
660 [UPLOADING] foo v0.1.0 [..]
661 ",
662 )
663 .run();
664
665 publish::validate_upload_with_contents(
666 r#"
667 {
668 "authors": [],
669 "badges": {},
670 "categories": [],
671 "deps": [
672 {
673 "default_features": true,
674 "features": [],
675 "kind": "normal",
676 "name": "bar",
677 "optional": true,
678 "registry": "https://github.com/rust-lang/crates.io-index",
679 "target": null,
680 "version_req": "^1.0"
681 }
682 ],
683 "description": "foo",
684 "documentation": null,
685 "features": {
686 "feat1": [],
687 "feat2": ["bar?/feat"]
688 },
689 "homepage": "https://example.com/",
690 "keywords": [],
691 "license": "MIT",
692 "license_file": null,
693 "links": null,
694 "name": "foo",
695 "readme": null,
696 "readme_file": null,
697 "repository": null,
698 "vers": "0.1.0"
699 }
700 "#,
701 "foo-0.1.0.crate",
702 &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
703 &[(
704 "Cargo.toml",
705 r#"[..]
706 [package]
707 name = "foo"
708 version = "0.1.0"
709 description = "foo"
710 homepage = "https://example.com/"
711 license = "MIT"
712 [dependencies.bar]
713 version = "1.0"
714 optional = true
715
716 [features]
717 feat1 = []
718 feat2 = ["bar?/feat"]
719 "#,
720 )],
721 );
722 }
723