Dario Gabriel LipicarandClaude Opus 4.8 804047e205 feat: report version, commit, and dev/portable build in lgpm --version
Bake a nix-generated build-info header into lgpm so `--version` reports the
release version (a VERSION file on release/** branches, pre-release-<sha7> on
clean master builds, or "dev" for dirty local builds), this repo's commit with
a -dirty marker, whether it is a dev or portable build, and the locked commits
of the flake inputs. Follows the logos-basecamp approach.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 15:48:09 -03:00
2026-05-30 17:35:27 -04:00
2026-05-30 14:40:42 -04:00

logos-package-manager

C++ library and CLI for local Logos package management — installing .lgx packages, scanning installed modules, platform variant selection, and LGX extraction.

This repo handles local operations only. It does not fetch packages from the network — that is the responsibility of logos-package-downloader.

Library API

#include <package_manager_lib.h>

PackageManagerLib pm;

// Configure embedded directories (multiple, read-only at runtime)
pm.setEmbeddedModulesDirectory("/path/to/embedded/modules");       // clears + sets first
pm.addEmbeddedModulesDirectory("/path/to/more/embedded/modules");  // appends additional
pm.setEmbeddedUiPluginsDirectory("/path/to/embedded/plugins");
pm.addEmbeddedUiPluginsDirectory("/path/to/more/embedded/plugins");

// Configure user directories (single, writable, where new packages are installed)
pm.setUserModulesDirectory("/path/to/user/modules");
pm.setUserUiPluginsDirectory("/path/to/user/plugins");

// Install from a local .lgx file (auto-detects type, installs to user directory)
std::string errorMsg;
std::string installedPath = pm.installPluginFile("/path/to/module.lgx", errorMsg);
// skipIfNotNewer: skip installation if an equal-or-newer version is already installed
std::string path = pm.installPluginFile("/path/to/module.lgx", errorMsg, /*skipIfNotNewer=*/true);

// Scan installed packages (returns struct vectors)
// Each InstalledPackage carries all manifest.json fields plus installDir,
// mainFilePath, installType, and a nested Hashes struct. For ui_qml packages,
// `view` is the required QML entry point and `mainFilePath` is the optional
// backend plugin path.
std::vector<InstalledPackage> modules   = pm.getInstalledModules();     // type=core only
std::vector<InstalledPackage> uiPlugins = pm.getInstalledUiPlugins();   // type=ui,ui_qml only
std::vector<InstalledPackage> all       = pm.getInstalledPackages();    // all types

// Direct dependencies are already on InstalledPackage::dependencies; the
// library does not ship a parallel name-only API. Callers that want a flat
// transitive list build it over resolveDependencies / resolveDependents
// (see the `lgpm` CLI `deps` / `dependents` subcommands for a reference
// implementation — ~15 lines of BFS with a seen-set).

// Dependency graph as a rich struct tree. Returns std::nullopt when the
// package is not installed. Children with status == NotInstalled are
// leaves; Cycle marks a back-edge during the walk.
std::optional<DependencyTreeNode> tree = pm.resolveDependencies("waku_module");

// Reverse-edge tree, rooted at the queried package. `children` are the
// direct reverse neighbours; their `children` are transitive ones.
// Returns std::nullopt when the package is not installed.
std::optional<DependentTreeNode> users = pm.resolveDependents("waku_module");

// Both tree types ship a BFS-with-dedup `flatten()` — handy for callers
// that only want "every unique descendant", no tree shape.
if (tree)  std::vector<DependencyTreeNode> allDeps   = tree->flatten();
if (users) std::vector<DependentTreeNode>  allUsers  = users->flatten();

// JSON serialization lives in a separate header (opt-in; the core lib
// header does not pull in nlohmann::json). nlohmann ADL `to_json` hooks
// are provided for every struct above.
#include <package_manager_json.h>
#include <nlohmann/json.hpp>
std::string modulesJson = nlohmann::json(modules).dump(2);

// Platform variant helpers
std::string variant = PackageManagerLib::currentPlatformVariant();     // e.g. "darwin-arm64"
std::vector<std::string> variants = PackageManagerLib::platformVariantsToTry(); // ordered fallback list

// Low-level LGX extraction
pm.extractLgxPackage("/path/to/module.lgx", "/output/dir", errorMsg);
pm.copyLibraryFromExtracted("/extracted/dir", "/target/dir",
                            /*isCoreModule=*/true, outModuleName, errorMsg);

// Signature verification policy
pm.setSignaturePolicy(SignaturePolicy::WARN);     // NONE, WARN (default), REQUIRE
pm.setKeyringDirectory("/path/to/trusted-keys");  // default: ~/.config/logos/trusted-keys/

// Standalone signature verification
SignatureVerificationResult sigInfo = pm.verifyPackageSignature("/path/to/module.lgx");
// sigInfo.is_signed, sigInfo.signature_valid, sigInfo.package_valid,
// sigInfo.signer_did, sigInfo.signer_name, sigInfo.signer_url, sigInfo.trusted_as, sigInfo.error

// Utilities
bool newer = PackageManagerLib::versionGreaterOrEqual("1.2.0", "1.1.0");
PackageManagerLib::copyDirectoryContents("/src", "/dst", errorMsg);

C API

A C-compatible API is available via lgpm.h for use from non-C++ consumers:

#include <lgpm.h>

lgpm_context_t ctx = lgpm_create();
lgpm_set_embedded_modules_dir(ctx, "/path/to/embedded/modules");
lgpm_add_embedded_modules_dir(ctx, "/path/to/more/embedded/modules");
lgpm_set_user_modules_dir(ctx, "/path/to/user/modules");

// Signature policy
lgpm_set_signature_policy(ctx, "require");  // "none", "warn", "require"
lgpm_set_keyring_path(ctx, "/path/to/trusted-keys");

char* result = lgpm_install_file(ctx, "/path/to/module.lgx", false, NULL, NULL);
lgpm_free_string(result);

// Flat name-only dependency queries (mirror liblogos' logos_core_get_module_*).
// Null-terminated array; caller frees with lgpm_free_string_array.
const char** deps       = lgpm_get_module_dependencies(ctx, "waku_module", false);
const char** allDeps    = lgpm_get_module_dependencies(ctx, "waku_module", true);
const char** dependents = lgpm_get_module_dependents(ctx, "waku_module", false);
const char** allUsers   = lgpm_get_module_dependents(ctx, "waku_module", true);
for (size_t i = 0; deps[i]; ++i) printf("%s\n", deps[i]);
lgpm_free_string_array(deps);
lgpm_free_string_array(allDeps);
lgpm_free_string_array(dependents);
lgpm_free_string_array(allUsers);

lgpm_free(ctx);

CLI (lgpm)

lgpm [options] <command> [arguments]

Commands:
  install --file <path>       Install from a local LGX file
  install --dir <path>        Install all LGX files in a directory
  list                        List installed packages
  info <package>              Show installed package details
  deps <package>              List packages that <package> depends on
  dependents <package>        List installed packages that depend on <package>

Options:
  --modules-dir <path>        Target directory for core modules
  --ui-plugins-dir <path>     Target directory for UI plugins
  --recursive, -r             For deps/dependents: walk the graph transitively
  --json                      Output in JSON format
  --allow-unsigned            Accept unsigned packages without warning
  --require-signatures        Reject unsigned packages and untrusted signers
  --keyring <path>            Override keyring directory
  -h, --help                  Show help
  -v, --version               Show version

Examples

# Install a local .lgx package
lgpm --modules-dir ./modules install --file ./waku_module.lgx

# Install all .lgx files in a directory
lgpm --modules-dir ./modules --ui-plugins-dir ./plugins install --dir ./packages/

# List installed modules
lgpm --modules-dir ./modules --ui-plugins-dir ./plugins list

# Show package info (JSON)
lgpm --modules-dir ./modules info waku_module --json

# What does waku_module depend on? (one name per line)
lgpm --modules-dir ./modules deps waku_module
lgpm --modules-dir ./modules deps waku_module --recursive

# What depends on waku_module?
lgpm --modules-dir ./modules --ui-plugins-dir ./plugins dependents waku_module
lgpm --modules-dir ./modules --ui-plugins-dir ./plugins dependents waku_module -r --json

How to Build

The package manager ships as a C++ library plus the lgpm CLI. There are two flavors: a dev build for local iteration and a portable build for distribution. They differ in which .lgx platform variant the CLI selects when installing packages — the dev build picks the -dev-suffixed variant (e.g. darwin-arm64-dev), the portable build picks the plain one (e.g. darwin-arm64). Installing a package built for the wrong flavor fails with a variant-mismatch error.

Dev Build

A standard Nix derivation whose dependencies live in /nix/store. It is the fastest way to iterate during development but is not portable — it only runs on the machine that built it. It matches the local/dev .lgx packages produced by nix-bundle-lgx (the default, non-portable bundler).

nix build                        # library + CLI (combined, dev)
nix build '.#lib'                # library only
nix build '.#cli'                # CLI only
./result/bin/lgpm --help

Portable Builds

Portable builds select the plain platform variant, matching portable .lgx packages — releases from logos-modules, downloads via logos-package-downloader, or bundles generated with nix bundle --bundler github:logos-co/nix-bundle-lgx#portable .... The cli-bundle-dir and cli-appimage outputs go further and are fully self-contained — no /nix/store references at runtime — for distribution.

Output Platform Format
cli-portable / lib-portable Linux, macOS Nix derivation (portable variant selection)
cli-bundle-dir Linux, macOS Self-contained flat directory with bin/ and lib/
cli-appimage Linux Single-file .AppImage executable
Portable Nix derivation (all platforms)
nix build '.#cli-portable'       # CLI only
nix build '.#lib-portable'       # library only
./result/bin/lgpm --help
Self-contained directory bundle (all platforms)
nix build '.#cli-bundle-dir'
./result/bin/lgpm --help
Linux AppImage (Linux only)
nix build '.#cli-appimage'
./result/lgpm.AppImage --help

Development Shell

nix develop

Note: In zsh, quote targets containing # to prevent glob expansion (e.g., '.#cli').

If you don't have flakes enabled globally:

nix build --extra-experimental-features 'nix-command flakes'

Testing

nix flake check                  # run all tests
nix build .#tests                # build and run tests

Dependencies

  • logos-package — LGX format library
  • nlohmann_json — JSON parsing

Executable doc-tests

The doctests/ directory holds executable docs powered by logos-doctest

Spec What it covers
lgpm-cli.test.yaml Builds lgpm, hand-crafts two .lgx packages, installs them, and exercises every subcommand (install, list, info, deps, dependents).
install-real-module.test.yaml Clones a real module (logos-accounts-module), builds its .lgx via the module's #lgx flake output, and installs it with lgpm.
./doctests/run.sh

Or invoke the doctest CLI directly on a single spec:

# Execute a spec end-to-end (builds lgpm, installs packages, asserts output)
nix run github:logos-co/logos-doctest -- run doctests/lgpm-cli.test.yaml --verbose

# Render the Markdown doc from a spec (into the gitignored outputs/ dir)
nix run github:logos-co/logos-doctest -- generate doctests/lgpm-cli.test.yaml -o doctests/outputs/lgpm-cli.md

Add --report report.html to the run to get a self-contained two-column HTML report — the rendered doc on the left, the exact commands that ran and their output on the right — handy for reviewing a run or publishing from CI:

nix run github:logos-co/logos-doctest -- run doctests/lgpm-cli.test.yaml --report report.html
S
Description
No description provided
Readme
3.6 MiB
Languages
C++ 82.1%
Shell 6.5%
Nix 5.5%
CMake 2.6%
C 2.6%
Other 0.7%