diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5df4cea..b4bef11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -22,6 +22,7 @@ jobs: # Generate schema.json from model.py # Note: generate command outputs to stdout, so we redirect to a file pixi run generate > schema.json + pixi run genvariant > variant_schema.json - name: Test recipe schema run: | pixi run test diff --git a/README.md b/README.md index 4c331db..adcdfc3 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@ This repository contains the [JSON Schema](./schema.json) for the new `recipe.ya - https://github.com/conda-incubator/ceps/pull/56 - https://github.com/conda-incubator/ceps/pull/57 (not yet implemented) +And [Variant Schema](./variant_schema.json) for the `variant_config.yaml` files. + ## How to use There is a fully JSON Schema 1.0 compliant [schema.json](./schema.json) at the root of this repository. @@ -20,6 +22,12 @@ Using the YAML extension for VSCode all you need to do is add: to the top of your recipe file and you should have autocompletion and validation! +Similarly for that `variant_schema.json` add, + +```yaml +# yaml-language-server: $schema=https://raw.githubusercontent.com/prefix-dev/recipe-format/main/variant_schema.json +``` + ## Contributing The project is using [`pixi`](https://github.com/prefix-dev/pixi) to be able to quickly format, validate and generate the schema. @@ -30,6 +38,7 @@ Note: When submitting changes to `model.py` make sure to run following commands pixi run fmt pixi run lint pixi run generate > schema.json +pixi run genvariant > variant_schema.json pixi run test ``` diff --git a/examples/mamba/variant_config.yaml b/examples/mamba/variant_config.yaml new file mode 100644 index 0000000..6b30849 --- /dev/null +++ b/examples/mamba/variant_config.yaml @@ -0,0 +1,6 @@ +# yaml-language-server: $schema=../../variant_schema.json + +python: + - "3.11.* *cpython" + # - "3.12.* *cpython" + # - "3.9.* *cpython" diff --git a/examples/variant_config.yaml b/examples/variant_config.yaml new file mode 100644 index 0000000..96565de --- /dev/null +++ b/examples/variant_config.yaml @@ -0,0 +1,79 @@ +# yaml-language-server: $schema=../variant_schema.json + +zip_keys: + - if: not win + then: + - c_compiler_version + - cxx_compiler_version + - fortran_compiler_version + - docker_image + - + - cudnn + - cuda_compiler_version + - + - python + - numpy + - python_impl + - + - arrow_cpp + - libarrow + +pin_run_as_build: + # boost is special, see https://github.com/conda-forge/boost-cpp-feedstock/pull/82 + boost: + max_pin: x.x.x + boost-cpp: + max_pin: x.x.x + # TODO: add run_exports to the following feedstocks + flann: + max_pin: x.x.x + graphviz: + max_pin: x + libsvm: + max_pin: x + netcdf-cxx4: + max_pin: x.x + occt: + max_pin: x.x + poppler: + max_pin: x.x + r-base: + max_pin: x.x + min_pin: x.x + vlfeat: + max_pin: x.x.x + +# Pinning packages + +# blas +libblas: + - 3.9 *netlib +libcblas: + - 3.9 *netlib +liblapack: + - 3.9 *netlib +liblapacke: + - 3.9 *netlib +blas_impl: + - openblas + - mkl # [x86 or x86_64] + - blis # [x86 or x86_64] + +# this output was dropped as of libabseil 20230125 +abseil_cpp: + - '20220623.0' +alsa_lib: + - 1.2.8 +arb: + - '2.23' +arpack: + - '3.7' +# keep in sync with libarrow +arrow_cpp: + - 11.0.0 + - 10.0.1 + - 9.0.0 + - 8.0.1 +assimp: + - 5.2.5 +aws-sdk-cpp: "4.5" \ No newline at end of file diff --git a/pixi.toml b/pixi.toml index e503dae..119657c 100644 --- a/pixi.toml +++ b/pixi.toml @@ -8,8 +8,9 @@ platforms = ["win-64", "linux-64", "osx-64", "osx-arm64"] [tasks] generate = "python model.py" -fmt = "ruff format model.py" -lint = "ruff model.py --fix" +genvariant = "python variant_model.py" +fmt = "ruff format model.py variant_model.py" +lint = "ruff model.py variant_model.py --fix" test = "pytest" [dependencies] diff --git a/test/test_recipe.py b/test/test_recipe.py index 4e482ce..32ed9a5 100644 --- a/test/test_recipe.py +++ b/test/test_recipe.py @@ -27,3 +27,28 @@ def recipe_schema(): def test_recipe_schema(recipe_schema, recipe): validate(instance=recipe, schema=recipe_schema) + + +@pytest.fixture( + scope="module", + params=[ + "examples/mamba", + "examples", + ], +) +def variantconfig(request) -> str: + recipe_name = request.param + with open(f"./{recipe_name}/variant_config.yaml") as f: + recipe = f.read() + recipe_yml = yaml.safe_load(recipe) + return recipe_yml + +@pytest.fixture() +def variant_schema(): + with open("variant_schema.json", "r") as f: + schema = json.load(f) + return schema + + +def test_variant_schema(variantconfig, variant_schema): + validate(instance=variantconfig, schema=variant_schema) diff --git a/variant_model.py b/variant_model.py new file mode 100644 index 0000000..4fd59e2 --- /dev/null +++ b/variant_model.py @@ -0,0 +1,64 @@ +import json +from typing import Generic, TypeVar, Union + +from pydantic import BaseModel, Field, TypeAdapter + +T = TypeVar("T") +ConditionalList = Union[T, "IfStatement[T]", list[Union[T, "IfStatement[T]"]]] + + +class IfStatement(BaseModel, Generic[T]): + expr: str = Field(..., alias="if") + then: T | list[T] + otherwise: T | list[T] | None = Field(None, alias="else") + + +class MinPin(BaseModel): + min_pin: str | None = Field( + default=None, description="Defaults to x.x.x.x.x for pin, change to make less specific" + ) + + +class MaxPin(BaseModel): + max_pin: str | None = Field( + default=None, description="Defaults to x for pin, change to make more specific" + ) + + +class BothPin(BaseModel): + min_pin: str | None = Field( + default=None, description="Defaults to x.x.x.x.x for pin, change to make less specific" + ) + max_pin: str | None = Field( + default=None, description="Defaults to x for pin, change to make more specific" + ) + + +class VariantConfig(BaseModel, extra="allow"): + """Usage docs: https://prefix-dev.github.io/rattler-build/variants + + Schema for variant configuration file for specifying recipe variants + for automating builds. + """ + + zip_keys: ConditionalList[ConditionalList[str]] = Field( + default=None, + description="Zip keys have variant key that has multiple entries which is expanded to a build matrix for variants.", + ) + pin_run_as_build: dict[str, MaxPin | MinPin | BothPin] = Field( + default=None, description="Pinning package versions for variant" + ) + model_config = { + "json_schema_extra": { + "additionalProperties": { + "anyOf": [ + {"type": "string"}, + {"items": {"type": "string"}, "type": "array"}, + ] + }, + }, + } + + +if __name__ == "__main__": + print(json.dumps(TypeAdapter(VariantConfig).json_schema(), indent=2)) diff --git a/variant_schema.json b/variant_schema.json new file mode 100644 index 0000000..b2da2dc --- /dev/null +++ b/variant_schema.json @@ -0,0 +1,201 @@ +{ + "$defs": { + "BothPin": { + "properties": { + "min_pin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Defaults to x.x.x.x.x for pin, change to make less specific", + "title": "Min Pin" + }, + "max_pin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Defaults to x for pin, change to make more specific", + "title": "Max Pin" + } + }, + "title": "BothPin", + "type": "object" + }, + "IfStatement": { + "properties": { + "if": { + "title": "If", + "type": "string" + }, + "then": { + "anyOf": [ + {}, + { + "items": {}, + "type": "array" + } + ], + "title": "Then" + }, + "else": { + "anyOf": [ + {}, + { + "items": {}, + "type": "array" + }, + { + "type": "null" + } + ], + "default": null, + "title": "Else" + } + }, + "required": [ + "if", + "then" + ], + "title": "IfStatement", + "type": "object" + }, + "MaxPin": { + "properties": { + "max_pin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Defaults to x for pin, change to make more specific", + "title": "Max Pin" + } + }, + "title": "MaxPin", + "type": "object" + }, + "MinPin": { + "properties": { + "min_pin": { + "anyOf": [ + { + "type": "string" + }, + { + "type": "null" + } + ], + "default": null, + "description": "Defaults to x.x.x.x.x for pin, change to make less specific", + "title": "Min Pin" + } + }, + "title": "MinPin", + "type": "object" + } + }, + "additionalProperties": { + "anyOf": [ + { + "type": "string" + }, + { + "items": { + "type": "string" + }, + "type": "array" + } + ] + }, + "description": "Usage docs: https://prefix-dev.github.io/rattler-build/variants\n\nSchema for variant configuration file for specifying recipe variants\nfor automating builds.", + "properties": { + "zip_keys": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/IfStatement" + }, + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/IfStatement" + } + ] + }, + "type": "array" + }, + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/IfStatement" + }, + { + "items": { + "anyOf": [ + { + "type": "string" + }, + { + "$ref": "#/$defs/IfStatement" + } + ] + }, + "type": "array" + } + ] + }, + "type": "array" + } + ], + "default": null, + "description": "Zip keys have variant key that has multiple entries which is expanded to a build matrix for variants.", + "title": "Zip Keys" + }, + "pin_run_as_build": { + "additionalProperties": { + "anyOf": [ + { + "$ref": "#/$defs/MaxPin" + }, + { + "$ref": "#/$defs/MinPin" + }, + { + "$ref": "#/$defs/BothPin" + } + ] + }, + "default": null, + "description": "Pinning package versions for variant", + "title": "Pin Run As Build", + "type": "object" + } + }, + "title": "VariantConfig", + "type": "object" +}