From 6fe4c5baba6531aefd76317f97f99b0386c88e9b Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 16:31:53 -0500 Subject: [PATCH 01/31] chore(linter): use starbase --- .editorconfig | 43 +++++++++ .gitignore | 36 +++---- .pre-commit-config.yaml | 30 ++++++ .yamllint.yaml | 12 +++ pyproject.toml | 204 +++++++++++++++++++++++++++------------- 5 files changed, 240 insertions(+), 85 deletions(-) create mode 100644 .editorconfig create mode 100644 .pre-commit-config.yaml create mode 100644 .yamllint.yaml diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 000000000..6056a3e84 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,43 @@ +# Editor configuration options. +# See: https://spec.editorconfig.org/ +root = true + +[*] +charset = utf-8 +end_of_line = lf +indent_size = 4 +indent_style = space +insert_final_newline = true +max_line_length = 80 +trim_trailing_whitespace = true + +[.editorconfig] +max_line_length = off + +[Makefile] +indent_style = tab + +[{*.py,*.pyi}] +max_line_length = 88 + +[{*.bash,*.sh,*.zsh}] +indent_size = 2 +tab_width = 2 + +[{*.har,*.json,*.json5}] +indent_size = 2 +max_line_length = off + +[{*.markdown,*.md,*.rst}] +max_line_length = off +ij_visual_guides = none + +[{*.toml,Cargo.lock,Cargo.toml.orig,Gopkg.lock,Pipfile,poetry.lock}] +max_line_length = off + +[{*.ini, *.cfg}] +max_line_length = off + +[{*.yaml,*.yml}] +indent_size = 2 +max_line_length = off diff --git a/.gitignore b/.gitignore index 1149d9bc9..f30802502 100644 --- a/.gitignore +++ b/.gitignore @@ -51,9 +51,6 @@ coverage.xml .hypothesis/ .pytest_cache/ -# Spread tests -.spread-reuse.yaml - # Translations *.mo *.pot @@ -73,6 +70,8 @@ instance/ # Sphinx documentation docs/_build/ +docs/reference/commands +docs/sphinx-starter-pack/ # PyBuilder target/ @@ -131,27 +130,28 @@ dmypy.json # Pyre type checker .pyre/ -# IDE settings -.vscode/ + +# Caches for various tools +/.*_cache/ + +# Test results +/results/ # direnv .direnv .envrc -# snap -rockcraft_*.snap +# Ignore version module generated by setuptools_scm +/*/_version.py -# rock -*.rock +# Visual Studio Code +.vscode/ -# sphinx +# Ignore external tools +/tools/external/ -docs/warnings.txt -docs/.sphinx/.wordlist.dic -docs/reference/commands -.DS_Store -__pycache__ -.idea/ +# snap +rockcraft_*.snap -# build-generated version file -rockcraft/_version.py +# rock +*.rock \ No newline at end of file diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 000000000..e20b479df --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,30 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +repos: + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.4.0 + hooks: + - id: trailing-whitespace + - id: end-of-file-fixer + - id: check-yaml + - id: check-added-large-files + - id: check-merge-conflict + - id: check-toml + - id: fix-byte-order-marker + - id: mixed-line-ending + - repo: https://github.com/charliermarsh/ruff-pre-commit + # renovate: datasource=pypi;depName=ruff + rev: "v0.1.6" + hooks: + - id: ruff + args: [--fix, --exit-non-zero-on-fix] + - repo: https://github.com/psf/black + # renovate: datasource=pypi;depName=black + rev: "23.3.0" + hooks: + - id: black + - repo: https://github.com/adrienverge/yamllint.git + # renovate: datasource=pypi;depName=yamllint + rev: "v1.31.0" + hooks: + - id: yamllint diff --git a/.yamllint.yaml b/.yamllint.yaml new file mode 100644 index 000000000..8f7da7486 --- /dev/null +++ b/.yamllint.yaml @@ -0,0 +1,12 @@ +--- +ignore-from-file: [.gitignore] + +extends: default + +rules: + document-start: disable + float-values: enable + line-length: disable + octal-values: enable + truthy: + check-keys: false diff --git a/pyproject.toml b/pyproject.toml index b4aa0873c..568837712 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,3 +1,18 @@ +[project] +name = "rockcraft" +dynamic = ["version", "readme"] + +classifiers = [ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "Operating System :: MacOS :: MacOS X", + "Operating System :: POSIX :: Linux", + "Programming Language :: Python :: 3", + "Programming Language :: Python :: 3.10", +] +requires-python = ">=3.10" + [build-system] requires = [ "setuptools==67.7.2", @@ -5,6 +20,9 @@ requires = [ ] build-backend = "setuptools.build_meta" +[tool.setuptools.dynamic] +readme = {file = "README.rst"} + [tool.setuptools_scm] write_to = "rockcraft/_version.py" # the version comes from the latest annotated git tag formatted as 'X.Y.Z' @@ -22,6 +40,19 @@ version_scheme = "post-release" # - only match tags formatted as 'X.Y.Z' git_describe_command = "git describe --dirty --long --match '[0-9]*.[0-9]*.[0-9]*' --exclude '*[^0-9.]*'" +[tool.setuptools.packages.find] +include = ["*craft*"] +namespaces = false + +[tool.black] +target-version = ["py310"] + +[tool.codespell] +ignore-words-list = "buildd,crate,keyserver,comandos,ro,dedent,dedented" +skip = ".tox,.git,build,.*_cache,__pycache__,*.tar,*.snap,*.png,./node_modules,./docs/_build,.direnv,.venv,venv,.vscode" +quiet-level = 3 +check-filenames = true + [tool.isort] multi_line_output = 3 include_trailing_comma = true @@ -30,32 +61,51 @@ use_parentheses = true ensure_newline_before_comments = true line_length = 88 -[tool.black] -# Use extend-exclude so that the default excludes (including those from .gitignore) -# are not overwritten. -extend-exclude = "docs/sphinx-starter-pack" +[tool.pytest.ini_options] +minversion = "7.0" +testpaths = "tests" +xfail_strict = true + +[tool.coverage.run] +branch = true +parallel = true +omit = ["tests/**"] + +[tool.coverage.report] +skip_empty = true +exclude_also = [ + "if (typing\\.)?TYPE_CHECKING:", +] [tool.pyright] -ignore = ["docs/sphinx-starter-pack"] +strict = ["rockcraft"] +pythonVersion = "3.10" +pythonPlatform = "Linux" +exclude = [ + "**/.*", + "**/__pycache__", + # pyright might not like the annotations generated by setuptools_scm + "**/_version.py", +] [tool.mypy] -python_version = "3.10" +python_version = "3.8" exclude = [ "build", + "tests", "results", - "tests/spread" ] warn_unused_configs = true warn_redundant_casts = true strict_equality = true strict_concatenate = true -#warn_return_any = true +warn_return_any = true disallow_subclassing_any = true disallow_untyped_decorators = true -#disallow_any_generics = true +disallow_any_generics = true [[tool.mypy.overrides]] -module = ["rockcraft"] +module = ["rockcraft.*"] disallow_untyped_defs = true no_implicit_optional = true @@ -63,37 +113,13 @@ no_implicit_optional = true module = ["tests.*"] strict = false -[[tool.mypy.overrides]] -module = ["craft_archives"] -ignore_missing_imports = true - -[tool.pylint.messages_control] -disable = "too-many-ancestors,too-few-public-methods,fixme,unspecified-encoding,use-implicit-booleaness-not-comparison,unnecessary-lambda-assignment,line-too-long,cyclic-import" - -[tool.pylint.similarities] -min-similarity-lines=12 - -[tool.pylint.format] -max-line-length = "88" -max-locals = 16 - -[tool.pylint.MASTER] -extension-pkg-whitelist = [ - "pydantic" -] -load-plugins = "pylint_pytest" -ignore-paths = [ - "rockcraft/_version.py" -] - [tool.ruff] line-length = 88 -target-version = "py38" +target-version = "py310" src = ["rockcraft", "tests"] extend-exclude = [ "docs", "__pycache__", - "rockcraft/_version.py" ] # Follow ST063 - Maintaining and updating linting specifications for updating these. select = [ # Base linting rule selections. @@ -103,49 +129,74 @@ select = [ # Base linting rule selections. # failures with ruff updates. "F", # The rules built into Flake8 "E", "W", # pycodestyle errors and warnings - "D", # Implement pydocstyle checking as well. "I", # isort checking - "PLC", "PLE", "PLR", "PLW", # Pylint "N", # PEP8 naming + "D", # Implement pydocstyle checking as well. + "UP", # Pyupgrade - note that some of are excluded below due to Python versions "YTT", # flake8-2020: Misuse of `sys.version` and `sys.version_info` + "ANN", # Type annotations. + "ASYNC", # Catching blocking calls in async functions + # flake8-bandit: security testing. https://docs.astral.sh/ruff/rules/#flake8-bandit-s + # https://bandit.readthedocs.io/en/latest/plugins/index.html#complete-test-plugin-listing + "S101", "S102", # assert or exec + "S103", "S108", # File permissions and tempfiles - use #noqa to silence when appropriate. + "S104", # Network binds + "S105", "S106", "S107", # Hardcoded passwords + "S110", # try-except-pass (use contextlib.suppress instead) + "S113", # Requests calls without timeouts + "S3", # Serialising, deserialising, hashing, crypto, etc. + "S5", # Unsafe cryptography or YAML loading. + "S602", # Subprocess call with shell=true + "S701", # jinja2 templates without autoescape "BLE", # Do not catch blind exceptions "FBT", # Disallow boolean positional arguments (make them keyword-only) + "B0", # Common mistakes and typos. "A", # Shadowing built-ins. + "COM", # Trailing commas "C4", # Encourage comprehensions, which tend to be faster than alternatives. "T10", # Don't call the debugger in production code "ISC", # Implicit string concatenation that can cause subtle issues "ICN", # Only use common conventions for import aliases. + "INP", # Implicit namespace packages + # flake8-pie: miscellaneous linters (enabled individually because they're not really related) + "PIE790", # Unnecessary pass statement + "PIE794", # Multiple definitions of class field + "PIE796", # Duplicate value in an enum (reasonable to noqa for backwards compatibility) + "PIE804", # Don't use a dict with unnecessary kwargs + "PIE807", # prefer `list` over `lambda: []` + "PIE810", # Use a tuple rather than multiple calls. E.g. `mystr.startswith(("Hi", "Hello"))` + "PYI", # Linting for type stubs. + "PT", # Pytest "Q", # Consistent quotations + "RSE", # Errors on pytest raises. "RET", # Simpler logic after return, raise, continue or break - "UP018", "C408", # Convert type calls to literals. The latest pylint enforces this, but ruff has auto-fixes. -] -extend-select = [ - # These sets are still frequently getting new rules. - # Therefore, they're getting frozen with the current rules so we can - # upgrade ruff without breaking linting. - # Pyupgrade: https://github.com/charliermarsh/ruff#pyupgrade-up - "UP00", "UP01", "UP02", "UP030", "UP032", "UP033", - # "UP034", # Very new, not yet enabled in ruff 0.0.227 - # Annotations: https://github.com/charliermarsh/ruff#flake8-annotations-ann - "ANN0", # Type annotations for arguments other than `self` and `cls` - "ANN2", # Return type annotations - # flake8-bandit: security testing. https://github.com/charliermarsh/ruff#flake8-bandit-s - # https://bandit.readthedocs.io/en/latest/plugins/index.html#complete-test-plugin-listing - "S101", "S102", # assert or exec - "S103", "S108", # File permissions and tempfiles - use #noqa to silence when appropriate. - "S104", # Network binds - "S105", "S106", "S107", # Hardcoded passwords - "S113", # Requests calls without timeouts - "S506", # Unsafe YAML load - "S508", "S509", # Insecure SNMP - "S701", # jinja2 templates without autoescape - "B0", # Common mistakes and typos. + "SLF", # Prevent accessing private class members. + "SIM", # Code simplification + "TID", # Tidy imports + # The team have chosen to only use type-checking blocks when necessary to prevent circular imports. + # As such, the only enabled type-checking checks are those that warn of an import that needs to be + # removed from a type-checking block. + "TCH004", # Remove imports from type-checking guard blocks if used at runtime + "TCH005", # Delete empty type-checking blocks + "ARG", # Unused arguments + "PTH", # Migrate to pathlib + "FIX", # All TODOs, FIXMEs, etc. should be turned into issues instead. + "ERA", # Don't check in commented out code + "PGH", # Pygrep hooks + "PL", # Pylint + "TRY", # Cleaner try/except, + "FLY", # Detect things that would be better as f-strings. + "PERF", # Catch things that can slow down the application like unnecessary casts to list. "RUF001", "RUF002", "RUF003", # Ambiguous unicode characters - "B026", # Keyword arguments must come after starred arguments "RUF005", # Encourages unpacking rather than concatenation - "RUF100", # #noqa directive that doesn't flag anything. + "RUF008", # Do not use mutable default values for dataclass attributes + "RUF011", # Don't use static keys in dict comprehensions. + "RUF013", # Prohibit implicit Optionals (PEP 484) + "RUF100", # #noqa directive that doesn't flag anything + "RUF200", # If ruff fails to parse pyproject.toml... ] ignore = [ + "ANN10", # Type annotations for `self` and `cls` #"E203", # Whitespace before ":" -- Commented because ruff doesn't currently check E203 "E501", # Line too long (reason: black will automatically fix this for us) "D105", # Missing docstring in magic method (reason: magic methods already have definitions) @@ -154,18 +205,37 @@ ignore = [ "D213", # Multi-line docstring summary should start at the second line (reason: pep257 default) "D215", # Section underline is over-indented (reason: pep257 default) "A003", # Class attribute shadowing built-in (reason: Class attributes don't often get bare references) + "SIM117", # Use a single `with` statement with multiple contexts instead of nested `with` statements + # (reason: this creates long lines that get wrapped and reduces readability) + # Ignored due to common usage in current code + "TRY003", # Avoid specifying long messages outside the exception class + "COM812", # Allow trailing comma missing ] +[tool.ruff.flake8-annotations] +allow-star-arg-any = true + +[tool.ruf.lint.pylint] +max-args = 8 + +[tool.ruff.pep8-naming] +# Allow Pydantic's `@validator` decorator to trigger class method treatment. +classmethod-decorators = ["pydantic.validator", "pydantic.root_validator"] + [tool.ruff.per-file-ignores] "tests/**.py" = [ # Some things we want for the moin project are unnecessary in tests. "D", # Ignore docstring rules in tests - "ANN", # Ignore type annotations in tests + "ANN", # Ignore type annotations in tests + "ARG", # Allow unused arguments in tests (e.g. for fake functions/methods/classes) "S101", # Allow assertions in tests "S103", # Allow `os.chmod` setting a permissive mask `0o555` on file or directory "S108", # Allow Probable insecure usage of temporary file or directory - "PLR0913", # Allow many arguments for test functions + "PLR0913", # Allow many arguments for test functions (useful if we need many fixtures) + "PLR2004", # Allow magic values in tests + "SLF", # Allow accessing private members from tests. ] -# isort leaves init files alone by default, this makes ruff ignore them too. -"__init__.py" = ["I001"] - +"__init__.py" = [ + "I001", # isort leaves init files alone by default, this makes ruff ignore them too. + "F401", # Allows unused imports in __init__ files. +] \ No newline at end of file From 42670bacb79cf672d6a15d63036717b674d4e759 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 16:36:13 -0500 Subject: [PATCH 02/31] chore(deps): sync dev deps with starbase --- requirements-dev.txt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/requirements-dev.txt b/requirements-dev.txt index 31f288f77..5ae501b09 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -3,7 +3,7 @@ astroid==2.15.8 autoflake==1.7.8 Babel==2.13.1 beautifulsoup4==4.12.2 -black==23.10.1 +black==23.11.0 bracex==2.4 cachetools==5.3.2 certifi==2023.7.22 @@ -11,7 +11,7 @@ cffi==1.16.0 chardet==5.2.0 charset-normalizer==3.3.1 click==8.1.7 -codespell==2.2.6 +codespell[toml]==2.2.6 colorama==0.4.6 coverage==7.3.2 craft-application==1.0.0 @@ -53,7 +53,7 @@ MarkupSafe==2.1.3 mccabe==0.6.1 mdurl==0.1.2 more-itertools==10.1.0 -mypy==1.6.1 +mypy[reports]==1.7.1 mypy-extensions==1.0.0 nh3==0.2.14 oauthlib==3.2.2 @@ -76,9 +76,11 @@ pylint-fixme-info==1.0.3 pylint-pytest==1.1.3 pyparsing==3.1.1 pyproject-api==1.6.1 +pyright==1.1.337 pyspelling==2.9 pytest==7.4.3 pytest-check==2.2.2 +pytest-cov==4.1.0 pytest-mock==3.12.0 pytest-subprocess==1.5.0 pyxdg==0.28 @@ -133,4 +135,5 @@ wadllib==1.3.6 wcmatch==8.5 webencodings==0.5.1 wrapt==1.15.0 +yamllint==1.33.0 zipp==3.17.0 From ab041634ce680e8c98635eafa4cdd2bae72e14e3 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 16:56:21 -0500 Subject: [PATCH 03/31] chore(tests): update tox.ini with starbase --- requirements-jammy.txt | 2 +- tox.ini | 129 +++++++++++++++++++++++++++++++++++------ 2 files changed, 112 insertions(+), 19 deletions(-) diff --git a/requirements-jammy.txt b/requirements-jammy.txt index 13c377195..cf0946ab0 100644 --- a/requirements-jammy.txt +++ b/requirements-jammy.txt @@ -1 +1 @@ -https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys_platform == 'linux' +python-apt@https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys_platform == 'linux' diff --git a/tox.ini b/tox.ini index 5072c7cf9..a88ae0277 100644 --- a/tox.ini +++ b/tox.ini @@ -1,6 +1,13 @@ [tox] env_list = # Environments to run when called with no parameters. -minversion = 4.3.5 + format-{black,ruff,codespell} + pre-commit + lint-{black,ruff,mypy,pyright,shellcheck,codespell,docs,yaml} + unit-py3.{10,11} + integration-py3.10 +# Integration tests probably take a while, so we're only running them on Python +# 3.10, which is included in core22. +minversion = 4.6 # Tox will use these requirements to bootstrap a venv if necessary. # tox-igore-env-name-mismatch allows us to have one virtualenv for all linting. # By setting requirements here, we make this INI file compatible with older @@ -9,7 +16,9 @@ minversion = 4.3.5 # install tox from apt. Older than that, the user gets an upgrade warning. requires = # renovate: datasource=pypi - tox-ignore-env-name-mismatch==0.2.0.post2 + tox-ignore-env-name-mismatch>=0.2.0.post2 + # renovate: datasource=pypi + tox-gh==1.3.1 # Allow tox to access the user's $TMPDIR environment variable if set. # This workaround is required to avoid circular dependencies for TMPDIR, # since tox will otherwise attempt to use the environment's TMPDIR variable. @@ -19,42 +28,126 @@ user_tmp_dir = {env:TMPDIR} # We have many tests that create temporary files. Unless the user has set a # TMPDIR, this will prefer putting those temp files in $XDG_RUNTIME_DIR, # which will speed up those tests since they'll run on a ramdisk. +deps = + -r requirements.txt + -r requirements-dev.txt + -r requirements-jammy.txt env_tmp_dir = {user_tmp_dir:{env:XDG_RUNTIME_DIR:{work_dir}}}/tox_tmp/{env_name} set_env = TMPDIR={env_tmp_dir} COVERAGE_FILE={env_tmp_dir}/.coverage_{env_name} +pass_env = + CI + CRAFT_* + PYTEST_ADDOPTS + +[test] # Base configuration for unit and integration tests +package = editable +extras = dev +allowlist_externals = mkdir +commands_pre = mkdir -p {tox_root}/results + +[testenv:{unit,integration}-py3.{10,11,12}] # Configuration for all tests using pytest +base = testenv, test +description = + unit: Run unit tests with pytest + integration: Run integration tests with pytest +labels = + py3.{10,11}: tests + unit-py3.{10,11}: unit-tests + integration-py3.{10,11}: integration-tests +change_dir = + unit: tests/unit + integration: tests/integration +commands = pytest {tty:--color=yes} --cov={tox_root}/starcraft --cov-config={tox_root}/pyproject.toml --cov-report=xml:{tox_root}/results/coverage-{env_name}.xml --junit-xml={tox_root}/results/test-results-{env_name}.xml {posargs} + +[lint] # Standard linting configuration +package = editable +extras = lint +env_dir = {work_dir}/linting +runner = ignore_env_name_mismatch + +[shellcheck] +find = git ls-files +filter = file --mime-type -Nnf- | grep shellscript | cut -f1 -d: + +[testenv:lint-{black,ruff,shellcheck,codespell,yaml}] +description = Lint the source code +base = testenv, lint +labels = lint +allowlist_externals = + shellcheck: bash, xargs +commands_pre = + shellcheck: bash -c '{[shellcheck]find} | {[shellcheck]filter} > {env_tmp_dir}/shellcheck_files' +commands = + black: black --check --diff {tty:--color} {posargs} . + ruff: ruff check --respect-gitignore {posargs:.} + shellcheck: xargs -ra {env_tmp_dir}/shellcheck_files shellcheck + codespell: codespell --toml {tox_root}/pyproject.toml {posargs} + yaml: yamllint {posargs} . + +[testenv:lint-{mypy,pyright}] +description = Static type checking +base = testenv, lint +env_dir = {work_dir}/typing +extras = dev, types +labels = lint, type +allowlist_externals = + mypy: mkdir +commands_pre = + mypy: mkdir -p {tox_root}/.mypy_cache +commands = + pyright: pyright {posargs} + mypy: mypy --install-types --non-interactive {posargs:.} + +[testenv:format-{black,ruff,codespell}] +description = Automatically format source code +base = testenv, lint +labels = format +commands = + black: black {tty:--color} {posargs} . + ruff: ruff --fix --respect-gitignore {posargs} . + codespell: codespell --toml {tox_root}/pyproject.toml --write-changes {posargs} + +[testenv:pre-commit] +base = +deps = pre-commit +package = skip +no_package = true +env_dir = {work_dir}/pre-commit +runner = ignore_env_name_mismatch +description = Run pre-commit on staged files or arbitrary pre-commit commands (tox run -e pre-commit -- [args]) +commands = pre-commit {posargs:run} [docs] # Sphinx documentation configuration -deps = -r requirements-jammy.txt -extras = doc +deps = -r requirements-docs.txt +extras = docs package = editable no_package = true env_dir = {work_dir}/docs runner = ignore_env_name_mismatch -allowlist_externals = bash -commands_pre = - bash -c 'if [[ ! -e docs ]];then echo "No docs directory. Run `tox run -e sphinx-quickstart` to create one.";return 1;fi' - -[testenv:link-docs-pkg] -description = - Use a local (editable) dependency package for documentation rather than the version in requirements. - To run: `tox run -e link-docs-pkg -- [repo_directory]` -base = docs -commands = pip install -e {posargs} +source_dir = {tox_root}/{project_name} [testenv:build-docs] description = Build sphinx documentation base = docs +allowlist_externals = bash +commands_pre = bash -c 'if [[ ! -e docs ]];then echo "No docs directory. Run `tox run -e sphinx-quickstart` to create one.;";return 1;fi' # "-W" is to treat warnings as errors -commands = sphinx-build {posargs:-b html} {tox_root}/docs {tox_root}/docs/_build +commands = sphinx-build {posargs:-b html} -W {tox_root}/docs {tox_root}/docs/_build [testenv:autobuild-docs] description = Build documentation with an autoupdating server base = docs -commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} --watch {tox_root}/rockcraft {tox_root}/docs {tox_root}/docs/_build +commands = sphinx-autobuild {posargs:-b html --open-browser --port 8080} -W --watch {source_dir} {tox_root}/docs {tox_root}/docs/_build + +[lint-docs] +find = git ls-files [testenv:lint-docs] description = Lint the documentation with sphinx-lint base = docs -commands = sphinx-lint --ignore docs/_build -e all {posargs} docs/ -labels = lint \ No newline at end of file +labels = lint +allowlist_externals = bash, xargs +commands_pre = bash -c '{[lint-docs]find} > {env_tmp_dir}/lint_docs_files' +commands = xargs --no-run-if-empty --arg-file {env_tmp_dir}/lint_docs_files sphinx-lint --max-line-length 80 --enable all --ignore "docs/sphinx-starter-pack/" {posargs} \ No newline at end of file From c20d6d5c09484afd4ce3e6a1912b23bce89dcc45 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 17:04:12 -0500 Subject: [PATCH 04/31] chore(linter): update to 3.10 style --- rockcraft/application.py | 2 - rockcraft/architectures.py | 2 - rockcraft/commands/extensions.py | 7 +- rockcraft/extensions/_utils.py | 14 +-- rockcraft/extensions/extension.py | 15 +-- rockcraft/extensions/registry.py | 8 +- rockcraft/layers.py | 31 +++--- rockcraft/models/project.py | 94 ++++++++----------- rockcraft/oci.py | 34 +++---- rockcraft/parts.py | 6 +- rockcraft/pebble.py | 75 +++++++-------- rockcraft/plugins/python_plugin.py | 5 +- rockcraft/services/image.py | 2 - rockcraft/services/lifecycle.py | 2 - rockcraft/services/package.py | 2 - rockcraft/services/provider.py | 3 - rockcraft/services/service_factory.py | 2 - rockcraft/utils.py | 4 +- .../integration/plugins/test_python_plugin.py | 2 +- tests/integration/test_oci.py | 6 +- tests/testing/lifecycle.py | 1 - tests/unit/conftest.py | 2 +- tests/unit/test_layers.py | 5 +- tests/unit/test_oci.py | 6 +- tests/unit/test_pebble.py | 50 +++++----- tests/unit/test_project.py | 4 +- tests/unit/testing/extensions.py | 24 ++--- 27 files changed, 187 insertions(+), 221 deletions(-) diff --git a/rockcraft/application.py b/rockcraft/application.py index f724f7171..1bb199619 100644 --- a/rockcraft/application.py +++ b/rockcraft/application.py @@ -16,8 +16,6 @@ """Main Rockcraft Application.""" -from __future__ import annotations - from typing import Any from craft_application import Application, AppMetadata, util diff --git a/rockcraft/architectures.py b/rockcraft/architectures.py index 118fc6cc1..2abc00753 100644 --- a/rockcraft/architectures.py +++ b/rockcraft/architectures.py @@ -15,8 +15,6 @@ # along with this program. If not, see . """Architecture definitions and conversions for Debian and Go/Docker.""" -from __future__ import annotations - import dataclasses diff --git a/rockcraft/commands/extensions.py b/rockcraft/commands/extensions.py index c2c93142c..91b43e468 100644 --- a/rockcraft/commands/extensions.py +++ b/rockcraft/commands/extensions.py @@ -20,7 +20,6 @@ import argparse import textwrap from pathlib import Path -from typing import Dict, List import tabulate from craft_application.commands import AppCommand @@ -36,9 +35,9 @@ class ExtensionModel(BaseModel): """Extension model for presentation.""" name: str - bases: List[str] + bases: list[str] - def marshal(self) -> Dict[str, str]: + def marshal(self) -> dict[str, str]: """Marshal model into a dictionary for presentation.""" return { "Extension name": self.name, @@ -60,7 +59,7 @@ class ListExtensionsCommand(AppCommand, abc.ABC): @overrides def run(self, parsed_args: argparse.Namespace): """Print the list of available extensions and their bases.""" - extension_presentation: Dict[str, ExtensionModel] = {} + extension_presentation: dict[str, ExtensionModel] = {} for extension_name in extensions.registry.get_extension_names(): extension_class = extensions.registry.get_extension_class(extension_name) diff --git a/rockcraft/extensions/_utils.py b/rockcraft/extensions/_utils.py index ac9a28e7a..f66ff62e6 100644 --- a/rockcraft/extensions/_utils.py +++ b/rockcraft/extensions/_utils.py @@ -18,13 +18,13 @@ import copy from pathlib import Path -from typing import Any, Dict, List, Set, cast +from typing import Any, cast from .extension import Extension from .registry import get_extension_class -def apply_extensions(project_root: Path, yaml_data: Dict[str, Any]) -> Dict[str, Any]: +def apply_extensions(project_root: Path, yaml_data: dict[str, Any]) -> dict[str, Any]: """Apply all extensions. :param dict yaml_data: Loaded, unprocessed rockcraft.yaml @@ -32,7 +32,7 @@ def apply_extensions(project_root: Path, yaml_data: Dict[str, Any]) -> Dict[str, """ # Don't modify the dict passed in yaml_data = copy.deepcopy(yaml_data) - declared_extensions: List[str] = cast(List[str], yaml_data.get("extensions", [])) + declared_extensions: list[str] = cast(list[str], yaml_data.get("extensions", [])) if not declared_extensions: return yaml_data @@ -50,7 +50,7 @@ def apply_extensions(project_root: Path, yaml_data: Dict[str, Any]) -> Dict[str, def _apply_extension( - yaml_data: Dict[str, Any], + yaml_data: dict[str, Any], extension: Extension, ) -> None: # Apply the root components of the extension (if any) @@ -102,10 +102,10 @@ def _apply_extension_property(existing_property: Any, extension_property: Any) - return extension_property -def _remove_list_duplicates(seq: List[str]) -> List[str]: +def _remove_list_duplicates(seq: list[str]) -> list[str]: """De-dupe string list maintaining ordering.""" - seen: Set[str] = set() - deduped: List[str] = [] + seen: set[str] = set() + deduped: list[str] = [] for item in seq: if item not in seen: diff --git a/rockcraft/extensions/extension.py b/rockcraft/extensions/extension.py index 96de34999..43a2ce127 100644 --- a/rockcraft/extensions/extension.py +++ b/rockcraft/extensions/extension.py @@ -20,7 +20,8 @@ import os import sys from pathlib import Path -from typing import Any, Dict, Optional, Sequence, Tuple, final +from typing import Any, final +from collections.abc import Sequence from craft_cli import emit @@ -41,7 +42,7 @@ def __init__( self, *, project_root: Path, - yaml_data: Dict[str, Any], + yaml_data: dict[str, Any], ) -> None: """Create a new Extension.""" self.project_root = project_root @@ -49,24 +50,24 @@ def __init__( @staticmethod @abc.abstractmethod - def get_supported_bases() -> Tuple[str, ...]: + def get_supported_bases() -> tuple[str, ...]: """Return a tuple of supported bases.""" @staticmethod @abc.abstractmethod - def is_experimental(base: Optional[str]) -> bool: + def is_experimental(base: str | None) -> bool: """Return whether or not this extension is unstable for given base.""" @abc.abstractmethod - def get_root_snippet(self) -> Dict[str, Any]: + def get_root_snippet(self) -> dict[str, Any]: """Return the root snippet to apply.""" @abc.abstractmethod - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self) -> dict[str, Any]: """Return the part snippet to apply to existing parts.""" @abc.abstractmethod - def get_parts_snippet(self) -> Dict[str, Any]: + def get_parts_snippet(self) -> dict[str, Any]: """Return the parts to add to parts.""" @final diff --git a/rockcraft/extensions/registry.py b/rockcraft/extensions/registry.py index d48b59744..98c7f4563 100644 --- a/rockcraft/extensions/registry.py +++ b/rockcraft/extensions/registry.py @@ -16,19 +16,19 @@ """Extension registry.""" -from typing import TYPE_CHECKING, Dict, List, Type +from typing import TYPE_CHECKING from rockcraft import errors if TYPE_CHECKING: from .extension import Extension - ExtensionType = Type[Extension] + ExtensionType = type[Extension] -_EXTENSIONS: Dict[str, "ExtensionType"] = {} +_EXTENSIONS: dict[str, "ExtensionType"] = {} -def get_extension_names() -> List[str]: +def get_extension_names() -> list[str]: """Obtain a extension class given the name. :param name: The extension name. diff --git a/rockcraft/layers.py b/rockcraft/layers.py index b8a2766f0..2c6fae7d9 100644 --- a/rockcraft/layers.py +++ b/rockcraft/layers.py @@ -19,7 +19,6 @@ import tarfile from collections import defaultdict from pathlib import Path -from typing import DefaultDict, Dict, List, Optional, Set, Tuple from craft_cli import emit from craft_parts.executor.collisions import paths_collide @@ -32,7 +31,7 @@ def archive_layer( new_layer_dir: Path, temp_tar_file: Path, - base_layer_dir: Optional[Path] = None, + base_layer_dir: Path | None = None, ) -> None: """Prepare new OCI layer by archiving its content into tar file. @@ -55,7 +54,7 @@ def archive_layer( tar_file.add(filepath, arcname=arcname, recursive=False) -def prune_prime_files(prime_dir: Path, files: Set[str], base_layer_dir: Path) -> None: +def prune_prime_files(prime_dir: Path, files: set[str], base_layer_dir: Path) -> None: """Remove (prune) files in a prime directory if they exist in the base layer. Given a set of filenames ``files``, this function will remove (prune) all those @@ -87,8 +86,8 @@ def prune_prime_files(prime_dir: Path, files: Set[str], base_layer_dir: Path) -> def _gather_layer_paths( - new_layer_dir: Path, base_layer_dir: Optional[Path] = None -) -> Dict[str, List[Path]]: + new_layer_dir: Path, base_layer_dir: Path | None = None +) -> dict[str, list[Path]]: """Map paths in ``new_layer_dir`` to names in a layer file. See ``_archive_layer()`` for the parameters. @@ -132,7 +131,7 @@ def get_target_path(self, path: Path) -> Path: return path layer_linker = LayerLinker() - result: DefaultDict[str, List[Path]] = defaultdict(list) + result: defaultdict[str, list[Path]] = defaultdict(list) for dirpath, subdirs, filenames in os.walk(new_layer_dir): # Sort `subdirs` in-place, to ensure that `os.walk()` iterates on # them in sorted order. @@ -180,7 +179,7 @@ def get_target_path(self, path: Path) -> Path: return result -def _merge_layer_paths(candidate_paths: Dict[str, List[Path]]) -> Dict[str, Path]: +def _merge_layer_paths(candidate_paths: dict[str, list[Path]]) -> dict[str, Path]: """Merge ``candidate_paths`` into a single path per name. This function handles the case where multiple paths refer to the same name @@ -192,7 +191,7 @@ def _merge_layer_paths(candidate_paths: Dict[str, List[Path]]) -> Dict[str, Path A dict where the values are Paths and the keys are the names those paths correspond to in the new layer. """ - result: Dict[str, Path] = {} + result: dict[str, Path] = {} for name, paths in candidate_paths.items(): if len(paths) == 1: @@ -224,8 +223,8 @@ def _merge_layer_paths(candidate_paths: Dict[str, List[Path]]) -> Dict[str, Path def _symlink_target_in_base_layer( - relative_path: Path, base_layer_dir: Optional[Path] -) -> Optional[Path]: + relative_path: Path, base_layer_dir: Path | None +) -> Path | None: """If `relative_path` is a dir symlink in `base_layer_dir`, return its 'target'. This function checks if `relative_path` exists in the base `base_layer_dir` as @@ -246,7 +245,7 @@ def _symlink_target_in_base_layer( return None -def _all_compatible_directories(paths: List[Path]) -> bool: +def _all_compatible_directories(paths: list[Path]) -> bool: """Whether ``paths`` contains only directories with the same ownership and permissions.""" if not all(p.is_dir() for p in paths): return False @@ -254,7 +253,7 @@ def _all_compatible_directories(paths: List[Path]) -> bool: if len(paths) < 2: return True - def stat_props(stat: os.stat_result) -> Tuple[int, int, int]: + def stat_props(stat: os.stat_result) -> tuple[int, int, int]: return stat.st_uid, stat.st_gid, stat.st_mode first_stat = stat_props(paths[0].stat()) @@ -263,17 +262,15 @@ def stat_props(stat: os.stat_result) -> Tuple[int, int, int]: other_stat = stat_props(other_path.stat()) if first_stat != other_stat: emit.debug( - ( - f"Path attributes differ for '{paths[0]}' and '{other_path}': " - f"{first_stat} vs {other_stat}" - ) + f"Path attributes differ for '{paths[0]}' and '{other_path}': " + f"{first_stat} vs {other_stat}" ) return False return True -def _all_compatible_files(paths: List[Path]) -> bool: +def _all_compatible_files(paths: list[Path]) -> bool: """Whether ``paths`` contains only files with the same attributes and contents.""" if not all(p.is_file() for p in paths): return False diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 42a2f1b9b..0c97b2031 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -21,16 +21,10 @@ from typing import ( TYPE_CHECKING, Any, - Dict, - List, Literal, - Mapping, - Optional, - Sequence, - Tuple, - Union, cast, ) +from collections.abc import Mapping, Sequence import craft_cli import pydantic @@ -56,12 +50,8 @@ class Platform(pydantic.BaseModel): """Rockcraft project platform definition.""" - build_on: Optional[ # type: ignore - pydantic.conlist(str, unique_items=True, min_items=1) # type: ignore - ] - build_for: Optional[ # type: ignore - pydantic.conlist(str, unique_items=True, min_items=1) # type: ignore - ] + build_on: pydantic.conlist(str, unique_items=True, min_items=1) | None + build_for: pydantic.conlist(str, unique_items=True, min_items=1) | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -71,7 +61,7 @@ class Config: # pylint: disable=too-few-public-methods @pydantic.validator("build_for", pre=True) @classmethod - def _vectorise_build_for(cls, val: Union[str, List[str]]) -> List[str]: + def _vectorise_build_for(cls, val: str | list[str]) -> list[str]: """Vectorise target architecture if needed.""" if isinstance(val, str): val = [val] @@ -136,18 +126,18 @@ class Project(YamlModelMixin, BaseProject): summary: str # type: ignore description: str rock_license: str = pydantic.Field(alias="license") - platforms: Dict[str, Any] + platforms: dict[str, Any] base: Literal["bare", "ubuntu@20.04", "ubuntu@22.04"] - build_base: Optional[Literal["ubuntu@20.04", "ubuntu@22.04"]] - environment: Optional[Dict[str, str]] - run_user: Optional[Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)]] # type: ignore - services: Optional[Dict[str, Service]] - checks: Optional[Dict[str, Check]] - entrypoint_service: Optional[str] + build_base: Literal["ubuntu@20.04", "ubuntu@22.04"] | None + environment: dict[str, str] | None + run_user: Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)] | None # type: ignore + services: dict[str, Service] | None + checks: dict[str, Check] | None + entrypoint_service: str | None - package_repositories: Optional[List[Dict[str, Any]]] + package_repositories: list[dict[str, Any]] | None - parts: Dict[str, Any] + parts: dict[str, Any] class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -193,7 +183,7 @@ def _validate_license(cls, rock_license: str) -> str: # This is the license name we use on our stores. return rock_license - lic: Optional[spdx_lookup.License] = spdx_lookup.by_id(rock_license) + lic: spdx_lookup.License | None = spdx_lookup.by_id(rock_license) if lic is None: raise ProjectValidationError( f"License {rock_license} not valid. It must be valid and in SPDX format." @@ -202,7 +192,7 @@ def _validate_license(cls, rock_license: str) -> str: @pydantic.validator("title", always=True) @classmethod - def _validate_title(cls, title: Optional[str], values: Mapping[str, Any]) -> str: + def _validate_title(cls, title: str | None, values: Mapping[str, Any]) -> str: """If title is not provided, it defaults to the provided ROCK name.""" if not title: title = values.get("name", "") @@ -210,7 +200,7 @@ def _validate_title(cls, title: Optional[str], values: Mapping[str, Any]) -> str @pydantic.validator("build_base", always=True) @classmethod - def _validate_build_base(cls, build_base: Optional[str], values: Any) -> str: + def _validate_build_base(cls, build_base: str | None, values: Any) -> str: """Build-base defaults to the base value if not specified. :raises ProjectValidationError: If base validation fails. @@ -226,20 +216,16 @@ def _validate_build_base(cls, build_base: Optional[str], values: Any) -> str: @pydantic.validator("base", pre=True) @classmethod - def _validate_deprecated_base(cls, base_value: Optional[str]) -> Optional[str]: + def _validate_deprecated_base(cls, base_value: str | None) -> str | None: return cls._check_deprecated_base(base_value, "base") @pydantic.validator("build_base", pre=True) @classmethod - def _validate_deprecated_build_base( - cls, base_value: Optional[str] - ) -> Optional[str]: + def _validate_deprecated_build_base(cls, base_value: str | None) -> str | None: return cls._check_deprecated_base(base_value, "build_base") @staticmethod - def _check_deprecated_base( - base_value: Optional[str], field_name: str - ) -> Optional[str]: + def _check_deprecated_base(base_value: str | None, field_name: str) -> str | None: if base_value in DEPRECATED_COLON_BASES: at_value = base_value.replace(":", "@") message = ( @@ -253,7 +239,7 @@ def _check_deprecated_base( @pydantic.validator("platforms") @classmethod - def _validate_all_platforms(cls, platforms: Dict[str, Any]) -> Dict[str, Any]: + def _validate_all_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]: """Make sure all provided platforms are tangible and sane.""" for platform_label in platforms: platform = platforms[platform_label] if platforms[platform_label] else {} @@ -313,14 +299,14 @@ def _validate_all_platforms(cls, platforms: Dict[str, Any]) -> Dict[str, Any]: @pydantic.validator("parts", each_item=True) @classmethod - def _validate_parts(cls, item: Dict[str, Any]) -> Dict[str, Any]: + def _validate_parts(cls, item: dict[str, Any]) -> dict[str, Any]: """Verify each part (craft-parts will re-validate this).""" validate_part(item) return item @pydantic.validator("parts", each_item=True) @classmethod - def _validate_base_and_overlay(cls, item: Dict[str, Any], values) -> Dict[str, Any]: + def _validate_base_and_overlay(cls, item: dict[str, Any], values) -> dict[str, Any]: """Projects with "bare" bases cannot use overlays.""" if values.get("base") == "bare" and part_has_overlay(item): raise ProjectValidationError( @@ -331,8 +317,8 @@ def _validate_base_and_overlay(cls, item: Dict[str, Any], values) -> Dict[str, A @pydantic.validator("entrypoint_service") @classmethod def _validate_entrypoint_service( - cls, entrypoint_service: Optional[str], values: Any - ) -> Optional[str]: + cls, entrypoint_service: str | None, values: Any + ) -> str | None: """Verify that the entrypoint_service exists in the services dict.""" craft_cli.emit.message( "Warning: defining an entrypoint-service will result in a rock with " @@ -369,8 +355,8 @@ def _validate_entrypoint_service( @pydantic.validator("package_repositories") @classmethod def _validate_package_repositories( - cls, package_repositories: Optional[List[Dict[str, Any]]] - ) -> List[Dict[str, Any]]: + cls, package_repositories: list[dict[str, Any]] | None + ) -> list[dict[str, Any]]: if not package_repositories: return [] @@ -390,8 +376,8 @@ def _validate_package_repositories( @pydantic.validator("environment") @classmethod def _forbid_env_var_bash_interpolation( - cls, environment: Optional[Dict[str, str]] - ) -> Dict[str, str]: + cls, environment: dict[str, str] | None + ) -> dict[str, str]: """Variable interpolation isn't yet supported, so forbid attempts to do it.""" if not environment: return {} @@ -428,7 +414,7 @@ def _repr_str(dumper, data): ) @classmethod - def unmarshal(cls, data: Dict[str, Any]) -> "Project": + def unmarshal(cls, data: dict[str, Any]) -> "Project": """Overridden to raise ProjectValidationError() for Pydantic errors.""" if not isinstance(data, dict): raise TypeError("project data is not a dictionary") @@ -442,7 +428,7 @@ def unmarshal(cls, data: Dict[str, Any]) -> "Project": def generate_metadata( self, generation_time: str, base_digest: bytes - ) -> Tuple[dict, dict]: + ) -> tuple[dict, dict]: """Generate the ROCK's metadata (both the OCI annotation and internal metadata. :param generation_time: the UTC time at the time of calling this method @@ -471,9 +457,9 @@ def generate_metadata( return (annotations, metadata) - def get_build_plan(self) -> List[BuildInfo]: + def get_build_plan(self) -> list[BuildInfo]: """Obtain the list of architectures and bases from the project file.""" - build_infos: List[BuildInfo] = [] + build_infos: list[BuildInfo] = [] base = self.effective_base for platform_entry, platform in self.platforms.items(): @@ -492,10 +478,10 @@ def get_build_plan(self) -> List[BuildInfo]: def _format_pydantic_errors( - errors: List["ErrorDict"], + errors: list["ErrorDict"], *, file_name: str = "rockcraft.yaml", - base_location: Optional[str] = None, + base_location: str | None = None, ) -> str: """Format errors. @@ -516,7 +502,7 @@ def _format_pydantic_errors( combined = [f"Bad {file_name} content:"] for error in errors: if base_location: - error_loc: List[Union[int, str]] = [base_location] + error_loc: list[int | str] = [base_location] else: error_loc = [] error_loc.extend(error["loc"]) @@ -539,7 +525,7 @@ def _format_pydantic_errors( return "\n".join(combined) -def _format_pydantic_error_location(loc: Sequence[Union[str, int]]) -> str: +def _format_pydantic_error_location(loc: Sequence[str | int]) -> str: """Format location.""" loc_parts = [] for loc_part in loc: @@ -568,7 +554,7 @@ def _format_pydantic_error_message(msg: str) -> str: return msg -def _printable_field_location_split(location: str) -> Tuple[str, str]: +def _printable_field_location_split(location: str) -> tuple[str, str]: """Return split field location. If top-level, location is returned as unquoted "top-level". @@ -588,7 +574,7 @@ def _printable_field_location_split(location: str) -> Tuple[str, str]: return field_name, "top-level" -def load_project(filename: Path) -> Dict[str, Any]: +def load_project(filename: Path) -> dict[str, Any]: """Load and unmarshal the project YAML file. :param filename: The YAML file to load. @@ -612,7 +598,7 @@ def load_project(filename: Path) -> Dict[str, Any]: return yaml_data -def transform_yaml(project_root: Path, yaml_data: Dict[str, Any]) -> Dict[str, Any]: +def transform_yaml(project_root: Path, yaml_data: dict[str, Any]) -> dict[str, Any]: """Do Rockcraft-specific transformations on a project yaml. :param project_root: The path that contains the "rockcraft.yaml" file. @@ -625,7 +611,7 @@ def transform_yaml(project_root: Path, yaml_data: Dict[str, Any]) -> Dict[str, A return yaml_data -def _add_pebble_data(yaml_data: Dict[str, Any]) -> None: +def _add_pebble_data(yaml_data: dict[str, Any]) -> None: """Add pebble-specific contents to YAML-loaded data. This function adds a special "pebble" part to a project's specification, to be diff --git a/rockcraft/oci.py b/rockcraft/oci.py index 4e496c833..86db17fec 100644 --- a/rockcraft/oci.py +++ b/rockcraft/oci.py @@ -27,7 +27,7 @@ from dataclasses import dataclass from datetime import datetime from pathlib import Path -from typing import Any, Dict, List, Optional, Tuple +from typing import Any import yaml from craft_cli import emit @@ -67,7 +67,7 @@ def from_docker_registry( *, image_dir: Path, arch: str, - ) -> Tuple["Image", str]: + ) -> tuple["Image", str]: """Obtain an image from a docker registry. The image is fetched from the registry at ``REGISTRY_URL``. @@ -115,7 +115,7 @@ def new_oci_image( image_name: str, image_dir: Path, arch: str, - ) -> Tuple["Image", str]: + ) -> tuple["Image", str]: """Create a new OCI image out of thin air. :param image_name: The image to initiate, in ``name@tag`` format. @@ -193,7 +193,7 @@ def add_layer( self, tag: str, new_layer_dir: Path, - base_layer_dir: Optional[Path] = None, + base_layer_dir: Path | None = None, ) -> "Image": """Add a layer to the image. @@ -295,7 +295,7 @@ def add_user( emit.progress(f"Adding user {username}:{uid} with group {username}:{uid}") self.add_layer(tag, Path(tmpfs)) - def stat(self) -> Dict[Any, Any]: + def stat(self) -> dict[Any, Any]: """Obtain the image statistics, as reported by "umoci stat --json".""" image_path = self.path / self.image_name output = _process_run( @@ -352,7 +352,7 @@ def set_default_user(self, user: str) -> None: _config_image(image_path, params) emit.progress(f"Default user set to {user}") - def set_entrypoint(self, entrypoint_service: Optional[str] = None) -> None: + def set_entrypoint(self, entrypoint_service: str | None = None) -> None: """Set the OCI image entrypoint. It is always Pebble.""" emit.progress("Configuring entrypoint...") image_path = self.path / self.image_name @@ -366,7 +366,7 @@ def set_entrypoint(self, entrypoint_service: Optional[str] = None) -> None: _config_image(image_path, params) emit.progress(f"Entrypoint set to {entrypoint}") - def set_cmd(self, command: Optional[str] = None) -> None: + def set_cmd(self, command: str | None = None) -> None: """Set the OCI image CMD.""" emit.progress("Configuring CMD...") image_path = self.path / self.image_name @@ -389,8 +389,8 @@ def set_cmd(self, command: Optional[str] = None) -> None: def set_pebble_layer( self, - services: Dict[str, Any], - checks: Dict[str, Any], + services: dict[str, Any], + checks: dict[str, Any], name: str, tag: str, summary: str, @@ -408,7 +408,7 @@ def set_pebble_layer( :param base_layer_dir: Path to the base layer's root filesystem """ # pylint: disable=too-many-arguments - pebble_layer_content: Dict[str, Any] = { + pebble_layer_content: dict[str, Any] = { "summary": summary, "description": description, } @@ -433,7 +433,7 @@ def set_pebble_layer( emit.progress("Writing new Pebble layer file") self.add_layer(tag, tmpfs_path) - def set_environment(self, env: Dict[str, str]) -> None: + def set_environment(self, env: dict[str, str]) -> None: """Set the OCI image environment. :param env: A dictionary mapping environment variables to @@ -451,7 +451,7 @@ def set_environment(self, env: Dict[str, str]) -> None: _config_image(image_path, params) emit.progress(f"Environment set to {env_list}") - def set_control_data(self, metadata: Dict[str, Any]) -> None: + def set_control_data(self, metadata: dict[str, Any]) -> None: """Create and populate the ROCK's control data folder. :param metadata: content for the ROCK's metadata YAML file @@ -480,7 +480,7 @@ def set_control_data(self, metadata: Dict[str, Any]) -> None: emit.progress("Control data written") shutil.rmtree(local_control_data_path) - def set_annotations(self, annotations: Dict[str, Any]) -> None: + def set_annotations(self, annotations: dict[str, Any]) -> None: """Add the given annotations to the final image. :param annotations: A dictionary with each annotation/label and its value @@ -507,7 +507,7 @@ def _copy_image( source: str, destination: str, *system_params: str, - copy_params: Optional[List[str]] = None, + copy_params: list[str] | None = None, ) -> None: """Transfer images from source to destination. @@ -530,7 +530,7 @@ def _copy_image( ) -def _config_image(image_path: Path, params: List[str]) -> None: +def _config_image(image_path: Path, params: list[str]) -> None: """Configure the OCI image.""" _process_run(["umoci", "config", "--image", str(image_path)] + params) @@ -600,7 +600,7 @@ def _inject_architecture_variant(image_path: Path, variant: str) -> None: tl_index_path.write_bytes(json.dumps(tl_index).encode("utf-8")) -def _process_run(command: List[str], **kwargs: Any) -> subprocess.CompletedProcess: +def _process_run(command: list[str], **kwargs: Any) -> subprocess.CompletedProcess: """Run a command and handle its output.""" if not Path(command[0]).is_absolute(): command[0] = get_snap_command_path(command[0]) @@ -613,7 +613,7 @@ def _process_run(command: List[str], **kwargs: Any) -> subprocess.CompletedProce **kwargs, capture_output=True, check=True, - universal_newlines=True, + text=True, ) except subprocess.CalledProcessError as err: msg = f"Failed to copy image: {err!s}" diff --git a/rockcraft/parts.py b/rockcraft/parts.py index 36ad00b16..dac212b15 100644 --- a/rockcraft/parts.py +++ b/rockcraft/parts.py @@ -15,12 +15,12 @@ # along with this program. If not, see . """Craft-parts lifecycle.""" -from typing import Any, Dict +from typing import Any import craft_parts -def validate_part(data: Dict[str, Any]) -> None: +def validate_part(data: dict[str, Any]) -> None: """Validate the given part data against common and plugin models. :param data: The part data to validate. @@ -28,6 +28,6 @@ def validate_part(data: Dict[str, Any]) -> None: craft_parts.validate_part(data) -def part_has_overlay(data: Dict[str, Any]) -> bool: +def part_has_overlay(data: dict[str, Any]) -> bool: """Whether ``data`` declares an overlay-using part.""" return craft_parts.part_has_overlay(data) diff --git a/rockcraft/pebble.py b/rockcraft/pebble.py index c80bae457..d5caf7e6d 100644 --- a/rockcraft/pebble.py +++ b/rockcraft/pebble.py @@ -18,7 +18,8 @@ import glob from pathlib import Path -from typing import Any, Dict, List, Literal, Mapping, Optional +from typing import Any, Literal +from collections.abc import Mapping import pydantic import yaml @@ -31,7 +32,7 @@ class HttpCheck(pydantic.BaseModel): """Lightweight schema validation for a Pebble HTTP check.""" url: pydantic.AnyHttpUrl - headers: Optional[Dict[str, str]] + headers: dict[str, str] | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -44,7 +45,7 @@ class TcpCheck(pydantic.BaseModel): """Lightweight schema validation for a Pebble TCP check.""" port: int - host: Optional[str] + host: str | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -57,13 +58,13 @@ class ExecCheck(pydantic.BaseModel): """Lightweight schema validation for a Pebble exec check.""" command: str - service_context: Optional[str] - environment: Optional[Dict[str, str]] - user: Optional[str] - user_id: Optional[int] - group: Optional[str] - group_id: Optional[int] - working_dir: Optional[str] + service_context: str | None + environment: dict[str, str] | None + user: str | None + user_id: int | None + group: str | None + group_id: int | None + working_dir: str | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -81,13 +82,13 @@ class Check(pydantic.BaseModel): """ override: Literal["merge", "replace"] - level: Optional[Literal["alive", "ready"]] - period: Optional[str] - timeout: Optional[str] - threshold: Optional[int] - http: Optional[HttpCheck] - tcp: Optional[TcpCheck] - exec: Optional[ExecCheck] + level: Literal["alive", "ready"] | None + period: str | None + timeout: str | None + threshold: int | None + http: HttpCheck | None + tcp: TcpCheck | None + exec: ExecCheck | None @pydantic.root_validator(pre=True) @classmethod @@ -128,25 +129,25 @@ class Service(pydantic.BaseModel): override: Literal["merge", "replace"] command: str - summary: Optional[str] - description: Optional[str] - startup: Optional[Literal["enabled", "disabled"]] - after: Optional[List[str]] - before: Optional[List[str]] - requires: Optional[List[str]] - environment: Optional[Dict[str, str]] - user: Optional[str] - user_id: Optional[int] - group: Optional[str] - group_id: Optional[int] - working_dir: Optional[str] - on_success: Optional[Literal["restart", "shutdown", "ignore"]] - on_failure: Optional[Literal["restart", "shutdown", "ignore"]] - on_check_failure: Optional[Dict[str, Literal["restart", "shutdown", "ignore"]]] - backoff_delay: Optional[str] - backoff_factor: Optional[float] - backoff_limit: Optional[str] - kill_delay: Optional[str] + summary: str | None + description: str | None + startup: Literal["enabled", "disabled"] | None + after: list[str] | None + before: list[str] | None + requires: list[str] | None + environment: dict[str, str] | None + user: str | None + user_id: int | None + group: str | None + group_id: int | None + working_dir: str | None + on_success: Literal["restart", "shutdown", "ignore"] | None + on_failure: Literal["restart", "shutdown", "ignore"] | None + on_check_failure: dict[str, Literal["restart", "shutdown", "ignore"]] | None + backoff_delay: str | None + backoff_factor: float | None + backoff_limit: str | None + kill_delay: str | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -174,7 +175,7 @@ def define_pebble_layer( self, target_dir: Path, ref_fs: Path, - layer_content: Dict[str, Any], + layer_content: dict[str, Any], rock_name: str, ) -> None: """Infers and defines a new Pebble layer file. diff --git a/rockcraft/plugins/python_plugin.py b/rockcraft/plugins/python_plugin.py index 3f38c87d4..b8425ce48 100644 --- a/rockcraft/plugins/python_plugin.py +++ b/rockcraft/plugins/python_plugin.py @@ -18,7 +18,6 @@ import logging from textwrap import dedent -from typing import List, Optional from craft_parts.plugins import python_plugin from overrides import override @@ -79,7 +78,7 @@ def _should_remove_symlinks(self) -> bool: return self._part_info.base != "bare" @override - def _get_system_python_interpreter(self) -> Optional[str]: + def _get_system_python_interpreter(self) -> str | None: """Overridden because Python must always be provided by the parts.""" return None @@ -89,7 +88,7 @@ def _get_script_interpreter(self) -> str: return "#!/bin/${PARTS_PYTHON_INTERPRETER}" @override - def get_build_commands(self) -> List[str]: + def get_build_commands(self) -> list[str]: """Overridden to add a sitecustomize.py .""" commands = [] diff --git a/rockcraft/services/image.py b/rockcraft/services/image.py index 055951f50..24f1ccec6 100644 --- a/rockcraft/services/image.py +++ b/rockcraft/services/image.py @@ -16,8 +16,6 @@ """Rockcraft Image Service.""" -from __future__ import annotations - from dataclasses import dataclass from pathlib import Path from typing import cast diff --git a/rockcraft/services/lifecycle.py b/rockcraft/services/lifecycle.py index 993c0dfba..8755bd34c 100644 --- a/rockcraft/services/lifecycle.py +++ b/rockcraft/services/lifecycle.py @@ -16,8 +16,6 @@ """Rockcraft Lifecycle service.""" -from __future__ import annotations - import contextlib from pathlib import Path from typing import cast diff --git a/rockcraft/services/package.py b/rockcraft/services/package.py index 501ac56be..c295f1444 100644 --- a/rockcraft/services/package.py +++ b/rockcraft/services/package.py @@ -16,8 +16,6 @@ """Rockcraft Package service.""" -from __future__ import annotations - import datetime import pathlib import typing diff --git a/rockcraft/services/provider.py b/rockcraft/services/provider.py index aa7ef5414..f11fcc7ea 100644 --- a/rockcraft/services/provider.py +++ b/rockcraft/services/provider.py @@ -16,9 +16,6 @@ """Rockcraft Provider service.""" - -from __future__ import annotations - from craft_application import ProviderService from overrides import override diff --git a/rockcraft/services/service_factory.py b/rockcraft/services/service_factory.py index 0b3be7d47..397a26054 100644 --- a/rockcraft/services/service_factory.py +++ b/rockcraft/services/service_factory.py @@ -16,8 +16,6 @@ """Rockcraft Service Factory.""" -from __future__ import annotations - from dataclasses import dataclass from typing import TYPE_CHECKING diff --git a/rockcraft/utils.py b/rockcraft/utils.py index 913bc45cd..0b4651d59 100644 --- a/rockcraft/utils.py +++ b/rockcraft/utils.py @@ -54,7 +54,7 @@ def get_managed_environment_log_path() -> pathlib.Path: return pathlib.Path("/tmp/rockcraft.log") -def get_managed_environment_snap_channel() -> Optional[str]: +def get_managed_environment_snap_channel() -> str | None: """User-specified channel to use when installing Rockcraft snap from Snap Store. :returns: Channel string if specified, else None. @@ -85,7 +85,7 @@ def confirm_with_user(prompt: str, default: bool = False) -> bool: return reply[0] == "y" if reply else default -def _find_command_path_in_root(root: str, command_name: str) -> Optional[str]: +def _find_command_path_in_root(root: str, command_name: str) -> str | None: """Find the path of a command in a given root path.""" for bin_directory in ( "usr/local/sbin", diff --git a/tests/integration/plugins/test_python_plugin.py b/tests/integration/plugins/test_python_plugin.py index 862350d74..052891127 100644 --- a/tests/integration/plugins/test_python_plugin.py +++ b/tests/integration/plugins/test_python_plugin.py @@ -70,7 +70,7 @@ def create_python_project(base, extra_part_props=None) -> Project: class ExpectedValues: """Expected venv Python values for a given Ubuntu host.""" - symlinks: typing.List[str] + symlinks: list[str] symlink_target: str version_dir: str diff --git a/tests/integration/test_oci.py b/tests/integration/test_oci.py index 0f49a1fd1..46f7d5f31 100644 --- a/tests/integration/test_oci.py +++ b/tests/integration/test_oci.py @@ -18,7 +18,7 @@ import tarfile import textwrap from pathlib import Path -from typing import Callable, List, Tuple +from collections.abc import Callable import pytest @@ -31,7 +31,7 @@ def create_base_image( work_dir: Path, populate_base_layer: Callable[[Path], None] -) -> Tuple[oci.Image, Path]: +) -> tuple[oci.Image, Path]: """Create a base image with content provided by a callable. This function will create an empty image, extract it to a bundle and then @@ -72,7 +72,7 @@ def create_base_image( return image, base_layer_dir -def get_names_in_layer(image: oci.Image, layer_number: int = -1) -> List[str]: +def get_names_in_layer(image: oci.Image, layer_number: int = -1) -> list[str]: """Get the list of file/dir names contained in the given layer, sorted.""" umoci_stat = image.stat() diff --git a/tests/testing/lifecycle.py b/tests/testing/lifecycle.py index c9b542766..018525b39 100644 --- a/tests/testing/lifecycle.py +++ b/tests/testing/lifecycle.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License along # with this program. If not, see . """Project-related utility functions for running lifecycles.""" -from __future__ import annotations import pathlib from typing import cast diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index c02d00700..424061a9b 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -78,7 +78,7 @@ def launched_environment( project_name: str, project_path: Path, base_configuration: base.Base, - build_base: Optional[str] = None, + build_base: str | None = None, instance_name: str, allow_unstable: bool = False, ): diff --git a/tests/unit/test_layers.py b/tests/unit/test_layers.py index a6aae9e39..de5d18848 100644 --- a/tests/unit/test_layers.py +++ b/tests/unit/test_layers.py @@ -19,7 +19,6 @@ import sys import tarfile from pathlib import Path -from typing import List, Tuple import pytest from craft_parts.overlays import overlays @@ -27,12 +26,12 @@ from rockcraft import errors, layers -def get_tar_contents(tar_path: Path) -> List[str]: +def get_tar_contents(tar_path: Path) -> list[str]: with tarfile.open(tar_path, "r") as tar_file: return tar_file.getnames() -def duplicate_dirs_setup(tmp_path) -> Tuple[Path, Path]: +def duplicate_dirs_setup(tmp_path) -> tuple[Path, Path]: """Create a filetree with an upper layer and a fake 'rootfs' structure. layer_dir/ diff --git a/tests/unit/test_oci.py b/tests/unit/test_oci.py index 1e84cee87..c8e13d6fa 100644 --- a/tests/unit/test_oci.py +++ b/tests/unit/test_oci.py @@ -797,7 +797,7 @@ def mock_write(s): "raw", "add-layer", "--image", - str("/c/a:b"), + "/c/a:b", str(f"/c/.temp_layer.control_data.{os.getpid()}.tar"), ] assert mock_run.mock_calls == [ @@ -829,7 +829,7 @@ def test_set_annotations(self, mocker): ], capture_output=True, check=True, - universal_newlines=True, + text=True, ), call( [ @@ -845,7 +845,7 @@ def test_set_annotations(self, mocker): ], capture_output=True, check=True, - universal_newlines=True, + text=True, ), ] diff --git a/tests/unit/test_pebble.py b/tests/unit/test_pebble.py index 1e7f98f32..51a418583 100644 --- a/tests/unit/test_pebble.py +++ b/tests/unit/test_pebble.py @@ -60,20 +60,20 @@ def test_attributes(self): }, ( "summary: mock summary" - "{n}" + f"{os.linesep}" "description: mock description" - "{n}" + f"{os.linesep}" "services:" - "{n}" + f"{os.linesep}" " mockServiceOne:" - "{n}" + f"{os.linesep}" " override: replace" - "{n}" + f"{os.linesep}" " command: foo" - "{n}" + f"{os.linesep}" " on-success: shutdown" - "{n}" - ).format(n=os.linesep), + f"{os.linesep}" + ), ), # Test Case 2: # With existing layers, the default layer prefix is an increment. @@ -91,24 +91,24 @@ def test_attributes(self): }, ( "summary: mock summary" - "{n}" + f"{os.linesep}" "description: mock description" - "{n}" + f"{os.linesep}" "services:" - "{n}" + f"{os.linesep}" " mockServiceOne:" - "{n}" + f"{os.linesep}" " override: replace" - "{n}" + f"{os.linesep}" " command: foo" - "{n}" + f"{os.linesep}" " mockServiceTwo:" - "{n}" + f"{os.linesep}" " override: merge" - "{n}" + f"{os.linesep}" " command: bar" - "{n}" - ).format(n=os.linesep), + f"{os.linesep}" + ), ), # Test Case 3: # If there are more files that are not layers, they are ignored. @@ -124,18 +124,18 @@ def test_attributes(self): }, ( "summary: mock summary" - "{n}" + f"{os.linesep}" "description: mock description" - "{n}" + f"{os.linesep}" "services:" - "{n}" + f"{os.linesep}" " mockServiceOne:" - "{n}" + f"{os.linesep}" " override: replace" - "{n}" + f"{os.linesep}" " command: foo" - "{n}" - ).format(n=os.linesep), + f"{os.linesep}" + ), ), ], ) diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py index 27ddb061b..92dc25100 100644 --- a/tests/unit/test_project.py +++ b/tests/unit/test_project.py @@ -19,7 +19,7 @@ import subprocess import textwrap from pathlib import Path -from typing import Any, Dict +from typing import Any import pydantic import pytest @@ -98,7 +98,7 @@ def yaml_loaded_data(): @pytest.fixture -def pebble_part() -> Dict[str, Any]: +def pebble_part() -> dict[str, Any]: return { "pebble": { "plugin": "nil", diff --git a/tests/unit/testing/extensions.py b/tests/unit/testing/extensions.py index 1b4561379..6306c9545 100644 --- a/tests/unit/testing/extensions.py +++ b/tests/unit/testing/extensions.py @@ -16,7 +16,7 @@ """Fake Extensions for use in tests.""" import textwrap -from typing import Any, Dict, Optional, Tuple +from typing import Any from overrides import override @@ -29,24 +29,24 @@ class FakeExtension(Extension): NAME = "fake-extension" @staticmethod - def get_supported_bases() -> Tuple[str, ...]: + def get_supported_bases() -> tuple[str, ...]: """Return a tuple of supported bases.""" return ("ubuntu@22.04",) @staticmethod - def is_experimental(base: Optional[str]) -> bool: + def is_experimental(base: str | None) -> bool: """Return whether or not this extension is unstable for given base.""" return False - def get_root_snippet(self) -> Dict[str, Any]: + def get_root_snippet(self) -> dict[str, Any]: """Return the root snippet to apply.""" return {} - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self) -> dict[str, Any]: """Return the part snippet to apply to existing parts.""" return {} - def get_parts_snippet(self) -> Dict[str, Any]: + def get_parts_snippet(self) -> dict[str, Any]: """Return the parts to add to parts.""" return {} @@ -57,12 +57,12 @@ class ExperimentalExtension(FakeExtension): NAME = "experimental-extension" @staticmethod - def get_supported_bases() -> Tuple[str, ...]: + def get_supported_bases() -> tuple[str, ...]: """Return a tuple of supported bases.""" return ("ubuntu@22.04", "ubuntu@20.04") @staticmethod - def is_experimental(base: Optional[str]) -> bool: + def is_experimental(base: str | None) -> bool: return True @@ -72,7 +72,7 @@ class InvalidPartExtension(FakeExtension): NAME = "invalid-extension" @override - def get_parts_snippet(self) -> Dict[str, Any]: + def get_parts_snippet(self) -> dict[str, Any]: return {"bad-name": {"plugin": "dump", "source": None}} @@ -82,7 +82,7 @@ class FullExtension(FakeExtension): NAME = "full-extension" @override - def get_root_snippet(self) -> Dict[str, Any]: + def get_root_snippet(self) -> dict[str, Any]: """Return the root snippet to apply.""" return { "services": { @@ -94,12 +94,12 @@ def get_root_snippet(self) -> Dict[str, Any]: } @override - def get_part_snippet(self) -> Dict[str, Any]: + def get_part_snippet(self) -> dict[str, Any]: """Return the part snippet to apply to existing parts.""" return {"stage-packages": ["new-package-1"]} @override - def get_parts_snippet(self) -> Dict[str, Any]: + def get_parts_snippet(self) -> dict[str, Any]: """Return the parts to add to parts.""" return {"full-extension/new-part": {"plugin": "nil", "source": None}} From 518aa7e24757ab86d6b7503ec9b968a0ed28a498 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 17:07:48 -0500 Subject: [PATCH 05/31] chore(linter): misc auto fix --- rockcraft/extensions/extension.py | 2 +- rockcraft/models/project.py | 2 +- rockcraft/pebble.py | 2 +- rockcraft/utils.py | 1 - tests/conftest.py | 25 +++++----- .../integration/plugins/test_python_plugin.py | 2 +- tests/integration/services/test_lifecycle.py | 2 +- tests/integration/test_application.py | 2 +- tests/integration/test_oci.py | 6 +-- tests/unit/commands/test_expand_extensions.py | 4 +- tests/unit/commands/test_list_extensions.py | 4 +- tests/unit/conftest.py | 7 ++- tests/unit/extensions/test_extensions.py | 6 +-- tests/unit/extensions/test_registry.py | 3 +- tests/unit/plugins/test_python_plugin.py | 2 +- tests/unit/services/test_lifecycle.py | 5 +- tests/unit/services/test_package.py | 2 +- tests/unit/test_cli.py | 5 +- tests/unit/test_layers.py | 1 - tests/unit/test_oci.py | 50 +++++++++---------- tests/unit/test_pebble.py | 4 +- tests/unit/test_project.py | 7 ++- tests/unit/test_usernames.py | 1 - tests/unit/test_utils.py | 13 +++-- tests/unit/testing/extensions.py | 1 - 25 files changed, 74 insertions(+), 85 deletions(-) diff --git a/rockcraft/extensions/extension.py b/rockcraft/extensions/extension.py index 43a2ce127..6b182989c 100644 --- a/rockcraft/extensions/extension.py +++ b/rockcraft/extensions/extension.py @@ -19,9 +19,9 @@ import abc import os import sys +from collections.abc import Sequence from pathlib import Path from typing import Any, final -from collections.abc import Sequence from craft_cli import emit diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 0c97b2031..ae148fa96 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -17,6 +17,7 @@ """Project definition and helpers.""" import re import shlex +from collections.abc import Mapping, Sequence from pathlib import Path from typing import ( TYPE_CHECKING, @@ -24,7 +25,6 @@ Literal, cast, ) -from collections.abc import Mapping, Sequence import craft_cli import pydantic diff --git a/rockcraft/pebble.py b/rockcraft/pebble.py index d5caf7e6d..1981f9da8 100644 --- a/rockcraft/pebble.py +++ b/rockcraft/pebble.py @@ -17,9 +17,9 @@ """Pebble metadata and configuration helpers.""" import glob +from collections.abc import Mapping from pathlib import Path from typing import Any, Literal -from collections.abc import Mapping import pydantic import yaml diff --git a/rockcraft/utils.py b/rockcraft/utils.py index 0b4651d59..25bfa28d9 100644 --- a/rockcraft/utils.py +++ b/rockcraft/utils.py @@ -24,7 +24,6 @@ import sys from collections import namedtuple from distutils.util import strtobool # pylint: disable=deprecated-module -from typing import Optional import rockcraft.errors diff --git a/tests/conftest.py b/tests/conftest.py index 7f60d5fe9..12dd4ff2f 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -21,7 +21,7 @@ import xdg # type: ignore -@pytest.fixture +@pytest.fixture() def new_dir(tmpdir): """Change to a new temporary directory.""" @@ -103,16 +103,15 @@ def assert_recorded_raw(self, expected): self._check(expected, self.raw) -@pytest.fixture +@pytest.fixture() def extra_project_params(): """Configuration fixture for the Project used by the default services.""" return {} -@pytest.fixture +@pytest.fixture() def default_project(extra_project_params): from craft_application.models import VersionStr - from rockcraft.models.project import NameStr, Project parts = extra_project_params.pop("parts", {}) @@ -130,7 +129,7 @@ def default_project(extra_project_params): ) -@pytest.fixture +@pytest.fixture() def default_factory(default_project): from rockcraft.application import APP_METADATA from rockcraft.services import RockcraftServiceFactory @@ -143,7 +142,7 @@ def default_factory(default_project): return factory -@pytest.fixture +@pytest.fixture() def default_image_info(): from rockcraft import oci from rockcraft.services.image import ImageInfo @@ -155,14 +154,14 @@ def default_image_info(): ) -@pytest.fixture +@pytest.fixture() def default_application(default_factory, default_project): from rockcraft.application import APP_METADATA, Rockcraft return Rockcraft(APP_METADATA, default_factory) -@pytest.fixture +@pytest.fixture() def image_service(default_project, default_factory, tmp_path): from rockcraft.application import APP_METADATA from rockcraft.services import RockcraftImageService @@ -176,7 +175,7 @@ def image_service(default_project, default_factory, tmp_path): ) -@pytest.fixture +@pytest.fixture() def provider_service(default_project, default_factory, tmp_path): from rockcraft.application import APP_METADATA from rockcraft.services import RockcraftProviderService @@ -189,7 +188,7 @@ def provider_service(default_project, default_factory, tmp_path): ) -@pytest.fixture +@pytest.fixture() def package_service(default_project, default_factory): from rockcraft.application import APP_METADATA from rockcraft.services import RockcraftPackageService @@ -203,7 +202,7 @@ def package_service(default_project, default_factory): ) -@pytest.fixture +@pytest.fixture() def lifecycle_service(default_project, default_factory): from rockcraft.application import APP_METADATA from rockcraft.services import RockcraftLifecycleService @@ -218,14 +217,14 @@ def lifecycle_service(default_project, default_factory): ) -@pytest.fixture +@pytest.fixture() def mock_obtain_image(default_factory, mocker): """Mock and return the "obtain_image()" method of the default image service.""" image_service = default_factory.image return mocker.patch.object(image_service, "obtain_image") -@pytest.fixture +@pytest.fixture() def run_lifecycle(mocker): """Helper to call testing.run_mocked_lifecycle().""" diff --git a/tests/integration/plugins/test_python_plugin.py b/tests/integration/plugins/test_python_plugin.py index 052891127..5cc4091df 100644 --- a/tests/integration/plugins/test_python_plugin.py +++ b/tests/integration/plugins/test_python_plugin.py @@ -23,10 +23,10 @@ from craft_cli import EmitterMode, emit from craft_parts.errors import OsReleaseVersionIdError from craft_parts.utils.os_utils import OsRelease - from rockcraft import plugins from rockcraft.models.project import Project from rockcraft.plugins.python_plugin import SITECUSTOMIZE_TEMPLATE + from tests.testing.project import create_project from tests.util import ubuntu_only diff --git a/tests/integration/services/test_lifecycle.py b/tests/integration/services/test_lifecycle.py index b8a042856..1bc674ff4 100644 --- a/tests/integration/services/test_lifecycle.py +++ b/tests/integration/services/test_lifecycle.py @@ -19,8 +19,8 @@ import pytest from craft_parts import overlays - from rockcraft.services import lifecycle + from tests.testing.project import create_project from tests.util import jammy_only diff --git a/tests/integration/test_application.py b/tests/integration/test_application.py index 0532514cb..61ee59fc7 100644 --- a/tests/integration/test_application.py +++ b/tests/integration/test_application.py @@ -18,10 +18,10 @@ import pytest import yaml - from rockcraft.application import APP_METADATA, Rockcraft from rockcraft.services import RockcraftServiceFactory from rockcraft.services.image import ImageInfo, RockcraftImageService + from tests.util import jammy_only pytestmark = [jammy_only, pytest.mark.usefixtures("reset_callbacks")] diff --git a/tests/integration/test_oci.py b/tests/integration/test_oci.py index 46f7d5f31..f976fdca1 100644 --- a/tests/integration/test_oci.py +++ b/tests/integration/test_oci.py @@ -17,13 +17,13 @@ import subprocess import tarfile import textwrap -from pathlib import Path from collections.abc import Callable +from pathlib import Path import pytest - from rockcraft import oci from rockcraft.services.image import ImageInfo + from tests.util import jammy_only pytestmark = jammy_only @@ -128,7 +128,7 @@ def populate_base_layer(base_layer_dir): ] -@pytest.fixture +@pytest.fixture() def extra_project_params(): """Fixture used to configure the Project used by the default test services.""" return { diff --git a/tests/unit/commands/test_expand_extensions.py b/tests/unit/commands/test_expand_extensions.py index 77124547f..219061418 100644 --- a/tests/unit/commands/test_expand_extensions.py +++ b/tests/unit/commands/test_expand_extensions.py @@ -18,9 +18,9 @@ from pathlib import Path import pytest - from rockcraft import extensions from rockcraft.commands import ExpandExtensionsCommand + from tests.unit.testing.extensions import FULL_EXTENSION_YAML, FullExtension # The project with the extension (FullExtension) expanded @@ -67,7 +67,7 @@ ) -@pytest.fixture +@pytest.fixture() def setup_extensions(mock_extensions): extensions.register(FullExtension.NAME, FullExtension) diff --git a/tests/unit/commands/test_list_extensions.py b/tests/unit/commands/test_list_extensions.py index e9db67c1f..da3a64467 100644 --- a/tests/unit/commands/test_list_extensions.py +++ b/tests/unit/commands/test_list_extensions.py @@ -17,13 +17,13 @@ from textwrap import dedent import pytest - from rockcraft import extensions from rockcraft.commands import ExtensionsCommand, ListExtensionsCommand + from tests.unit.testing.extensions import ExperimentalExtension, FakeExtension -@pytest.fixture +@pytest.fixture() def setup_extensions(mock_extensions): extensions.register(FakeExtension.NAME, FakeExtension) extensions.register(ExperimentalExtension.NAME, ExperimentalExtension) diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 424061a9b..234822cb3 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -16,7 +16,6 @@ import contextlib from pathlib import Path -from typing import Optional from unittest import mock import pytest @@ -25,14 +24,14 @@ # pylint: disable=import-outside-toplevel -@pytest.fixture +@pytest.fixture() def mock_instance(): """Provide a mock instance (Executor).""" _mock_instance = mock.Mock(spec=Executor) - yield _mock_instance + return _mock_instance -@pytest.fixture +@pytest.fixture() def mock_extensions(monkeypatch): from rockcraft.extensions import registry diff --git a/tests/unit/extensions/test_extensions.py b/tests/unit/extensions/test_extensions.py index f1985e278..df2112673 100644 --- a/tests/unit/extensions/test_extensions.py +++ b/tests/unit/extensions/test_extensions.py @@ -15,9 +15,9 @@ # along with this program. If not, see . import pytest - from rockcraft import errors, extensions from rockcraft.models import load_project + from tests.unit.testing.extensions import ( FULL_EXTENSION_YAML, ExperimentalExtension, @@ -27,7 +27,7 @@ ) -@pytest.fixture +@pytest.fixture() def fake_extensions(mock_extensions): extensions.register(FakeExtension.NAME, FakeExtension) extensions.register(ExperimentalExtension.NAME, ExperimentalExtension) @@ -35,7 +35,7 @@ def fake_extensions(mock_extensions): extensions.register(FullExtension.NAME, FullExtension) -@pytest.fixture +@pytest.fixture() def input_yaml(): return {"base": "ubuntu@22.04"} diff --git a/tests/unit/extensions/test_registry.py b/tests/unit/extensions/test_registry.py index 5839e9faa..352c92cbe 100644 --- a/tests/unit/extensions/test_registry.py +++ b/tests/unit/extensions/test_registry.py @@ -14,7 +14,6 @@ # You should have received a copy of the GNU General Public License # along with this program. If not, see . import pytest - from rockcraft import errors, extensions from rockcraft.extensions.extension import Extension @@ -37,7 +36,7 @@ class FakeExtension3(Extension): NAME = "fake-extension-3" -@pytest.fixture +@pytest.fixture() def fake_extensions(mock_extensions): for ext_class in (FakeExtension1, FakeExtension2): extensions.register(ext_class.NAME, ext_class) diff --git a/tests/unit/plugins/test_python_plugin.py b/tests/unit/plugins/test_python_plugin.py index 3c9e477d8..4ab9c1e31 100644 --- a/tests/unit/plugins/test_python_plugin.py +++ b/tests/unit/plugins/test_python_plugin.py @@ -20,9 +20,9 @@ import pytest from craft_parts import Part, PartInfo, ProjectInfo - from rockcraft.models.project import Project from rockcraft.plugins import PythonPlugin + from tests.util import ubuntu_only pytestmark = ubuntu_only diff --git a/tests/unit/services/test_lifecycle.py b/tests/unit/services/test_lifecycle.py index b5451be58..7e590f262 100644 --- a/tests/unit/services/test_lifecycle.py +++ b/tests/unit/services/test_lifecycle.py @@ -18,13 +18,12 @@ import pytest from craft_parts import LifecycleManager, callbacks - from rockcraft.services import lifecycle as lifecycle_module # pylint: disable=protected-access -@pytest.fixture +@pytest.fixture() def extra_project_params(): return {"package_repositories": [{"type": "apt", "ppa": "ppa/ppa"}]} @@ -47,7 +46,7 @@ def test_lifecycle_args(lifecycle_service, default_factory, default_image_info, application_name="rockcraft", arch="x86_64", base="ubuntu@22.04", - base_layer_dir=Path("."), + base_layer_dir=Path(), base_layer_hash=b"deadbeef", cache_dir=Path("cache"), ignore_local_sources=["*.rock"], diff --git a/tests/unit/services/test_package.py b/tests/unit/services/test_package.py index 9d3bfdc92..2434a5339 100644 --- a/tests/unit/services/test_package.py +++ b/tests/unit/services/test_package.py @@ -35,7 +35,7 @@ def test_pack(package_service, default_factory, default_image_info, mocker): # parameters. mock_inner_pack.assert_called_once_with( base_digest=b"deadbeef", - base_layer_dir=Path("."), + base_layer_dir=Path(), build_for="amd64", prime_dir=Path("prime"), project=default_factory.project, diff --git a/tests/unit/test_cli.py b/tests/unit/test_cli.py index 95bacfecd..5a530b1a4 100644 --- a/tests/unit/test_cli.py +++ b/tests/unit/test_cli.py @@ -21,13 +21,12 @@ import pytest import yaml from craft_cli import emit - from rockcraft import cli, services from rockcraft.application import Rockcraft from rockcraft.models import project -@pytest.fixture +@pytest.fixture() def lifecycle_init_mock(): """Mock for ui.init.""" patcher = patch("rockcraft.commands.init.init") @@ -66,7 +65,7 @@ def test_run_pack_services(mocker, monkeypatch, tmp_path): lifecycle_mocks["run"].assert_called_once_with(step_name="prime", part_names=[]) package_mocks["write_metadata"].assert_called_once_with(fake_prime_dir) - package_mocks["pack"].assert_called_once_with(fake_prime_dir, Path(".")) + package_mocks["pack"].assert_called_once_with(fake_prime_dir, Path()) assert mock_ended_ok.called assert log_path.is_file() diff --git a/tests/unit/test_layers.py b/tests/unit/test_layers.py index de5d18848..c2eb93f74 100644 --- a/tests/unit/test_layers.py +++ b/tests/unit/test_layers.py @@ -22,7 +22,6 @@ import pytest from craft_parts.overlays import overlays - from rockcraft import errors, layers diff --git a/tests/unit/test_oci.py b/tests/unit/test_oci.py index c8e13d6fa..66b4ea26d 100644 --- a/tests/unit/test_oci.py +++ b/tests/unit/test_oci.py @@ -23,11 +23,11 @@ from unittest.mock import ANY, call, mock_open, patch import pytest - -import tests from rockcraft import errors, oci from rockcraft.architectures import SUPPORTED_ARCHS +import tests + MOCK_NEW_USER = { "user": "foo", "uid": 585287, @@ -41,54 +41,54 @@ } -@pytest.fixture +@pytest.fixture() def mock_run(mocker): - yield mocker.patch("rockcraft.oci._process_run") + return mocker.patch("rockcraft.oci._process_run") -@pytest.fixture +@pytest.fixture() def mock_archive_layer(mocker): - yield mocker.patch("rockcraft.layers.archive_layer") + return mocker.patch("rockcraft.layers.archive_layer") -@pytest.fixture +@pytest.fixture() def mock_rmtree(mocker): - yield mocker.patch("shutil.rmtree") + return mocker.patch("shutil.rmtree") -@pytest.fixture +@pytest.fixture() def mock_mkdir(mocker): - yield mocker.patch("pathlib.Path.mkdir") + return mocker.patch("pathlib.Path.mkdir") -@pytest.fixture +@pytest.fixture() def mock_mkdtemp(mocker): - yield mocker.patch("tempfile.mkdtemp") + return mocker.patch("tempfile.mkdtemp") -@pytest.fixture +@pytest.fixture() def mock_tmpdir(mocker): - yield mocker.patch("tempfile.TemporaryDirectory") + return mocker.patch("tempfile.TemporaryDirectory") -@pytest.fixture +@pytest.fixture() def mock_inject_variant(mocker): - yield mocker.patch("rockcraft.oci._inject_architecture_variant") + return mocker.patch("rockcraft.oci._inject_architecture_variant") -@pytest.fixture +@pytest.fixture() def mock_read_bytes(mocker): - yield mocker.patch("pathlib.Path.read_bytes") + return mocker.patch("pathlib.Path.read_bytes") -@pytest.fixture +@pytest.fixture() def mock_write_bytes(mocker): - yield mocker.patch("pathlib.Path.write_bytes") + return mocker.patch("pathlib.Path.write_bytes") -@pytest.fixture +@pytest.fixture() def mock_add_layer(mocker): - yield mocker.patch("rockcraft.oci.Image.add_layer") + return mocker.patch("rockcraft.oci.Image.add_layer") @tests.linux_only @@ -769,9 +769,9 @@ def test_set_control_data( now = datetime.datetime.now(datetime.timezone.utc).isoformat() metadata = {"name": "rock-name", "version": 1, "created": now} - expected = ( - f"created: '{now}'" + "{n}" "name: rock-name{n}" "version: 1{n}" - ).format(n=os.linesep) + expected = (f"created: '{now}'" + "{n}name: rock-name{n}version: 1{n}").format( + n=os.linesep + ) mocked_data = {"writes": ""} diff --git a/tests/unit/test_pebble.py b/tests/unit/test_pebble.py index 51a418583..85742afd0 100644 --- a/tests/unit/test_pebble.py +++ b/tests/unit/test_pebble.py @@ -20,11 +20,11 @@ import pydantic import pytest import yaml - -import tests from rockcraft.models.project import ProjectValidationError from rockcraft.pebble import Check, ExecCheck, HttpCheck, Pebble, Service, TcpCheck +import tests + @tests.linux_only class TestPebble: diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py index 92dc25100..25e624267 100644 --- a/tests/unit/test_project.py +++ b/tests/unit/test_project.py @@ -26,7 +26,6 @@ import yaml from craft_application.models import BuildInfo from craft_providers.bases import BaseName - from rockcraft.errors import ProjectLoadError, ProjectValidationError from rockcraft.models import Project, load_project from rockcraft.models.project import INVALID_NAME_MESSAGE, Platform @@ -87,17 +86,17 @@ """ -@pytest.fixture +@pytest.fixture() def yaml_data(): return ROCKCRAFT_YAML -@pytest.fixture +@pytest.fixture() def yaml_loaded_data(): return yaml.safe_load(ROCKCRAFT_YAML) -@pytest.fixture +@pytest.fixture() def pebble_part() -> dict[str, Any]: return { "pebble": { diff --git a/tests/unit/test_usernames.py b/tests/unit/test_usernames.py index c279c9e72..d5cd2c105 100644 --- a/tests/unit/test_usernames.py +++ b/tests/unit/test_usernames.py @@ -16,7 +16,6 @@ import pydantic import pytest - from rockcraft import usernames diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index d36d3c2db..2a307de6e 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -18,23 +18,22 @@ from unittest.mock import call import pytest - from rockcraft import utils -@pytest.fixture +@pytest.fixture() def mock_isatty(mocker): - yield mocker.patch("rockcraft.utils.sys.stdin.isatty", return_value=True) + return mocker.patch("rockcraft.utils.sys.stdin.isatty", return_value=True) -@pytest.fixture +@pytest.fixture() def mock_input(mocker): - yield mocker.patch("rockcraft.utils.input", return_value="") + return mocker.patch("rockcraft.utils.input", return_value="") -@pytest.fixture +@pytest.fixture() def mock_is_managed_mode(mocker): - yield mocker.patch("rockcraft.utils.is_managed_mode", return_value=False) + return mocker.patch("rockcraft.utils.is_managed_mode", return_value=False) def test_get_managed_environment_home_path(): diff --git a/tests/unit/testing/extensions.py b/tests/unit/testing/extensions.py index 6306c9545..fed18152b 100644 --- a/tests/unit/testing/extensions.py +++ b/tests/unit/testing/extensions.py @@ -19,7 +19,6 @@ from typing import Any from overrides import override - from rockcraft.extensions.extension import Extension From bbb3493241de7210711fc76521025b75dfea3fb8 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 17:09:11 -0500 Subject: [PATCH 06/31] chore(linter): use tuple for pytest.mark.parametrize --- tests/unit/test_oci.py | 6 +++--- tests/unit/test_pebble.py | 12 ++++++------ tests/unit/test_project.py | 8 ++++---- tests/unit/test_utils.py | 2 +- 4 files changed, 14 insertions(+), 14 deletions(-) diff --git a/tests/unit/test_oci.py b/tests/unit/test_oci.py index 66b4ea26d..94d6d9ef7 100644 --- a/tests/unit/test_oci.py +++ b/tests/unit/test_oci.py @@ -164,7 +164,7 @@ def _get_arch_from_call(self, mock_call): # The archs here were taken from the supported architectures in the registry # that we currently use (https://gallery.ecr.aws/ubuntu/ubuntu) @pytest.mark.parametrize( - ["deb_arch", "expected_arch", "expected_variant"], + ("deb_arch", "expected_arch", "expected_variant"), [ ("amd64", "amd64", None), ("arm64", "arm64", "v8"), @@ -361,7 +361,7 @@ def test_add_new_user( check.is_in("conflict with existing user/group in the base filesystem", err) @pytest.mark.parametrize( - "base_user_files,prime_user_files,whiteouts_exist,expected_user_files", + ("base_user_files", "prime_user_files", "whiteouts_exist", "expected_user_files"), [ # If file in prime, the rest doesn't matter ( @@ -645,7 +645,7 @@ def test_set_cmd_nonempty2(self, mock_run): ] @pytest.mark.parametrize( - "mock_services,mock_checks", + ("mock_services", "mock_checks"), [ # Both services and checks are given ( diff --git a/tests/unit/test_pebble.py b/tests/unit/test_pebble.py index 85742afd0..e9f945426 100644 --- a/tests/unit/test_pebble.py +++ b/tests/unit/test_pebble.py @@ -40,7 +40,7 @@ def test_attributes(self): ) @pytest.mark.parametrize( - "existing_layers,expected_new_layer_prefix,layer_content,expected_layer_yaml", + ("existing_layers", "expected_new_layer_prefix", "layer_content", "expected_layer_yaml"), [ # Test Case 1: # Without any previous layers, the default layer prefix is 001. @@ -221,7 +221,7 @@ def test_full_service(self, service): _ = Service(**service) @pytest.mark.parametrize( - "bad_service,error", + ("bad_service", "error"), [ # Missing fields ({}, r"^2 validation errors[\s\S]*override[\s\S]*command"), @@ -274,7 +274,7 @@ def test_bad_services(self, bad_service, error): _ = Service(**bad_service) @pytest.mark.parametrize( - "bad_http_check,error", + ("bad_http_check", "error"), [ # Missing fields ({}, r"^1 validation error[\s\S]*url[\s\S]"), @@ -295,7 +295,7 @@ def test_bad_http_checks(self, bad_http_check, error): _ = HttpCheck(**bad_http_check) @pytest.mark.parametrize( - "bad_tcp_check,error", + ("bad_tcp_check", "error"), [ # Missing fields ({}, r"^1 validation error[\s\S]*port[\s\S]"), @@ -316,7 +316,7 @@ def test_bad_tcp_checks(self, bad_tcp_check, error): _ = TcpCheck(**bad_tcp_check) @pytest.mark.parametrize( - "bad_exec_check,error", + ("bad_exec_check", "error"), [ # Missing fields ({}, r"^1 validation error[\s\S]*command[\s\S]"), @@ -363,7 +363,7 @@ def test_minimal_check(self): _ = Check(override="merge", exec={"command": "foo cmd"}) # pyright: ignore @pytest.mark.parametrize( - "bad_check,exception,error", + ("bad_check", "exception", "error"), [ # Missing check type fields ( diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py index 25e624267..11ca1c0d7 100644 --- a/tests/unit/test_project.py +++ b/tests/unit/test_project.py @@ -180,7 +180,7 @@ def test_project_unmarshal_with_unsupported_fields(unsupported_field, yaml_loade @pytest.mark.parametrize( - "variable,is_forbidden", + ("variable", "is_forbidden"), [("$BAR", True), ("BAR_$BAZ", True), ("BAR$", False)], ) def test_forbidden_env_var_interpolation( @@ -303,7 +303,7 @@ def test_project_build_base(yaml_loaded_data): @pytest.mark.parametrize( - ["base", "build_base", "expected_base", "expected_build_base"], + ("base", "build_base", "expected_base", "expected_build_base"), [ ("ubuntu:22.04", None, "ubuntu@22.04", "ubuntu@22.04"), ("ubuntu:22.04", "ubuntu:20.04", "ubuntu@22.04", "ubuntu@20.04"), @@ -456,7 +456,7 @@ def test_project_parts_validation(yaml_loaded_data): @pytest.mark.parametrize( - "packages,script", + ("packages", "script"), [ (["pkg"], None), ([], "ls"), @@ -612,7 +612,7 @@ def test_project_yaml(yaml_loaded_data): @pytest.mark.parametrize( - ["platforms", "expected_build_infos"], + ("platforms", "expected_build_infos"), [ ( { diff --git a/tests/unit/test_utils.py b/tests/unit/test_utils.py index 2a307de6e..f77eb4d00 100644 --- a/tests/unit/test_utils.py +++ b/tests/unit/test_utils.py @@ -82,7 +82,7 @@ def test_confirm_with_user_defaults_without_tty(mock_input, mock_isatty): @pytest.mark.parametrize( - "user_input,expected", + ("user_input", "expected"), [ ("y", True), ("Y", True), From 9fc89bc4f9a01a8117a2692736d457a926a2e20f Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 17:12:44 -0500 Subject: [PATCH 07/31] chore(linter): create list without concatenate --- rockcraft/oci.py | 8 +++----- tests/integration/test_oci.py | 2 +- tests/unit/test_oci.py | 11 ++++++++--- tests/unit/test_pebble.py | 7 ++++++- 4 files changed, 18 insertions(+), 10 deletions(-) diff --git a/rockcraft/oci.py b/rockcraft/oci.py index 86db17fec..d93bf0660 100644 --- a/rockcraft/oci.py +++ b/rockcraft/oci.py @@ -519,9 +519,7 @@ def _copy_image( [ "skopeo", "--insecure-policy", - ] - + list(system_params) - + [ + *list(system_params), "copy", *copy_extra, source, @@ -532,7 +530,7 @@ def _copy_image( def _config_image(image_path: Path, params: list[str]) -> None: """Configure the OCI image.""" - _process_run(["umoci", "config", "--image", str(image_path)] + params) + _process_run(["umoci", "config", "--image", str(image_path), *params]) def _add_layer_into_image( @@ -551,7 +549,7 @@ def _add_layer_into_image( str(image_path), str(archived_content), ] + [arg_val for k, v in kwargs.items() for arg_val in [k, v]] - _process_run(cmd + ["--history.created_by", " ".join(cmd)]) + _process_run([*cmd, "--history.created_by", " ".join(cmd)]) def _inject_architecture_variant(image_path: Path, variant: str) -> None: diff --git a/tests/integration/test_oci.py b/tests/integration/test_oci.py index f976fdca1..b9bc9513e 100644 --- a/tests/integration/test_oci.py +++ b/tests/integration/test_oci.py @@ -111,7 +111,7 @@ def populate_base_layer(base_layer_dir): new_layer_dir = Path("new") new_layer_dir.mkdir() - for target in targets + ["tmp"]: + for target in [*targets, "tmp"]: new_target_dir = new_layer_dir / target new_target_dir.mkdir() (new_target_dir / f"new_{target}_file").write_text(f"new {target} file") diff --git a/tests/unit/test_oci.py b/tests/unit/test_oci.py index 94d6d9ef7..c0541fa3f 100644 --- a/tests/unit/test_oci.py +++ b/tests/unit/test_oci.py @@ -301,7 +301,7 @@ def test_add_layer(self, mocker, mock_run, new_dir): "tag", ] assert mock_run.mock_calls == [ - call(expected_cmd + ["--history.created_by", " ".join(expected_cmd)]) + call([*expected_cmd, "--history.created_by", " ".join(expected_cmd)]) ] def test_add_new_user( @@ -361,7 +361,12 @@ def test_add_new_user( check.is_in("conflict with existing user/group in the base filesystem", err) @pytest.mark.parametrize( - ("base_user_files", "prime_user_files", "whiteouts_exist", "expected_user_files"), + ( + "base_user_files", + "prime_user_files", + "whiteouts_exist", + "expected_user_files", + ), [ # If file in prime, the rest doesn't matter ( @@ -802,7 +807,7 @@ def mock_write(s): ] assert mock_run.mock_calls == [ call( - expected_cmd + ["--history.created_by", " ".join(expected_cmd)], + [*expected_cmd, "--history.created_by", " ".join(expected_cmd)], ) ] mock_rmtree.assert_called_once_with(Path(mock_control_data_path)) diff --git a/tests/unit/test_pebble.py b/tests/unit/test_pebble.py index e9f945426..c10bc92a3 100644 --- a/tests/unit/test_pebble.py +++ b/tests/unit/test_pebble.py @@ -40,7 +40,12 @@ def test_attributes(self): ) @pytest.mark.parametrize( - ("existing_layers", "expected_new_layer_prefix", "layer_content", "expected_layer_yaml"), + ( + "existing_layers", + "expected_new_layer_prefix", + "layer_content", + "expected_layer_yaml", + ), [ # Test Case 1: # Without any previous layers, the default layer prefix is 001. From d9ef8939d6057d9a2eeca6c327871dca460c88dc Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 17:14:26 -0500 Subject: [PATCH 08/31] chore(linter): misc fix --- rockcraft/extensions/_utils.py | 2 +- rockcraft/extensions/extension.py | 2 +- rockcraft/models/project.py | 24 ++++++++++-------------- rockcraft/oci.py | 2 +- rockcraft/pebble.py | 2 +- rockcraft/services/image.py | 2 +- tests/unit/conftest.py | 3 +-- tests/unit/services/test_lifecycle.py | 2 +- tests/unit/test_project.py | 6 +++--- 9 files changed, 20 insertions(+), 25 deletions(-) diff --git a/rockcraft/extensions/_utils.py b/rockcraft/extensions/_utils.py index f66ff62e6..30727abac 100644 --- a/rockcraft/extensions/_utils.py +++ b/rockcraft/extensions/_utils.py @@ -66,7 +66,7 @@ def _apply_extension( yaml_data["parts"] = {} parts = yaml_data["parts"] - for _, part_definition in parts.items(): + for part_definition in parts.values(): for property_name, property_value in part_extension.items(): part_definition[property_name] = _apply_extension_property( part_definition.get(property_name), property_value diff --git a/rockcraft/extensions/extension.py b/rockcraft/extensions/extension.py index 6b182989c..efe87b63a 100644 --- a/rockcraft/extensions/extension.py +++ b/rockcraft/extensions/extension.py @@ -71,7 +71,7 @@ def get_parts_snippet(self) -> dict[str, Any]: """Return the parts to add to parts.""" @final - def validate(self, extension_name: str): + def validate(self, extension_name: str) -> None: """Validate that the extension can be used with the current project. :param extension_name: the name of the extension being parsed. diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index ae148fa96..3ca179f76 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -322,15 +322,15 @@ def _validate_entrypoint_service( """Verify that the entrypoint_service exists in the services dict.""" craft_cli.emit.message( "Warning: defining an entrypoint-service will result in a rock with " - + "an atypical OCI Entrypoint. While that might be acceptable for " - + "testing and personal use, it shall require prior approval before " - + "submitting to a Canonical registry namespace." + "an atypical OCI Entrypoint. While that might be acceptable for " + "testing and personal use, it shall require prior approval before " + "submitting to a Canonical registry namespace." ) if entrypoint_service not in values.get("services", {}): raise ProjectValidationError( f"The provided entrypoint-service '{entrypoint_service}' is not " - + "a valid Pebble service." + "a valid Pebble service." ) command = values.get("services")[entrypoint_service].command @@ -341,13 +341,13 @@ def _validate_entrypoint_service( if command_sh_args.index("[") >= command_sh_args.index("]"): raise IndexError( "Bad syntax for the entrypoint-service command's" - + " additional args." + " additional args." ) except ValueError as ex: raise ProjectValidationError( f"The Pebble service '{entrypoint_service}' has a command " - + f"{command} without default arguments and thus cannot be used " - + "as the entrypoint-service." + f"{command} without default arguments and thus cannot be used " + "as the entrypoint-service." ) from ex return entrypoint_service @@ -543,15 +543,13 @@ def _format_pydantic_error_location(loc: Sequence[str | int]) -> str: new_loc = ".".join(loc_parts) # Filter out internal __root__ detail. - new_loc = new_loc.replace(".__root__", "") - return new_loc + return new_loc.replace(".__root__", "") def _format_pydantic_error_message(msg: str) -> str: """Format pydantic's error message field.""" # Replace shorthand "str" with "string". - msg = msg.replace("str type expected", "string type expected") - return msg + return msg.replace("str type expected", "string type expected") def _printable_field_location_split(location: str) -> tuple[str, str]: @@ -593,9 +591,7 @@ def load_project(filename: Path) -> dict[str, Any]: msg = f"{msg}: {err.filename!r}." raise ProjectLoadError(msg) from err - yaml_data = transform_yaml(filename.parent, yaml_data) - - return yaml_data + return transform_yaml(filename.parent, yaml_data) def transform_yaml(project_root: Path, yaml_data: dict[str, Any]) -> dict[str, Any]: diff --git a/rockcraft/oci.py b/rockcraft/oci.py index d93bf0660..f35d1278e 100644 --- a/rockcraft/oci.py +++ b/rockcraft/oci.py @@ -379,7 +379,7 @@ def set_cmd(self, command: str | None = None) -> None: except ValueError: emit.debug( f"The entrypoint-service command '{command}' has no default " - + "arguments. CMD won't be set." + "arguments. CMD won't be set." ) return for arg in opt_args: diff --git a/rockcraft/pebble.py b/rockcraft/pebble.py index 1981f9da8..21b53c723 100644 --- a/rockcraft/pebble.py +++ b/rockcraft/pebble.py @@ -195,7 +195,7 @@ def define_pebble_layer( pebble_layers_path_in_base + "/[0-9][0-9][0-9]-???*.yaml" ) + glob.glob(pebble_layers_path_in_base + "/[0-9][0-9][0-9]-???*.yml") - prefixes = list(map(lambda l: Path(l).name[:3], existing_pebble_layers)) + prefixes = [Path(l).name[:3] for l in existing_pebble_layers] prefixes.sort() emit.progress( f"Found {len(existing_pebble_layers)} Pebble layers in the base's root filesystem" diff --git a/rockcraft/services/image.py b/rockcraft/services/image.py index 24f1ccec6..21ec4c5b0 100644 --- a/rockcraft/services/image.py +++ b/rockcraft/services/image.py @@ -46,7 +46,7 @@ def __init__( project: models.Project, work_dir: Path, build_for: str, - ): + ) -> None: super().__init__(app, services, project=project) self._work_dir = work_dir diff --git a/tests/unit/conftest.py b/tests/unit/conftest.py index 234822cb3..dce48d404 100644 --- a/tests/unit/conftest.py +++ b/tests/unit/conftest.py @@ -27,8 +27,7 @@ @pytest.fixture() def mock_instance(): """Provide a mock instance (Executor).""" - _mock_instance = mock.Mock(spec=Executor) - return _mock_instance + return mock.Mock(spec=Executor) @pytest.fixture() diff --git a/tests/unit/services/test_lifecycle.py b/tests/unit/services/test_lifecycle.py index 7e590f262..c734cb2c8 100644 --- a/tests/unit/services/test_lifecycle.py +++ b/tests/unit/services/test_lifecycle.py @@ -54,7 +54,7 @@ def test_lifecycle_args(lifecycle_service, default_factory, default_image_info, project_name="default", project_vars={"version": "1.0"}, work_dir=Path("work"), - rootfs_dir=Path("."), + rootfs_dir=Path(), ) diff --git a/tests/unit/test_project.py b/tests/unit/test_project.py index 11ca1c0d7..dd073cb07 100644 --- a/tests/unit/test_project.py +++ b/tests/unit/test_project.py @@ -271,9 +271,9 @@ def test_project_entrypoint_service_valid( assert project.entrypoint_service == entrypoint_service emitter.assert_message( "Warning: defining an entrypoint-service will result in a rock with " - + "an atypical OCI Entrypoint. While that might be acceptable for " - + "testing and personal use, it shall require prior approval before " - + "submitting to a Canonical registry namespace." + "an atypical OCI Entrypoint. While that might be acceptable for " + "testing and personal use, it shall require prior approval before " + "submitting to a Canonical registry namespace." ) From d1a6efb03222f785065293348adf91210b86ddad Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 28 Nov 2023 18:20:49 -0500 Subject: [PATCH 09/31] chore(linter): fix some annotations --- rockcraft/cli.py | 2 +- rockcraft/commands/extensions.py | 4 ++-- rockcraft/extensions/_utils.py | 5 ++++- rockcraft/models/project.py | 12 ++++++++---- rockcraft/services/lifecycle.py | 12 ++++++++---- 5 files changed, 23 insertions(+), 12 deletions(-) diff --git a/rockcraft/cli.py b/rockcraft/cli.py index d8976d058..7acc9f44e 100644 --- a/rockcraft/cli.py +++ b/rockcraft/cli.py @@ -39,7 +39,7 @@ def run() -> int: return app.run() -def _create_app(): +def _create_app(): # noqa: ANN202 # pylint: disable=import-outside-toplevel # Import these here so that the script that generates the docs for the # commands doesn't need to know *too much* of the application. diff --git a/rockcraft/commands/extensions.py b/rockcraft/commands/extensions.py index 91b43e468..a52b5b4df 100644 --- a/rockcraft/commands/extensions.py +++ b/rockcraft/commands/extensions.py @@ -57,7 +57,7 @@ class ListExtensionsCommand(AppCommand, abc.ABC): ) @overrides - def run(self, parsed_args: argparse.Namespace): + def run(self, parsed_args: argparse.Namespace) -> None: """Print the list of available extensions and their bases.""" extension_presentation: dict[str, ExtensionModel] = {} @@ -95,7 +95,7 @@ class ExpandExtensionsCommand(AppCommand, abc.ABC): ) @overrides - def run(self, parsed_args: argparse.Namespace): + def run(self, parsed_args: argparse.Namespace) -> None: """Print the project's specification with the extensions expanded.""" project = Project.unmarshal(load_project(Path("rockcraft.yaml"))) diff --git a/rockcraft/extensions/_utils.py b/rockcraft/extensions/_utils.py index 30727abac..a7c956707 100644 --- a/rockcraft/extensions/_utils.py +++ b/rockcraft/extensions/_utils.py @@ -79,7 +79,10 @@ def _apply_extension( parts[part_name] = parts_snippet[part_name] -def _apply_extension_property(existing_property: Any, extension_property: Any) -> Any: +def _apply_extension_property( + existing_property: list[Any] | dict[str, Any] | None, + extension_property: list[Any] | dict[str, Any], +) -> list[Any] | dict[str, Any] | None: if existing_property: # If the property is not scalar, merge them if isinstance(existing_property, list) and isinstance(extension_property, list): diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 3ca179f76..e13e00e91 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -200,7 +200,9 @@ def _validate_title(cls, title: str | None, values: Mapping[str, Any]) -> str: @pydantic.validator("build_base", always=True) @classmethod - def _validate_build_base(cls, build_base: str | None, values: Any) -> str: + def _validate_build_base( + cls, build_base: str | None, values: dict[str, Any] + ) -> str: """Build-base defaults to the base value if not specified. :raises ProjectValidationError: If base validation fails. @@ -306,7 +308,9 @@ def _validate_parts(cls, item: dict[str, Any]) -> dict[str, Any]: @pydantic.validator("parts", each_item=True) @classmethod - def _validate_base_and_overlay(cls, item: dict[str, Any], values) -> dict[str, Any]: + def _validate_base_and_overlay( + cls, item: dict[str, Any], values: dict[str, Any] + ) -> dict[str, Any]: """Projects with "bare" bases cannot use overlays.""" if values.get("base") == "bare" and part_has_overlay(item): raise ProjectValidationError( @@ -317,7 +321,7 @@ def _validate_base_and_overlay(cls, item: dict[str, Any], values) -> dict[str, A @pydantic.validator("entrypoint_service") @classmethod def _validate_entrypoint_service( - cls, entrypoint_service: str | None, values: Any + cls, entrypoint_service: str | None, values: dict[str, Any] ) -> str | None: """Verify that the entrypoint_service exists in the services dict.""" craft_cli.emit.message( @@ -398,7 +402,7 @@ def _forbid_env_var_bash_interpolation( def to_yaml(self) -> str: """Dump this project as a YAML string.""" - def _repr_str(dumper, data): + def _repr_str(dumper: yaml.SafeDumper, data: str) -> yaml.ScalarNode: """Multi-line string representer for the YAML dumper.""" if "\n" in data: return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") diff --git a/rockcraft/services/lifecycle.py b/rockcraft/services/lifecycle.py index 8755bd34c..1b016362e 100644 --- a/rockcraft/services/lifecycle.py +++ b/rockcraft/services/lifecycle.py @@ -18,13 +18,14 @@ import contextlib from pathlib import Path -from typing import cast +from typing import Any, cast from craft_application import LifecycleService from craft_archives import repo from craft_cli import emit -from craft_parts import Features, Step, callbacks +from craft_parts import Features, LifecycleManager, Step, callbacks from craft_parts.errors import CallbackRegistrationError +from craft_parts.infos import ProjectInfo from overrides import override from rockcraft import layers @@ -83,7 +84,10 @@ def run(self, step_name: str | None, part_names: list[str] | None = None) -> Non callbacks.unregister_all() -def _install_package_repositories(package_repositories, lifecycle_manager) -> None: +def _install_package_repositories( + package_repositories: list[dict[str, Any]] | None, + lifecycle_manager: LifecycleManager, +) -> None: """Install package repositories in the environment.""" if not package_repositories: emit.debug("No package repositories specified, none to install.") @@ -97,7 +101,7 @@ def _install_package_repositories(package_repositories, lifecycle_manager) -> No emit.progress("Package repositories installed") -def _install_overlay_repositories(overlay_dir, project_info): +def _install_overlay_repositories(overlay_dir: Path, project_info: ProjectInfo) -> None: if project_info.base != "bare": package_repositories = project_info.package_repositories repo.install_in_root( From d4b888a9a564493149d18abd2e6e2d79db2f9d29 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Wed, 29 Nov 2023 13:10:05 -0500 Subject: [PATCH 10/31] chore(linter): temporarily turn off unfixed rules --- pyproject.toml | 34 ++++++++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/pyproject.toml b/pyproject.toml index 568837712..6956d8ea1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -211,6 +211,40 @@ ignore = [ # Ignored due to common usage in current code "TRY003", # Avoid specifying long messages outside the exception class "COM812", # Allow trailing comma missing + + # Ignored for now + "PGH003", + "PT004", + "PTH123", + "INP001", + "PTH118", + "PLR0913", + "PT007", + "PT012", + "ARG002", + "PTH110", + "ERA001", + "S101", + "FIX002", + "PYI024", + "PTH115", + "PTH207", + "PLR2004", + "ANN001", + "S108", + "FBT001", + "FBT002", + "B007", + "B026", + "PT011", + "PTH109", + "PTH116", + "PERF203", + "PERF401", + "E741", + "PLW290", + "TRY004", + "TRY200", ] [tool.ruff.flake8-annotations] From 39303dc8201c42d1981ffddf330de509cd11a059 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Wed, 29 Nov 2023 17:00:32 -0500 Subject: [PATCH 11/31] feat(project): migrate to pyproject.toml --- .readthedocs.yaml | 3 +- Makefile | 148 ----------------------------------------- pyproject.toml | 102 +++++++++++++++++++++++++++- requirements-dev.txt | 139 -------------------------------------- requirements-doc.txt | 78 ---------------------- requirements-jammy.txt | 1 - requirements.txt | 37 ----------- setup.cfg | 132 ------------------------------------ setup.py | 24 ------- snap/snapcraft.yaml | 16 +++-- spread.yaml | 4 +- tox.ini | 9 +-- 12 files changed, 116 insertions(+), 577 deletions(-) delete mode 100644 Makefile delete mode 100644 requirements-dev.txt delete mode 100644 requirements-doc.txt delete mode 100644 requirements-jammy.txt delete mode 100644 requirements.txt delete mode 100644 setup.cfg delete mode 100644 setup.py diff --git a/.readthedocs.yaml b/.readthedocs.yaml index e48636709..cacd8b9c4 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -34,6 +34,7 @@ formats: # and requirements required to build your docs python: install: - - requirements: requirements-doc.txt - method: pip path: . + extra_requirements: + - docs diff --git a/Makefile b/Makefile deleted file mode 100644 index 9fd8c6c8d..000000000 --- a/Makefile +++ /dev/null @@ -1,148 +0,0 @@ -.PHONY: help -help: ## Show this help. - @printf "%-40s %s\n" "Target" "Description" - @printf "%-40s %s\n" "------" "-----------" - @fgrep " ## " $(MAKEFILE_LIST) | fgrep -v grep | awk -F ': .*## ' '{$$1 = sprintf("%-40s", $$1)} 1' - -.PHONY: autoformat -autoformat: ## Run automatic code formatters. - isort . - autoflake rockcraft/ tests/ - black . - ruff check --fix-only rockcraft tests - -.PHONY: clean -clean: ## Clean artefacts from building, testing, etc. - rm -rf build/ - rm -rf dist/ - rm -rf .eggs/ - find . -name '*.egg-info' -exec rm -rf {} + - find . -name '*.egg' -exec rm -f {} + - rm -rf docs/_build/ - rm -f docs/rockcraft.* - rm -f docs/modules.rst - rm -rf docs/reference/commands - find . -name '*.pyc' -exec rm -f {} + - find . -name '*.pyo' -exec rm -f {} + - find . -name '*~' -exec rm -f {} + - find . -name '__pycache__' -exec rm -rf {} + - rm -rf .tox/ - rm -f .coverage - rm -rf htmlcov/ - rm -rf .pytest_cache - $(MAKE) -C docs clean - rm -rf .mypy_cache - -.PHONY: coverage -coverage: ## Run pytest with coverage report. - coverage run --source craft_sore -m pytest - coverage report -m - coverage html - -.PHONY: preparedocs -preparedocs: ## move file from the sphinx-starter-pack to docs folder - cp docs/sphinx-starter-pack/.sphinx/_static/* docs/_static - mkdir -p docs/_templates - cp -R docs/sphinx-starter-pack/.sphinx/_templates/* docs/_templates - cp docs/sphinx-starter-pack/.sphinx/spellingcheck.yaml docs/spellingcheck.yaml - -.PHONY: installdocs -installdocs: preparedocs ## install documentation dependencies. - $(MAKE) -C docs install - -.PHONY: docs -docs: ## Generate documentation. - rm -f docs/rockcraft.rst - rm -f docs/modules.rst - $(MAKE) -C docs clean-doc - $(MAKE) -C docs html - -.PHONY: rundocs -rundocs: ## start a documentation runserver - $(MAKE) -C docs run - -.PHONY: dist -dist: clean ## Build python package. - python setup.py sdist - python setup.py bdist_wheel - ls -l dist - -.PHONY: freeze-requirements -freeze-requirements: ## Re-freeze requirements. - tools/freeze-requirements.sh - -.PHONY: install -install: clean ## Install python package. - python setup.py install - -.PHONY: lint -lint: test-black test-codespell test-flake8 test-isort test-mypy test-pydocstyle test-pyright test-pylint test-sphinx-lint test-shellcheck ## Run all linting tests. - -.PHONY: release -release: dist ## Release with twine. - twine upload dist/* - -.PHONY: test-black -test-black: - black --check --diff . - -.PHONY: test-codespell -test-codespell: - codespell . - -.PHONY: test-flake8 -test-flake8: - flake8 rockcraft tests - -.PHONY: test-ruff -test-ruff: - ruff rockcraft tests - -.PHONY: test-integrations -test-integrations: ## Run integration tests. - pytest tests/integration - -.PHONY: test-isort -test-isort: - isort --check rockcraft tests - -.PHONY: test-mypy -test-mypy: - mypy rockcraft tests - -.PHONY: test-pydocstyle -test-pydocstyle: - pydocstyle rockcraft - -.PHONY: test-pylint -test-pylint: - pylint rockcraft - pylint tests --disable=invalid-name,missing-module-docstring,missing-function-docstring,redefined-outer-name,too-many-arguments,too-many-public-methods,no-member,import-outside-toplevel - -.PHONY: test-pyright -test-pyright: - pyright . - -.PHONY: test-shellcheck -test-shellcheck: - # shellcheck for shell scripts - git ls-files | file --mime-type -Nnf- | grep shellscript | cut -f1 -d: | xargs shellcheck - # shellcheck for bash commands inside spread task.yaml files - tools/external/utils/spread-shellcheck tests/spread/ spread.yaml - -.PHONY: test-sphinx-lint -test-sphinx-lint: - sphinx-lint --ignore docs/sphinx-starter-pack/ --ignore docs/_build --ignore docs/env --max-line-length 80 -e all docs/* - -.PHONY: test-units -test-units: ## Run unit tests. - pytest tests/unit - -.PHONY: test-docs -test-docs: installdocs ## Run docs tests. - $(MAKE) -C docs linkcheck - $(MAKE) -C docs woke - $(MAKE) -C docs spelling - -.PHONY: tests -tests: lint test-integrations test-units test-docs ## Run all tests. diff --git a/pyproject.toml b/pyproject.toml index 6956d8ea1..e63e347be 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,17 +1,107 @@ [project] name = "rockcraft" +description = "Create ROCKS" dynamic = ["version", "readme"] - +license = {file = "LICENSE"} +authors = [ + {name = "Canonical Ltd.", email = "snapcraft@lists.snapcraft.io"} +] + classifiers = [ "Development Status :: 2 - Pre-Alpha", "Intended Audience :: Developers", - "License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", "Operating System :: MacOS :: MacOS X", "Operating System :: POSIX :: Linux", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.10", ] requires-python = ">=3.10" +dependencies = [ + "certifi==2023.7.22", + "charset-normalizer==3.3.1", + "craft-application==1.0.0", + "craft-archives==1.1.3", + "craft-cli==2.5.0", + "craft-parts==1.25.2", + "craft-providers==1.20.0", + "Deprecated==1.2.14", + "distro==1.8.0", + "httplib2==0.22.0", + "idna==3.4", + "importlib-metadata==6.8.0", + "launchpadlib==1.11.0", + "lazr.restfulclient==0.14.5", + "lazr.uri==1.0.6", + "oauthlib==3.2.2", + "overrides==7.4.0", + "packaging==23.2", + "platformdirs==3.11.0", + "pydantic==1.10.13", + "pydantic-yaml==0.11.2", + "pyparsing==3.1.1", + "pyxdg==0.28", + "PyYAML==6.0.1", + "requests==2.31.0", + "requests-unixsocket==0.3.0", + "six==1.16.0", + "spdx==2.5.1", + "spdx-lookup==0.3.3", + "tabulate==0.9.0", + "types-Deprecated==1.2.9.3", + "types-PyYAML==6.0.12.12", + "typing_extensions==4.8.0", + "urllib3==1.26.18", + "wadllib==1.3.6", + "wrapt==1.15.0", + "zipp==3.17.0", +] + +[project.scripts] +rockcraft = "rockcraft.cli:run" + +[project.urls] +documentation = "https://rockcraft.readthedocs.io/en/latest/" +source = "https://github.com/canonical/rockcraft.git" +issues = "https://github.com/canonical/rockcraft/issues" + +[project.optional-dependencies] +dev = [ + "build", + "coverage[toml]==7.3.2", + "pytest==7.4.3", + "pytest-check>=2.0", + "pytest-cov==4.1.0", + "pytest-mock==3.12.0", + "pytest-subprocess", +] +ubuntu-jammy = [ + "python-apt @ https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz ; sys_platform=='linux'" +] +lint = [ + "black==23.11.0", + "codespell[toml]==2.2.6", + "ruff==0.1.6", + "yamllint==1.33.0" +] +types = [ + "mypy[reports]==1.7.1", + "pyright==1.1.337", + "types-requests", + "types-setuptools", + "types-pyyaml", + "types-tabulate>=0.9.0.2", +] +docs = [ + "furo==2023.9.10", + "sphinx>=7.1.2,<8", + "sphinx-autobuild==2021.3.14", + "sphinx-copybutton==0.5.2", + "sphinx-design==0.5.0", + "sphinx-pydantic==0.1.1", + "sphinx-toolbox==3.5.0", + "sphinx-lint==0.9.0", +] [build-system] requires = [ @@ -22,6 +112,8 @@ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] readme = {file = "README.rst"} +version = {attr = "rockcraft.__version__"} + [tool.setuptools_scm] write_to = "rockcraft/_version.py" @@ -86,14 +178,18 @@ exclude = [ "**/__pycache__", # pyright might not like the annotations generated by setuptools_scm "**/_version.py", + # sphinx-starter-pack not under control + "docs/sphinx-starter-pack/", ] [tool.mypy] -python_version = "3.8" +python_version = "3.10" exclude = [ "build", "tests", "results", + # sphinx-starter-pack not under control + "docs", ] warn_unused_configs = true warn_redundant_casts = true diff --git a/requirements-dev.txt b/requirements-dev.txt deleted file mode 100644 index 5ae501b09..000000000 --- a/requirements-dev.txt +++ /dev/null @@ -1,139 +0,0 @@ -alabaster==0.7.13 -astroid==2.15.8 -autoflake==1.7.8 -Babel==2.13.1 -beautifulsoup4==4.12.2 -black==23.11.0 -bracex==2.4 -cachetools==5.3.2 -certifi==2023.7.22 -cffi==1.16.0 -chardet==5.2.0 -charset-normalizer==3.3.1 -click==8.1.7 -codespell[toml]==2.2.6 -colorama==0.4.6 -coverage==7.3.2 -craft-application==1.0.0 -craft-archives==1.1.3 -craft-cli==2.5.0 -craft-parts==1.25.2 -craft-providers==1.20.0 -cryptography==41.0.5 -Deprecated==1.2.14 -dill==0.3.7 -distlib==0.3.7 -distro==1.8.0 -docutils==0.18.1 -exceptiongroup==1.1.3 -filelock==3.12.4 -flake8==4.0.1 -furo==2023.9.10 -html5lib==1.1 -httplib2==0.22.0 -idna==3.4 -imagesize==1.4.1 -importlib-metadata==6.8.0 -iniconfig==2.0.0 -isort==5.12.0 -jaraco.classes==3.3.0 -jeepney==0.8.0 -Jinja2==3.1.2 -jsonpointer==2.4 -keyring==24.2.0 -launchpadlib==1.11.0 -lazr.restfulclient==0.14.5 -lazr.uri==1.0.6 -lazy-object-proxy==1.9.0 -livereload==2.6.3 -lxml==4.9.3 -Markdown==3.5 -markdown-it-py==3.0.0 -MarkupSafe==2.1.3 -mccabe==0.6.1 -mdurl==0.1.2 -more-itertools==10.1.0 -mypy[reports]==1.7.1 -mypy-extensions==1.0.0 -nh3==0.2.14 -oauthlib==3.2.2 -overrides==7.4.0 -packaging==23.2 -pathspec==0.11.2 -pkginfo==1.9.6 -platformdirs==3.11.0 -pluggy==1.3.0 -polib==1.2.0 -pycodestyle==2.8.0 -pycparser==2.21 -pydantic==1.10.13 -pydantic-yaml==0.11.2 -pydocstyle==6.3.0 -pyflakes==2.4.0 -Pygments==2.16.1 -pylint==2.17.7 -pylint-fixme-info==1.0.3 -pylint-pytest==1.1.3 -pyparsing==3.1.1 -pyproject-api==1.6.1 -pyright==1.1.337 -pyspelling==2.9 -pytest==7.4.3 -pytest-check==2.2.2 -pytest-cov==4.1.0 -pytest-mock==3.12.0 -pytest-subprocess==1.5.0 -pyxdg==0.28 -PyYAML==6.0.1 -readme-renderer==42.0 -regex==2023.10.3 -requests==2.31.0 -requests-toolbelt==1.0.0 -requests-unixsocket==0.3.0 -rfc3986==2.0.0 -rich==13.6.0 -ruff==0.1.6 -SecretStorage==3.3.3 -six==1.16.0 -snowballstemmer==2.2.0 -soupsieve==2.5 -spdx==2.5.1 -spdx-lookup==0.3.3 -Sphinx==6.2.1 -sphinx-autobuild==2021.3.14 -sphinx-autodoc-typehints==1.23.0 -sphinx-basic-ng==1.0.0b2 -sphinx-copybutton==0.5.2 -sphinx-jsonschema==1.19.1 -sphinx-lint==0.8.1 -sphinx-pydantic==0.1.1 -sphinx-rtd-theme==1.3.0 -sphinx_design==0.5.0 -sphinxcontrib-applehelp==1.0.7 -sphinxcontrib-devhelp==1.0.5 -sphinxcontrib-htmlhelp==2.0.4 -sphinxcontrib-jquery==4.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.6 -sphinxcontrib-serializinghtml==1.1.9 -tabulate==0.9.0 -tomli==2.0.1 -tomlkit==0.12.1 -tornado==6.3.3 -tox==4.11.3 -twine==4.0.2 -types-Deprecated==1.2.9.3 -types-PyYAML==6.0.12.12 -types-requests==2.31.0.6 -types-setuptools==68.2.0.0 -types-tabulate==0.9.0.3 -types-urllib3==1.26.25.14 -typing_extensions==4.8.0 -urllib3==1.26.18 -virtualenv==20.24.6 -wadllib==1.3.6 -wcmatch==8.5 -webencodings==0.5.1 -wrapt==1.15.0 -yamllint==1.33.0 -zipp==3.17.0 diff --git a/requirements-doc.txt b/requirements-doc.txt deleted file mode 100644 index 88df20b7c..000000000 --- a/requirements-doc.txt +++ /dev/null @@ -1,78 +0,0 @@ -alabaster==0.7.13 -Babel==2.13.1 -beautifulsoup4==4.12.2 -bracex==2.4 -certifi==2023.7.22 -charset-normalizer==3.3.1 -colorama==0.4.6 -craft-application==1.0.0 -craft-archives==1.1.3 -craft-cli==2.5.0 -craft-parts==1.25.2 -craft-providers==1.20.0 -Deprecated==1.2.14 -distro==1.8.0 -docutils==0.18.1 -furo==2023.9.10 -html5lib==1.1 -httplib2==0.22.0 -idna==3.4 -imagesize==1.4.1 -importlib-metadata==6.8.0 -Jinja2==3.1.2 -jsonpointer==2.4 -launchpadlib==1.11.0 -lazr.restfulclient==0.14.5 -lazr.uri==1.0.6 -livereload==2.6.3 -lxml==4.9.3 -Markdown==3.5 -MarkupSafe==2.1.3 -oauthlib==3.2.2 -overrides==7.4.0 -packaging==23.2 -platformdirs==3.11.0 -polib==1.2.0 -pydantic==1.10.13 -pydantic-yaml==0.11.2 -Pygments==2.16.1 -pyparsing==3.1.1 -pyspelling==2.9 -pyxdg==0.28 -PyYAML==6.0.1 -regex==2023.10.3 -requests==2.31.0 -requests-unixsocket==0.3.0 -six==1.16.0 -snowballstemmer==2.2.0 -soupsieve==2.5 -spdx==2.5.1 -spdx-lookup==0.3.3 -Sphinx==6.2.1 -sphinx-autobuild==2021.3.14 -sphinx-autodoc-typehints==1.23.0 -sphinx-basic-ng==1.0.0b2 -sphinx-copybutton==0.5.2 -sphinx-jsonschema==1.19.1 -sphinx-lint==0.8.1 -sphinx-pydantic==0.1.1 -sphinx-rtd-theme==1.3.0 -sphinx_design==0.5.0 -sphinxcontrib-applehelp==1.0.7 -sphinxcontrib-devhelp==1.0.5 -sphinxcontrib-htmlhelp==2.0.4 -sphinxcontrib-jquery==4.1 -sphinxcontrib-jsmath==1.0.1 -sphinxcontrib-qthelp==1.0.6 -sphinxcontrib-serializinghtml==1.1.9 -tabulate==0.9.0 -tornado==6.3.3 -types-Deprecated==1.2.9.3 -types-PyYAML==6.0.12.12 -typing_extensions==4.8.0 -urllib3==1.26.18 -wadllib==1.3.6 -wcmatch==8.5 -webencodings==0.5.1 -wrapt==1.15.0 -zipp==3.17.0 diff --git a/requirements-jammy.txt b/requirements-jammy.txt deleted file mode 100644 index cf0946ab0..000000000 --- a/requirements-jammy.txt +++ /dev/null @@ -1 +0,0 @@ -python-apt@https://launchpad.net/ubuntu/+archive/primary/+sourcefiles/python-apt/2.4.0ubuntu1/python-apt_2.4.0ubuntu1.tar.xz; sys_platform == 'linux' diff --git a/requirements.txt b/requirements.txt deleted file mode 100644 index 084076644..000000000 --- a/requirements.txt +++ /dev/null @@ -1,37 +0,0 @@ -certifi==2023.7.22 -charset-normalizer==3.3.1 -craft-application==1.0.0 -craft-archives==1.1.3 -craft-cli==2.5.0 -craft-parts==1.25.2 -craft-providers==1.20.0 -Deprecated==1.2.14 -distro==1.8.0 -httplib2==0.22.0 -idna==3.4 -importlib-metadata==6.8.0 -launchpadlib==1.11.0 -lazr.restfulclient==0.14.5 -lazr.uri==1.0.6 -oauthlib==3.2.2 -overrides==7.4.0 -packaging==23.2 -platformdirs==3.11.0 -pydantic==1.10.13 -pydantic-yaml==0.11.2 -pyparsing==3.1.1 -pyxdg==0.28 -PyYAML==6.0.1 -requests==2.31.0 -requests-unixsocket==0.3.0 -six==1.16.0 -spdx==2.5.1 -spdx-lookup==0.3.3 -tabulate==0.9.0 -types-Deprecated==1.2.9.3 -types-PyYAML==6.0.12.12 -typing_extensions==4.8.0 -urllib3==1.26.18 -wadllib==1.3.6 -wrapt==1.15.0 -zipp==3.17.0 diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index 135ed889a..000000000 --- a/setup.cfg +++ /dev/null @@ -1,132 +0,0 @@ -[metadata] -name = rockcraft -version = attr: rockcraft.__version__ -description="Create ROCKS" -long_description = file: README.rst -url = https://github.com/canonical/rockcraft -project_urls = - Documentation = https://rockcraft.readthedocs.io/en/latest/ - Source = https://github.com/canonical/rockcraft.git - Issues = https://github.com/canonical/rockcraft/issues -author = Canonical Ltd. -author_email = snapcraft@lists.snapcraft.io -license = GNU Lesser General Public License v3 (LGPLv3) -license_files = LICENSE -classifiers = - Development Status :: 2 - Pre-Alpha - Intended Audience :: Developers - License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3) - Operating System :: MacOS :: MacOS X - Operating System :: POSIX :: Linux - Programming Language :: Python :: 3 - Programming Language :: Python :: 3.10 - -[options] -python_requires = >= 3.10 -include_package_data = True -packages = find: -zip_safe = False -install_requires = - craft-application>=1.0.0 - craft-archives>=1.1.0 - craft-cli - craft-parts - craft-providers - overrides - spdx-lookup - tabulate>=0.8.10 - -[options.entry_points] -console_scripts = - rockcraft = rockcraft.cli:run - -[options.package_data] -rockcraft = py.typed - -[options.extras_require] -doc = - furo - sphinx<7 - sphinx-autobuild - sphinx-autodoc-typehints - sphinx-copybutton - sphinx_design - sphinx-lint - sphinx-pydantic - sphinx-rtd-theme - pyspelling -release = - twine - wheel -test = - mccabe<0.7.0 # to resolve version conflict - coverage - black - codespell - flake8>=4.0.1 - isort - mypy - pydocstyle - pylint - pylint-fixme-info - pylint-pytest>=1.1.3 - pytest - pytest-check>=2.0 - pytest-mock - pytest-subprocess - ruff==0.1.6 - tox - types-requests - types-setuptools - types-pyyaml - types-tabulate>=0.9.0.2 -dev = - autoflake - %(doc)s - %(release)s - %(test)s - -[options.packages.find] -exclude = - tests - tests.* - -[bdist_wheel] -universal = 1 - -[codespell] -quiet-level = 3 -skip = ./docs/_build,.direnv,.git,.mypy_cache,.pytest_cache,.venv,__pycache__,venv -ignore-words-list = warmup,buildd,astroid - -[flake8] -exclude = .direnv .git .mypy_cache .pytest_cache .venv __pycache__ venv -max-line-length = 88 -# E203 whitespace before ':' -# E501 line too long -extend-ignore = E203,E501 - -[autoflake] -remove-all-unused-imports=true -ignore-init-module-imports=true -recursive=true -in-place=true - -[pydantic-mypy] -init_forbid_extra = True -init_typed = True -warn_required_dynamic_aliases = True -warn_untyped_fields = True - -[pydocstyle] -# D105 Missing docstring in magic method (reason: magic methods already have definitions) -# D107 Missing docstring in __init__ (reason: documented in class docstring) -# D203 1 blank line required before class docstring (reason: pep257 default) -# D213 Multi-line docstring summary should start at the second line (reason: pep257 default) -# D215 Section underline is over-indented (reason: pep257 default) -ignore = D105, D107, D203, D213, D215 - -[aliases] -test = pytest - -[tool:pytest] diff --git a/setup.py b/setup.py deleted file mode 100644 index 03d6377cb..000000000 --- a/setup.py +++ /dev/null @@ -1,24 +0,0 @@ -#!/usr/bin/env python3 -# -# Copyright 2021 Canonical Ltd. -# -# This program is free software; you can redistribute it and/or -# modify it under the terms of the GNU Lesser General Public -# License version 3 as published by the Free Software Foundation. -# -# This program is distributed in the hope that it will be useful, -# but WITHOUT ANY WARRANTY; without even the implied warranty of -# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU -# Lesser General Public License for more details. -# -# You should have received a copy of the GNU Lesser General Public License -# along with this program; if not, write to the Free Software Foundation, -# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -# - - -"""The setup script.""" - -from setuptools import setup - -setup() diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 9bf05c61f..95280ae08 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -57,9 +57,6 @@ parts: - wheel - pip - setuptools - python-requirements: - - requirements-jammy.txt - - requirements.txt build-attributes: - enable-patchelf build-environment: @@ -69,11 +66,22 @@ parts: override-build: | ${SNAP}/libexec/snapcraft/craftctl default - version="$(python3 setup.py --version)" + # craft-parts changed the python symlink in venv, and unusable at the time + rm -f "${CRAFT_PART_INSTALL}/bin/python3" + ln -s "${CRAFT_PART_INSTALL}/../../../stage/usr/bin/python3" "${CRAFT_PART_INSTALL}/bin/python3" + + # stable Snapcraft not support pure pyproject.toml, install it manually + "${CRAFT_PART_INSTALL}/bin/pip" install -U "${CRAFT_PART_BUILD}[ubuntu-jammy]" + + version="$(${CRAFT_PART_INSTALL}/bin/python3 -B -c 'import importlib.metadata; print(importlib.metadata.version("rockcraft"))')" + ${SNAP}/libexec/snapcraft/craftctl set version="$version" [ -n "$(echo $version | grep "post")" ] && grade=devel || grade=stable ${SNAP}/libexec/snapcraft/craftctl set grade="${grade}" + # rerun to fix the symlinks + ${SNAP}/libexec/snapcraft/craftctl default + sed -i -e '1 s|^#!/.*|#!/snap/rockcraft/current/bin/python -E|' $CRAFT_PART_INSTALL/bin/craftctl after: - rockcraft-libs diff --git a/spread.yaml b/spread.yaml index 551aa1781..908f20b66 100644 --- a/spread.yaml +++ b/spread.yaml @@ -10,9 +10,7 @@ include: - tests/ - tools/ - docs/ - - requirements-doc.txt - - requirements-jammy.txt - - Makefile + - pyproject.toml - rockcraft/ backends: diff --git a/tox.ini b/tox.ini index a88ae0277..2998ba8e4 100644 --- a/tox.ini +++ b/tox.ini @@ -28,10 +28,6 @@ user_tmp_dir = {env:TMPDIR} # We have many tests that create temporary files. Unless the user has set a # TMPDIR, this will prefer putting those temp files in $XDG_RUNTIME_DIR, # which will speed up those tests since they'll run on a ramdisk. -deps = - -r requirements.txt - -r requirements-dev.txt - -r requirements-jammy.txt env_tmp_dir = {user_tmp_dir:{env:XDG_RUNTIME_DIR:{work_dir}}}/tox_tmp/{env_name} set_env = TMPDIR={env_tmp_dir} @@ -43,7 +39,7 @@ pass_env = [test] # Base configuration for unit and integration tests package = editable -extras = dev +extras = dev, ubuntu-jammy allowlist_externals = mkdir commands_pre = mkdir -p {tox_root}/results @@ -59,7 +55,7 @@ labels = change_dir = unit: tests/unit integration: tests/integration -commands = pytest {tty:--color=yes} --cov={tox_root}/starcraft --cov-config={tox_root}/pyproject.toml --cov-report=xml:{tox_root}/results/coverage-{env_name}.xml --junit-xml={tox_root}/results/test-results-{env_name}.xml {posargs} +commands = pytest {tty:--color=yes} --cov={tox_root}/rockcraft --cov-config={tox_root}/pyproject.toml --cov-report=xml:{tox_root}/results/coverage-{env_name}.xml --junit-xml={tox_root}/results/test-results-{env_name}.xml {posargs} [lint] # Standard linting configuration package = editable @@ -120,7 +116,6 @@ description = Run pre-commit on staged files or arbitrary pre-commit commands (t commands = pre-commit {posargs:run} [docs] # Sphinx documentation configuration -deps = -r requirements-docs.txt extras = docs package = editable no_package = true From 7dd859891e72eac2eaf2344f5755ce7893809d9b Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Wed, 29 Nov 2023 17:49:24 -0500 Subject: [PATCH 12/31] feat(ci): use tox.ini for ci --- .github/workflows/tests.yaml | 181 +++++++++++++++++++++-------------- tox.ini | 1 + 2 files changed, 109 insertions(+), 73 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index ce7a6fae2..23dae82e9 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -1,108 +1,143 @@ name: Tests on: - pull_request: push: branches: - - main + - "main" + - "feature/*" + - "hotfix/*" + - "release/*" + - "renovate/*" + pull_request: jobs: - linters: + lint: runs-on: ubuntu-22.04 steps: - - name: Checkout code + - name: Checkout uses: actions/checkout@v4 with: fetch-depth: 0 submodules: true + - name: conventional commits + uses: webiny/action-conventional-commits@v1.1.0 - name: Install packages run: | sudo apt update sudo apt install -y libapt-pkg-dev aspell aspell-en - - name: Set up Python 3.10 + - name: Setup Python uses: actions/setup-python@v4 with: - python-version: "3.10" - - name: Install python packages and dependencies - run: | - pip install -U wheel -r requirements-jammy.txt -r requirements.txt -r requirements-dev.txt - pip install -e . - - name: Run black - run: | - make test-black - - name: Run codespell - run: | - make test-codespell - - name: Run flake8 - run: | - make test-flake8 - - name: Run isort - run: | - make test-isort - - name: Run mypy - run: | - make test-mypy - - name: Run pydocstyle - run: | - make test-pydocstyle - - name: Run pyright - run: | - sudo snap install --classic node - sudo snap install --classic pyright - make test-pyright - - name: Run pylint - run: | - make test-pylint - - name: Run sphinx-lint - run: | - make test-sphinx-lint - - name: Run shellcheck - run: | - sudo snap install shellcheck - make test-shellcheck - - name: Run linkcheck,woke,spelling - run: | - sudo snap install woke - make test-docs - - tests: + python-version: '3.10' + cache: 'pip' + - name: Configure environment + run: | + echo "::group::Begin snap install" + echo "Installing snaps in the background while running apt and pip..." + sudo snap install --no-wait --classic pyright + sudo snap install --no-wait shellcheck + echo "::endgroup::" + echo "::group::pip install" + python -m pip install tox + echo "::endgroup::" + echo "::group::Create virtual environments for linting processes." + tox run --colored yes -m lint --notest + echo "::endgroup::" + echo "::group::Wait for snap to complete" + snap watch --last=install + echo "::endgroup::" + - name: Run Linters + run: tox run --skip-pkg-install --no-list-dependencies --colored yes -m lint + unit: strategy: matrix: - os: [ubuntu-22.04, windows-2019] - python-version: ["3.10", "3.12"] - - runs-on: ${{ matrix.os }} + platform: [ubuntu-22.04, windows-2019] + runs-on: ${{ matrix.platform }} steps: - - name: Checkout code - uses: actions/checkout@v4 + - uses: actions/checkout@v4 with: fetch-depth: 0 - - name: Set up Python ${{ matrix.python-version }} on ${{ matrix.os }} + - name: Set up Python uses: actions/setup-python@v4 with: - python-version: ${{ matrix.python-version }} - - name: Install python packages and dependencies - run: | - pip install -U wheel + python-version: | + 3.10 + 3.12 + cache: 'pip' - name: Install Ubuntu-specific dependencies - if: ${{ startsWith(matrix.os, 'ubuntu') }} + if: ${{ startsWith(matrix.platform, 'ubuntu') }} run: | sudo apt update - sudo apt install -y python3-pip python3-setuptools python3-wheel python3-venv libapt-pkg-dev + sudo apt install -y libapt-pkg-dev - name: Install Ubuntu 22.04-specific dependencies - if: ${{ matrix.os == 'ubuntu-22.04' }} + if: ${{ matrix.platform == 'ubuntu-22.04' }} run: | - pip install -U -r requirements-jammy.txt # 22.04 is the only one that has an 'umoci' package sudo apt install -y umoci - - name: Install dependencies - run: | - pip install -U -r requirements.txt -r requirements-dev.txt - pip install -e . - - name: Run unit tests + - name: Configure environment + run: | + echo "::group::pip install" + python -m pip install tox + echo "::endgroup::" + mkdir -p results + - name: Setup Tox environments + run: tox run --colored yes -m tests --notest + - name: Test with tox + run: tox run --skip-pkg-install --no-list-dependencies --result-json results/tox-${{ matrix.platform }}.json --colored yes -m unit-tests + env: + PYTEST_ADDOPTS: "--no-header -vv -rN" + - name: Upload code coverage + uses: codecov/codecov-action@v3 + with: + directory: ./results/ + files: coverage*.xml + - name: Upload test results + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: test-results-${{ matrix.platform }} + path: results/ + integration: + strategy: + matrix: + platform: [ubuntu-22.04, windows-2019] + runs-on: ${{ matrix.platform }} + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: | + 3.10 + 3.12 + cache: 'pip' + - name: Install Ubuntu-specific dependencies + if: ${{ startsWith(matrix.platform, 'ubuntu') }} run: | - make test-units - - name: Run integration tests + sudo apt update + sudo apt install -y libapt-pkg-dev + - name: Install Ubuntu 22.04-specific dependencies + if: ${{ matrix.platform == 'ubuntu-22.04' }} run: | - make test-integrations - + # 22.04 is the only one that has an 'umoci' package + sudo apt install -y umoci + - name: Configure environment + run: | + echo "::group::pip install" + python -m pip install tox + echo "::endgroup::" + mkdir -p results + - name: Setup Tox environments + run: tox run --colored yes -m tests --notest + - name: Test with tox + run: tox run --skip-pkg-install --no-list-dependencies --result-json results/tox-${{ matrix.platform }}.json --colored yes -m integration-tests + env: + PYTEST_ADDOPTS: "--no-header -vv -rN" + - name: Upload test results + if: success() || failure() + uses: actions/upload-artifact@v3 + with: + name: test-results-${{ matrix.platform }} + path: results/ \ No newline at end of file diff --git a/tox.ini b/tox.ini index 2998ba8e4..2d4532d03 100644 --- a/tox.ini +++ b/tox.ini @@ -36,6 +36,7 @@ pass_env = CI CRAFT_* PYTEST_ADDOPTS + RUNNER_ARCH [test] # Base configuration for unit and integration tests package = editable From f7ce055211c01ed842ab4a4bbfde76aa658403b4 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Thu, 30 Nov 2023 13:23:12 -0500 Subject: [PATCH 13/31] test: allow venv sh shebang --- tests/integration/plugins/test_python_plugin.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/tests/integration/plugins/test_python_plugin.py b/tests/integration/plugins/test_python_plugin.py index 5cc4091df..9f7e35020 100644 --- a/tests/integration/plugins/test_python_plugin.py +++ b/tests/integration/plugins/test_python_plugin.py @@ -111,9 +111,13 @@ def test_python_plugin_ubuntu(base, tmp_path, run_lifecycle): assert list(bin_dir.glob("python*")) == [] # Check the shebang in the "hello" script - expected_shebang = "#!/bin/python3" + # In test env this could be replaced with sh that exec python by the venv + expected_shebang = ("#!/bin/python3", "#!/bin/sh") hello = bin_dir / "hello" - assert hello.read_text().startswith(expected_shebang) + hello_text = hello.read_text() + assert hello_text.startswith(expected_shebang) + if hello_text.startswith("#!/bin/sh"): + assert "bin/python" in hello_text # Check the extra sitecustomize.py module that we add expected_text = SITECUSTOMIZE_TEMPLATE.replace("EOF", "") @@ -146,9 +150,13 @@ def test_python_plugin_bare(tmp_path, run_lifecycle): ) # Check the shebang in the "hello" script - expected_shebang = "#!/bin/python3" + # In test env this could be replaced with sh that exec python by the venv + expected_shebang = ("#!/bin/python3", "#!/bin/sh") hello = bin_dir / "hello" - assert hello.read_text().startswith(expected_shebang) + hello_text = hello.read_text() + assert hello_text.startswith(expected_shebang) + if hello_text.startswith("#!/bin/sh"): + assert "bin/python" in hello_text # Check the extra sitecustomize.py module that we add expected_text = SITECUSTOMIZE_TEMPLATE.replace("EOF", "") From cecd5330f4b0ce6c468d1458a7ae6be08338d0dd Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Thu, 30 Nov 2023 13:31:45 -0500 Subject: [PATCH 14/31] docs: use starbase readthedocs --- .readthedocs.yaml | 29 ++++++++--------------------- 1 file changed, 8 insertions(+), 21 deletions(-) diff --git a/.readthedocs.yaml b/.readthedocs.yaml index cacd8b9c4..463459e80 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -1,40 +1,27 @@ # .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details ---- + # Required version: 2 -build: - os: ubuntu-22.04 - tools: - python: "3.10" - jobs: - post_checkout: - - git submodule update --init -- docs/sphinx-starter-pack - - make preparedocs - post_install: - - ./tools/mkdoclinks.sh - # Build documentation in the docs/ directory with Sphinx sphinx: configuration: docs/conf.py - builder: dirhtml - fail_on_warning: true - -# Build documentation with MkDocs -# mkdocs: -# configuration: mkdocs.yml # Optionally build your docs in additional formats such as PDF formats: - pdf + - epub + +build: + os: ubuntu-22.04 + tools: + python: "3" -# Optionally set the version of Python -# and requirements required to build your docs python: install: - method: pip path: . extra_requirements: - - docs + - docs \ No newline at end of file From aaca6173ae4f533a0505cbf73f6da997dbb6fa92 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Fri, 1 Dec 2023 12:31:29 -0500 Subject: [PATCH 15/31] test(docs): delete docs build spread test --- docs/_static/favicon.png | Bin 0 -> 7622 bytes docs/how-to/code/build-docs/task.yaml | 34 -------------------------- 2 files changed, 34 deletions(-) create mode 100644 docs/_static/favicon.png delete mode 100644 docs/how-to/code/build-docs/task.yaml diff --git a/docs/_static/favicon.png b/docs/_static/favicon.png new file mode 100644 index 0000000000000000000000000000000000000000..316643d6dbe2f29c8999541749009b4d042cf4d0 GIT binary patch literal 7622 zcmeH~c{tSV_s0i`>}1JO#uOoAW{eqTGIk;RzN8qlFfudDjAd-0vPUY(S}MD!BuQC9 zv^+&b*%hKJSt8lKLp|H|`~LG>*YEq^GuQQ*`OJNv^E&r=-{*5*pA&CwY0S?f#RC8U z_)Sd=ZCSs-t&4jn>zf`DvIqe1_=VUzGinibr?W&;$ElZxMbKW$%`27VAreO`1X2wqw!G8 zrQ;&tn(?r?qlmZdno2$EKA-yBC&R8f1xI|?wCWjHjcv$pgAHz8UhCg{I5Wi2ANw?8 z4Qeg1j7wY0vZgk2`-+kHt@n!d!l*$Tw z?b!9s&7(^NP%A#Hku0PR7sej96t9Z3Zzbe&ZClq4;Apk={%{8rc*I%f8Ht>$x&h8$Dk z;n|T=LEY-``A&`cb#&_pmt4W^Xc`~(OmKNxQC^HNglqWOU^S!I4hdDczX>Go$3pTVoT8eZMCh&dC>X!qF) zNE0IZTDH1`_`C85!HyQ4ezkoq9E{N;Bf%24dRF*!6RsTNerhcA;BLx?%6j!3-*UCY zzmoXu_097*q9k@*R5VC{aR}tM_G{hP=$AgwpDwHuDfHPUs^IBP$6aU7=w$N9_=w2I zn~0}@99)x8glDLeNHMY$qS)KVBsIQ7X>;;O62 z=W_Phy~uSbwE*!IBh7A+=!5s0yQLhlq@iZ(N08snb$F#(*$`$#$pIlQ0ahI_#nd#E zY-l#D2a(#S-Shdh)vke#S%uWa@RkhYmo*iVORmj#y1MV^KVTYFz8KTi*!(s&QMWo0 zi@U1LAWtm#(#_KE&YPeNl_L`}a7w65?PZywVsc`QFwYpKl?z<{H(Or5OiSF`ZEiB%5^` zDAW-(nc>IC*jb|_{l)I9sIJO=fzg6-OEbfqn4@J)m*-&CwY>>79}n{mUwnUeHRin_ zygdVdLoTAvLu1tJJmsI)2;U*yL+!O6Z`1L94^R>@+h$vXlD6E+pELEF6%3LFx44>y>W9A_m@Nm+Y_hXZysy zJX}zVb}XbwauU2AMc-I(yxty`oMlRJD-Gww=+}emrg>+KT{vSiGzQ4F??^aV3oA=V zMwmlZk5R&~J$^r>+u-h@o%d48jX{=vj>eJglVOW3dZ4hgvyW$OpH&xH)o2WkN1JgU z@U`^TxBlXypKnVlnv+tXo87-~Ti_7*0d}?M{>9zbNemUb`x2j$yKh;DQXg6d#dK(P zUaw^bdT88;CwK)g7anMh>Cl#@y8Jq7EA>B$A@=oN5y}KKYcWo|DB0WCIsQh_#PfG}OmyqkPbSIWSri0v*x`+QNiw)?2+(8a!W8It#7X~R zs=CtS7|m(*GCSOl)RT&JmeT#!=~k`+*{OP+^n@Mdyj7h_h*IEYv$*2JFIFM(3v}HR zNe*-4E52t$zRDK9sPMluDa4i-je9@UNtfQ!6uc&|_f5a4gM{*ZgO|)b<$N(JoJEfw z^qBKMuPmPq$U5}UXi17Hac5}CKTyr=u;|{gMdoL&jHtbyY3c_<3p@4S*}Ox>H<&1_ zgz}M!$v4N#rh!q2)qpsATE7G7!6B($dmVk56!5<1g$}_~jBD`=J=x~zB2#b|zwzw} zp~qJ{mi(pFzr{b0r?l96-FG__%*!THH%%r$ongSF6bZsT7#ZB*RA}?m$MY=e(WbhuYFO9j5O2 zck23Jvm`zhH2d}R>WCkos~MvgQqm$0O7TDG;8%Vb(0)sy*tr6+L)!FF*4PupwuX1T ztzFq?Y0?W_OZRgQRL<^85nbp^b(KnY;ip110F}{)V=mZyi0Zr z@1%PRBuYnTCTTqkFk1hTRmN%QTbb$#OD7zWGfFvicVF*s15~iFTN%>g^tmgv&r(Xr zda?0P*=FUOVzZ}ITfb2}@*+l$>C$t3;_EVzVgv0*)#9508U{0wAL4GVNPoHd%G>;q zv7x_G(r!g$^y!z&$eXEkg2hw5y%GcLc?O59xN4uI$)J7p5Ylo- z`10v4J9i^8Z-V-lI|5SggO40?^@)pCi@80}R`cL~rhVHFn*!%Vo;2}O##I%DI&#x{ z!rNjq^iq`A>y^uvPYzRD^7l7RjNCrwSvkO$tT7a`JXdR;h?49c)NZ~S#w)H6r^KC~ zb*B~adYR69(4i?gE@k}%=-T?dkoVOSCC?TskLf9Kzdr!|SUcbvVl|*abwl+aI5Fr#ugVRE+;*qHGqGBzbzwG zWHVjVwK>?YKP6~+U(hTd?*y^rJxrh|lW_9`40iFjSnUa6U7d%*Pj5GyhpT3oH_*cd znp%5m6<^P5FMpbFpeb;Kf#y^?`4fXr2ZwaNLCx-tb+)mMdmZ1(_YgR@zlV@Mq$wdI z9!zXADIAYCX!guIH-;n{8OY~?k`+sitmr=_oLvz2s&XUz7CJAnlivGWt%oEamu#dW zw@X;9L@|$#*TxlLYkt#PXM9AKv1UqpHmV$zienQiIxnO1U3P7z`=yoT1JlMm?JJ&r zvio&=RNIT*FD>68Yghs{;ePp5YiVfcf@#RDe52&ZI`Z&`hxyM!8^#2LBHqXw&<^NW z$X(?cd_9qMx1j;tUOB$h-QPONalW8jM5Ccm4(fVsDj+3@t2cCY*}Sq|)^#r}=U!(Q ziq#`XkyyQevxPYtM<#G&vY9c>=1U^ELrCNQwTV6rcTjt<6vwz+85{nju9 z1l%@ZoWy{fEv$hCR2l)OuBxsI1sesE0@Xm;JU}fPo`|+JJn{>{`iB8|GZ=no2qY*d zNHqwqN~L*0U?>y{0#$>ksexG*U^qd)?MvH!0KJnj#FzW|!=b~<<*gy2gcvrOr%sIb3bs;SGKe&Ih{yq0?W0sYL1=^5`3)otosUZfm6(5bK;z)S( z_N^vPgNVg>62amBiwx zAqi?kC>91L!qpLAbpi|KiStwkt7&*@s^c_qc-Gus5LPr2t17X+e_hoU3eQ3z)SyTx zOj8YvRfpoh>To0;j3VMSz;J>(5l$q)VFdh76ds2@LZy+htnDO`v0em-AH{1s#+Kk{ zJ!?}8NKF;`=ZLj0mO*4WU_j<1Nr}Tj}-Vv z#{X8=zjFN}1^$uozt#2sCKu124G)3BdJ76-wKG61fp4r)*wcFI zbssaT>98UgslMIhN@1+^y;@p9FXv zxZj*dTNA-sDVigP=Z+54^c(hj#XPYJ;7HxiC7{wXzCK+OsI&a7zWmFn^@>&TA4uY{ z{uX&!dJhILpS>ZzLzn#pK;?Xn;-T*S@Ai7v?`@qwkwpct&(DiYSzY#c@mw#-Y)5B9 zWvI0N6VQjjU}>hF6ri9&hDzr5`FL=FLpS2Pi(7sLuJ+WV^XQEiLm?syPvu2|rlo@f zfJa(|mT8hR3kPAf$3E?Bs3B>Tf6M1iAomj|(J`N7yWFjS5U{@Gi|kBD?e1fZX!Za) zK&Wc$xSM>|(;MBZA$wNtbgMGf0f)Y0~s zgVUXTneEgiP~lC>yT^QlrWavC^3ZedtMjr`&-MCCnsx4>d>0=oB-mxAs983JdI&z% z-8{445t&ETZk^CJo_ckf`_;|%dv!NNc|}YD+t=erDIQB3Y~70`U_*%Ev!O^2`n4(~ zcFA47AWZg^7$;x&viwGGiR2~S^Krd(u|?5^LdWlTV~+FngvT(~mv7^%!orJMms3ue zy~usK@?9Aq!?YT%$kE$%{r=;W((jzj?{)~uUrO@*;NExk(lPauIcmL99p%S4bxqxq zu4GH^j0Apo3E}Io6v%FY7#+CWJCuXVJ#drDDTWQG9M=KMdoLv+D-38F2znuDiUsV^ zTg)wQPrc`!Y?bzMhW$Q5xc> zWtXrgJ+j+BemFJdPBcgAnUo7@@Lsvhgu!k#xV$>rYPj!|l+gLR^Fr`++`a0Tz4f>J z4%41bM84VZX`LUqQX|^eGXo5yOGYWPrE%yD)J$HKAfl}mbAc%fh@yLat*aJeYKR8G(OD*SjW zczbO0L$Tt%+6VnlQxgbt)U%fnyKj{WdTmJUaM820FlLr5-%6jV{j7iw(-wA}}s4xe0lq znSAts@O)B3hmT5-*XS}^RdEd4kkFlqOZ!_6o-6Sj^vPzH?m7{c+*rczoGFC{AT)KV zJ*T^R7M*;Xd)6@HZOxjFZz^^Dg*8{{al+&e)N;X!-JNM3G*zzlS6v@BN1zY;5+%7k zBzKO-Sq@K}IAbIzS4aTHi4;)d^7eDtNH52vC#-~J?%IWlBbh-fvY+q>Y;es+_nn-X iZ1~FP56xV!2dweJhhhjgC3&H( Date: Fri, 1 Dec 2023 13:43:12 -0500 Subject: [PATCH 16/31] chore(linter): fix mypy annotation errors --- rockcraft/cli.py | 4 ++-- rockcraft/models/project.py | 19 +++++++++++++------ rockcraft/oci.py | 9 +++++---- rockcraft/plugins/python_plugin.py | 2 +- rockcraft/services/lifecycle.py | 10 ++++++---- rockcraft/usernames.py | 3 ++- 6 files changed, 29 insertions(+), 18 deletions(-) diff --git a/rockcraft/cli.py b/rockcraft/cli.py index 7acc9f44e..816a59ed3 100644 --- a/rockcraft/cli.py +++ b/rockcraft/cli.py @@ -36,10 +36,10 @@ def run() -> int: app = _create_app() - return app.run() + return app.run() # type: ignore[no-any-return] -def _create_app(): # noqa: ANN202 +def _create_app(): # type: ignore[no-untyped-def] # noqa: ANN202 # pylint: disable=import-outside-toplevel # Import these here so that the script that generates the docs for the # commands doesn't need to know *too much* of the application. diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index e13e00e91..a7846b2ec 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -32,7 +32,7 @@ import yaml from craft_application.models import BuildInfo from craft_application.models import Project as BaseProject -from craft_archives import repo +from craft_archives import repo # type: ignore[import-untyped] from craft_providers import bases from pydantic_yaml import YamlModelMixin @@ -50,8 +50,10 @@ class Platform(pydantic.BaseModel): """Rockcraft project platform definition.""" - build_on: pydantic.conlist(str, unique_items=True, min_items=1) | None - build_for: pydantic.conlist(str, unique_items=True, min_items=1) | None + build_on: pydantic.conlist(str, unique_items=True, min_items=1) | None # type: ignore[valid-type] + build_for: pydantic.conlist( # type: ignore[valid-type] + str, unique_items=True, min_items=1 + ) | None class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" @@ -188,7 +190,7 @@ def _validate_license(cls, rock_license: str) -> str: raise ProjectValidationError( f"License {rock_license} not valid. It must be valid and in SPDX format." ) - return lic.id + return str(lic.id) @pydantic.validator("title", always=True) @classmethod @@ -337,7 +339,12 @@ def _validate_entrypoint_service( "a valid Pebble service." ) - command = values.get("services")[entrypoint_service].command + services = values.get("services") + if services: + command = services[entrypoint_service].command + else: + raise ProjectValidationError("The project does not define any services.") + command_sh_args = shlex.split(command) # optional arg is surrounded by brackets, so check that they exist in the # right order @@ -432,7 +439,7 @@ def unmarshal(cls, data: dict[str, Any]) -> "Project": def generate_metadata( self, generation_time: str, base_digest: bytes - ) -> tuple[dict, dict]: + ) -> tuple[dict[str, Any], dict[str, Any]]: """Generate the ROCK's metadata (both the OCI annotation and internal metadata. :param generation_time: the UTC time at the time of calling this method diff --git a/rockcraft/oci.py b/rockcraft/oci.py index f35d1278e..fa93cefd6 100644 --- a/rockcraft/oci.py +++ b/rockcraft/oci.py @@ -295,13 +295,14 @@ def add_user( emit.progress(f"Adding user {username}:{uid} with group {username}:{uid}") self.add_layer(tag, Path(tmpfs)) - def stat(self) -> dict[Any, Any]: + def stat(self) -> dict[str, Any]: """Obtain the image statistics, as reported by "umoci stat --json".""" image_path = self.path / self.image_name - output = _process_run( + output: bytes = _process_run( ["umoci", "stat", "--json", "--image", str(image_path)] ).stdout - return json.loads(output) + result: dict[str, Any] = json.loads(output) + return result @staticmethod def digest(source_image: str) -> bytes: @@ -598,7 +599,7 @@ def _inject_architecture_variant(image_path: Path, variant: str) -> None: tl_index_path.write_bytes(json.dumps(tl_index).encode("utf-8")) -def _process_run(command: list[str], **kwargs: Any) -> subprocess.CompletedProcess: +def _process_run(command: list[str], **kwargs: Any) -> subprocess.CompletedProcess[Any]: """Run a command and handle its output.""" if not Path(command[0]).is_absolute(): command[0] = get_snap_command_path(command[0]) diff --git a/rockcraft/plugins/python_plugin.py b/rockcraft/plugins/python_plugin.py index b8425ce48..59daff671 100644 --- a/rockcraft/plugins/python_plugin.py +++ b/rockcraft/plugins/python_plugin.py @@ -75,7 +75,7 @@ class PythonPlugin(python_plugin.PythonPlugin): @override def _should_remove_symlinks(self) -> bool: """Overridden because for ubuntu bases we must always remove the symlinks.""" - return self._part_info.base != "bare" + return bool(self._part_info.base != "bare") @override def _get_system_python_interpreter(self) -> str | None: diff --git a/rockcraft/services/lifecycle.py b/rockcraft/services/lifecycle.py index 1b016362e..ec477d7ad 100644 --- a/rockcraft/services/lifecycle.py +++ b/rockcraft/services/lifecycle.py @@ -21,11 +21,11 @@ from typing import Any, cast from craft_application import LifecycleService -from craft_archives import repo +from craft_archives import repo # type: ignore[import-untyped] from craft_cli import emit from craft_parts import Features, LifecycleManager, Step, callbacks from craft_parts.errors import CallbackRegistrationError -from craft_parts.infos import ProjectInfo +from craft_parts.infos import ProjectInfo, StepInfo from overrides import override from rockcraft import layers @@ -111,10 +111,12 @@ def _install_overlay_repositories(overlay_dir: Path, project_info: ProjectInfo) ) -def _post_prime_callback(step_info) -> bool: +def _post_prime_callback(step_info: StepInfo) -> bool: prime_dir = step_info.prime_dir - files = step_info.state.files base_layer_dir = step_info.rootfs_dir + files: set[str] + + files = step_info.state.files if step_info.state else set() layers.prune_prime_files(prime_dir, files, base_layer_dir) return True diff --git a/rockcraft/usernames.py b/rockcraft/usernames.py index 105bdbf20..6593358a4 100644 --- a/rockcraft/usernames.py +++ b/rockcraft/usernames.py @@ -16,6 +16,7 @@ """List of allowed shared usernames/UIDs (analogously to SnapD).""" + import pydantic @@ -38,7 +39,7 @@ def _validate_run_user(cls, username: str) -> str: return username - def get_dict(self) -> dict: + def get_dict(self) -> dict[str, dict[str, int]]: """Cast the object into a dict using the username as the key.""" return {self.username: {"uid": self.uid}} From 3144a2b3097b05c0ba7b4fd0626a0c21b0dfa491 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Fri, 1 Dec 2023 14:48:41 -0500 Subject: [PATCH 17/31] chore(linter): fix pyright annotation errors --- rockcraft/application.py | 2 +- rockcraft/commands/extensions.py | 2 +- rockcraft/commands/init.py | 2 +- rockcraft/extensions/_utils.py | 2 +- rockcraft/models/project.py | 46 +++++++++++++++------------ rockcraft/oci.py | 6 ++-- rockcraft/pebble.py | 14 +++++--- rockcraft/plugins/python_plugin.py | 4 +-- rockcraft/services/lifecycle.py | 2 +- rockcraft/services/package.py | 4 +-- rockcraft/services/provider.py | 2 +- rockcraft/services/service_factory.py | 4 +-- rockcraft/utils.py | 10 ++++-- 13 files changed, 58 insertions(+), 42 deletions(-) diff --git a/rockcraft/application.py b/rockcraft/application.py index 1bb199619..d933a0022 100644 --- a/rockcraft/application.py +++ b/rockcraft/application.py @@ -19,7 +19,7 @@ from typing import Any from craft_application import Application, AppMetadata, util -from overrides import override +from overrides import override # type: ignore[reportUnknownVariableType] from rockcraft import models from rockcraft.models import project diff --git a/rockcraft/commands/extensions.py b/rockcraft/commands/extensions.py index a52b5b4df..9870bde96 100644 --- a/rockcraft/commands/extensions.py +++ b/rockcraft/commands/extensions.py @@ -24,7 +24,7 @@ import tabulate from craft_application.commands import AppCommand from craft_cli import emit -from overrides import overrides +from overrides import overrides # type: ignore[reportUnknownVariableType] from pydantic import BaseModel from rockcraft import extensions diff --git a/rockcraft/commands/init.py b/rockcraft/commands/init.py index 08077fe09..903bfef28 100644 --- a/rockcraft/commands/init.py +++ b/rockcraft/commands/init.py @@ -21,7 +21,7 @@ from craft_application.commands import AppCommand from craft_cli import emit -from overrides import overrides +from overrides import overrides # type: ignore[reportUnknownVariableType] from rockcraft import errors diff --git a/rockcraft/extensions/_utils.py b/rockcraft/extensions/_utils.py index a7c956707..45291d21e 100644 --- a/rockcraft/extensions/_utils.py +++ b/rockcraft/extensions/_utils.py @@ -65,7 +65,7 @@ def _apply_extension( if "parts" not in yaml_data: yaml_data["parts"] = {} - parts = yaml_data["parts"] + parts: dict[str, Any] = yaml_data["parts"] for part_definition in parts.values(): for property_name, property_value in part_extension.items(): part_definition[property_name] = _apply_extension_property( diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index a7846b2ec..7f0be8274 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -17,7 +17,7 @@ """Project definition and helpers.""" import re import shlex -from collections.abc import Mapping, Sequence +from collections.abc import Callable, Mapping, Sequence from pathlib import Path from typing import ( TYPE_CHECKING, @@ -59,7 +59,9 @@ class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + alias_generator: Callable[[str], str] = lambda s: s.replace( # noqa: E731 + "_", "-" + ) @pydantic.validator("build_for", pre=True) @classmethod @@ -73,8 +75,8 @@ def _vectorise_build_for(cls, val: str | list[str]) -> list[str]: @classmethod def _validate_platform_set(cls, values: Mapping[str, Any]) -> Mapping[str, Any]: """Validate the build_on build_for combination.""" - build_for = values["build_for"] if values.get("build_for") else [] - build_on = values["build_on"] if values.get("build_on") else [] + build_for: list[str] = values["build_for"] if values.get("build_for") else [] + build_on: list[str] = values["build_on"] if values.get("build_on") else [] # We can only build for 1 arch at the moment if len(build_for) > 1: @@ -126,13 +128,16 @@ class Project(YamlModelMixin, BaseProject): name: NameStr # type: ignore # summary is Optional[str] in BaseProject summary: str # type: ignore - description: str + description: str # type: ignore[reportIncompatibleVariableOverride] rock_license: str = pydantic.Field(alias="license") platforms: dict[str, Any] base: Literal["bare", "ubuntu@20.04", "ubuntu@22.04"] build_base: Literal["ubuntu@20.04", "ubuntu@22.04"] | None environment: dict[str, str] | None - run_user: Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)] | None # type: ignore + if TYPE_CHECKING: + run_user: str | None + else: + run_user: Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)] | None # type: ignore services: dict[str, Service] | None checks: dict[str, Check] | None entrypoint_service: str | None @@ -148,7 +153,9 @@ class Config: # pylint: disable=too-few-public-methods extra = "forbid" allow_mutation = False allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + alias_generator: Callable[[str], str] = lambda s: s.replace( # noqa: E731 + "_", "-" + ) error_msg_templates = { "value_error.str.regex": INVALID_NAME_MESSAGE, } @@ -185,12 +192,12 @@ def _validate_license(cls, rock_license: str) -> str: # This is the license name we use on our stores. return rock_license - lic: spdx_lookup.License | None = spdx_lookup.by_id(rock_license) + lic: spdx_lookup.License | None = spdx_lookup.by_id(rock_license) # type: ignore[reportUnknownMemberType] if lic is None: raise ProjectValidationError( f"License {rock_license} not valid. It must be valid and in SPDX format." ) - return str(lic.id) + return str(lic.id) # type: ignore[reportUnknownMemberType] @pydantic.validator("title", always=True) @classmethod @@ -246,7 +253,9 @@ def _check_deprecated_base(base_value: str | None, field_name: str) -> str | Non def _validate_all_platforms(cls, platforms: dict[str, Any]) -> dict[str, Any]: """Make sure all provided platforms are tangible and sane.""" for platform_label in platforms: - platform = platforms[platform_label] if platforms[platform_label] else {} + platform: dict[str, Any] = ( + platforms[platform_label] if platforms[platform_label] else {} + ) error_prefix = f"Error for platform entry '{platform_label}'" # Make sure the provided platform_set is valid @@ -371,7 +380,7 @@ def _validate_package_repositories( if not package_repositories: return [] - errors = [] + errors: list[ErrorDict] = [] for repository in package_repositories: try: repo.validate_repository(repository) @@ -412,11 +421,11 @@ def to_yaml(self) -> str: def _repr_str(dumper: yaml.SafeDumper, data: str) -> yaml.ScalarNode: """Multi-line string representer for the YAML dumper.""" if "\n" in data: - return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") - return dumper.represent_scalar("tag:yaml.org,2002:str", data) + return dumper.represent_scalar("tag:yaml.org,2002:str", data, style="|") # type: ignore[reportUnknownMemberType] + return dumper.represent_scalar("tag:yaml.org,2002:str", data) # type: ignore[reportUnknownMemberType] yaml.add_representer(str, _repr_str, Dumper=yaml.SafeDumper) - return super().yaml( + return super().yaml( # type: ignore[reportUnknownMemberType] by_alias=True, exclude_none=True, allow_unicode=True, @@ -427,9 +436,6 @@ def _repr_str(dumper: yaml.SafeDumper, data: str) -> yaml.ScalarNode: @classmethod def unmarshal(cls, data: dict[str, Any]) -> "Project": """Overridden to raise ProjectValidationError() for Pydantic errors.""" - if not isinstance(data, dict): - raise TypeError("project data is not a dictionary") - try: project = super().unmarshal(data) except pydantic.ValidationError as err: @@ -538,18 +544,16 @@ def _format_pydantic_errors( def _format_pydantic_error_location(loc: Sequence[str | int]) -> str: """Format location.""" - loc_parts = [] + loc_parts: list[str] = [] for loc_part in loc: if isinstance(loc_part, str): loc_parts.append(loc_part) - elif isinstance(loc_part, int): + else: # Integer indicates an index. Go # back and fix up previous part. previous_part = loc_parts.pop() previous_part += f"[{loc_part}]" loc_parts.append(previous_part) - else: - raise RuntimeError(f"unhandled loc: {loc_part}") new_loc = ".".join(loc_parts) diff --git a/rockcraft/oci.py b/rockcraft/oci.py index fa93cefd6..a0df5b48e 100644 --- a/rockcraft/oci.py +++ b/rockcraft/oci.py @@ -442,8 +442,8 @@ def set_environment(self, env: dict[str, str]) -> None: """ emit.progress("Configuring OCI environment...") image_path = self.path / self.image_name - params = [] - env_list = [] + params: list[str] = [] + env_list: list[str] = [] for name, value in env.items(): env_item = f"{name}={value}" @@ -491,7 +491,7 @@ def set_annotations(self, annotations: dict[str, Any]) -> None: label_params = ["--clear=config.labels"] annotation_params = ["--clear=manifest.annotations"] - labels_list = [] + labels_list: list[str] = [] for label_key, label_value in annotations.items(): label_item = f"{label_key}={label_value}" labels_list.append(label_item) diff --git a/rockcraft/pebble.py b/rockcraft/pebble.py index 21b53c723..feccab07a 100644 --- a/rockcraft/pebble.py +++ b/rockcraft/pebble.py @@ -17,7 +17,7 @@ """Pebble metadata and configuration helpers.""" import glob -from collections.abc import Mapping +from collections.abc import Callable, Mapping from pathlib import Path from typing import Any, Literal @@ -70,7 +70,9 @@ class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + alias_generator: Callable[[str], str] = lambda s: s.replace( # noqa: E731 + "_", "-" + ) extra = "forbid" @@ -116,7 +118,9 @@ class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + alias_generator: Callable[[str], str] = lambda s: s.replace( # noqa: E731 + "_", "-" + ) extra = "forbid" @@ -153,7 +157,9 @@ class Config: # pylint: disable=too-few-public-methods """Pydantic model configuration.""" allow_population_by_field_name = True - alias_generator = lambda s: s.replace("_", "-") # noqa: E731 + alias_generator: Callable[[str], str] = lambda s: s.replace( # noqa: E731 + "_", "-" + ) extra = "forbid" diff --git a/rockcraft/plugins/python_plugin.py b/rockcraft/plugins/python_plugin.py index 59daff671..3e8cfac7a 100644 --- a/rockcraft/plugins/python_plugin.py +++ b/rockcraft/plugins/python_plugin.py @@ -20,7 +20,7 @@ from textwrap import dedent from craft_parts.plugins import python_plugin -from overrides import override +from overrides import override # type: ignore[reportUnknownVariableType] logger = logging.getLogger(__name__) @@ -90,7 +90,7 @@ def _get_script_interpreter(self) -> str: @override def get_build_commands(self) -> list[str]: """Overridden to add a sitecustomize.py .""" - commands = [] + commands: list[str] = [] # Detect whether PARTS_PYTHON_INTERPRETER is a full path (not supported) commands.append( diff --git a/rockcraft/services/lifecycle.py b/rockcraft/services/lifecycle.py index ec477d7ad..10639f877 100644 --- a/rockcraft/services/lifecycle.py +++ b/rockcraft/services/lifecycle.py @@ -26,7 +26,7 @@ from craft_parts import Features, LifecycleManager, Step, callbacks from craft_parts.errors import CallbackRegistrationError from craft_parts.infos import ProjectInfo, StepInfo -from overrides import override +from overrides import override # type: ignore[reportUnknownVariableType] from rockcraft import layers from rockcraft.models.project import Project diff --git a/rockcraft/services/package.py b/rockcraft/services/package.py index c295f1444..84be70782 100644 --- a/rockcraft/services/package.py +++ b/rockcraft/services/package.py @@ -23,7 +23,7 @@ from craft_application import AppMetadata, PackageService, models, util from craft_cli import emit -from overrides import override +from overrides import override # type: ignore[reportUnknownVariableType] from rockcraft import errors, oci from rockcraft.models import Project @@ -39,7 +39,7 @@ class RockcraftPackageService(PackageService): def __init__( self, app: AppMetadata, - services: RockcraftServiceFactory, + services: "RockcraftServiceFactory", *, project: models.Project, platform: str | None, diff --git a/rockcraft/services/provider.py b/rockcraft/services/provider.py index f11fcc7ea..050dd221f 100644 --- a/rockcraft/services/provider.py +++ b/rockcraft/services/provider.py @@ -17,7 +17,7 @@ """Rockcraft Provider service.""" from craft_application import ProviderService -from overrides import override +from overrides import override # type: ignore[reportUnknownVariableType] class RockcraftProviderService(ProviderService): diff --git a/rockcraft/services/service_factory.py b/rockcraft/services/service_factory.py index 397a26054..20ae8bf98 100644 --- a/rockcraft/services/service_factory.py +++ b/rockcraft/services/service_factory.py @@ -35,11 +35,11 @@ class RockcraftServiceFactory(ServiceFactory): ImageClass: type[services.RockcraftImageService] = services.RockcraftImageService # These are overrides of default ServiceFactory services - LifecycleClass: type[ + LifecycleClass: type[ # type: ignore[reportIncompatibleVariableOverride] services.RockcraftLifecycleService ] = services.RockcraftLifecycleService PackageClass: type[base_services.PackageService] = services.RockcraftPackageService - ProviderClass: type[ + ProviderClass: type[ # type: ignore[reportIncompatibleVariableOverride] services.RockcraftProviderService ] = services.RockcraftProviderService diff --git a/rockcraft/utils.py b/rockcraft/utils.py index 25bfa28d9..3368da8eb 100644 --- a/rockcraft/utils.py +++ b/rockcraft/utils.py @@ -22,14 +22,20 @@ import pathlib import shutil import sys -from collections import namedtuple from distutils.util import strtobool # pylint: disable=deprecated-module +from typing import NamedTuple import rockcraft.errors logger = logging.getLogger(__name__) -OSPlatform = namedtuple("OSPlatform", "system release machine") + +class OSPlatform(NamedTuple): + """Tuple containing the OS platform information.""" + + system: str + release: str + machine: str def is_managed_mode() -> bool: From 7d2cd8e0f16f861671da6bad40b35137612fe6bf Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Fri, 1 Dec 2023 14:57:18 -0500 Subject: [PATCH 18/31] chore(linter): fix docs linter errors --- README.rst | 12 +++++++----- tox.ini | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/README.rst b/README.rst index 0f46721ab..0171c7b64 100644 --- a/README.rst +++ b/README.rst @@ -8,7 +8,8 @@ Rockcraft Purpose ------- -Tool to create OCI Images using the language from `Snapcraft`_ and `Charmcraft`_. +Tool to create OCI Images using the language from `Snapcraft`_ and +`Charmcraft`_. .. _Snapcraft: https://snapcraft.io @@ -44,13 +45,14 @@ https://canonical-rockcraft.readthedocs-hosted.com/ :target: https://snapcraft.io/rockcraft - + Testing ------- -In addition to unit tests in :code:`tests/unit`, which can be run with :code:`make test-units`, -a number of integrated tests in :code:`tests/spread` can be run with `Spread`_. See the -`general notes`_ and take note of these ``rockcraft``-specific instructions: +In addition to unit tests in :code:`tests/unit`, which can be run with +:code:`make test-units`, a number of integrated tests in :code:`tests/spread` +can be run with `Spread`_. See the `general notes`_ and take note of these +``rockcraft``-specific instructions: * Initialize/update git submodules to fetch Spread-related helper scripts: diff --git a/tox.ini b/tox.ini index 2d4532d03..4ed15b3ec 100644 --- a/tox.ini +++ b/tox.ini @@ -146,4 +146,4 @@ base = docs labels = lint allowlist_externals = bash, xargs commands_pre = bash -c '{[lint-docs]find} > {env_tmp_dir}/lint_docs_files' -commands = xargs --no-run-if-empty --arg-file {env_tmp_dir}/lint_docs_files sphinx-lint --max-line-length 80 --enable all --ignore "docs/sphinx-starter-pack/" {posargs} \ No newline at end of file +commands = xargs --no-run-if-empty --arg-file {env_tmp_dir}/lint_docs_files sphinx-lint --max-line-length 80 --enable all --ignore "docs/sphinx-starter-pack/" --ignore "README.rst" {posargs} \ No newline at end of file From a3c4eac91e742ea6066453ec15c40ebd5801c7e4 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Fri, 1 Dec 2023 15:23:10 -0500 Subject: [PATCH 19/31] chore(linter): fix yaml linter errors --- .github/ISSUE_TEMPLATE/bug.yaml | 1 - .github/workflows/snap.yaml | 2 +- .github/workflows/spread-large.yaml | 4 +- .github/workflows/tests.yaml | 2 +- .readthedocs.yaml | 2 +- .../convert-to-pebble-layer/rockcraft.yaml | 7 +- .../code/convert-to-pebble-layer/task.yaml | 6 +- docs/how-to/code/create-slice/openssl.yaml | 8 +- docs/how-to/code/create-slice/task.yaml | 4 +- docs/how-to/code/get-started/task.yaml | 6 +- docs/how-to/code/install-slice/task.yaml | 4 +- docs/how-to/code/publish-slice/task.yaml | 6 +- .../code/rockcraft-pack-action/task.yaml | 6 +- docs/reference/code/example/task.yaml | 1 - docs/tutorials/code/chisel/task.yaml | 8 +- docs/tutorials/code/hello-world/task.yaml | 6 +- .../migrate-to-chiselled-rock/rockcraft.yaml | 2 +- .../code/migrate-to-chiselled-rock/task.yaml | 10 +-- docs/tutorials/code/node-app/rockcraft.yaml | 24 +++--- docs/tutorials/code/node-app/task.yaml | 1 - docs/tutorials/code/pyfiglet/task.yaml | 6 +- snap/snapcraft.yaml | 86 +++++++++---------- spread.yaml | 19 ++-- tests/spread/general/bare-base/task.yaml | 2 +- tests/spread/general/big/rockcraft.yaml | 7 +- tests/spread/general/big/task.yaml | 4 +- tests/spread/general/chisel/rockcraft.yaml | 2 +- tests/spread/general/clean/rockcraft.yaml | 1 - tests/spread/general/craftctl/rockcraft.yaml | 2 +- tests/spread/general/craftctl/task.yaml | 1 - .../spread/general/destructive/rockcraft.yaml | 2 +- tests/spread/general/destructive/task.yaml | 8 +- .../general/entrypoint-service/task.yaml | 2 +- .../spread/general/environment/rockcraft.yaml | 2 +- tests/spread/general/environment/task.yaml | 2 +- .../general/health-checks/rockcraft.yaml | 4 +- tests/spread/general/invalid-name/task.yaml | 2 +- tests/spread/general/plugin-go/task.yaml | 1 - .../plugin-python-3.6/rockcraft.orig.yaml | 6 +- .../plugin-python/base-2204/rockcraft.yaml | 2 +- tests/spread/general/plugin-python/parts.yaml | 1 - tests/spread/general/plugin-python/task.yaml | 16 ++-- tests/spread/general/prune/task.yaml | 2 +- tests/spread/general/repo-bare-base/task.yaml | 2 +- .../spread/general/repo-ubuntu-base/task.yaml | 4 +- tests/spread/general/run-user/rockcraft.yaml | 1 - tests/spread/general/run-user/task.yaml | 2 +- tests/spread/general/usrmerge/rockcraft.yaml | 4 +- tests/spread/general/usrmerge/task.yaml | 4 +- 49 files changed, 148 insertions(+), 159 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/bug.yaml b/.github/ISSUE_TEMPLATE/bug.yaml index 6d2ee0afc..a6906bd0f 100644 --- a/.github/ISSUE_TEMPLATE/bug.yaml +++ b/.github/ISSUE_TEMPLATE/bug.yaml @@ -58,4 +58,3 @@ body: render: shell validations: required: true - diff --git a/.github/workflows/snap.yaml b/.github/workflows/snap.yaml index dcdeb50bf..27ffd426f 100644 --- a/.github/workflows/snap.yaml +++ b/.github/workflows/snap.yaml @@ -32,7 +32,7 @@ jobs: run: | if [[ "${{ github.event_name }}" == "pull_request" ]] then - echo "branch=pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT" + echo "branch=pr-${{ github.event.number }}" >> "$GITHUB_OUTPUT" else branch=$(echo ${GITHUB_REF#refs/*/} | sed -e 's|feature/\(.*\)|\1|') echo "branch=$branch" >> "$GITHUB_OUTPUT" diff --git a/.github/workflows/spread-large.yaml b/.github/workflows/spread-large.yaml index 894ec0c6c..e85cb41f6 100644 --- a/.github/workflows/spread-large.yaml +++ b/.github/workflows/spread-large.yaml @@ -1,9 +1,9 @@ name: Spread (large) on: pull_request: - types: [ labeled ] + types: [labeled] schedule: - - cron: "0 2 * * 0,3" # run at 2 AM on Sundays and Wednesdays + - cron: "0 2 * * 0,3" # run at 2 AM on Sundays and Wednesdays jobs: snap-build: diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 23dae82e9..41b6304f4 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -140,4 +140,4 @@ jobs: uses: actions/upload-artifact@v3 with: name: test-results-${{ matrix.platform }} - path: results/ \ No newline at end of file + path: results/ diff --git a/.readthedocs.yaml b/.readthedocs.yaml index 463459e80..fb5811702 100644 --- a/.readthedocs.yaml +++ b/.readthedocs.yaml @@ -24,4 +24,4 @@ python: - method: pip path: . extra_requirements: - - docs \ No newline at end of file + - docs diff --git a/docs/how-to/code/convert-to-pebble-layer/rockcraft.yaml b/docs/how-to/code/convert-to-pebble-layer/rockcraft.yaml index 6aa6b300a..430eed349 100644 --- a/docs/how-to/code/convert-to-pebble-layer/rockcraft.yaml +++ b/docs/how-to/code/convert-to-pebble-layer/rockcraft.yaml @@ -3,16 +3,16 @@ base: "ubuntu@22.04" version: latest summary: An NGINX ROCK description: | - A ROCK equivalent of the official NGINX Docker image from Docker Hub. + A ROCK equivalent of the official NGINX Docker image from Docker Hub. license: Apache-2.0 platforms: - amd64: + amd64: package-repositories: - type: apt url: https://nginx.org/packages/mainline/ubuntu key-id: 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 - suites: + suites: - jammy components: - nginx @@ -49,4 +49,3 @@ services: environment: TZ: UTC on-failure: shutdown - \ No newline at end of file diff --git a/docs/how-to/code/convert-to-pebble-layer/task.yaml b/docs/how-to/code/convert-to-pebble-layer/task.yaml index ee2ba0a1f..c5bf4aac4 100644 --- a/docs/how-to/code/convert-to-pebble-layer/task.yaml +++ b/docs/how-to/code/convert-to-pebble-layer/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the "How to convert a popular entrypoint to a Pebble layer" guide @@ -27,4 +27,4 @@ execute: | # [docs:curl-end] docker rm -f nginx-pebble-service - docker rmi -f custom-nginx-rock:latest \ No newline at end of file + docker rmi -f custom-nginx-rock:latest diff --git a/docs/how-to/code/create-slice/openssl.yaml b/docs/how-to/code/create-slice/openssl.yaml index 3de2ff4b0..9ed4f9da0 100644 --- a/docs/how-to/code/create-slice/openssl.yaml +++ b/docs/how-to/code/create-slice/openssl.yaml @@ -2,10 +2,10 @@ package: openssl slices: bins: essential: - - libc6_libs - - libc6_config - - libssl3_libs - - openssl_config + - libc6_libs + - libc6_config + - libssl3_libs + - openssl_config contents: /usr/bin/c_rehash: /usr/bin/openssl: diff --git a/docs/how-to/code/create-slice/task.yaml b/docs/how-to/code/create-slice/task.yaml index f87127ee8..8571c4683 100644 --- a/docs/how-to/code/create-slice/task.yaml +++ b/docs/how-to/code/create-slice/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the "Create a package slice for Chisel" guide diff --git a/docs/how-to/code/get-started/task.yaml b/docs/how-to/code/get-started/task.yaml index 6d2963856..98cd298a1 100644 --- a/docs/how-to/code/get-started/task.yaml +++ b/docs/how-to/code/get-started/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the steps for getting started with Rockcraft @@ -13,7 +13,7 @@ execute: | # [docs:snap-version-end] # [docs:lxd-version] - lxd --version + lxd --version # [docs:lxd-version-end] # [docs:lxd-status] diff --git a/docs/how-to/code/install-slice/task.yaml b/docs/how-to/code/install-slice/task.yaml index e729d9cb4..c5fbd2a8d 100644 --- a/docs/how-to/code/install-slice/task.yaml +++ b/docs/how-to/code/install-slice/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the "Install a custom package slice" guide diff --git a/docs/how-to/code/publish-slice/task.yaml b/docs/how-to/code/publish-slice/task.yaml index e1da7f4dc..69b6fdcdf 100644 --- a/docs/how-to/code/publish-slice/task.yaml +++ b/docs/how-to/code/publish-slice/task.yaml @@ -1,15 +1,15 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the "Release a slice definitions file" guide execute: | git clone -b ubuntu-22.04 https://github.com/canonical/chisel-releases.git - + # [docs:new-branch] cd chisel-releases git checkout -b create-openssl-bins-slice diff --git a/docs/how-to/code/rockcraft-pack-action/task.yaml b/docs/how-to/code/rockcraft-pack-action/task.yaml index 3c72b3f46..9b5658fe1 100644 --- a/docs/how-to/code/rockcraft-pack-action/task.yaml +++ b/docs/how-to/code/rockcraft-pack-action/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test the "Use the GitHub Action" guide @@ -11,4 +11,4 @@ prepare: | tests.pkgs install yamllint execute: | - yamllint rockcraft-pack.yaml \ No newline at end of file + yamllint rockcraft-pack.yaml diff --git a/docs/reference/code/example/task.yaml b/docs/reference/code/example/task.yaml index d3af7b4c1..313b96e29 100644 --- a/docs/reference/code/example/task.yaml +++ b/docs/reference/code/example/task.yaml @@ -1,4 +1,3 @@ - summary: Check that we can build the example rockcraft.yaml execute: | diff --git a/docs/tutorials/code/chisel/task.yaml b/docs/tutorials/code/chisel/task.yaml index 0371ddf79..23bd8374f 100644 --- a/docs/tutorials/code/chisel/task.yaml +++ b/docs/tutorials/code/chisel/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: test for the "Install packages slices into a ROCK" tutorial @@ -15,7 +15,7 @@ execute: | # [docs:build-rock] rockcraft pack # [docs:build-rock-end] - + test -f chisel-openssl_0.0.1_amd64.rock # [docs:skopeo-copy] @@ -29,5 +29,5 @@ execute: | # [docs:docker-run-with-args] docker run --rm chisel-openssl exec --env=SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt openssl s_client -connect ubuntu.com:443 -brief # [docs:docker-run-with-args-end] - + docker run --rm chisel-openssl exec --env=SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt openssl s_client -connect ubuntu.com:443 -brief 2>&1 | grep "Verification: OK" diff --git a/docs/tutorials/code/hello-world/task.yaml b/docs/tutorials/code/hello-world/task.yaml index c7db9fa92..915801911 100644 --- a/docs/tutorials/code/hello-world/task.yaml +++ b/docs/tutorials/code/hello-world/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: hello world tutorial @@ -36,5 +36,5 @@ execute: | # [docs:docker-run] docker run --rm hello:1.0 exec hello -t # [docs:docker-run-end] - + docker run --rm hello:1.0 exec hello -t | grep "hello, world" diff --git a/docs/tutorials/code/migrate-to-chiselled-rock/rockcraft.yaml b/docs/tutorials/code/migrate-to-chiselled-rock/rockcraft.yaml index 8ce8c384f..f0233a13c 100644 --- a/docs/tutorials/code/migrate-to-chiselled-rock/rockcraft.yaml +++ b/docs/tutorials/code/migrate-to-chiselled-rock/rockcraft.yaml @@ -25,7 +25,7 @@ parts: stage-packages: - base-files_base - dotnet-runtime-6.0_libs - + # Based on requirement R4, create the symbolic link override-prime: | craftctl default diff --git a/docs/tutorials/code/migrate-to-chiselled-rock/task.yaml b/docs/tutorials/code/migrate-to-chiselled-rock/task.yaml index 3b28d9791..a734b0d70 100644 --- a/docs/tutorials/code/migrate-to-chiselled-rock/task.yaml +++ b/docs/tutorials/code/migrate-to-chiselled-rock/task.yaml @@ -1,8 +1,8 @@ ########################################### # IMPORTANT # Comments matter! -# The docs use the wrapping comments as -# markers for including said instructions +# The docs use the wrapping comments as +# markers for including said instructions # as snippets in the docs. ########################################### summary: docker image migration tutorial @@ -21,7 +21,7 @@ execute: | # [docs:build-docker-image] docker build -t dotnet-runtime:reference . # [docs:build-docker-image-end] - + popd # [docs:inspect-docker-image] @@ -48,7 +48,7 @@ execute: | # [docs:skopeo-copy] sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:dotnet-runtime_chiselled_amd64.rock docker-daemon:dotnet-runtime:chiselled # [docs:skopeo-copy-end] - + # [docs:inspect-rock] docker images dotnet-runtime:chiselled # [docs:inspect-rock-end] @@ -56,7 +56,7 @@ execute: | # [docs:run-rock] docker run --rm dotnet-runtime:chiselled exec dotnet --info # [docs:run-rock-end] - + restore: | rm dotnet-runtime_chiselled_amd64.rock docker rmi -f dotnet-runtime:chiselled dotnet-runtime:reference diff --git a/docs/tutorials/code/node-app/rockcraft.yaml b/docs/tutorials/code/node-app/rockcraft.yaml index e2bbe66ef..5200f3491 100644 --- a/docs/tutorials/code/node-app/rockcraft.yaml +++ b/docs/tutorials/code/node-app/rockcraft.yaml @@ -9,17 +9,17 @@ platforms: amd64: services: - app: - override: replace - command: node server.js - startup: enabled - on-success: shutdown - on-failure: shutdown - working-dir: /lib/node_modules/node_web_app + app: + override: replace + command: node server.js + startup: enabled + on-success: shutdown + on-failure: shutdown + working-dir: /lib/node_modules/node_web_app parts: - app: - plugin: npm - npm-include-node: True - npm-node-version: "21.1.0" - source: src/ + app: + plugin: npm + npm-include-node: true + npm-node-version: "21.1.0" + source: src/ diff --git a/docs/tutorials/code/node-app/task.yaml b/docs/tutorials/code/node-app/task.yaml index 2d225940d..60caff41b 100644 --- a/docs/tutorials/code/node-app/task.yaml +++ b/docs/tutorials/code/node-app/task.yaml @@ -1,4 +1,3 @@ - summary: nodejs tutorial execute: | diff --git a/docs/tutorials/code/pyfiglet/task.yaml b/docs/tutorials/code/pyfiglet/task.yaml index 4d592bc43..62eae7cc7 100644 --- a/docs/tutorials/code/pyfiglet/task.yaml +++ b/docs/tutorials/code/pyfiglet/task.yaml @@ -11,12 +11,12 @@ execute: | # [docs:create-pyfiglet-dir] mkdir pyfiglet-rock && cd pyfiglet-rock # [docs:create-pyfiglet-dir-end] - + cp ../rockcraft.yaml . - + # [docs:build-rock] rockcraft pack - # [docs:build-rock-end] + # [docs:build-rock-end] # [docs:skopeo-copy] sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:pyfiglet_0.7.6_amd64.rock docker-daemon:pyfiglet:0.7.6 diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 95280ae08..3460df796 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -27,42 +27,42 @@ parts: rockcraft-libs: plugin: nil build-attributes: - - enable-patchelf + - enable-patchelf stage-packages: - - apt - - apt-transport-https - - apt-utils - - binutils - - gpg - - gpgv - - libpython3-stdlib - - libpython3.10-stdlib - - libpython3.10-minimal - - python3-pip - - python3-setuptools - - python3-wheel - - python3-venv - - python3-minimal - - python3-distutils - - python3-pkg-resources - - python3.10-minimal - - fuse-overlayfs + - apt + - apt-transport-https + - apt-utils + - binutils + - gpg + - gpgv + - libpython3-stdlib + - libpython3.10-stdlib + - libpython3.10-minimal + - python3-pip + - python3-setuptools + - python3-wheel + - python3-venv + - python3-minimal + - python3-distutils + - python3-pkg-resources + - python3.10-minimal + - fuse-overlayfs organize: - "usr/bin/fuse-overlayfs": "libexec/rockcraft/fuse-overlayfs" + "usr/bin/fuse-overlayfs": "libexec/rockcraft/fuse-overlayfs" rockcraft: source: . plugin: python python-packages: - - wheel - - pip - - setuptools + - wheel + - pip + - setuptools build-attributes: - - enable-patchelf + - enable-patchelf build-environment: - - "CFLAGS": "$(pkg-config python-3.10 yaml-0.1 --cflags)" + - "CFLAGS": "$(pkg-config python-3.10 yaml-0.1 --cflags)" organize: - bin/craftctl: libexec/rockcraft/craftctl + bin/craftctl: libexec/rockcraft/craftctl override-build: | ${SNAP}/libexec/snapcraft/craftctl default @@ -91,14 +91,14 @@ parts: source: https://github.com/opencontainers/umoci.git source-tag: v0.4.7 make-parameters: - - umoci.static + - umoci.static override-build: | - make umoci.static - mkdir "$CRAFT_PART_INSTALL"/bin - install -m755 umoci.static "$CRAFT_PART_INSTALL"/bin/umoci + make umoci.static + mkdir "$CRAFT_PART_INSTALL"/bin + install -m755 umoci.static "$CRAFT_PART_INSTALL"/bin/umoci build-packages: - - golang-go - - make + - golang-go + - make skopeo: plugin: nil @@ -109,20 +109,20 @@ parts: mkdir "$CRAFT_PART_INSTALL"/bin install -m755 skopeo "$CRAFT_PART_INSTALL"/bin/skopeo stage-packages: - - libgpgme11 - - libassuan0 - - libbtrfs0 - - libdevmapper1.02.1 + - libgpgme11 + - libassuan0 + - libbtrfs0 + - libdevmapper1.02.1 build-attributes: - - enable-patchelf + - enable-patchelf build-snaps: - - go/1.17/stable + - go/1.17/stable build-packages: - - libgpgme-dev - - libassuan-dev - - libbtrfs-dev - - libdevmapper-dev - - pkg-config + - libgpgme-dev + - libassuan-dev + - libbtrfs-dev + - libdevmapper-dev + - pkg-config chisel: plugin: go diff --git a/spread.yaml b/spread.yaml index 908f20b66..ad6a12b50 100644 --- a/spread.yaml +++ b/spread.yaml @@ -7,11 +7,11 @@ environment: PATH: /snap/bin:$PATH:$SNAPD_TESTING_TOOLS:$PROJECT_PATH/tools/spread include: - - tests/ - - tools/ - - docs/ - - pyproject.toml - - rockcraft/ + - tests/ + - tools/ + - docs/ + - pyproject.toml + - rockcraft/ backends: google: @@ -79,11 +79,11 @@ prepare: | snap install docker --channel=core18/stable else snap install docker - fi + fi # make sure docker is working retry -n 10 --wait 2 sh -c 'docker run --rm hello-world' - + install_rockcraft restore-each: | @@ -96,7 +96,7 @@ restore-each: | lxc --project=rockcraft delete --force "$instance" fi done - fi + fi debug-each: | # output latest rockcraft log file on test failure @@ -116,7 +116,7 @@ suites: docs/how-to/code/: summary: tests how-to guides from the docs systems: - - ubuntu-22.04-64 + - ubuntu-22.04-64 docs/reference/code/: summary: tests reference code from the docs @@ -127,4 +127,3 @@ suites: tests/spread/large/: summary: bigger tests that take longer to run manual: true - diff --git a/tests/spread/general/bare-base/task.yaml b/tests/spread/general/bare-base/task.yaml index 84b912866..69bd4ac67 100644 --- a/tests/spread/general/bare-base/task.yaml +++ b/tests/spread/general/bare-base/task.yaml @@ -15,5 +15,5 @@ execute: | grep_docker_log "$id" "ship it!" docker exec "$id" pebble services | grep hello docker exec "$id" pebble ls /usr/bin/hello - + docker rm -f "$id" diff --git a/tests/spread/general/big/rockcraft.yaml b/tests/spread/general/big/rockcraft.yaml index d0cd87a28..6f3cacbf5 100644 --- a/tests/spread/general/big/rockcraft.yaml +++ b/tests/spread/general/big/rockcraft.yaml @@ -3,10 +3,10 @@ version: latest summary: A big ROCK to test many features description: | A big ROCK whose purpose is to test many features while only paying the "setup" - and "teardown" price once. Feel free to add to this file and to "task.yaml", + and "teardown" price once. Feel free to add to this file and to "task.yaml", adding references to issues/PRs where appropriate. license: Apache-2.0 -base: ubuntu:22.04 # Leaving ":"-notation on purpose here +base: ubuntu:22.04 # Leaving ":"-notation on purpose here platforms: amd64: @@ -18,7 +18,7 @@ services: on-success: shutdown on-failure: shutdown working-dir: /tmp - + parts: issue-44-dir-owner: plugin: dump @@ -52,4 +52,3 @@ parts: mkdir fake_rock_dir touch fake_rock_dir/fake_rock_file ln -s fake_rock_dir .rock - diff --git a/tests/spread/general/big/task.yaml b/tests/spread/general/big/task.yaml index a52273150..ff06de04a 100644 --- a/tests/spread/general/big/task.yaml +++ b/tests/spread/general/big/task.yaml @@ -28,7 +28,7 @@ execute: | # Check the ROCK's output docker run --rm big:latest | MATCH "/tmp" - + ############################################################################################ # test ownership: "newfiles" and "a.txt" are owned by uid 9999, "b.txt" is owned by uid 3333 # (github issue #44) @@ -54,7 +54,7 @@ execute: | file pebble | grep "statically linked" docker rm -f big-container rm pebble - + ############################################################################################ # This check documents the fact that we currently don't preserve/observe symlinks between # layers - we only take the base on which the ROCK was built into account. If the behavior diff --git a/tests/spread/general/chisel/rockcraft.yaml b/tests/spread/general/chisel/rockcraft.yaml index 0dd12ace8..d9c7a6787 100644 --- a/tests/spread/general/chisel/rockcraft.yaml +++ b/tests/spread/general/chisel/rockcraft.yaml @@ -7,7 +7,7 @@ version: "0.0.1" base: bare build_base: ubuntu@22.04 run-user: _daemon_ -services: +services: dotnet: override: replace command: /usr/lib/dotnet/dotnet [ --info ] diff --git a/tests/spread/general/clean/rockcraft.yaml b/tests/spread/general/clean/rockcraft.yaml index 061142057..aa6f5c111 100644 --- a/tests/spread/general/clean/rockcraft.yaml +++ b/tests/spread/general/clean/rockcraft.yaml @@ -10,4 +10,3 @@ platforms: parts: my-part: plugin: nil - diff --git a/tests/spread/general/craftctl/rockcraft.yaml b/tests/spread/general/craftctl/rockcraft.yaml index 4a76d2349..db731c916 100644 --- a/tests/spread/general/craftctl/rockcraft.yaml +++ b/tests/spread/general/craftctl/rockcraft.yaml @@ -8,7 +8,7 @@ platforms: craftctl: build-on: ["amd64", "i386"] build-for: amd64 - + parts: hello: plugin: make diff --git a/tests/spread/general/craftctl/task.yaml b/tests/spread/general/craftctl/task.yaml index 82fe1331f..2141a6a52 100644 --- a/tests/spread/general/craftctl/task.yaml +++ b/tests/spread/general/craftctl/task.yaml @@ -17,4 +17,3 @@ execute: | docker run --rm --entrypoint /usr/bin/hello craftctl-test:latest docker run --rm craftctl-test:latest help docker run --rm craftctl-test:latest --help - \ No newline at end of file diff --git a/tests/spread/general/destructive/rockcraft.yaml b/tests/spread/general/destructive/rockcraft.yaml index a76f992d8..a76820a77 100644 --- a/tests/spread/general/destructive/rockcraft.yaml +++ b/tests/spread/general/destructive/rockcraft.yaml @@ -3,7 +3,7 @@ version: latest summary: A destructively-built ROCK description: Building a ROCK in destructive mode license: Apache-2.0 -build-base: ubuntu:22.04 # Leaving ":"-notation on purpose here +build-base: ubuntu:22.04 # Leaving ":"-notation on purpose here base: bare platforms: amd64: diff --git a/tests/spread/general/destructive/task.yaml b/tests/spread/general/destructive/task.yaml index 7e51f472c..52b4aaff7 100644 --- a/tests/spread/general/destructive/task.yaml +++ b/tests/spread/general/destructive/task.yaml @@ -3,16 +3,16 @@ summary: destructive-mode test execute: | # Check that work dirs *don't* exist test ! -d parts -a ! -d stage -a ! -d prime - + run_rockcraft pack --destructive-mode test -f destructive-mode*.rock - + # Check that work dirs *do* exist test -d parts -a -d stage -a -d prime - + run_rockcraft clean --destructive-mode - + # Check that work dirs *don't* exist again test ! -d parts -a ! -d stage -a ! -d prime diff --git a/tests/spread/general/entrypoint-service/task.yaml b/tests/spread/general/entrypoint-service/task.yaml index 8af1dba18..31882dd54 100644 --- a/tests/spread/general/entrypoint-service/task.yaml +++ b/tests/spread/general/entrypoint-service/task.yaml @@ -7,7 +7,7 @@ execute: | # test container execution sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:entrypoint-service-test_latest_amd64.rock docker-daemon:entrypoint-service-test:latest - rm entrypoint-service-test_latest*.rock + rm entrypoint-service-test_latest*.rock docker images entrypoint-service-test:latest id=$(docker run -d entrypoint-service-test) test "$(docker inspect "$id" -f '{{json .Config.Entrypoint}}')" = '["/bin/pebble","enter","--verbose","--args","test-service"]' diff --git a/tests/spread/general/environment/rockcraft.yaml b/tests/spread/general/environment/rockcraft.yaml index 64da79e9d..cd0b10294 100644 --- a/tests/spread/general/environment/rockcraft.yaml +++ b/tests/spread/general/environment/rockcraft.yaml @@ -21,7 +21,7 @@ platforms: amd64v2: build-on: ["amd64", "i386"] build-for: amd64 - + parts: part1: plugin: nil diff --git a/tests/spread/general/environment/task.yaml b/tests/spread/general/environment/task.yaml index 31a3b23fd..83d41e899 100644 --- a/tests/spread/general/environment/task.yaml +++ b/tests/spread/general/environment/task.yaml @@ -10,7 +10,7 @@ execute: | # test container execution docker images sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:environment-test_latest_amd64v2.rock docker-daemon:environment-test:latest - rm environment-test_latest*.rock + rm environment-test_latest*.rock docker images environment-test:latest id=$(docker run --rm -d environment-test -v) grep_docker_log "$id" "X=ship it!" diff --git a/tests/spread/general/health-checks/rockcraft.yaml b/tests/spread/general/health-checks/rockcraft.yaml index 562815209..3cf38a02a 100644 --- a/tests/spread/general/health-checks/rockcraft.yaml +++ b/tests/spread/general/health-checks/rockcraft.yaml @@ -6,7 +6,7 @@ license: Apache-2.0 version: latest base: ubuntu@22.04 -services: +services: webserver: override: replace command: timeout 30 nginx -g 'daemon off;' @@ -39,7 +39,7 @@ package-repositories: - type: apt url: https://nginx.org/packages/mainline/ubuntu key-id: 573BFD6B3D8FBC641079A6ABABF5BD827BD9BF62 - suites: + suites: - jammy components: - nginx diff --git a/tests/spread/general/invalid-name/task.yaml b/tests/spread/general/invalid-name/task.yaml index c44d99b56..b5ced0587 100644 --- a/tests/spread/general/invalid-name/task.yaml +++ b/tests/spread/general/invalid-name/task.yaml @@ -5,4 +5,4 @@ execute: | do sed "s/placeholder-name/$name/" rockcraft.orig.yaml > rockcraft.yaml rockcraft pack 2>&1 >/dev/null | MATCH "Invalid name for ROCK" - done \ No newline at end of file + done diff --git a/tests/spread/general/plugin-go/task.yaml b/tests/spread/general/plugin-go/task.yaml index c3e88fb74..e29eab3a2 100644 --- a/tests/spread/general/plugin-go/task.yaml +++ b/tests/spread/general/plugin-go/task.yaml @@ -2,4 +2,3 @@ summary: check that the build-snap used by the go plugin does not interfere with execute: | run_rockcraft prime - diff --git a/tests/spread/general/plugin-python-3.6/rockcraft.orig.yaml b/tests/spread/general/plugin-python-3.6/rockcraft.orig.yaml index df4c47edb..6b99d9375 100644 --- a/tests/spread/general/plugin-python-3.6/rockcraft.orig.yaml +++ b/tests/spread/general/plugin-python-3.6/rockcraft.orig.yaml @@ -9,9 +9,9 @@ platforms: amd64: package-repositories: - - type: apt - ppa: deadsnakes/ppa - priority: always + - type: apt + ppa: deadsnakes/ppa + priority: always parts: my-part: diff --git a/tests/spread/general/plugin-python/base-2204/rockcraft.yaml b/tests/spread/general/plugin-python/base-2204/rockcraft.yaml index f02ddd2c7..6a74d6a53 100644 --- a/tests/spread/general/plugin-python/base-2204/rockcraft.yaml +++ b/tests/spread/general/plugin-python/base-2204/rockcraft.yaml @@ -2,4 +2,4 @@ name: base-2204 base: ubuntu@22.04 # Remaining contents will come from "parts.yaml" -#'/usr/lib/python3/dist-packages', '/lib/python3.10/site-packages' \ No newline at end of file +# '/usr/lib/python3/dist-packages', '/lib/python3.10/site-packages' diff --git a/tests/spread/general/plugin-python/parts.yaml b/tests/spread/general/plugin-python/parts.yaml index 77684e9cf..5ebd0ee0c 100644 --- a/tests/spread/general/plugin-python/parts.yaml +++ b/tests/spread/general/plugin-python/parts.yaml @@ -1,4 +1,3 @@ - # Actual parts definition for the rockcraft + python cases that we are testing. # (this gets appended to all rockcraft.yaml files in the subdirectories in the # main task.yaml). diff --git a/tests/spread/general/plugin-python/task.yaml b/tests/spread/general/plugin-python/task.yaml index 8866dfd30..d1511c97f 100644 --- a/tests/spread/general/plugin-python/task.yaml +++ b/tests/spread/general/plugin-python/task.yaml @@ -8,40 +8,40 @@ execute: | SCENARIO_DIR="${SCENARIO}" ROCK_FILE="${SCENARIO}_0.1_amd64.rock" IMAGE="${SCENARIO}:0.1" - + # change into the scenario's directory cd ${SCENARIO_DIR} - + # add the parts definition, common to all scenarios cat ../parts.yaml >> rockcraft.yaml - + # copy the Python source of the project we're building (also shared) cp -r ../src . - + # Build the ROCK & load it into docker run_rockcraft pack test -f ${ROCK_FILE} sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:${ROCK_FILE} docker-daemon:${IMAGE} docker images rm ${ROCK_FILE} - + # Run the packaged project, both via the console script and via "python -m" docker run --rm $IMAGE exec hello | MATCH "hello world" docker run --rm $IMAGE exec /bin/python3 -m hello | MATCH "hello world" docker run --rm $IMAGE exec /usr/bin/python3 -m hello | MATCH "hello world" docker run --rm $IMAGE exec python3 -m hello | MATCH "hello world" - + # Run the extra Python package, installed as a python-package, to make sure it's found docker run --rm $IMAGE exec black --version docker run --rm $IMAGE exec /bin/python3 -m black --version docker run --rm $IMAGE exec /usr/bin/python3 -m black --version docker run --rm $IMAGE exec python3 -m black --version - + # Run the extra Python dist-package, installed as a stage-package, to make sure it's found docker run --rm $IMAGE exec /bin/python3 -m cpuinfo --help docker run --rm $IMAGE exec /usr/bin/python3 -m cpuinfo --help docker run --rm $IMAGE exec python3 -m cpuinfo --help - + # Run "check-pythonpath.py" to make sure the ordering of the packages dirs is correct docker run --rm $IMAGE exec /bin/python3 /check-pythonpath.py docker run --rm $IMAGE exec /usr/bin/python3 /check-pythonpath.py diff --git a/tests/spread/general/prune/task.yaml b/tests/spread/general/prune/task.yaml index 4a66f0b2d..e5f0fc163 100644 --- a/tests/spread/general/prune/task.yaml +++ b/tests/spread/general/prune/task.yaml @@ -4,7 +4,7 @@ execute: | run_rockcraft pack test -f ./*.rock - + # Unpack the ROCK and verify that the lifecycle-based layer has no files in # common with the base Ubuntu layer. tar -xf ./*.rock diff --git a/tests/spread/general/repo-bare-base/task.yaml b/tests/spread/general/repo-bare-base/task.yaml index 794a5bf1d..0162d67e7 100644 --- a/tests/spread/general/repo-bare-base/task.yaml +++ b/tests/spread/general/repo-bare-base/task.yaml @@ -12,7 +12,7 @@ execute: | sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:apt-repo-static-test_latest_amd64.rock docker-daemon:apt-repo-static-test:latest # Ensure container exists docker images apt-repo-static-test | MATCH "apt-repo-static-test" - + docker run --rm apt-repo-static-test exec /bin/bash-static /usr/bin/test-ppa | MATCH "hello!" restore: | diff --git a/tests/spread/general/repo-ubuntu-base/task.yaml b/tests/spread/general/repo-ubuntu-base/task.yaml index 2dc789e63..be4c10f32 100644 --- a/tests/spread/general/repo-ubuntu-base/task.yaml +++ b/tests/spread/general/repo-ubuntu-base/task.yaml @@ -12,9 +12,9 @@ execute: | sudo /snap/rockcraft/current/bin/skopeo --insecure-policy copy oci-archive:apt-repo-test_latest_amd64.rock docker-daemon:apt-repo-test:latest # Ensure container exists docker images apt-repo-test | MATCH "apt-repo-test" - + docker run --rm apt-repo-test exec /usr/bin/python3.12 -c "import sys;print(sys.version)" | MATCH "3.12" - + restore: | rm -f apt-repo-test_latest_amd64.rock docker rmi -f apt-repo-test diff --git a/tests/spread/general/run-user/rockcraft.yaml b/tests/spread/general/run-user/rockcraft.yaml index 1c03db75b..5406ec807 100644 --- a/tests/spread/general/run-user/rockcraft.yaml +++ b/tests/spread/general/run-user/rockcraft.yaml @@ -21,4 +21,3 @@ parts: overlay-script: | set -x useradd -R $CRAFT_OVERLAY -M -U -r test-user - \ No newline at end of file diff --git a/tests/spread/general/run-user/task.yaml b/tests/spread/general/run-user/task.yaml index 5b5571653..b5e8de0c8 100644 --- a/tests/spread/general/run-user/task.yaml +++ b/tests/spread/general/run-user/task.yaml @@ -13,7 +13,7 @@ execute: | # Ensure container exists docker images run-user-test | MATCH "run-user-test" docker inspect run-user-test --format '{{.Config.User}}' | MATCH "_daemon_" - + # ensure container doesn't exist docker rm -f run-user-test-container docker run --rm --entrypoint /bin/sh run-user-test -c 'whoami' | MATCH "_daemon_" diff --git a/tests/spread/general/usrmerge/rockcraft.yaml b/tests/spread/general/usrmerge/rockcraft.yaml index 0cb855d02..c94ee105e 100644 --- a/tests/spread/general/usrmerge/rockcraft.yaml +++ b/tests/spread/general/usrmerge/rockcraft.yaml @@ -14,11 +14,11 @@ parts: # This build script adds a file in bin/ - the symlink in the base should be preserved. mkdir ${CRAFT_PART_INSTALL}/bin touch ${CRAFT_PART_INSTALL}/bin/new_bin_file - + # Also add subdirectories in bin/ to make sure they are correctly handled. mkdir -p ${CRAFT_PART_INSTALL}/bin/subdir1/subdir2 touch ${CRAFT_PART_INSTALL}/bin/subdir1/subdir2/subdir_bin_file - + # Also add the same subdirectory structure in usr/bin/ to make sure they are not # duplicated in the layer file mkdir -p ${CRAFT_PART_INSTALL}/usr/bin/subdir1/subdir2 diff --git a/tests/spread/general/usrmerge/task.yaml b/tests/spread/general/usrmerge/task.yaml index ab4dde6ec..8829a45be 100644 --- a/tests/spread/general/usrmerge/task.yaml +++ b/tests/spread/general/usrmerge/task.yaml @@ -13,13 +13,13 @@ execute: | rm "$ROCK" docker images usrmerge:latest - + ############################################################################################ # check that the /bin symlink to /usr/bin *was not* broken by the /bin/new_bin_file addition ############################################################################################ docker run --rm usrmerge exec readlink /bin | MATCH usr/bin docker run --rm usrmerge exec ls /bin/bash /bin/new_bin_file /bin/subdir1/subdir2/subdir_bin_file - + ############################################################################################ # check that the contents of bin/ and usr/bin *both* ended up in /usr/bin ############################################################################################ From 49990bceed3a291a01a1c19428b0cacc213d092a Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 12:08:41 -0500 Subject: [PATCH 20/31] chore(ci): sync pre commit config with pyproject --- .pre-commit-config.yaml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index e20b479df..a3302107c 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -20,11 +20,11 @@ repos: args: [--fix, --exit-non-zero-on-fix] - repo: https://github.com/psf/black # renovate: datasource=pypi;depName=black - rev: "23.3.0" + rev: "23.11.0" hooks: - id: black - repo: https://github.com/adrienverge/yamllint.git # renovate: datasource=pypi;depName=yamllint - rev: "v1.31.0" + rev: "v1.33.0" hooks: - id: yamllint From 33e8ca389509d198679cb10e7a6aa4c51e6288cb Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 12:26:19 -0500 Subject: [PATCH 21/31] chore(linter): move run_user typing to the top --- rockcraft/models/project.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 7f0be8274..03b907784 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -46,6 +46,12 @@ if TYPE_CHECKING: # pragma: no cover from pydantic.error_wrappers import ErrorDict +# pyright workaround +if TYPE_CHECKING: + _RunUser = str | None +else: + _RunUser = Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)] | None + class Platform(pydantic.BaseModel): """Rockcraft project platform definition.""" @@ -134,10 +140,7 @@ class Project(YamlModelMixin, BaseProject): base: Literal["bare", "ubuntu@20.04", "ubuntu@22.04"] build_base: Literal["ubuntu@20.04", "ubuntu@22.04"] | None environment: dict[str, str] | None - if TYPE_CHECKING: - run_user: str | None - else: - run_user: Literal[tuple(SUPPORTED_GLOBAL_USERNAMES)] | None # type: ignore + run_user: _RunUser services: dict[str, Service] | None checks: dict[str, Check] | None entrypoint_service: str | None From af5594dfc3f4a5ca979e3e64c30e9060c65ad180 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 12:47:58 -0500 Subject: [PATCH 22/31] chore(linter): fix _create_app return type --- rockcraft/cli.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/rockcraft/cli.py b/rockcraft/cli.py index 816a59ed3..f3cbb1f73 100644 --- a/rockcraft/cli.py +++ b/rockcraft/cli.py @@ -17,12 +17,16 @@ """Command-line application entry point.""" import logging +from typing import TYPE_CHECKING from rockcraft import plugins from . import commands from .services import RockcraftServiceFactory +if TYPE_CHECKING: + from .application import Rockcraft + def run() -> int: """Command-line interface entrypoint.""" @@ -39,7 +43,7 @@ def run() -> int: return app.run() # type: ignore[no-any-return] -def _create_app(): # type: ignore[no-untyped-def] # noqa: ANN202 +def _create_app() -> "Rockcraft": # pylint: disable=import-outside-toplevel # Import these here so that the script that generates the docs for the # commands doesn't need to know *too much* of the application. From 5ad65540ceefea7cd86cbf5ed33ed0d1ac8101ac Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 13:06:41 -0500 Subject: [PATCH 23/31] chore: update the snapcraft workaround comment --- snap/snapcraft.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index 3460df796..ba19fd023 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -70,7 +70,8 @@ parts: rm -f "${CRAFT_PART_INSTALL}/bin/python3" ln -s "${CRAFT_PART_INSTALL}/../../../stage/usr/bin/python3" "${CRAFT_PART_INSTALL}/bin/python3" - # stable Snapcraft not support pure pyproject.toml, install it manually + # Snapcraft 7 currently not support pure pyproject.toml, and will not install anything. + # Install the package manually "${CRAFT_PART_INSTALL}/bin/pip" install -U "${CRAFT_PART_BUILD}[ubuntu-jammy]" version="$(${CRAFT_PART_INSTALL}/bin/python3 -B -c 'import importlib.metadata; print(importlib.metadata.version("rockcraft"))')" From 83ffa4f9904c74ec41330c59454376f6b0339bc4 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 13:16:01 -0500 Subject: [PATCH 24/31] chore(linter): increase line length for README --- tox.ini | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 4ed15b3ec..a51c30444 100644 --- a/tox.ini +++ b/tox.ini @@ -146,4 +146,6 @@ base = docs labels = lint allowlist_externals = bash, xargs commands_pre = bash -c '{[lint-docs]find} > {env_tmp_dir}/lint_docs_files' -commands = xargs --no-run-if-empty --arg-file {env_tmp_dir}/lint_docs_files sphinx-lint --max-line-length 80 --enable all --ignore "docs/sphinx-starter-pack/" --ignore "README.rst" {posargs} \ No newline at end of file +commands = + xargs --no-run-if-empty --arg-file {env_tmp_dir}/lint_docs_files sphinx-lint --max-line-length 80 --enable all --ignore "docs/sphinx-starter-pack/" --ignore "README.rst" {posargs} + sphinx-lint --max-line-length 100 --enable all "README.rst" {posargs} \ No newline at end of file From aaf57c08bd7f9bb6a0296cec90099f1e39134191 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 16:39:50 -0500 Subject: [PATCH 25/31] fix: use values for get services --- rockcraft/models/project.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/rockcraft/models/project.py b/rockcraft/models/project.py index 03b907784..1d6db58a3 100644 --- a/rockcraft/models/project.py +++ b/rockcraft/models/project.py @@ -351,12 +351,7 @@ def _validate_entrypoint_service( "a valid Pebble service." ) - services = values.get("services") - if services: - command = services[entrypoint_service].command - else: - raise ProjectValidationError("The project does not define any services.") - + command = values["services"][entrypoint_service].command command_sh_args = shlex.split(command) # optional arg is surrounded by brackets, so check that they exist in the # right order From bf6a312404c2fec859685506726b01d431705451 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 17:52:40 -0500 Subject: [PATCH 26/31] test: disable tests that will not work in tox --- pyproject.toml | 3 +++ tests/integration/plugins/test_python_plugin.py | 6 ++++-- tox.ini | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 7a1e226d3..2b73bde5e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -157,6 +157,9 @@ line_length = 88 minversion = "7.0" testpaths = "tests" xfail_strict = true +markers = [ + "notox: tests that will not work in tox env", +] [tool.coverage.run] branch = true diff --git a/tests/integration/plugins/test_python_plugin.py b/tests/integration/plugins/test_python_plugin.py index 9f7e35020..e983eca46 100644 --- a/tests/integration/plugins/test_python_plugin.py +++ b/tests/integration/plugins/test_python_plugin.py @@ -99,6 +99,7 @@ class ExpectedValues: VALUES_FOR_HOST = RELEASE_TO_VALUES["22.04"] +@pytest.mark.notox() @pytest.mark.parametrize("base", tuple(UBUNTU_BASES)) def test_python_plugin_ubuntu(base, tmp_path, run_lifecycle): project = create_python_project(base=base) @@ -112,7 +113,7 @@ def test_python_plugin_ubuntu(base, tmp_path, run_lifecycle): # Check the shebang in the "hello" script # In test env this could be replaced with sh that exec python by the venv - expected_shebang = ("#!/bin/python3", "#!/bin/sh") + expected_shebang = "#!/bin/python3" hello = bin_dir / "hello" hello_text = hello.read_text() assert hello_text.startswith(expected_shebang) @@ -132,6 +133,7 @@ def test_python_plugin_ubuntu(base, tmp_path, run_lifecycle): assert not pyvenv_cfg.is_file() +@pytest.mark.notox() def test_python_plugin_bare(tmp_path, run_lifecycle): project = create_python_project(base="bare") run_lifecycle(project=project, work_dir=tmp_path) @@ -151,7 +153,7 @@ def test_python_plugin_bare(tmp_path, run_lifecycle): # Check the shebang in the "hello" script # In test env this could be replaced with sh that exec python by the venv - expected_shebang = ("#!/bin/python3", "#!/bin/sh") + expected_shebang = "#!/bin/python3" hello = bin_dir / "hello" hello_text = hello.read_text() assert hello_text.startswith(expected_shebang) diff --git a/tox.ini b/tox.ini index a51c30444..038dfaebb 100644 --- a/tox.ini +++ b/tox.ini @@ -56,7 +56,7 @@ labels = change_dir = unit: tests/unit integration: tests/integration -commands = pytest {tty:--color=yes} --cov={tox_root}/rockcraft --cov-config={tox_root}/pyproject.toml --cov-report=xml:{tox_root}/results/coverage-{env_name}.xml --junit-xml={tox_root}/results/test-results-{env_name}.xml {posargs} +commands = pytest {tty:--color=yes} --cov={tox_root}/rockcraft --cov-config={tox_root}/pyproject.toml --cov-report=xml:{tox_root}/results/coverage-{env_name}.xml --junit-xml={tox_root}/results/test-results-{env_name}.xml -m "not notox" {posargs} [lint] # Standard linting configuration package = editable From c8c2259e8bcc76f5fc8b43046ed63639f2dbc4aa Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 20:35:15 -0500 Subject: [PATCH 27/31] test(spread): add an spread test for shebang --- spread.yaml | 6 ++++++ tests/spread/integration/shebang/task.yaml | 13 +++++++++++++ 2 files changed, 19 insertions(+) create mode 100644 tests/spread/integration/shebang/task.yaml diff --git a/spread.yaml b/spread.yaml index ad6a12b50..80d141f52 100644 --- a/spread.yaml +++ b/spread.yaml @@ -12,6 +12,7 @@ include: - docs/ - pyproject.toml - rockcraft/ + - tox.ini backends: google: @@ -127,3 +128,8 @@ suites: tests/spread/large/: summary: bigger tests that take longer to run manual: true + + tests/spread/integration/: + summary: tests for rockcraft integration that not work in tox + systems: + - ubuntu-22.04-64 diff --git a/tests/spread/integration/shebang/task.yaml b/tests/spread/integration/shebang/task.yaml new file mode 100644 index 000000000..636c38de2 --- /dev/null +++ b/tests/spread/integration/shebang/task.yaml @@ -0,0 +1,13 @@ +summary: test shebang handling (not working in tox) + +execute: | + tests.pkgs install tox libapt-pkg-dev + + pushd $PROJECT_PATH/ + + git init + git add . + git commit -m "test" + + tox run --colored yes -m tests --notest + ./.tox/integration-py3.10/bin/pytest -m notox tests/integration/ From d12616a5c1b9e0baa1ec7628bcf35457e7565fa5 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 20:59:25 -0500 Subject: [PATCH 28/31] docs: update the how-to build docs to use tox --- docs/how-to/build-docs.rst | 19 ++++----------- docs/how-to/code/build-docs/task.yaml | 35 +++++++++++++++++++++++++++ 2 files changed, 40 insertions(+), 14 deletions(-) create mode 100644 docs/how-to/code/build-docs/task.yaml diff --git a/docs/how-to/build-docs.rst b/docs/how-to/build-docs.rst index 7b83c57e7..e297524f2 100644 --- a/docs/how-to/build-docs.rst +++ b/docs/how-to/build-docs.rst @@ -3,21 +3,13 @@ How to build the documentation ****************************** -Use the provided ``Makefile`` to install the documentation requirements: - -.. literalinclude:: code/build-docs/task.yaml - :language: bash - :start-after: [docs:install-deps] - :end-before: [docs:install-deps-end] - :dedent: 2 - -Once the requirements are installed, you can use the provided ``Makefile`` to +Use the provided ``tox.ini`` to install the documentation requirements and build the documentation: .. literalinclude:: code/build-docs/task.yaml :language: bash - :start-after: [docs:make-docs] - :end-before: [docs:make-docs-end] + :start-after: [docs:build-docs] + :end-before: [docs:build-docs-end] :dedent: 2 Even better, serve it locally on port 8080. The documentation will be rebuilt @@ -26,8 +18,7 @@ on each file change, and will reload the browser view. .. literalinclude:: code/build-docs/task.yaml :language: bash :start-after: timeout -s SIGINT - :end-before: [docs:make-rundocs-end] + :end-before: [docs:build-autobuild-end] :dedent: 2 -Note that ``make rundocs`` automatically activates the virtual environment, -as long as it already exists. +Note that ``tox`` automatically creates and activates the virtual environment. diff --git a/docs/how-to/code/build-docs/task.yaml b/docs/how-to/code/build-docs/task.yaml new file mode 100644 index 000000000..03b05762d --- /dev/null +++ b/docs/how-to/code/build-docs/task.yaml @@ -0,0 +1,35 @@ +########################################### +# IMPORTANT +# Comments matter! +# The docs use the wrapping comments as +# markers for including said instructions +# as snippets in the docs. +########################################### +summary: test the "Build the docs" guide + +prepare: | + tests.pkgs install tox libapt-pkg-dev + +execute: | + pushd $PROJECT_PATH/ + + # create git for getting version + git init + git add . + git commit -m "test" + + # [docs:build-docs] + tox -e build-docs # the home page can be found at docs/_build/html/index.html + # [docs:build-docs-end] + + set +e + timeout -s SIGINT 60 \ + tox -e autobuild-docs + # [docs:build-autobuild-end] + ret=$? + set -e + + if [ $ret -ne 124 ] + then + exit $ret + fi From 20a0ab9cc90598c8b5ca60cb394309ff5abde10c Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Mon, 4 Dec 2023 21:12:50 -0500 Subject: [PATCH 29/31] chore(ci): fix naming --- .github/workflows/tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 41b6304f4..20edf7719 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -19,7 +19,7 @@ jobs: with: fetch-depth: 0 submodules: true - - name: conventional commits + - name: Conventional commits uses: webiny/action-conventional-commits@v1.1.0 - name: Install packages run: | From a1fd0efd82b831512c3b981914ddebff30c71b04 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 5 Dec 2023 12:25:52 -0500 Subject: [PATCH 30/31] chore(deps): use requirements.txt with pyproject.toml --- pyproject.toml | 42 ++---------------------------------------- requirements.txt | 37 +++++++++++++++++++++++++++++++++++++ 2 files changed, 39 insertions(+), 40 deletions(-) create mode 100644 requirements.txt diff --git a/pyproject.toml b/pyproject.toml index 2b73bde5e..7ee41891d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,7 +1,7 @@ [project] name = "rockcraft" description = "Create ROCKS" -dynamic = ["version", "readme"] +dynamic = ["dependencies", "readme", "version"] license = {file = "LICENSE"} authors = [ {name = "Canonical Ltd.", email = "snapcraft@lists.snapcraft.io"} @@ -17,45 +17,6 @@ classifiers = [ "Programming Language :: Python :: 3.10", ] requires-python = ">=3.10" -dependencies = [ - "certifi==2023.7.22", - "charset-normalizer==3.3.1", - "craft-application==1.1.0", - "craft-archives==1.1.3", - "craft-cli==2.5.0", - "craft-parts==1.26.0", - "craft-providers==1.20.1", - "Deprecated==1.2.14", - "distro==1.8.0", - "httplib2==0.22.0", - "idna==3.4", - "importlib-metadata==6.8.0", - "launchpadlib==1.11.0", - "lazr.restfulclient==0.14.5", - "lazr.uri==1.0.6", - "oauthlib==3.2.2", - "overrides==7.4.0", - "packaging==23.2", - "platformdirs==3.11.0", - "pydantic==1.10.13", - "pydantic-yaml==0.11.2", - "pyparsing==3.1.1", - "pyxdg==0.28", - "PyYAML==6.0.1", - "requests==2.31.0", - "requests-unixsocket==0.3.0", - "six==1.16.0", - "spdx==2.5.1", - "spdx-lookup==0.3.3", - "tabulate==0.9.0", - "types-Deprecated==1.2.9.3", - "types-PyYAML==6.0.12.12", - "typing_extensions==4.8.0", - "urllib3==1.26.18", - "wadllib==1.3.6", - "wrapt==1.15.0", - "zipp==3.17.0", -] [project.scripts] rockcraft = "rockcraft.cli:run" @@ -111,6 +72,7 @@ requires = [ build-backend = "setuptools.build_meta" [tool.setuptools.dynamic] +dependencies = {file = ["requirements.txt"]} readme = {file = "README.rst"} version = {attr = "rockcraft.__version__"} diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 000000000..981b4ffe0 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,37 @@ +certifi==2023.7.22 +charset-normalizer==3.3.1 +craft-application==1.1.0 +craft-archives==1.1.3 +craft-cli==2.5.0 +craft-parts==1.26.0 +craft-providers==1.20.1 +Deprecated==1.2.14 +distro==1.8.0 +httplib2==0.22.0 +idna==3.4 +importlib-metadata==6.8.0 +launchpadlib==1.11.0 +lazr.restfulclient==0.14.5 +lazr.uri==1.0.6 +oauthlib==3.2.2 +overrides==7.4.0 +packaging==23.2 +platformdirs==3.11.0 +pydantic==1.10.13 +pydantic-yaml==0.11.2 +pyparsing==3.1.1 +pyxdg==0.28 +PyYAML==6.0.1 +requests==2.31.0 +requests-unixsocket==0.3.0 +six==1.16.0 +spdx==2.5.1 +spdx-lookup==0.3.3 +tabulate==0.9.0 +types-Deprecated==1.2.9.3 +types-PyYAML==6.0.12.12 +typing_extensions==4.8.0 +urllib3==1.26.18 +wadllib==1.3.6 +wrapt==1.15.0 +zipp==3.17.0 \ No newline at end of file From a3417f876ed7a407fa3906cce77d111f3a711844 Mon Sep 17 00:00:00 2001 From: Sheng Yu Date: Tue, 5 Dec 2023 12:26:50 -0500 Subject: [PATCH 31/31] chore(snap): use setup.py --- setup.py | 24 ++++++++++++++++++++++++ snap/snapcraft.yaml | 14 +------------- 2 files changed, 25 insertions(+), 13 deletions(-) create mode 100755 setup.py diff --git a/setup.py b/setup.py new file mode 100755 index 000000000..03d6377cb --- /dev/null +++ b/setup.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python3 +# +# Copyright 2021 Canonical Ltd. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU Lesser General Public +# License version 3 as published by the Free Software Foundation. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# Lesser General Public License for more details. +# +# You should have received a copy of the GNU Lesser General Public License +# along with this program; if not, write to the Free Software Foundation, +# Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. +# + + +"""The setup script.""" + +from setuptools import setup + +setup() diff --git a/snap/snapcraft.yaml b/snap/snapcraft.yaml index ba19fd023..3f456fd43 100644 --- a/snap/snapcraft.yaml +++ b/snap/snapcraft.yaml @@ -66,23 +66,11 @@ parts: override-build: | ${SNAP}/libexec/snapcraft/craftctl default - # craft-parts changed the python symlink in venv, and unusable at the time - rm -f "${CRAFT_PART_INSTALL}/bin/python3" - ln -s "${CRAFT_PART_INSTALL}/../../../stage/usr/bin/python3" "${CRAFT_PART_INSTALL}/bin/python3" - - # Snapcraft 7 currently not support pure pyproject.toml, and will not install anything. - # Install the package manually - "${CRAFT_PART_INSTALL}/bin/pip" install -U "${CRAFT_PART_BUILD}[ubuntu-jammy]" - - version="$(${CRAFT_PART_INSTALL}/bin/python3 -B -c 'import importlib.metadata; print(importlib.metadata.version("rockcraft"))')" - + version="$(python3 setup.py --version)" ${SNAP}/libexec/snapcraft/craftctl set version="$version" [ -n "$(echo $version | grep "post")" ] && grade=devel || grade=stable ${SNAP}/libexec/snapcraft/craftctl set grade="${grade}" - # rerun to fix the symlinks - ${SNAP}/libexec/snapcraft/craftctl default - sed -i -e '1 s|^#!/.*|#!/snap/rockcraft/current/bin/python -E|' $CRAFT_PART_INSTALL/bin/craftctl after: - rockcraft-libs