Repository navigation
ponyup Specification (DRAFT) #378
SeanTAllen
started this conversation in
Research
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
1. Purpose
ponyup is the Pony toolchain multiplexer. It installs, selects, and removes
versioned packages of Pony development tools, managing a local directory of
installed versions and providing a single stable path for each tool's binary.
2. State Model
2.1 Filesystem State
A ponyup installation occupies a single root directory
R. The structure is:Where
<pkg-string>is the fully qualified package name:<app>-<channel>-<version>-<cpu>-<os>[-<distro>]Example:
ponyc-release-0.61.1-x86_64-linux-ubuntu24.042.2 Abstract State
Let the state of a ponyup installation be the tuple:
Where:
2.3 Lockfile
The lockfile (
.lock) is the persistent representation ofInstalledandSelected. Each line is:The
*marker denotes the selected version for that application. Lines aregrouped implicitly by application name.
2.4 Platform Triple
The platform triple is stored in
.platformand represents the targetplatform for package downloads. Format:
<cpu>-<os>[-<distro>]On Linux, ponyc requires a distro component. Other applications do not use it.
3. Invariants
These properties must hold at the end of every successful operation:
INV-1 (Lockfile reflects filesystem):
For every package P in
Installed, the directoryR/<string(P)>/exists andcontains the expected binaries. For every package directory under
R/, thereis a corresponding entry in
Installed.INV-2 (At most one selection per application):
For each application A,
|{P ∈ Installed : P.application = A ∧ Selected(A) = P}| ≤ 1INV-3 (Selection implies installed):
If
Selected(A) = PthenP ∈ Installed.INV-4 (Bin references match selection):
For each application A where
Selected(A) = P:Bin(B.name)is a symlinkpointing to
R/<string(P)>/bin/<B.name>.Bin(B.name + ".bat")is abatch file that delegates to
R/<string(P)>/bin/<B.name>.exe.R/<string(P)>/bin/, acorresponding link/shim exists in
Bin. If it does not exist, nolink/shim is created (and any stale link from a prior version is removed).
INV-5 (No orphan bin entries):
Every entry in
Bincorresponds to a binary of some selected package.INV-6 (Platform set before install):
The
updateoperation requiresPlatform ≠ Unset.INV-7 (Distro required for ponyc on Linux):
If a package P has
P.application = ponycandP.os = linux, thenP.distro ≠ None.4. Operations
4.1 update (sync)
Install a package, or confirm it is already installed.
Input: application name
A, channel/version specifierVPreconditions:
Platform ≠ UnsetAis a recognized applicationVis a valid channel name, a channel-version string (e.g., "release-0.61.1"),or "latest" (meaning: latest in the specified channel)
Steps:
Installed: report already installed, doneR/<pkg-string>/Installedselect(P)to make this the active versionPostconditions (on success):
P ∈ InstalledSelected(A) = PError conditions:
4.2 select
Make an installed package version the active one for its application.
Input: application name
A, versionVPreconditions:
P ∈ InstalledwithP.application = Aand version matchingVVis "latest", at least one package forAis installedSteps:
Vis "latest", resolve to the most recent installed version ofAPAP's binariesSelected(A) = PPostconditions:
Selected(A) = PError conditions:
4.3 remove
Remove an installed package version.
Input: application name
A, versionVPreconditions:
P ∈ InstalledwithP.application = Aand version matchingVSelected(A) ≠ P(cannot remove the currently selected version)Steps:
R/<string(P)>/PfromInstalledPostconditions:
P ∉ InstalledSelected(A)unchangedError conditions:
4.4 show
Display installed packages and optionally their remote availability.
Input: optional application name filter
A, flaglocalPreconditions: None (read-only operation)
Behavior:
Installed(filtered byAif given)*local = false: query Cloudsmith for latest available version of eachinstalled application (with 5-second timeout); display if newer version exists
4.5 find
Search for available packages on Cloudsmith.
Input: application name
A, optional channelC, optional countN(default varies), flag
all_platformsPreconditions: None (read-only operation)
Behavior:
all_platforms = false: filter to current Platform4.6 default
Set the platform triple for future operations.
Input: platform string
TPreconditions:
Tis a valid platform triple (parseable into CPU, OS, and optional Distro)Steps:
TTtoR/.platformPlatform = TPostconditions:
Platformis set to the parsed triple.platformfile containsT5. Package Identity and Resolution
5.1 Package String Format
A package is identified by its string representation:
<app>-<channel>-<version>-<cpu>-<os>[-<distro>]The string uniquely identifies a package within an installation.
5.2 Version Resolution
When a version is specified as "latest":
update: resolves via Cloudsmith query (most recent completed package)select: resolves to the most recent installed version (by string sort)remove: resolves to the most recent installed version5.3 Cloudsmith Name Mapping
Ponyup package names map to Cloudsmith package names with these transformations:
-nightly,-release)x86_64→x86-64linux→unknown-linuxdarwin→apple-darwinwindows→pc-windows5.4 Platform Parsing
Input platform strings accept target triples with optional vendor field:
x86_64-unknown-linux-ubuntu24.04→ (x86_64, linux, "ubuntu24.04")arm64-apple-darwin→ (arm64, darwin, None)x86_64-linux-ubuntu24.04→ (x86_64, linux, "ubuntu24.04") [vendor omitted]CPU aliases:
x64,amd64→x86_64;aarch64→arm64Vendor field is always stripped (not stored).
6. Platform-Specific Behavior
.tar.gz.ziptar -xzfwith--strip-components=1Expand-Archive.batshim files.exe$HOME/.local/share/ponyup(or XDG)%LOCALAPPDATA%\ponyup7. Bootstrap
The bootstrap scripts (
ponyup-init.sh,ponyup-init.ps1) are responsiblefor initial installation of ponyup itself. They:
.platformfileponyup default <platform>Note: The bootstrap scripts are separate from ponyup itself and have their own
platform detection logic. Distro detection on Linux uses
cc -dumpmachine(glibc vs musl) and
lsb_releaseor/etc/alpine-release.8. Open Questions / Observations
These are areas where the current implementation's behavior is implicit or
potentially inconsistent, and where a spec could make an explicit choice:
Partial install cleanup: If extraction fails during
update, is thepartial directory cleaned up? The code attempts removal but doesn't guarantee
it. Should the invariant require cleanup?
Lockfile atomicity: The lockfile is written by
dispose()whichoverwrites the file. If the process crashes mid-write, the lockfile could be
corrupted. Should writes be atomic (write to temp, rename)?
Concurrent access: What happens if two ponyup processes run
simultaneously? The lockfile has no locking mechanism. Should it?
Auto-select on update:
updatealways callsselectfor the newlyinstalled version. Is this the desired behavior? A user installing an older
version might not want it to become active.
Remove prevents removing selected version: This means to remove the last
installed version, you must first install a different version, select it,
then remove the old one. Is this intentional? Should there be a way to
remove everything for an application?
"latest" resolution differs by operation:
updateresolves "latest" viaCloudsmith (newest available).
selectandremoveresolve "latest" viainstalled packages (newest installed). This is reasonable but should be
explicit.
Version ordering: Versions are compared by string sort (via
Comparableon the full package string). This works for date-based nightly versions but
may not correctly order semver release versions (e.g., "0.9.0" > "0.10.0"
by string sort). Is this a bug?
No certificate validation: The SSL context disables server certificate
verification. This is a security concern — MITM attacks could serve
malicious binaries. The SHA-512 checksum mitigates this only if the checksum
itself comes over a trusted channel, but both URL and checksum come from the
same unverified connection.
Bootstrap checksum algorithm mismatch: Bootstrap uses SHA-256, ponyup
itself uses SHA-512. Is this intentional? Should they align?
Optional binary handling on version switch: When selecting a new version,
if the old version had an optional binary (e.g., pony-lsp) but the new one
doesn't, is the old symlink removed? The spec says it should be (INV-4/INV-5)
— need to verify the code does this.
Self-update: ponyup can install ponyup (it's a recognized application).
What happens when the running binary replaces itself? On Unix this works
because the old inode stays valid until the process exits. On Windows this
may fail. Is self-update tested/supported?
Lockfile entry for ponyup itself: When ponyup updates itself, it adds
an entry to the lockfile and creates a symlink. But the bootstrap also
creates the initial binary. Is there a potential conflict between the
bootstrapped binary and the lockfile-managed one?
All reactions