diff --git a/src/rapids_dependency_file_generator/constants.py b/src/rapids_dependency_file_generator/constants.py index 2ec3ef02..658f7a09 100644 --- a/src/rapids_dependency_file_generator/constants.py +++ b/src/rapids_dependency_file_generator/constants.py @@ -3,6 +3,7 @@ class OutputTypes(Enum): CONDA = "conda" + CONDA_META = "conda_meta" REQUIREMENTS = "requirements" PYPROJECT = "pyproject" NONE = "none" @@ -22,6 +23,7 @@ def __str__(self): ] default_conda_dir = "conda/environments" +default_conda_meta_dir = "conda/recipes/" default_requirements_dir = "python" default_pyproject_dir = "python" default_dependency_file_path = "dependencies.yaml" diff --git a/src/rapids_dependency_file_generator/rapids_dependency_file_generator.py b/src/rapids_dependency_file_generator/rapids_dependency_file_generator.py index 9d7ed046..47e7a0a7 100644 --- a/src/rapids_dependency_file_generator/rapids_dependency_file_generator.py +++ b/src/rapids_dependency_file_generator/rapids_dependency_file_generator.py @@ -11,6 +11,7 @@ cli_name, default_channels, default_conda_dir, + default_conda_meta_dir, default_pyproject_dir, default_requirements_dir, ) @@ -55,6 +56,8 @@ def dedupe(dependencies): """ deduped = sorted({dep for dep in dependencies if not isinstance(dep, dict)}) dict_deps = defaultdict(list) + # The purpose of the outer loop is to support nested dependency lists such as the + # `pip:` list. If multiple are present, they must be internally deduped as well. for dep in filter(lambda dep: isinstance(dep, dict), dependencies): for key, values in dep.items(): dict_deps[key].extend(values) @@ -83,6 +86,10 @@ def grid(gridspec): Iterable[dict] Each yielded value is a dictionary containing one of the unique combinations of parameter values from `gridspec`. + + Notes + ----- + An empty `gridspec` dict will result in an empty dict as the single yielded value. """ for values in itertools.product(*gridspec.values()): yield dict(zip(gridspec.keys(), values)) @@ -131,6 +138,12 @@ def make_dependency_file( "dependencies": dependencies, } ) + elif file_type == str(OutputTypes.CONDA_META): + file_contents += yaml.dump( + { + "dependencies": dependencies, + } + ) elif file_type == str(OutputTypes.REQUIREMENTS): file_contents += "\n".join(dependencies) + "\n" elif file_type == str(OutputTypes.PYPROJECT): @@ -208,7 +221,7 @@ def get_requested_output_types(output): return output -def get_filename(file_type, file_key, matrix_combo): +def get_filename(file_type, file_key, matrix_combo, extras=None): """Get the name of the file to which to write a generated dependency set. The file name will be composed of the following components, each determined @@ -229,6 +242,8 @@ def get_filename(file_type, file_key, matrix_combo): matrix_combo : dict A mapping of key-value pairs corresponding to the [files.$FILENAME.matrix] entry in dependencies.yaml. + extras : dict + Any extra information provided for generating this filename. Returns ------- @@ -240,6 +255,18 @@ def get_filename(file_type, file_key, matrix_combo): file_name_prefix = file_key if file_type == str(OutputTypes.CONDA): file_ext = ".yaml" + elif file_type == str(OutputTypes.CONDA_META): + file_ext = ".yaml" + file_name_prefix = "_".join( + filter( + None, + [ + "meta_dependencies", + extras.get("output", ""), + extras.get("section", ""), + ], + ) + ) elif file_type == str(OutputTypes.REQUIREMENTS): file_ext = ".txt" file_type_prefix = "requirements" @@ -282,6 +309,8 @@ def get_output_dir(file_type, config_file_path, file_config): path = [os.path.dirname(config_file_path)] if file_type == str(OutputTypes.CONDA): path.append(file_config.get("conda_dir", default_conda_dir)) + elif file_type == str(OutputTypes.CONDA_META): + path.append(file_config.get("conda_meta_dir", default_conda_meta_dir)) elif file_type == str(OutputTypes.REQUIREMENTS): path.append(file_config.get("requirements_dir", default_requirements_dir)) elif file_type == str(OutputTypes.PYPROJECT): @@ -425,7 +454,7 @@ def make_dependency_files(parsed_config, config_file_path, to_stdout): ) # Dedupe deps and print / write to filesystem - full_file_name = get_filename(file_type, file_key, matrix_combo) + full_file_name = get_filename(file_type, file_key, matrix_combo, extras) deduped_deps = dedupe(dependencies) output_dir = ( diff --git a/src/rapids_dependency_file_generator/schema.json b/src/rapids_dependency_file_generator/schema.json index 1c2c7d03..5882ec9d 100644 --- a/src/rapids_dependency_file_generator/schema.json +++ b/src/rapids_dependency_file_generator/schema.json @@ -17,6 +17,7 @@ "matrix": {"$ref": "#/$defs/matrix"}, "requirements_dir": {"type": "string"}, "conda_dir": {"type": "string"}, + "conda_meta_dir": {"type": "string"}, "pyproject_dir": {"type": "string"} }, "additionalProperties": false, @@ -118,7 +119,7 @@ "items": {"$ref": "#/$defs/matrix-matcher"} }, "output-types": { - "enum": ["conda", "requirements", "pyproject"] + "enum": ["conda", "conda_meta", "requirements", "pyproject"] }, "output-types-array": { "type": "array", @@ -162,22 +163,16 @@ "extras": { "type": "object", "properties": { + "section": { + "type": "string", + "enum": ["build", "host", "run"] + }, "table": { "type": "string", "enum": ["build-system", "project", "project.optional-dependencies"] }, - "key": {"type": "string"} - }, - "if": { - "properties": { "table": { "const": "project.optional-dependencies" } } - }, - "then": { - "required": ["key"] - }, - "else": { - "not": { - "required": ["key"] - } + "key": {"type": "string"}, + "additionalProperties": false }, "additionalProperties": false }