diff --git a/.gitignore b/.gitignore index fe358ea54..b535b7bd1 100644 --- a/.gitignore +++ b/.gitignore @@ -136,7 +136,7 @@ dmypy.json # Pyre type checker .pyre/ -#Macaron +# Macaron __pycache__ .pyc .idea diff --git a/Makefile b/Makefile index 13394ddb3..bcf20a4fb 100644 --- a/Makefile +++ b/Makefile @@ -143,22 +143,22 @@ install-slsa-verifier: setup-schemastore: $(PACKAGE_PATH)/resources/schemastore/github-workflow.json $(PACKAGE_PATH)/resources/schemastore/LICENSE $(PACKAGE_PATH)/resources/schemastore/NOTICE $(PACKAGE_PATH)/resources/schemastore/github-workflow.json: cd $(PACKAGE_PATH)/resources \ - && mkdir -p schemastore \ - && cd schemastore \ - && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/src/schemas/json/github-workflow.json \ - && cd $(REPO_PATH) + && mkdir -p schemastore \ + && cd schemastore \ + && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/src/schemas/json/github-workflow.json \ + && cd $(REPO_PATH) $(PACKAGE_PATH)/resources/schemastore/LICENSE: cd $(PACKAGE_PATH)/resources \ - && mkdir -p schemastore \ - && cd schemastore \ - && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/LICENSE \ - && cd $(REPO_PATH) + && mkdir -p schemastore \ + && cd schemastore \ + && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/LICENSE \ + && cd $(REPO_PATH) $(PACKAGE_PATH)/resources/schemastore/NOTICE: cd $(PACKAGE_PATH)/resources \ - && mkdir -p schemastore \ - && cd schemastore \ - && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/NOTICE \ - && cd $(REPO_PATH) + && mkdir -p schemastore \ + && cd schemastore \ + && wget https://raw.githubusercontent.com/SchemaStore/schemastore/a1689388470d1997f2e5ebd8b430e99587b8d354/NOTICE \ + && cd $(REPO_PATH) # Supports OL8+, Fedora 34+, Ubuntu 22.04+ and 24.04+, and macOS. OS := "$(shell uname)" @@ -170,6 +170,7 @@ else OS_MAJOR_VERSION := "$(shell grep '^VERSION=' /etc/os-release | sed -r 's/^[^0-9]+([0-9]+)\..*/\1/')" endif endif + # If Souffle cannot be installed, we advise the user to install it manually # and return status code 0, which is not considered a failure. .PHONY: souffle @@ -227,7 +228,7 @@ gnu-sed: # here instead of `go get -u` to avoid updating indirect dependencies # and creating a broken state: # https://github.com/golang/go/issues/28424#issuecomment-1101896499 -.PHONY: upgrade force-upgrade +.PHONY: upgrade force-upgrade upgrade-quiet upgrade-go upgrade: .venv/upgraded-on .venv/upgraded-on: pyproject.toml python -m pip install --upgrade pip @@ -335,10 +336,9 @@ check-actionlint: check: pre-commit run --all-files - # Run all unit tests. The --files option avoids stashing but passes files; however, # the hook setup itself does not pass files to pytest (see .pre-commit-config.yaml). -.PHONY: test +.PHONY: test test-go test: test-go pre-commit run pytest --hook-stage push --files tests/ test-go: @@ -349,37 +349,37 @@ test-go: .PHONY: integration-test integration-test: if [ "${NO_NPM}" == "TRUE" ]; then \ - echo "Note: NO_NPM environment variable is set to TRUE, so npm tests will be skipped."; \ - python ./tests/integration/run.py \ - run \ - --include-tag macaron-python-package \ - --exclude-tag skip \ - --exclude-tag npm-registry-testcase \ - ./tests/integration/cases/...; \ + echo "Note: NO_NPM environment variable is set to TRUE, so npm tests will be skipped."; \ + python ./tests/integration/run.py \ + run \ + --include-tag macaron-python-package \ + --exclude-tag skip \ + --exclude-tag npm-registry-testcase \ + ./tests/integration/cases/...; \ else \ - python ./tests/integration/run.py \ - run \ - --include-tag macaron-python-package \ - --exclude-tag skip \ - ./tests/integration/cases/...; \ + python ./tests/integration/run.py \ + run \ + --include-tag macaron-python-package \ + --exclude-tag skip \ + ./tests/integration/cases/...; \ fi .PHONY: integration-test-docker integration-test-docker: python ./tests/integration/run.py \ - run \ - --macaron scripts/release_scripts/run_macaron.sh \ - --include-tag macaron-docker-image \ - --exclude-tag skip \ - ./tests/integration/cases/... + run \ + --macaron scripts/release_scripts/run_macaron.sh \ + --include-tag macaron-docker-image \ + --exclude-tag skip \ + ./tests/integration/cases/... # Update the expected results of the integration tests after generating the actual results. .PHONY: integration-test-update integration-test-update: python ./tests/integration/run.py \ - update \ - --exclude-tag skip \ - ./tests/integration/cases/... + update \ + --exclude-tag skip \ + ./tests/integration/cases/... # Build a source distribution package and a binary wheel distribution artifact. # When building these artifacts, we need the environment variable SOURCE_DATE_EPOCH @@ -387,10 +387,10 @@ integration-test-update: .PHONY: dist dist: dist/$(PACKAGE_WHEEL_DIST_NAME).whl dist/$(PACKAGE_SDIST_NAME).tar.gz dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip dist/$(PACKAGE_WHEEL_DIST_NAME)-build-epoch.txt dist/$(PACKAGE_WHEEL_DIST_NAME).whl: check test integration-test - SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) flit build --setup-py --format wheel + SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) python -m flit build --setup-py --format wheel mv dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-py3-none-any.whl dist/$(PACKAGE_WHEEL_DIST_NAME).whl dist/$(PACKAGE_SDIST_NAME).tar.gz: check test integration-test - SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) flit build --setup-py --format sdist + SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH) python -m flit build --setup-py --format sdist dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip: docs python -m zipfile -c dist/$(PACKAGE_NAME)-$(PACKAGE_VERSION)-docs-html.zip docs/_build/html dist/$(PACKAGE_WHEEL_DIST_NAME)-build-epoch.txt: @@ -458,11 +458,13 @@ clean: dist-clean bin-clean docs-clean rm -fr .coverage .hypothesis/ .mypy_cache/ .pytest_cache/ # Remove code caches, or the entire virtual environment if it is deactivated.. -.PHONY: nuke-caches nuke +.PHONY: nuke-git-hooks nuke-caches nuke +nuke-git-hooks: + find .git/hooks/ -type f ! -name '*.sample' -exec rm -fr {} + nuke-caches: clean find src/ -type d -name __pycache__ -exec rm -fr {} + find tests/ -type d -name __pycache__ -exec rm -fr {} + -nuke: nuke-caches +nuke: nuke-git-hooks nuke-caches if [ ! -z "${VIRTUAL_ENV}" ]; then \ echo "Please deactivate the virtual environment first!" && exit 1; \ fi diff --git a/pyproject.toml b/pyproject.toml index acb5fa041..cd4af0d8c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,8 +18,10 @@ maintainers = [ {"name" = "Trong Nhan Mai", "email" = "trong.nhan.mai@oracle.com"}, {"name" = "Behnaz Hassanshahi", "email" = "behnaz.hassanshahi@oracle.com"}, ] -dynamic = ["version", "description"] -license = {file = "LICENSE.txt"} +dynamic = ["version"] +license = "UPL-1.0" # https://spdx.org/licenses/UPL-1.0.html +license-files = ["LICENSE.txt"] +description = "Macaron is an extensible supply-chain security analysis framework from Oracle Labs that supports a wide range of build systems and CI/CD services." readme = "README.md" dependencies = [ "requests >=2.32.3,<3.0.0", @@ -29,26 +31,25 @@ dependencies = [ "jinja2 >=3.1.2,<4.0.0", "SQLAlchemy >=2.0.0,<3.0.0", "defusedxml >=0.7.1,<1.0.0", - "packageurl-python >= 0.11.1,<1.0.0", - "ruamel.yaml >= 0.18.6,<1.0.0", - "jsonschema >= 4.22.0,<5.0.0", + "packageurl-python >=0.11.1,<1.0.0", + "ruamel.yaml >=0.18.6,<1.0.0", + "jsonschema >=4.22.0,<5.0.0", "cyclonedx-bom >=7.0.0,<8.0.0", "cyclonedx-python-lib[validation] >=9.0.0,<12.0.0", - "beautifulsoup4 >= 4.12.0,<5.0.0", - "problog >= 2.2.6,<3.0.0", + "beautifulsoup4 >=4.12.0,<5.0.0", + "problog >=2.2.6,<3.0.0", "cryptography >=46.0.5,<47.0.0", - "semgrep == 1.151.0", + "semgrep ==1.151.0", "email-validator >=2.2.0,<3.0.0", "rich >=13.5.3,<15.0.0", - "lark >= 1.3.0,<2.0.0", - "frozendict >= 2.4.6, <3.0.0", + "lark >=1.3.0,<2.0.0", + "frozendict >=2.4.6,<3.0.0", ] keywords = [] # https://pypi.org/classifiers/ classifiers = [ - "Development Status :: 1 - Planning", + "Development Status :: 4 - Beta", "Intended Audience :: Developers", - "License :: OSI Approved :: Universal Permissive License (UPL)", "Natural Language :: English", "Operating System :: POSIX", "Operating System :: POSIX :: Linux", @@ -79,10 +80,11 @@ dev = [ "types-pyyaml >=6.0.4,<7.0.0", "types-requests >=2.25.6,<3.0.0", "types-jsonschema >=4.22.0,<5.0.0", + "types-defusedxml >=0.7.0,<1.0.0", "pip-audit >=2.5.6,<3.0.0", "pylint >=4.0.4,<5.0.0", "cyclonedx-bom >=7.0.0,<8.0.0", - "types-beautifulsoup4 >= 4.12.0,<5.0.0", + "types-beautifulsoup4 >=4.12.0,<5.0.0", ] docs = [ "sphinx >=8.0.0,<9.0.0", @@ -101,13 +103,14 @@ test = [ "pytest >=9.0.2,<10.0.0", "pytest-custom_exit_code >=0.3.0,<1.0.0", "pytest-cov >=7.0.0,<8.0.0", + "pytest-doctestplus >=1.7.0,<2.0.0", "pytest-env >=1.0.0,<2.0.0", "pytest_httpserver >=1.0.10,<2.0.0", "syrupy >=5.1.0,<6.0.0", ] test-docker = [ - "jsonschema >= 4.22.0,<5.0.0", + "jsonschema >=4.22.0,<5.0.0", "cfgv >=3.4.0,<4.0.0", "ruamel.yaml >=0.18.6,<1.0.0", ] @@ -124,14 +127,14 @@ Issues = "https://github.com/oracle/macaron/issues" [tool.bandit] tests = [] skips = ["B101"] -exclude_dirs = ['tests/malware_analyzer/pypi/resources/sourcecode_samples'] +exclude_dirs = ["tests/malware_analyzer/pypi/resources/sourcecode_samples"] + # https://github.com/psf/black#configuration [tool.black] line-length = 120 -force-exclude = ''' -tests/malware_analyzer/pypi/resources/sourcecode_samples/ -''' +force-exclude = ["tests/malware_analyzer/pypi/resources/sourcecode_samples/"] + # https://github.com/commitizen-tools/commitizen # https://commitizen-tools.github.io/commitizen/bump/ @@ -177,12 +180,14 @@ exclude = [ "SECURITY.md", ] + # https://pycqa.github.io/isort/ [tool.isort] profile = "black" multi_line_output = 3 line_length = 120 skip_gitignore = true +filter_files = true # https://mypy.readthedocs.io/en/stable/config_file.html#using-a-pyproject-toml @@ -201,19 +206,19 @@ disallow_untyped_calls = true disallow_untyped_defs = true disallow_incomplete_defs = true disallow_untyped_decorators = true -disable_error_code = [] +# disable_error_code = [[tool.mypy.overrides]] module = [ - "pytest.*", + "pytest.*", # https://github.com/pytest-dev/pytest/issues/7469 "pydriller.*", "gitdb.*", "yamale.*", - "defusedxml.*", "problog.*", ] ignore_missing_imports = true + # https://pylint.pycqa.org/en/latest/user_guide/configuration/index.html [tool.pylint.MASTER] fail-under = 10.0 @@ -262,10 +267,23 @@ max-line-length = 120 # https://docs.pytest.org/en/latest/reference/customize.html#configuration-file-formats # https://docs.pytest.org/en/latest/reference/reference.html#configuration-options # https://docs.pytest.org/en/latest/reference/reference.html#command-line-flags +# +# To integrate Hypothesis into pytest and coverage, we use its native plugin: +# https://hypothesis.readthedocs.io/en/latest/details.html#the-hypothesis-pytest-plugin +# +# To discover tests in documentation, we use doctest and the doctest-plus plugin which +# adds multiple useful options to control tests in documentation. More details at: +# https://docs.python.org/3/library/doctest.html +# https://github.com/scientific-python/pytest-doctestplus +# +# To avoid failing pytest when no tests were dicovered, we need an extra plugin: +# https://docs.pytest.org/en/latest/reference/exit-codes.html +# https://github.com/yashtodi94/pytest-custom_exit_code [tool.pytest.ini_options] minversion = "7.0" -addopts = """-vv -ra --tb native \ - --doctest-modules --doctest-continue-on-failure --doctest-glob '*.rst' \ +addopts = """-vv -ra --tb native --import-mode importlib \ + --hypothesis-show-statistics --hypothesis-explain --hypothesis-verbosity verbose \ + --doctest-modules --doctest-continue-on-failure --doctest-glob '*.rst' --doctest-plus \ --cov macaron \ --ignore tests/integration \ --ignore tests/malware_analyzer/pypi/resources/sourcecode_samples \ @@ -274,9 +292,11 @@ addopts = """-vv -ra --tb native \ doctest_optionflags = "IGNORE_EXCEPTION_DETAIL" env = [ - "PYTHONWARNINGS=always::DeprecationWarning", + "PYTHONDEVMODE=1", # https://docs.python.org/3/library/devmode.html ] filterwarnings = [ + "error", + "always::DeprecationWarning", # https://docs.pytest.org/en/latest/how-to/failures.html#warning-about-unraisable-exceptions-and-unhandled-thread-exceptions "error::pytest.PytestUnraisableExceptionWarning", "error::pytest.PytestUnhandledThreadExceptionWarning",