diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 00000000..e69de29b diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md new file mode 100644 index 00000000..440f2add --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -0,0 +1,3 @@ +- [ ] Have you signed the [CLA](http://www.ubuntu.com/legal/contributors/)? + +----- diff --git a/.github/release-drafter.yml b/.github/release-drafter.yml new file mode 100644 index 00000000..5262e23c --- /dev/null +++ b/.github/release-drafter.yml @@ -0,0 +1,24 @@ +categories: + - title: "New Features" + labels: + - "enhancement" + - title: "Maintenance" + labels: + - "maintenance" + - title: "Bug Fixes" + labels: + - "bug" + - title: "Specifications and Documentation" + label: + - "specification" + - "doc" + - title: "Tooling" + label: + - "tooling" +change-template: '- $TITLE @$AUTHOR (#$NUMBER)' +template: | + Special thanks to the contributors that made this release happen: $CONTRIBUTORS + + ## Full list of changes + + $CHANGES diff --git a/.github/workflows/cla-check.yaml b/.github/workflows/cla-check.yaml new file mode 100644 index 00000000..6978a35e --- /dev/null +++ b/.github/workflows/cla-check.yaml @@ -0,0 +1,21 @@ +name: cla-check +on: [pull_request] + +jobs: + cla-check: + runs-on: ubuntu-18.04 + steps: + - name: Install dependencies + run: | + sudo apt-get update + sudo apt-get install python3-launchpadlib git + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + # ensure we pull PR /head, not autogenerated /merge commit + ref: ${{ github.event.pull_request.head.sha }} + - name: Fetching base ref ${{ github.base_ref }} + run: git fetch origin ${{ github.base_ref }}:${{ github.base_ref }} + - name: Perform CLA check + run: ./tools/cla-check.py "${{ github.base_ref }}..HEAD" diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml new file mode 100644 index 00000000..6f066720 --- /dev/null +++ b/.github/workflows/tests.yaml @@ -0,0 +1,66 @@ +name: Tests + +on: + pull_request: + push: + branches: + - main +jobs: + tests: + runs-on: ubuntu-20.04 + steps: + - name: Checkout code + uses: actions/checkout@v2 + with: + fetch-depth: 0 + - name: Add local bin to PATH + run: | + echo "${HOME}/.local/bin" >> $GITHUB_PATH + - name: Install python package and dependencies + run: | + sudo apt install -y python3-pip python3-venv libapt-pkg-dev + python3 -m venv ${HOME}/.venv + source ${HOME}/.venv/bin/activate + pip install -U pip wheel setuptools + pip install -U -r requirements.txt -r requirements-dev.txt + pip install -e . + - name: Run black + run: | + source ${HOME}/.venv/bin/activate + make test-black + - name: Run codespell + run: | + source ${HOME}/.venv/bin/activate + make test-codespell + - name: Run flake8 + run: | + source ${HOME}/.venv/bin/activate + make test-flake8 + - name: Run isort + run: | + source ${HOME}/.venv/bin/activate + make test-isort + - name: Run mypy + run: | + source ${HOME}/.venv/bin/activate + make test-mypy + - name: Run pydocstyle + run: | + source ${HOME}/.venv/bin/activate + make test-pydocstyle + - name: Run pyright + run: | + sudo apt install -y npm + sudo npm install -g pyright + source ${HOME}/.venv/bin/activate + make test-pyright + - name: Run unit tests + run: | + source ${HOME}/.venv/bin/activate + make test-units + - name: Run integration tests + run: | + source ${HOME}/.venv/bin/activate + make test-integrations + - name: Upload code coverage + uses: codecov/codecov-action@v1 diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..5b35cacc --- /dev/null +++ b/.gitignore @@ -0,0 +1,114 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +.hypothesis/ +.pytest_cache/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# pyenv +.python-version + +# celery beat schedule file +celerybeat-schedule + +# SageMath parsed files +*.sage.py + +# dotenv +.env + +# virtualenv +.venv +venv/ +ENV/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ + +# IDE settings +.vscode/ + +# direnv +.direnv +.envrc + +# Lifecycle +parts/ +stage/ +prime/ diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..4106c494 --- /dev/null +++ b/Makefile @@ -0,0 +1,105 @@ +.PHONY: help +help: ## Show this help. + @printf "%-30s %s\n" "Target" "Description" + @printf "%-30s %s\n" "------" "-----------" + @fgrep " ## " $(MAKEFILE_LIST) | fgrep -v grep | awk -F ': .*## ' '{$$1 = sprintf("%-30s", $$1)} 1' + +.PHONY: autoformat +autoformat: ## Run automatic code formatters. + isort . + autoflake --remove-all-unused-imports --ignore-init-module-imports -ri . + black . + +.PHONY: clean +clean: ## Clean artifacts 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/craft_parts.* + rm -f docs/modules.rst + 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 + +.PHONY: coverage +coverage: ## Run pytest with coverage report. + coverage run --source craft_parts -m pytest + coverage report -m + coverage html + +.PHONY: docs +docs: ## Generate documentation. + rm -f docs/craft_parts.rst + rm -f docs/modules.rst + sphinx-apidoc -o docs/ craft_parts --no-toc --ext-githubpages + $(MAKE) -C docs clean + $(MAKE) -C docs html + +.PHONY: dist +dist: clean ## Build python package. + python setup.py sdist + python setup.py bdist_wheel + ls -l dist + +.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 ## 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 . + +.PHONY: test-integrations +test-integrations: ## Run integration tests. + pytest tests/integration + +.PHONY: test-isort +test-isort: + isort --check craft_parts tests + +.PHONY: test-mypy +test-mypy: + mypy craft_parts tests + +.PHONY: test-pydocstyle +test-pydocstyle: + pydocstyle craft_parts + +.PHONY: test-pylint +test-pylint: + pylint --fail-under=9.0 craft_parts + pylint tests --fail-under=9.0 --disable=invalid-name,missing-module-docstring,missing-function-docstring,redefined-outer-name,no-self-use,duplicate-code,protected-access,too-few-public-methods + +.PHONY: test-pyright +test-pyright: + pyright . + +.PHONY: test-units +test-units: ## Run unit tests. + pytest tests/unit + +.PHONY: tests +tests: lint test-units test-integrations ## Run all tests. diff --git a/README.md b/README.md new file mode 100644 index 00000000..e17318b7 --- /dev/null +++ b/README.md @@ -0,0 +1,8 @@ +# Craft Parts + +Craft-parts provides a mechanism to obtain data from different sources, +process it in various ways, and prepare a filesystem subtree suitable for +deployment. The components used in its project specification are called +*parts*, which can be independently downloaded, built and installed, and +also depend on each other in order to assemble the subtree containing the +final artifacts. diff --git a/craft_parts/__init__.py b/craft_parts/__init__.py new file mode 100644 index 00000000..4a9dd6bc --- /dev/null +++ b/craft_parts/__init__.py @@ -0,0 +1,19 @@ +# -*- Mode:Python; indent-tabs-mode:nil; tab-width:4 -*- +# +# Copyright 2021 Canonical Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""Craft a project from several parts.""" + +__version__ = "0.0.1" # noqa: F401 diff --git a/docs/.gitignore b/docs/.gitignore new file mode 100644 index 00000000..6253fb7d --- /dev/null +++ b/docs/.gitignore @@ -0,0 +1,3 @@ +craft_parts.rst +craft_parts.*.rst +modules.rst diff --git a/docs/Makefile b/docs/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/docs/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/docs/conf.py b/docs/conf.py new file mode 100644 index 00000000..981a74d4 --- /dev/null +++ b/docs/conf.py @@ -0,0 +1,88 @@ +# Copyright 2021 Canonical Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. +# +import os +import sys + +sys.path.insert(0, os.path.abspath("..")) + + +# -- Project information ----------------------------------------------------- + +project = "Craft Parts" +copyright = "2021, Canonical Ltd." +author = "Canonical Ltd." + +# The full version, including alpha/beta/rc tags +release = "0.0.1" + + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + "sphinx.ext.autodoc", + "sphinx.ext.napoleon", + "sphinx.ext.viewcode", + "sphinx_autodoc_typehints", # must be loaded after napoleon +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ["_templates"] + +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store"] + +autodoc_mock_imports = ["apt"] + + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = "sphinx_rtd_theme" + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ["_static"] + +# Do (not) include module names. +add_module_names = True + +# sphinx_autodoc_typehints +set_type_checking_flag = True +typehints_fully_qualified = False +always_document_param_types = True +typehints_document_rtype = True + +# Enable support for google-style instance attributes. +napoleon_use_ivar = True diff --git a/docs/index.rst b/docs/index.rst new file mode 100644 index 00000000..9ab64a46 --- /dev/null +++ b/docs/index.rst @@ -0,0 +1,44 @@ +.. Craft Parts documentation master file + You can adapt this file completely to your liking, but it should at least + contain the root `toctree` directive. + +====================================== +Welcome to Craft Parts' documentation! +====================================== + +Craft Parts provides a mechanism to obtain data from different sources, +process it in various ways, and prepare a filesystem subtree suitable for +deployment. The components used in its project specification are called +*parts*, which can be independently downloaded, built and installed, and +also depend on each other in order to assemble the subtree containing the +final artifacts. + +Application development +======================= + +Public APIs +----------- + + +Examples +-------- + + +Craft-parts development +======================= + +Internal APIs +------------- + + +.. toctree:: + :caption: Reference: + + craft_parts + +Indices and tables +================== + +* :ref:`genindex` +* :ref:`modindex` +* :ref:`search` diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 00000000..16f81064 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[tool.isort] +multi_line_output = 3 +include_trailing_comma = true +force_grid_wrap = 0 +use_parentheses = true +ensure_newline_before_comments = true +line_length = 88 + +[tool.pylint.messages_control] +disable = "bad-continuation,bad-whitespace,too-many-ancestors,too-few-public-methods" + +[tool.pylint.similarities] +min-similarity-lines=13 + +[tool.pylint.format] +max-line-length = "88" +max-attributes = 15 +max-args= 6 +max-locals = 16 + +[tool.pylint.MASTER] +extension-pkg-whitelist = [ + "apt_pkg" +] + +[tool.black] +exclude = '/((\.eggs|\.git|\.hg|\.mypy_cache|\.nox|\.tox|\.venv|_build|buck-out|build|dist|parts|stage|prime)/|setup.py)' diff --git a/pyrightconfig.json b/pyrightconfig.json new file mode 100644 index 00000000..575416b5 --- /dev/null +++ b/pyrightconfig.json @@ -0,0 +1,10 @@ +{ + "exclude": [ + "**/__pycache__", + "**/.mypy_cache", + "**/.pytest_cache", + ".direnv", + "build", + "docs" + ] +} diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 00000000..80db02dd --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] + +addopts = -W ignore::DeprecationWarning diff --git a/requirements-dev.txt b/requirements-dev.txt new file mode 100644 index 00000000..76b2ec31 --- /dev/null +++ b/requirements-dev.txt @@ -0,0 +1,13 @@ +black==20.8b1 +codespell +coverage +flake8 +isort +mypy +pydocstyle +pylint +pytest +pytest-mock +sphinx +sphinx-autodoc-typehints +sphinx-rtd-theme diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..e69de29b diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 00000000..c1753440 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,45 @@ +[bumpversion] +current_version = 0.0.1 +commit = True +tag = True + +[bumpversion:file:docs/conf.py] +search = release = "{current_version}" +replace = release = "{new_version}" + +[bumpversion:file:setup.py] +search = version="{current_version}" +replace = version="{new_version}" + +[bumpversion:file:craft_parts/__init__.py] +search = __version__ = "{current_version}" +replace = __version__ = "{new_version}" + +[bdist_wheel] +universal = 1 + +[codespell] +quiet-level = 3 +skip = .direnv,.git,.mypy_cache,.pytest_cache,.venv,__pycache__,venv + +[flake8] +exclude = docs venv .venv .mypy_cache .direnv .git __pycache__ +max-line-length = 88 +# E501 line too long +extend-ignore = E501 + +[mypy] +python_version = 3.8 + +[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, D204, D213, D215 + +[aliases] +test = pytest + +[tool:pytest] diff --git a/setup.py b/setup.py new file mode 100644 index 00000000..ed032bf6 --- /dev/null +++ b/setup.py @@ -0,0 +1,68 @@ +#!/usr/bin/env python +# +# Copyright 2021 Canonical Ltd +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU 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 General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + +"""The setup script.""" + +from setuptools import find_packages, setup # type: ignore + + +with open("README.md") as readme_file: + readme = readme_file.read() + +requirements = [ + "pyyaml", +] + +setup_requirements = [ + "pytest-runner", +] + +test_requirements = [ + "pytest>=3", +] + +setup( + author="Canonical Ltd", + author_email="Canonical Ltd", + python_requires=">=3.8", + classifiers=[ + "Development Status :: 2 - Pre-Alpha", + "Intended Audience :: Developers", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", + "Natural Language :: English", + "Programming Language :: Python :: 3.8", + ], + description="Craft parts tooling", + entry_points={ + "console_scripts": [ + "craft_parts=craft_parts.main:main", + ], + }, + install_requires=requirements, + license="GNU General Public License v3", + long_description=readme, + include_package_data=True, + keywords="craft_parts", + name="craft-parts", + package_data={"craft_parts": ["py.typed", "data/schema"]}, + packages=find_packages(include=["craft_parts", "craft_parts.*"]), + setup_requires=setup_requirements, + test_suite="tests", + tests_require=test_requirements, + url="https://github.com/canonical/craft_parts", + version="0.0.1", + zip_safe=False, +) diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tox.ini b/tox.ini new file mode 100644 index 00000000..bfc45c17 --- /dev/null +++ b/tox.ini @@ -0,0 +1,24 @@ +[tox] +envlist = py38 + +[testenv] +setenv = + PYTHONPATH = {toxinidir} +deps = + -r{toxinidir}/requirements-dev.txt + -r{toxinidir}/requirements.txt +commands = + pip install -U pip + pytest --basetemp={envtmpdir} + +[testenv:docs] +commands = make docs + +[testenv:lint] +commands = make lint + +[testenv:integrations] +commands = make test-integrations + +[testenv:units] +commands = make test-units