- Introduction
- High-Level Architecture
- Module Overview
- Directory Layout on Disk (Pack Root)
- CLI Layer (
cmd/commands/) - Installer Core (
cmd/installer/) - XML Data Model (
cmd/xml/) - Utilities (
cmd/utils/) - Cryptography (
cmd/cryptography/) - User Interface (
cmd/ui/) - Error Handling (
cmd/errors/) - Key Data Flows
- Version Resolution Strategy
- Security Model
- Concurrency Model
- Design Patterns
- External Dependencies
- Testing Strategy
cpackget is a command-line package manager for Open-CMSIS-Pack software packs, developed as part of the CMSIS-Toolbox. It enables developers to discover, install, update, and remove CMSIS-Pack packages on their local systems. The project is written in Go and licensed under Apache 2.0.
- Install packs from the public index, URLs, or local files
- Manage a local pack root directory with cached downloads
- Resolve and install pack dependencies
- Update the public pack index and cached PDSC files
- List installed, cached, and publicly available packs
- Verify pack integrity via SHA-256 checksums
- Digitally sign and verify packs using X.509 certificates or PGP keys
- Present and manage End User License Agreements (EULA)
┌───────────────────────────────────────────────────────────────────┐
│ CLI Entry Point │
│ cmd/main.go │
│ (logging, signal handling, version, user-agent) │
└───────────────────────────────┬───────────────────────────────────┘
│
▼
┌───────────────────────────────────────────────────────────────────┐
│ Command Layer (Cobra) │
│ cmd/commands/*.go │
│ ┌──────┐ ┌────┐ ┌──────┐ ┌────────┐ ┌──────────────┐ │
│ │ add │ │ rm │ │ list │ │ update │ │ update-index │ ... │
│ └──┬───┘ └─┬──┘ └──┬───┘ └───┬────┘ └──────┬───────┘ │
└─────┼────────┼────────┼──────────┼──────────────┼─────────────────┘
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌───────────────────────────────────────────────────────────────────┐
│ Installer Core Engine │
│ cmd/installer/root.go │
│ (pack root management, index I/O, coordination) │
│ │
│ ┌───────────────────────┐ ┌──────────────────────┐ │
│ │ cmd/installer/pack │ │ cmd/installer/pdsc │ │
│ │ (PackType: fetch, │ │ (PdscType: install, │ │
│ │ install, uninstall, │ │ uninstall, local │ │
│ │ validate, EULA) │ │ repository mgmt) │ │
│ └──────────┬────────────┘ └──────────┬───────────┘ │
└─────────────────┼──────────────────────────────┼──────────────────┘
│ │
┌───────────┴─────┬──────────────────┬─────┴──────────┐
▼ ▼ ▼ ▼
┌───────────┐ ┌──────────────┐ ┌────────────┐ ┌─────────┐
│ cmd/xml/ │ │ cmd/utils/ │ │ cmd/crypto │ │ cmd/ui/ │
│ (PDSC, │ │ (download, │ │ (checksum, │ │ (EULA │
│ PIDX │ │ files, I/O, │ │ signing, │ │ TUI) │
│ parsing) │ │ semver) │ │ verify) │ │ │
└───────────┘ └──────────────┘ └────────────┘ └─────────┘
The architecture follows a layered design:
- CLI Layer — Parses flags, checks arguments, and passes them to the installer
- Installer Core — Coordinates all pack operations: add, remove, list, update
- Domain Types —
PackTypeandPdscTyperepresent a pack/PDSC through its lifecycle - Support Modules — XML parsing, utilities, cryptography, and UI
| Package | Path | Responsibility |
|---|---|---|
main |
cmd/main.go |
Entry point, logging setup, signal watcher, version |
commands |
cmd/commands/ |
Cobra CLI command definitions and flag handling |
installer |
cmd/installer/ |
Core business logic for pack management |
xml |
cmd/xml/ |
PDSC and PIDX XML data structures and I/O |
utils |
cmd/utils/ |
File operations, HTTP downloads, semver, security utilities |
cryptography |
cmd/cryptography/ |
Checksum generation/verification, digital signatures |
ui |
cmd/ui/ |
Terminal UI for EULA display and interaction |
errors |
cmd/errors/ |
Predefined error types |
cpackget manages an on-disk directory structure referred to as the Pack Root. The default location is OS-dependent:
- Windows:
%LOCALAPPDATA%\Arm\Packs - Linux/macOS:
$XDG_CACHE_HOME/arm/packs(fallback:~/.cache/arm/packs)
It can also be specified via the CMSIS_PACK_ROOT environment variable or the --pack-root flag.
<PackRoot>/
├── .Download/ # Cached downloaded .pack files
│ ├── Vendor.PackName.1.0.0.pack
│ ├── Vendor.PackName.1.0.0.pdsc
│ └── Vendor.PackName.1.0.0.LICENSE.txt # Extracted licenses
├── .Local/
│ └── local_repository.pidx # Index of locally-added PDSC packs
├── .Web/
│ ├── index.pidx # Public pack index (from Keil/Arm)
│ ├── cache.pidx # Local cache index (tracks cached PDSCs)
│ └── Vendor.PackName.pdsc # Cached PDSC files for public packs
├── Vendor/
│ └── PackName/
│ └── 1.0.0/ # Extracted pack contents
│ ├── Vendor.PackName.pdsc
│ └── ...pack files...
└── ...
| File | Purpose |
|---|---|
.Web/index.pidx |
Main public pack index, downloaded from the upstream source |
.Web/cache.pidx |
Locally-maintained cache tracking which PDSC files exist in .Web/ |
.Local/local_repository.pidx |
Registry of packs added via local PDSC file references |
As of v0.7.0, the pack root is read-only by default.
cpackget manages file permissions internally via
LockPackRoot() / UnlockPackRoot() and SetReadOnly() / UnsetReadOnly() utilities.
This prevents accidental modification of managed content.
The CLI is built with Cobra.
Each command is defined in its own file and registered in root.go.
Sets up the Cobra root command with global flags and the configureInstaller pre-run hook:
| Global Flag | Short | Description |
|---|---|---|
--pack-root |
-R |
Path to pack root (overrides CMSIS_PACK_ROOT) |
--verbose |
-v |
Enable debug-level logging |
--quiet |
-q |
Suppress all non-error output |
--concurrent-downloads |
-C |
Max parallel HTTP connections (default: 5) |
--timeout |
-T |
HTTP download timeout in seconds (0 = disabled) |
--version |
-V |
Print version and exit |
The configureInstaller pre-run hook:
- Resolves the pack root directory (flag → env var → OS default)
- Sets the pack root via
installer.SetPackRoot() - Validates its existence
| Command | File | Description |
|---|---|---|
init |
init.go |
Creates pack root and downloads the public index |
add |
add.go |
Installs packs from index, URL, local file, or PDSC |
rm |
rm.go |
Removes installed packs (with optional cache purge) |
list |
list.go |
Lists installed/cached/public packs with filtering |
update |
update.go |
Updates installed packs to latest versions |
update-index |
update_index.go |
Refreshes the public index and cached PDSC files |
checksum-create |
checksum.go |
Generates .checksum digest files for packs |
checksum-verify |
checksum.go |
Verifies pack integrity against .checksum files |
signature-create |
signature.go |
Digitally signs packs (X.509 or PGP) |
signature-verify |
signature.go |
Verifies signed packs |
connection |
connection.go |
Tests online connectivity |
| Subcommand | Description |
|---|---|
list (default) |
Lists all installed packs |
list --cached |
Lists packs in .Download/ |
list --public |
Lists all non-deprecated packs from the public index |
list --public --deprecated |
Lists all packs from the public index |
list --deprecated |
Lists deprecated packs from the public index |
list --updates |
Lists packs with newer versions available |
list --filter |
Filters results (case-sensitive, accepts multiple expressions) |
list-required |
Lists dependencies of installed packs |
This is the heart of cpackget, containing all business logic for managing packs.
The Installation singleton (type PacksInstallationType) holds:
type PacksInstallationType struct {
PackRoot string // Absolute path to pack root
WebDir string // .Web/ directory path
DownloadDir string // .Download/ directory path
LocalDir string // .Local/ directory path
PublicIndex xml.PidxXML // Parsed public index (index.pidx)
PublicIndexXML xml.PidxXML // In-memory public index state
LocalPidx xml.PidxXML // Parsed local repository index
CacheIdx xml.PidxXML // Parsed cache index (cache.pidx)
// ...
}GetDefaultCmsisPackRoot()— Returns OS-dependent default pack root pathAddPack()— Main entry point for installing a packRemovePack()— Main entry point for removing a packAddPdsc()/RemovePdsc()— Manages local PDSC file referencesUpdatePack()— Updates one or all packs to latest versionsInitializeCache()— Buildscache.pidxfrom existing PDSC files in.Web/CheckConcurrency()— Validates and adjusts the concurrent-downloads settingDownloadPDSCFiles()— Downloads all PDSC files from the public index in parallel, optionally skipping deprecated packsUpdateInstalledPDSCFiles()— Refreshes already-cached PDSC files from the indexUpdatePublicIndexIfOnline()— Updates the public index only when connectivity is availableUpdatePublicIndex()— Downloads and updates the public index and PDSC files, with option to skip deprecated PDSC filesListInstalledPacks()— Lists packs with various filter modes; supports--deprecatedflag to show only deprecated packs (hidden by default in--publiclisting)FindPackURL()— Resolves a pack ID to a download URL from the indexSetPackRoot()— Initializes theInstallationsingleton and directory pathsReadIndexFiles()— Loadsindex.pidx,local_repository.pidx, andcache.pidxLockPackRoot()/UnlockPackRoot()— Manages read-only permissions on pack root
Represents a single pack through all stages of its life: discovery → download → validation → installation → removal.
type PackType struct {
xml.PdscTag // Embedded: Vendor, Name, Version, URL
path string // Source path (URL, file path, or pack ID)
isPackID bool // True if input was a pack ID (vs file/URL)
isPublic bool // True if found in public index
isInstalled bool // True if already installed locally
isDownloaded bool // True if cached in .Download/
versionModifier int // Version resolution strategy
targetVersion string // Resolved target version
Requirements Requirements // Dependencies from PDSC
zipReader *zip.ReadCloser
// ...
}preparePack() → fetch() → validate() → install()
├── checkEula()
├── extract files
└── update indexes
preparePack()— Parses the pack path/ID, looks up metadata, checks if already installedfetch()— Downloads the.packfile or validates a local file referencevalidate()— Checks that the pack content is intact and contains a PDSC filepurge()— Removes the cached.packfile from.Download/install()— Extracts the ZIP, handles EULA, updates cache indexuninstall()— Removes extracted pack directory, cleans empty parent dirscheckEula()/extractEula()— Handles license agreement display and extractionresolveVersionModifier()— Picks the actual version to install based on a modifier (@^,@~,@>=,@latest)loadDependencies()— Parses the PDSC<requirements>tag for package dependenciesRequirementsSatisfied()— Returnstrueif all pack dependencies are installedPackID()— ReturnsVendor.PackNameidentifierPackIDWithVersion()— ReturnsVendor.PackName.VersionidentifierPackFileName()— ReturnsVendor.PackName.Version.packfilenamePdscFileName()/PdscFileNameWithVersion()— Returns PDSC filename (with or without version)GetVersion()/GetVersionNoMeta()— Returns the resolved version (with or without metadata)Lock()/Unlock()— Sets or removes read-only permissions on the pack directory
Represents packs registered through local PDSC file references (not from the public index).
type PdscType struct {
xml.PdscTag // Embedded: Vendor, Name, Version, URL
path string // Local file path to the PDSC
}preparePdsc()— Validates the PDSC file and extracts pack metadatatoPdscTag()— Converts to anxml.PdscTagso it can be written to an index fileinstall()— Adds an entry tolocal_repository.pidxuninstall()— Removes the entry fromlocal_repository.pidx
Represents the .pidx index files (index.pidx, local_repository.pidx, cache.pidx).
type PidxXML struct {
SchemaVersion string
Vendor string
URL string
Timestamp string
Pindex struct {
Pdscs []PdscTag // List of all pack references
}
// Internal lookup maps for O(1) access
pdscList map[string][]PdscTag // key → PdscTags
pdscListName map[string]string // vendor.name → key
deprecatedDate time.Time // today UTC, set once per Read()
}
type PdscTag struct {
URL string `xml:"url,attr"`
Vendor string `xml:"vendor,attr"`
Name string `xml:"name,attr"`
Version string `xml:"version,attr"`
Deprecated string `xml:"deprecated,attr,omitempty"`
Replacement string `xml:"replacement,attr,omitempty"`
isDeprecated bool // cached flag, computed on insert
}NewPidxXML()— Creates a new PidxXML instance for a given fileGetFileName()/SetFileName()— Access the underlying file pathGetListSizes()— Returns the number of entries in both lookup mapsClear()— Resets all entriesAddPdsc()/AddReplacePdsc()— Insert or update entriesReplacePdscVersion()— Updates the version of an existing entryEmpty()— Checks if the index has no entriesRemovePdsc()— Remove entries by keyHasPdsc()— Returns the index of a PdscTag if present, orPdscIndexNotFoundListPdscTags()/FindPdscTags()/FindPdscNameTags()— Query with optional filteringCheckTime()— Validates 24-hour freshness for update throttlingRead()/Write()— XML file I/O with timestamp handlingKey()/VName()— ReturnsVendor.Name.VersionorVendor.NameidentifierYamlPackID()— ReturnsVendor::Name@VersionformatPackURL()— Constructs the full.packdownload URL (PdscTag method)PdscFileName()— Returns the.pdscfilename (PdscTag method)IsDeprecated()— Returns the cached deprecated flag. Computed viacomputeIsDeprecated()when a PdscTag is inserted (Read,AddPdsc,AddReplacePdsc). UsesPidxXML.deprecatedDate(today UTC, set once perNewPidxXML/Read) as reference (PdscTag method)
Represents a .pdsc pack description file (PACK.xsd schema).
type PdscXML struct {
Vendor string
Name string
License string
URL string
ReleasesTag struct {
Releases []ReleaseTag // <release> tags
}
RequirementsTag struct {
Packages []PackagesTag // <packages> groups inside <requirements>
}
FileName string
}
type ReleaseTag struct {
Version string
Date string
URL string // Override download URL per release
}
// PackagesTag wraps a <packages> group, which contains one or more <package> entries
type PackagesTag struct {
Packages []PackageTag
}
type PackageTag struct {
Vendor string
Name string
Version string // Version range (e.g., "1.0.0:2.0.0")
}NewPdscXML()— Creates a new PdscXML instance for a given fileLatestVersion()— Returns the newest release versionAllReleases()— Lists all available versionsFindReleaseTagByVersion()— Looks up a specific release by version stringTag()— Converts to a PdscTag for use in index filesRead()/Write()— Loads and saves the PDSC XML fileBaseURL()— Returns the pack URL with a trailing slashPackURL()— Constructs the download URL for a specific versionDependencies()— Parses requirements with version range resolution
This is how cpackget interprets user input. Validation helpers:
IsPackVendorNameValid()/IsPackNameValid()— Checks vendor/pack names against the PACK.xsd regexIsPackVersionValid()— Checks that a version string matches the semver patternFormatPackVersion()— Converts an internal[Name, Vendor, Version]triple to theVendor::Name@VersionformatFormatVersions()— Converts internal version ranges (e.g.1.0.0:_) to display format (e.g.>=1.0.0)
The ExtractPackInfo() function parses any of these formats:
| Input Format | Example | Type |
|---|---|---|
| Pack ID (dotted) | Vendor.PackName.1.0.0 |
Pack ID |
| Pack ID (YAML) | Vendor::PackName@1.0.0 |
Pack ID |
| Version modifier | Vendor::PackName@^1.0.0 |
Pack ID with modifier |
| Pack file path | /path/to/Vendor.PackName.1.0.0.pack |
File reference |
| Pack URL | https://host/Vendor.PackName.1.0.0.pack |
URL reference |
| PDSC file | Vendor.PackName.pdsc |
PDSC reference |
Returns a PackInfo struct:
type PackInfo struct {
Location string // URL or file path (empty for pack IDs)
Vendor string
Pack string
Version string
Extension string // ".pack", ".pdsc", or ""
IsPackID bool
VersionModifier int
}| Constant | Syntax | Meaning |
|---|---|---|
ExactVersion (0) |
@1.2.3 |
Install exactly this version |
LatestVersion (1) |
@latest |
Install the latest available version |
AnyVersion (2) |
(none) | Use any/latest if not installed |
GreaterVersion (3) |
@>=1.2.3 |
Install any version ≥ specified |
GreatestCompatibleVersion (4) |
@^1.2.3 |
Same major version, latest minor/patch |
PatchVersion (5) |
@~1.2.3 |
Same major.minor, latest patch |
RangeVersion (6) |
1.0.0:2.0.0 |
Within specified range |
Extended semver comparison functions built on top of golang.org/x/mod/semver:
SemverCompare()— Compares two version strings (handles leading zeros)SemverCompareRange()— Checks if a version is within alow[:high]rangeSemverMajor()/SemverMajorMinor()— Extracts version componentsSemverHasMeta()— Checks if a version string contains+metadata, returns the metadata and a booleanSemverStripMeta()— Removes+metadatasuffixVersionList()— Joins a slice of versions into a comma-separated string (strips metadata)
FileURLToPath()— Convertsfile://URIs to filesystem pathsSetEncodedProgress()/GetEncodedProgress()— Enables/queries machine-readable progress modeSetSkipTouch()/GetSkipTouch()— Controls whether touch operations are skippedSetUserAgent()— Sets the HTTP User-Agent string for downloadsDownloadFile()— HTTP GET with progress bar, ETag caching, and configurable timeoutCheckConnection()— Connectivity test to a given URLFileExists()/DirExists()— Checks existence of files or directoriesEnsureDir()— Recursive directory creationSameFile()— Checks if two paths point to the same file (viaos.SameFile)CopyFile()/MoveFile()— File manipulation with permission handlingReadXML()/WriteXML()— Read/write Go structs from/to XML filesListDir()— Lists files/directories with optional regex filtering (non-recursive)TouchFile()— Creates or updates the modify timestamp of a fileIsBase64()— Checks whether a string is valid base64IsEmpty()— Checks whether a directory is emptyRandStringBytes()— Generates a random alphanumeric string of length nCountLines()— Returns the number of lines in a stringFilterPackID()— Matches a pack ID against a space-separated filter stringIsTerminalInteractive()— Returnstrueif stdout is a character deviceCleanPath()— Path normalizationSetReadOnly()/SetReadOnlyR()— Sets file/directory to read-only (optionally recursive)UnsetReadOnly()/UnsetReadOnlyR()— Sets file/directory to read-write (optionally recursive)GetListFiles()— Reads lines from a file and returns them as a pack argument list
- Constants:
MaxDownloadSize(20 GB),DownloadBufferSize(4 KB) SecureCopy()— Size-limited streaming with abort supportSecureInflateFile()— ZIP extraction with path traversal prevention (rejects../)
StartSignalWatcher()— Starts a goroutine that listens forSIGINT/SIGTERMStopSignalWatcher()— Stops the signal listener cleanlyShouldAbortFunction— Global callback checked during long-running operations
Machine-readable progress output for tool integration (IDEs, CI systems).
NewEncodedProgress(max int64, instNo int, filename string) creates a new progress tracker:
| Parameter | Description |
|---|---|
max |
Total size in bytes (used to calculate percentage) |
instNo |
Instance number (auto-incremented per download, links output to a filename) |
filename |
Name of the file being processed |
The EncodedProgress struct implements io.Writer, so it can be passed directly
to an io.MultiWriter alongside the file output.
Each Write() call updates the internal byte counter and emits a log line
when the percentage changes.
Output format — first message includes all fields, subsequent messages only percentage and count:
[I<instNo>:F"<filename>",T<max>,P<percent>] // initial
[I<instNo>:P<percent>,C<current>] // updates
Field codes used across encoded progress messages:
| Code | Meaning |
|---|---|
I |
Instance number (always counts up), connected to the filename |
F |
Filename currently processed |
T |
Total bytes of file or number of files |
P |
Currently processed percentage |
C |
Currently processed bytes or number of files |
J |
Total number of files being processed |
L |
License file follows |
O |
Online connection status (offline / online) |
Generates and verifies .checksum files containing SHA-256 digests of all files inside a .pack (ZIP).
File naming convention: Vendor.PackName.1.0.0.sha256.checksum
File format: Standard digest file with lines of <hex-digest> <filename>
WriteChecksumFile()— Writes the digest file to diskGenerateChecksum()— Creates a.checksumfile for a given packVerifyChecksum()— Validates a pack against its.checksumfilegetDigestList()— Computes SHA-256 of every file in the ZIP
Signs packs by embedding a cryptographic signature in the ZIP comment field.
cpackget-v<version>:<mode>:<payload1>[:<payload2>]
| Mode | Code | Payload | Security Level |
|---|---|---|---|
| Full (X.509) | f |
<base64-cert>:<base64-signed-hash> |
Highest — integrity + authenticity |
| Cert-only | c |
<base64-cert> |
Medium — authenticity only (no digest) |
| PGP | p |
<base64-pgp-message> |
High — integrity + authenticity |
.pack file
│
├─→ calculatePackHash() → SHA-256 of ZIP content
├─→ loadCertificate() → Parse X.509 certificate
├─→ signPackHashX509() → RSA-sign the hash with private key
├─→ Build signature string → "cpackget-v...:f:<cert>:<signed-hash>"
└─→ embedPack() → Write signature into ZIP comment → .pack.signed
Main entry points:
SignPack()— Top-level function for creating a pack signature (X.509 or PGP)VerifyPackSignature()— Top-level function for verifying a signed pack
.pack.signed file
│
├─→ validateSignatureScheme() → Parse mode from ZIP comment
├─→ Extract certificate/key → Decode base64 payload
├─→ calculatePackHash() → SHA-256 of ZIP content
└─→ verifyPackFullSignature() → RSA-verify signed hash with certificate
calculatePackHash()— SHA-256 hash of ZIP file contentsdetectKeyType()— Identifies PKCS#1 vs. PKCS#8 private key formatdisplayCertificateInfo()— Pretty-prints X.509 certificate detailsgetUnlockedKeyring()— Loads and unlocks PGP keyringsisPrivateKeyFromCertificate()— Validates that a private key matches a certificatesanitizeVersionForSignature()— Normalizes a version string for embedding in a signature
An interactive terminal UI built with gocui for displaying pack licenses:
┌─── License: PackName ─────────────────────┐
│ │
│ <scrollable license text> │
│ │
│ (Arrow keys / Page Up / Page Down) │
└───────────────────────────────────────────┘
┌───────────────────────────────────────────┐
│ [A]ccept [D]ecline [E]xtract License │
└───────────────────────────────────────────┘
Key functions:
DisplayAndWaitForEULA()— High-level function that shows a license and returns whether the user acceptedNewLicenseWindow()— Creates and configures the TUI windowSetup()— Initializes the gocui layout and key bindingsPromptUser()— Runs the event loop and returns the user's choice
Fallback behavior: When the terminal is not interactive (pipes, CI), falls back to stdin prompt.
Minimum terminal dimensions: 8 rows × prompt text width.
All errors are predefined constants in errors.go, allowing consistent error checking with errors.Is():
| Category | Examples |
|---|---|
| Pack naming | ErrBadPackName, ErrBadPackURL |
| Pack state | ErrPackNotInstalled, ErrPackVersionNotAvailable, ErrPackAlreadyInstalled |
| File system | ErrFileNotFound, ErrFailedDecompressingFile, ErrInsecureZipFileName |
| Network | ErrOffline, ErrHTTPtimeout, ErrBadRequest |
| Security | ErrIntegrityCheckFailed, ErrCannotVerifySignature, ErrFileTooBig |
| Index | ErrIndexPathNotSafe, ErrInvalidPublicIndexReference |
| EULA | ErrEula (internal, not surfaced to user) |
Helper functions:
Is()— Wrapserrors.Is()for convenienceAlreadyLogged()— Wraps errors to prevent the same message from being logged twice as the error travels up the call stack
User input (pack ID / URL / path / PDSC)
│
▼
commands/add.go — Parse flags, iterate pack arguments
│
▼
installer.AddPack()
├── ReadIndexFiles() — Load index.pidx, local_repository.pidx, cache.pidx
├── preparePack() — Parse input, resolve metadata, check install status
├── FindPackURL() — Look up download URL in public index (if pack ID)
├── pack.fetch() — Download .pack file or validate local file
├── pack.install()
│ ├── Validate ZIP contents and PDSC presence
│ ├── Check EULA (display TUI or auto-accept)
│ ├── Extract files to <PackRoot>/Vendor/Name/Version/
│ ├── Cache .pack in .Download/
│ └── Update cache.pidx
└── pack.loadDependencies() — Recursively call AddPack() for requirements
commands/update_index.go
│
▼
installer.UpdatePublicIndex()
├── Check timestamp (24-hour freshness rule)
├── Download new index.pidx from upstream URL
├── Compare old vs. new entries
├── Download updated/new PDSC files (concurrent, via semaphore)
│ └── Skip PDSC files where Deprecated date ≤ today
├── Update cache.pidx to reflect changes
└── Remove deprecated entries
commands/rm.go
│
▼
installer.RemovePack()
├── ReadIndexFiles()
├── preparePack() — Locate installed pack
├── pack.uninstall() — Remove extracted directory
├── (if --purge) pack.purge() — Remove from .Download/
└── Update cache.pidx
User specifies: Vendor::PackName@^1.2.0
│
▼
ExtractPackInfo() → versionModifier = GreatestCompatibleVersion
│
▼
resolveVersionModifier()
├── Query public index for all available versions
├── Filter by major version = 1
├── Sort by semver
└── Return highest match (e.g., 1.9.3)
cpackget supports flexible version matching, similar to npm or apt:
| Modifier | Symbol | Resolution Logic |
|---|---|---|
| Exact | @1.2.3 |
Must match exactly |
| Latest | @latest |
Highest available version |
| Any | (omitted) | Latest if not installed; current if installed |
| Greater-or-equal | @>=1.2.3 |
Highest version ≥ 1.2.3 |
| Compatible (caret) | @^1.2.3 |
Highest with same major (≥1.2.3, <2.0.0) |
| Patch (tilde) | @~1.2.3 |
Highest with same major.minor (≥1.2.3, <1.3.0) |
| Range | 1.0.0:2.0.0 |
Any version in [1.0.0, 2.0.0] |
Versions follow Semantic Versioning 2.0 with optional
pre-release (-alpha.1) and build metadata (+meta) suffixes.
Build metadata is stripped during comparison.
- Size limits:
MaxDownloadSize(20 GB) prevents decompression bombs - Secure copy:
SecureCopy()enforces byte-level size limits during streaming - Path traversal prevention:
SecureInflateFile()rejects ZIP entries containing../ - Insecure ZIP filename detection:
ErrInsecureZipFileNameerror
- SHA-256 checksums of individual files within
.packarchives - Checksum files use a standard digest format so other tools can read them too
- X.509 Full mode: Certificate + RSA-signed SHA-256 hash provides integrity and authenticity
- PGP mode: Detached PGP signature of pack hash
- Certificate validation: Expiry checks, key usage validation, key-cert matching
- Pack root directory and contents are set read-only after installation
- Permissions are managed exclusively by cpackget to prevent tampering
LockPackRoot()/UnlockPackRoot()are called before and after every write operation
- Proxy support via
HTTP_PROXY/HTTPS_PROXYenvironment variables - Configurable TLS certificate verification (can be disabled for testing)
- User-agent identification:
CMSIS-Toolbox cpackget/<version>
Batch downloads (e.g., update-index) use a semaphore to limit the number of parallel goroutines:
sem := semaphore.NewWeighted(int64(concurrentDownloads))
for _, pdsc := range pdscsToUpdate {
sem.Acquire(ctx, 1)
go func(p PdscTag) {
defer sem.Release(1)
downloadPdsc(p)
}(pdsc)
}- Default concurrency: 5 parallel connections (
--concurrent-downloads) - Setting to 0 disables parallelism entirely
- Results are collected in a thread-safe
lockedSlicestruct
A dedicated goroutine listens for OS signals (SIGINT, SIGTERM).
Long-running operations check the abort flag via ShouldAbortFunction
so they can stop cleanly when the user presses Ctrl+C.
EncodedProgress uses a mutex so multiple goroutines can safely update
progress at the same time.
The output format is machine-readable, designed for IDE and tool integration.
| Pattern | Usage |
|---|---|
| Singleton | installer.Installation — Single global pack root state |
| Command | Each Cobra command wraps a CLI action into its own object |
| Factory | preparePack() / preparePdsc() — Build pack/PDSC objects from raw user input |
| Strategy | Version modifiers pick the right version-matching logic at runtime |
| Template Method | configureInstaller pre-run hook runs the same setup for every command |
| Observer | Signal watcher notifies long-running operations of user interrupts |
| Wrapper | installer.AddPack() / RemovePack() — simple interface hiding complex steps |
| Fixed Error Constants | Predefined error values for consistent error-type checking |
| Dependency | Version | Purpose |
|---|---|---|
spf13/cobra |
v1.10.2 | CLI framework and command routing |
spf13/pflag |
v1.0.10 | POSIX/GNU-style flag parsing (used by Cobra) |
spf13/viper |
v1.21.0 | Configuration management |
sirupsen/logrus |
v1.9.4 | Structured logging |
schollz/progressbar |
v3.19.0 | Terminal progress bars for downloads |
jroimartin/gocui |
v0.5.0 | Terminal UI for EULA display |
ProtonMail/gopenpgp |
v2.9.0 | PGP key handling and signature creation |
golang.org/x/mod |
v0.33.0 | Semantic version comparison |
golang.org/x/sync |
v0.19.0 | Semaphore for concurrent downloads |
golang.org/x/net |
v0.51.0 | Network utilities |
golang.org/x/term |
v0.40.0 | Terminal capability detection |
stretchr/testify |
v1.11.1 | Test assertions and mocking |
lu4p/cat |
v0.1.5 | File content type detection |
Go version: 1.25.0
Each package has comprehensive unit tests (*_test.go files)
co-located with production code.
Tests use the testify assertion library.
root_unix_test.go/root_windows_test.go— OS-specific file permission behaviorsignal_unix_test.go/signal_windows_test.go— Signal handling differences
The testdata/ directory provides fixtures for testing:
| Path | Purpose |
|---|---|
testdata/*.pidx |
Sample and malformed index files for parsing tests |
testdata/devpack/ |
Mock pack PDSC files at different versions |
testdata/integration/ |
Full integration test fixtures with versioned packs |
testdata/utils/ |
File listing test data |
Integration test fixtures in testdata/integration/ include:
- Sample public indexes (
SamplePublicIndex.pidx) - Packs at various versions (
0.1.0/,1.2.3/,1.2.4/) - Pre-release versions (
1.2.3-alpha.1.0/) - Metadata versions (
1.2.3+meta/) - Dependency chains (
dependencies/) - Concurrent download scenarios (
concurrent/) - Bad/malformed inputs for negative testing
- Makefile targets:
make test,make build,make coverage-report - CI pipelines: GitHub Actions for build, test, and release workflows
- Linting: GolangCI-Lint for static analysis