sdkmgr 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.
@@ -0,0 +1,7 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.pyc
4
+ .pytest_cache/
5
+ .ruff_cache/
6
+ dist/
7
+ *.egg-info/
@@ -0,0 +1,38 @@
1
+ # Changelog
2
+
3
+ ## Unreleased
4
+
5
+ ## 0.1.0 — 2026-10-18
6
+
7
+ - Use PyJS's BSD-3-Clause license, copyright, author identity, and community
8
+ metadata for this companion project.
9
+ - Keep failed or interrupted downloads out of their destination paths and
10
+ preserve existing files. Report malformed redirects without tracebacks.
11
+ - List Android packages offline even when command-line tools are absent.
12
+ - Validate wheel and source installations against the packaged tests, with a
13
+ single source for the package and CLI version.
14
+ - Keep the Android installation smoke's bootstrap inside its temporary SDK.
15
+ - Validate original ZIP member names before Windows path normalization or
16
+ NUL truncation can hide malformed archive entries.
17
+
18
+ - Provision Android SDK package sets through Google's `android sdk` CLI,
19
+ reusing existing SDKs and delegating package dependencies to Google.
20
+ - Bootstrap missing Android tools from pinned, checksum-verified archives,
21
+ with explicit download-license consent and safe staged extraction.
22
+ - Add offline Android package listings, explicit installation consent, install
23
+ locking, and diagnostics for partial installations. Disable CLI metrics.
24
+ - Use slash-separated Android package IDs; omit the deprecated `sdkmanager`
25
+ adapter, license-review command, and unnecessary external JDK dependency.
26
+
27
+ - Discover JDK 21 and 25 across environment, system, and package locations.
28
+ - Install JDKs through apt, dnf, pacman, Homebrew, or WinGet after confirmation.
29
+ - Support Arch/Omarchy without refreshing databases or upgrading the system.
30
+ - Prefer existing mise for Node, reusing inactive versions without switching
31
+ global defaults or running project hooks.
32
+ - Defer privileged authentication to the OS and support manual-install retries.
33
+ - Return executable paths and subprocess-local environments.
34
+ - Reuse existing nvm when mise is absent; otherwise bootstrap a pinned,
35
+ checksum-verified mise without editing shell profiles.
36
+ - Replace the direct Node downloader with manager-owned provisioning.
37
+ - Preserve Unix nvm defaults by staging installations before publishing them.
38
+ - Add real-provider CI smoke jobs alongside the isolated unit tests.
sdkmgr-0.1.0/LICENSE ADDED
@@ -0,0 +1,28 @@
1
+ BSD 3-Clause License
2
+
3
+ Copyright (c) 2025, Lex Berezhny
4
+
5
+ Redistribution and use in source and binary forms, with or without
6
+ modification, are permitted provided that the following conditions are met:
7
+
8
+ 1. Redistributions of source code must retain the above copyright notice, this
9
+ list of conditions and the following disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above copyright notice,
12
+ this list of conditions and the following disclaimer in the documentation
13
+ and/or other materials provided with the distribution.
14
+
15
+ 3. Neither the name of the copyright holder nor the names of its
16
+ contributors may be used to endorse or promote products derived from
17
+ this software without specific prior written permission.
18
+
19
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
22
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
23
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
24
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
25
+ SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
26
+ CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
27
+ OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
28
+ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
sdkmgr-0.1.0/PKG-INFO ADDED
@@ -0,0 +1,382 @@
1
+ Metadata-Version: 2.5
2
+ Name: sdkmgr
3
+ Version: 0.1.0
4
+ Summary: Discover and provision Node, Java, and Android development tools.
5
+ Project-URL: Homepage, https://github.com/pyjs/sdkmgr
6
+ Project-URL: Release notes, https://github.com/pyjs/sdkmgr/releases
7
+ Project-URL: Source, https://github.com/pyjs/sdkmgr
8
+ Project-URL: Tracker, https://github.com/pyjs/sdkmgr/issues
9
+ Project-URL: Discord, https://discord.gg/YktKpQPG7
10
+ Project-URL: X, https://x.com/pyjsdev
11
+ Author-email: Lex Berezhny <lex@damoti.com>
12
+ License-Expression: BSD-3-Clause
13
+ License-File: LICENSE
14
+ Keywords: android,java,node,sdk,toolchain
15
+ Classifier: Development Status :: 3 - Alpha
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python
19
+ Classifier: Programming Language :: Python :: 3
20
+ Classifier: Programming Language :: Python :: 3 :: Only
21
+ Classifier: Topic :: Software Development :: Build Tools
22
+ Requires-Python: >=3.12
23
+ Requires-Dist: filelock>=3.16
24
+ Requires-Dist: httpx<1,>=0.27
25
+ Requires-Dist: platformdirs>=4
26
+ Description-Content-Type: text/markdown
27
+
28
+ # sdkmgr
29
+
30
+ A Python library and command-line tool for finding development SDKs and
31
+ installing missing tools through established package managers.
32
+
33
+ This is an alpha project. Java provisioning supports **JDK 21 and 25** through
34
+ apt, dnf, pacman, Homebrew, or WinGet.
35
+ There is no JDK fallback,
36
+ vendor requirement for existing installations, or exact patch-version pinning.
37
+
38
+ Node provisioning prefers existing mise, otherwise existing nvm, otherwise
39
+ bootstraps mise. sdkmgr no longer downloads Node archives itself. Android
40
+ provisioning delegates package installation to Google's command-line tools.
41
+ LLVM compilation dependencies follow in a later version.
42
+
43
+ ## Install
44
+
45
+ Python 3.12 or newer is required. Install the CLI in an isolated environment:
46
+
47
+ ```sh
48
+ uv tool install sdkmgr
49
+ sdkmgr --help
50
+ ```
51
+
52
+ For a library dependency, use `pip install sdkmgr` in your environment.
53
+ To try unreleased source, use
54
+ `uv tool install git+https://github.com/pyjs/sdkmgr.git` instead.
55
+ Installing sdkmgr itself does not download any SDK or change tool selections.
56
+
57
+ ## Android SDK packages
58
+
59
+ ```sh
60
+ sdkmgr android list
61
+ sdkmgr android install platform-tools platforms/android-35 build-tools/35.0.1
62
+ ```
63
+
64
+ Android provisioning uses Google's **`android sdk` CLI only**. There is no
65
+ legacy `sdkmanager` adapter or fallback. Package IDs use the CLI's slash
66
+ syntax; sdkmgr translates semicolons only when reading Google's package
67
+ records. Existing SDK packages do not need to be reinstalled when changing
68
+ the management tool.
69
+
70
+ Root selection is `--sdk-root`, then `ANDROID_HOME`/`ANDROID_SDK_ROOT`, then
71
+ the standard user location on Linux, macOS, or Windows. Conflicting environment
72
+ variables require an explicit root. The install command prints the SDK root
73
+ to stdout; progress goes to stderr.
74
+
75
+ Installation reuses registered packages without upgrading them, verifies key
76
+ files for platform/build tools, and delegates dependencies to Google.
77
+ Packages such as `platform-tools` select Google's stable release when absent,
78
+ not a pinned revision. This is not yet a reproducible package lock.
79
+ The `list` command reads local records without network access and does not
80
+ require the Android CLI. A missing SDK root produces an empty listing.
81
+
82
+ Before downloading the CLI or installing missing packages, review Google's
83
+ [SDK terms](https://developer.android.com/studio/terms) and the requested
84
+ packages' licenses, then explicitly confirm acceptance:
85
+
86
+ ```sh
87
+ sdkmgr android install --accept-sdk-license platform-tools platforms/android-35
88
+ ```
89
+
90
+ This flag acknowledges prior acceptance; sdkmgr never supplies automatic
91
+ affirmative input. Google's new CLI no longer provides the old
92
+ `--licenses` flow, so sdkmgr requires confirmation before invoking installation,
93
+ even if the CLI would proceed without prompting. Reusing already installed
94
+ packages does not require the flag. Failed or incomplete installs are reported
95
+ even when Google's command exits successfully.
96
+
97
+ The CLI bootstrap archive is pinned and SHA-256 verified, extracted in staging,
98
+ then atomically published to a separate command-tools directory. **The launcher
99
+ fetches Google's CLI payload on first use.** That subsequent payload and its
100
+ cache are managed by Google, not pinned or independently verified by sdkmgr.
101
+ Commands launched by sdkmgr include `--no-metrics` and an explicit `--sdk`.
102
+ Discovery and no-op provisioning do not launch the CLI.
103
+
104
+ Existing tools are not replaced. SDK installs are serialized among sdkmgr
105
+ callers; do not run Android Studio's SDK installer concurrently. Interrupted
106
+ installs are rechecked on retry; registered but damaged packages need explicit
107
+ repair. No external JDK is required for provisioning. Obtain one separately
108
+ with `ensure("java", version="21")` when your build needs it.
109
+
110
+ Bootstrap covers Linux x64, macOS x64/ARM64, and Windows x64. Other hosts need
111
+ compatible command-line tools installed separately. Windows/macOS support has
112
+ simulated tests but still needs real installation verification. Linux bootstrap
113
+ and existing SDK reuse are verified locally. See the
114
+ [CLI reference](https://developer.android.com/tools/agents/android-cli).
115
+
116
+ ```python
117
+ from sdkmgr import Manager
118
+
119
+ sdk = Manager().ensure_android(
120
+ ["platform-tools", "platforms/android-35", "build-tools/35.0.1"],
121
+ accept_sdk_license=True, # Only after reviewing and accepting the terms.
122
+ )
123
+ adb = sdk.executable("adb")
124
+ environment = sdk.environment()
125
+ ```
126
+
127
+ The returned environment sets SDK paths without changing your shell. Emulator
128
+ packages and system images must be requested explicitly. Provisioning never
129
+ creates an AVD or starts an emulator.
130
+
131
+ For an opt-in live smoke, after accepting the terms:
132
+
133
+ ```sh
134
+ python scripts/smoke_android.py --accept-sdk-license
135
+ ```
136
+
137
+ The smoke bootstraps tools and installs API 35 into a temporary SDK, then
138
+ checks a no-op retry and removes that SDK. Google's separate CLI payload cache
139
+ may persist; no packages are added to the user's existing SDK.
140
+
141
+ This bootstraps the CLI if needed, installs API 35 into a temporary SDK,
142
+ verifies it, checks a no-op retry, and removes the temporary packages.
143
+
144
+ ## Java from the command line
145
+
146
+ ```sh
147
+ sdkmgr install java@21
148
+ sdkmgr install java@25
149
+ sdkmgr which java@21
150
+ sdkmgr list
151
+ ```
152
+
153
+ `which` only discovers installations. `install` first looks for a complete,
154
+ working JDK of the requested major version. It accepts maintenance updates
155
+ and does not require Temurin. A runtime without `javac` and `jar` is not enough.
156
+
157
+ Discovery checks `JAVA_HOME`, resolved executables on `PATH`, and platform
158
+ installation locations. macOS also uses `java_home` and Homebrew's prefix;
159
+ Windows also checks registered JDK uninstall locations. It verifies both
160
+ `java` and `javac` with ten-second subprocess timeouts.
161
+
162
+ If the JDK is missing, sdkmgr shows the package-manager command:
163
+
164
+ ```text
165
+ JDK installation requires this command:
166
+
167
+ /usr/bin/sudo -- /usr/bin/apt-get install openjdk-21-jdk
168
+
169
+ You can quit, run it yourself, and restart your original command.
170
+ Completed installations will be detected automatically.
171
+ ```
172
+
173
+ Press Enter to continue, or type `q` to exit. Authentication is handled by
174
+ sudo or the Windows installer, never by sdkmgr. On typical sudo systems,
175
+ the password requested is your account password, not the root password.
176
+ Package-manager confirmation and license prompts remain visible.
177
+
178
+ If you exit, run the displayed command yourself and restart the command you
179
+ originally ran. Discovery happens again, so sdkmgr proceeds without repeating
180
+ the completed installation. No separate resume flag or checkpoint is needed.
181
+
182
+ Without an interactive terminal, or with `--non-interactive`, missing Java
183
+ produces instructions and exit status **2** without running an installer:
184
+
185
+ ```sh
186
+ sdkmgr --non-interactive install java@21
187
+ ```
188
+
189
+ Successful `install` and `which` print the executable path to stdout.
190
+ Messages and package-manager output go to stderr. Other expected failures
191
+ return status 1; interruption returns 130.
192
+
193
+ ## Package providers
194
+
195
+ | Platform | Installation command, before elevation |
196
+ | --- | --- |
197
+ | Debian/Ubuntu | `apt-get install openjdk-21-jdk` |
198
+ | Fedora/RHEL family | `dnf install java-21-openjdk-devel` |
199
+ | Arch/Omarchy | `pacman -S --needed jdk21-openjdk` |
200
+ | macOS | `brew install openjdk@21` |
201
+ | Windows | `winget install --exact --id Microsoft.OpenJDK.21 --source winget` |
202
+
203
+ Java 25 uses the corresponding `25` package names. Linux commands use sudo
204
+ unless already running as root. Homebrew is never run through sudo by sdkmgr;
205
+ Windows installers handle elevation through the OS.
206
+
207
+ The package manager must already be installed. sdkmgr does not add repositories,
208
+ refresh package indexes, install Homebrew, accept licenses automatically, or
209
+ silently substitute another major version. If the requested package is absent
210
+ from your OS repositories, installation fails with the package-manager output.
211
+ Install an appropriate JDK yourself or use a supported OS release, then retry.
212
+
213
+ Arch and Omarchy require complete system updates; partial upgrades are not
214
+ supported. sdkmgr does not run `pacman -Sy` or perform a whole-system upgrade.
215
+ If packages cannot be installed, use your normal system updater (Omarchy's
216
+ updater on Omarchy), then retry the original sdkmgr command.
217
+
218
+ sdkmgr does not explicitly change Java alternatives, shell profiles, or global
219
+ environment variables. Package-manager installers can have their own system
220
+ side effects. Security updates remain the responsibility of the package manager
221
+ and the user's update policy; `ensure()` does not force upgrades.
222
+
223
+ ## Node manager selection
224
+
225
+ ```sh
226
+ sdkmgr install node@22.14.0
227
+ sdkmgr which node@22.14.0
228
+ ```
229
+
230
+ Selection follows this order:
231
+
232
+ 1. Existing mise on `PATH`, in `~/.local/bin`, or previously bootstrapped by
233
+ sdkmgr under its private root.
234
+ 2. Existing Unix nvm or nvm-windows.
235
+ 3. Bootstrap a pinned mise if neither manager is present.
236
+
237
+ A selected manager's failure does not trigger fallback to another provider.
238
+ `which` and `list` never bootstrap managers or install Node. A standalone Node
239
+ on `PATH` does not bypass this selection order.
240
+
241
+ ### Mise
242
+
243
+ When mise is present, sdkmgr prefers it, including
244
+ versions that are installed but not currently active. It requests an exact
245
+ version through mise's built-in Node provider, validates `node --version`, and
246
+ requires the bundled npm and npx executables. A missing version is installed
247
+ in mise's storage, not sdkmgr's cache. No sudo is used.
248
+
249
+ Discovery uses `mise ls --installed --json core:node`; installation uses
250
+ `mise install core:node@VERSION --jobs=1`. sdkmgr does not run `mise use`,
251
+ activate a shell, edit project configuration, or switch global defaults.
252
+ Returned paths bypass shims, and `environment()` selects Node only for the
253
+ subprocess receiving that environment.
254
+
255
+ The adapter requires mise's `--no-config` and `--no-hooks` options. These
256
+ prevent unrelated project configuration or hooks from running. It also
257
+ disables source compilation and the default extra-npm-packages file. Existing
258
+ environment settings, including mise's data directory, remain available.
259
+ Configuration-file-only customizations are intentionally not loaded.
260
+
261
+ A broken or incompatible mise installation produces an error instead of a
262
+ silent fallback to another installer. Upgrade or repair mise and retry.
263
+ Discovery commands have a 30-second timeout; installation has ten minutes and
264
+ sends progress to stderr. `find()` never installs a missing version.
265
+
266
+ Bootstrap downloads mise 2026.9.12 from its official GitHub release and checks
267
+ the binary against a SHA-256 pinned in sdkmgr before executing it. No shell
268
+ installer runs. Linux uses the musl binary; macOS and Windows use their native
269
+ binaries. Both x64 and ARM64 assets are pinned. The executable is installed
270
+ atomically under `ROOT/managers/`, without changing `PATH` or shell profiles.
271
+ Concurrent bootstrap calls share a lock. A corrupt cached binary produces an
272
+ error rather than being silently overwritten.
273
+
274
+ ### Nvm
275
+
276
+ Unix discovery checks `NVM_DIR`, `~/.nvm`, `XDG_CONFIG_HOME/nvm`, and common
277
+ Homebrew/Arch script locations. Inactive versions are reusable. Installation
278
+ sources the existing script in a noninteractive Bash subprocess without
279
+ loading shell profiles. It requests a prebuilt Node and skips default extra
280
+ npm packages. To avoid nvm creating a default alias, installation uses a
281
+ temporary `NVM_DIR`; only the validated version is moved into the user's store.
282
+
283
+ On Windows, sdkmgr discovers `nvm.exe` through `PATH` or `NVM_HOME`, then asks
284
+ nvm-windows v1 or v2 for its installation root. It installs missing versions
285
+ without invoking `nvm use`. Existing manager settings still apply, including
286
+ v2's configured extra npm packages. Version 2 automatically activates its first
287
+ Node installation, so an empty v2 store produces manual-install instructions
288
+ and exit status 2. Run the displayed command and retry. sdkmgr does not modify
289
+ registry preferences or undo manager-owned state changes.
290
+
291
+ `--non-interactive` disables prompts for privileged Java installation; ordinary
292
+ Node provisioning and mise bootstrap remain automatic, including in CI.
293
+
294
+ ## Python integration
295
+
296
+ ```python
297
+ import subprocess
298
+
299
+ from sdkmgr import Manager
300
+
301
+ manager = Manager()
302
+ jdk = manager.ensure("java", version="21")
303
+
304
+ subprocess.run(
305
+ [jdk.executable("javac"), "Example.java"],
306
+ env=jdk.environment(),
307
+ check=True,
308
+ )
309
+ ```
310
+
311
+ `environment()` returns a new complete environment with the selected JDK's
312
+ `bin` directory first in `PATH` and its home in `JAVA_HOME`. Pass an explicit
313
+ mapping to `environment(base)` if needed. Neither form mutates `os.environ`.
314
+ `Installation.version` records the detected runtime version, such as
315
+ `21.0.12`, rather than just the requested major.
316
+
317
+ `find("java", version="21")` returns an installation or `None` without
318
+ installing anything. `installed()` returns discovered Java 21/25 installations
319
+ and versions from the selected Node manager. Old prototype Node caches are not
320
+ used or removed.
321
+
322
+ For unattended callers, disable prompting explicitly:
323
+
324
+ ```python
325
+ from sdkmgr import InstallationRequired, Manager
326
+
327
+ manager = Manager(interactive=False)
328
+ try:
329
+ jdk = manager.ensure("java", version="21")
330
+ except InstallationRequired as error:
331
+ required_command = error.command
332
+ raise
333
+ ```
334
+
335
+ `InstallationRequired.command` is an argument tuple; no shell parsing is needed.
336
+ `InstallationRequired` is an `SDKError`. Library calls never terminate the
337
+ calling process. Even `interactive=True` cannot launch an installer without
338
+ terminal input and terminal stderr. Installer processes inherit stdin;
339
+ sdkmgr never reads, stores, forwards, or logs a password.
340
+
341
+ After an installer reports success, discovery runs again. If its installation
342
+ location is not recognized, set `JAVA_HOME` and retry. Long-running interactive
343
+ package installations are not given an arbitrary timeout while authentication
344
+ or user confirmation may be pending.
345
+
346
+ ## Development
347
+
348
+ Python 3.12 or newer is required. Activate the environment after `uv sync`:
349
+
350
+ ```sh
351
+ uv sync
352
+ source .venv/bin/activate
353
+ pytest
354
+ ruff check .
355
+ python scripts/check_release.py
356
+ ```
357
+
358
+ The default tests use simulated SDKs and installers. They do not install
359
+ packages, ask for passwords, or depend on installed Java, mise, or nvm versions.
360
+ The CI configuration covers Python 3.12 and 3.14 on Linux, macOS, and Windows.
361
+ The release check builds both distributions in temporary storage, audits their
362
+ contents, installs each into a fresh environment, and runs the packaged tests
363
+ outside the checkout. It does not publish anything. `uv build` creates the
364
+ wheel and source archive in `dist/` when you are ready to retain artifacts.
365
+ The Python 3.12/3.14 tests and installed-artifact checks pass on all three
366
+ systems. Real mise/Node smokes pass on Linux, macOS, and Windows; real nvm/Node
367
+ smokes pass on Linux and macOS. Real OS JDK installation still needs
368
+ verification on those platforms.
369
+ Do not run an automatic source formatter.
370
+
371
+ Separate CI jobs exercise real mise bootstrap and Node installation on Linux,
372
+ macOS, and Windows, plus Unix nvm on Linux and macOS. These networked smoke
373
+ tests are outside the default pytest run:
374
+
375
+ ```sh
376
+ python scripts/smoke_node.py mise
377
+ python scripts/smoke_node.py nvm
378
+ ```
379
+
380
+ The mise smoke uses temporary storage. The nvm smoke installs into the detected
381
+ nvm store: use a disposable `NVM_DIR` when testing missing versions. Native
382
+ nvm-windows installation still needs a real Windows smoke test.