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