From 2c7d9fc089bb428d367152b1c5a8cd23be094c9c Mon Sep 17 00:00:00 2001 From: Olivier Lacroix Date: Sat, 27 Apr 2024 12:25:26 +1000 Subject: [PATCH] Revert to boolean no-default-feature --- docs/reference/configuration.md | 18 ++-- examples/polarify/pixi.toml | 2 +- schema/model.py | 98 ++++++++++--------- schema/schema.json | 66 +------------ src/cli/add.rs | 4 +- src/cli/info.rs | 5 +- src/lock_file/update.rs | 20 ++-- src/project/dependencies.rs | 1 + src/project/environment.rs | 35 +++---- src/project/grouped_environment.rs | 9 ++ src/project/manifest/environment.rs | 146 ++-------------------------- src/project/manifest/mod.rs | 8 +- src/project/solve_group.rs | 2 +- 13 files changed, 111 insertions(+), 303 deletions(-) diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 439c89136..ad9fdbca5 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -617,29 +617,23 @@ platforms = ["linux-64", "osx-arm64"] The `environments` table allows you to define environments that are created using the features defined in the `feature` tables. -Each environment is defined using the following fields: +The environments table is defined using the following fields: -- `features`: The features that are included in the environment. Unless `include-default` or `exclude-default` are set, all components of the default feature are included by default. +- `features`: The features that are included in the environment. Unless `no-default-feature` is set to `true`; the default feature is always included. - `solve-group`: The solve group is used to group environments together at the solve stage. This is useful for environments that need to have the same dependencies but might extend them with additional dependencies. For instance when testing a production environment with additional test dependencies. These dependencies will then be the same version in all environments that have the same solve group. But the different environments contain different subsets of the solve-groups dependencies set. -- `include-default`: It is used to list the components of the default feature to include in that environment. Other components of the default feature are excluded. -- `exclude-default`: It is used to list the components of the default feature to exclude from that environment. Other components of the default feature are included. - -Note that fields `include-default` and `exclude-default`: - - are mutually exclusive; only one of them can be specified for a given environment. - - can contain any of "system-requirements", "channels", "platforms", "dependencies", "pypi-dependencies", "activation", "tasks". +- `no-default-feature`: Whether to include the default feature in that environment. The default is to include the default feature. ```toml title="Full environments table specification" [environments] test = {features = ["test"], solve-group = "test"} prod = {features = ["prod"], solve-group = "test"} -lint = {features = ["lint"], include-default = ["channels", "platforms"]} +prod = ["lint"] ``` - -In the simplest of cases, it is possible to define an environment only by listing its features: +As shown in the example above, in the simplest of cases, it is possible to define an environment only by listing its features: ```toml title="Simplest example" [environments] @@ -648,7 +642,7 @@ test = ["test"] Which is equivalent to -```toml title="Simplest example" +```toml title="Simplest example expanded" [environments] test = {features = ["test"]} ``` diff --git a/examples/polarify/pixi.toml b/examples/polarify/pixi.toml index 38c0c5b9a..b4a1037d2 100644 --- a/examples/polarify/pixi.toml +++ b/examples/polarify/pixi.toml @@ -52,7 +52,7 @@ py39 = ["py39", "test"] py310 = ["py310", "test"] py311 = ["py311", "test"] py312 = ["py312", "test"] -lint = { features=["lint"], include-default=["platforms","channels"] } +lint = { features=["lint"], no-default-feature=true } ## test this with: #pixi run test diff --git a/schema/model.py b/schema/model.py index a436b1885..5ef06d08a 100644 --- a/schema/model.py +++ b/schema/model.py @@ -1,4 +1,5 @@ """A canonical schema definition for the ``pixi.toml`` manifest file.""" + from __future__ import annotations import json @@ -62,6 +63,7 @@ class Config: class ChannelInlineTable(StrictBaseModel): """A precise description of a `conda` channel, with an optional priority.""" + channel: ChannelName = Field(description="The channel the packages needs to be fetched from") priority: int | None = Field(None, description="The priority of the channel") @@ -71,21 +73,28 @@ class ChannelInlineTable(StrictBaseModel): class Project(StrictBaseModel): """The project's metadata information.""" + name: NonEmptyStr = Field( description="The name of the project; we advise use of the name of the repository" ) version: NonEmptyStr | None = Field( - None, description="The version of the project; we advise use of [SemVer](https://semver.org)", examples=["1.2.3"] + None, + description="The version of the project; we advise use of [SemVer](https://semver.org)", + examples=["1.2.3"], ) description: NonEmptyStr | None = Field(None, description="A short description of the project") authors: list[NonEmptyStr] | None = Field( None, description="The authors of the project", examples=["John Doe "] ) channels: list[Channel] = Field( - None, description="The `conda` channels that can be used in the project. Unless overridden by `priority`, the first channel listed will be preferred." + None, + description="The `conda` channels that can be used in the project. Unless overridden by `priority`, the first channel listed will be preferred.", ) platforms: list[Platform] = Field(description="The platforms that the project supports") - license: NonEmptyStr | None = Field(None, description="The license of the project; we advise using an [SPDX](https://spdx.org/licenses/) identifier.") + license: NonEmptyStr | None = Field( + None, + description="The license of the project; we advise using an [SPDX](https://spdx.org/licenses/) identifier.", + ) license_file: PathNoBackslash | None = Field( None, alias="license-file", description="The path to the license file of the project" ) @@ -111,6 +120,7 @@ class Project(StrictBaseModel): class MatchspecTable(StrictBaseModel): """A precise description of a `conda` package version.""" + version: NonEmptyStr | None = Field( None, description="The version of the package in [MatchSpec](https://github.com/conda/conda/blob/078e7ee79381060217e1ec7f9b0e9cf80ecc8f3f/conda/models/match_spec.py) format", @@ -134,7 +144,10 @@ class MatchspecTable(StrictBaseModel): class _PyPIRequirement(StrictBaseModel): - extras: list[NonEmptyStr] | None = Field(None, description="The [PEP 508 extras](https://peps.python.org/pep-0508/#extras) of the package") + extras: list[NonEmptyStr] | None = Field( + None, + description="The [PEP 508 extras](https://peps.python.org/pep-0508/#extras) of the package", + ) class _PyPiGitRequirement(_PyPIRequirement): @@ -198,7 +211,8 @@ class PyPIVersion(_PyPIRequirement): HostDependenciesField = Field( None, alias="host-dependencies", - description="The host `conda` dependencies, used in the build process", examples=[{"python": ">=3.8"}] + description="The host `conda` dependencies, used in the build process", + examples=[{"python": ">=3.8"}], ) BuildDependenciesField = Field( None, @@ -212,21 +226,26 @@ class PyPIVersion(_PyPIRequirement): ################ TaskName = Annotated[str, Field(pattern=r"^[^\s\$]+$", description="A valid task name.")] + class TaskInlineTable(StrictBaseModel): """A precise definition of a task.""" + cmd: list[NonEmptyStr] | NonEmptyStr | None = Field( - None, description="A shell command to run the task in the limited, but cross-platform `bash`-like `deno_task_shell`. See the documentation for [supported syntax](https://pixi.sh/latest/features/advanced_tasks/#syntax)" + None, + description="A shell command to run the task in the limited, but cross-platform `bash`-like `deno_task_shell`. See the documentation for [supported syntax](https://pixi.sh/latest/features/advanced_tasks/#syntax)", ) cwd: PathNoBackslash | None = Field(None, description="The working directory to run the task") depends_on: list[TaskName] | TaskName | None = Field( - None, description="The tasks that this task depends on. Environment variables will **not** be expanded." + None, + description="The tasks that this task depends on. Environment variables will **not** be expanded.", ) inputs: list[Glob] | None = Field( None, description="A list of `.gitignore`-style glob patterns that should be watched for changes before this command is run. Environment variables _will_ be expanded.", ) outputs: list[Glob] | None = Field( - None, description="A list of `.gitignore`-style glob patterns that are generated by this command. Environment variables _will_ be expanded." + None, + description="A list of `.gitignore`-style glob patterns that are generated by this command. Environment variables _will_ be expanded.", ) env: dict[NonEmptyStr, NonEmptyStr] | None = Field( None, @@ -247,6 +266,7 @@ class LibcFamily(StrictBaseModel): class SystemRequirements(StrictBaseModel): """Platform-specific requirements""" + linux: PositiveFloat | NonEmptyStr | None = Field( None, description="The minimum version of the Linux kernel" ) @@ -269,19 +289,11 @@ class SystemRequirements(StrictBaseModel): EnvironmentName = Annotated[str, Field(pattern=r"^[a-z\d\-]+$")] FeatureName = NonEmptyStr SolveGroupName = NonEmptyStr -Component = ( - Literal["system-requirements"] - | Literal["channels"] - | Literal["platforms"] - | Literal["dependencies"] - | Literal["pypi-dependencies"] - | Literal["activation"] - | Literal["tasks"] -) class Environment(StrictBaseModel): """A composition of the dependencies of features which can be activated to run tasks or provide a shell""" + features: list[FeatureName] | None = Field( None, description="The features that define the environment" ) @@ -290,15 +302,10 @@ class Environment(StrictBaseModel): alias="solve-group", description="The group name for environments that should be solved together", ) - include_default: list[Component] | None = Field( - False, - alias="include-default", - description="Components of the default feature to include", - ) - exclude_default: list[Component] | None = Field( + no_default_feature: Optional[bool] = Field( False, - alias="exclude-default", - description="Components of the default feature to exclude", + alias="no-default-feature", + description="Whether to add the default feature to this environment", ) @@ -307,6 +314,7 @@ class Environment(StrictBaseModel): ###################### class Activation(StrictBaseModel): """A description of steps performed when an environment is activated""" + scripts: list[NonEmptyStr] | None = Field( None, description="The scripts to run when the environment is activated", @@ -322,6 +330,7 @@ class Activation(StrictBaseModel): class Target(StrictBaseModel): """A machine-specific configuration of dependencies and tasks""" + dependencies: Dependencies = DependenciesField host_dependencies: Dependencies = HostDependenciesField build_dependencies: Dependencies = BuildDependenciesField @@ -341,8 +350,10 @@ class Target(StrictBaseModel): ################### class Feature(StrictBaseModel): """A composable aspect of the project which can contribute dependencies and tasks to an environment""" + channels: list[Channel] | None = Field( - None, description="The `conda` channels that can be considered when solving environments containing this feature" + None, + description="The `conda` channels that can be considered when solving environments containing this feature", ) platforms: list[NonEmptyStr] | None = Field( None, @@ -377,11 +388,12 @@ class Feature(StrictBaseModel): class BaseManifest(StrictBaseModel): """The configuration for a [`pixi`](https://pixi.sh) project.""" + class Config: json_schema_extra = { "$id": SCHEMA_URI, "$schema": SCHEMA_DRAFT, - "title": "`pixi.toml` manifest file" + "title": "`pixi.toml` manifest file", } schema_: str | None = Field( @@ -389,7 +401,7 @@ class Config: alias="$schema", title="Schema", description="The schema identifier for the project's configuration", - format="uri-reference" + format="uri-reference", ) project: Project = Field(..., description="The project's metadata information") @@ -406,7 +418,8 @@ class Config: None, alias="system-requirements", description="The system requirements of the project" ) environments: dict[EnvironmentName, Environment | list[FeatureName]] | None = Field( - None, description="The environments of the project, defined as a full object or a list of feature names." + None, + description="The environments of the project, defined as a full object or a list of feature names.", ) feature: dict[FeatureName, Feature] | None = Field( None, description="The features of the project" @@ -419,7 +432,9 @@ class Config: description="The targets of the project", examples=[{"linux": {"dependencies": {"python": "3.8"}}}], ) - tool: dict[str, Any] = Field(None, description="Third-party tool configurations, ignored by pixi") + tool: dict[str, Any] = Field( + None, description="Third-party tool configurations, ignored by pixi" + ) ######################### @@ -429,6 +444,7 @@ class Config: class SchemaJsonEncoder(json.JSONEncoder): """A custom schema encoder for normalizing schema to be used with TOML files.""" + HEADER_ORDER = [ "$schema", "$id", @@ -440,8 +456,7 @@ class SchemaJsonEncoder(json.JSONEncoder): "required", "additionalProperties", "default", - "items" - "properties", + "items" "properties", "patternProperties", "allOf", "anyOf", @@ -483,7 +498,7 @@ class SchemaJsonEncoder(json.JSONEncoder): def encode(self, obj): """Overload the default ``encode`` behavior.""" if isinstance(obj, dict): - obj = self.normalize_schema(deepcopy(obj)) + obj = self.normalize_schema(deepcopy(obj)) return super().encode(obj) @@ -534,7 +549,8 @@ def strip_nulls(self, obj: dict[str, Any]) -> dict[str, Any]: for nest in self.SORT_NESTED_ARR: some_of = [ - self.normalize_schema(option) for option in obj.get(nest, []) + self.normalize_schema(option) + for option in obj.get(nest, []) if option.get("type") != "null" ] @@ -552,22 +568,14 @@ def sort_nested(self, obj: dict[str, Any], key: str) -> dict[str, Any]: return obj obj[key] = { k: self.normalize_schema(v) if isinstance(v, dict) else v - for k, v in sorted( - obj[key].items(), - key=lambda kv: kv[0] - ) + for k, v in sorted(obj[key].items(), key=lambda kv: kv[0]) } return obj + ########################## # Command Line Interface # ########################## if __name__ == "__main__": - print( - json.dumps( - BaseManifest.model_json_schema(), - indent=2, - cls=SchemaJsonEncoder - ) - ) + print(json.dumps(BaseManifest.model_json_schema(), indent=2, cls=SchemaJsonEncoder)) diff --git a/schema/schema.json b/schema/schema.json index cab13e0c0..bf044ab62 100644 --- a/schema/schema.json +++ b/schema/schema.json @@ -241,37 +241,6 @@ "type": "object", "additionalProperties": false, "properties": { - "exclude-default": { - "title": "Exclude-Default", - "description": "Components of the default feature to exclude", - "type": "array", - "default": false, - "items": { - "anyOf": [ - { - "const": "system-requirements" - }, - { - "const": "channels" - }, - { - "const": "platforms" - }, - { - "const": "dependencies" - }, - { - "const": "pypi-dependencies" - }, - { - "const": "activation" - }, - { - "const": "tasks" - } - ] - } - }, "features": { "title": "Features", "description": "The features that define the environment", @@ -281,36 +250,11 @@ "minLength": 1 } }, - "include-default": { - "title": "Include-Default", - "description": "Components of the default feature to include", - "type": "array", - "default": false, - "items": { - "anyOf": [ - { - "const": "system-requirements" - }, - { - "const": "channels" - }, - { - "const": "platforms" - }, - { - "const": "dependencies" - }, - { - "const": "pypi-dependencies" - }, - { - "const": "activation" - }, - { - "const": "tasks" - } - ] - } + "no-default-feature": { + "title": "No-Default-Feature", + "description": "Whether to add the default feature to this environment", + "type": "boolean", + "default": false }, "solve-group": { "title": "Solve-Group", diff --git a/src/cli/add.rs b/src/cli/add.rs index 63a34679c..c4380f084 100644 --- a/src/cli/add.rs +++ b/src/cli/add.rs @@ -277,9 +277,7 @@ pub async fn add_conda_specs_to_project( .grouped_environments() .iter() .filter(|env| { - // The default feature is included if its dependencies are included in the GroupedEnvironment - env.environments() - .flat_map(|e| e.features(e.manifest().from_default_feature.dependencies)) + env.features() .map(|feat| &feat.name) .contains(&feature_name) }) diff --git a/src/cli/info.rs b/src/cli/info.rs index 75f17b112..d2a288cfc 100644 --- a/src/cli/info.rs +++ b/src/cli/info.rs @@ -314,10 +314,7 @@ pub async fn execute(args: Args) -> miette::Result<()> { EnvironmentInfo { name: env.name().clone(), - features: env - .features(true) - .map(|feature| feature.name.clone()) - .collect(), + features: env.features().map(|feature| feature.name.clone()).collect(), solve_group: env .solve_group() .map(|solve_group| solve_group.name().to_string()), diff --git a/src/lock_file/update.rs b/src/lock_file/update.rs index f3b29675c..966b13f6a 100644 --- a/src/lock_file/update.rs +++ b/src/lock_file/update.rs @@ -1099,18 +1099,14 @@ fn make_unsupported_pypi_platform_error( } // Find all the features that are excluding the current platform. - let features_without_platform = grouped_environment - .environments() - // The default feature is included if its platforms are included in the environment - .flat_map(|e| e.features(e.manifest().from_default_feature.platforms)) - .filter_map(|feature| { - let platforms = feature.platforms.as_ref()?; - if !platforms.value.contains(¤t_platform) { - Some((feature, platforms)) - } else { - None - } - }); + let features_without_platform = grouped_environment.features().filter_map(|feature| { + let platforms = feature.platforms.as_ref()?; + if !platforms.value.contains(¤t_platform) { + Some((feature, platforms)) + } else { + None + } + }); for (feature, platforms) in features_without_platform { let Some(span) = platforms.span.as_ref() else { diff --git a/src/project/dependencies.rs b/src/project/dependencies.rs index f2f2dc6df..b3acbc252 100644 --- a/src/project/dependencies.rs +++ b/src/project/dependencies.rs @@ -65,6 +65,7 @@ impl Dependencies { for (name, specs) in &other.map { let entry = map.entry(name.clone()).or_default(); for spec in specs { + // TODO entry should be a set to avoid duplicates if !entry.contains(spec) { entry.push(spec.clone()); } diff --git a/src/project/environment.rs b/src/project/environment.rs index 448ba2cf0..cc6aa51f9 100644 --- a/src/project/environment.rs +++ b/src/project/environment.rs @@ -1,12 +1,10 @@ use super::{ dependencies::Dependencies, errors::{UnknownTask, UnsupportedPlatformError}, - manifest::{ - self, channel::PrioritizedChannel, EnvironmentName, Feature, FeatureName, - SystemRequirements, - }, + manifest::{self, EnvironmentName, Feature, FeatureName, SystemRequirements}, PyPiRequirement, SolveGroup, SpecType, }; +use crate::project::manifest::channel::PrioritizedChannel; use crate::project::manifest::python::PyPiPackageName; use crate::task::TaskName; use crate::{task::Task, Project}; @@ -110,10 +108,7 @@ impl<'p> Environment<'p> { } /// Returns references to the features that make up this environment. - pub fn features( - &self, - include_default: bool, - ) -> impl DoubleEndedIterator + 'p { + pub fn features(&self) -> impl DoubleEndedIterator + 'p { let environment_features = self.environment.features.iter().map(|feature_name| { self.project .manifest @@ -123,14 +118,14 @@ impl<'p> Environment<'p> { .expect("feature usage should have been validated upfront") }); - if include_default { - Either::Left(environment_features.chain([self.project.manifest.default_feature()])) - } else { + if self.environment.no_default_feature { Either::Right(environment_features) + } else { + Either::Left(environment_features.chain([self.project.manifest.default_feature()])) } } - /// Returns the prioritized channels associated with this environment. + /// Returns the channels associated with this environment. /// /// Users can specify custom channels on a per feature basis. This method collects and /// deduplicates all the channels from all the features in the order they are defined in the @@ -139,7 +134,7 @@ impl<'p> Environment<'p> { /// If a feature does not specify any channel the default channels from the project metadata are /// used instead. pub fn prioritized_channels(&self) -> IndexSet<&'p PrioritizedChannel> { - self.features(self.environment.from_default_feature.channels) + self.features() .flat_map(|feature| match &feature.channels { Some(channels) => channels, None => &self.project.manifest.parsed.project.channels, @@ -178,7 +173,7 @@ impl<'p> Environment<'p> { /// Features can specify which platforms they support through the `platforms` key. If a feature /// does not specify any platforms the features defined by the project are used. pub fn platforms(&self) -> HashSet { - self.features(self.environment.from_default_feature.platforms) + self.features() .map(|feature| { match &feature.platforms { Some(platforms) => &platforms.value, @@ -206,7 +201,7 @@ impl<'p> Environment<'p> { ) -> Result, UnsupportedPlatformError> { self.validate_platform_support(platform)?; let result = self - .features(self.environment.from_default_feature.tasks) + .features() .flat_map(|feature| feature.targets.resolve(platform)) .rev() // Reverse to get the most specific targets last. .flat_map(|target| target.tasks.iter()) @@ -262,7 +257,7 @@ impl<'p> Environment<'p> { /// the features that make up the environment. If multiple features specify a requirement for /// the same system package, the highest is chosen. pub fn local_system_requirements(&self) -> SystemRequirements { - self.features(self.environment.from_default_feature.system_requirements) + self.features() .map(|feature| &feature.system_requirements) .fold(SystemRequirements::default(), |acc, req| { acc.union(req) @@ -276,7 +271,7 @@ impl<'p> Environment<'p> { /// requirement for the same package that both requirements are returned. The different /// requirements per package are sorted in the same order as the features they came from. pub fn dependencies(&self, kind: Option, platform: Option) -> Dependencies { - self.features(self.environment.from_default_feature.dependencies) + self.features() .filter_map(|f| f.dependencies(kind, platform)) .map(|deps| Dependencies::from(deps.into_owned())) .reduce(|acc, deps| acc.union(&deps)) @@ -292,7 +287,7 @@ impl<'p> Environment<'p> { &self, platform: Option, ) -> IndexMap> { - self.features(self.environment.from_default_feature.pypi_dependencies) + self.features() .filter_map(|f| f.pypi_dependencies(platform)) .fold(IndexMap::default(), |mut acc, deps| { // Either clone the values from the Cow or move the values from the owned map. @@ -319,7 +314,7 @@ impl<'p> Environment<'p> { /// The activation scripts of all features are combined in the order they are defined for the /// environment. pub fn activation_scripts(&self, platform: Option) -> Vec { - self.features(self.environment.from_default_feature.activation) + self.features() .filter_map(|f| f.activation_scripts(platform)) .flatten() .cloned() @@ -346,7 +341,7 @@ impl<'p> Environment<'p> { /// Returns true if the environments contains any reference to a pypi dependency. pub fn has_pypi_dependencies(&self) -> bool { - self.features(true).any(|f| f.has_pypi_dependencies()) + self.features().any(|f| f.has_pypi_dependencies()) } } diff --git a/src/project/grouped_environment.rs b/src/project/grouped_environment.rs index 21754a1f4..4f5391ae2 100644 --- a/src/project/grouped_environment.rs +++ b/src/project/grouped_environment.rs @@ -1,4 +1,5 @@ use crate::project::manifest::python::PyPiPackageName; +use crate::project::manifest::Feature; use crate::{ consts, prefix::Prefix, @@ -165,6 +166,14 @@ impl<'p> GroupedEnvironment<'p> { GroupedEnvironment::Environment(env) => env.has_pypi_dependencies(), } } + + /// Returns the features of the group + pub fn features(&self) -> impl DoubleEndedIterator + 'p { + match self { + GroupedEnvironment::Group(group) => Either::Left(group.features(true)), + GroupedEnvironment::Environment(env) => Either::Right(env.features(true)), + } + } } /// A name of a [`GroupedEnvironment`]. diff --git a/src/project/manifest/environment.rs b/src/project/manifest/environment.rs index 863f803dd..238992e9b 100644 --- a/src/project/manifest/environment.rs +++ b/src/project/manifest/environment.rs @@ -143,8 +143,8 @@ pub struct Environment { /// dependencies of the environment that share the same solve-group will be solved together. pub solve_group: Option, - /// Components to include from the default feature - pub from_default_feature: FromDefaultFeature, + /// Whether to include the default feature in that environment + pub no_default_feature: bool, } impl Default for Environment { @@ -154,119 +154,23 @@ impl Default for Environment { features: Vec::new(), features_source_loc: None, solve_group: None, - from_default_feature: FromDefaultFeature::default(), - } - } -} - -#[derive(Debug, Clone)] -pub struct FromDefaultFeature { - pub system_requirements: bool, - pub channels: bool, - pub platforms: bool, - pub dependencies: bool, - pub pypi_dependencies: bool, - pub activation: bool, - pub tasks: bool, -} - -// by default, include everything from the default feature -impl Default for FromDefaultFeature { - fn default() -> Self { - Self { - system_requirements: true, - channels: true, - platforms: true, - dependencies: true, - pypi_dependencies: true, - activation: true, - tasks: true, - } - } -} - -/// Deserialisation conversion helper to get a FromDefaultFeature from TOML environment data -impl From> for FromDefaultFeature { - fn from(opt: Option) -> Self { - match opt { - None => FromDefaultFeature::default(), - Some(FromDefaultToml::IncludeDefault(included)) => { - let mut f = FromDefaultFeature { - system_requirements: false, - channels: false, - platforms: false, - dependencies: false, - pypi_dependencies: false, - activation: false, - tasks: false, - }; - - for component in &included { - match component { - FeatureComponentToml::SystemRequirements => f.system_requirements = true, - FeatureComponentToml::Channels => f.channels = true, - FeatureComponentToml::Platforms => f.platforms = true, - FeatureComponentToml::Dependencies => f.dependencies = true, - FeatureComponentToml::PypiDependencies => f.pypi_dependencies = true, - FeatureComponentToml::Activation => f.activation = true, - FeatureComponentToml::Tasks => f.tasks = true, - } - } - - f - } - Some(FromDefaultToml::ExcludeDefault(excluded)) => { - let mut f = FromDefaultFeature::default(); - for component in &excluded { - match component { - FeatureComponentToml::SystemRequirements => f.system_requirements = false, - FeatureComponentToml::Channels => f.channels = false, - FeatureComponentToml::Platforms => f.platforms = false, - FeatureComponentToml::Dependencies => f.dependencies = false, - FeatureComponentToml::PypiDependencies => f.pypi_dependencies = false, - FeatureComponentToml::Activation => f.activation = false, - FeatureComponentToml::Tasks => f.tasks = false, - } - } - - f - } + no_default_feature: false, } } } /// Helper struct to deserialize the environment from TOML. /// The environment description can only hold these values. -#[derive(Deserialize, Debug)] +#[derive(Deserialize)] #[serde(deny_unknown_fields, rename_all = "kebab-case")] pub(super) struct TomlEnvironment { #[serde(default)] pub features: PixiSpanned>, pub solve_group: Option, - #[serde(flatten)] - pub from_default: Option, -} - -#[derive(Debug, Deserialize, Clone)] -#[serde(rename_all = "kebab-case")] -pub(super) enum FromDefaultToml { - IncludeDefault(Vec), - ExcludeDefault(Vec), -} - -#[derive(Debug, Clone, Deserialize, PartialEq)] -#[serde(rename_all = "kebab-case")] -pub(super) enum FeatureComponentToml { - SystemRequirements, - Channels, - Platforms, - Dependencies, - PypiDependencies, - Activation, - Tasks, + #[serde(default)] + pub no_default_feature: bool, } -#[derive(Debug)] pub(super) enum TomlEnvironmentMapOrSeq { Map(TomlEnvironment), Seq(Vec), @@ -287,8 +191,6 @@ impl<'de> Deserialize<'de> for TomlEnvironmentMapOrSeq { #[cfg(test)] mod tests { - use indexmap::IndexMap; - use super::*; #[test] @@ -348,40 +250,4 @@ mod tests { EnvironmentName::Named("foo".to_string()) ); } - - fn from_default(source: &str) -> FromDefaultFeature { - let env = - toml_edit::de::from_str::>(source) - .unwrap(); - match env.values().next().unwrap() { - TomlEnvironmentMapOrSeq::Map(env) => env.from_default.clone().into(), - TomlEnvironmentMapOrSeq::Seq(_) => FromDefaultFeature::default(), - } - } - - #[test] - fn test_deserialize_exclude_from_default() { - let source = r#"test = { exclude-default=["channels"]}"#; - assert_eq!(false, from_default(source).channels); - assert_eq!(true, from_default(source).platforms); - } - #[test] - fn test_deserialize_include_from_default() { - let source = r#"test = { include-default=["channels"]}"#; - assert_eq!(true, from_default(source).channels); - assert_eq!(false, from_default(source).platforms); - } - #[test] - fn test_deserialize_no_from_default() { - let source = r#"test = ["bla"]"#; - assert_eq!(true, from_default(source).channels); - assert_eq!(true, from_default(source).platforms); - } - #[test] - #[should_panic(expected = "unknown field `exclude-default`")] - fn test_deserialize_from_default_conflict() { - let source = r#"test = { include-default=["channels"], exclude-default=["platform"]}"#; - from_default(source); - () - } } diff --git a/src/project/manifest/mod.rs b/src/project/manifest/mod.rs index c7e407b85..905cc2ed4 100644 --- a/src/project/manifest/mod.rs +++ b/src/project/manifest/mod.rs @@ -1111,14 +1111,14 @@ impl<'de> Deserialize<'de> for ProjectManifest { // Add all named environments for (name, env) in toml_manifest.environments { // Decompose the TOML - let (features, features_source_loc, solve_group, from_default_feature) = match env { + let (features, features_source_loc, solve_group, no_default_feature) = match env { TomlEnvironmentMapOrSeq::Map(env) => ( env.features.value, env.features.span, env.solve_group, - env.from_default, + env.no_default_feature, ), - TomlEnvironmentMapOrSeq::Seq(features) => (features, None, None, None), + TomlEnvironmentMapOrSeq::Seq(features) => (features, None, None, false), }; let environment_idx = environments.environments.len(); @@ -1128,7 +1128,7 @@ impl<'de> Deserialize<'de> for ProjectManifest { features, features_source_loc, solve_group: solve_group.map(|sg| solve_groups.add(&sg, environment_idx)), - from_default_feature: from_default_feature.into(), + no_default_feature, }); } diff --git a/src/project/solve_group.rs b/src/project/solve_group.rs index c4d548691..b8065b142 100644 --- a/src/project/solve_group.rs +++ b/src/project/solve_group.rs @@ -172,7 +172,7 @@ mod tests { [environments] foo = { features=["foo"], solve-group="group1" } bar = { features=["bar"], solve-group="group1" } - baz = { features=["bar"], solve-group="group2", exclude-default=["dependencies"] } + baz = { features=["bar"], solve-group="group2", no-default-feature=true } "#, ) .unwrap();