Runtimes and Ecosystem Tools
This page covers Node.js, Python, Java/JRE, Go, Rust, Maven, Gradle, and Kotlin. See JavaScript Package Managers for package managers and Sources and Supply-chain Security for arbitrary GitHub Release tools.
Common command forms
osdk install TOOL[@VERSION]... [-o|--opt KEY=VALUE ...]
osdk lock [TOOL[@VERSION] ...] [-o|--opt KEY=VALUE ...]
osdk upgrade [TOOL[@VERSION] ...] [-o|--opt KEY=VALUE ...]
osdk use|u TOOL[@VERSION] [-g|--global] [-o|--opt KEY=VALUE ...]
osdk exec (-t|--tool TOOL[@VERSION])... -- COMMAND [ARG ...]
osdk list|ls [TOOL]
osdk list-remote|lsr TOOL [FILTER]
osdk current [TOOL]
osdk where TOOL[@VERSION]
osdk uninstall|rm TOOL@VERSION-o/--opt is repeatable and must be KEY=VALUE. It applies to every tool in the invocation; do not pass an option specific to one backend in a mixed-backend command.
When an existing structured tool is excluded from the current platform by when, osdk use TOOL@VERSION updates only its version and preserves when, lazy, and backend options. It does not bypass the platform condition to install the tool or write a lock entry for the current platform. -o/--opt cannot be changed in this case; update and verify backend options on a matching platform.
An inline option block is written tool[key=value,...]@selector, with the block before the @. Quote the whole operand on PowerShell: it treats an unquoted comma inside an argument as an array separator and splits one expression into two.
osdk install 'npm:esbuild[installer=pnpm,allow_builds=true]@0.21'Quote the value as well when it contains commas: allow_builds="a,b". When an unquoted operand does get split, osdk recognizes it and prints the correctly quoted command instead of complaining about an unterminated bracket.
Backend overview
| Backend | Tool aliases | Native version files | Install options |
|---|---|---|---|
node | nodejs | .nvmrc, .node-version, package.json | arch, corepack |
python | py, cpython | .python-version | variant, tag |
java | jdk, openjdk | .java-version, .sdkmanrc | distribution, package-type |
go | golang | go.mod, .go-version | none |
rust | rustup | rust-toolchain.toml, rust-toolchain | profile, components, targets |
maven | mvn | .mvn-version | none |
gradle | — | .gradle-version | none |
kotlin | kotlinc | .kotlin-version | none |
zig | — | .zig-version | none |
See Project version discovery for the full precedence and the current native-file boundary of no-argument commands.
Node.js
osdk install node@20
osdk install node@20.19.0 -o corepack=true
osdk lock node@20 -o arch=arm64| Option | Values | Effect |
|---|---|---|
arch | x64, arm64, x86, arm | Select the Node artifact architecture; default is the host |
corepack | `true | 1 |
Without an explicit corepack option, [settings.node].corepack applies and defaults to false. Failure removes the incomplete installation. The Node shim owns node and corepack; independent npm or the matching Node installation provides coordinated npm/npx routing. The runtime environment supplies shared npm_config_cache only when the user has not set it.
arch can generate another architecture's lock section, but osdk has no download-only cross-architecture mode; actual installation rejects a Node artifact that cannot execute on the host.
Node merges version indexes from every reachable source while retaining the higher-ranked source's LTS metadata for duplicates. A responsive but stale mirror therefore cannot hide a release present on a later source; the archive download then verifies each SHASUMS256.txt candidate in the same order.
Node also supports global-package migration:
osdk node migrate-packages --from VERSION --to VERSION [--apply]# Plan only
osdk node migrate-packages --from 20.19.0 --to 22.17.0
# Apply the plan
osdk node migrate-packages --from 20.19.0 --to 22.17.0 --applyBoth versions must be installed, managed Node versions that contain npm. osdk enumerates the source with npm ls -g --depth=0 --json --long, skips npm itself, packages already present on the target, and packages declaring hasInstallScript=true or gypfile=true. --apply installs exact versions; on failure, it restores the target's original global-package set.
Python
python@3.14 is shorthand for default CPython. The complete request form is python@IMPLEMENTATION-VERSION+VARIANT:
osdk install python@3.14
osdk install python@cpython-3.14+freethreaded
osdk install python@cpython-3.14+debug
osdk install python@cpython-3.14+freethreaded+debug
osdk install python@pypy-3.11
osdk install python@graalpy-3.12
osdk install python@pyodide-3.14| Implementation | Supported variants | Identity examples |
|---|---|---|
cpython | default, freethreaded, debug, freethreaded+debug | 3.14.7, cpython-3.14.7+debug |
pypy | default | pypy-3.11.x |
graalpy | default | graalpy-3.12.x |
pyodide | default | pyodide-3.14.x |
| Option | Value | Effect |
|---|---|---|
variant | A variant above | Equivalent to request suffix +VARIANT; the explicit option wins |
tag | A python-build-standalone date such as 20240224 | Select a historical PBS release for classic CPython |
Implementation, exact Python version, variant, and catalog artifact are locked, and distinct identities can coexist. Standard CPython uses the embedded python-build-standalone index; multiple implementations, variants, and pre-releases use an embedded verified catalog. A custom catalog needs its exact SHA-256:
[settings.python]
catalog_url = "/approved/python-catalog.json"
catalog_sha256 = "0123456789abcdef..."catalog_url accepts HTTP(S) or a local path. Only a valid digest, schema, and checksum on every artifact can replace last-good. Refresh failure tries last-good and then the embedded catalog. See Pre-releases.
For classic CPython, osdk merges SHA256SUMS from every reachable source for the same release tag, preserving the higher-ranked entry for duplicate filenames. A first mirror missing one platform artifact can therefore be completed by a later source and enter normal verified download failover.
Interpreter discovery uses:
osdk python find [REQUEST]osdk python find
osdk python find pypy-3.11
osdk python find 3.14+freethreadedManaged results are filtered by the optional request. osdk then scans PATH and system candidates, deduplicates them, and labels each as managed, PATH, or system. It returns an error if nothing is found.
Java JDK and JRE
osdk install java@21
osdk install java@zulu-17.0.1
osdk install java@21 -o distribution=zulu -o package-type=jdk
osdk install java@21 -o package-type=jre| Option | Values | Default |
|---|---|---|
distribution | A Foojay distribution ID such as temurin or zulu | temurin |
package-type | jdk or jre | jdk |
The distribution can also be inline, as in java@temurin-21. Foojay lookup filters distribution, OS, architecture, archive type, JDK/JRE, and Linux libc. A JRE uses identity jre-<resolved-version> and can coexist with the matching JDK. Execution exports JAVA_HOME.
Temurin versions carry a build number (such as 21.0.12+8), and a PSU adds a fourth segment (such as 21.0.12.1+1). You may omit the build number in the request: java@21.0.12 matches the same-core 21.0.12+8, and only falls back to a four-part PSU when no same-core release exists.
The embedded Temurin LTS catalog covers 8, 11, 17, 21, and 25, allowing resolution with an empty metadata cache. A locked artifact remains installable when Foojay is unavailable. Configure a Foojay-compatible /packages endpoint or static mirror with:
[settings.java]
catalog_url = "https://mirror.example/disco/v3.0/packages"Unless that single explicit catalog is set, Java queries each ranked Foojay-compatible source. A reachable endpoint that lacks the requested distribution/JDK/JRE combination falls through, and checksum detail lookup uses the same order before the vendor archive enters the shared verified pipeline.
JVM tools
osdk install maven@3.9.16
osdk install "gradle@=9.3.1"
osdk install kotlin@2.4.10Gradle resolves from the upstream version index, so any historical release is installable: osdk list-remote gradle lists every finished release (179 measured on 2026-09-14), and each version's download URL and SHA-256 come from the index rather than being hardcoded per version. Nightlies, -rc-N and -milestone-N entries are classified as unstable, so latest only ever lands on a finished release; ask for a preview by name. An entry that publishes no checksum is refused rather than installed unverified.
Maven and Kotlin have no comparable machine-readable index upstream, so they stay a fixed catalog: Maven 3.9.16 with SHA-512 and Kotlin 2.4.10 with SHA-256; other versions fail. All three have their own installation directory and shims; Kotlin also has a GitHub proxy download candidate. Maven and Kotlin use the ranked effective source URLs. Gradle tries each source index and rebases the index's absolute distribution URL across all ranked download roots, so a source with a readable index but a missing target zip can still fall through.
Relationship to the Gradle wrapper
When a project has gradle/wrapper/gradle-wrapper.properties, the wrapper remains authoritative -- its distributionSha256Sum is a stronger guarantee than a version pin. An osdk gradle pin is for the cases without a wrapper, such as a new project or invoking gradle directly. The two agree on the digest: the index's checksum for 9.3.1 is byte-identical to the distributionSha256Sum a wrapper pins for it.
Go
osdk install go@1.22
osdk use -g golang@1.23Go has no backend-specific -o. osdk selects the host OS/architecture archive from the go.dev JSON index and verifies its SHA-256. Download candidates include go.dev, Aliyun, and golang.google.cn. Execution exports GOROOT and exposes go and gofmt.
Rust
The Rust backend delegates to rustup inside osdk's isolated directories:
osdk install rust@stable
osdk install rust@nightly -o profile=minimal \
-o components=clippy,rustfmt \
-o targets=wasm32-unknown-unknown,x86_64-pc-windows-gnu| Option | Value | Default |
|---|---|---|
profile | Any profile accepted by rustup | default |
components | Comma-separated rustup components | empty |
targets | Comma-separated rustup targets | empty |
latest, lts, and system map to stable; stable, beta, nightly, and exact toolchains are otherwise passed through to isolated rustup. The rustup bootstrap itself uses minimal and installs no default toolchain. Runtime exports RUSTUP_HOME=<data>/rustup and CARGO_HOME=<data>/cargo. The lock preserves those floating channel names too, so reinstalling stable, beta, or nightly later may yield a newer toolchain. Use an explicit or dated toolchain when the result must be immutable.
The source probe ranks candidates using a generic stable manifest; it does not prove that a mirror has synchronized the requested exact toolchain, component, or target. Every downloading rustup operation runs the complete command against each ranked source in turn. A 404, transfer failure, or rustup rejection falls through until the command itself succeeds.
Exposed commands and cargo install
On install and on osdk reshim, osdk generates shims for every executable in the active toolchain bin and the isolated CARGO_HOME/bin, not just the five core launchers rustc, cargo, rustup, rustfmt, and clippy-driver. rustup proxies such as rustdoc, rust-analyzer, and cargo-miri, together with any CLI installed later through the managed cargo, are exposed as well. These shims still inject the isolated RUSTUP_HOME/CARGO_HOME at run time, so they can never reach a system rustup.
Third-party tools installed with the managed cargo install land in <data>/cargo/bin (not the system ~/.cargo); run osdk reshim once afterwards to make a new command directly callable from your shell. The cargo <subcommand> form (for example cargo tauri) needs no reshim, because cargo locates that launcher in the isolated CARGO_HOME/bin itself.
Management boundary versus a system rustup
osdk drives only the isolated rustup under its data directory and never takes over a rustup already installed on PATH:
- Before
osdk install rusthas run, the isolated rustup does not exist;osdk rust *fails with an explicit message pointing at the install command instead of falling through to the system rustup. osdk source pin rust <source>and the one-shot--source <source>change only theRUSTUP_DIST_SERVERinjected when osdk drives the managed rustup. Neither edits an external rustup, its environment, or its configuration; when no managed Rust exists the command additionally prints this scope note. To mirror a system rustup, setRUSTUP_DIST_SERVER,RUSTUP_UPDATE_ROOT, and related variables yourself.- Every managed operation that downloads (
osdk install rust,osdk rust component add,osdk rust target add) shares one source selection, so a pinned source also applies when you add a component or target later. A pin only puts that source first; an unavailable target still falls through to the remaining candidates. - The managed rustup always uses the source osdk selected: a
RUSTUP_DIST_SERVERorRUSTUP_UPDATE_ROOTalready exported in your shell does not affect managed operations, so an external mirror cannot override osdk's choice. To use a different source for one run, pass--source <source>instead of exporting those variables.
Components and targets
osdk rust component add NAME [--toolchain TOOLCHAIN]
osdk rust component remove NAME [--toolchain TOOLCHAIN]
osdk rust component list [--toolchain TOOLCHAIN]
osdk rust target add NAME [--toolchain TOOLCHAIN]
osdk rust target remove NAME [--toolchain TOOLCHAIN]
osdk rust target list [--toolchain TOOLCHAIN]--toolchain defaults to stable; all commands act on isolated rustup.
osdk rust component add rustfmt --toolchain stable
osdk rust target add wasm32-unknown-unknown --toolchain stableStatus, overrides, and local toolchains
osdk rust check [--repair]
osdk rust override import [PATH]
osdk rust override export [PATH]
osdk rust toolchain link NAME PATHcheckruns isolatedrustup check;--repaircreates missing markers for real toolchain directories and removes markers with no toolchain.override importreads isolated rustup's override forPATH(default current directory) and writes it to the nearest projectosdk.toml.override exportwrites the active osdk Rust pin for that directory as an isolated rustup directory override.toolchain linkrequires a canonicalizablePATHcontainingbin/;NAMEcannot contain whitespace or slashes or equal.or...
A linked toolchain can run through shims and activation, but it is a machine-local path and osdk lock rejects it as a reproducible artifact.
Zig
osdk install zig@latest
osdk use zig@0.16
osdk exec --tool zig -- zig versionVersions come from ziglang.org/download/index.json, which lists every release with its archive URL and SHA-256 together, so each install is checksum-verified and can be locked. Zig's GitHub releases carry only source and bootstrap archives, so the generic github: backend cannot install it. Although the index contains absolute tarball URLs, osdk rebases their relative release path across the ranked source download roots. A mirror whose index works but whose target archive is missing, corrupt, or not extractable therefore falls through.
master is a rolling nightly, not a release. It is treated as a prerelease, so zig@latest always selects a tagged version; request it explicitly with osdk install zig@master under a permissive prerelease policy.
Zig as a C and C++ cross compiler
zig cc and zig c++ are Clang drivers that ship their own libc: musl, several glibc versions, mingw-w64 and wasi-libc are all bundled. One install therefore cross-compiles to many targets with no per-target sysroot, which is exactly what the Android NDK cannot do outside Android.
# Same source, no extra downloads, no sysroot to point at.
osdk exec --tool zig -- zig cc -target aarch64-linux-musl -o hello hello.c
osdk exec --tool zig -- zig cc -target x86_64-windows-gnu -o hello.exe hello.cVerified on a Windows x86-64 host with a source that includes <stdio.h>; every target below produced a binary for the right machine:
-target | Output |
|---|---|
x86_64-linux-gnu | ELF, x86-64 |
aarch64-linux-gnu | ELF, aarch64 |
x86_64-linux-musl | ELF, x86-64, static |
aarch64-linux-musl | ELF, aarch64, static |
riscv64-linux-musl | ELF, riscv |
x86_64-windows-gnu | PE |
wasm32-wasi | wasm |
Zig can also stand in for a C compiler in other build systems by pointing CC at it, which is useful for Rust crates with C dependencies:
osdk exec --tool zig -- cargo build # with CC="zig cc" in the environmentZIG_GLOBAL_CACHE_DIR is set to a directory inside osdk's cache so build artifacts do not accumulate in your home directory. An existing value is left untouched.