Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add menuinst JSON file validation #4405

Merged
merged 46 commits into from
Nov 25, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
46be3ad
add menuinst JSON file validation
jaimergp Mar 24, 2022
4750410
use RECIPE_DIR
jaimergp Mar 29, 2022
d252214
add deps
jaimergp Mar 29, 2022
1ac5588
oopsie typo
jaimergp Mar 29, 2022
33a607a
use pip for now
jaimergp Mar 29, 2022
9d34d9d
add on windows too
jaimergp Mar 29, 2022
08642f1
make sure test can fail
jaimergp Mar 29, 2022
17867b4
revert, it fails as expected!
jaimergp Mar 29, 2022
cc30e69
why timeout=120 on windows?
jaimergp Mar 29, 2022
15cb635
Merge branch 'main' of github.com:conda/conda-build into menuinst-val…
jaimergp Aug 16, 2023
bca2604
pre-commit
jaimergp Aug 16, 2023
b668b00
take from canary channel
jaimergp Aug 21, 2023
aecb2a6
Merge branch 'main' into menuinst-validation
jaimergp Aug 22, 2023
0466d52
--no-deps
jaimergp Aug 22, 2023
42cf630
fix import error
jaimergp Aug 22, 2023
10db4d2
fix tests
jaimergp Aug 22, 2023
5094c60
pre-commit
jaimergp Aug 22, 2023
a3a75ee
require menuinst v2 stable
jaimergp Sep 15, 2023
807e96e
fix backslashes
jaimergp Sep 15, 2023
0906938
use the libmamba solver in env setup
jaimergp Sep 15, 2023
cf32230
exit early
jaimergp Sep 15, 2023
1dd4f09
Merge branch 'main' into menuinst-validation
jaimergp Nov 2, 2023
04dc682
use jsonschema instead of pydantic
jaimergp Nov 2, 2023
72e8ab3
remove solver=libmamba bits
jaimergp Nov 2, 2023
81f824c
pre-commit
jaimergp Nov 2, 2023
76dbc1e
Merge branch 'main' into menuinst-validation
jaimergp Nov 6, 2023
00b2544
do not deduplicate log warnings
jaimergp Nov 6, 2023
41b2b50
make them docstrings
jaimergp Nov 6, 2023
d1250ed
no clear needed here
jaimergp Nov 6, 2023
52e23f8
no print needed here either
jaimergp Nov 6, 2023
1cc30c5
try with libmamba again
jaimergp Nov 7, 2023
bf9ddd7
Merge branch 'main' into menuinst-validation
jaimergp Nov 10, 2023
2ecdb66
Merge branch 'main' into menuinst-validation
jaimergp Nov 20, 2023
8edf934
windows uses pwsh, not batch
jaimergp Nov 20, 2023
b108076
exit on error early by switching to cmd on this step
jaimergp Nov 20, 2023
d5b4318
get latest miniconda to avoid pre 23.11 bugs with history
jaimergp Nov 20, 2023
6c4ba18
revert
jaimergp Nov 20, 2023
fe26449
install menuinst from url on windows
jaimergp Nov 20, 2023
3c7c362
pre-commit
jaimergp Nov 20, 2023
6a5ff45
rename to underscore
jaimergp Nov 20, 2023
84b2749
use copy?
jaimergp Nov 21, 2023
5a7ee7d
debug
jaimergp Nov 21, 2023
f779ae4
gotta use CALL in 'conda' for CMD
jaimergp Nov 21, 2023
122f5f7
fix comment syntax
jaimergp Nov 21, 2023
8be5448
add docstring
jaimergp Nov 22, 2023
dd949a9
lowercase match
jaimergp Nov 22, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ jobs:
runs-on: ubuntu-latest
defaults:
run:
shell: bash -l {0}
shell: bash -el {0}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -125,7 +125,7 @@ jobs:
--file ./tests/requirements.txt \
--file ./tests/requirements-linux.txt \
${{ env.CONDA_CHANNEL_LABEL }}::${{ env.CONDA_VERSION }}
pip install -e .
pip install -e . --no-deps

- name: Show info
run: |
Expand Down Expand Up @@ -224,13 +224,19 @@ jobs:
run-post: false # skip post cleanup

- name: Setup environment
shell: cmd /C CALL {0}
run: |
choco install visualstudio2017-workload-vctools
conda install -q -y -c defaults `
--file .\tests\requirements.txt `
--file .\tests\requirements-windows.txt `
${{ env.CONDA_CHANNEL_LABEL }}::conda
pip install -e .
@echo on
CALL choco install visualstudio2017-workload-vctools || exit 1
CALL conda install -q -y -c defaults ^
--file .\tests\requirements.txt ^
--file .\tests\requirements-windows.txt ^
${{ env.CONDA_CHANNEL_LABEL }}::conda || exit 1
:: TEMPORARY
if "${{ matrix.python-version }}" == "3.8" CALL conda install "https://anaconda.org/conda-forge/menuinst/2.0.0/download/win-64/menuinst-2.0.0-py38hd3f51b4_1.conda" || exit 1
if "${{ matrix.python-version }}" == "3.11" CALL conda install "https://anaconda.org/conda-forge/menuinst/2.0.0/download/win-64/menuinst-2.0.0-py311h12c1d0e_1.conda" || exit 1
:: /TEMPORARY
CALL pip install -e . --no-deps || exit 1

- name: Show info
run: |
Expand Down Expand Up @@ -288,7 +294,7 @@ jobs:
runs-on: macos-11
defaults:
run:
shell: bash -l {0}
shell: bash -el {0}
strategy:
fail-fast: false
matrix:
Expand Down Expand Up @@ -341,7 +347,7 @@ jobs:
--file ./tests/requirements.txt \
--file ./tests/requirements-macos.txt \
${{ env.CONDA_CHANNEL_LABEL }}::conda
pip install -e .
pip install -e . --no-deps

- name: Show info
run: |
Expand Down
60 changes: 60 additions & 0 deletions conda_build/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -1732,6 +1732,65 @@ def fix_permissions(files, prefix):
log.warn(str(e))


def check_menuinst_json(files, prefix) -> None:
"""
Check that Menu/*.json files are valid menuinst v2 JSON documents,
as defined by the CEP-11 schema. This JSON schema is part of the `menuinst`
package.

Validation can fail if the menu/*.json file is not valid JSON, or if it doesn't
comply with the menuinst schema.

We validate at build-time so we don't have to validate at install-time, saving
`conda` a few dependencies.
"""
json_files = fnmatch_filter(files, "[Mm][Ee][Nn][Uu][/\\]*.[Jj][Ss][Oo][Nn]")
if not json_files:
return

print("Validating Menu/*.json files")
travishathaway marked this conversation as resolved.
Show resolved Hide resolved
log = utils.get_logger(__name__, dedupe=False)
try:
import jsonschema
from menuinst.utils import data_path
except ModuleNotFoundError as exc:
log.warning(
"Found 'Menu/*.json' files but couldn't validate: %s",
", ".join(json_files),
exc_info=exc,
)
return

try:
schema_path = data_path("menuinst.schema.json")
with open(schema_path) as f:
schema = json.load(f)
ValidatorClass = jsonschema.validators.validator_for(schema)
validator = ValidatorClass(schema)
except (jsonschema.SchemaError, json.JSONDecodeError, OSError) as exc:
log.warning("'%s' is not a valid menuinst schema", schema_path, exc_info=exc)
return

for json_file in json_files:
try:
with open(join(prefix, json_file)) as f:
text = f.read()
if "$schema" not in text:
log.warning(
"menuinst v1 JSON document '%s' won't be validated.", json_file
)
continue
validator.validate(json.loads(text))
except (jsonschema.ValidationError, json.JSONDecodeError, OSError) as exc:
log.warning(
"'%s' is not a valid menuinst JSON document!",
json_file,
exc_info=exc,
)
else:
log.info("'%s' is a valid menuinst JSON document", json_file)


def post_build(m, files, build_python, host_prefix=None, is_already_linked=False):
print("number of files:", len(files))

Expand Down Expand Up @@ -1765,6 +1824,7 @@ def post_build(m, files, build_python, host_prefix=None, is_already_linked=False
):
post_process_shared_lib(m, f, prefix_files, host_prefix)
check_overlinking(m, files, host_prefix)
check_menuinst_json(files, host_prefix)


def check_symlinks(files, prefix, croot):
Expand Down
2 changes: 2 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ dependencies = [
"six",
"tomli ; python_version<'3.11'",
"tqdm",
"jsonschema >=4.19",
"menuinst >=2"
]
dynamic = ["version"]

Expand Down
2 changes: 2 additions & 0 deletions recipe/meta.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ requirements:
- six
- tomli # [py<311]
- tqdm
- menuinst >=2
- jsonschema >=4.19
run_constrained:
- conda-verify >=3.1.0

Expand Down
2 changes: 2 additions & 0 deletions tests/requirements-linux.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
# TEMP
conda-forge::menuinst >=2
patch
patchelf
shellcheck
2 changes: 2 additions & 0 deletions tests/requirements-macos.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# TEMP
conda-forge::menuinst >=2
patch
shellcheck
1 change: 1 addition & 0 deletions tests/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ cytoolz
filelock
git
jinja2
jsonschema
numpy
perl
pip
Expand Down
23 changes: 23 additions & 0 deletions tests/test-recipes/metadata/_menu_json_validation/menu.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
{
"$schema": "https://json-schema.org/draft-07/schema",
"$id": "https://schemas.conda.io/menuinst-1.schema.json",
"menu_name": "Example 1",
"menu_items": [
{
"name": "Example",
"description": "This will install to Windows and Linux with default options. MacOS has a custom option.",
"command": [
"{{ PYTHON }}",
"-c",
"import sys; print(sys.executable)"
],
"platforms": {
"win": {},
"linux": {},
"osx": {
"CFBundleName": "My Example"
}
}
}
]
}
10 changes: 10 additions & 0 deletions tests/test-recipes/metadata/_menu_json_validation/meta.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package:
name: menu_json_validation
version: "1.0"

build:
script:
- mkdir -p "${PREFIX}/Menu" # [unix]
- cp "${RECIPE_DIR}/menu.json" "${PREFIX}/Menu/menu_json_validation.json" # [unix]
- md "%PREFIX%\\Menu" # [win]
- copy /y "%RECIPE_DIR%\\menu.json" "%PREFIX%\\Menu\\menu_json_validation.json" # [win]
57 changes: 57 additions & 0 deletions tests/test_post.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
# Copyright (C) 2014 Anaconda, Inc
# SPDX-License-Identifier: BSD-3-Clause
import json
import logging
import os
import shutil
import sys
from pathlib import Path

import pytest

Expand Down Expand Up @@ -91,3 +94,57 @@ def test_pypi_installer_metadata(testing_config):
get_site_packages("", "3.9")
)
assert "conda" == (package_has_file(pkg, expected_installer, refresh_mode="forced"))


def test_menuinst_validation_ok(testing_config, caplog, tmp_path):
"1st check - validation passes with recipe as is"
recipe = Path(metadata_dir, "_menu_json_validation")
recipe_tmp = tmp_path / "_menu_json_validation"
shutil.copytree(recipe, recipe_tmp)

with caplog.at_level(logging.INFO):
pkg = api.build(str(recipe_tmp), config=testing_config, notest=True)[0]

captured_text = caplog.text
assert "Found 'Menu/*.json' files but couldn't validate:" not in captured_text
assert "not a valid menuinst JSON file" not in captured_text
assert "is a valid menuinst JSON document" in captured_text
assert package_has_file(pkg, "Menu/menu_json_validation.json")


def test_menuinst_validation_fails_bad_schema(testing_config, caplog, tmp_path):
"2nd check - valid JSON but invalid content fails validation"
recipe = Path(metadata_dir, "_menu_json_validation")
recipe_tmp = tmp_path / "_menu_json_validation"
shutil.copytree(recipe, recipe_tmp)
menu_json = recipe_tmp / "menu.json"
menu_json_contents = menu_json.read_text()

bad_data = json.loads(menu_json_contents)
bad_data["menu_items"][0]["osx"] = ["bad", "schema"]
menu_json.write_text(json.dumps(bad_data, indent=2))
with caplog.at_level(logging.WARNING):
api.build(str(recipe_tmp), config=testing_config, notest=True)

captured_text = caplog.text
assert "Found 'Menu/*.json' files but couldn't validate:" not in captured_text
assert "not a valid menuinst JSON document" in captured_text
assert "ValidationError" in captured_text


def test_menuinst_validation_fails_bad_json(testing_config, caplog, tmp_path):
"3rd check - non-parsable JSON fails validation"
recipe = Path(metadata_dir, "_menu_json_validation")
recipe_tmp = tmp_path / "_menu_json_validation"
shutil.copytree(recipe, recipe_tmp)
menu_json = recipe_tmp / "menu.json"
menu_json_contents = menu_json.read_text()
menu_json.write_text(menu_json_contents + "Make this an invalid JSON")

with caplog.at_level(logging.WARNING):
api.build(str(recipe_tmp), config=testing_config, notest=True)

captured_text = caplog.text
assert "Found 'Menu/*.json' files but couldn't validate:" not in captured_text
assert "not a valid menuinst JSON document" in captured_text
assert "JSONDecodeError" in captured_text
Loading