Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/terrapkg/packages/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Terra uses Anda as its build system, which provides automation for building RPM packages from the monorepo. The build system includes:
  • anda.hcl - Package configuration files
  • andax/ - Automation scripts written in Rhai
  • CI/CD integration - GitHub Actions workflows
  • Mock - Isolated build environments

Build Process Flow

Anda Command Line Interface

Building Packages

# Build a specific package
cd anda/apps/discord
anda build

Testing Builds

# Install built package locally
sudo dnf install ./anda-build/RPMS/x86_64/discord-*.rpm

# Test in clean container
podman run -it -v $(pwd):/work:z fedora:rawhide
dnf install /work/anda-build/RPMS/x86_64/discord-*.rpm

Andax Scripts

The andax/ directory contains Rhai scripts that extend Anda’s functionality.

spec.rhai

Utility functions for parsing RPM spec files:
fn get_version() {
    return `(?m)^Version:\s*(.+)$`.find(this.f, 1);
}

fn get_release() {
    let r = `(?m)^Release:\s*(.+)$`.find(this.f, 1);
    r = sub(`(?m)(%\??dist|%\{\??dist\})\s*$`, "", r);
    r.replace("%autorelease", "1");
    return r;
}

fn get_global(macro) {
    return `(?m)^%global\s+${macro}\s+(.+)$`.find(this.f, 1);
}

fn get_define(macro) {
    return `(?m)^%define\s+${macro}\s+(.+)$`.find(this.f, 1);
}
Usage:
import "spec" as rpm;

let spec_file = read_file("package.spec");
let version = rpm::get_version(spec_file);
let release = rpm::get_release(spec_file);

nvidia.rhai

Functions for fetching NVIDIA driver and CUDA component versions:
fn nvidia_component_list() {
    let url = "https://developer.download.nvidia.com/compute/cuda/redist/";
    let matches = find_all("redistrib_[\\d.]+.json", get(url));
    let series = `${url}${matches[matches.len - 1][0]}`;
    return get(series).json();
}

fn nvidia_component_version(component) {
    let components = nvidia_component_list();
    return components[component]["version"];
}

fn nvidia_driver_version() {
    let driver = get("https://gfwsl.geforce.com/services_toolkit/services/com/nvidia/services/AjaxDriverService.php?func=DriverManualLookup&osID=12&languageCode=1033&numberOfResults=1&beta=0")
        .json().IDS[0].downloadInfo.DisplayVersion;
    return(driver);
}

fn nvidia_legacy_version() {
    let driver = get("https://gfwsl.geforce.com/services_toolkit/services/com/nvidia/services/AjaxDriverService.php?func=DriverManualLookup&osID=12&languageCode=1033&numberOfResults=1&beta=0&release=580")
        .json().IDS[0].downloadInfo.DisplayVersion;
    return(driver);
}
Usage:
import "nvidia" as nv;

let driver_ver = nv::nvidia_driver_version();
let cuda_ver = nv::nvidia_component_version("cuda_cudart");
print(`Latest NVIDIA driver: ${driver_ver}`);

bump_extras.rhai

Helper functions for version tracking across distributions:
// Get latest version from AlmaLinux repos
fn alma(pkg, repo, branch) {
  let vr = alma_vr(pkg, repo, branch);
  return(vr[1]);
}

// Get latest version from Fedora Bodhi
fn bodhi(pkg, branch) {
    let url = `https://bodhi.fedoraproject.org/updates/?search=${pkg}&status=stable&releases=${branch}&rows_per_page=1&page=1`;
    for entry in get(url).json().updates[0].title.split(' ') {
        let matches = find_all(`${pkg}-([\\d.]+)-(\\d+)\\.[\\w\\d]+$`, entry);
        if matches.len() != 0 {
            return matches[0][1];
        }
    }
}

// Get latest version from Terra repos
fn madoguchi(pkg, branch) {
    return madoguchi_json(pkg, branch).ver;
}

// Get latest release from Codeberg
fn codeberg(repo) {
    return get(`https://codeberg.org/api/v1/repos/${repo}/releases/latest`).json().tag_name;
}
Usage:
import "bump_extras" as bump;

// Check Fedora version
let fedora_ver = bump::bodhi("firefox", "F41");

// Check Codeberg release
let release = bump::codeberg("owner/repo");

get_proj_label.rhai

Extract labels from anda.hcl files:
import "anda::cfg" as cfg;
print(cfg::load_file(labels.project).project.pkg.labels.to_json());
Usage in CI:
# Get subrepo label for a package
anda run andax/get_proj_label.rhai --project anda/apps/discord/anda.hcl

CI Scripts

update_commit_message.rhai

Generates commit messages for automated updates:
import "anda::cfg" as cfg;

let cmd = `git status | sed -nE '/^\tmodified:/{s@^\tmodified:\s+@@;s@[^/]+$@@;p}' | sort -u`;
let filelist = sh(cmd, #{ "stdout": "piped" }).ctx.stdout.split('\n');

let modified_list = "";
for file in filelist {
	if file.is_empty() { continue; }
	let spec = cfg::load_file(`${file}/anda.hcl`).project.pkg.rpm.spec;
	spec.pop(5); // remove `.spec` suffix
	modified_list += `${spec} `;
}
print(modified_list[..modified_list.len()-1]);

extra_repos.rhai

Installs extra repositories during mock builds:
import "anda::cfg" as cfg;

fn install(labels) {
    if labels.script_path == () {
        print("fatal: labels.script_path is empty");
        terminate();
    }
    let releasever = sh("rpm -E '%fedora'", #{"stdout": "piped"}).ctx.stdout;
    releasever.trim();
    let basearch = sh("rpm -E '%_arch'", #{"stdout": "piped"}).ctx.stdout;
    basearch.trim();
    let hcl = cfg::load_file(sub(`(.+/)[^.]+\\.rhai`, "${1}anda.hcl", labels.script_path));
    for repo in hcl.project.pkg.rpm.extra_repos {
        repo = sub(`\\$releasever`, releasever, repo);
        repo = sub(`\\$basearch`, basearch, repo);
        let filename = sub(`\\W`, "_", repo);
        let file = open_file(`/etc/yum.repos.d/${filename}.repo`);
        file.write(`
[filename]
name=${filename}
baseurl=${repo}
enabled=1
gpgcheck=0
`);
    }
}

Mock Build Environment

Mock provides isolated build environments that prevent dependency pollution.

When to Use Mock

Enable mock builds (labels.mock = 1) when:
  • Package requires extra_repos
  • Build has complex dependency chains
  • Need guaranteed clean environment
  • Package is security-sensitive

Mock Configuration

Mock environments are configured per Fedora version:
# Default mock config
/etc/mock/fedora-rawhide-x86_64.cfg

# With Terra repos
/etc/mock/terra-rawhide-x86_64.cfg

Mock Build Process

Build Artifacts

Directory Structure

package-directory/
├── anda.hcl
├── package.spec
├── patch1.patch
├── source1.conf
└── anda-build/
    ├── BUILD/          # Extracted source
    ├── BUILDROOT/      # %install destination
    ├── RPMS/
    │   └── x86_64/
    │       └── package-1.0-1.fc42.x86_64.rpm
    ├── SOURCES/        # Source files
    ├── SPECS/          # Spec file
    └── SRPMS/          # Source RPM
        └── package-1.0-1.fc42.src.rpm

Build Logs

# View build log
less anda-build/build.log

# Check for errors
grep -i error anda-build/build.log

# View mock logs
less /var/lib/mock/fedora-rawhide-x86_64/result/build.log

CI/CD Integration

GitHub Actions Workflow

Terra uses GitHub Actions for automated builds:
name: Build Packages

on:
  push:
    branches: [ main ]
  pull_request:

jobs:
  build:
    runs-on: ubuntu-latest
    container: ghcr.io/terrapkg/builder:latest
    
    steps:
      - uses: actions/checkout@v4
      
      - name: Build changed packages
        run: |
          anda build --changed
      
      - name: Upload artifacts
        uses: actions/upload-artifact@v4
        with:
          name: rpms
          path: "**/anda-build/RPMS/**/*.rpm"

Build Matrix

Builds run across multiple Fedora versions:
strategy:
  matrix:
    fedora: [40, 41, 42, rawhide]
    arch: [x86_64, aarch64]

Troubleshooting

Solution 1: Add to BuildRequires in spec file
BuildRequires:  missing-package-devel
Solution 2: Add extra repository to anda.hcl
rpm {
  spec = "package.spec"
  extra_repos = ["https://repo.example.com/fedora/\\$releasever/\\$basearch"]
}
Check that all dependencies are properly declared:
# List actual dependencies
rpm -qpR package.rpm

# Compare with spec file
grep "^Requires:" package.spec
Missing BuildRequires often cause mock-only failures.
Verify repository URL format:
# Correct - double backslash
extra_repos = ["https://example.com/\\$releasever/\\$basearch"]

# Wrong - single backslash
extra_repos = ["https://example.com/\$releasever/\$basearch"]
Check file conflicts:
# List package files
rpm -qpl package.rpm

# Check for conflicts with installed packages
rpm -qf /conflicting/file
May need Conflicts: or Obsoletes: in spec.
For large packages:
labels {
  large = 1
}
Parallelize builds:
%build
%cmake_build -j%{_smp_build_ncpus}

Advanced Topics

Custom Build Scripts

Create custom Rhai scripts for specialized workflows:
// custom_update.rhai
import "nvidia" as nv;
import "spec" as rpm;

// Get latest NVIDIA version
let latest = nv::nvidia_driver_version();

// Update spec file
let spec = read_file("nvidia.spec");
spec = spec.replace(rpm::get_version(spec), latest);
write_file("nvidia.spec", spec);

print(`Updated to ${latest}`);

Multi-Stage Builds

Some packages require building dependencies first:
#!/bin/bash
# Build dependency chain

cd anda/lib/library-a
anda build
sudo dnf install ./anda-build/RPMS/x86_64/library-a-*.rpm

cd ../library-b
anda build
sudo dnf install ./anda-build/RPMS/x86_64/library-b-*.rpm

cd ../../apps/application
anda build

Cross-Architecture Builds

# Build for ARM on x86_64
anda build --arch aarch64

# Requires qemu-user-static
sudo dnf install qemu-user-static

Build System Architecture

Component Diagram

Performance Optimization

Caching

# Enable ccache for C/C++ builds
export PATH="/usr/lib64/ccache:$PATH"

# Enable sccache for Rust builds
export RUSTC_WRAPPER=sccache

Parallel Builds

# In spec file %build section
%cmake_build -j%{_smp_build_ncpus}

# Or for make
make -j%{_smp_build_ncpus}

Mock Optimizations

# Keep mock root between builds
anda build --mock --no-clean

# Use tmpfs for faster builds
mock --enable-plugin=tmpfs

Build docs developers (and LLMs) love