Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions Makefile
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
PROJECT=craft_providers

include common.mk

.PHONY: format
format: format-ruff format-codespell ## Run all automatic formatters

.PHONY: lint
lint: lint-ruff lint-codespell lint-mypy lint-pyright lint-shellcheck lint-yaml lint-docs lint-twine ## Run all linters

.PHONY: pack
pack: pack-pip ## Build all packages

.PHONY: publish
publish: publish-pypi ## Publish packages

.PHONY: publish-pypi
publish-pypi: clean package-pip lint-twine ##- Publish Python packages to pypi
uv tool run twine upload dist/*

# Used for installing build dependencies in CI.
.PHONY: install-build-deps
install-build-deps: install-lint-build-deps
ifeq ($(shell which apt-get),)
$(warning Cannot install build dependencies without apt.)
else ifeq ($(wildcard /usr/include/libxml2/libxml/xpath.h),)
sudo $(APT) install libxml2-dev libxslt1-dev python3-venv
else ifeq ($(wildcard /usr/include/libxslt/xslt.h),)
sudo $(APT) install libxslt1-dev python3-venv
else ifeq ($(wildcard /usr/share/doc/python3-venv/copyright),)
sudo $(APT) install python3-venv
endif

# If additional build dependencies need installing in order to build the linting env.
.PHONY: install-lint-build-deps
install-lint-build-deps:
269 changes: 269 additions & 0 deletions common.mk
Original file line number Diff line number Diff line change
@@ -0,0 +1,269 @@
# Common items for all Starcraft Makefiles. Should only be edited in the `starbase` repository:
# https://github.com/canonical/starbase

SOURCES=$(wildcard *.py) $(PROJECT) tests
DOCS=docs

ifneq ($(OS),Windows_NT)
OS := $(shell uname)
endif
ifdef CI
APT := apt-get --yes
else
APT := apt-get
endif

.DEFAULT_GOAL := help

.ONESHELL:

.SHELLFLAGS = -ec

.PHONY: help
help: ## Show this help.
@printf "\e[1m%-30s\e[0m | \e[1m%s\e[0m\n" "Target" "Description"
printf "\e[2m%-30s + %-41s\e[0m\n" "------------------------------" "------------------------------------------------"
egrep '^[^:]+\: [^#]*##' $$(echo $(MAKEFILE_LIST) | tac --separator=' ') | sed -e 's/^[^:]*://' -e 's/:[^#]*/ /' | sort -V| awk -F '[: ]*' \
'{
if ($$2 == "##")
{
$$1=sprintf(" %-28s", $$1);
$$2=" | ";
print $$0;
}
else
{
$$1=sprintf(" └ %-25s", $$1);
$$2=" | ";
$$3=sprintf(" └ %s", $$3);
print $$0;
}
}'

.PHONY: setup
setup: install-uv setup-precommit ## Set up a development environment
uv sync --frozen --all-extras

.PHONY: setup-tests
setup-tests: install-uv install-build-deps ##- Set up a testing environment without linters
uv sync --frozen

.PHONY: setup-lint
setup-lint: install-uv install-shellcheck install-pyright install-lint-build-deps ##- Set up a linting-only environment
uv sync --frozen --no-install-workspace --extra lint --extra types

.PHONY: setup-docs
setup-docs: install-uv ##- Set up a documentation-only environment
uv sync --frozen --no-dev --no-install-workspace --extra docs

.PHONY: setup-precommit
setup-precommit: install-uv ##- Set up pre-commit hooks in this repository.
ifeq ($(shell which pre-commit),)
uv tool install pre-commit
endif
ifeq ($(shell which pre-commit),)
uv tool run pre-commit install
else
pre-commit install
endif

.PHONY: clean
clean: ## Clean up the development environment
uv tool run pyclean .
rm -rf dist/ build/ docs/_build/ *.snap .coverage*

.PHONY: autoformat
autoformat: format # Hidden alias for 'format'

.PHONY: format-ruff
format-ruff: install-ruff ##- Automatically format with ruff
success=true
ruff check --fix $(SOURCES) || success=false
ruff format $(SOURCES)
$$success || exit 1

.PHONY: format-codespell
format-codespell: ##- Fix spelling issues with codespell
uv run codespell --toml pyproject.toml --write-changes $(SOURCES)

.PHONY: lint-ruff
lint-ruff: install-ruff ##- Lint with ruff
ifneq ($(CI),)
@echo ::group::$@
endif
ruff check $(SOURCES)
ruff format --diff $(SOURCES)
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-codespell
lint-codespell: ##- Check spelling with codespell
ifneq ($(CI),)
@echo ::group::$@
endif
uv run codespell --toml pyproject.toml $(SOURCES)
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-mypy
lint-mypy: ##- Check types with mypy
ifneq ($(CI),)
@echo ::group::$@
endif
uv run mypy --show-traceback --show-error-codes $(PROJECT)
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-pyright
lint-pyright: ##- Check types with pyright
ifneq ($(CI),)
@echo ::group::$@
endif
ifneq ($(shell which pyright),) # Prefer the system pyright
pyright --pythonpath .venv/bin/python
else
uv tool run pyright --pythonpath .venv/bin/python
endif
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-shellcheck
lint-shellcheck: ##- Lint shell scripts
ifneq ($(CI),)
@echo ::group::$@
endif
git ls-files | file --mime-type -Nnf- | grep shellscript | cut -f1 -d: | xargs -r shellcheck
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-yaml
lint-yaml: ##- Lint YAML files with yamllint
ifneq ($(CI),)
@echo ::group::$@
endif
uv run --extra lint yamllint .
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-docs
lint-docs: ##- Lint the documentation
ifneq ($(CI),)
@echo ::group::$@
endif
uv run --extra docs sphinx-lint --max-line-length 88 --enable all $(DOCS)
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: lint-twine
lint-twine: pack-pip ##- Lint Python packages with twine
ifneq ($(CI),)
@echo ::group::$@
endif
uv tool run twine check dist/*
ifneq ($(CI),)
@echo ::endgroup::
endif

.PHONY: test
test: ## Run all tests
uv run pytest

.PHONY: test-fast
test-fast: ##- Run fast tests
uv run pytest -m 'not slow'

.PHONY: test-slow
test-slow: ##- Run slow tests
uv run pytest -m 'slow'

.PHONY: test-coverage
test-coverage: ## Generate coverage report
uv run coverage run --source $(PROJECT) -m pytest
uv run coverage xml -o coverage.xml
uv run coverage report -m
uv run coverage html

.PHONY: docs
docs: ## Build documentation
uv run --extra docs sphinx-build -b html -W $(DOCS) $(DOCS)/_build

.PHONY: docs-auto
docs-auto: ## Build and host docs with sphinx-autobuild
uv run --extra docs sphinx-autobuild -b html --open-browser --port=8080 --watch $(PROJECT) -W $(DOCS) $(DOCS)/_build

.PHONY: pack-pip
pack-pip: ##- Build packages for pip (sdist, wheel)
ifneq ($(CI),)
@echo ::group::$@
endif
uv build .
ifneq ($(CI),)
@echo ::endgroup::
endif

# Below are intermediate targets for setup. They are not included in help as they should
# not be used independently.

.PHONY: install-uv
install-uv:
ifneq ($(shell which uv),)
else ifneq ($(shell which snap),)
sudo snap install --classic astral-uv
else ifneq ($(shell which brew),)
brew install uv
else ifeq ($(OS),Windows_NT)
pwsh -c "irm https://astral.sh/uv/install.ps1 | iex"
else
curl -LsSf https://astral.sh/uv/install.sh | sh
endif

.PHONY: install-codespell
install-codespell:
ifneq ($(shell which codespell),)
else ifneq ($(shell which snap),)
sudo snap install codespell
else ifneq ($(shell which brew),)
make install-uv
uv tool install codespell
else
$(warning Codespell not installed. Please install it yourself.)
endif

.PHONY: install-pyright
install-pyright: install-uv
ifneq ($(shell which pyright),)
else ifneq ($(shell which snap),)
sudo snap install --classic pyright
else
# Workaround for a bug in npm
[ -d "$(HOME)/.npm/_cacache" ] && chown -R `id -u`:`id -g` "$(HOME)/.npm" || true
uv tool install pyright
endif

.PHONY: install-ruff
install-ruff:
ifneq ($(shell which ruff),)
else ifneq ($(shell which snap),)
sudo snap install ruff
else
make install-uv
uv tool install ruff
endif

.PHONY: install-shellcheck
install-shellcheck:
ifneq ($(shell which shellcheck),)
else ifneq ($(shell which snap),)
sudo snap install shellcheck
else ifneq ($(shell which brew),)
brew install shellcheck
else
$(warning Shellcheck not installed. Please install it yourself.)
endif
33 changes: 28 additions & 5 deletions craft_providers/bases/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# Copyright 2021-2023 Canonical Ltd.
# Copyright 2021-2025 Canonical Ltd.
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU Lesser General Public
Expand All @@ -20,6 +20,9 @@
# Backward compatible, will be removed in 2.0
import sys
from typing import Dict, Literal, NamedTuple, Tuple, Type, Union, overload
from typing_extensions import Self

import craft_platforms

from craft_providers.errors import BaseCompatibilityError, BaseConfigurationError
from craft_providers.base import Base
Expand Down Expand Up @@ -48,11 +51,24 @@


class BaseName(NamedTuple):
"""A base image, by distribution and version."""
"""A base image, by distribution and version.

DEPRECATED: This class is deprecated and will be replaced with the craft_platforms
DistroBase class in a future major release.
"""

name: str
version: str

@classmethod
def from_distro_base(cls, distro_base: craft_platforms.DistroBase) -> Self:
"""Convert a DistroBase from craft-platforms to a craft-providers BaseName."""
return cls(name=distro_base.distribution, version=distro_base.series)

def to_distro_base(self) -> craft_platforms.DistroBase:
"""Convert this to a craft-platforms DistroBase."""
return craft_platforms.DistroBase(distribution=self.name, series=self.version)


BASE_NAME_TO_BASE_ALIAS: Dict[BaseName, BaseAlias] = {
BaseName("ubuntu", "16.04"): ubuntu.BuilddBaseAlias.XENIAL,
Expand Down Expand Up @@ -80,9 +96,16 @@ def get_base_alias(
base_name: Tuple[Literal["almalinux"], str]
) -> almalinux.AlmaLinuxBaseAlias: ...
@overload
def get_base_alias(base_name: BaseName) -> BaseAlias: ...
def get_base_alias(base_name: BaseName | craft_platforms.DistroBase) -> BaseAlias: ...
def get_base_alias(base_name):
"""Return a Base alias from a base (name, version) tuple."""
"""Return a Base alias from a base (name, version) tuple.

:param: base_name: A tuple of (distribution, series), a BaseName, or a DistroBase
of the same.
:returns: A BaseAlias corresponding to the base name.
"""
if isinstance(base_name, craft_platforms.DistroBase):
base_name = BaseName.from_distro_base(base_name)
base_name = BaseName(*base_name)
if base_name.name == "ubuntu" and base_name in BASE_NAME_TO_BASE_ALIAS:
return BASE_NAME_TO_BASE_ALIAS[base_name]
Expand Down Expand Up @@ -114,4 +137,4 @@ def get_base_from_alias(alias: BaseAlias) -> Type[Base]:
if isinstance(alias, almalinux.AlmaLinuxBaseAlias):
return almalinux.AlmaLinuxBase

raise BaseConfigurationError(f"Base not found for alias {alias}")
raise BaseConfigurationError(f"Base not found for alias {alias!r}")
1 change: 1 addition & 0 deletions docs/.custom_wordlist.txt
Original file line number Diff line number Diff line change
Expand Up @@ -71,3 +71,4 @@ urllib
VM
VMs
warmup
YYYY
5 changes: 5 additions & 0 deletions docs/reference/changelog.rst
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,11 @@ Changelog
See the `Releases page`_ on GitHub for a complete list of commits that are
included in each version.

X.Y.Z (YYYY-Mon-DD)
-------------------

- ``bases.BaseName`` is now compatible with the ``craft-platforms.DistroBase``.

2.2.0 (2025-Jan-16)
-------------------
- ``hookutil.py`` now available for dependent projects to clean up lxd
Expand Down
Loading