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 writeln!(s, "#[cfg(not(feature=\"{feature}\"))] compile_error!(\"expected feature {feature} to be enabled\");",
14 feature=feature).unwrap();
15 }
16 for feature in disabled_features {
17 writeln!(s, "#[cfg(feature=\"{feature}\")] compile_error!(\"did not expect feature {feature} to be enabled\");",
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 `crates-io`
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 // and 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]
weak_namespaced()471 fn weak_namespaced() {
472 // Behavior with a dep: dependency.
473 Package::new("bar", "1.0.0")
474 .feature("feat", &[])
475 .file("src/lib.rs", &require(&["feat"], &[]))
476 .publish();
477 let p = project()
478 .file(
479 "Cargo.toml",
480 r#"
481 [package]
482 name = "foo"
483 version = "0.1.0"
484
485 [dependencies]
486 bar = { version = "1.0", optional = true }
487
488 [features]
489 f1 = ["bar?/feat"]
490 f2 = ["dep:bar"]
491 "#,
492 )
493 .file("src/lib.rs", &require(&["f1"], &["f2", "bar"]))
494 .build();
495
496 p.cargo("check -Z weak-dep-features -Z namespaced-features --features f1")
497 .masquerade_as_nightly_cargo()
498 .with_stderr(
499 "\
500 [UPDATING] [..]
501 [DOWNLOADING] crates ...
502 [DOWNLOADED] bar v1.0.0 [..]
503 [CHECKING] foo v0.1.0 [..]
504 [FINISHED] [..]
505 ",
506 )
507 .run();
508
509 p.cargo("tree -Z weak-dep-features -Z namespaced-features -f")
510 .arg("{p} feats:{f}")
511 .masquerade_as_nightly_cargo()
512 .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:")
513 .run();
514
515 p.cargo("tree -Z weak-dep-features -Z namespaced-features --features f1 -f")
516 .arg("{p} feats:{f}")
517 .masquerade_as_nightly_cargo()
518 .with_stdout("foo v0.1.0 ([ROOT]/foo) feats:f1")
519 .run();
520
521 p.cargo("tree -Z weak-dep-features -Z namespaced-features --features f1,f2 -f")
522 .arg("{p} feats:{f}")
523 .masquerade_as_nightly_cargo()
524 .with_stdout(
525 "\
526 foo v0.1.0 ([ROOT]/foo) feats:f1,f2
527 └── bar v1.0.0 feats:feat
528 ",
529 )
530 .run();
531
532 // "bar" remains not-a-feature
533 p.change_file("src/lib.rs", &require(&["f1", "f2"], &["bar"]));
534
535 p.cargo("check -Z weak-dep-features -Z namespaced-features --features f1,f2")
536 .masquerade_as_nightly_cargo()
537 .with_stderr(
538 "\
539 [CHECKING] bar v1.0.0
540 [CHECKING] foo v0.1.0 [..]
541 [FINISHED] [..]
542 ",
543 )
544 .run();
545 }
546
547 #[cargo_test]
tree()548 fn tree() {
549 Package::new("bar", "1.0.0")
550 .feature("feat", &[])
551 .file("src/lib.rs", &require(&["feat"], &[]))
552 .publish();
553 let p = project()
554 .file(
555 "Cargo.toml",
556 r#"
557 [package]
558 name = "foo"
559 version = "0.1.0"
560
561 [dependencies]
562 bar = { version = "1.0", optional = true }
563
564 [features]
565 f1 = ["bar?/feat"]
566 "#,
567 )
568 .file("src/lib.rs", &require(&["f1"], &[]))
569 .build();
570
571 p.cargo("tree -Z weak-dep-features --features f1")
572 .masquerade_as_nightly_cargo()
573 .with_stdout("foo v0.1.0 ([ROOT]/foo)")
574 .run();
575
576 p.cargo("tree -Z weak-dep-features --features f1,bar")
577 .masquerade_as_nightly_cargo()
578 .with_stdout(
579 "\
580 foo v0.1.0 ([ROOT]/foo)
581 └── bar v1.0.0
582 ",
583 )
584 .run();
585
586 p.cargo("tree -Z weak-dep-features --features f1,bar -e features")
587 .masquerade_as_nightly_cargo()
588 .with_stdout(
589 "\
590 foo v0.1.0 ([ROOT]/foo)
591 └── bar feature \"default\"
592 └── bar v1.0.0
593 ",
594 )
595 .run();
596
597 p.cargo("tree -Z weak-dep-features --features f1,bar -e features -i bar")
598 .masquerade_as_nightly_cargo()
599 .with_stdout(
600 "\
601 bar v1.0.0
602 ├── bar feature \"default\"
603 │ └── foo v0.1.0 ([ROOT]/foo)
604 │ ├── foo feature \"bar\" (command-line)
605 │ ├── foo feature \"default\" (command-line)
606 │ └── foo feature \"f1\" (command-line)
607 └── bar feature \"feat\"
608 └── foo feature \"f1\" (command-line)
609 ",
610 )
611 .run();
612
613 p.cargo("tree -Z weak-dep-features -e features --features bar?/feat")
614 .masquerade_as_nightly_cargo()
615 .with_stdout("foo v0.1.0 ([ROOT]/foo)")
616 .run();
617
618 // This is a little strange in that it produces no output.
619 // Maybe `cargo tree` should print a note about why?
620 p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat")
621 .masquerade_as_nightly_cargo()
622 .with_stdout("")
623 .run();
624
625 p.cargo("tree -Z weak-dep-features -e features -i bar --features bar?/feat,bar")
626 .masquerade_as_nightly_cargo()
627 .with_stdout(
628 "\
629 bar v1.0.0
630 ├── bar feature \"default\"
631 │ └── foo v0.1.0 ([ROOT]/foo)
632 │ ├── foo feature \"bar\" (command-line)
633 │ └── foo feature \"default\" (command-line)
634 └── bar feature \"feat\" (command-line)
635 ",
636 )
637 .run();
638 }
639
640 #[cargo_test]
publish()641 fn publish() {
642 // Publish behavior with /? syntax.
643 Package::new("bar", "1.0.0").feature("feat", &[]).publish();
644 let p = project()
645 .file(
646 "Cargo.toml",
647 r#"
648 [package]
649 name = "foo"
650 version = "0.1.0"
651 description = "foo"
652 license = "MIT"
653 homepage = "https://example.com/"
654
655 [dependencies]
656 bar = { version = "1.0", optional = true }
657
658 [features]
659 feat1 = []
660 feat2 = ["bar?/feat"]
661 "#,
662 )
663 .file("src/lib.rs", "")
664 .build();
665
666 p.cargo("publish --token sekrit -Z weak-dep-features")
667 .masquerade_as_nightly_cargo()
668 .with_stderr(
669 "\
670 [UPDATING] [..]
671 [PACKAGING] foo v0.1.0 [..]
672 [VERIFYING] foo v0.1.0 [..]
673 [COMPILING] foo v0.1.0 [..]
674 [FINISHED] [..]
675 [UPLOADING] foo v0.1.0 [..]
676 ",
677 )
678 .run();
679
680 publish::validate_upload_with_contents(
681 r#"
682 {
683 "authors": [],
684 "badges": {},
685 "categories": [],
686 "deps": [
687 {
688 "default_features": true,
689 "features": [],
690 "kind": "normal",
691 "name": "bar",
692 "optional": true,
693 "registry": "https://github.com/rust-lang/crates.io-index",
694 "target": null,
695 "version_req": "^1.0"
696 }
697 ],
698 "description": "foo",
699 "documentation": null,
700 "features": {
701 "feat1": [],
702 "feat2": ["bar?/feat"]
703 },
704 "homepage": "https://example.com/",
705 "keywords": [],
706 "license": "MIT",
707 "license_file": null,
708 "links": null,
709 "name": "foo",
710 "readme": null,
711 "readme_file": null,
712 "repository": null,
713 "vers": "0.1.0"
714 }
715 "#,
716 "foo-0.1.0.crate",
717 &["Cargo.toml", "Cargo.toml.orig", "src/lib.rs"],
718 &[(
719 "Cargo.toml",
720 &format!(
721 r#"{}
722 [package]
723 name = "foo"
724 version = "0.1.0"
725 description = "foo"
726 homepage = "https://example.com/"
727 license = "MIT"
728 [dependencies.bar]
729 version = "1.0"
730 optional = true
731
732 [features]
733 feat1 = []
734 feat2 = ["bar?/feat"]
735 "#,
736 cargo::core::package::MANIFEST_PREAMBLE
737 ),
738 )],
739 );
740 }
741