Skip to content

Commit

Permalink
Use hatchling to build distribution (#1112)
Browse files Browse the repository at this point in the history
* refactor(packaging): use hatchling to build package

This includes a custom build hook to build our frontend js & css

* refactor(ci): use pypa/gh-action-pypi-publish action

* refactor(ci): move publish job to the ci workflow

This accomplishes two things:

- Ensures that tests pass before publishing
- On each workflow run, tests that the packaging works

* test(ci): upload built wheel and sdist for examination

* refactor(packaging): delete build-dist testenv from tox config

* refactor(packaging): remove dist and upload targets from Makefile

* docs(README): update the development instructions
  • Loading branch information
dairiki committed Mar 9, 2023
1 parent 3ddb731 commit 0ba8a9c
Show file tree
Hide file tree
Showing 10 changed files with 351 additions and 143 deletions.
43 changes: 40 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ on:
branches:
- master
- "*-maintenance"
tags:
- "v*"
pull_request:
branches:
- master
Expand Down Expand Up @@ -33,7 +35,7 @@ jobs:
with:
python-version: "3.9"
cache: "pip"
cache-dependency-path: "**/setup.cfg"
cache-dependency-path: "**/pyproject.toml"
- uses: actions/cache@v3
with:
path: ~/.cache/pre-commit
Expand All @@ -54,7 +56,7 @@ jobs:
############################################################################
# Node tests
############################################################################
node:
node-tests:
name: ${{ matrix.os}} node-${{ matrix.node }}
runs-on: ${{ matrix.os }}
strategy:
Expand Down Expand Up @@ -110,7 +112,7 @@ jobs:
with:
python-version: ${{ matrix.python }}
cache: "pip"
cache-dependency-path: "**/setup.cfg"
cache-dependency-path: "**/pyproject.toml"

- name: Install Linux system dependencies
if: runner.os == 'Linux' && matrix.install-ffmpeg
Expand Down Expand Up @@ -139,3 +141,38 @@ jobs:
run: tox
- name: Publish coverage data to codecov
uses: codecov/codecov-action@v3

############################################################################
# Package and (Possibly) Publish to PyPI
############################################################################
deploy:
needs: ["lint", "node-tests", "python-tests"]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
with:
fetch-depth: 0
- uses: actions/setup-python@v4
- uses: actions/setup-node@v3
with:
node-version: "lts/*"

- name: Install dependencies
run: python -m pip install build

- name: Build release artifacts
env:
HATCH_BUILD_CLEAN: "true"
run: python -m build

- name: Save release artifacts (for inspection)
uses: actions/upload-artifact@v3
with:
name: dist
path: dist/*

- name: Publish release
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags')
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.PYPI_PASSWORD }}
30 changes: 0 additions & 30 deletions .github/workflows/publish.yml

This file was deleted.

16 changes: 0 additions & 16 deletions MANIFEST.in

This file was deleted.

11 changes: 0 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -37,14 +37,3 @@ test: lint test-python test-js
test-all: test-js
pre-commit run -a
tox

# This creates source distribution and a wheel.
dist: build-js setup.cfg MANIFEST.in
rm -rf build dist
python -m build

# Before making a release, CHANGES.md needs to be updated and
# a tag should be created (and pushed with `git push --tags`).
.PHONY: upload
upload: dist
twine upload dist/*
19 changes: 13 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,24 +28,31 @@ For installation instructions head to the official documentation:

## Want to develop on Lektor?

This gets you started (assuming you have Python, pip, Make and pre-commit
This gets you started (assuming you have Python, pip, npm, and pre-commit
installed):

```
```bash
$ git clone https://github.com/lektor/lektor
$ cd lektor
$ virtualenv venv
$ . venv/bin/activate
$ python -m venv _venv
$ . _venv/bin/activate

# pip>=21.3 is required for PEP 610 support
$ pip install -U "pip>=21.3"

$ pip install --editable .
$ make build-js

# If you plan on committing:
$ pre-commit install

# Run the Lektor server
$ export LEKTOR_DEV=1
$ cp -r example example-project
$ lektor --project example-project server
```

If you want to run the test suite (you'll need tox installed):

```
```sh
$ tox
```
80 changes: 80 additions & 0 deletions build_frontend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
"""A hatch build-hook that builds the front-end js/css at PEP 517 build time.
By default, when building the dist, if the built frontend code is not
present in the source directory, it will be built. (Node must be installed,
or this will fail.)
Environment Variables
---------------------
There are a couple of environment variables that can be used to provide
additional control over the building of the frontend code.
- ``HATCH_BUILD_NO_HOOKS=true``: skip building the frontend code
- ``HATCH_BUILD_CLEAN=true``: force (re)building of the frontend code
"""
import shutil
import subprocess
from pathlib import Path

from hatchling.builders.hooks.plugin.interface import BuildHookInterface

# path to frontend source
FRONTEND = "frontend"

# path to compiled JS entry point
APP_JS = "lektor/admin/static/app.js"


class FrontendBuildHook(BuildHookInterface):
"""Hatching build hook to compile our frontend JS."""

def clean(self, versions):
"""Called at the beginning of each PEP 517 build, if HATCH_BUILD_CLEAN is set.
This implementation deletes any compiled frontend code that may be present
in the source tree.
"""
app = self.app
root = self.root

if not Path(root, FRONTEND).is_dir():
app.display_info(
"frontend source missing, skipping cleaning compiled output"
)
return

output_path = Path(root, APP_JS).parent
app.display_info(f"cleaning {output_path.relative_to(root)}")
if output_path.is_dir():
shutil.rmtree(output_path)

def initialize(self, version, build_data):
"""Hook called before each package build.
This implementation builds the compiled frontend source, but only
if it is not already present in the source tree.
Node (and npm) must be installed or this step will fail.
"""
app = self.app
root = self.root

if Path(root, APP_JS).is_file():
app.display_info(f"{APP_JS} exists, skipping frontend build")
return

npm = shutil.which("npm")
if npm is None:
app.abort("npm is not available. can not build frontend")

frontend = Path(root, FRONTEND)
if not frontend.is_dir():
app.abort("frontend source is missing. can not build frontend")

app.display_info("npm install")
subprocess.run((npm, "install"), cwd=frontend, check=True)
app.display_info("npm run build")
subprocess.run((npm, "run", "build"), cwd=frontend, check=True)
app.display_success("built frontend static files")
89 changes: 86 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,8 +1,91 @@
[build-system]
requires = ["setuptools>=45", "setuptools_scm>=6.2"]
build-backend = "setuptools.build_meta"
requires = ["hatchling>=1.13.0,<2.0.0", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "Lektor"
description = "A static content management system."
dynamic = ["version"]
readme = "README.md"
license = { file = "LICENSE" }
authors = [
{ name = "Armin Ronacher", email = "armin.ronacher@active-4.com" },
]
urls.Homepage = "https://www.getlektor.com/"
urls.Source = "https://github.com/lektor/lektor/"
urls.Documentation = "https://www.getlektor.com/docs/"
urls.Changelog = "https://github.com/lektor/lektor/blob/master/CHANGES.md"
classifiers = [
"Framework :: Lektor",
"Environment :: Web Environment",
"Intended Audience :: Developers",
"License :: OSI Approved :: BSD License",
"Operating System :: OS Independent",
"Programming Language :: Python",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
"Topic :: Software Development :: Libraries :: Python Modules",
]

requires-python = ">=3.7"
dependencies = [
"Babel",
"click>=6.0",
"EXIFRead",
"filetype>=1.0.7",
"Flask",
"importlib_metadata; python_version<'3.8'",
"inifile>=0.4.1",
"Jinja2>=3.0",
"MarkupSafe",
"marshmallow",
"marshmallow_dataclass>=8.5.9",
"mistune>=0.7.0,<3",
"pip",
"python-slugify",
"pytz; python_version<'3.9'", # favor zoneinfo for python>=3.9
"requests",
"tzdata; python_version>='3.9' and sys_platform == 'win32'",
"watchdog",
"Werkzeug>=2.1.0,<3",
]
optional-dependencies.ipython = [
"ipython",
"traitlets",
]

[project.scripts]
lektor = "lektor.cli:main"

[tool.hatch.version]
source = "vcs"

[tool.hatch.build]
include = [
"/lektor",
]
artifacts = [
"/lektor/admin/static",
]

[tool.hatch.build.targets.sdist]
include = [
"/lektor",
"/build_frontend.py",
"/CHANGES.md",
"/tests",
"/tox.ini",
]

[tool.hatch.build.hooks.custom]
path = "build_frontend.py"


[tool.setuptools_scm]

################################################################
#
Expand Down

0 comments on commit 0ba8a9c

Please sign in to comment.