Skip to content

Commit f9ca042

Browse files
committed
setup: use setuptools-scm to generate the version
This commit changes the versioning scheme of non-release versions of mkosi to align with the standard Python version scheme [1]. Unfortunately this scheme is not fully compatible with the UAPI group version format specification. Concretely, this changes 26~devel to 25.3.post1.dev244+g0eac3718 The used format ensures that for prereleases the following inequalities still hold 25.3 < 25.3.post1.dev244+g0eac3718 < 25.4 < 26 Usage of this new format has the following benefits: 1. It is compatible with PyPI and allows publishing mkosi there in the future 2. Since it contains the number of commits since the last tag as well as part of the commit hash in the "local part" of the version, it becomes easier for bug reporters to show the version they are on no matter how mkosi has been installed. 3. The version no longer needs to be managed in multiple places. The current implementation moves the version from mkosi/sandbox.py to a new file _version.py that obtains the version either via importlib metadata (in case mkosi has been installed) or by calling setuptools-scm directly, to support the bin/ shims. Since the version is no longer available in mkosi/sandbox.py, it passed as an environment variable for scripts. [1] https://packaging.python.org/en/latest/specifications/version-specifiers/
1 parent 578830f commit f9ca042

File tree

10 files changed

+131
-13
lines changed

10 files changed

+131
-13
lines changed

mkosi/__init__.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
from pathlib import Path
2828
from typing import Any, Optional, Union, cast
2929

30+
from mkosi._version import __version__
3031
from mkosi.archive import can_extract_tar, extract_tar, make_cpio, make_tar
3132
from mkosi.bootloader import (
3233
efi_boot_binary,
@@ -128,7 +129,6 @@
128129
MOUNT_ATTR_RDONLY,
129130
MS_REC,
130131
MS_SLAVE,
131-
__version__,
132132
acquire_privileges,
133133
have_effective_cap,
134134
join_new_session_keyring,
@@ -600,6 +600,7 @@ def run_configure_scripts(config: Config) -> Config:
600600
MKOSI_UID=str(os.getuid()),
601601
MKOSI_GID=str(os.getgid()),
602602
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
603+
MKOSI_VERSION=__version__,
603604
)
604605

605606
if config.profiles:
@@ -641,6 +642,7 @@ def run_sync_scripts(config: Config) -> None:
641642
MKOSI_UID=str(os.getuid()),
642643
MKOSI_GID=str(os.getgid()),
643644
MKOSI_CONFIG="/work/config.json",
645+
MKOSI_VERSION=__version__,
644646
CACHED=one_zero(have_cache(config)),
645647
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
646648
)
@@ -760,6 +762,7 @@ def run_prepare_scripts(context: Context, build: bool) -> None:
760762
MKOSI_UID=str(os.getuid()),
761763
MKOSI_GID=str(os.getgid()),
762764
MKOSI_CONFIG="/work/config.json",
765+
MKOSI_VERSION=__version__,
763766
WITH_DOCS=one_zero(context.config.with_docs),
764767
WITH_NETWORK=one_zero(context.config.with_network),
765768
WITH_TESTS=one_zero(context.config.with_tests),
@@ -830,6 +833,7 @@ def run_build_scripts(context: Context) -> None:
830833
MKOSI_UID=str(os.getuid()),
831834
MKOSI_GID=str(os.getgid()),
832835
MKOSI_CONFIG="/work/config.json",
836+
MKOSI_VERSION=__version__,
833837
WITH_DOCS=one_zero(context.config.with_docs),
834838
WITH_NETWORK=one_zero(context.config.with_network),
835839
WITH_TESTS=one_zero(context.config.with_tests),
@@ -905,6 +909,7 @@ def run_postinst_scripts(context: Context) -> None:
905909
MKOSI_UID=str(os.getuid()),
906910
MKOSI_GID=str(os.getgid()),
907911
MKOSI_CONFIG="/work/config.json",
912+
MKOSI_VERSION=__version__,
908913
WITH_NETWORK=one_zero(context.config.with_network),
909914
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
910915
)
@@ -974,6 +979,7 @@ def run_finalize_scripts(context: Context) -> None:
974979
MKOSI_UID=str(os.getuid()),
975980
MKOSI_GID=str(os.getgid()),
976981
MKOSI_CONFIG="/work/config.json",
982+
MKOSI_VERSION=__version__,
977983
WITH_NETWORK=one_zero(context.config.with_network),
978984
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
979985
)
@@ -1037,6 +1043,7 @@ def run_postoutput_scripts(context: Context) -> None:
10371043
MKOSI_GID=str(os.getgid()),
10381044
MKOSI_CONFIG="/work/config.json",
10391045
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
1046+
MKOSI_VERSION=__version__,
10401047
)
10411048

10421049
if context.config.profiles:
@@ -4569,6 +4576,7 @@ def run_clean_scripts(config: Config) -> None:
45694576
MKOSI_GID=str(os.getgid()),
45704577
MKOSI_CONFIG="/work/config.json",
45714578
MKOSI_DEBUG=one_zero(ARG_DEBUG.get()),
4579+
MKOSI_VERSION=__version__,
45724580
)
45734581

45744582
if config.profiles:

mkosi/_staticversion.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
# SPDX-License-Identifier: LGPL-2.1-or-later
2+
__version__ = "25.3.post0"

mkosi/_version.py

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
# SPDX-License-Identifier: LGPL-2.1-or-later
2+
# The __version__ generation here supports the following modes
3+
# 1. The version is obtained from the environment variable MKOSI_VERSION, to trump all other e.g. for
4+
# debugging purposes
5+
# 2. By default the version is obtained von the Python distribution's metadata on installed packages, unless
6+
# a. the installed version of mkosi lacks this file
7+
# b. the path of that file is not equal to the path of this particular file
8+
# 3. If mkosi has not been installed as a Python package or the metadata pertains to a different mkosi than
9+
# is being called the version is
10+
# b. generated from the output of git describe
11+
# c. looked up in a static version file from resources
12+
# 4. If no version can be found, it is set to "0"
13+
14+
import datetime
15+
import importlib.metadata
16+
import logging
17+
import os
18+
import subprocess
19+
from importlib.metadata import PackageNotFoundError
20+
from pathlib import Path
21+
from typing import Optional
22+
23+
24+
def version_from_metadata() -> Optional[str]:
25+
try:
26+
dist = importlib.metadata.distribution("mkosi")
27+
28+
this_file = dist.locate_file("mkosi/_version.py")
29+
# If the installed version is too old, it might not have the _version.py file
30+
if not this_file.exists():
31+
return None
32+
33+
# If the file importlib.metadata thinks we are talking about is not this one, let's pretend we didn't
34+
# find anything at all and fall back
35+
if this_file != Path(__file__):
36+
return None
37+
38+
return importlib.metadata.version("mkosi")
39+
except PackageNotFoundError:
40+
return None
41+
42+
43+
def version_from_git() -> Optional[str]:
44+
try:
45+
p = subprocess.run(
46+
["git", "describe"],
47+
cwd=Path(__file__).parent.parent,
48+
check=True,
49+
text=True,
50+
capture_output=True,
51+
)
52+
# output has form like v25.3-244-g8f491df9 when not on a tag, else just the tag
53+
tag, *rest = p.stdout.strip().split("-")
54+
tag = tag.lstrip("v")
55+
if rest:
56+
numcommits, commit = rest
57+
return f"{tag}.post1.dev{numcommits}+{commit}.d{datetime.datetime.now():%Y%m%d}"
58+
59+
# we are exactly on a tag
60+
return tag
61+
except (subprocess.CalledProcessError, NotADirectoryError, FileNotFoundError):
62+
return None
63+
64+
65+
def version_from_static() -> Optional[str]:
66+
try:
67+
import mkosi._staticversion
68+
69+
return mkosi._staticversion.__version__
70+
except ImportError:
71+
return None
72+
73+
74+
def version_fallback() -> str:
75+
logging.warning("Unable to determine mkosi version")
76+
return "0"
77+
78+
79+
__version__ = (
80+
os.getenv("MKOSI_VERSION")
81+
or version_from_metadata()
82+
or version_from_git()
83+
or version_from_static()
84+
or version_fallback()
85+
)

mkosi/config.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,12 @@
3030
from pathlib import Path
3131
from typing import Any, Callable, ClassVar, Generic, Optional, Protocol, TypeVar, Union, cast
3232

33+
from mkosi._version import __version__
3334
from mkosi.distributions import Distribution, detect_distribution
3435
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SANDBOX, ARG_DEBUG_SHELL, die
3536
from mkosi.pager import page
3637
from mkosi.run import SandboxProtocol, find_binary, nosandbox, run, sandbox_cmd, workdir
37-
from mkosi.sandbox import Style, __version__
38+
from mkosi.sandbox import Style
3839
from mkosi.user import INVOKING_USER
3940
from mkosi.util import (
4041
PathString,

mkosi/initrd.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,11 +14,12 @@
1414
from typing import Optional, cast
1515

1616
import mkosi.resources
17+
from mkosi._version import __version__
1718
from mkosi.config import DocFormat, InitrdProfile, OutputFormat
1819
from mkosi.documentation import show_docs
1920
from mkosi.log import ARG_DEBUG, ARG_DEBUG_SHELL, log_notice, log_setup
2021
from mkosi.run import find_binary, run, uncaught_exception_handler
21-
from mkosi.sandbox import __version__, umask
22+
from mkosi.sandbox import umask
2223
from mkosi.tree import copy_tree, move_tree, rmtree
2324
from mkosi.util import PathString, mandatory_variable, resource_path
2425

mkosi/resources/man/mkosi.1.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2578,6 +2578,8 @@ Scripts executed by **mkosi** receive the following environment variables:
25782578
current image. This file can be parsed inside scripts to gain access to all
25792579
settings for the current image.
25802580

2581+
* `$MKOSI_VERSION` is the version string of mkosi.
2582+
25812583
* `$IMAGE_ID` contains the identifier from the `ImageId=` or `--image-id=` setting.
25822584

25832585
* `$IMAGE_VERSION` contains the version from the `ImageVersion=` or `--image-version=` setting.
@@ -2608,6 +2610,7 @@ Consult this table for which script receives which environment variables:
26082610
| `MKOSI_CONFIG` | ||||||||
26092611
| `MKOSI_GID` |||||||||
26102612
| `MKOSI_UID` |||||||||
2613+
| `MKOSI_VERSION` |||||||||
26112614
| `OUTPUTDIR` | | | | |||||
26122615
| `PACKAGEDIR` | | ||||| | |
26132616
| `PROFILES` ||||||| ||

mkosi/sandbox.py

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,6 @@
1313
import sys
1414
import warnings # noqa: F401 (loaded lazily by os.execvp() which happens too late)
1515

16-
__version__ = "26~devel"
17-
1816
# The following constants are taken from the Linux kernel headers.
1917
AT_EMPTY_PATH = 0x1000
2018
AT_FDCWD = -100
@@ -897,8 +895,18 @@ def main() -> None:
897895
print(HELP, file=sys.stderr)
898896
sys.exit(0)
899897
elif arg == "--version":
900-
print(__version__, file=sys.stderr)
901-
sys.exit(0)
898+
try:
899+
from mkosi._version import __version__
900+
901+
print(__version__, file=sys.stderr)
902+
sys.exit(0)
903+
except ImportError:
904+
try:
905+
print(os.environ["MKOSI_VERSION"])
906+
sys.exit(0)
907+
except KeyError:
908+
print("Cannot determine version", file=sys.stderr)
909+
sys.exit(1)
902910
if arg == "--tmpfs":
903911
fsops.append(TmpfsOperation(argv.pop()))
904912
elif arg == "--dev":

pyproject.toml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
[build-system]
2-
requires = ["setuptools", "setuptools-scm"]
2+
requires = ["setuptools>=64", "setuptools-scm>=8"]
33
build-backend = "setuptools.build_meta"
44

55
[project]
66
name = "mkosi"
77
authors = [
88
{name = "mkosi contributors", email = "[email protected]"},
99
]
10-
version = "25.3"
10+
dynamic = ["version"]
1111
description = "Build Bespoke OS Images"
1212
readme = "README.md"
1313
requires-python = ">=3.9"
@@ -50,6 +50,9 @@ packages = [
5050
"tmpfiles.d/*",
5151
]
5252

53+
[tool.setuptools_scm]
54+
version_scheme = "no-guess-dev"
55+
5356
[tool.isort]
5457
profile = "black"
5558
include_trailing_comma = true

tools/do-a-release.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,10 @@ if ! git diff-index --quiet HEAD; then
1313
exit 1
1414
fi
1515

16-
sed -r -i "s/^version = \".*\"$/version = \"$VERSION\"/" pyproject.toml
17-
sed -r -i "s/^__version__ = \".*\"$/__version__ = \"$VERSION\"/" mkosi/sandbox.py
16+
printf "%s\n" "$VERSION" >mkosi/resources/staticversion
17+
printf '# SPDX-License-Identifier: LGPL-2.1-or-later\n__version__ = "%s"\n' \
18+
"$VERSION" \
19+
>"${BUILDDIR}/mkosi/_staticversion.py"
1820

1921
git add -p pyproject.toml mkosi
2022

@@ -25,8 +27,6 @@ git tag -s "v$VERSION" -m "mkosi $VERSION"
2527
VERSION_MAJOR=${VERSION%%.*}
2628
VERSION="$((VERSION_MAJOR + 1))~devel"
2729

28-
sed -r -i "s/^__version__ = \".*\"$/__version__ = \"$VERSION\"/" mkosi/sandbox.py
29-
3030
git add -p mkosi
3131

3232
git commit -m "Bump version to $VERSION"

tools/generate-zipapp.sh

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,13 @@ mkdir -p builddir
1010

1111
cp -r mkosi "${BUILDDIR}/"
1212

13+
# HACK: importlib metadata doesn't seem to be there in a zipapp even if
14+
# properly installed via pip, so let's patch it in there manually.
15+
mkosiversion="$(python3 -m mkosi --version)"
16+
printf '# SPDX-License-Identifier: LGPL-2.1-or-later\n__version__ = "%s"\n' \
17+
"${mkosiversion#mkosi }" \
18+
>"${BUILDDIR}/mkosi/_staticversion.py"
19+
1320
python3 -m zipapp \
1421
-p "/usr/bin/env python3" \
1522
-o builddir/mkosi \

0 commit comments

Comments
 (0)