Skip to content

Why ncmake

ernolf edited this page Aug 3, 2026 · 7 revisions

Why ncmake

Packaging a Nextcloud app is a solved problem — there are good tools for it already. ncmake exists because it makes a few different design bets, and this page is honest about which they are, how it compares to the established krankerl, and which files an ncmake app no longer has to carry.

The design bets

Nothing on your host. make build runs composer and npm in throwaway containers picked from the app's own declared PHP and Node versions. You need no PHP, no Node, no version manager — only podman or docker (or RUNTIME=bare if you insist on host tools). You can build a PHP 8.1 app on a machine that has only PHP 8.4, or no PHP at all.

Nothing to configure. ncmake reads what it needs from the files an app has anyway — appinfo/info.xml, composer.json, package.json, .gitignore (see How ncmake understands your app). A standard app needs no build config of its own: drop in the bootstrap stub, run make, done. An app that genuinely deviates sets a single variable in ncmake.mk (see Per-app tuning).

Keep model, not ignore model. What ships is defined as an allowlist of the standard runtime paths, each only when it exists (see Building and packaging). A new dev file cannot leak into a release, because it is not on the list; a missing runtime directory fails loudly instead of shipping a broken app. .nextcloudignore still exists, but only to trim within that set (test ballast inside a shipped vendor package), not as the sole line of defence.

One tool, shared and self-updating. The real Makefile lives once in a per-machine cache and is fetched by a dozen-line stub. Update ncmake and every app on the machine follows — there is no copy of build logic to maintain per repository. NCMAKE_REF pins a version when you want to freeze it.

The workflow lifecycle, not just packaging. ncmake installs your CI workflows from their upstream templates, tracks which are yours and which drifted, and can open a pull request to update them on a schedule (see Workflows and Workflow updater). Releasing is a flow of its own: a validated version bump, a changelog generated from your conventional commits, a signed tag, the tarball, and App Store publishing that signs the exact bytes the store will fetch (see Releasing and App Store).

Compared to krankerl

krankerl is the established CLI for packaging and publishing Nextcloud apps — a Rust binary by Christoph Wurst. It does what it does, but it is a separate binary you have to install (and one that has seen no functional change in years), it runs your build straight on the host toolchain, and its scope ends at packaging, signing and publishing. ncmake does everything krankerl does and then a good deal more: its build tool — make — is already on every system, the build itself runs in throwaway containers so the host needs no PHP, Composer or npm, and the whole App Store and CI lifecycle is covered on top. The table lists ncmake's capabilities first and measures krankerl against each:

Capability ncmake krankerl
Build tool make, already present on every Unix system a Rust binary you install and update first (AUR, .deb, cargo install)
Host toolchain for the build none — composer and npm run in throwaway containers on the app's declared PHP/Node version PHP, Composer and npm must be installed and correct on the host
Build configuration auto-detected from composer.json / package.json, overridable in ncmake.mk hand-listed in krankerl.toml (before_cmds)
Shipped file set an allowlist of runtime paths (keep model), .nextcloudignore trims within it a full copy of the tree minus a .nextcloudignore exclude list
Reproducible staging yes — one staged set feeds dist, rsync and cp alike yes — stages into build/artifacts
Runs in a CI runner yes yes
Package the tarball make dist krankerl package
Sign the package make sign krankerl sign
Publish to the App Store make publish — signs the exact downloaded bytes krankerl publish
Version bump make version — validates against the latest tag, opens the ncmake/release/X.Y.Z branch, bumps info.xml plus package.json/composer.json and the re-synced lockfiles, and commits it krankerl version major/minor/patch — rewrites the version in info.xml, nothing else
Changelog generation make changelog, from your conventional commits
Key and CSR generation make csr
Certificate registration make register
List your releases make list-releases / list-releases-full
List an author's apps make list-for-author
Ratings and comments make ratings
Delete a release make delete-release
Install and auto-update CI workflows yes, from upstream templates
Self-updating, shared across all your apps one cached Makefile, updated once for every app on the machine upgrade the binary per machine
License MIT (REUSE compliant) GPL-3.0

The honest summary: krankerl is enough if all you want is a native binary that packages and publishes, and you are content to install it, maintain a krankerl.toml, and build on a local toolchain. ncmake does that same packaging and publishing but derives the configuration from the app itself, runs every build in containers so the host stays clean, and wraps the whole App Store and CI lifecycle around it — the same job, reasoned out once and kept DRY. Both stage their build for reproducibility, both sign the App Store upload; neither is a fork or reimplementation of the other.

DRY: which files an ncmake app stops carrying

The whole point of deriving everything from the app is that the app stops carrying redundant scaffolding. With ncmake in place, these are gone:

  • krankerl.toml or any equivalent build-recipe file — the build steps come from composer.json and package.json.
  • A hand-written Makefile full of build logic — the repository carries only the dozen-line stub, identical across every app.
  • Bespoke packaging or exclude scripts — the keep model plus .nextcloudignore define the shipped set.
  • A per-app release.yml that re-lists the build steps, the includes and the excludes — the copy-paste that sits, and quietly rots, in countless Nextcloud apps: the CI packaging drifts away from what the maintainer builds locally, and one day a release ships a file set nobody deployed. ncmake's release.yml carries no app-specific data at all. The shipped set comes from the keep model plus .nextcloudignore, the build is make build && make dist, and the tarball is found by glob, so the workflow is byte-identical across every ncmake app. Local build and CI release are the same code path by construction — they cannot disagree, because there is only one of them. The packaging is reasoned out once and lives in one place, instead of being re-derived, and silently diverging, in every repository.
  • Hand-maintained .github/workflows/* — they are installed from upstream templates and kept current by the workflow updater; only genuinely app-specific workflows are written by hand.
  • The github-actions ecosystem in dependabot.yml — the updater owns .github/workflows/, so that entry comes out (npm and Composer stay under Dependabot).
  • Routine CHANGELOG.md editing — the release section is generated from the conventional commits since the last tag, then yours to extend by hand.

What stays is the app itself, and one stub. That is the DRY principle the whole of ncmake is built on: a fact lives in exactly one place, and everything else is derived from it.

Clone this wiki locally