Skip to content

Commit

Permalink
Introduce a formal build system
Browse files Browse the repository at this point in the history
  • Loading branch information
jonct committed Jul 15, 2024
1 parent 109491c commit 47f332b
Show file tree
Hide file tree
Showing 16 changed files with 407 additions and 2 deletions.
56 changes: 56 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Inspiration from <https://github.com/pypa/hatch/issues/669>

name: Build

on:
push:
branches: ["**"]
pull_request:
branches: ["**"]

workflow_dispatch:

jobs:

build:
name: Build jlmkr tool
runs-on: ubuntu-24.04
strategy:
matrix:
python-version:
- "3.11" # TrueNAS SCALE 24.04 Dragonfish
steps:

- name: Checkout code
uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Set up pip cache
uses: actions/cache@v3
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('pyproject.toml') }}
restore-keys: ${{ runner.os }}-pip-

- name: Install Hatch
uses: pypa/hatch@install

- name: Run unit tests
run: hatch run +py=${{ matrix.python-version }} test:test

- name: Build distribution
run: hatch build -t zipapp -t appzip

# - name: Upload artifacts
# uses: actions/upload-artifact@v4
# with:
# path:
# - dist/jlmkr
# - dist/jlmkr-*.zip
# if-no-files-found: error
21 changes: 20 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,23 @@
/.venv/
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

dist/

# jail-specific
/.lxc/
/jails/

# Mac-specific
.DS_Store
._.DS_Store

# <https://github.com/github/gitignore/blob/main/Python.gitignore>

/.venv/

__pycache__/
*.py[cod]

.pytest_cache/
.ruff_cache/
21 changes: 20 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,7 @@ After cloning the project, navigate into its working directory and create a self
> .venv/bin/virtualenv --no-setuptools .venv
> rm -rf microvenv pip.pyz
> ```
*Note: This process and the resulting build environment will cache some items under `~/.local/share` in addition to the project directory.*
Activate the venv into your *current* shell session.
Expand All @@ -239,7 +240,25 @@ Develop away. Note that when you're done, you can undo this activation and retur
For more information on Python standard venvs, go to [the source](https://packaging.python.org/en/latest/tutorials/installing-packages/#creating-and-using-virtual-environments).
*TODO: introduce the tools we use for building, testing, and stylechecking.*
### Hatching a build
While in an *active* session, install the [Hatch](https://hatch.pypa.io) project manager. This will load quite a flurry of dependencies, but will only do so into the new `.venv` directory.
pip install hatch
Build the "zipapp" target. This will create a `dist/jlmkr` tool which is the direct descendant of Jip-Hop's original `jlmkr.py` script.
hatch build -t zipapp
Now build the "appzip" target. This bundles the tool, `README.md` and `LICENSING` into a downloadable zip archive.
hatch build -t appzip
If you make any changes *to the embedded builder plugins* that perform the above, then you will need to clear caches between builds. Otherwise and generally, you will not need to do so.
hatch env prune
Hatch has oodles more features yet to be explored, such as: automated testing, code coverage, and style checking. For now, we've gotten it building.
## Filing Issues and Community Support
Expand Down
85 changes: 85 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

[project]
name = "jlmkr"
description = "Build and manage jails on TrueNAS SCALE"
dynamic = ["version"]
readme = "README.md"
classifiers = [
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Private :: Do Not Upload",
]
authors = [
{name = "Jip-Hop"},
{name = "Lockszmith-GH"},
{name = "jonct"},
]
maintainers = [
{name = "Jip-Hop"},
]
requires-python = ">=3.8"

[project.urls]
GitHub = "https://github.com/Jip-Hop/jailmaker"

[build-system]
requires = [
"hatchling",
# "hatch-zipapp @ file:src/builder/app",
# "hatch-appzip @ file:src/builder/zip",
]
build-backend = "hatchling.build"

[project.scripts]
jlmkr = "jlmkr.donor:main"

[tool.hatch.version]
path = "src/jlmkr/__about__.py" # or source = "vcs"

[tool.hatch.build.zipapp]
dependencies = ["hatch-zipapp-builder @ file:src/builder/app"]

[tool.hatch.build.appzip]
dependencies = ["hatch-appzip-builder @ file:src/builder/zip"]

[tool.hatch.build.targets.custom]
path = "src/builder/builder.py"

[tool.hatch.env]
requires = [
"hatch-zipapp @ file:src/builder/app",
"hatch-appzip @ file:src/builder/zip",
]

[[tool.hatch.envs.test.matrix]]
python = [
"3.11", # TrueNAS SCALE 24.04 Dragonfish
]

[tool.hatch.envs.types]
extra-dependencies = [
"mypy>=1.0.0",
]
[tool.hatch.envs.types.scripts]
check = "mypy --install-types --non-interactive {args:src/jlmkr tests}"

[tool.coverage.run]
source_pkgs = ["jlmkr", "tests"]
branch = true
parallel = true
omit = [
"src/jlmkr/__about__.py",
]

[tool.coverage.paths]
jlmkr = ["src/jlmkr"]
tests = ["src/tests"]

[tool.coverage.report]
exclude_lines = [
"no cov",
"if __name__ == .__main__.:",
"if TYPE_CHECKING:",
]
3 changes: 3 additions & 0 deletions src/builder/app/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only
56 changes: 56 additions & 0 deletions src/builder/app/build_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

import os
from io import BytesIO
from pathlib import Path
from typing import Any, Callable, Iterable
from zipapp import create_archive

from hatchling.builders.plugin.interface import BuilderInterface


class ZipAppBuilder(BuilderInterface):
PLUGIN_NAME = "zipapp"

def get_version_api(self) -> dict[str, Callable[..., str]]:
return {"standard": self.build_standard}

def clean(self, directory: str, versions: Iterable[str]) -> None:
try:
os.remove(Path(directory, 'jlmkr'))
except:
pass

def build_standard(self, directory: str, **build_data: Any) -> str:

# generate zipapp source archive
pyzbuffer = BytesIO()
create_archive('src/jlmkr', target=pyzbuffer,
interpreter='=PLACEHOLDER=',
# main='donor.jlmkr:main',
compressed=True)
zipdata = pyzbuffer.getvalue() #.removeprefix(b"#!=PLACEHOLDER=\n")

# output with preamble
outpath = Path(directory, 'jlmkr')
with open(outpath, 'wb') as f:
f.write(preamble(self.metadata.version).encode())
f.write(zipdata)
os.chmod(outpath, 0o755)
return os.fspath(outpath)


# 10 lines will conveniently match the default of head(1)
def preamble(version): return f'''#!/usr/bin/env python3
jlmkr {version}
Persistent Linux 'jails' on TrueNAS SCALE to install software (k3s, docker, portainer, podman, etc.) with full access to all files via bind mounts.
SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
SPDX-License-Identifier: LGPL-3.0-only
-=-=-=- this is a zip file -=-=-=- what follows is binary -=-=-=-
'''
11 changes: 11 additions & 0 deletions src/builder/app/hooks_app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

from hatchling.plugin import hookimpl

from build_app import ZipAppBuilder

@hookimpl
def hatch_register_builder():
return ZipAppBuilder
22 changes: 22 additions & 0 deletions src/builder/app/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]

[project]
name = "hatch-zipapp"
version = "0.0.dev0"
classifiers = [
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Private :: Do Not Upload",
]
dependencies = ["hatchling"]

[project.entry-points.hatch]
zipapp = "hooks_app"

[tool.hatch.build.targets.wheel]
packages = ["."]
3 changes: 3 additions & 0 deletions src/builder/zip/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only
60 changes: 60 additions & 0 deletions src/builder/zip/build_zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

# hat tip: <https://github.com/dairiki/hatch-zipped-directory/blob/master/hatch_zipped_directory/builder.py>

import os
from hatchling.builders.config import BuilderConfig
from hatchling.builders.plugin.interface import BuilderInterface
from hatchling.builders.plugin.interface import IncludedFile
from hatchling.builders.utils import normalize_relative_path
from pathlib import Path
from typing import Any, Callable, Iterable
from zipfile import ZipFile, ZIP_DEFLATED


class AppZipBuilderConfig(BuilderConfig): pass

class AppZipBuilder(BuilderInterface):
PLUGIN_NAME = "appzip"

@classmethod
def get_config_class(cls):
return AppZipBuilderConfig

def get_version_api(self) -> dict[str, Callable[..., str]]:
return {'standard': self.build_standard}

def clean(self, directory: str, versions: Iterable[str]) -> None:
for filename in os.listdir(directory):
if filename.startswith('jlmkr-') and filename.endswith('.zip'):
os.remove(Path(directory, filename))

def build_standard(self, directory: str, **build_data: Any) -> str:
outpath = Path(directory, f'jlmkr-{self.metadata.version}.zip')
with ZipFile(outpath, 'w') as zip:
zip.write(Path(directory, 'jlmkr'), 'jlmkr')
force_map = build_data['force_include']
for included_file in self.recurse_forced_files(force_map):
zip.write(
included_file.relative_path,
included_file.distribution_path,
ZIP_DEFLATED)
return os.fspath(outpath)

def get_default_build_data(self) -> dict[str, Any]:
build_data: dict[str, Any] = super().get_default_build_data()

extra_files = []
if self.metadata.core.readme_path:
extra_files.append(self.metadata.core.readme_path)
if self.metadata.core.license_files:
extra_files.extend(self.metadata.core.license_files)

force_include = build_data.setdefault("force_include", {})
for fn in map(normalize_relative_path, extra_files):
force_include[os.path.join(self.root, fn)] = Path(fn).name
build_data['force_include'] = force_include

return build_data
11 changes: 11 additions & 0 deletions src/builder/zip/hooks_zip.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

from hatchling.plugin import hookimpl

from build_zip import AppZipBuilder

@hookimpl
def hatch_register_builder():
return AppZipBuilder
22 changes: 22 additions & 0 deletions src/builder/zip/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
# SPDX-FileCopyrightText: © 2024 Jip-Hop and the Jailmakers <https://github.com/Jip-Hop/jailmaker>
#
# SPDX-License-Identifier: LGPL-3.0-only

[build-system]
build-backend = "hatchling.build"
requires = ["hatchling"]

[project]
name = "hatch-appzip"
version = "0.0.dev0"
classifiers = [
"License :: OSI Approved :: GNU Lesser General Public License v3 (LGPLv3)",
"Private :: Do Not Upload",
]
dependencies = ["hatchling"]

[project.entry-points.hatch]
appzip = "hooks_zip"

[tool.hatch.build.targets.wheel]
packages = ["."]
Loading

0 comments on commit 47f332b

Please sign in to comment.