@@ -10,6 +10,59 @@ from pathlib import Path
10
10
import aw_core
11
11
import flask_restx
12
12
13
+
14
+ def build_analysis (name , location , binaries = [], datas = [], hiddenimports = []):
15
+ name_py = name .replace ("-" , "_" )
16
+ location_candidates = [
17
+ location / f"{ name_py } /__main__.py" ,
18
+ location / f"src/{ name_py } /__main__.py" ,
19
+ ]
20
+ try :
21
+ location = next (p for p in location_candidates if p .exists ())
22
+ except StopIteration :
23
+ raise Exception (f"Could not find { name } location from { location_candidates } " )
24
+
25
+ return Analysis (
26
+ [location ],
27
+ pathex = [],
28
+ binaries = binaries ,
29
+ datas = datas ,
30
+ hiddenimports = hiddenimports ,
31
+ hookspath = [],
32
+ runtime_hooks = [],
33
+ excludes = [],
34
+ win_no_prefer_redirects = False ,
35
+ win_private_assemblies = False ,
36
+ )
37
+
38
+
39
+ def build_collect (analysis , name , console = True ):
40
+ """Used to build the COLLECT statements for each module"""
41
+ pyz = PYZ (analysis .pure , analysis .zipped_data )
42
+ exe = EXE (
43
+ pyz ,
44
+ analysis .scripts ,
45
+ exclude_binaries = True ,
46
+ name = name ,
47
+ debug = False ,
48
+ strip = False ,
49
+ upx = True ,
50
+ console = console ,
51
+ entitlements_file = entitlements_file ,
52
+ codesign_identity = codesign_identity ,
53
+ )
54
+ return COLLECT (
55
+ exe ,
56
+ analysis .binaries ,
57
+ analysis .zipfiles ,
58
+ analysis .datas ,
59
+ strip = False ,
60
+ upx = True ,
61
+ name = name ,
62
+ )
63
+
64
+
65
+ # Get the current release version
13
66
current_release = subprocess .run (
14
67
shlex .split ("git describe --tags --abbrev=0" ),
15
68
stdout = subprocess .PIPE ,
@@ -18,6 +71,7 @@ current_release = subprocess.run(
18
71
).stdout .strip ()
19
72
print ("bundling activitywatch version " + current_release )
20
73
74
+ # Get entitlements and codesign identity
21
75
entitlements_file = Path ("." ) / "scripts" / "package" / "entitlements.plist"
22
76
codesign_identity = os .environ .get ("APPLE_PERSONALID" , "" ).strip ()
23
77
if not codesign_identity :
@@ -32,69 +86,42 @@ aw_server_rust_bin = aw_server_rust_location / "target/package/aw-server-rust"
32
86
aw_qt_location = Path ("aw-qt" )
33
87
awa_location = Path ("aw-watcher-afk" )
34
88
aww_location = Path ("aw-watcher-window" )
89
+ awi_location = Path ("aw-watcher-input" )
90
+ aw_notify_location = Path ("aw-notify" )
35
91
36
92
if platform .system () == "Darwin" :
37
93
icon = aw_qt_location / "media/logo/logo.icns"
38
94
else :
39
95
icon = aw_qt_location / "media/logo/logo.ico"
40
- block_cipher = None
41
-
42
- extra_pathex = []
43
- if platform .system () == "Windows" :
44
- # The Windows version includes paths to Qt binaries which are
45
- # not automatically found due to bug in PyInstaller 3.2.
46
- # See: https://github.com/pyinstaller/pyinstaller/issues/2152
47
- import PyQt5
48
-
49
- pyqt_path = os .path .dirname (PyQt5 .__file__ )
50
- extra_pathex .append (pyqt_path + "\\ Qt\\ bin" )
51
96
52
97
skip_rust = False
53
98
if not aw_server_rust_bin .exists ():
54
99
skip_rust = True
55
100
print ("Skipping Rust build because aw-server-rust binary not found." )
56
101
57
- aw_server_a = Analysis (
58
- ["aw-server/__main__.py" ],
59
- pathex = [],
60
- binaries = None ,
61
- datas = [
62
- (aws_location / "aw_server/static" , "aw_server/static" ),
63
- (restx_path / "templates" , "flask_restx/templates" ),
64
- (restx_path / "static" , "flask_restx/static" ),
65
- (aw_core_path / "schemas" , "aw_core/schemas" ),
66
- ],
67
- hiddenimports = [],
68
- hookspath = [],
69
- runtime_hooks = [],
70
- excludes = [],
71
- win_no_prefer_redirects = False ,
72
- win_private_assemblies = False ,
73
- cipher = block_cipher ,
74
- )
75
102
76
- aw_qt_a = Analysis (
77
- [ aw_qt_location / "aw_qt/__main__.py" ] ,
78
- pathex = [] + extra_pathex ,
103
+ aw_qt_a = build_analysis (
104
+ "aw-qt" ,
105
+ aw_qt_location ,
79
106
binaries = [(aw_server_rust_bin , "." )] if not skip_rust else [],
80
107
datas = [
81
108
(aw_qt_location / "resources/aw-qt.desktop" , "aw_qt/resources" ),
82
109
(aw_qt_location / "media" , "aw_qt/media" ),
83
110
],
84
- hiddenimports = [],
85
- hookspath = [],
86
- runtime_hooks = [],
87
- excludes = [],
88
- win_no_prefer_redirects = False ,
89
- win_private_assemblies = False ,
90
- cipher = block_cipher ,
91
111
)
92
-
93
- aw_watcher_afk_a = Analysis (
94
- [awa_location / "aw_watcher_afk/__main__.py" ],
95
- pathex = [],
96
- binaries = None ,
97
- datas = None ,
112
+ aw_server_a = build_analysis (
113
+ "aw-server" ,
114
+ aws_location ,
115
+ datas = [
116
+ (aws_location / "aw_server/static" , "aw_server/static" ),
117
+ (restx_path / "templates" , "flask_restx/templates" ),
118
+ (restx_path / "static" , "flask_restx/static" ),
119
+ (aw_core_path / "schemas" , "aw_core/schemas" ),
120
+ ],
121
+ )
122
+ aw_watcher_afk_a = build_analysis (
123
+ "aw_watcher_afk" ,
124
+ awa_location ,
98
125
hiddenimports = [
99
126
"Xlib.keysymdef.miscellany" ,
100
127
"Xlib.keysymdef.latin1" ,
@@ -117,17 +144,11 @@ aw_watcher_afk_a = Analysis(
117
144
"pynput.keyboard._darwin" ,
118
145
"pynput.mouse._darwin" ,
119
146
],
120
- hookspath = [],
121
- runtime_hooks = [],
122
- excludes = [],
123
- win_no_prefer_redirects = False ,
124
- win_private_assemblies = False ,
125
- cipher = block_cipher ,
126
147
)
127
-
128
- aw_watcher_window_a = Analysis (
129
- [ aww_location / "aw_watcher_window/__main__.py" ] ,
130
- pathex = [] ,
148
+ aw_watcher_input_a = build_analysis ( "aw_watcher_input" , awi_location )
149
+ aw_watcher_window_a = build_analysis (
150
+ "aw_watcher_window" ,
151
+ aww_location ,
131
152
binaries = [
132
153
(
133
154
aww_location / "aw_watcher_window/aw-watcher-window-macos" ,
@@ -139,13 +160,9 @@ aw_watcher_window_a = Analysis(
139
160
datas = [
140
161
(aww_location / "aw_watcher_window/printAppStatus.jxa" , "aw_watcher_window" )
141
162
],
142
- hiddenimports = [],
143
- hookspath = [],
144
- runtime_hooks = [],
145
- excludes = [],
146
- win_no_prefer_redirects = False ,
147
- win_private_assemblies = False ,
148
- cipher = block_cipher ,
163
+ )
164
+ aw_notify_a = build_analysis (
165
+ "aw_notify" , aw_notify_location , hiddenimports = ["desktop_notifier.resources" ]
149
166
)
150
167
151
168
# https://pythonhosted.org/PyInstaller/spec-files.html#multipackage-bundles
@@ -156,110 +173,40 @@ MERGE(
156
173
(aw_qt_a , "aw-qt" , "aw-qt" ),
157
174
(aw_watcher_afk_a , "aw-watcher-afk" , "aw-watcher-afk" ),
158
175
(aw_watcher_window_a , "aw-watcher-window" , "aw-watcher-window" ),
176
+ (aw_watcher_input_a , "aw-watcher-input" , "aw-watcher-input" ),
177
+ (aw_notify_a , "aw-notify" , "aw-notify" ),
159
178
)
160
179
161
- aww_pyz = PYZ (
162
- aw_watcher_window_a .pure , aw_watcher_window_a .zipped_data , cipher = block_cipher
163
- )
164
- aww_exe = EXE (
165
- aww_pyz ,
166
- aw_watcher_window_a .scripts ,
167
- exclude_binaries = True ,
168
- name = "aw-watcher-window" ,
169
- debug = False ,
170
- strip = False ,
171
- upx = True ,
172
- console = True ,
173
- entitlements_file = entitlements_file ,
174
- codesign_identity = codesign_identity ,
175
- )
176
- aww_coll = COLLECT (
177
- aww_exe ,
178
- aw_watcher_window_a .binaries ,
179
- aw_watcher_window_a .zipfiles ,
180
- aw_watcher_window_a .datas ,
181
- strip = False ,
182
- upx = True ,
183
- name = "aw-watcher-window" ,
184
- )
185
180
186
- awa_pyz = PYZ (aw_watcher_afk_a .pure , aw_watcher_afk_a .zipped_data , cipher = block_cipher )
187
- awa_exe = EXE (
188
- awa_pyz ,
189
- aw_watcher_afk_a .scripts ,
190
- exclude_binaries = True ,
191
- name = "aw-watcher-afk" ,
192
- debug = False ,
193
- strip = False ,
194
- upx = True ,
195
- console = True ,
196
- entitlements_file = entitlements_file ,
197
- codesign_identity = codesign_identity ,
198
- )
199
- awa_coll = COLLECT (
200
- awa_exe ,
201
- aw_watcher_afk_a .binaries ,
202
- aw_watcher_afk_a .zipfiles ,
203
- aw_watcher_afk_a .datas ,
204
- strip = False ,
205
- upx = True ,
206
- name = "aw-watcher-afk" ,
207
- )
181
+ # aw-server
182
+ aws_coll = build_collect (aw_server_a , "aw-server" )
208
183
209
- aws_pyz = PYZ (aw_server_a .pure , aw_server_a .zipped_data , cipher = block_cipher )
184
+ # aw-watcher-window
185
+ aww_coll = build_collect (aw_watcher_window_a , "aw-watcher-window" )
210
186
211
- aws_exe = EXE (
212
- aws_pyz ,
213
- aw_server_a .scripts ,
214
- exclude_binaries = True ,
215
- name = "aw-server" ,
216
- debug = False ,
217
- strip = False ,
218
- upx = True ,
219
- console = True ,
220
- entitlements_file = entitlements_file ,
221
- codesign_identity = codesign_identity ,
222
- )
223
- aws_coll = COLLECT (
224
- aws_exe ,
225
- aw_server_a .binaries ,
226
- aw_server_a .zipfiles ,
227
- aw_server_a .datas ,
228
- strip = False ,
229
- upx = True ,
230
- name = "aw-server" ,
231
- )
187
+ # aw-watcher-afk
188
+ awa_coll = build_collect (aw_watcher_afk_a , "aw-watcher-afk" )
232
189
233
- awq_pyz = PYZ (aw_qt_a .pure , aw_qt_a .zipped_data , cipher = block_cipher )
234
- awq_exe = EXE (
235
- awq_pyz ,
236
- aw_qt_a .scripts ,
237
- exclude_binaries = True ,
238
- name = "aw-qt" ,
239
- debug = True ,
240
- strip = False ,
241
- upx = True ,
242
- icon = icon ,
190
+ # aw-qt
191
+ awq_coll = build_collect (
192
+ aw_qt_a ,
193
+ "aw-qt" ,
243
194
console = False if platform .system () == "Windows" else True ,
244
- entitlements_file = entitlements_file ,
245
- codesign_identity = codesign_identity ,
246
- )
247
- awq_coll = COLLECT (
248
- awq_exe ,
249
- aw_qt_a .binaries ,
250
- aw_qt_a .zipfiles ,
251
- aw_qt_a .datas ,
252
- strip = False ,
253
- upx = True ,
254
- name = "aw-qt" ,
255
195
)
256
196
197
+ # aw-watcher-input
198
+ awi_coll = build_collect (aw_watcher_input_a , "aw-watcher-input" )
199
+
200
+ aw_notify_coll = build_collect (aw_notify_a , "aw-notify" )
201
+
257
202
if platform .system () == "Darwin" :
258
203
app = BUNDLE (
259
204
awq_coll ,
205
+ aws_coll ,
260
206
aww_coll ,
261
207
awa_coll ,
262
- aws_coll ,
208
+ awi_coll ,
209
+ aw_notify_coll ,
263
210
name = "ActivityWatch.app" ,
264
211
icon = icon ,
265
212
bundle_identifier = "net.activitywatch.ActivityWatch" ,
0 commit comments