Unofficial
On this page
  1. 01Make the project
  2. 02The starter recipe
  3. 03Your program's source
  4. 04Check it
  5. 05Build the two apps
  6. 06Pack and install
  7. 07A second recipe

Your first own recipe: a program the library lacks, as a QTS package

The App Builder's recipe library builds a long list of programs. When yours is not among them, a project can carry its own recipe: two files in its recipes/ folder, in the library's format. This guide packages cmatrix 2.0, a small terminal program that needs ncurses from the library, as a QTS package for x86_64 and ARM 64-bit units.

  1. ProjectPackage from source
  2. Reciperecipe.json, build.sh
  3. Checkthe lint in the editor
  4. Buildtwo apps, two processors
  5. Packagemake qpkg

The pictures come from the panel and the editor of Bucket & Code Server IDE, with each step done as this guide describes. The data folder is /share/ZFS530_DATA/BucketVS.

01Make the project

On the Projects page, New project asks for the template first. Choose Package from source: a QTS package of programs you compile from source with your own recipes, for x86_64 and arm64.

New project, its templates for QTS packages: Package from source chosen

Call it cmatrix. The project's name becomes the package's name, and, in lower case and without the characters a recipe name cannot hold, the name of its starter recipe and of its two apps. The Service step says what the package does while it runs. Command-line tool, already chosen when the step opens, links the package's bin/cmatrix into /usr/bin while the package is started. The project's folder then holds:

PathWhat it is
recipes/<name>/the starter recipe: recipe.json and build.sh
recipes.txtthe recipes the two apps build, one name per line: this project's and the library's
Makefilemake app, make build, make lint, make qpkg
licenses.txtthe licenses the package carries, one line per recipe
qpkg/the package: qpkg.cfg and the start and stop script from the Service step

The Makefile builds two apps: <name> for this NAS's processor and <name>-arm64 for ARM 64-bit units, a cross build on an x86_64 NAS. Both build in the Ubuntu 24.04 builder and link statically; when that environment is missing, Create this environment on the Toolchains page makes it. Build right after creating is off for this template: the starter recipe names a placeholder source with a sha256 of 64 zeros, so a build fails until the recipe names your program.

02The starter recipe

Open in editor in the Project view opens the project's folder in the editor. recipe.json says what to build; build.sh says how.

The editor on the project cmatrix: the starter recipe.json under recipes/cmatrix, with the placeholder address and a sha256 of zeros
FieldWhat it says
name, versionthe recipe's name (its folder's) and the program's version
summary, category, license, homepagewhat the panel shows about it
sourcethe address of the source archive and its sha256; a download that does not match is refused
depsthe recipes it needs first, from the library or this project
env_packagesbuild tools the builder environment installs for it (never libraries)
static, nsswhether it links static, and whether it looks up users or hostnames
checka command, run in the finished prefix, that proves the program works

build.sh defines three phases, each run in the unpacked source. The starter's are CMake's, and its comments name the autoconf and Meson form of each:

do_configure() {
  # an autoconf source: configure_std; meson: meson_std. In a static app CMake ends the link line with -Wl,-Bdynamic
  # after a library it found as a file (lib<name>.a), and the link then asks for a shared libc;
  # CMAKE_LINK_SEARCH_END_STATIC keeps the line static to its end (D312)
  if [ "$STATIC" = 1 ]; then cmake_std -DCMAKE_LINK_SEARCH_END_STATIC=ON; else cmake_std; fi
}
do_build() {
  cmake --build _build -j"$JOBS" # autoconf: make_j; meson: meson compile -C _build
}
do_install() {
  cmake --install _build # autoconf: make install; meson: meson install -C _build
}

cmake_std, configure_std and meson_std pass the app's prefix, the link mode and, for an ARM app, the cross target, so the same file serves both apps.

03Your program's source

Take the address of the release archive and let the editor's terminal compute its sha256:

curl -fsSL https://github.com/abishekvashok/cmatrix/archive/refs/tags/v2.0.tar.gz | sha256sum
ad93ba39acd383696ab6a9ebbed1259ecf2d3cf9f49d6b97038c66f80749e99a  -

cmatrix builds with CMake and draws with ncurses, which the library has. Its CMake script finds ncurses as a file, libncurses.a, and in a static app CMake ends the link line with -Wl,-Bdynamic after it, so the link would ask for a shared libc and stop. The starter's do_configure keeps the line static to its end, so build.sh needs no change:

do_configure() {
  if [ "$STATIC" = 1 ]; then cmake_std -DCMAKE_LINK_SEARCH_END_STATIC=ON; else cmake_std; fi
}

In recipe.json the check becomes bin/cmatrix -V, the option that prints cmatrix 2.0's version (it answers --version with its help text):

{
  "name": "cmatrix",
  "version": "2.0",
  "summary": "cmatrix: the falling characters of The Matrix in a terminal",
  "category": "tool",
  "license": "GPL-3.0-or-later",
  "homepage": "https://github.com/abishekvashok/cmatrix",
  "source": {
    "url": "https://github.com/abishekvashok/cmatrix/archive/refs/tags/v2.0.tar.gz",
    "sha256": "ad93ba39acd383696ab6a9ebbed1259ecf2d3cf9f49d6b97038c66f80749e99a"
  },
  "deps": ["ncurses"],
  "env_packages": [],
  "static": true,
  "nss": false,
  "check": "bin/cmatrix -V"
}
  • A recipe folder holds only recipe.json, build.sh and the vendor file that recipe.json may name. A patch goes into build.sh as a here-document.
  • recipes.txt already names the starter recipe; the library's recipes it depends on are added to the build in their order.

04Check it

Each time you save a recipe's file, the project's recipes are checked and the findings appear in the Problems view, each at its file and line. The check reads recipe.json and parses build.sh with bash -n; it never runs the recipe. The starter's 64 zeros are such a finding, a warning on the sha256 line until the real sum is there.

The Problems view: bvs recipe lint warns that sha256 is the template's placeholder, at line 10 of recipe.json

The same check runs in the terminal and in a CI job:

make lint                  # bvs recipe lint . (the project folder)
bvs recipe lint --json .   # the findings as JSON, for a CI step

The Recipes card of the Project view lists the project's recipes as its apps take them, each with its lint result and its findings. A click on a row opens the recipe's folder in the editor, and the row's menu copies the lint command.

The Recipes card of the Project view: the recipe cmatrix 2.0, clean

05Build the two apps

Build in the Project view runs the project's Makefile: it makes the two apps when they are missing and builds them one after the other. Each app's page on the Apps page names the project its recipes come from, and the build's log names each recipe in its order, ncurses first.

The app cmatrix built: Recipes from the project cmatrix under its name; ncurses, needed by cmatrix, and cmatrix, chosen, both passed
  • A build reads the project's recipes once, when it starts. A file saved later waits for the next build.
  • A project recipe with the name of a library recipe replaces that recipe in this project's apps only, and the build's log says so.
  • After the last recipe, every recipe's check runs again over the finished prefix: bin/cmatrix -V for this one. On an x86_64 NAS the ARM app is a cross build, and its checks run only where qemu-user is registered; without it the build records them as not run.

06Pack and install

licenses.txt says what the package's share/licenses/ holds: one line per recipe the package carries, with its license and the license files in its source archive. The starter's line names the placeholder. cmatrix needs two lines, because ncurses is linked into it:

cmatrix GPL-3.0-or-later COPYING
ncurses X11 COPYING

make qpkg in the editor's terminal packs the newest successful build of each app into the package with QDK from the Toolchains page, one file per architecture, and leaves out an app that has none. An app linked dynamically is refused: its programs would look for their libraries at the app's prefix, which the package does not make.

make qpkg

The packages land in build/qpkg/build/, named after qpkg.cfg's QPKG_NAME and QPKG_VER: cmatrix_0.1.0_x86_64.qpkg and cmatrix_0.1.0_arm_64.qpkg. Each carries the app's bin/, sbin/, share/ (without man, doc and info) and etc/: here cmatrix, and what ncurses installed there, its tools and its terminal database. KEEP at the top of scripts/make-qpkg.sh names the paths a package should carry instead, for example bin/cmatrix share/terminfo.

With the code-signing files in qpkg/ beside qpkg.cfg, or in a folder named by QPKG_CERT_DIR, the package is signed, and Test deploy in the Project view (an admin's) installs it on this NAS. App Center from your own repository shows signing and publishing in more detail. Transmission from scratch follows a Package from source project with five recipes from start to finish, from a NAS with nothing set up to the signed package installed and answering in the browser.

07A second recipe

Bucket: New recipe adds a recipe from the same starter. Run it from the Command Palette (Ctrl+Shift+P) while a file of the project is open, or right-click the recipes folder or a recipe's folder in the Explorer. It asks for the name (lower-case letters, digits and + . _ -), writes recipe.json and build.sh, opens recipe.json and checks the project.

Bucket: New recipe asks for the new recipe's name in the project's recipes folder
  • Put the new name into recipes.txt for apps made from now on. An app made before keeps its list: Change on its page adds the recipe.
  • A recipe whose build needs npm, Go or Cargo packages lists them in a vendor file. npm-vendor.py, go-vendor.py and cargo-vendor.py in /usr/bin/bucketvs/share/bucketvs/tools/ write that list; they need a Python runtime from the Toolchains page.
  • Project recipes run as root in the builder environment, as the library's do, and any developer can write and build them. A viewer sees the names of an app's recipes in its build log, never the recipe files.
  • A project recipe stays in its project. It joins the recipe library, and the signed catalog every NAS takes, only through a reviewed change to Bucket & Code Server IDE itself.