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