SparkleHelper 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- sparklehelper-0.1.0/LICENSE +21 -0
- sparklehelper-0.1.0/MANIFEST.in +19 -0
- sparklehelper-0.1.0/PKG-INFO +371 -0
- sparklehelper-0.1.0/README.md +332 -0
- sparklehelper-0.1.0/pyproject.toml +72 -0
- sparklehelper-0.1.0/scripts/sync_native_deps.py +395 -0
- sparklehelper-0.1.0/setup.cfg +4 -0
- sparklehelper-0.1.0/setup.py +160 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/PKG-INFO +371 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/SOURCES.txt +43 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/dependency_links.txt +1 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/entry_points.txt +5 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/requires.txt +18 -0
- sparklehelper-0.1.0/src/SparkleHelper.egg-info/top_level.txt +1 -0
- sparklehelper-0.1.0/src/sparklehelper/__init__.py +62 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/__init__.py +54 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_macos/__init__.py +45 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_macos/_backend.py +377 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_macos/_delegates.py +656 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_macos/_loading.py +202 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_macos/_runtime.py +263 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_windows/__init__.py +20 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_windows/_backend.py +335 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_windows/_bindings.py +181 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/_windows/_loading.py +218 -0
- sparklehelper-0.1.0/src/sparklehelper/_backend/base.py +330 -0
- sparklehelper-0.1.0/src/sparklehelper/_framework.py +956 -0
- sparklehelper-0.1.0/src/sparklehelper/_nuitka_plugin.py +139 -0
- sparklehelper-0.1.0/src/sparklehelper/_pyinstaller/__init__.py +265 -0
- sparklehelper-0.1.0/src/sparklehelper/_pyinstaller/hook-sparklehelper.py +124 -0
- sparklehelper-0.1.0/src/sparklehelper/errors.py +68 -0
- sparklehelper-0.1.0/src/sparklehelper/sparklehelper.nuitka-package.config.yml +31 -0
- sparklehelper-0.1.0/src/sparklehelper/types.py +265 -0
- sparklehelper-0.1.0/src/sparklehelper/updater.py +474 -0
- sparklehelper-0.1.0/src/sparklehelper/winsparkle/winsparkle.h +663 -0
- sparklehelper-0.1.0/tests/test_bridge.py +93 -0
- sparklehelper-0.1.0/tests/test_delegates.py +264 -0
- sparklehelper-0.1.0/tests/test_errors.py +43 -0
- sparklehelper-0.1.0/tests/test_framework.py +198 -0
- sparklehelper-0.1.0/tests/test_nuitka.py +30 -0
- sparklehelper-0.1.0/tests/test_objc.py +170 -0
- sparklehelper-0.1.0/tests/test_types.py +170 -0
- sparklehelper-0.1.0/tests/test_updater.py +539 -0
- sparklehelper-0.1.0/tests/test_updater_windows.py +136 -0
- sparklehelper-0.1.0/tests/test_windows.py +586 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LumenMarch
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Sparkle.framework 与 WinSparkle.dll 由 scripts/sync_native_deps.py 在 wheel
|
|
2
|
+
# 构建期生成到源码树;sdist 仅含源码,不应携带这些大体积 native 产物。
|
|
3
|
+
# (MANIFEST.in 只影响 sdist,不影响 wheel;wheel 由 package_data 决定。)
|
|
4
|
+
include LICENSE
|
|
5
|
+
prune src/sparklehelper/Sparkle.framework
|
|
6
|
+
prune src/sparklehelper/licenses
|
|
7
|
+
exclude src/sparklehelper/Sparkle.framework.symlinks.json
|
|
8
|
+
global-exclude WinSparkle.dll
|
|
9
|
+
|
|
10
|
+
# 上游源码 submodule 只用于仓库中的版本锁定与 GitHub 展示;sdist 仍保持轻量。
|
|
11
|
+
prune Sparkle
|
|
12
|
+
prune winsparkle
|
|
13
|
+
|
|
14
|
+
# sdist 必须携带同步脚本与小型源码资源,否则从 sdist 构建 wheel 会失败:
|
|
15
|
+
# - scripts/ 不是 Python 包,不会被自动收录,但 setup.py 构建期依赖它;
|
|
16
|
+
# - winsparkle.h 属于 Windows 的 package_data,在非 Windows 构建的
|
|
17
|
+
# sdist 中不会被自动收集,需显式包含以保证跨平台可构建。
|
|
18
|
+
recursive-include scripts *.py
|
|
19
|
+
include src/sparklehelper/winsparkle/winsparkle.h
|
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: SparkleHelper
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python runtime interface for Sparkle and WinSparkle app updates
|
|
5
|
+
Author: SparkleHelper Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/LumenMarch/SparkleHelper
|
|
8
|
+
Project-URL: Repository, https://github.com/LumenMarch/SparkleHelper
|
|
9
|
+
Keywords: sparkle,winsparkle,macos,windows,auto-update,pyobjc,software-update
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: MacOS X
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: MacOS :: MacOS X
|
|
14
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
21
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
22
|
+
Requires-Python: >=3.11
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
License-File: LICENSE
|
|
25
|
+
Requires-Dist: pyobjc-core>=10.0; sys_platform == "darwin"
|
|
26
|
+
Requires-Dist: pyobjc-framework-Cocoa>=10.0; sys_platform == "darwin"
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-cov>=4.0; extra == "dev"
|
|
30
|
+
Provides-Extra: demo
|
|
31
|
+
Requires-Dist: wxPython>=4.2; extra == "demo"
|
|
32
|
+
Requires-Dist: pyinstaller>=6.21.0; extra == "demo"
|
|
33
|
+
Requires-Dist: nuitka>=4.1.2; extra == "demo"
|
|
34
|
+
Provides-Extra: demo-qt
|
|
35
|
+
Requires-Dist: PySide6>=6.7; extra == "demo-qt"
|
|
36
|
+
Requires-Dist: pyinstaller>=6.21.0; extra == "demo-qt"
|
|
37
|
+
Requires-Dist: nuitka>=4.1.2; extra == "demo-qt"
|
|
38
|
+
Dynamic: license-file
|
|
39
|
+
|
|
40
|
+
# 1. SparkleHelper
|
|
41
|
+
|
|
42
|
+
[English](README.md) | [简体中文](README.zh-CN.md)
|
|
43
|
+
|
|
44
|
+
> A Python runtime interface for native app updates with
|
|
45
|
+
> [Sparkle](https://github.com/sparkle-project/Sparkle) on macOS and
|
|
46
|
+
> [WinSparkle](https://github.com/vslavik/winsparkle) on Windows.
|
|
47
|
+
|
|
48
|
+
SparkleHelper lets you integrate check-for-updates, background download, and
|
|
49
|
+
native update UI into packaged Python desktop apps — Sparkle for macOS `.app`
|
|
50
|
+
bundles and WinSparkle for Windows executables — without writing
|
|
51
|
+
Objective-C / Swift or Win32 update plumbing.
|
|
52
|
+
|
|
53
|
+
## 2. Why
|
|
54
|
+
|
|
55
|
+
[Sparkle](https://github.com/sparkle-project/Sparkle) and
|
|
56
|
+
[WinSparkle](https://github.com/vslavik/winsparkle) are mature native update
|
|
57
|
+
frameworks, but they expose platform-specific Objective-C and C APIs.
|
|
58
|
+
SparkleHelper gives Python desktop apps one runtime facade while preserving
|
|
59
|
+
the native updater on each platform. It solves three things:
|
|
60
|
+
|
|
61
|
+
1. **Runtime loading** — dynamically load the bundled `Sparkle.framework` on
|
|
62
|
+
macOS or `WinSparkle.dll` on Windows.
|
|
63
|
+
2. **Pythonic API** — expose a typed `Updater` facade over
|
|
64
|
+
`SPUStandardUpdaterController` / `SPUUpdater` and the WinSparkle C API.
|
|
65
|
+
3. **Offline packaging** — ship platform-native update runtimes inside wheels
|
|
66
|
+
and let supported bundlers collect them without network access.
|
|
67
|
+
|
|
68
|
+
## 3. Quick start
|
|
69
|
+
|
|
70
|
+
### 3.1. Install
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
# Install this repo in editable mode, including dev dependencies
|
|
74
|
+
uv pip install -e ".[dev]"
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### 3.2. Use in an app
|
|
78
|
+
|
|
79
|
+
On macOS, Sparkle must run inside a packaged `.app` bundle because it depends
|
|
80
|
+
on the bundle structure and `Info.plist`. On Windows, WinSparkle is configured
|
|
81
|
+
from `Updater(...)` before its native runtime is started.
|
|
82
|
+
|
|
83
|
+
macOS minimal usage:
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from sparklehelper import Updater
|
|
87
|
+
|
|
88
|
+
updater = Updater() # reads SUFeedURL from Info.plist
|
|
89
|
+
updater.check_for_updates() # pops the native Sparkle update window
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
Windows minimal usage:
|
|
93
|
+
|
|
94
|
+
```python
|
|
95
|
+
from sparklehelper import Updater
|
|
96
|
+
|
|
97
|
+
updater = Updater(
|
|
98
|
+
feed_url="https://example.com/appcast.xml",
|
|
99
|
+
public_key="...",
|
|
100
|
+
company="Example",
|
|
101
|
+
app_name="ExampleApp",
|
|
102
|
+
version="0.1.0",
|
|
103
|
+
)
|
|
104
|
+
updater.check_for_updates() # pops the native WinSparkle update window
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
On macOS, bind it to a GUI menu item and drive its enabled state via KVO:
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
updater = Updater()
|
|
111
|
+
|
|
112
|
+
with updater.observe_can_check_for_updates(menu_item.setEnabled):
|
|
113
|
+
# the menu item's state refreshes automatically while inside the block
|
|
114
|
+
...
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Other macOS KVO properties can be subscribed to through the unified entry point:
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
subscription = updater.observe(
|
|
121
|
+
"automatically_downloads_updates",
|
|
122
|
+
settings_view.set_auto_download_enabled,
|
|
123
|
+
)
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
macOS custom feed source and channel filtering (implement **any subset** of
|
|
127
|
+
`UpdaterDelegate`). On Windows, pass `feed_url` directly to `Updater(...)`:
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
class MyDelegate:
|
|
131
|
+
def feed_url_string_for_updater(self):
|
|
132
|
+
return "https://example.com/appcast.xml"
|
|
133
|
+
def allowed_channels_for_updater(self):
|
|
134
|
+
return ("beta",) if is_beta_user() else ()
|
|
135
|
+
|
|
136
|
+
updater = Updater(delegate=MyDelegate())
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
## 4. Platform configuration
|
|
140
|
+
|
|
141
|
+
### 4.1. macOS
|
|
142
|
+
|
|
143
|
+
Sparkle's core macOS configuration lives in the `.app` `Info.plist`:
|
|
144
|
+
|
|
145
|
+
| Key | Required | Description |
|
|
146
|
+
|---|---|---|
|
|
147
|
+
| `SUFeedURL` | Yes | Public URL of appcast.xml |
|
|
148
|
+
| `SUPublicEDKey` | Yes | EdDSA public key used to verify update signatures |
|
|
149
|
+
| `SUEnableAutomaticChecks` | No | Enable automatic checks (on by default) |
|
|
150
|
+
| `SUScheduledCheckInterval` | No | Check interval in seconds (default 86400) |
|
|
151
|
+
|
|
152
|
+
The Nuitka wrapper supports Sparkle's full host-app plist key set exposed by
|
|
153
|
+
Sparkle 2.x `SUConstants.h`; pass any of them with `--sparkle-key KEY=VALUE`
|
|
154
|
+
or the generated kebab-case option such as `--su-automatically-update true`.
|
|
155
|
+
|
|
156
|
+
### 4.2. Windows
|
|
157
|
+
|
|
158
|
+
WinSparkle must receive its init-time settings from Python before
|
|
159
|
+
`win_sparkle_init()`:
|
|
160
|
+
|
|
161
|
+
| `Updater(...)` argument | Required | Description |
|
|
162
|
+
|---|---|---|
|
|
163
|
+
| `feed_url` | Yes | Public URL of appcast.xml |
|
|
164
|
+
| `public_key` | Recommended | EdDSA public key used to verify update signatures |
|
|
165
|
+
| `company` / `app_name` / `version` | Recommended | App identity shown by WinSparkle and used for its settings |
|
|
166
|
+
| `build` | No | Build version used for update comparison |
|
|
167
|
+
|
|
168
|
+
## 5. Packaging
|
|
169
|
+
|
|
170
|
+
SparkleHelper ships first-class support for the two mainstream Python
|
|
171
|
+
bundlers. Wheels include the native runtime for their target platform:
|
|
172
|
+
`Sparkle.framework` for macOS and `WinSparkle.dll` for Windows, so packaging
|
|
173
|
+
never downloads native assets.
|
|
174
|
+
|
|
175
|
+
### 5.1. PyInstaller
|
|
176
|
+
|
|
177
|
+
SparkleHelper registers a built-in PyInstaller hook (via the
|
|
178
|
+
`pyinstaller40` entry point) that collects the platform-native updater at
|
|
179
|
+
build time. It uses the wheel copy by default; `SPARKLEHELPER_FRAMEWORK_PATH`
|
|
180
|
+
on macOS and `SPARKLEHELPER_WINSPARKLE_PATH` on Windows can override the source
|
|
181
|
+
for development or compatibility testing.
|
|
182
|
+
|
|
183
|
+
```bash
|
|
184
|
+
pyinstaller my_app.spec
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
On macOS, inject the Sparkle keys via `info_plist={}` in your `.spec`; see
|
|
188
|
+
[`examples/demo_app/build.spec`](examples/demo_app/build.spec) for a
|
|
189
|
+
reference. On Windows, pass WinSparkle settings to `Updater(...)`.
|
|
190
|
+
|
|
191
|
+
For macOS onefile `.app` specs shaped like `BUNDLE(exe, ...)`, use the
|
|
192
|
+
wrapper command instead:
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
uv run sparklehelper pyinstaller my_app.spec
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
The wrapper creates a temporary patched spec that moves `Sparkle.framework`
|
|
199
|
+
from `Analysis` into `BUNDLE`, so it lands in `Contents/Frameworks` instead of
|
|
200
|
+
the onefile `_MEI...` extraction directory. Onedir specs shaped like
|
|
201
|
+
`BUNDLE(coll, ...)` do not need this wrapper and should keep using plain
|
|
202
|
+
`pyinstaller`. If a onefile `.app` spec is built with plain `pyinstaller`, the
|
|
203
|
+
hook stops the build and points to this wrapper command.
|
|
204
|
+
|
|
205
|
+
### 5.2. Nuitka
|
|
206
|
+
|
|
207
|
+
SparkleHelper ships a Nuitka package config and user plugin as package data.
|
|
208
|
+
Nuitka does not auto-discover either third-party resource, so the
|
|
209
|
+
`sparklehelper nuitka` wrapper injects both before forwarding all remaining
|
|
210
|
+
arguments to Nuitka.
|
|
211
|
+
|
|
212
|
+
Requirements:
|
|
213
|
+
|
|
214
|
+
- Nuitka **4.1.2** or newer.
|
|
215
|
+
- macOS 11 or newer for Sparkle wheels, or Windows for WinSparkle wheels.
|
|
216
|
+
|
|
217
|
+
```bash
|
|
218
|
+
uv run sparklehelper nuitka \
|
|
219
|
+
--version 0.1.0 \
|
|
220
|
+
--build-version 1 \
|
|
221
|
+
--feed-url https://example.com/appcast.xml \
|
|
222
|
+
--public-ed-key YOUR_EDDSA_PUBLIC_KEY_BASE64 \
|
|
223
|
+
--mode=app \
|
|
224
|
+
my_app.py
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
The config collects the wheel's bundled copy and places it at
|
|
228
|
+
`Contents/Frameworks/Sparkle.framework` on macOS and at
|
|
229
|
+
`WinSparkle.dll` in the Windows Nuitka dist directory; the runtime locates this
|
|
230
|
+
directory through Nuitka's `__compiled__.containing_dir`.
|
|
231
|
+
|
|
232
|
+
On macOS, the plugin restores the framework's top-level `Autoupdate` symlink
|
|
233
|
+
before Nuitka signs the app. The wrapper's
|
|
234
|
+
`--version` option writes `CFBundleShortVersionString` and defaults to `0.1.0`
|
|
235
|
+
when neither `--version` nor Nuitka's native `--macos-app-version` is passed.
|
|
236
|
+
`--build-version` optionally writes the Sparkle-comparable `CFBundleVersion`;
|
|
237
|
+
when it is omitted, the wrapper derives an Apple-compatible build version from
|
|
238
|
+
`--version`, for example `0.1.0` becomes build version `1`. The wrapper also
|
|
239
|
+
accepts
|
|
240
|
+
Sparkle Info.plist keys via aliases such as `--feed-url`,
|
|
241
|
+
`--public-ed-key`, `--automatic-checks`, and `--scheduled-check-interval`, or
|
|
242
|
+
via repeated `--sparkle-key KEY=VALUE` entries for the full Sparkle key set. If
|
|
243
|
+
Nuitka's native `--macos-app-version` is forwarded without wrapper version
|
|
244
|
+
options, the plugin reads Nuitka's generated `CFBundleShortVersionString` from
|
|
245
|
+
`Info.plist` and only fills in a missing `CFBundleVersion`. If the application
|
|
246
|
+
also uses pypylon,
|
|
247
|
+
Nuitka intentionally keeps its loader-relative framework at
|
|
248
|
+
`Contents/Frameworks/pypylon/pylon.framework`.
|
|
249
|
+
|
|
250
|
+
To build the demo:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
cd examples/demo_app
|
|
254
|
+
uv run sparklehelper nuitka \
|
|
255
|
+
--version 0.1.0 \
|
|
256
|
+
--build-version 1 \
|
|
257
|
+
--feed-url https://example.com/appcast.xml \
|
|
258
|
+
--public-ed-key YOUR_EDDSA_PUBLIC_KEY_BASE64 \
|
|
259
|
+
--mode=app \
|
|
260
|
+
demo.py
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
The wheel does not include Sparkle release authoring tools such as
|
|
264
|
+
`generate_keys` or `sign_update`; obtain those separately when producing
|
|
265
|
+
signed updates.
|
|
266
|
+
|
|
267
|
+
## 6. API overview
|
|
268
|
+
|
|
269
|
+
```text
|
|
270
|
+
Updater main entry (wraps the native platform backend)
|
|
271
|
+
├─ check_for_updates() pops the native update window
|
|
272
|
+
├─ check_for_updates_in_background()
|
|
273
|
+
├─ start() / reset_update_cycle() / reset_update_cycle_after_short_delay()
|
|
274
|
+
├─ can_check_for_updates macOS-only KVO property
|
|
275
|
+
├─ feed_url / host_bundle_path / last_update_check_date / system_profile
|
|
276
|
+
├─ automatically_checks_for_updates / update_check_interval
|
|
277
|
+
├─ automatically_downloads_updates / allows_automatic_updates
|
|
278
|
+
├─ user_agent_string / http_headers / sends_system_profile
|
|
279
|
+
└─ observe(property_name, cb) -> Subscription macOS-only KVO subscription
|
|
280
|
+
|
|
281
|
+
UpdaterDelegate macOS-only optional callback Protocol
|
|
282
|
+
├─ updater_may_perform_update_check()
|
|
283
|
+
├─ feed_url_string_for_updater()
|
|
284
|
+
├─ allowed_channels_for_updater()
|
|
285
|
+
├─ feed_parameters_for_updater() / allowed_system_profile_keys_for_updater()
|
|
286
|
+
├─ updater_did_find_valid_update()
|
|
287
|
+
├─ updater_did_not_find_update()
|
|
288
|
+
├─ updater_should_proceed_with_update() / updater_user_did_make_choice()
|
|
289
|
+
├─ updater_will_download_update() / updater_did_download_update()
|
|
290
|
+
├─ updater_failed_to_download_update() / user_did_cancel_download()
|
|
291
|
+
├─ updater_will_extract_update() / updater_did_extract_update()
|
|
292
|
+
├─ updater_will_install_update() / updater_should_relaunch_application()
|
|
293
|
+
├─ updater_will_schedule_update_check() / updater_will_not_schedule_update_check()
|
|
294
|
+
└─ updater_did_abort() / updater_did_finish_cycle()
|
|
295
|
+
|
|
296
|
+
UpdateInfo / SystemProfileEntry / UpdateCheckResult / UserUpdateState dataclass
|
|
297
|
+
UpdateCheckKind / UserUpdateChoice / UserUpdateStage enums
|
|
298
|
+
|
|
299
|
+
ensure_runnable() aggregate check: platform/native runtime/config
|
|
300
|
+
errors SparkleError exception hierarchy
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
## 7. Platform constraints
|
|
304
|
+
|
|
305
|
+
- **macOS main thread**: Sparkle/Cocoa APIs must be called on the main thread;
|
|
306
|
+
SparkleHelper asserts this at every entry point.
|
|
307
|
+
- **macOS bundle**: Sparkle must be packaged as a `.app`; `ensure_runnable()`
|
|
308
|
+
checks the bundle and plist.
|
|
309
|
+
- **Sparkle ships its own UI**: `check_for_updates()` pops the native window
|
|
310
|
+
with zero config (built-in `SPUStandardUserDriver` + the in-framework nib).
|
|
311
|
+
- **macOS persistence**: dynamic settings still land in Sparkle's own
|
|
312
|
+
NSUserDefaults, following the official advice to "not add another layer on
|
|
313
|
+
top of user preferences".
|
|
314
|
+
- **macOS GUI run loop**: Sparkle's
|
|
315
|
+
`startUpdater` is asynchronous and depends on the NSApp run loop to
|
|
316
|
+
complete; `canCheckForUpdates` only becomes `True` once the run loop is
|
|
317
|
+
spinning. So the host GUI must drive the NSApp run loop — **wxPython,
|
|
318
|
+
PyQt/PySide, PyObjC/AppKit native** all work; **tkinter does not** (it
|
|
319
|
+
uses a separate Tcl event loop that does not interoperate with NSApp,
|
|
320
|
+
leaving menu items permanently greyed out). See
|
|
321
|
+
[`examples/demo_app/README.md`](examples/demo_app/README.md).
|
|
322
|
+
- **macOS delay `start()`**: `Updater()` does not start automatic checks by
|
|
323
|
+
default. In GUI apps, call `start()` after entering the mainloop (the demo
|
|
324
|
+
does this via `wx.CallAfter`), or pass `start=True` only when the NSApp run
|
|
325
|
+
loop is already ready.
|
|
326
|
+
- **Windows cleanup**: WinSparkle starts background work in
|
|
327
|
+
`win_sparkle_init()`. Call `cleanup()` before application exit, or use
|
|
328
|
+
`Updater(...)` as a context manager.
|
|
329
|
+
|
|
330
|
+
## 8. Development
|
|
331
|
+
|
|
332
|
+
```bash
|
|
333
|
+
uv venv && uv pip install -e ".[dev]"
|
|
334
|
+
uv run pytest # unit tests (mock the ObjC layer; run on any platform)
|
|
335
|
+
```
|
|
336
|
+
|
|
337
|
+
### 8.1. Building wheels (maintainers)
|
|
338
|
+
|
|
339
|
+
This is a packaging repo: the main package source tree no longer ships the
|
|
340
|
+
large native runtime binaries (`Sparkle.framework`, `WinSparkle.dll`). They
|
|
341
|
+
are fetched at wheel build time.
|
|
342
|
+
|
|
343
|
+
- Normal install — published wheels already embed the native assets, so end
|
|
344
|
+
users need no network access or compiler.
|
|
345
|
+
- Upstream source refs — `Sparkle/` and `winsparkle/` are Git submodules
|
|
346
|
+
pinned to upstream commits for source browsing, matching the opencv-python
|
|
347
|
+
packaging-repo style where GitHub shows entries such as `Sparkle @ <commit>`.
|
|
348
|
+
- Fresh clone — use `git clone --recursive`, or run
|
|
349
|
+
`git submodule update --init --recursive` after cloning.
|
|
350
|
+
- Building a wheel — `uv build --wheel` resolves the latest upstream
|
|
351
|
+
Sparkle / WinSparkle release assets, verifies their SHA256, syncs the
|
|
352
|
+
matching upstream license files, and unpacks them into the wheel. The archive
|
|
353
|
+
is cached under
|
|
354
|
+
`build/native-cache/` and reused on subsequent builds.
|
|
355
|
+
- Offline build — either keep the previously generated native files in place,
|
|
356
|
+
or set `SPARKLEHELPER_SKIP_NATIVE_SYNC=1`, which forbids network access and
|
|
357
|
+
only validates the local native/license assets (the build fails with a clear
|
|
358
|
+
error if they are missing).
|
|
359
|
+
- Tracked vs. generated — upstream source references are tracked as gitlinks;
|
|
360
|
+
package resources only commit `src/sparklehelper/winsparkle/winsparkle.h`.
|
|
361
|
+
`Sparkle.framework/`, `winsparkle/*/WinSparkle.dll`, and
|
|
362
|
+
`src/sparklehelper/licenses/*.txt` are git-ignored and regenerated by
|
|
363
|
+
`scripts/sync_native_deps.py`.
|
|
364
|
+
|
|
365
|
+
## 9. License
|
|
366
|
+
|
|
367
|
+
SparkleHelper is MIT licensed. Bundled native dependencies are redistributed
|
|
368
|
+
under their upstream licenses:
|
|
369
|
+
|
|
370
|
+
- `Sparkle.framework`: `sparklehelper/licenses/Sparkle-LICENSE.txt`
|
|
371
|
+
- `WinSparkle.dll`: `sparklehelper/licenses/WinSparkle-LICENSE.txt`
|