diff --git a/.forgejo/workflows/alt.yaml b/.forgejo/workflows/alt.yaml index 8a11c4b..ef1faa4 100644 --- a/.forgejo/workflows/alt.yaml +++ b/.forgejo/workflows/alt.yaml @@ -2,6 +2,7 @@ on: [push] jobs: alpine: + runs-on: ubuntu-latest container: image: ${{vars.DOCKER}}alpine:3.19 steps: @@ -16,6 +17,7 @@ jobs: - run: spcd-browse-workspace debian: + runs-on: ubuntu-latest needs: alpine if: failure() || success() container: @@ -32,6 +34,7 @@ jobs: - run: spcd-browse-workspace ubuntu: + runs-on: ubuntu-latest needs: debian if: failure() || success() container: @@ -48,6 +51,7 @@ jobs: - run: spcd-browse-workspace arch: + runs-on: ubuntu-latest needs: ubuntu if: failure() || success() container: @@ -64,6 +68,7 @@ jobs: - run: spcd-browse-workspace opensuse: + runs-on: ubuntu-latest needs: arch if: failure() || success() container: @@ -80,6 +85,7 @@ jobs: - run: spcd-browse-workspace fedora: + runs-on: ubuntu-latest needs: opensuse if: failure() || success() container: @@ -96,6 +102,7 @@ jobs: - run: spcd-browse-workspace alma: + runs-on: ubuntu-latest needs: fedora if: failure() || success() container: @@ -112,6 +119,7 @@ jobs: - run: spcd-browse-workspace rocky: + runs-on: ubuntu-latest needs: alma if: failure() || success() container: diff --git a/.forgejo/workflows/main.yaml b/.forgejo/workflows/main.yaml index 45b1978..05b6e0a 100644 --- a/.forgejo/workflows/main.yaml +++ b/.forgejo/workflows/main.yaml @@ -2,6 +2,7 @@ on: [push] jobs: alpine: + runs-on: ubuntu-latest container: image: ${{vars.DOCKER}}alpine:3.20 steps: @@ -16,6 +17,7 @@ jobs: - run: spcd-browse-workspace debian: + runs-on: ubuntu-latest needs: alpine if: failure() || success() container: @@ -35,6 +37,7 @@ jobs: - run: spcd-synchronize ubuntu: + runs-on: ubuntu-latest needs: debian if: failure() || success() container: @@ -51,6 +54,7 @@ jobs: - run: spcd-browse-workspace arch: + runs-on: ubuntu-latest needs: ubuntu if: failure() || success() container: @@ -67,6 +71,7 @@ jobs: - run: spcd-browse-workspace opensuse: + runs-on: ubuntu-latest needs: arch if: failure() || success() container: @@ -83,6 +88,7 @@ jobs: - run: spcd-browse-workspace fedora: + runs-on: ubuntu-latest needs: opensuse if: failure() || success() container: @@ -99,6 +105,7 @@ jobs: - run: spcd-browse-workspace alma: + runs-on: ubuntu-latest needs: fedora if: failure() || success() container: @@ -115,6 +122,7 @@ jobs: - run: spcd-browse-workspace rocky: + runs-on: ubuntu-latest needs: alma if: failure() || success() container: diff --git a/pyproject.toml b/pyproject.toml index 103539a..1436be7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -18,7 +18,7 @@ dependencies = ["rwx"] description = "Shell to Python Continuous Deployment" dynamic = ["version"] keywords = [] -license-files = { paths = ["license.md"] } +license-files = ["license.md"] name = "spcd" readme = "readme.md" requires-python = ">= 3.11" diff --git a/readme.md b/readme.md index 0728687..552f13d 100644 --- a/readme.md +++ b/readme.md @@ -54,9 +54,10 @@ Picture it… * [X] installing Git to clone * [X] this project * [X] its parent framework -* [X] installing both Python +* [X] installing Python * [X] system environment * [X] virtual environment + * [ ] managed version * [X] generating a Python module to switch context #### Python @@ -95,30 +96,16 @@ Handle project workflows in a unified way: * [ ] SourceHut * whatever the Operating System container - * [X] Alma - * [X] 9 - * [X] 8 - * [X] Alpine - * [X] 3.20 - * [X] 3.19 - * [X] Arch - * [X] 20240818 (.0.255804) - * [X] 20240101 (.0.204074) - * [X] Debian - * [X] Bookworm (12) - * [ ] Bullseye (11) - * [X] Fedora - * [X] 40 - * [X] 39 - * [ ] OpenSUSE - * [ ] 15.6 - * [ ] 15.5 - * [X] Rocky - * [X] 9 - * [X] 8 - * [X] Ubuntu - * [X] Noble (24.04) - * [ ] Jammy (22.04) +| System | Latest | Previous | +|:---------|:---------------------------|:---------------------------| +| Alma | * [X] 9 | * [X] 8 | +| Alpine | * [X] 3.20 | * [X] 3.19 | +| Arch | * [X] 20240818 (.0.255804) | * [X] 20240101 (.0.204074) | +| Debian | * [X] Bookworm (12) | * [ ] Bullseye (11) | +| Fedora | * [X] 40 | * [X] 39 | +| OpenSUSE | * [ ] 15.6 | * [ ] 15.5 | +| Rocky | * [X] 9 | * [X] 8 | +| Ubuntu | * [X] Noble (24.04) | * [ ] Jammy (22.04) | ### Environment variables @@ -129,6 +116,7 @@ Handle project workflows in a unified way: | SPCD_GIT_RWX | RWX Git repository | rwx | | SPCD_GIT_SHUNIT | ShUnit Git repository | shunit2 | | SPCD_GIT_SPCD | SPCD Git repository | spcd | +| SPCD_REF_ARCHIVE | Archive deployment ref | old | | SPCD_REF_FEATURE | Feature deployment ref | f | | SPCD_REF_RELEASE | Release deployment ref | main | | SPCD_REF_STAGING | Staging deployment ref | dev | @@ -150,44 +138,40 @@ Handle project workflows in a unified way: #### Latest -| os | https | up ca | python | graphviz | plantuml | shellcheck | shunit | shfmt | gource | ffmpeg | -|:----------------|---|---|------------:|------:|-----------:|------:|------:|-----:|-----:|-------:| -| Arch 20240818 | ☑ | ☑ | 3.12 | 12.0 | 1.2023.13 | 0.10 | 2.1.8 | 3.8 | 0.54 | 7.0.2 | -| Alpine 3.20 | ☑ | ☐ | 3.12 | 9.0 | 1.2024.4 | 0.10 | 2.1.8 | 3.8 | 0.54 | 6.1.1 | -| Fedora 40 | ☑ | ☑ | 3.12 → 3.13 | 9.0 | 1.2024.6 | 0.9 | 2.1.6 | 3.7 | 0.55 | 6.1.2 | -| Debian Bookworm | ☐ | ☐ | 3.11 | 2.42 | 1.2020.2 | 0.9 | 2.1.8 | 3.6 | 0.54 | 5.1.6 | -| OpenSUSE 15.6 | ☐ | ☑ | 3.6 → 3.12 | 2.48 | 1.2020.9 | 0.8 | 2.1.6 | 3.5 | 0.54 | 4.4.4 | -| Ubuntu Noble | ☐ | ☐ | 3.12 | u2.42 | u1.2020.2 | u0.9 | 2.1.8 | u3.8 | 0.54 | 6.1.1 | -| Alma / Rocky 9 | ☑ | ☑ | 3.9 → 3.12 | 2.44 | e1.2024.6 | e0.8 | | | | e5.1.4 | +| os | https | up ca | python | ffmpeg | gource | graphviz | plantuml | shellcheck | shfmt | shunit | +|:----------------|---|---|------------:|-------:|-----:|------:|-----------:|------:|-----:|------:| +| Arch 20240818 | ☑ | ☑ | 3.12 | 7.0.2 | 0.54 | 12.0 | 1.2023.13 | 0.10 | 3.8 | 2.1.8 | +| Alpine 3.20 | ☑ | ☐ | 3.12 | 6.1.1 | 0.54 | 9.0 | 1.2024.4 | 0.10 | 3.8 | 2.1.8 | +| Fedora 40 | ☑ | ☑ | 3.12 → 3.13 | 6.1.2 | 0.55 | 9.0 | 1.2024.6 | 0.9 | 3.7 | 2.1.6 | +| Debian Bookworm | ☐ | ☐ | 3.11 | 5.1.6 | 0.54 | 2.42 | 1.2020.2 | 0.9 | 3.6 | 2.1.8 | +| OpenSUSE 15.6 | ☐ | ☑ | 3.6 → 3.12 | 4.4.4 | 0.54 | 2.48 | 1.2020.9 | 0.8 | 3.5 | 2.1.6 | +| Ubuntu Noble | ☐ | ☐ | 3.12 | 6.1.1 | 0.54 | u2.42 | u1.2020.2 | u0.9 | u3.8 | 2.1.8 | +| Alma / Rocky 9 | ☑ | ☑ | 3.9 → 3.12 | e5.1.4 | | 2.44 | e1.2024.6 | e0.8 | | | #### Previous -| os | https | up ca | python | graphviz | plantuml | shellcheck | shunit | shfmt | gource | ffmpeg | -|:----------------|---|---|------------:|------:|-----------:|------:|------:|-----:|-----:|-------:| -| Alpine 3.19 | ☑ | ☐ | 3.11 | 9.0 | 1.2023.12 | 0.9 | 2.1.8 | 3.7 | 0.54 | 6.1.1 | -| Fedora 39 | ☑ | ☑ | 3.12 → 3.13 | 8.1 | 1.2024.6 | 0.9 | 2.1.6 | 3.5 | 0.55 | 6.1.1 | -| OpenSUSE 15.5 | ☐ | ☑ | 3.6 → 3.11 | 2.48 | 1.2020.9 | 0.8 | 2.1.6 | 3.5 | 0.54 | 4.4.4 | -| Alma / Rocky 8 | ☑ | ☑ | 3.6 → 3.12 | 2.40 | e1.2024.6 | e0.6 | | | | | +| os | https | up ca | python | ffmpeg | gource | graphviz | plantuml | shellcheck | shfmt | shunit | +|:----------------|---|---|------------:|-------:|-----:|------:|-----------:|------:|-----:|------:| +| Alpine 3.19 | ☑ | ☐ | 3.11 | 6.1.1 | 0.54 | 9.0 | 1.2023.12 | 0.9 | 3.7 | 2.1.8 | +| Fedora 39 | ☑ | ☑ | 3.12 → 3.13 | 6.1.1 | 0.55 | 8.1 | 1.2024.6 | 0.9 | 3.5 | 2.1.6 | +| OpenSUSE 15.5 | ☐ | ☑ | 3.6 → 3.11 | 4.4.4 | 0.54 | 2.48 | 1.2020.9 | 0.8 | 3.5 | 2.1.6 | +| Alma / Rocky 8 | ☑ | ☑ | 3.6 → 3.12 | | | 2.40 | e1.2024.6 | e0.6 | | | #### Older Python -| os | https | up ca | python | graphviz | plantuml | shellcheck | shunit | shfmt | gource | ffmpeg | -|:----------------|---|---|------------:|------:|-----------:|------:|------:|-----:|-----:|-------:| -| Ubuntu Jammy | ☐ | ☐ | 3.10 | u2.42 | u1.2020.2 | u0.8 | 2.1.6 | u3.4 | 0.51 | 4.4.2 | -| Debian Bullseye | ☐ | ☐ | 3.9 | 2.42 | 1.2020.2 | 0.7 | 2.1.6 | | 0.51 | 4.3.7 | +| os | https | up ca | python | ffmpeg | gource | graphviz | plantuml | shellcheck | shfmt | shunit | +|:----------------|---|---|------------:|-------:|-----:|------:|-----------:|------:|-----:|------:| +| Ubuntu Jammy | ☐ | ☐ | 3.10 | 4.4.2 | 0.51 | u2.42 | u1.2020.2 | u0.8 | u3.4 | 2.1.6 | +| Debian Bullseye | ☐ | ☐ | 3.9 | 4.3.7 | 0.51 | 2.42 | 1.2020.2 | 0.7 | | 2.1.6 | --- ## Who -### By +### Author * [Marc Beninca](https://marc.beninca.link) -### For - -* People feeling the need to aim for consistency in the CI / CD universe - --- ## Where @@ -290,5 +274,7 @@ Handle project workflows in a unified way: * rpm fusion * tex * translate to french -* try to support nix +* try to support + * guix + * nix * uv diff --git a/spcd/__init__.py b/spcd/__init__.py index 4b9b6a5..31cd594 100644 --- a/spcd/__init__.py +++ b/spcd/__init__.py @@ -10,12 +10,13 @@ from rwx import fs from rwx.log import stream as log from rwx.ps import run -from spcd import cmd +from spcd import act, commands from spcd.ci import project, projects from spcd.shell import env from spcd.util import browse, cat, split, step COMMANDS_PREFIX = "spcd-" +ENTRY_POINT = "__main__.py" def clone_project_branch() -> None: @@ -44,6 +45,45 @@ def clone_project_branch() -> None: ) +def install_actions() -> None: + """Make actions usable in workflows.""" + step("Install actions") + name = "action.yaml" + root = project.root / "act" + vpy = Path(env.SPCD_PYTHON_VENV_BINARIES) / "python" + for action in ("action", "synchronize"): + log.info(action) + directory = root / action + fs.make_directory(directory) + match action: + case "action": + inputs = """\ + arg_1: + required: true + arg_2: + required: true + arg_3: + required: true + arg_4: + default: '"placeholder"' +""" + case "synchronize": + inputs = """\ + source: + default: out + required: false +""" + yaml = f"""\ +runs: + using: composite + steps: + - run: {vpy} -m spcd {action} +inputs: +{inputs}""" + fs.write(directory / name, yaml) + cat(directory / name) + + def install_commands(path: Path) -> None: """Make commands callable in the operating system. @@ -52,12 +92,12 @@ def install_commands(path: Path) -> None: """ step("Install commands") user = Path("/usr/local/bin") - for command in [ + for command in ( "browse-workspace", "build-project", "check-project", "synchronize", - ]: + ): log.info(command) (user / f"{COMMANDS_PREFIX}{command}").symlink_to(path) @@ -108,15 +148,20 @@ def main(main_file: Path) -> None: if env.SPCD_PYTHON_VENV_BINARIES not in paths: environ["PATH"] = pathsep.join([env.SPCD_PYTHON_VENV_BINARIES, *paths]) path, *arguments = sys.argv - name = Path(path).name - if name == "__main__.py": - list_environment_variables() - clone_project_branch() - set_ssh() - install_commands(main_file) - install_python_packages() + if (name := Path(path).name) == ENTRY_POINT: + if arguments: + name, *arguments = arguments + f = getattr(act, name) + f(*arguments) + else: + list_environment_variables() + clone_project_branch() + set_ssh() + install_actions() + install_commands(main_file) + install_python_packages() else: - f = getattr(cmd, name.replace("-", "_")) + f = getattr(commands, name.replace("-", "_")) f(*arguments) diff --git a/spcd/act.py b/spcd/act.py new file mode 100644 index 0000000..7886bbe --- /dev/null +++ b/spcd/act.py @@ -0,0 +1,62 @@ +"""Actions available for workflows.""" + +from __future__ import annotations + +import os +from ast import literal_eval +from pathlib import Path + +from rwx import ps +from rwx.log import stream as log + +from spcd.ci import project, projects +from spcd.shell import env + +PREFIX = "INPUT_" + + +def action() -> None: + """Display action inputs.""" + for variable, value in parse_inputs().items(): + log.info("%s = %s", variable, value) + + +def parse_inputs() -> dict[str, object]: + """Parse inputs as a dictionary. + + :return: name & value pairs + :rtype: dict[str, object] + """ + inputs = {} + for variable, value in sorted(projects.environment.items()): + if variable.startswith(PREFIX): + name = variable.removeprefix(PREFIX).lower() + inputs[name] = literal_eval(value) + return inputs + + +def synchronize(source: str | None = None, target: str | None = None) -> None: + """Synchronize output towards a target. + + :param source: where to deploy from + :type source: str | None + :param target: where to deploy to + :type target: str | None + """ + if not target: + user = "cd" + host = env.SPCD_PROJECT_PATH + root = ( + Path(os.sep) / user / projects.group / project.name / project.branch + ) + target = f"{user}@{host}:{root}" + if not source: + source = "out" + ps.run( + "rsync", + "--archive", + "--delete-before", + "--verbose", + f"{source}/", + f"{target}/", + ) diff --git a/spcd/bootstrap.sh b/spcd/bootstrap.sh index b9b1702..3ce4993 100644 --- a/spcd/bootstrap.sh +++ b/spcd/bootstrap.sh @@ -1,5 +1,3 @@ -#! /usr/bin/env sh - # ╭───────────────╮ # │ __ = internal │ # ╰───────────────╯ @@ -386,6 +384,7 @@ spcd_e_default() { [ -n "${SPCD_GIT_SHUNIT}" ] || SPCD_GIT_SHUNIT="shunit2" + [ -n "${SPCD_REF_ARCHIVE}" ] || SPCD_REF_ARCHIVE="old" [ -n "${SPCD_REF_FEATURE}" ] || SPCD_REF_FEATURE="f" [ -n "${SPCD_REF_RELEASE}" ] || SPCD_REF_RELEASE="main" [ -n "${SPCD_REF_STAGING}" ] || SPCD_REF_STAGING="dev" @@ -513,7 +512,8 @@ ${SPCD_PROJECT_ROOT}$(basename "${GITHUB_SERVER_URL}")" fi # check project variables case "${SPCD_PROJECT_BRANCH}" in - "${SPCD_REF_RELEASE}" | \ + "${SPCD_REF_ARCHIVE}" | \ + "${SPCD_REF_RELEASE}" | \ "${SPCD_REF_STAGING}" | \ "${SPCD_REF_FEATURE}") ;; *) spcd_error_ci "SPCD_PROJECT_BRANCH" ;; @@ -796,6 +796,15 @@ spcd_f_pkg() { "${SPCD_OS_ALMA}" | "${SPCD_OS_ROCKY}") ;; *) spcd_f_pm_pkg_install "gource" ;; esac + # gpg + spcd_step "GnuPG" + case "${SPCD_OS_ID}" in + "${SPCD_OS_DEBIAN}") + spcd_f_pm_pkg_install "gpg" + ;; + # TODO other operating systems + *) ;; + esac # graphviz spcd_step "GraphViz" spcd_f_pm_pkg_install "graphviz" @@ -817,6 +826,9 @@ spcd_f_pkg() { # plantuml spcd_step "PlantUML" spcd_f_pm_pkg_install "plantuml" + # qrencode + spcd_step "QRencode" + spcd_f_pm_pkg_install "qrencode" # rsync spcd_step "Rsync" spcd_f_pm_pkg_install "rsync" @@ -840,6 +852,20 @@ spcd_f_pkg() { ;; *) spcd_f_pm_pkg_install "shfmt" ;; esac + # tex + spcd_step "TeX" + case "${SPCD_OS_ID}" in + "${SPCD_OS_DEBIAN}") + spcd_f_pm_pkg_install "texlive-xetex" + spcd_f_pm_pkg_install "texlive-lang-french" + spcd_f_pm_pkg_install "texlive-plain-generic" + spcd_f_pm_pkg_install "texlive-bibtex-extra" + spcd_f_pm_pkg_install "texlive-fonts-recommended" + spcd_f_pm_pkg_install "biber" + ;; + # TODO other operating systems + *) ;; + esac spcd_step_out } @@ -1379,6 +1405,7 @@ index-url = ${SPCD_URL_PYTHON}/simple spcd_step "Activate" export PATH="${SPCD_PYTHON_VENV_BINARIES}:${PATH}" export VIRTUAL_ENV="${SPCD_PYTHON_VENV}" + pip install "PyYAML" spcd_step_out } diff --git a/spcd/cmd.py b/spcd/commands.py similarity index 64% rename from spcd/cmd.py rename to spcd/commands.py index 188d191..cc1a944 100644 --- a/spcd/cmd.py +++ b/spcd/commands.py @@ -1,6 +1,8 @@ """Commands available for workflows.""" -import os +from __future__ import annotations + +from os import sep from pathlib import Path from rwx import ps @@ -17,8 +19,8 @@ def spcd_browse_workspace() -> None: def spcd_build_project() -> None: """Perform the actual building process.""" - for extension in ["py", "sh"]: - path = project.root / f"build.{extension}" + for extension in ("py", "sh"): + path = project.root / f"render.{extension}" if path.exists(): ps.run(str(path)) break @@ -26,11 +28,25 @@ def spcd_build_project() -> None: def spcd_check_project() -> None: """Check the project for anything wrong.""" - ps.run("ruff", "check") + ps.run( + "ruff", + "check", + ("--ignore", "D203,D213"), + "--isolated", + ("--select", "ALL"), + ) + ps.run( + "ruff", + "format", + "--diff", + "--isolated", + ("--line-length", "80"), + ) def spcd_synchronize( - source: str | None = None, target: str | None = None + source: str | None = None, + target: str | None = None, ) -> None: """Synchronize output towards a target. @@ -42,9 +58,7 @@ def spcd_synchronize( if not target: user = "cd" host = env.SPCD_PROJECT_PATH - root = ( - Path(os.sep) / user / projects.group / project.name / project.branch - ) + root = Path(sep) / user / projects.group / project.name / project.branch target = f"{user}@{host}:{root}" if not source: source = "out" @@ -53,6 +67,6 @@ def spcd_synchronize( "--archive", "--delete-before", "--verbose", - f"{source}/", - f"{target}/", + f"{source}{sep}", + f"{target}{sep}", )