From 228694bfd6745cd9c9bf3510f7e7a9675cb6388a Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:30:07 -0600 Subject: [PATCH 1/9] Switch to emake --- .github/workflows/build.yaml | 51 ----------- .github/workflows/build.yml | 172 +++++++++++++++++++++++++++++++++++ Makefile | 18 +--- launcherctl/_app.py | 27 +++--- launcherctl/_launcher.py | 38 ++++---- launcherctl/_util.py | 10 +- pyproject.toml | 43 ++++++++- 7 files changed, 246 insertions(+), 113 deletions(-) delete mode 100644 .github/workflows/build.yaml create mode 100644 .github/workflows/build.yml diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml deleted file mode 100644 index 78dbaf5..0000000 --- a/.github/workflows/build.yaml +++ /dev/null @@ -1,51 +0,0 @@ -name: Check and Build -on: - push: - branches: - - main - pull_request: - workflow_dispatch: - release: - types: [released] -permissions: read-all -jobs: - build: - name: Make pip packages - runs-on: ubuntu-latest - steps: - - name: Checkout the Git repository - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: '3.11' - cache: 'pip' - - name: Install build tool - run: pip install build - - name: Building package - run: python -m build - - uses: actions/upload-artifact@v4 - with: - name: pip - path: dist/* - if-no-files-found: error - publish: - name: Publish to PyPi - runs-on: ubuntu-latest - needs: [build] - if: github.repository == 'Eeems-Org/python-launcherctl' && github.event_name == 'release' && startsWith(github.ref, 'refs/tags') - permissions: - id-token: write - environment: - name: pypi - url: https://pypi.org/p/launcherctl - steps: - - name: Download pip packages - id: download - uses: actions/download-artifact@v4 - with: - name: pip - - name: Publish package distributions to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - packages-dir: ${{ steps.download.outputs.download-path }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml new file mode 100644 index 0000000..80a4a8a --- /dev/null +++ b/.github/workflows/build.yml @@ -0,0 +1,172 @@ +name: Check and Build + +on: + push: + branches: + - master + pull_request: + workflow_dispatch: + release: + types: + - released + +permissions: read-all + +jobs: + lint: + name: Lint codebase + runs-on: ubuntu-latest + strategy: + matrix: + python: &python-versions + - "3.11" + - "3.12" + - "3.13" + - "3.14" + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - uses: actions/setup-python@v6 + with: + python-version: ${{ matrix.python }} + cache: pip + - &install-emake + name: Install emake + run: pip install emake + - name: Run lint + run: emake lint + + build-sdist: + name: Build sdist + needs: &build-needs + - lint + - test + runs-on: ubuntu-latest + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building sdist + run: emake build --sdist + - uses: actions/upload-artifact@v6 + with: + name: pip-sdist + path: dist/* + if-no-files-found: error + + build-any-wheel: + name: Build wheel + needs: *build-needs + runs-on: ubuntu-latest + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building wheel + run: emake build --wheel + - name: Test wheel + run: emake test --wheel + - uses: actions/upload-artifact@v6 + with: + name: pip-wheel-none-any + path: dist/* + if-no-files-found: error + + build-wheel: + name: Build wheel + needs: *build-needs + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + python: *python-versions + arch: + - x86_64 + - i686 + - ppc64le + - aarch64 + - armv7l + - riscv64 + - s390x + libc: + - glibc + - musl + exclude: + - arch: i686 # segfaults currently + python: 3.14 + libc: glibc + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - *install-emake + - name: Building wheel + run: | + emake build \ + --native-wheel \ + --arch ${{ matrix.arch }} \ + --libc ${{ matrix.libc }} \ + --python ${{ matrix.python }} + - name: Testing wheel + run: | + emake test \ + --wheel \ + --arch ${{ matrix.arch }} \ + --libc ${{ matrix.libc }} \ + --python ${{ matrix.python }} + - uses: actions/upload-artifact@v6 + with: + name: pip-wheel-${{ matrix.python }}-${{ matrix.arch }}-${{ matrix.libc }} + path: dist/* + if-no-files-found: error + + publish: + name: Publish to PyPi + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + needs: &release-needs + - build-sdist + - build-any-wheel + - build-wheel + runs-on: ubuntu-latest + permissions: + id-token: write + contents: write + environment: + name: pypi + url: https://pypi.org/p/launcherctl + steps: + - name: Download pip packages + id: download + uses: actions/download-artifact@v8 + with: + pattern: pip-* + merge-multiple: true + path: dist + - name: Publish package distributions to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + packages-dir: ${{ steps.download.outputs.download-path }} + skip-existing: true + + release: + name: Add release artifacts + if: github.event_name == 'release' && startsWith(github.ref, 'refs/tags') + needs: *release-needs + runs-on: ubuntu-latest + permissions: + contents: write + steps: + - name: Checkout the Git repository + uses: actions/checkout@v6 + - name: Download artifact + id: download + uses: actions/download-artifact@v8 + with: + pattern: pip-* + merge-multiple: true + path: dist + - name: Upload to release + run: find . -type f | xargs -rI {} gh release upload "$TAG" {} --clobber + env: + GH_TOKEN: ${{ github.token }} + TAG: ${{ github.event.release.tag_name }} + working-directory: ${{ steps.download.outputs.download-path }} diff --git a/Makefile b/Makefile index 9022369..9fded10 100644 --- a/Makefile +++ b/Makefile @@ -46,20 +46,4 @@ $(VENV_BIN_ACTIVATE): . $(VENV_BIN_ACTIVATE); \ python -m pip install ruff -lint: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff check - -lint-fix: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff check - -format: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff format --diff - -format-fix: $(VENV_BIN_ACTIVATE) - . $(VENV_BIN_ACTIVATE); \ - python -m ruff format - -.PHONY: clean install test deploy lint lint-fix format format-fix +.PHONY: clean install test deploy diff --git a/launcherctl/_app.py b/launcherctl/_app.py index 6661c83..3be4eae 100644 --- a/launcherctl/_app.py +++ b/launcherctl/_app.py @@ -2,8 +2,8 @@ class App: - def __init__(self, name: str): - self.name = name + def __init__(self, name: str) -> None: + self.name: str = name @property def is_running(self) -> bool: @@ -14,21 +14,21 @@ def is_paused(self) -> bool: return self.name in api.paused.keys() def start(self) -> None: - launcherctl("start-app", self.name) + _ = launcherctl("start-app", self.name) def stop(self) -> None: - launcherctl("stop-app", self.name) + _ = launcherctl("stop-app", self.name) def pause(self) -> None: - launcherctl("pause-app", self.name) + _ = launcherctl("pause-app", self.name) def resume(self) -> None: - launcherctl("resume-app", self.name) + _ = launcherctl("resume-app", self.name) class API: def keys(self) -> list[str]: - return [x.decode("utf-8") for x in launcherctl("list-apps").splitlines()] + return [x for x in launcherctl("list-apps").splitlines()] def __contains__(self, key: str) -> bool: return key in self.keys() @@ -40,21 +40,16 @@ def __getitem__(self, key: str) -> App: return App(key) @property - def running(self) -> dict[App]: + def running(self) -> dict[str, App]: return { x: App(x) - for x in [ - x.decode("utf-8") for x in launcherctl("list-running-apps").splitlines() - ] + for x in [x for x in launcherctl("list-running-apps").splitlines()] } @property - def paused(self) -> dict[App]: + def paused(self) -> dict[str, App]: return { - x: App(x) - for x in [ - x.decode("utf-8") for x in launcherctl("list-paused-apps").splitlines() - ] + x: App(x) for x in [x for x in launcherctl("list-paused-apps").splitlines()] } diff --git a/launcherctl/_launcher.py b/launcherctl/_launcher.py index d12ad4f..93b315a 100644 --- a/launcherctl/_launcher.py +++ b/launcherctl/_launcher.py @@ -1,30 +1,28 @@ import subprocess +from collections.abc import Callable -from typing import Callable - -from ._app import App from ._util import launcherctl class Launcher: - def __init__(self, name: str): - self.name = name + def __init__(self, name: str) -> None: + self.name: str = name - def logs(self, onlogline: Callable[[str], None] = None) -> list[str] | None: - pass + def logs(self, _onlogline: Callable[[str], None] | None = None) -> list[str] | None: + raise NotImplementedError() - def start(self): - launcherctl("start-launcher", self.name) + def start(self) -> None: + _ = launcherctl("start-launcher", self.name) - def stop(self): - launcherctl("stop-launcher", self.name) + def stop(self) -> None: + _ = launcherctl("stop-launcher", self.name) - def enable(self, start: bool = False): + def enable(self, start: bool = False) -> None: if start: - launcherctl("switch-launcher", "--start", self.name) + _ = launcherctl("switch-launcher", "--start", self.name) else: - launcherctl("switch-launcher", self.name) + _ = launcherctl("switch-launcher", self.name) @property def is_current(self) -> bool: @@ -50,23 +48,23 @@ def is_active(self) -> bool: class API: def keys(self) -> list[str]: - return [x.decode("utf-8") for x in launcherctl("list-launchers").splitlines()] + return [x for x in launcherctl("list-launchers").splitlines()] def __contains__(self, key: str) -> bool: return key in self.keys() - def __getitem__(self, key: str) -> App: + def __getitem__(self, key: str) -> Launcher: if key not in self: raise KeyError() return Launcher(key) @property - def current(self): - return Launcher(launcherctl("status").splitlines()[0][14:-4].decode("utf-8")) + def current(self) -> Launcher: + return Launcher(launcherctl("status").splitlines()[0][14:-4]) - def switch(launcher: Launcher | str, start: bool = False): - if not isinstance(Launcher, launcher): + def switch(self, launcher: Launcher | str, start: bool = False) -> None: + if not isinstance(launcher, Launcher): launcher = Launcher(launcher) launcher.enable(start) diff --git a/launcherctl/_util.py b/launcherctl/_util.py index d2e8a00..d2d03d8 100644 --- a/launcherctl/_util.py +++ b/launcherctl/_util.py @@ -1,13 +1,13 @@ import subprocess -def LauncherCtlException(Exception): +class LauncherCtlException(Exception): pass -def launcherctl(*args) -> str | None: +def launcherctl(*args: str) -> str: try: - return subprocess.check_output(["/opt/bin/launcherctl"] + list(args)) + return subprocess.check_output(["/opt/bin/launcherctl", *args], text=True) + except subprocess.CalledProcessError as e: - stdout = e.output.decode("utf-8") - raise LauncherCtlException(stdout) from e + raise LauncherCtlException(f"{e.stdout}\n{e.stderr}") from e # pyright: ignore[reportAny] diff --git a/pyproject.toml b/pyproject.toml index cc46bd7..958e0cc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -2,7 +2,8 @@ name = "launcherctl" description = "Python wrapper around launcherctl." readme = "README.md" -version = "1.0.1" +license = "MIT" +version = "1.0.2" requires-python = ">= 3.11" authors = [ {name = "Nathaniel van Diepen", email = "eeems@eeems.email"}, @@ -13,16 +14,50 @@ maintainers = [ classifiers = [ "Intended Audience :: Developers", "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ] +[project.optional-dependencies] +test = ["pytest"] + [project.urls] Homepage = "https://github.com/Eeems-Org/python-launcherctl" Repository = "https://github.com/Eeems-Org/python-launcherctl.git" Issues = "https://github.com/Eeems-Org/python-launcherctl/issues" [build-system] -requires = ["setuptools >= 65.0"] -build-backend = "setuptools.build_meta" +requires = ["setuptools>=70.1", "nuitka>=4.0.6"] +build-backend = "nuitka.distutils.Build" + +[tool.ruff] +exclude = [".venv", "build"] + +[tool.ruff.lint] +extend-select = [ + "UP", + "PL", + "ANN", + "S", +] +ignore = [ + "PLW0603", + "PLR2004", + "PLR0915", + "PLR0912", + "PLR0911", + "PLR6301", + "PLR0913", + "S101", + "S404", + "S603", + "S607", + "ANN401", + "ANN001", + "ANN003", +] + +[tool.pyright] +exclude = [".venv", "build"] From 76bbb1b40453e8fa9d1be850f78cd719892291c8 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:37:10 -0600 Subject: [PATCH 2/9] Fix branch reference --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 80a4a8a..5e0665d 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -3,7 +3,7 @@ name: Check and Build on: push: branches: - - master + - main pull_request: workflow_dispatch: release: From 815ff685b0a31dab006d08bb0fd9cadf2ab50531 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:39:29 -0600 Subject: [PATCH 3/9] Remove nonexistant needs --- .github/workflows/build.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5e0665d..0fa4bf4 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -40,7 +40,6 @@ jobs: name: Build sdist needs: &build-needs - lint - - test runs-on: ubuntu-latest steps: - name: Checkout the Git repository @@ -93,7 +92,7 @@ jobs: - musl exclude: - arch: i686 # segfaults currently - python: 3.14 + python: "3.14" libc: glibc steps: - name: Checkout the Git repository From ef227991c7bcfc563969f5a371bd9d1982c9f7a5 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:43:55 -0600 Subject: [PATCH 4/9] Review feedback --- launcherctl/_app.py | 11 +++-------- launcherctl/_launcher.py | 2 +- launcherctl/_util.py | 10 ++++++---- pyproject.toml | 2 +- 4 files changed, 11 insertions(+), 14 deletions(-) diff --git a/launcherctl/_app.py b/launcherctl/_app.py index 3be4eae..fd8fb16 100644 --- a/launcherctl/_app.py +++ b/launcherctl/_app.py @@ -28,7 +28,7 @@ def resume(self) -> None: class API: def keys(self) -> list[str]: - return [x for x in launcherctl("list-apps").splitlines()] + return launcherctl("list-apps").splitlines() def __contains__(self, key: str) -> bool: return key in self.keys() @@ -41,16 +41,11 @@ def __getitem__(self, key: str) -> App: @property def running(self) -> dict[str, App]: - return { - x: App(x) - for x in [x for x in launcherctl("list-running-apps").splitlines()] - } + return {x: App(x) for x in launcherctl("list-running-apps").splitlines()} @property def paused(self) -> dict[str, App]: - return { - x: App(x) for x in [x for x in launcherctl("list-paused-apps").splitlines()] - } + return {x: App(x) for x in launcherctl("list-paused-apps").splitlines()} api = API() diff --git a/launcherctl/_launcher.py b/launcherctl/_launcher.py index 93b315a..b44e167 100644 --- a/launcherctl/_launcher.py +++ b/launcherctl/_launcher.py @@ -48,7 +48,7 @@ def is_active(self) -> bool: class API: def keys(self) -> list[str]: - return [x for x in launcherctl("list-launchers").splitlines()] + return launcherctl("list-launchers").splitlines() def __contains__(self, key: str) -> bool: return key in self.keys() diff --git a/launcherctl/_util.py b/launcherctl/_util.py index d2d03d8..a86a2f4 100644 --- a/launcherctl/_util.py +++ b/launcherctl/_util.py @@ -6,8 +6,10 @@ class LauncherCtlException(Exception): def launcherctl(*args: str) -> str: - try: - return subprocess.check_output(["/opt/bin/launcherctl", *args], text=True) + proc = subprocess.run( + ["/opt/bin/launcherctl", *args], text=True, check=False, capture_output=True + ) + if not proc.returncode: + return proc.stdout - except subprocess.CalledProcessError as e: - raise LauncherCtlException(f"{e.stdout}\n{e.stderr}") from e # pyright: ignore[reportAny] + raise LauncherCtlException(f"{proc.stdout}\n{proc.stderr}") diff --git a/pyproject.toml b/pyproject.toml index 958e0cc..8d29664 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -29,7 +29,7 @@ Repository = "https://github.com/Eeems-Org/python-launcherctl.git" Issues = "https://github.com/Eeems-Org/python-launcherctl/issues" [build-system] -requires = ["setuptools>=70.1", "nuitka>=4.0.6"] +requires = ["setuptools>=77.0", "nuitka>=4.0.6"] build-backend = "nuitka.distutils.Build" [tool.ruff] From 8a1f5fddf4f146d14323c6f7a2491b24ff998624 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:46:35 -0600 Subject: [PATCH 5/9] Remove unneeded from Makefile --- Makefile | 11 ----------- 1 file changed, 11 deletions(-) diff --git a/Makefile b/Makefile index 9fded10..f710c6c 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,6 @@ pip install --force-reinstall /tmp/launcherctl-${VERSION}-py3-none-any.whl endef export SCRIPT -ifeq ($(VENV_BIN_ACTIVATE),) -VENV_BIN_ACTIVATE := .venv/bin/activate -endif - - dist/launcherctl-${VERSION}.tar.gz: $(shell find launcherctl -type f) python -m build --sdist @@ -40,10 +35,4 @@ test: install | ssh root@10.11.99.1 \ "bash -ec 'PATH=${PATH} /opt/bin/python -u'" -$(VENV_BIN_ACTIVATE): - @echo "Setting up development virtual env in .venv" - python -m venv .venv - . $(VENV_BIN_ACTIVATE); \ - python -m pip install ruff - .PHONY: clean install test deploy From c20dd0bf361c2ae9204c3c54689c3b5784fdbc86 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 13:47:32 -0600 Subject: [PATCH 6/9] Use emake for wheel in makefile --- Makefile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Makefile b/Makefile index f710c6c..691097d 100644 --- a/Makefile +++ b/Makefile @@ -15,11 +15,8 @@ pip install --force-reinstall /tmp/launcherctl-${VERSION}-py3-none-any.whl endef export SCRIPT -dist/launcherctl-${VERSION}.tar.gz: $(shell find launcherctl -type f) - python -m build --sdist - dist/launcherctl-${VERSION}-py3-none-any.whl: $(shell find launcherctl -type f) - python -m build --wheel + emake build --wheel clean: git clean --force -dX From d7fc045735e3502ee65854890a259e2d2a0f32bf Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 14:26:26 -0600 Subject: [PATCH 7/9] Only build for target versions --- .github/workflows/build.yml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0fa4bf4..ff5743f 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -80,20 +80,10 @@ jobs: matrix: python: *python-versions arch: - - x86_64 - - i686 - - ppc64le - aarch64 - armv7l - - riscv64 - - s390x libc: - glibc - - musl - exclude: - - arch: i686 # segfaults currently - python: "3.14" - libc: glibc steps: - name: Checkout the Git repository uses: actions/checkout@v6 @@ -105,13 +95,6 @@ jobs: --arch ${{ matrix.arch }} \ --libc ${{ matrix.libc }} \ --python ${{ matrix.python }} - - name: Testing wheel - run: | - emake test \ - --wheel \ - --arch ${{ matrix.arch }} \ - --libc ${{ matrix.libc }} \ - --python ${{ matrix.python }} - uses: actions/upload-artifact@v6 with: name: pip-wheel-${{ matrix.python }}-${{ matrix.arch }}-${{ matrix.libc }} From 4f1fdab26cb8761017f4f257170edf69c4cd87c9 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 15:16:41 -0600 Subject: [PATCH 8/9] Remove test since there are no tests --- .github/workflows/build.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index ff5743f..25530ae 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -63,8 +63,6 @@ jobs: - *install-emake - name: Building wheel run: emake build --wheel - - name: Test wheel - run: emake test --wheel - uses: actions/upload-artifact@v6 with: name: pip-wheel-none-any From 83d680c0c63db685a1e687d9cac0fb35e854e290 Mon Sep 17 00:00:00 2001 From: Nathaniel van Diepen Date: Fri, 24 Apr 2026 15:39:55 -0600 Subject: [PATCH 9/9] Reduce required permisions --- .github/workflows/build.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 25530ae..4040fef 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -109,7 +109,6 @@ jobs: runs-on: ubuntu-latest permissions: id-token: write - contents: write environment: name: pypi url: https://pypi.org/p/launcherctl