Workflow#

Rules#

  • Assume that the root of the repository is ./.

  • 3.5 <= Python version < 4

Variable reference#

See also

  • Arch User Repository - ArchWiki [1]

  • Semantic Versioning 2.0.0 | Semantic Versioning [2]

Variable name

Description

project_python_module_name

the name of the project as a Python module

project_directory

a relative path that corresponds project name

projects_aur_git_directory

the directory of the corresponding AUR package

gpg_signing_key

a valid PGP key used to sign commits

dev_branch

development git branch

version_id

the version identifier used in the git tag

MAJOR

a variable of the Semantic Versioning document

MINOR

a variable of the Semantic Versioning document

PATCH

a variable of the Semantic Versioning document

Sequence#

  1. enable git signing

    git config commit.gpgsign true
    git config user.signingkey ${gpg_signing_key}
    
  2. setup a bare git repository serving static pages. Follow this blog post

  3. if you need to publish a package on AUR follow the instructions

  4. finish working on the development branch

    cd ${project_directory}
    
  5. check that the current branch is not master

    git branch --no-color --show-current
    # dev
    
  6. if you need to publish a new asciinema video follow the instructions

  7. commit

    git add -A
    git commit
    git push
    
  8. update version numbers

    • ./setup.cfg

    • ./docs/conf.py

    • ./packages/*

  9. if necessary update dependencies in

    • ./requirements.txt

    • ./requirements-dev.txt

    • ./setup.cfg

    and regenerate the pinned dependencies

    make regenerate-freeze
    

    Read the changelogs of the dependencies and decide what packages to update

  10. run a final test

    make uninstall-dev; make install-dev
    make doc
    make test
    make install
    cd ~ && python3 -c "import ${package_name}" && cd ${OLDPWD}
    make uninstall
    make clean
    
  11. For each of these files update copyright years, emails and contributors

    • ./README.md

    • ./docs/conf.py

    • ./docs/misc.rst

    • all changed Python source files

    • ./packages/*

  12. commit

    git add -A
    make pre-commit
    git commit
    git push
    
  13. update the documentation

    make clean && make doc
    rm -rf ~/html && cp -aR docs/_build/html ~
    git branch --delete --force docs
    git checkout --orphan docs
    rm -rf .mypy_cache .venv
    git rm --cached -r .
    git clean -f && git clean -f -d
    mv ~/html/{*,.buildinfo} .
    git add -A
    git commit --no-verify -m "New release"
    git push --force deploy-docs docs
    
  14. merge the branches and tag the release

    git checkout master
    git merge ${dev_branch}
    
  15. tag the release.

    git tag -s -a ${version_id}
    

    Write a message like this. Use the infinitive tense

    Release ${MAJOR}.${MINOR}.${PATCH}
    
    - Item 1
    - Item 2
    - [ ... ]
    - Item n
    
  16. push the changes

    git push
    git push --tags
    
  17. upload the package to PyPI if applicable

    make install-dev
    make clean
    make dist
    make upload
    
  18. upload the package on the software page. Follow the instructions reported here

  19. update downstream distribution packages. See the updating packages section

  20. update other resources if applicable

Other steps#

Upstream packages#

AUR#

See also

  • An alternative to GitHub Pages [3]

  1. create an Arch Linux virtual machine

  2. create a bare repository

    git init --bare ${project_directory}
    cd ${project_directory}
    
  3. use this post-receive git hook

    .git/hooks/post-receive#
    #!/usr/bin/bash -l
    #
    # The MIT License (MIT)
    #
    # Copyright (c) 2008-present Tom Preston-Werner and Jekyll contributors
    #               2021 Franco Masotti
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in all
    # copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    # SOFTWARE.
    
    cleanup()
    {
        local ok='${1}'
    
        rm --recursive --force "${tmp_git_clone}" /dev/shm/"${PROJECT}"
        if [ "${ok}" != 'true' ]; then
            printf '\n%s\n%s\n%s %s\n\n' 'remember to run' '"git tag | xargs git tag -d; git update-ref HEAD -d"' 'before a new attempt with the same tag' "(${tag})"
        fi
    }
    
    set -euo pipefail
    
    . 'hooks/post-receive.conf'
    
    # Run cleanup on signal.
    trap cleanup EXIT
    
    # Check if last commit has a tag pointing to it.
    tag="$(git describe --exact-match)" || { printf '%s\n' 'error: tag not present'; exit 1; }
    # Check AUR dependencies.
    pacman -Qi ${AUR_DEPENDENCIES} 1>/dev/null 2>/dev/null || { printf '%s\n' 'error: AUR dependencies not met'; exit 1; }
    
    sha512_checksum="$(/usr/bin/curl ${BASE_URL}/${PROJECT}-${tag}/${PROJECT}-${tag}.tar.gz.SHA512SUM.txt | awk '{ print $1 }')"
    tmp_git_clone=""${HOME}"/tmp/${PROJECT}"
    
    ###########
    # Cleanup #
    ###########
    cleanup 'true'
    
    #########
    # Clone #
    #########
    git clone "${GIT_DIR}" "${tmp_git_clone}"
    unset GIT_DIR
    
    #########
    # Build #
    #########
    # Create a disposable directory because we need to clean the temporary repository later.
    mkdir /dev/shm/"${PROJECT}"
    pushd "${tmp_git_clone}"/packages/aur
    cp -aR PKGBUILD /dev/shm/"${PROJECT}"
    pushd /dev/shm/"${PROJECT}"
    
    # Replace SKIP with SHA512SUM.
    sed -i "s/sha512sums=('SKIP' 'SKIP'/sha512sums=('SKIP' '${sha512_checksum}'/" PKGBUILD
    
    # Build the package.
    makepkg -rsi --noconfirm
    makepkg --printsrcinfo > .SRCINFO
    
    ########
    # Test #
    ########
    # Run a command to test the package.
    pushd ~
    eval ${TEST_COMMAND}
    popd
    
    ###########
    # Cleanup #
    ###########
    sudo pacman -Rnus --noconfirm "${ARCHLINUX_PACKAGE}"
    rm -rf pkg src *.tar.*
    
    popd
    popd
    
    ##########
    # Commit #
    ##########
    pushd "${tmp_git_clone}"
    
    # Push updated pkgbuild to an empty disposable git branch.
    # Put PKGBUILD and .SRCINFO
    git checkout --orphan packages-aur
    git rm -rf .
    mv /dev/shm/"${PROJECT}"/{PKGBUILD,.SRCINFO} .
    git add PKGBUILD .SRCINFO
    git commit -m "Updated PKGBUILD and .SRCINFO."
    
    # Update remotes.
    git remote add repo "${ORIGIN}"
    
    # Remove remote branch.
    git push repo --delete packages-aur || echo OK
    
    # Push the files.
    git push --set-upstream repo packages-aur
    
    popd
    
  4. use this configuration

    .git/hooks/post-receive.conf#
    # The MIT License (MIT)
    #
    # Copyright (c) 2008-present Tom Preston-Werner and Jekyll contributors
    #               2021 Franco Masotti
    #
    # Permission is hereby granted, free of charge, to any person obtaining a copy
    # of this software and associated documentation files (the "Software"), to deal
    # in the Software without restriction, including without limitation the rights
    # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
    # copies of the Software, and to permit persons to whom the Software is
    # furnished to do so, subject to the following conditions:
    #
    # The above copyright notice and this permission notice shall be included in all
    # copies or substantial portions of the Software.
    #
    # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
    # SOFTWARE.
    
    # Example for fpyutils.
    
    TEST_COMMAND="python3 -c 'import fpyutils'"
    BASE_URL='https://blog.franco.net.eu.org/software'
    PROJECT='fpyutils'
    ARCHLINUX_PACKAGE='python-fpyutils'
    ORIGIN="gitea@franco.net.eu.org:frnmst/fpyutils.git"
    AUR_DEPENDENCIES=''
    
  5. mark the hook as executable

    chmod 700 .git/hooks/post-receive
    
  6. add the pacman command to the ones not requiring password in the sudoers file

    visudo
    build-user ALL=NOPASSWD: /bin/pacman
    
  7. on the local repository (development machine) create a new remote called packages-aur which points to the Arch Linux virtual machine

  8. create a repository containing the AUR source. Add a normal origin remote and a git aur remote that to points to https://aur.archlinux.org/"$(basedir "${projects_aur_git_directory}")".git

Updating packages#

AUR#

git push --atomic packages-aur master ${MAJOR}.${MINOR}.${PATCH}
make clean
git branch -D packages-aur && git fetch --all && git checkout packages-aur
cp PKGBUILD .SRCINFO ${projects_aur_git_directory}
git checkout ${dev_branch}
cd ${projects_aur_git_directory}
git add PKGBUILD .SRCINFO
git commit -m "New release."
git push
git push aur

Asciinema#

  1. create a new asciinema demo file

    cd ./asciinema
    touch ${project_directory}_asciinema_${MAJOR}_${MINOR}_${PATCH}_demo.sh
    
  2. edit the demo file

  3. record and check the demo file

    . .venv/bin/activate
    asciinema rec --command=./${project_directory}_asciinema_${MAJOR}_${MINOR}_${PATCH}_demo.sh ${project_directory}_asciinema_${MAJOR}_${MINOR}_${PATCH}.json
    asciinema play ${project_directory}_asciinema_${MAJOR}_${MINOR}_${PATCH}.json
    deactivate
    
  4. upload

    . .venv/bin/activate
    asciinema upload ${project_directory}_asciinema_${MAJOR}_${MINOR}_${PATCH}.json
    deactivate
    
  5. edit the ./README.md file with the new asciinema link

  6. commit

    git add -A
    git commit
    git push
    

Files#

Makefile#

#
# Makefile
#

export PACKAGE_NAME=${project_python_module_name}

# See
# https://docs.python.org/3/library/venv.html#how-venvs-work
export VENV_CMD=. .venv/bin/activate

default: install-dev

doc:
     $(VENV_CMD) \
             && $(MAKE) -C docs html \
             && deactivate

 install:
     pip3 install . --user

uninstall:
     pip3 uninstall --verbose --yes $(PACKAGE_NAME)

install-dev:
     python3 -m venv .venv
     $(VENV_CMD) \
             && pip install --requirement requirements-freeze.txt \
             && deactivate
     $(VENV_CMD) \
             && pre-commit install \
             && deactivate
     $(VENV_CMD) \
             && pre-commit install --hook-type commit-msg \
             && deactivate

regenerate-freeze: uninstall-dev
     python3 -m venv .venv
     $(VENV_CMD) \
             && pip install --requirement requirements.txt --requirement requirements-dev.txt \
             && pip freeze --local > requirements-freeze.txt \
             && deactivate

uninstall-dev:
     rm -rf .venv

update: install-dev
     $(VENV_CMD) \
             && pre-commit autoupdate \
                     --repo https://github.com/pre-commit/pre-commit-hooks \
                     --repo https://github.com/PyCQA/bandit \
                     --repo https://github.com/pycqa/isort \
                     --repo https://codeberg.org/frnmst/licheck \
                     --repo https://codeberg.org/frnmst/md-toc \
                     --repo https://github.com/mgedmin/check-manifest \
                     --repo https://github.com/jorisroovers/gitlint \
             && deactivate
             # --repo https://github.com/pre-commit/mirrors-mypy \

test:
     $(VENV_CMD) \
             && python -m unittest $(PACKAGE_NAME).tests.tests --failfast --locals --verbose \
             && deactivate

pre-commit:
     $(VENV_CMD) \
             && pre-commit run --all \
             && deactivate

dist:
     # Create a reproducible archive at least on the wheel.
     # See
     # https://bugs.python.org/issue31526
     # https://bugs.python.org/issue38727
     # https://github.com/pypa/setuptools/issues/1468
     # https://github.com/pypa/setuptools/issues/2133
     # https://reproducible-builds.org/docs/source-date-epoch/
     $(VENV_CMD) \
             && SOURCE_DATE_EPOCH=$$(git -c log.showSignature='false' log -1 --pretty=%ct) \
             python -m build \
             && deactivate
     $(VENV_CMD) \
             && twine check --strict dist/* \
             && deactivate

upload:
     $(VENV_CMD) \
             && twine upload dist/* \
             && deactivate

clean:
     rm -rf build dist *.egg-info tests/benchmark-results
     # Remove all markdown files except the readmes.
     find -regex ".*\.[mM][dD]" ! -name 'README.md' ! -name 'CONTRIBUTING.md' -type f -exec rm -f {} +
     $(VENV_CMD) \
             && $(MAKE) -C docs clean \
             && deactivate

.PHONY: default doc install uninstall install-dev uninstall-dev update test pre-commit

Footnotes