-
Notifications
You must be signed in to change notification settings - Fork 51
Expand file tree
/
Copy pathsetup_app.py
More file actions
171 lines (154 loc) · 5.68 KB
/
Copy pathsetup_app.py
File metadata and controls
171 lines (154 loc) · 5.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright (C) 2026 lollapalooza <https://github.com/aqua5230>
#
# Part of "usage". Free software licensed under the GNU Affero General Public
# License v3.0 only; see the LICENSE file for full terms and the warranty disclaimer.
from __future__ import annotations
import importlib
import tomllib
from pathlib import Path
from typing import Any, cast
from setuptools import setup # type: ignore[import-untyped]
from setuptools.dist import Distribution # type: ignore[import-untyped]
APP = ["main.py"]
def _version() -> str:
pyproject = Path(__file__).with_name("pyproject.toml")
with pyproject.open("rb") as file:
data = tomllib.load(file)
return str(data["project"]["version"])
class Py2AppDistribution(Distribution): # type: ignore[misc]
def __init__(self, attrs: dict[str, object] | None = None) -> None:
super().__init__(attrs)
self.install_requires: list[str] = []
def finalize_options(self) -> None:
super().finalize_options()
self.install_requires = []
def _py2app_command() -> type[Any]:
py2app_module: Any = importlib.import_module("py2app.build_app")
py2app_base = py2app_module.py2app
class Py2AppCommand(py2app_base): # type: ignore[misc, valid-type]
def finalize_options(self) -> None:
self.distribution.install_requires = []
super().finalize_options()
return Py2AppCommand
if __name__ == "__main__":
version = _version()
OPTIONS = {
"argv_emulation": False,
"iconfile": "assets/usage.icns",
"resources": [
"assets/claude.webp",
"assets/claude_color_menubar.png",
"assets/codex.webp",
"assets/codex_color_menubar.png",
"assets/agy_color_menubar.png",
"assets/claude_mono_menubar.png",
"assets/codex_mono_menubar.png",
"assets/agy_mono_menubar.png",
"assets/grok_mono_menubar.png",
"assets/critters",
"assets/panels",
"assets/windows",
"i18n.json",
"pyproject.toml",
"usage_statusline.py",
"usage_statusline_agy.py",
"usage_statusline_grok.py",
"usage_statusline_forwarder.py",
"usage_session_resume.py",
"usage_terse_mode.py",
"usage_terse_reminder.py",
],
"includes": [
"AppKit",
"Foundation",
"Quartz",
"WebKit",
"UserNotifications",
"objc",
"usage_notifications",
"usage_client",
"pricing",
"i18n",
"usage_cli",
"talent_market_bridge",
# discussion.window is lazy-imported inside menubar.toggleDiscussion_,
# so py2app's dependency graph can't reach it (or the three core
# modules it pulls in) from main.py — list them explicitly.
"rich",
"rich.align",
"rich.console",
"rich.live",
"rich.panel",
"rich.style",
"rich.table",
"rich.text",
],
"packages": [
"WebKit",
"UserNotifications",
# Unzipped to Resources/lib/python3.13/ instead of python313.zip so the
# report pipeline never loads through zipimport — that is where a
# corrupted local file header surfaces as "bad local file header".
"adapters",
"analyzer",
"discussion",
"installer",
"loaders",
"menubar",
"quota",
"tui",
"ui",
"updates",
"usage_common",
"wintray",
],
"excludes": [
# Test suites and build tooling that py2app's module graph drags in
# but the shipped app never runs. Keeping them out shrinks
# python313.zip (was ~1665 entries: 426 CPython test, 69 _pytest)
# and cuts the surface for a corrupt zip entry.
"test",
"tests",
"pytest",
"_pytest",
"py2app",
"modulegraph",
"altgraph",
"macholib",
"pip",
"wheel",
"mypy",
"ruff",
],
"plist": {
"CFBundleIdentifier": "com.lollapalooza.usage",
"CFBundleName": "usage",
"CFBundleDisplayName": "usage",
"CFBundleShortVersionString": version,
"CFBundleVersion": version,
"LSUIElement": True,
"LSMinimumSystemVersion": "12.0",
"NSHumanReadableCopyright": (
"Copyright © 2025-2026 lollapalooza. Licensed under AGPL-3.0-only."
),
},
}
# Conditionally bundle the compiled instate-cli (built from the separate
# ~/Developer/instate project) so the talent-market panel works in the
# shipped .app. Appended at runtime — not in the literal list above — so the
# packaged-resources test (which parses the literal) stays clean on machines
# where vendor/instate-cli does not exist. py2app flattens a single-file
# resource to Resources/<basename> (Resources/instate-cli), which the bridge
# finds via NSBundle.pathForResource:ofType:.
_instate_cli = Path(__file__).with_name("vendor") / "instate-cli"
if _instate_cli.exists():
cast(list[Any], OPTIONS["resources"]).append("vendor/instate-cli")
setup(
app=APP,
cmdclass={"py2app": _py2app_command()},
distclass=Py2AppDistribution,
options={"py2app": OPTIONS},
setup_requires=["py2app"],
install_requires=[],
)