Some checks failed
Research feed publisher tests / test (push) Failing after 3s
Release / Pre-flight checks (push) Failing after 0s
Release / Research Edition — patch and packaging smoke tests (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been skipped
Release / Build Qt artifacts (false, 22, ubuntu-22.04, 3.9, false, false, false) (push) Has been skipped
Release / Build Qt artifacts (manylinux_2_28 — glibc 2.28 ABI floor) (22, 3.9) (push) Has been skipped
Release / Generate release notes (push) Has been skipped
Release / Create dev release tag (push) Has been skipped
CodeQL / Analyze (javascript) (push) Failing after 1s
CodeQL / Analyze (python) (push) Failing after 1s
Release / Build Qt artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, ${{ github.event_name == 'pull_request' || (github.event_name == 'push' && !startsWith(github.ref, 'refs/tags/')) }}, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, ubuntu-24.04-arm, 3.9, false, false, false) (push) Has been cancelled
Release / Build Tauri artifacts (false, 22, windows-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-15-intel, 3.9, false, false, false) (push) Has been cancelled
Release / Build Qt artifacts (false, 22, macos-latest, 3.9, false, false, false) (push) Has been cancelled
Release / Publish draft release (push) Has been cancelled
256 lines
7.8 KiB
Python
256 lines
7.8 KiB
Python
# -*- mode: python -*-
|
|
# vi: set ft=python :
|
|
|
|
import os
|
|
import platform
|
|
import shlex
|
|
import subprocess
|
|
from pathlib import Path
|
|
|
|
import aw_core
|
|
import flask_restx
|
|
|
|
|
|
def build_analysis(name, location, binaries=[], datas=[], hiddenimports=[]):
|
|
name_py = name.replace("-", "_")
|
|
location_candidates = [
|
|
location / f"{name_py}/__main__.py",
|
|
location / f"src/{name_py}/__main__.py",
|
|
]
|
|
try:
|
|
location = next(p for p in location_candidates if p.exists())
|
|
except StopIteration:
|
|
raise Exception(f"Could not find {name} location from {location_candidates}")
|
|
|
|
return Analysis(
|
|
[location],
|
|
pathex=[],
|
|
binaries=binaries,
|
|
datas=datas,
|
|
hiddenimports=hiddenimports,
|
|
hookspath=[],
|
|
runtime_hooks=[],
|
|
excludes=[],
|
|
win_no_prefer_redirects=False,
|
|
win_private_assemblies=False,
|
|
)
|
|
|
|
|
|
def build_collect(analysis, name, console=True, entitlements=None):
|
|
"""Used to build the COLLECT statements for each module"""
|
|
pyz = PYZ(analysis.pure, analysis.zipped_data)
|
|
exe = EXE(
|
|
pyz,
|
|
analysis.scripts,
|
|
exclude_binaries=True,
|
|
name=name,
|
|
debug=False,
|
|
strip=False,
|
|
upx=True,
|
|
console=console,
|
|
contents_directory=".",
|
|
entitlements_file=entitlements or entitlements_file,
|
|
codesign_identity=codesign_identity,
|
|
)
|
|
return COLLECT(
|
|
exe,
|
|
analysis.binaries,
|
|
analysis.zipfiles,
|
|
analysis.datas,
|
|
strip=False,
|
|
upx=True,
|
|
name=name,
|
|
)
|
|
|
|
|
|
# Get the current release version
|
|
current_release = subprocess.run(
|
|
shlex.split("git describe --tags --abbrev=0"),
|
|
stdout=subprocess.PIPE,
|
|
stderr=subprocess.STDOUT,
|
|
encoding="utf8",
|
|
).stdout.strip()
|
|
print("bundling activitywatch version " + current_release)
|
|
|
|
# Get entitlements and codesign identity
|
|
entitlements_file = Path(".") / "scripts" / "package" / "entitlements.plist"
|
|
app_entitlements_file = Path(".") / "scripts" / "package" / "app-entitlements.plist"
|
|
codesign_identity = os.environ.get("APPLE_PERSONALID", "").strip()
|
|
if not codesign_identity:
|
|
print("Environment variable APPLE_PERSONALID not set. Releases won't be signed.")
|
|
|
|
aw_core_path = Path(os.path.dirname(aw_core.__file__))
|
|
restx_path = Path(os.path.dirname(flask_restx.__file__))
|
|
|
|
aws_location = Path("aw-server")
|
|
aw_server_rust_location = Path("aw-server-rust")
|
|
aw_server_rust_bin = aw_server_rust_location / "target/package/aw-server-rust"
|
|
aw_sync_bin = aw_server_rust_location / "target/package/aw-sync"
|
|
aw_qt_location = Path("aw-qt")
|
|
awa_location = Path("aw-watcher-afk")
|
|
aww_location = Path("aw-watcher-window")
|
|
awi_location = Path("aw-watcher-input")
|
|
aw_notify_location = Path("aw-notify")
|
|
|
|
if platform.system() == "Darwin":
|
|
from PyInstaller.utils import osx as osxutils
|
|
|
|
icon = aw_qt_location / "media/logo/logo.icns"
|
|
else:
|
|
icon = aw_qt_location / "media/logo/logo.ico"
|
|
|
|
skip_rust = False
|
|
if not aw_server_rust_bin.exists():
|
|
skip_rust = True
|
|
print("Skipping Rust build because aw-server-rust binary not found.")
|
|
|
|
|
|
aw_qt_a = build_analysis(
|
|
"aw-qt",
|
|
aw_qt_location,
|
|
binaries=[(aw_server_rust_bin, "."), (aw_sync_bin, ".")] if not skip_rust else [],
|
|
datas=[
|
|
(aw_qt_location / "resources/aw-qt.desktop", "aw_qt/resources"),
|
|
(aw_qt_location / "media", "aw_qt/media"),
|
|
],
|
|
)
|
|
aw_server_a = build_analysis(
|
|
"aw-server",
|
|
aws_location,
|
|
datas=[
|
|
(aws_location / "aw_server/static", "aw_server/static"),
|
|
(restx_path / "templates", "flask_restx/templates"),
|
|
(restx_path / "static", "flask_restx/static"),
|
|
(aw_core_path / "schemas", "aw_core/schemas"),
|
|
],
|
|
)
|
|
aw_watcher_afk_a = build_analysis(
|
|
"aw_watcher_afk",
|
|
awa_location,
|
|
hiddenimports=[
|
|
"Xlib.keysymdef.miscellany",
|
|
"Xlib.keysymdef.latin1",
|
|
"Xlib.keysymdef.latin2",
|
|
"Xlib.keysymdef.latin3",
|
|
"Xlib.keysymdef.latin4",
|
|
"Xlib.keysymdef.greek",
|
|
"Xlib.support.unix_connect",
|
|
"Xlib.ext.shape",
|
|
"Xlib.ext.xinerama",
|
|
"Xlib.ext.composite",
|
|
"Xlib.ext.randr",
|
|
"Xlib.ext.xfixes",
|
|
"Xlib.ext.security",
|
|
"Xlib.ext.xinput",
|
|
"pynput.keyboard._xorg",
|
|
"pynput.mouse._xorg",
|
|
"pynput.keyboard._win32",
|
|
"pynput.mouse._win32",
|
|
"pynput.keyboard._darwin",
|
|
"pynput.mouse._darwin",
|
|
],
|
|
)
|
|
aw_watcher_input_a = build_analysis("aw_watcher_input", awi_location)
|
|
aw_watcher_window_a = build_analysis(
|
|
"aw_watcher_window",
|
|
aww_location,
|
|
binaries=(
|
|
[
|
|
(
|
|
aww_location / "aw_watcher_window/aw-watcher-window-macos",
|
|
"aw_watcher_window",
|
|
)
|
|
]
|
|
if platform.system() == "Darwin"
|
|
else []
|
|
),
|
|
datas=[
|
|
(aww_location / "aw_watcher_window/printAppStatus.jxa", "aw_watcher_window")
|
|
],
|
|
)
|
|
# Check if aw-notify is a Python package
|
|
_notify_candidates = [
|
|
aw_notify_location / "aw_notify/__main__.py",
|
|
aw_notify_location / "src/aw_notify/__main__.py",
|
|
]
|
|
skip_aw_notify = not any(p.exists() for p in _notify_candidates)
|
|
if skip_aw_notify:
|
|
print("Skipping aw-notify Python packaging (Rust-based implementation detected)")
|
|
|
|
aw_notify_a = None if skip_aw_notify else build_analysis(
|
|
"aw_notify", aw_notify_location, hiddenimports=["desktop_notifier.resources"]
|
|
)
|
|
|
|
# https://pythonhosted.org/PyInstaller/spec-files.html#multipackage-bundles
|
|
# MERGE takes a bit weird arguments, it wants tuples which consists of
|
|
# the analysis paired with the script name and the bin name
|
|
merge_args = [
|
|
(aw_server_a, "aw-server", "aw-server"),
|
|
(aw_qt_a, "aw-qt", "aw-qt"),
|
|
(aw_watcher_afk_a, "aw-watcher-afk", "aw-watcher-afk"),
|
|
(aw_watcher_window_a, "aw-watcher-window", "aw-watcher-window"),
|
|
(aw_watcher_input_a, "aw-watcher-input", "aw-watcher-input"),
|
|
]
|
|
if aw_notify_a is not None:
|
|
merge_args.append((aw_notify_a, "aw-notify", "aw-notify"))
|
|
|
|
MERGE(*merge_args)
|
|
|
|
|
|
# aw-server
|
|
aws_coll = build_collect(aw_server_a, "aw-server")
|
|
|
|
# aw-watcher-window
|
|
aww_coll = build_collect(aw_watcher_window_a, "aw-watcher-window")
|
|
|
|
# aw-watcher-afk
|
|
awa_coll = build_collect(aw_watcher_afk_a, "aw-watcher-afk")
|
|
|
|
# aw-qt
|
|
awq_coll = build_collect(
|
|
aw_qt_a,
|
|
"aw-qt",
|
|
console=False if platform.system() == "Windows" else True,
|
|
entitlements=app_entitlements_file,
|
|
)
|
|
|
|
# aw-watcher-input
|
|
awi_coll = build_collect(aw_watcher_input_a, "aw-watcher-input")
|
|
|
|
# aw-notify (only if Python package exists)
|
|
aw_notify_coll = build_collect(aw_notify_a, "aw-notify") if aw_notify_a is not None else None
|
|
|
|
if platform.system() == "Darwin":
|
|
bundle_args = [
|
|
awq_coll,
|
|
aws_coll,
|
|
aww_coll,
|
|
awa_coll,
|
|
awi_coll,
|
|
]
|
|
if aw_notify_coll is not None:
|
|
bundle_args.append(aw_notify_coll)
|
|
|
|
app = BUNDLE(
|
|
*bundle_args,
|
|
name="ActivityWatch.app",
|
|
icon=icon,
|
|
bundle_identifier="net.activitywatch.ActivityWatch",
|
|
version=current_release.lstrip("v"),
|
|
info_plist={
|
|
"NSPrincipalClass": "NSApplication",
|
|
"CFBundleExecutable": "MacOS/aw-qt",
|
|
"CFBundleIconFile": "logo.icns",
|
|
"NSAppleEventsUsageDescription": "Please grant access to use Apple Events",
|
|
# This could be set to a more specific version string (including the commit id, for example)
|
|
"CFBundleVersion": current_release.lstrip("v"),
|
|
# Replaced by the 'version' kwarg above
|
|
# "CFBundleShortVersionString": current_release.lstrip('v'),
|
|
},
|
|
)
|
|
|
|
# BUNDLE inherits signing settings from the last COLLECT and deep-signs the
|
|
# assembled bundle with them. Re-sign the application layer so its responsible
|
|
# executable retains app-only capabilities without propagating them to helpers.
|
|
osxutils.sign_binary(app.name, codesign_identity, app_entitlements_file)
|