From db4943dbcc76bbcebd4b46b6c29d0cde60f1318a Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sat, 5 Nov 2016 18:52:38 -0700 Subject: [PATCH 1/3] Conditional dependencies. --- 0000-conditional_dependencies.md | 131 +++++++++++++++++++++++++++++++ 1 file changed, 131 insertions(+) create mode 100644 0000-conditional_dependencies.md diff --git a/0000-conditional_dependencies.md b/0000-conditional_dependencies.md new file mode 100644 index 00000000000..7eee525d544 --- /dev/null +++ b/0000-conditional_dependencies.md @@ -0,0 +1,131 @@ +- Feature Name: `conditional_dependencies` +- Start Date: 2016-11-05 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Support "conditional" dependencies, which will be used only if that dependency +is already present. Users will be able to write code to integrate with another +library (for example, serializations for their type) without introducing a +*necessary* dependency on that library for users who don't need it. + +# Motivation +[motivation]: #motivation + +Currently, a common pattern exists for libraries to have feature flags which +turn on a dependency. For example, if I have a library which defines a new +type, I might want users to be able to serialize it with `serde`, but I don't +want users who don't care about this functionality to have to add `serde` to +their dependency graph. So I add a `serialization` feature to my Cargo.toml, +and only if you turn on this feature do you get the feature for serialization. + +Ultimately, the source of this problem is the orphan rules - because only *I* +can define the impls for my type, I have to add this dependency. For every +commonly used trait defining library, I have to add a new feature, and all of +my clients have to turn on that feature. + +cargo features have many uses, but this particular pattern ends up being a lot +of fiddling for a very common use case. Instead of requiring users go through +the feature flag rigmarole, we can provide first class support for this +pattern through "conditional dependencies" - dependencies that are turned on +automatically if they were already present. + +# Detailed design +[design]: #detailed-design + +## Conditional dependencies in `Cargo.toml` + +When declaring a dependency in a Cargo.toml file, if that dependency is an +object, it can contain the `conditional` member, which is a boolean, similar to +the `optional` dependency. For example: + +```toml +[dependencies] +foobar = { version = "1.0.0", conditional = true } +``` + +Or: + +```toml +[dependencies.foobar] +version = "1.0.0" +conditional = true +``` + +A single dependency objecy cannot contain both a `conditional` key and an +`optional` key. + +When generating the dependency graph, cargo will not include dependencies +tagged conditional. It will then traverse the graph looking for conditional +dependencies; if a matching source for a conditional dependency (e.g. a +matching version number) is already present in the graph, cargo will +automatically add that dependency. + +## `#[cfg(dependency=)]` + +A new kind of cfg attribute will be added to rustc. The `dependency` attribute +will only be compiled when a certain dependency has been explicitly passed to +rustc using the --extern flag. + +Because cargo automatically passes dependencies explicitly with this command, +this code will be compiled only when cargo triggers the conditional dependency. + +## Convention for using conditional dependencies + +The above to features can be composed in any way, but to assist in adopting +this functionality, we can define an encouraged convention. + +If you want to add a new conditional dependency, you should add a top level +submodule (that is, mounted directly under lib, main, etc) with the same name +as the conditional dependency. This module will be tagged with the cfg, and the +extern crate declaration and all code that uses symbols from that crate will +go inside of that module. + +For example: + +```rust +/// lib.rs + +#[cfg(dependency=foobar] +mod foobar; +``` + +```rust +/// foobar.rs + +extern crate foobar; + +impl foobar::Bar for MyType { + ... +} +``` + +## Preventing cyclic dependencies on upload to crates.io + +When packaging and uploading a crate to crates.io, some strategy will be used +to ensure that its conditional dependencies could not introduce a cycle into +the dependency graph of your crate. + +## `cargo doc` compiles with all dependencies + +Unlike other forms of compilation, the `cargo doc` command will treat +conditional dependencies as present by default, in order to document the APIs +which exist only when conditional dependencies are present. The conditional +dependency may be provided somehow. + +# Drawbacks +[drawbacks]: #drawbacks + +Why should we *not* do this? + +# Alternatives +[alternatives]: #alternatives + +What other designs have been considered? What is the impact of not doing this? + +# Unresolved questions +[unresolved]: #unresolved-questions + +What parts of the design are still TBD? From 00b198a5ac1081ac527a48b100369582e6c9b158 Mon Sep 17 00:00:00 2001 From: Without Boats Date: Sat, 5 Nov 2016 18:58:45 -0700 Subject: [PATCH 2/3] Finish the RFC. --- 0000-conditional_dependencies.md | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/0000-conditional_dependencies.md b/0000-conditional_dependencies.md index 7eee525d544..ff5b08217c2 100644 --- a/0000-conditional_dependencies.md +++ b/0000-conditional_dependencies.md @@ -118,14 +118,25 @@ dependency may be provided somehow. # Drawbacks [drawbacks]: #drawbacks -Why should we *not* do this? +This adds more functionality to both cargo and rustc. The distinction between +"conditional" dependencies and "optional" dependencies is sort of subtle and +will have to be explained to users. More complexity always has a downside. # Alternatives [alternatives]: #alternatives -What other designs have been considered? What is the impact of not doing this? +We considered options like allowing the orphan rules to be broken by certain +crates that were recognized as "sibling" crates by a parent, as a way to get +around the orphan rule issue. However, this alternative would create a +soundness hole in the orphan rules and violate good layering, by making them +only fully enforced by cargo and not by rustc. Ultimately, conditionally +altering the shape of code compiled by the flags passed by cargo seemed like +a better solution to the problem. # Unresolved questions [unresolved]: #unresolved-questions -What parts of the design are still TBD? +The specific mechanisms used by cargo and crates.io to determine conditional +dependencies and to prevent cycles are rather unspecified in this RFC. It may +have interactions with other cargo features that haven't been fully thought +through. From 29b807e9efc746680b719a9bd1f052e1c2b6b928 Mon Sep 17 00:00:00 2001 From: Manish Goregaokar Date: Tue, 6 Dec 2016 11:55:45 -1000 Subject: [PATCH 3/3] Change conditional dependencies rfc to be about automatic features --- 0000-automatic_features.md | 136 +++++++++++++++++++++++++++++ 0000-conditional_dependencies.md | 142 ------------------------------- 2 files changed, 136 insertions(+), 142 deletions(-) create mode 100644 0000-automatic_features.md delete mode 100644 0000-conditional_dependencies.md diff --git a/0000-automatic_features.md b/0000-automatic_features.md new file mode 100644 index 00000000000..f49d0ccb66c --- /dev/null +++ b/0000-automatic_features.md @@ -0,0 +1,136 @@ +- Feature Name: `automatic_features` +- Start Date: 2016-11-05 +- RFC PR: (leave this empty) +- Rust Issue: (leave this empty) + +# Summary +[summary]: #summary + +Support "automatic" features, which will be used only if the dependencies of +that feature are already present. Users will be able to write code to integrate +with another library (for example, serializations for their type) without +introducing a *necessary* dependency on that library for users who don't need +it. + +# Motivation +[motivation]: #motivation + +Currently, a common pattern exists for libraries to have feature flags which +turn on a dependency. For example, if I have a library which defines a new +type, I might want users to be able to serialize it with `serde`, but I don't +want users who don't care about this functionality to have to add `serde` to +their dependency graph. So I add a `serialization` feature to my Cargo.toml, +and only if you turn on this feature do you get the feature for serialization. + +Ultimately, the source of this problem is the orphan rules - because only *I* +can define the impls for my type, I have to add this dependency. For every +commonly used trait defining library, I have to add a new feature, and all of +my clients have to turn on that feature. + +cargo features have many uses, but this particular pattern ends up being a lot +of fiddling for a very common use case. Instead of requiring users go through +the feature flag rigmarole, we can provide first class support for this +pattern through "conditional dependencies" - dependencies that are turned on +automatically if they were already present. + +# Detailed design +[design]: #detailed-design + +## Automatic features in `Cargo.toml` + +When declaring a feature in a Cargo.toml file, if that feature is an +object, it can contain the `automatic` member. For example: + +```toml +[features] +foobar = { dependencies = ["foo"], automatic = true } +``` + +Or: + +```toml +[features.foobar] +dependencies = ["foo"] +automatic = true +``` + +When generating the dependency graph, cargo will at first not include features +tagged automatic (unless asked to do so specifically via `--features` or if the +feature is a default feature and `--no-default-features` has not been past). It +will then look for features (in all crates in the graph) that only need +dependencies which are already part of the graph. These features will be +enabled. This will add more edges to the dependency graph, but not more nodes. + +Note that features can depend on crates as well as _other features_, so this +might be implemented as a multi-pass process. + +For example, I can have the `foo` crate with an automatic feature `serialize` +which enables a dependency on serde. I can also have the `bar` crate (which +depends on `foo`) with an automatic feature of the same name which enables both +a dependency on serde and a dependency on `foo/serialize`, i.e. it enables the +`serialize` feature of its dependency `foo`. If serde was included, then in the +first pass, the automatic feature `serialize` on `foo` will be enabled, but not +on `bar`. In the second pass, because `foo/serialize` now exists, +`bar/serialize` is automatically enabled as well. + +The process terminates because it is monotonic -- it only ever adds edges to the +graph, never removing them. + +## Convention for using automatic features + +If you want to add a new automatic feature, you should add a top level +submodule (that is, mounted directly under lib, main, etc) with the same name +as the automatic feature. This module will be tagged with the cfg, and the +extern crate declaration and all code that uses symbols from that crate will +go inside of that module. + +For example: + +```rust +/// lib.rs + +#[cfg(feature=foobar)] +mod foobar; +``` + +```rust +/// foobar.rs + +extern crate foobar; + +impl foobar::Bar for MyType { + ... +} +``` + +## `cargo doc` compiles with all dependencies + +Unlike other forms of compilation, the `cargo doc` command will treat +automatic features as present by default, in order to document the APIs +which exist only when automatic features are present. + +# Alternatives +[alternatives]: #alternatives + +We considered options like allowing the orphan rules to be broken by certain +crates that were recognized as "sibling" crates by a parent, as a way to get +around the orphan rule issue. However, this alternative would create a +soundness hole in the orphan rules and violate good layering, by making them +only fully enforced by cargo and not by rustc. Ultimately, conditionally +altering the shape of code compiled by the flags passed by cargo seemed like +a better solution to the problem. + +A previous version of this RFC proposed "conditional dependencies", which let +you specify individual dependencies as "conditional", which would be turned on +if the dependency already existed in the graph. However, this introduces a +parallel way of specifying optional dependencies which doesn't integrate with +features. Automatic features provide a smooth transition and also allow +dependencies to be grouped together. + +# Unresolved questions +[unresolved]: #unresolved-questions + +The Cargo.toml key names ("dependencies" and "automatic") could be bikeshedded. + +Should we allow automatic features which depend on other features? This seems +like a natural thing to do, but it complicates the resolution process. diff --git a/0000-conditional_dependencies.md b/0000-conditional_dependencies.md deleted file mode 100644 index ff5b08217c2..00000000000 --- a/0000-conditional_dependencies.md +++ /dev/null @@ -1,142 +0,0 @@ -- Feature Name: `conditional_dependencies` -- Start Date: 2016-11-05 -- RFC PR: (leave this empty) -- Rust Issue: (leave this empty) - -# Summary -[summary]: #summary - -Support "conditional" dependencies, which will be used only if that dependency -is already present. Users will be able to write code to integrate with another -library (for example, serializations for their type) without introducing a -*necessary* dependency on that library for users who don't need it. - -# Motivation -[motivation]: #motivation - -Currently, a common pattern exists for libraries to have feature flags which -turn on a dependency. For example, if I have a library which defines a new -type, I might want users to be able to serialize it with `serde`, but I don't -want users who don't care about this functionality to have to add `serde` to -their dependency graph. So I add a `serialization` feature to my Cargo.toml, -and only if you turn on this feature do you get the feature for serialization. - -Ultimately, the source of this problem is the orphan rules - because only *I* -can define the impls for my type, I have to add this dependency. For every -commonly used trait defining library, I have to add a new feature, and all of -my clients have to turn on that feature. - -cargo features have many uses, but this particular pattern ends up being a lot -of fiddling for a very common use case. Instead of requiring users go through -the feature flag rigmarole, we can provide first class support for this -pattern through "conditional dependencies" - dependencies that are turned on -automatically if they were already present. - -# Detailed design -[design]: #detailed-design - -## Conditional dependencies in `Cargo.toml` - -When declaring a dependency in a Cargo.toml file, if that dependency is an -object, it can contain the `conditional` member, which is a boolean, similar to -the `optional` dependency. For example: - -```toml -[dependencies] -foobar = { version = "1.0.0", conditional = true } -``` - -Or: - -```toml -[dependencies.foobar] -version = "1.0.0" -conditional = true -``` - -A single dependency objecy cannot contain both a `conditional` key and an -`optional` key. - -When generating the dependency graph, cargo will not include dependencies -tagged conditional. It will then traverse the graph looking for conditional -dependencies; if a matching source for a conditional dependency (e.g. a -matching version number) is already present in the graph, cargo will -automatically add that dependency. - -## `#[cfg(dependency=)]` - -A new kind of cfg attribute will be added to rustc. The `dependency` attribute -will only be compiled when a certain dependency has been explicitly passed to -rustc using the --extern flag. - -Because cargo automatically passes dependencies explicitly with this command, -this code will be compiled only when cargo triggers the conditional dependency. - -## Convention for using conditional dependencies - -The above to features can be composed in any way, but to assist in adopting -this functionality, we can define an encouraged convention. - -If you want to add a new conditional dependency, you should add a top level -submodule (that is, mounted directly under lib, main, etc) with the same name -as the conditional dependency. This module will be tagged with the cfg, and the -extern crate declaration and all code that uses symbols from that crate will -go inside of that module. - -For example: - -```rust -/// lib.rs - -#[cfg(dependency=foobar] -mod foobar; -``` - -```rust -/// foobar.rs - -extern crate foobar; - -impl foobar::Bar for MyType { - ... -} -``` - -## Preventing cyclic dependencies on upload to crates.io - -When packaging and uploading a crate to crates.io, some strategy will be used -to ensure that its conditional dependencies could not introduce a cycle into -the dependency graph of your crate. - -## `cargo doc` compiles with all dependencies - -Unlike other forms of compilation, the `cargo doc` command will treat -conditional dependencies as present by default, in order to document the APIs -which exist only when conditional dependencies are present. The conditional -dependency may be provided somehow. - -# Drawbacks -[drawbacks]: #drawbacks - -This adds more functionality to both cargo and rustc. The distinction between -"conditional" dependencies and "optional" dependencies is sort of subtle and -will have to be explained to users. More complexity always has a downside. - -# Alternatives -[alternatives]: #alternatives - -We considered options like allowing the orphan rules to be broken by certain -crates that were recognized as "sibling" crates by a parent, as a way to get -around the orphan rule issue. However, this alternative would create a -soundness hole in the orphan rules and violate good layering, by making them -only fully enforced by cargo and not by rustc. Ultimately, conditionally -altering the shape of code compiled by the flags passed by cargo seemed like -a better solution to the problem. - -# Unresolved questions -[unresolved]: #unresolved-questions - -The specific mechanisms used by cargo and crates.io to determine conditional -dependencies and to prevent cycles are rather unspecified in this RFC. It may -have interactions with other cargo features that haven't been fully thought -through.