From 9f526facf471a30afc6369f6d70b3976c772f3cd Mon Sep 17 00:00:00 2001 From: Patrick Lenihan Date: Mon, 3 Feb 2025 10:21:36 +0000 Subject: [PATCH 1/2] Added script to copy rust binaries to the Python scripts directory (venv/bin) --- Makefile | 2 ++ scripts/copy_rust_binaries.py | 63 +++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+) create mode 100644 scripts/copy_rust_binaries.py diff --git a/Makefile b/Makefile index 32ee5881..f1af0d0b 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ all: build build: aw-server aw-sync + python scripts/copy_rust_binaries.py target/$(targetdir) DESTDIR := ifeq ($(SUDO_USER),) @@ -107,4 +108,5 @@ install: install -m 644 aw-server.service $(DESTDIR)$(PREFIX)/lib/systemd/user/aw-server.service clean: + python scripts/copy_rust_binaries.py --clean target/$(targetdir) cargo clean diff --git a/scripts/copy_rust_binaries.py b/scripts/copy_rust_binaries.py new file mode 100644 index 00000000..cf81efeb --- /dev/null +++ b/scripts/copy_rust_binaries.py @@ -0,0 +1,63 @@ +import argparse +import sysconfig +import shutil +import os +import sys + +def add_rust_prefix(p): + base, ext = os.path.splitext(p) + return f"{base}-rust{ext}" + +def is_executable(p): + """ Cross-platform check for executable files. """ + if os.path.isfile(p) and os.access(p, os.X_OK): + return True + return os.name == "nt" and p.lower().endswith((".exe", ".bat", ".cmd", ".com")) + +def build(target_dir, python_bin_dir): + + if not os.path.exists(target_dir): + print(f"Error: {target_dir} does not exist. Did you run `cargo build --release`?", file=sys.stderr) + sys.exit(1) + + os.makedirs(python_bin_dir, exist_ok=True) + + for file_name in os.listdir(target_dir): + src_file = os.path.join(target_dir, file_name) + dst_file = add_rust_prefix(os.path.join(python_bin_dir, file_name)) + + if is_executable(src_file): + shutil.copy(src_file, dst_file) + +def clean(target_dir, python_bin_dir): + if not os.path.exists(python_bin_dir) or not os.path.exists(target_dir): + return + + for file_name in os.listdir(target_dir): + dst_file = add_rust_prefix(os.path.join(python_bin_dir, file_name)) + if is_executable(dst_file): + os.remove(dst_file) + +def main(args): + + python_bin_dir = sysconfig.get_path("scripts") + + if not python_bin_dir: + python_bin_dir = os.path.dirname(sys.executable) + + if args.clean: + clean(args.target_dir, python_bin_dir) + else: + build(args.target_dir, python_bin_dir) + +def parse_args(): + parser = argparse.ArgumentParser() + + parser.add_argument("--clean", action='store_true', default=False) + parser.add_argument("target_dir", type=str, default=os.path.join("target", "release"), nargs='?') + + return parser.parse_args() + +if __name__ == "__main__": + args = parse_args() + main(args) From 691a4ab250f147868ebae88254f9ad1dfd4d9f4a Mon Sep 17 00:00:00 2001 From: Patrick Lenihan Date: Mon, 3 Feb 2025 17:16:44 +0000 Subject: [PATCH 2/2] Switched to using Poetry script --- Makefile | 4 +-- python/aw_server_rust.py | 31 +++++++++++++++++ python/poetry.lock | 7 ++++ python/pyproject.toml | 18 ++++++++++ scripts/copy_rust_binaries.py | 63 ----------------------------------- 5 files changed, 58 insertions(+), 65 deletions(-) create mode 100644 python/aw_server_rust.py create mode 100644 python/poetry.lock create mode 100644 python/pyproject.toml delete mode 100644 scripts/copy_rust_binaries.py diff --git a/Makefile b/Makefile index f1af0d0b..e66478d7 100644 --- a/Makefile +++ b/Makefile @@ -2,7 +2,6 @@ all: build build: aw-server aw-sync - python scripts/copy_rust_binaries.py target/$(targetdir) DESTDIR := ifeq ($(SUDO_USER),) @@ -23,6 +22,7 @@ endif aw-server: set-version aw-webui cargo build $(cargoflag) --bin aw-server + poetry install --directory python aw-sync: set-version cargo build $(cargoflag) --bin aw-sync @@ -108,5 +108,5 @@ install: install -m 644 aw-server.service $(DESTDIR)$(PREFIX)/lib/systemd/user/aw-server.service clean: - python scripts/copy_rust_binaries.py --clean target/$(targetdir) + rm -rf __pycache__ python/__pycache__ cargo clean diff --git a/python/aw_server_rust.py b/python/aw_server_rust.py new file mode 100644 index 00000000..e5e7e0cc --- /dev/null +++ b/python/aw_server_rust.py @@ -0,0 +1,31 @@ +#!/usr/bin/env python3 + +import os +import sys +import subprocess + +def find_rust_binary(): + """Finds the Rust binary in the target directory.""" + + repo_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) + target = "debug" if os.getenv("RELEASE", "") == "false" else "release" + + rust_binary = os.path.join(repo_root, "target", target, "aw-server") + + if os.path.exists(rust_binary): + return rust_binary + + print(f"Error: Rust binary '{rust_binary}' not found. Did you run `cargo build --release`?", file=sys.stderr) + sys.exit(1) + +def main(): + """Executes the Rust binary and forwards all arguments.""" + rust_binary = find_rust_binary() + if os.name == "posix": + # Replace current Python process with rust binary + os.execvp(rust_binary, (rust_binary, *sys.argv[1:])) + else: + subprocess.run([rust_binary] + sys.argv[1:]) + +if __name__ == "__main__": + main() diff --git a/python/poetry.lock b/python/poetry.lock new file mode 100644 index 00000000..d196c8d4 --- /dev/null +++ b/python/poetry.lock @@ -0,0 +1,7 @@ +# This file is automatically @generated by Poetry 2.0.1 and should not be changed by hand. +package = [] + +[metadata] +lock-version = "2.1" +python-versions = "*" +content-hash = "1be3afc87beb00627d70ba9087279825e941f05a562df12e972061262422d79a" diff --git a/python/pyproject.toml b/python/pyproject.toml new file mode 100644 index 00000000..ca30a954 --- /dev/null +++ b/python/pyproject.toml @@ -0,0 +1,18 @@ +[tool.poetry] +name = "aw-server-rust" +version = "0.1.0" +description = "Server for ActivityWatch" +authors = ["Erik Bjäreholt "] +license = "MPL-2.0" +packages = [ + { include = "aw_server_rust.py" } +] + +[tool.poetry.scripts] +aw-server-rust = "aw_server_rust:main" + +[tool.poetry.dependencies] + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/scripts/copy_rust_binaries.py b/scripts/copy_rust_binaries.py deleted file mode 100644 index cf81efeb..00000000 --- a/scripts/copy_rust_binaries.py +++ /dev/null @@ -1,63 +0,0 @@ -import argparse -import sysconfig -import shutil -import os -import sys - -def add_rust_prefix(p): - base, ext = os.path.splitext(p) - return f"{base}-rust{ext}" - -def is_executable(p): - """ Cross-platform check for executable files. """ - if os.path.isfile(p) and os.access(p, os.X_OK): - return True - return os.name == "nt" and p.lower().endswith((".exe", ".bat", ".cmd", ".com")) - -def build(target_dir, python_bin_dir): - - if not os.path.exists(target_dir): - print(f"Error: {target_dir} does not exist. Did you run `cargo build --release`?", file=sys.stderr) - sys.exit(1) - - os.makedirs(python_bin_dir, exist_ok=True) - - for file_name in os.listdir(target_dir): - src_file = os.path.join(target_dir, file_name) - dst_file = add_rust_prefix(os.path.join(python_bin_dir, file_name)) - - if is_executable(src_file): - shutil.copy(src_file, dst_file) - -def clean(target_dir, python_bin_dir): - if not os.path.exists(python_bin_dir) or not os.path.exists(target_dir): - return - - for file_name in os.listdir(target_dir): - dst_file = add_rust_prefix(os.path.join(python_bin_dir, file_name)) - if is_executable(dst_file): - os.remove(dst_file) - -def main(args): - - python_bin_dir = sysconfig.get_path("scripts") - - if not python_bin_dir: - python_bin_dir = os.path.dirname(sys.executable) - - if args.clean: - clean(args.target_dir, python_bin_dir) - else: - build(args.target_dir, python_bin_dir) - -def parse_args(): - parser = argparse.ArgumentParser() - - parser.add_argument("--clean", action='store_true', default=False) - parser.add_argument("target_dir", type=str, default=os.path.join("target", "release"), nargs='?') - - return parser.parse_args() - -if __name__ == "__main__": - args = parse_args() - main(args)