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