Render Line (rLine) is an automated source-to-archive build engine designed specifically for the Cudane Linux ecosystem (but also available for GNU-based distributions). It reads declarative JSON manifests that can contain an unlimited number of package recipes, isolates execution within localized workspaces, builds whatever target you want from source, scans dependencies, packages everything cleanly into .xcs binary packages, and automatically writes a unified index.json for your own repository of packages (with auto-updating support).
- [Architecture]
- [The Manifest Schema]
- [The Package Struct]
- [The Symlink Struct]
- [The PackageMetadata Struct]
- [The CLI Entry Point (main.rs)]
- [The Library Core (lib.rs)]
- [Binary Reader & Dependency Freezer]
- [Dependency Freezing: Making Packages 100% Self-Contained]
- [Root Directory Tree Organization Guide]
- [SquashFS Compression: Commands, Flags & Full Reference]
- [fetch - Source Acquisition]
- [build - Compilation Execution]
- [install - Staging Installation]
- [symlink - Symbolic Link Management]
- [dependencies - Runtime Library Injection]
- [hash - Cryptographic Directory Checksum]
- [license - Heuristic License Scanner]
- [meta - Metadata Ledger Writer]
- [index - Repository Index Aggregator]
- [archive - SquashFS Compression]
- [components - Sub-Package Discovery]
- [services - Dinit Service Detection]
- [profile - Sandbox Profile Construction]
- [process - The Orchestrator]
- [CLI Flags in Detail]
- [The XCS Package Format]
- [Dependency Injection Mechanics]
- [License Detection Algorithm]
- [Repository Indexing Logic]
- [System Requirements]
- [Building for Cudane Linux]
- [Manual Package Operations]
- [Complete Lifecycle Walkthrough]
rLine is built around a single principle: declarative input, deterministic output. You provide a JSON manifest describing what to build and how, and rLine handles the rest — fetching source code, executing builds in isolation, scanning for runtime dependencies, generating metadata, and producing compressed archives.
The codebase is split into two files:
src/main.rs— The CLI argument parser and entry point. It parses command-line flags, reads the manifest, and iterates over each package, calling into the library.src/lib.rs— The core engine. Contains all data structures, the fetch/build/install pipeline, metadata generation, dependency injection, license detection, hashing, archiving, component scanning, service detection, sandbox profiling, repository indexing, and the Binary Reader dependency scanner.
The engine uses anyhow for error handling with context propagation, serde for JSON serialization and deserialization, sha2 for cryptographic hashing, chrono for timestamp generation, and regex for license pattern matching. External system tools (git, curl, tar, mksquashfs, unsquashfs, ldd, file) are invoked via std::process::Command rather than being linked as libraries, keeping the Rust binary lightweight and delegating specialized work to mature system utilities.
The manifest is the single input file that drives the entire build pipeline. It is deserialized into the Manifest struct:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Manifest {
pub packages: Vec<Package>,
}Tip
The Manifest struct is a thin wrapper around a Vec<Package>. There is no limit on the number of packages — a single manifest can define one package or orchestrate an entire operating system bootstrap with hundreds of sequential recipes. The packages field is the only top-level key; everything else is expressed within each individual Package entry.
Each element in the packages array deserializes into a Package:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Package {
pub name: String,
pub version: String,
pub source: String,
pub build_type: String,
pub build_cmd: String,
pub install_cmd: String,
pub links: Option<std::collections::HashMap<String, String>>,
}Every field is explained below:
The package identifier. This becomes part of the output filename (<name>-<version>XCS) and is used as the key in the repository index. It should be a simple alphanumeric string, typically lowercase with hyphens (for example, "hello", "shared-mime-info").
The package version string. Combined with name to form the unique package identity. Versions are treated as opaque strings — no semantic versioning parsing is performed. The version is embedded in the output filename and in the metadata.
The origin of the source code. This field is processed by the fetch() function and supports multiple formats:
- Remote archive URLs:
https://example.com/releases/pkg-1.0.0.tar.gz,.tar.bz2,.tar.xz— downloaded viacurland extracted viatar. - Git repository URLs: Any URL ending in
.git— cloned viagit clone --depth 1for a shallow, bandwidth-efficient checkout. - Local filesystem paths:
/home/user/projects/my-pkgor../relative/path— copied recursively into the workspace. file://URIs:file:///absolute/path/to/source— thefile://prefix is stripped and the path is treated as a local source.
Tip
The fetch() function can also check for local existence first (before any protocol-based logic), so local paths always take precedence and avoid network access entirely.
A classifier that influences how rLine handles the package when build_cmd is empty. Common values include:
"rust"— Triggers automaticcargo build --releasewith Cudane-specificRUSTFLAGSwhenbuild_cmdis empty."meson"— Informational; the actual build command must be provided inbuild_cmd."make"— Informational; the actual build command must be provided inbuild_cmd."custom"— Explicitly signals a custom build process;build_cmdandinstall_cmdare expected to be provided.
Note
The build_type is also stored in the output metadata for downstream tools to reference.
The shell command to execute for building the package. The behavior depends on the content:
- If the trimmed value equals
"none","skip", or"nothing"(case-insensitive): The build step is completely skipped. No command runs, no automatic fallback occurs. - If empty (
"") andRLINE_NO_AUTOis set: The build step is skipped (returns empty log). - If empty (
"") andbuild_typeis"rust": Automatic Rust build is triggered. The engine runs:
RUSTFLAGS="-C linker=clang -C link-arg=-target \
-C link-arg=x86_64-pc-linux-musl -C link-arg=--sysroot=/system \
-C target-feature=+crt-static" \
cargo build --target x86_64-unknown-linux-musl --releaseTip
Both stdout and stderr are captured into a capture.log file inside the source directory. If the build succeeds, the log content is returned for dependency scanning. If it fails, the error includes the full log output.
- If empty (
"") andbuild_typeis not"rust": The build step returns an empty string (no-op). - If non-empty: The command is executed via
sh -cinside the source directory. Both stdout and stderr are captured usingteeintocapture.log, which is then read back and deleted. The log content is returned for dependency scanning.
The shell command to install built artifacts into the staging directory. The behavior depends on the content:
- If the trimmed value equals
"none","skip", or"nothing"(case-insensitive): The install step is skipped, but any symlinks declared in thelinksmap are still created. - If empty (
"") andRLINE_NO_AUTOis set: The install step is skipped (symlinks still processed). - If empty (
"") andbuild_typeis"rust": Automatic Rust install is triggered. The engine copies all files fromtarget/release/inside the source directory into the package staging directory. This provides a sensible default for Rust projects where the compiled binaries are placed intarget/release/. - If empty (
"") andbuild_typeis not"rust": Falls through to execute the empty string as a command (which would do nothing), then processes symlinks. - If non-empty: The command is executed via
sh -cwith theCUDANE_DESTenvironment variable set to the package staging directory path. The command runs with the source directory as its working directory. After the command completes, any symlinks in thelinksmap are created.
The installation prefix path that controls where binaries, libraries, and data are organized within the package root. This field defaults to "system" if not provided, matching the Cudane root layout. Common values include:
"system"(default) — Organizes content undersystem/bin,system/lib,system/share, etc. This is the standard Cudane layout."usr"— Organizes content underusr/bin,usr/lib,usr/share, etc. for more traditional FHS-style packages."usr/local"— For locally-installed software that should not be managed by the system package manager.
The prefix is passed to externals(), spfx(), and ppfx() so that dependency scanning, service detection, and sandbox profiling all operate on the correct subdirectory (e.g., {prefix}/bin and {prefix}/lib instead of hardcoded system/bin and system/lib). The CUDANE_PREFIX environment variable is also set during install commands to allow build systems to target the correct prefix.
fn prefix(pkg: &Package) -> &strNote
This is a private helper function (not exported from the library) that normalizes the prefix field of a Package. It trims whitespace from the prefix and returns it if non-empty; otherwise, it defaults to "system". This function is called during process() and install() to ensure a valid prefix is always used, even when the manifest omits the field.
An optional map of symbolic links to create inside the package staging directory after installation. The map keys are the target paths (what the symlink points to) and the values are the link paths (where the symlink is placed). For example:
"links": {
"system/lib/libexample.so.1": "system/lib/libexample.so"
}This creates a symlink at pkg_root/system/lib/libexample.so that points to system/lib/libexample.so.1. The symlink() function handles the path logic: it strips leading slashes from the link path to keep it relative to the staging root, creates parent directories as needed, removes any existing file at the link location, and then creates the symlink using std::os::unix::fs::symlink.
Although the Package struct uses a raw HashMap<String, String> for links, there is also a dedicated Symlink struct in the codebase:
#[[derive(Deserialize, Serialize, Clone)]]
pub struct Symlink {
pub target: String,
pub link: String,
}This struct is available for serialization and deserialization but is not currently used by the main pipeline — the links field in Package uses the HashMap directly. And the Symlink struct exists as a potential future expansion point for more structured symlink definitions.
Every built package generates a metadata.json file embedded inside the archive. The structure is:
#[[derive(Serialize, Deserialize, Clone, Debug, PartialEq)]]
pub struct PackageMetadata {
pub pkg_name: String,
pub version: String,
pub source: String,
pub license: String,
pub build_type: String,
pub build_date: String,
pub checksum: String,
pub pkg_type: String,
#[[serde(skip_serializing_if = "Option::is_none")]]
pub components: Option<Vec<PackageMetadata>>,
#[[serde(skip_serializing_if = "Option::is_none")]]
pub services: Option<Vec<String>>,
pub profile: Vec<String>,
pub features: Vec<String>,
/// External library dependencies detected by the binary reader
#[[serde(skip_serializing_if = "Option::is_none")]]
pub externals: Option<Vec<String>>,
/// Libraries bundled into the package to make it self-contained
#[[serde(skip_serializing_if = "Option::is_none")]]
pub bundles: Option<Vec<String>>,
/// SHA-256 signature hash of the dependency list (ensures integrity)
#[[serde(skip_serializing_if = "Option::is_none")]]
pub depsig: Option<String>,
}Each field is populated as follows:
pkg_name: Mirrored directly from the manifest'snamefield.version: Mirrored directly from the manifest'sversionfield.source: Mirrored directly from the manifest'ssourcefield.license: Determined by thelicense()function, which scans the source directory for license files and extracts the license name using regex pattern matching.build_type: Mirrored directly from the manifest'sbuild_typefield.build_date: An ISO 8601 UTC timestamp generated at runtime viachrono::Utc::now().to_rfc3339(), recording exactly when the metadata was created.checksum: A SHA-256 hex digest of the entire package staging directory, computed byhash()which pipes the directory throughtar -cf -and hashes the resulting byte stream.pkg_type: Set to"meta"whencomponentsisSome(...)(the package contains a non-empty set of sub-packages), or"standalone"for ordinary single-component packages.components(optional): AnOption<Vec<PackageMetadata>>— if the package staging directory contains.xcssub-packages, their metadata is collected here, making this a composite or "meta" package that bundles multiple inner packages.services(optional): AnOption<Vec<String>>— if the package contains asystem/dinit.d/directory with service files, their filenames are listed here (without path). This enables the init system to discover available services at runtime.profile: AVec<String>of sandbox profile capabilities, automatically constructed by theprofile()function. Always starts with"isolated-rootfs"and may include"wayland","network", and"audio"based on binary content analysis.features: AVec<String>of optimization and capability features. For standard builds this includes"lazy-mount","cas-deduplication","atomic-rollback","delta-reconstruct", and"zero-bloat-deps".externals(optional): A sorted, deduplicated list of all non-core shared library filenames that the package references at either compile-time (via DT_NEEDED) or runtime (via dlopen/dlsym). Discovered by the Binary Reader's two-phase scan.bundles(optional): A sorted, deduplicated list of shared library filenames that were actually copied into the package'ssystem/lib/directory during the dependency resolution phase. These are the "frozen" supplemental libraries that make the package fully self-contained.depsig(optional): A SHA-256 hex signature computed over the concatenation (joined by|) of all entries inexternals. This verifiable hash ensures tamper-proof dependency tracking and can be re-computed at install time to assert integrity.prefix: The installation prefix under which this package was organized (e.g.,"system","usr","usr/local"). Defaults to"system"if not provided. This field is populated byprefix()during package processing and stored in the metadata for downstream tools to understand the package's directory layout.
The PartialEq derive is used by index() to compare metadata entries and avoid unnecessary updates when the content has not changed.
The main() function in src/main.rs is the command-line interface. It uses std::env::args() to collect arguments and processes them with a peekable iterator. The function signature is:
fn main() -> Result<()>Tip
The anyhow::Result return type allows the use of the ? operator for error propagation, with any errors printed to stderr by Rust's default panic and error handling.
The parser uses a while let Some(arg) = args.next() loop with a match on each argument. It maintains two mutable strings: manifest_path and output_dir. The parsing follows these rules:
-
Flags with values (for example,
-m,-o,-j,-z,-p,-t): The flag consumes the next argument as its value. For example,-m manifest.jsonsetsmanifest_pathto"manifest.json". -
Boolean flags (for example,
-n,-f,-c,-s,-q,-d,-y,-k,-l): These set environment variables usingunsafe { env::set_var(...) }. Theunsafeblock is required becauseset_varis unsafe in the Rust standard library (it can cause data races in multi-threaded contexts, thoughrLineis single-threaded). -
Positional arguments: Any argument that does not start with
-is treated as a positional argument. The first positional argument fillsmanifest_path, the second fillsoutput_dir. -
-h/--help: Prints the help message and exits with code 0. -
-v/--version: Prints the version string"rLine 0.2.0"and exits with code 0. -
-a/--archive: Takes two positional arguments (staging directory and output package path) and runsmksquashfsdirectly to create an.xcsarchive manually. This is a standalone utility mode that does not require a manifest. -
-x/--extract: Takes one or two positional arguments (package path or directory, and destination root). If the input is a directory, it scans for all.xcsfiles inside. Each package is extracted usingunsquashfsfirst; if that fails, it falls back totar --zstd -xforzstd -dc | tar -xf. This provides compatibility with both SquashFS and tar+zstd archives. -
-w/--write: Takes two positional arguments (source directory and destination directory). Generates ametadata.jsonfor the destination directory without archiving it. The package name is derived from the source directory filename. -
-i/--inspect: Takes one positional argument (path to an.xcspackage). Prints the file size, type (via thefilecommand), SquashFS compression specs (viaunsquashfs -stat), and the embeddedmetadata.jsoncontent (viaunsquashfs -cat).
After argument parsing, if both manifest_path and output_dir are non-empty, the program:
- Reads the manifest file from disk using
fs::read_to_string. - Deserializes it into a
Manifeststruct usingserde_json::from_str. - Creates the output directory with
fs::create_dir_all. - Iterates over each package in
manifest.packagesand callsprocess(pkg, &output_dir). - If
RLINE_QUIETis not set, prints"OK: <path>"for each successfully built package. - If any package fails, the error is propagated with
?and the program exits immediately (fail-fast behavior).
The library file contains all the data structures and functions that implement the build pipeline. Each function is designed to be independently callable, allowing for flexible composition and testing.
One of the most powerful features of rLine is its Binary Reader — a zero-bloat dependency scanner that replaces the simplistic ldd-only approach with a sophisticated, two-phase analysis engine. Rather than including bulky disassembler libraries (like libbfd, capstone, or llvm) that would bloat the engine's binary, rLine reads ELF binary files directly as raw byte streams and extracts meaningful dependency information from them.
The Binary Reader consists of four essential components, designed to work together like a mobile application's tightly integrated architecture:
Source function: elf(dir: &str) -> Result<Vec<PathBuf>>
The ELF Target Iterator is the entry point for the Binary Reader. It performs a recursive directory traversal, typically scanning system/bin and system/lib directories inside the package staging area.
Key design decisions:
- Magic byte validation: Rather than relying on file extensions (which scripts and non-ELF files can fake), the iterator reads the first 4 bytes of every file it encounters. ELF binaries begin with the magic bytes
\x7f E L F(0x7f 0x45 0x4c 0x46). Only files matching this signature are included in the results. - Script exclusion: Shell scripts, Python scripts, and other text-based executables begin with
#!(shebang) and are automatically filtered out by the ELF magic check. This prevents false positives from non-binary files. - Stack-based traversal: The iterator uses an explicit
Vec<PathBuf>as a stack for directory traversal rather than recursion, avoiding potential stack overflow on deeply nested directory trees. - Error tolerance: Directories that cannot be read (permission denied, broken symlinks) are silently skipped. This ensures that a single inaccessible directory does not block the entire dependency scan.
Algorithm:
1. If the root directory does not exist, return an empty vector
2. Push the root directory onto a stack
3. While the stack is not empty:
a. Pop a directory from the stack
b. Read all entries in the directory
c. For each entry:
- If it is a directory, push it onto the stack
- If it is a file, open it and read the first 4 bytes
- If the bytes match the ELF magic (0x7f, 0x45, 0x4c, 0x46), add the path to the result
4. Return the collected ELF binary paths
Source function: strings(path: &Path) -> Result<Vec<String>>
The Dynamic Symbol Parser is the core innovation of the Binary Reader. It reads an ELF binary as a raw byte stream and extracts printable ASCII strings of length >= 4 characters. This is the "zero-bloat" approach — no disassembler libraries, no heavy parsing infrastructure, just the binary's own byte content.
What this catches:
- DT_NEEDED entries from the
.dynstrsection of the ELF dynamic symbol table. These are the compile-time declared library dependencies thatlddalso shows. The library names (e.g.,libfoo.so,libbar.so.1.0.0) appear as printable strings in the.dynstrsection. dlopen()runtime calls: When a binary callsdlopen("/system/lib/libfoo.so", RTLD_NOW)ordlopen("libbar.so", RTLD_LAZY), the library path or name string is embedded in the.rodatasection of the binary.ldddoes NOT see these because they are not DT_NEEDED entries — they are runtime decisions. The Byte Stream Reader catches them because it scans all printable strings regardless of section.dlsym()patterns: Similar todlopen, any library name or path string indlsym()arguments is captured.- Embedded path strings: Strings like
/system/lib/libquux.soor/usr/lib/libextra.sothat may be embedded in configuration data or string tables.
Key design considerations:
- Minimum length filter: Only strings of length >= 4 characters are captured. This filters out noise from single-byte characters and short garbage sequences that happen to align with printable ASCII.
- Printable character set: The parser collects bytes that are ASCII graphic (alphanumeric and punctuation) plus the characters
/,.,-,_, and space. These are the characters commonly found in library paths and filenames. - Numeric noise filter: Purely numeric strings (e.g., version numbers, addresses) are filtered out, as they cannot be library names.
- Zero dependency footprint: The entire string extraction is done with Rust's standard library file I/O plus basic byte iteration. No external parsing libraries are needed.
- Streaming chunk-based reading: To handle very large binaries (hundreds of megabytes or more) without exhausting memory on constrained devices, the function reads the file in 64KB chunks rather than loading the entire file at once. A carryover buffer ensures that strings split across chunk boundaries are correctly reassembled.
Algorithm:
1. Open the binary file for reading
2. Initialize a carryover buffer (empty)
3. While not at EOF:
a. Read 64KB chunk from file
b. Combine carryover + current chunk
c. Iterate over each byte in combined buffer:
- If printable ASCII (graphic, /, ., -, _, or space):
* Append to current string buffer
- Otherwise (non-printable byte):
* If current buffer length >= 4:
- Convert to UTF-8 string
- Skip if purely numeric
- Add to results
* Clear current buffer
d. Save incomplete string (if any) to carryover for next iteration
4. Process any remaining carryover at EOF (same logic as above)
5. Return all extracted strings
Library Name Matcher (sub-function): lib(strings: &[[String]]) -> Vec<String>
After extracting all printable strings from a binary, the Library Name Matcher filters them to find actual shared library references. It uses two regex patterns:
-
lib[[\w.-]]+\.so[[\d.]]*— Matches library names likelibfoo.so,libbar.so.1.0.0,libbaz-2.0.so. This catches standardlib*.so*patterns anywhere in any string. -
[["']]?(/[[^"'\s]]+\.so[[\d.]]*)— Matches full path patterns like/system/lib/libquux.soor"/usr/local/lib/libextra.so.1". This catches strings that look like file paths passed todlopen().
The function:
- Extracts the library filename from full paths (strips the directory part)
- Deduplicates the results
- Sorts them for deterministic output
- Filters out some known noise patterns (e.g., benign strings that happen to match the pattern)
Source function: binary_dependency_scan(dirs: &[[&str]]) -> Result<Vec<String>>
The Zero-Bloat Dependency Matcher orchestrates Components 1 and 2 together. Given one or more directory paths, it:
- Calls
elf()on each directory to find all ELF binaries - For each binary found, calls
strings()to extract printable strings - Passes the strings to
lib()to extract library names - Aggregates all results into a single, sorted, deduplicated list
This function is designed to be called on multiple directories in sequence — first on system/bin (the package's own binaries), then on system/lib (any already-bundles libraries, for transitive dependency scanning).
Core system library classification function: is_core_system_lib(lib_name: &str) -> bool
Libraries that match known Cudane core prefixes are considered part of the base system and are NOT bundles into the package. This prevents unnecessary bloat while ensuring that third-party/supplemental libraries are still captured. The CORE_SYSTEM_LIBS constant contains an extensive list of known system libraries:
- C standard library:
libc.so,libm.so,libpthread.so,libdl.so,librt.so,libutil.so - C++ runtime:
libstdc++.so,libgcc_s.so,libatomic.so,libgomp.so,libquadmath.so - Sanitizers:
libasan.so,libubsan.so,liblsan.so,libtsan.so - Compression:
libz.so,libzstd.so,liblzma.so,libbz2.so - Cryptography:
libssl.so,libcrypto.so - Regex:
libpcre.so,libpcre2.so - XML, Unicode:
libexpat.so,libffi.so,libiconv.so,libintl.so - Terminal:
libncurses.so,libtinfo.so,libreadline.so,libhistory.so - Dynamic linker:
ld-linux,ld-musl,ld-musl-x86_64 - NSS:
libnss_,libnss3.so,libnssutil3.so - Security:
libselinux.so,libsepol.so,libpam.so,libcap.so - Filesystem:
libacl.so,libattr.so,libmount.so,libblkid.so,libuuid.so - JSON:
libjson-c.so - IPC:
libdbus-1.so - Graphics:
libEGL.so,libGL.so,libdrm_*.so,libX11.so,libxcb.so,libwayland-*.so - Audio:
libpulse.so,libasound.so,libsndfile.so - Fonts:
libfreetype.so,libfontconfig.so,libharfbuzz.so - Images:
libpng,libjpeg,libwebp,libtiff,libgif - Scripting:
libpython,libperl.so - Database:
libsqlite3.so - And many more...
The matching uses both prefix checks and substring-in-name checks, so libfoo.so.1.0.0 matches libfoo.so via the prefix test, and libpthread-2.33.so matches libpthread.so via a sliding window comparison.
Source function: compute(deps: &[[String]]) -> String
This component is the final piece of the Binary Reader architecture. It generates a deterministic SHA-256 hash over the sorted external dependency list. This serves as a cryptographic signature that:
- Ensures the package's dependency metadata has not been tampered with
- Allows verification that all expected bundles libraries are present at install time
- Provides a unique fingerprint that can be compared against the manifest
Algorithm:
SHA-256( join("|", sorted_deps) )Where sorted_deps is the alphabetically sorted list of all external library names discovered by the two-phase scan. The pipe character (|) is used as the delimiter because it is highly unlikely to appear in a library name.
Tip
The resulting 64-character hex string is stored in PackageMetadata.depsig.
Note
This signature is computed during process() after the dependency resolution phase and before the metadata is written to disk. It becomes a permanent part of the package metadata, embedded inside the .xcs archive alongside metadata.json.
The true power of rLine's dependency analysis comes from its two-phase approach:
Phase 1 (ldd — compile-time DT_NEEDED):
The traditional ldd command is still used as the first phase. It captures all compile-time declared shared library dependencies — i.e., the DT_NEEDED entries in the ELF dynamic section. These are the libraries listed in the binary's .dynamic section.
ldd works by:
- Reading the ELF binary's
.dynamicsection to findDT_NEEDEDentries - Resolving each entry name (e.g.,
libfoo.so.6) to an actual file path using the system's library search paths (/etc/ld.so.conf,LD_LIBRARY_PATH, default paths like/usr/lib,/lib) - Recursively repeating the process for each resolved library
The output is a line-by-line listing showing each library name, its resolved path, and its load address. For example:
linux-vdso.so.1 (0x00007ffd5a3e0000)
libfoo.so.6 => /usr/lib/x86_64-linux-gnu/libfoo.so.6 (0x00007f8a12340000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8a12000000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8a12400000)Phase 2 (Binary Reader — runtime dlopen/dlsym):
The Binary Reader opens each ELF binary (both executables and shared libraries) and reads it as a raw byte stream, extracting all printable strings of length >= 4 characters. This catches:
- Runtime dynamic loading: Libraries loaded via
dlopen(),dlsym(), or similar mechanisms. These are NOT visible tolddbecause they are not declared inDT_NEEDED. - Plugin architectures: Programs that dynamically discover and load plugins (e.g., libpurple protocol plugins, Apache modules, GStreamer plugins) often embed plugin library names in string tables that are readable via byte scanning.
- Configuration-embedded paths: Path strings embedded in
.rodataor configuration data that reference shared libraries. - Transitive dependencies: When scanning already-bundles libraries in
system/lib, the Binary Reader can discover dependencies of those libraries, whichlddmay not resolve if the libraries are not on the system's library path.
Why two phases are necessary:
| Aspect | ldd Only |
Binary Reader Only | Combined |
|---|---|---|---|
| Compile-time DT_NEEDED | ✅ | ✅ (via .dynstr) | ✅ |
| Runtime dlopen/dlsym | ❌ | ✅ | ✅ |
| Recursive transitive deps | ✅ | ✅ | ✅ |
| Path resolution to real files | ✅ | ❌ (names only) | ✅ |
| Cross-platform compatibility | ✅ | ✅ | ✅ |
| Works with statically-linked bins | ❌ | ✅ (finds strings) | ✅ |
Updated: Jun. 17th 3:40AM (GMT+3:00)
Source function: externals(dest: &str, prefix: &str) -> Result<(Vec<String>, Vec<String>)>
This is the combined orchestrator that performs both Phase 1 and Phase 2, then classifies and copies dependencies. It returns a tuple of (externals, bundles). The prefix parameter (e.g., "system", "usr") controls which subdirectory is scanned for binaries and libraries, making the function prefix-aware.
Step-by-step algorithm:
Phase 1 — ldd scan:
1. Check if {dest}/system/bin/ exists
2. For each file in system/bin/:
a. Run `ldd <file>` and capture output
b. For each line containing "=>" (resolved library):
- Extract the absolute path after "=>"
- If path starts with "/" and not with "/system":
- If library file exists on disk:
- Add its filename to the all_found set
- Check if it's a core system lib:
- If NOT core: copy it to {dest}/system/lib/ and add to bundles set
c. For each line containing "not found" (missing library):
- Extract the library name and add to all_found set
Phase 2 — Binary Reader scan:
1. Build scan directories from {dest}/system/bin/ and {dest}/system/lib/
2. If any scan directories exist:
a. Run binary_dependency_scan() on them
b. For each discovered library name:
- Skip if: empty, too short (<5), starts with '/' or '.', already found, or core system lib
- Try to locate the library on the host system using search paths:
[[/usr/lib/x86_64-linux-gnu, /usr/lib64, /usr/lib, /lib/x86_64-linux-gnu,
/lib64, /lib, /system/lib, /opt/lib, /usr/local/lib]]
- If found: copy to {dest}/system/lib/ and add to both all_found and bundles
- If not found but it's a library reference: record it in all_found anyway
Finalization:
1. Sort and deduplicate both lists
2. Return (all_found, bundles)The combination of the Binary Reader's two-phase scan and the dependency copying logic creates a system where packages can truly be 100% independent — completely canceling the notion of "dependencies" from the package distribution model.
How it works:
-
Scan: For every binary in the package's
system/bin/directory, run bothldd(Phase 1) and the Binary Reader byte stream scanner (Phase 2). -
Classify: Each discovered library is checked against the
CORE_SYSTEM_LIBSlist. If it matches (e.g.,libc.so,libpthread.so,ld-musl-x86_64.so), it is classified as a "core system library" that the target Cudane root already provides. If it does NOT match, it is classified as a "supplemental library." -
Freeze (Copy): All supplemental libraries are physically copied into the package's
system/lib/directory before the SquashFS archive is created. This "freezes" the exact versions of those libraries into the package. -
Verify (Sign): A SHA-256 signature is computed over the sorted list of all discovered external dependencies. This signature is embedded in the package's
metadata.jsonand can be verified at install time to ensure that all expected bundles libraries are present and unchanged.
What this achieves:
- No runtime dependencies: The package carries every library it needs (except core system libraries that are guaranteed to be on the target root).
- Version pinning: The exact versions of bundles libraries are the ones tested during the build. Incompatible updates on the host system will not affect the package at runtime.
- Portability: A package built on one system can be deployed to any Cudane system regardless of which supplemental libraries are installed there.
- Tamper resistance: The dependency signature makes it impossible to modify or replace bundles libraries without invalidating the package's checksum and signature.
- Offline operation: Once built and frozen, the package does not need network access or remote repositories to resolve dependencies.
For rLine packages to be truly 100% independent and benefit fully from the Binary Reader's dependency freezing, the staging directory (pkg/) must follow a strict root directory tree structure. This structure mirrors the Cudane Linux filesystem layout.
pkg/ # Package staging root
├── metadata.json # Generated automatically (populated by rLine)
├── system/
│ ├── bin/ # Executable binaries
│ │ ├── my-app # Main executable
│ │ ├── helper-tool # Associated utility
│ │ └── ... # Any other binaries
│ ├── lib/ # Shared libraries (frozen here by Binary Reader)
│ │ ├── libfoo.so.1.0.0 # Discovered + bundles automatically
│ │ ├── libbar.so.2 # Discovered + bundles automatically
│ │ └── ... # Any other supplemental libs
│ ├── dinit.d/ # Dinit service files (optional)
│ │ └── my-app.service # Service definition
│ ├── share/ # Read-only architecture-independent data
│ │ ├── doc/ # Documentation
│ │ │ └── my-app/
│ │ ├── man/ # Manual pages
│ │ │ └── man1/
│ │ └── licenses/ # License files
│ │ └── MIT
│ ├── etc/ # Configuration files (defaults)
│ │ └── my-app.conf
│ └── include/ # Header files (for -dev packages)
│ └── my-app/
│ └── my-app.h
├── config/ # Package-specific mutable config (optional)
│ └── my-app/
└── data/ # Package-specific mutable data (optional)
└── my-app/-
Everything under
system/: All system-level content belongs insidesystem/. This mirrors the Cudane root filesystem where/systemis the base prefix. -
Binaries go in
system/bin/: All executables must reside insystem/bin/. The Binary Reader scans this directory for ELF binaries to analyze their dependencies. -
Libraries go in
system/lib/: All shared libraries must be placed insystem/lib/. The Binary Reader:- Copies discovered supplemental libraries into this directory.
- Also scans this directory for transitive dependency analysis (already-bundles libraries may themselves depend on other libraries).
-
.xcssub-packages go anywhere: Embedded.xcspackages are discovered recursively by thecomponents()function. -
Service files go in
system/dinit.d/: Dinit service files placed here are automatically detected and listed in the package metadata. -
Symlinks follow the same structure: The
linksfield in the manifest should reference paths relative to the given structure (e.g.,"system/lib/libexample.so.1": "system/lib/libexample.so").
The simplest way to ensure correct organization is to use install commands that target the CUDANE_DEST environment variable. For example:
{
"name": "my-app",
"version": "1.0.0",
"build_type": "make",
"build_cmd": "make -j$(nproc)",
"install_cmd": "make DESTDIR=$CUDANE_DEST install prefix=/system",
"links": {
"system/bin/my-app": "system/bin/my-app-v1"
}
}Note
The $CUDANE_DEST environment variable is automatically set by rLine to the absolute path of the package staging directory (pkg/). By installing with DESTDIR=$CUDANE_DEST prefix=/system, the build system places files under $CUDANE_DEST/system/, which becomes pkg/system/ — exactly matching the expected layout.
rLine uses mksquashfs to create .xcs packages and unsquashfs to extract them. These tools are part of the squashfs-tools package and provide high-ratio, random-access read-only filesystem images.
mksquashfs <staging_dir> <output_xcs> -comp zstd -Xcompression-level 3 -noappend| Flag | Used by rLine | Purpose |
|---|---|---|
<staging_dir> |
✅ | Source directory containing the package root tree |
<output_xcs> |
✅ | Output SquashFS file (extension is arbitrary, but .xcs is the Cudane convention) |
-comp zstd |
✅ | Selects Zstandard compression algorithm over gzip, lzo, lz4, xz. Zstd offers an excellent trade-off between compression speed, decompression speed, and compression ratio. |
-Xcompression-level <N> |
✅ | Sets Zstd compression level. Level 3 is the default for zstd and provides fast compression with a ~2-3x compression ratio on typical mixed data. Acceptable range: 1-22. |
-noappend |
✅ | Creates a new filesystem instead of appending to an existing one. Always use this to ensure a clean, independent archive on each build. |
-b <block_size> |
❌ | Sets the filesystem block size (default: 128K). Larger blocks improve compression ratio but increase memory usage. Example: -b 256K. |
-always-use-fragments |
❌ | Store files smaller than block size as fragments shared across files. Reduces archive size. |
-no-duplicates |
❌ | Do not perform duplicate file detection. Can speed up archiving on packages with few identical files. |
-no-sparse |
❌ | Do not detect sparse files. Only relevant for files with large holes (seeked regions). |
-all-root |
❌ | Make all files owned by root. Useful when building packages for system installation. |
-force-uid / -force-gid |
❌ | Force all files to a specific UID/GID. Example: -force-uid 0 -force-gid 0 sets root ownership. |
-p <pseudo-file-definition> |
❌ | Create pseudo files (device nodes, pipes, symlinks) inside the SquashFS without needing them on disk. Example: -p "/dev/null c 1 3". |
-wildcards |
❌ | Enable wildcard matching for exclude files. |
-e <file/dir> |
❌ | Exclude specific files or directories from the image. Can be specified multiple times. |
-ef <exclude_file> |
❌ | Read exclude patterns from a file, one per line. |
-info |
❌ | Display files as they are added to the filesystem. Useful for debugging. |
-progress |
❌ | Display a progress bar during archiving. Disabled in non-interactive mode by default. |
unsquashfs -d <target_dir> -force <package.xcs>rLine supports a fallback extraction chain for packages that may have been created with alternative compression methods:
# Primary (SquashFS native):
unsquashfs -d <target> -force <package.xcs>
# Fallback 1 (tar + zstd):
tar --zstd -xf <package.xcs> -C <target>
# Fallback 2 (zstd pipe + tar):
zstd -dc <package.xcs> | tar -xf - -C <target>Highest compression (slower, smaller output):
mksquashfs staging output_xcs -comp zstd -Xcompression-level 19 -b 1M -always-use-fragments -noappendFastest compression (larger output, faster build):
mksquashfs staging output_xcs -comp zstd -Xcompression-level 1 -noappendRoot ownership for system packages:
mksquashfs staging output_xcs -comp zstd -Xcompression-level 3 -all-root -noappend| Algorithm | rLine Default? | Speed | Ratio | Best For |
|---|---|---|---|---|
| zstd | ✅ | Fast (native) | Good | General purpose; best overall trade-off |
| gzip | ❌ | Medium | Medium | Maximum compatibility (older systems) |
| lzo | ❌ | Very fast | Low | Fast boot times (live systems) |
| lz4 | ❌ | Fastest | Lowest | Very fast random access |
| xz | ❌ | Slow | Best | Maximum compression for distribution |
Tip
The default zstd at level 3 provides roughly 2-3× compression on typical mixed data with near-native decompression speed, making it ideal for package distribution.
pub fn fetch(src: &str, dir: &str) -> Result<()>This function is responsible for getting source code into the workspace. It takes a source string and a destination directory path.
Step-by-step logic:
-
file://prefix stripping: If the source starts withfile://, the prefix is stripped to get the actual filesystem path. This allows users to write"source": "file:///home/user/project"and have it treated as a local path. -
Local path check: The function checks if the (possibly stripped) path exists on the local filesystem using
Path::new(src_path).exists(). If it does, the source is handled locally:- If it is a directory: A recursive copy is performed using the inner
copy_dir_recursive()closure. This closure creates the destination directory, iterates over all entries in the source, and recursively copies directories or directly copies files. The closure is defined inline withinfetch()to keep the recursive logic scoped. - If it is a file: The destination directory is created, and the single file is copied into it, preserving the original filename.
- If it is a directory: A recursive copy is performed using the inner
-
Git clone: If the source ends with
.git, a shallow clone is performed:
git clone --depth 1 <src> <dir>The --depth 1 flag limits the clone to only the most recent commit, minimizing bandwidth and disk usage. If the clone fails, an error is returned.
- Remote archive download: For all other sources (assumed to be URLs pointing to compressed archives), the function:
- Determines the archive name based on the file extension:
.xzbecomestemp.tar.xz,.bz2becomestemp.tar.bz2, everything else becomestemp.tar.gz. - Downloads the file using
curl -fSL -o <archive_path> <src>. The flags mean:-f(fail silently on HTTP errors),-S(show errors),-L(follow redirects). - Extracts the archive using
tarwith the appropriate decompression flag:-xJffor.xz,-xjffor.bz2,-xzffor.gz. The--strip-components=1flag removes the top-level directory from the archive, so the contents are placed directly in the destination directory. - Deletes the downloaded archive file after extraction.
- Returns an error if either
curlortarfails.
- Determines the archive name based on the file extension:
pub fn build(pkg: &Package, dir: &str) -> Result<String>This function executes the build command for a package and returns the captured build log as a String. The return value is used by the dependency scanner (though the current codebase does not perform deep dependency scanning from logs — the log is captured for potential future use or external tooling).
Decision tree:
-
Skip keywords: If
build_cmd(trimmed) equals"none","skip", or"nothing"(case-insensitive comparison viaeq_ignore_ascii_case), the function returns an empty string immediately. No build occurs. -
Empty command with
RLINE_NO_AUTO: Ifbuild_cmdis empty and theRLINE_NO_AUTOenvironment variable is set, the function returns an empty string. This gives users explicit control to disable automatic behaviors. -
Empty command with
build_type == "rust": The automatic Rust build is triggered:- The
RUSTFLAGSenvironment variable is set to Cudane-specific values: linker isclang, target isx86_64-pc-linux-musl, sysroot is/system, and static CRT is enabled. cargo build --target x86_64-unknown-linux-musl --releaseis executed in the source directory.- Both stdout and stderr are captured into a single
log_contentstring. - The log is written to
capture.login the source directory. - If the build succeeds, the log content is returned. If it fails, the error includes the full log output for debugging.
- The
-
Empty command with other build types: Returns an empty string (no-op).
-
Non-empty command: The command is executed via:
sh -c "(<command>) 2>&1 | tee capture.log"The command is wrapped in parentheses to capture all output, 2>&1 redirects stderr to stdout, and tee writes the output to capture.log while also displaying it (though in a non-interactive context, the display effect is minimal). After execution, the log file is read into memory and deleted. If the command succeeds, the log content is returned; otherwise, an error is returned.
pub fn install(pkg: &Package, src: &str, dest: &str) -> Result<()>This function installs built artifacts from the source directory into the package staging directory (dest). The dest path is the pkg subdirectory under the workspace.
Decision tree:
-
Skip keywords: If
install_cmd(trimmed) equals"none","skip", or"nothing"(case-insensitive), the install command is skipped. However, any symlinks declared inpkg.linksare still created. This allows packages to declare symlinks without running any install command. -
Empty command with
RLINE_NO_AUTO: Same as above — install is skipped, symlinks are still processed. -
Empty command with
build_type == "rust": The automatic Rust install is triggered:- The function looks for
target/release/inside the source directory. - If the directory exists, it iterates over all entries.
- For each file (not directory) in
target/release/, it copies the file to the package staging directory, overwriting any existing file with the same name. - After copying, any symlinks in
pkg.linksare created.
- The function looks for
-
Non-empty command: The command is executed via:
sh -c "<install_cmd>"The CUDANE_DEST environment variable is set to the package staging directory path, and CUDANE_PREFIX is set to the package's prefix (e.g., "system", "usr"). The command runs with the source directory as its working directory (current_dir(src)). This allows install commands to reference $CUDANE_DEST as the target root and $CUDANE_PREFIX to target the correct installation prefix. After the command completes, any symlinks in pkg.links are created.
- Empty command with other build types: Falls through to execute the empty string as a command (which effectively does nothing in a shell), then processes symlinks.
pub fn symlink(target: &str, link_path: &str, root_dir: &str) -> Result<()>This function creates a symbolic link inside the package staging directory.
Step-by-step logic:
-
Path sanitization: The
link_pathis stripped of any leading/usingtrim_start_matches('/'). This ensures the link path is relative to the staging root, preventing accidental absolute paths that could escape the staging directory. -
Full path construction: The full link path is constructed as
{root_dir}/{safe_link_path}. -
Parent directory creation: The parent directory of the link path is created using
fs::create_dir_all. This ensures that nested link paths (for example,system/lib/libexample.so) work even if the intermediate directories do not exist yet. -
Existing file removal: Any existing file at the link path is removed with
fs::remove_file. This prevents "File exists" errors when updating symlinks. -
Symlink creation: The symlink is created using
std::os::unix::fs::symlink(target, &full_link_path). This is a Unix-specific system call that creates a symbolic link. Thetargetis stored as-is — it can be relative or absolute, depending on what the manifest specifies.
pub fn dependencies(dest: &str) -> Result<()>This function scans the installed binaries in the package staging directory and copies any external shared libraries they depend on into the package system/lib directory. This is a form of static dependency bundling — it ensures that the package carries its own runtime library dependencies, making it self-contained.
Modern two-phase implementation:
The legacy dependencies() function has been enhanced to delegate to externals(), which performs the two-phase scan described in the Binary Reader section. The function now:
- Calls
externals(dest)which runs bothldd(Phase 1) and the Binary Reader byte stream scanner (Phase 2). - Reports how many supplemental libraries were bundles.
- Returns success.
This makes dependencies() backward-compatible with existing code while leveraging the full power of the Binary Reader.
Legacy single-phase behavior preserved for reference: The old behavior (Phase 1 only) was:
- Scan
{dest}/system/bin/for binaries. - Run
lddon each binary. - Parse output for
=>lines to find external library paths. - Copy external libraries (that don't start with
/system) into{dest}/system/lib/.
pub fn hash(dir: &str) -> Result<String>This function computes a SHA-256 hash of the entire contents of a directory. The hash is deterministic — the same directory contents always produce the same hash.
Implementation:
-
The function runs
tar -cf - -C <dir> .which creates a tar archive of the directory contents on stdout. The-C <dir>flag changes to the target directory before archiving, and.includes everything in that directory. -
The raw byte stream from
taris piped through a SHA-256 hasher (sha2::Sha256::digest()). -
The resulting 32-byte hash is converted to a 64-character hexadecimal string using
format!("{:02x}", b)for each byte.
The use of tar as an intermediate format ensures that the hash covers the complete directory structure, including file contents, permissions, and metadata that tar preserves. This provides a reliable fingerprint for verifying package integrity.
fn license(src_dir: &str) -> StringThis function attempts to automatically determine the software license of a package by scanning its source directory. It is a private function (no pub visibility) called internally by process().
Step-by-step logic:
-
File name matching: The function checks for files with specific names (case-insensitive after uppercasing):
LICENSE,COPYING,LICENSE.MD,COPYING.MD,MIT-LICENSE,UNLICENSE. These are the most common license file names used in open-source projects. -
Regex pattern matching: For each matching file, the content is read and scanned against a comprehensive regex pattern:
(?i)(gnu\s+general\s+public\s+license|gpl|lgpl|agpl|apache|mit|bsd|mpl|\
mozilla\s+public\s+license|unlicense|isc)\s*(v(?:ersion)?\s*\d+(?:\.\d+)?|\
\d+[[---]]clause|\d+(?:\.\d+)?\b)?This regex captures:
- The license name (group 1): Supports GPL, LGPL, AGPL, Apache, MIT, BSD, MPL, Mozilla Public License, Unlicense, ISC, and their variations.
- The version or clause (group 2, optional): Captures version numbers (for example, v2, version 3, 2.0), clause specifications (for example, 2-clause, 3-clause), or bare version numbers.
-
Name formatting: The captured license name is formatted:
- If the name is 4 characters or fewer (for example,
MIT,GPL,BSD), it is uppercased. - Otherwise, it is converted to Title Case (each word capitalized).
- If a version is captured, it is appended: versions starting with
vare formatted asv<number>, others are appended as-is.
- If the name is 4 characters or fewer (for example,
-
Fallback to first line: If no regex match is found, the function reads the first non-empty line of the license file, strips common comment characters (
*,#,/), and if the result is non-empty and under 60 characters, returns it as the license string. -
Default: If no license files are found or none of them yield a recognizable license string, the function returns
"Unknown".
pub fn meta(meta: &PackageMetadata, dest: &str) -> Result<()>This function writes a PackageMetadata struct to disk as a JSON file named metadata.json inside the specified destination directory.
Implementation:
-
The
PackageMetadatastruct is serialized to a pretty-printed JSON string usingserde_json::to_string_pretty(meta). -
The JSON string is written to
{dest}/metadata.jsonusingfs::write.
The metadata.json file becomes part of the package archive and can be read by downstream tools (like the MCX Package Manager) to understand the package provenance, dependencies, and integrity.
pub fn index(index_root: &str, meta: &PackageMetadata) -> Result<()>This function maintains a repository-wide index of all built packages. The index is stored as index.json in the output directory.
Step-by-step logic:
-
Index file location: The index is stored at
{index_root}/index.json. The directory is created if it does not exist. -
Existing index loading: If the index file already exists, it is read and deserialized into a
Vec<PackageMetadata>. If deserialization fails (for example, corrupted file), an empty vector is used as a fallback. -
Entry matching: The function searches for an existing entry with the same
pkg_nameandversionas the new metadata. If found:- If the existing entry is identical to the new one (using
PartialEqcomparison), the function returns early — no update needed. - If different, the existing entry is replaced with the new metadata.
- If the existing entry is identical to the new one (using
-
New entry: If no matching entry exists, the new metadata is appended to the vector.
-
Sorting: The entries are sorted by
(pkg_name, version)using.sort_by()with a tuple comparison. This ensures deterministic ordering in the index file. -
Writing: The sorted vector is serialized to pretty-printed JSON and written to
index.json.
This mechanism allows repository management tools to quickly discover all available packages and their versions without scanning individual .xcs files.
pub fn archive(dest: &str, out: &str) -> Result<()>This function compresses a package staging directory into an .xcs file using mksquashfs with Zstandard compression.
Implementation:
-
Any existing file at the output path is removed (to prevent
mksquashfsfrom complaining about overwriting). -
The
mksquashfscommand is invoked with:
mksquashfs <dest> <out> -comp zstd -Xcompression-level 3 -noappend<dest>: The package staging directory to compress.<out>: The output.xcsfile path.-comp zstd: Uses Zstandard compression algorithm.-Xcompression-level 3: Sets the Zstandard compression level to 3 (fast compression with good ratio).-noappend: Creates a new filesystem instead of appending to an existing one.
- If
mksquashfssucceeds, the function returnsOk(()). Otherwise, it returns an error.
The .xcs extension is a convention used by Cudane for SquashFS images compressed with Zstandard. The -noappend flag ensures each build produces a clean, independent archive.
pub fn components(dest: &str) -> Result<Option<Vec<PackageMetadata>>>This function recursively scans the package staging directory for embedded .xcs sub-packages and extracts their metadata. It enables the construction of meta-packages — composite packages that bundle multiple inner packages into a single archive.
Step-by-step logic:
-
Directory traversal: The function uses a stack-based (
Vecused as a stack) recursive traversal of the entire staging directory tree. Starting from the root (dest), it pushes each discovered subdirectory onto the stack for further exploration. -
.xcsfile detection: For every file encountered during traversal, the extension is checked. Only files with the.xcsextension are processed. -
Metadata extraction: For each
.xcsfile, the function runs:
unsquashfs -p 1 -cat <path_to_xcs> metadata.jsonThe -p 1 flag limits extraction to a single pseudo-file, and -cat prints the content of metadata.json from inside the SquashFS image to stdout.
-
Deserialization: If the
unsquashfscommand succeeds, the stdout is parsed as JSON into aPackageMetadatastruct usingserde_json::from_str. Successfully parsed metadata entries are appended to the components list. -
Return value:
- If no
.xcsfiles were found (or none yielded parseable metadata), the function returnsOk(None). - If one or more components were discovered, the function returns
Ok(Some(components)).
- If no
This mechanism allows the orchestrator to distinguish between standalone packages (no sub-packages) and meta-packages (containing sub-packages), setting the pkg_type accordingly in the metadata.
pub fn services(dest: &str) -> Option<Vec<String>>This function scans the package staging directory for Dinit service files and returns their names. Dinit is the init system used by Cudane Linux, where service files are stored in system/dinit.d/.
Step-by-step logic:
-
Path check: The function looks for
{dest}/system/dinit.d/. If this directory does not exist, the function returnsNoneimmediately — there are no services to register. -
File enumeration: The function iterates over all entries in the
dinit.ddirectory. For each entry that is a file (not a subdirectory), the filename (as a string) is added to the services list. -
Return value:
- If no service files were found, the function returns
None. - If one or more service files were found, the function returns
Some(services).
- If no service files were found, the function returns
Tip
The returned list is stored in the PackageMetadata.services field, allowing the init system to discover which services a package provides without extracting the entire archive.
Internal implementation: The public services() function delegates to spfx(dest, "system") for backward compatibility. The internal spfx() function accepts a prefix parameter, allowing it to scan {dest}/{prefix}/dinit.d/ instead of hardcoding system/. This enables the function to work correctly with packages that use non-default prefixes like "usr".
pub fn profile(dest: &str) -> Vec<String>This function analyzes the binaries in the package staging directory to construct a sandbox profile — a list of capability requirements that the runtime sandbox should grant to the package.
Step-by-step logic:
-
Base profile: Every package starts with the
"isolated-rootfs"profile capability. This is the default sandboxing level that isolates the package's filesystem view. -
Binary scanning: The function looks for
{dest}/system/bin/and{dest}/system/lib/. If either directory exists, it iterates over all files inside. -
Content analysis: For each binary file, the function reads its full content into a buffer and performs substring matching:
- Wayland / display server: If the binary content contains
"wl_"or"wayland", the"wayland"capability is added to the profile (if not already present). - Network: If the binary content contains
"socket","connect", or"bind", the"network"capability is added to the profile (if not already present). - Audio: If the binary content contains
"snd_","pcm", or"pulse", the"audio"capability is added to the profile (if not already present).
- Wayland / display server: If the binary content contains
-
Deduplication: Each capability is only added once — duplicate detection is handled by checking
!profile.contains(&capability)before pushing. -
Return value: The function returns the constructed profile vector, which always contains at least
"isolated-rootfs"and may include any combination of the other capabilities.
This profile is stored in PackageMetadata.profile and can be used by the Cudane runtime sandbox to determine which system resources the package needs access to.
Internal implementation: The public profile() function delegates to ppfx(dest, "system") for backward compatibility. The internal ppfx() function accepts a prefix parameter, allowing it to scan {dest}/{prefix}/bin and {dest}/{prefix}/lib instead of hardcoding system/. This enables the function to work correctly with packages that use non-default prefixes like "usr".
pub fn process(pkg: &Package, out_dir: &str) -> Result<String>This is the main orchestrator function that ties together the entire build pipeline for a single package. It is called by main.rs for each package in the manifest.
Complete step-by-step walkthrough:
-
Output directory resolution: The function gets the current working directory and joins it with the provided
out_dirto create an absolute output directory path. This ensures that all paths are absolute and unambiguous. -
Short-circuit check: The function constructs the expected output path:
{absolute_out_dir}/{name}-{version}XCS. If this file already exists and theRLINE_FORCEenvironment variable is not set, the function returns immediately with the existing path. This prevents unnecessary rebuilds of already-built packages. To force a rebuild, users pass--force(which setsRLINE_FORCE=1). -
Workspace directory setup: The workspace root is determined by the
RLINE_WORKSPACEenvironment variable (defaulting to.rline). Note: The-p/--projectCLI flag setsRLINE_PROJECT_WORKSPACE, butprocess()readsRLINE_WORKSPACE. These are separate variables — the CLI flag does not affect the workspace path used by the library. The per-package workspace is{workspace}/{package_name}. Inside this:src/— Where source code is fetched and built.pkg/— Where installed artifacts are staged before archiving.
-
Clean workspace: If
RLINE_CLEANis set, the entire per-package workspace is deleted before starting. This ensures a completely fresh build. -
Directory creation: Both
src/andpkg/directories are created (withfs::create_dir_all). -
Source fetch:
fetch(&pkg.source, src_str)is called to populate thesrc/directory. If this fails, the error is wrapped with.context("Fetch step failed")for clear error reporting. -
Build execution:
build(pkg, src_str)is called to compile the source. The return value (build log) is discarded withlet _ = ...— it is captured for potential future use but not currently processed further. -
Installation:
install(pkg, src_str, root_str)is called to copy built artifacts into thepkg/staging directory. -
Dependency injection (two-phase binary reader):
externals(root_str)performs the combinedldd+ Binary Reader scan. It returns(external_deps, bundles_deps)— the list of all found external libraries and the list of those that were actually copied intosystem/lib/. -
Dependency signature:
compute(&external_deps)generates a SHA-256 hash over the sorted external dependency list. This signature is embedded in the package metadata. -
Directory hashing:
hash(root_str)computes a SHA-256 checksum of the entire staging directory. -
Component scanning:
components(root_str)recursively scans the staging directory for embedded.xcssub-packages. If found, their metadata is collected (making this a meta-package). -
Service detection:
services(root_str)checks for asystem/dinit.d/directory and collects service filenames. -
Profile construction:
profile(root_str)analyzes binaries insystem/bin/andsystem/lib/to build a sandbox capability profile. -
Metadata construction: A
PackageMetadatastruct is populated with:pkg_name,version,source,build_type: Directly from the manifest.license: Fromlicense(src_str)— the source directory is scanned for license files.build_date: Current UTC time in RFC 3339 format.checksum: The SHA-256 hash from step 10.pkg_type: Set to"meta"if components were found,"standalone"otherwise.components: The result from step 11 (orNoneif no sub-packages found).services: The result from step 12 (orNoneif no dinit services found).profile: The result from step 13.features: A fixed set of optimization features:"lazy-mount","cas-deduplication","atomic-rollback","delta-reconstruct","zero-bloat-deps".externals: The list from step 9 (orNoneif none found).bundles: The list from step 9 (orNoneif none were bundles).depsig: The signature from step 10.
-
Metadata writing:
meta(&metadata, root_str)writesmetadata.jsoninto the staging directory. -
Archiving:
archive(root_str, &final_path)compresses the staging directory into the final.xcsfile. -
Workspace cleanup: If
RLINE_KEEP_SRCis not set, the entire per-package workspace (.rline/{package_name}) is deleted. This keeps the build environment clean. To preserve the workspace for debugging, users pass--keep-src. -
Return: The function returns
Ok(final_path)— the absolute path to the built.xcsfile.
The CLI supports the following flags, each of which sets a corresponding environment variable or triggers a specific action:
| Flag | Long Flag | Environment Variable | Action |
|---|---|---|---|
-h |
--help |
--- | Print help message and exit |
-v |
--version |
--- | Print version and exit |
-a |
--archive |
--- | Manual archive mode (takes 2 args: staging dir, output path) |
-x |
--extract |
--- | Extract mode (takes 1-2 args: package or dir, destination) |
-w |
--write |
--- | Write metadata mode (takes 2 args: src dir, dest dir) |
-i |
--inspect |
--- | Inspect mode (takes 1 arg: package path) |
-n |
--no-auto |
RLINE_NO_AUTO=1 |
Disable automatic build and install behaviors |
-f |
--force |
RLINE_FORCE=1 |
Overwrite existing .xcs packages |
-c |
--clean |
RLINE_CLEAN=1 |
Clean workspace before building |
-s |
--strict |
RLINE_STRICT=1 |
Fail immediately on dependency mapping errors |
-q |
--quiet |
RLINE_QUIET=1 |
Suppress standard output messages |
-d |
--debug |
RLINE_DEBUG=1 |
Enable verbose debug logging |
-y |
--yes |
RLINE_ASSUME_YES=1 |
Assume yes to all prompts |
-k |
--keep-src |
RLINE_KEEP_SRC=1 |
Do not delete source directory after build |
-l |
--parallel |
RLINE_PARALLEL=1 |
Enable parallel package processing |
-j |
--jobs <NUM> |
RLINE_JOBS=<NUM> |
Set number of parallel make jobs |
-z |
--zstd-level <NUM> |
RLINE_ZSTD_LEVEL=<NUM> |
Set zstd compression level for mksquashfs |
-p |
--project <DIR> |
RLINE_PROJECT_WORKSPACE=<DIR> |
Define custom project or workspace directory |
-t |
--target <ARCH> |
RLINE_TARGET=<ARCH> |
Define target architecture |
-m |
--manifest <FILE> |
--- | Path to manifest.json |
-o |
--output <DIR> |
--- | Path to output directory |
Note
The environment variables are set using unsafe { env::set_var(...) } because the Rust standard library marks set_var as unsafe (it can cause data races in multi-threaded contexts). Since rLine processes packages sequentially in a single thread, this is safe in practice.
The .xcs file is a SquashFS filesystem image compressed with Zstandard at compression level 3. SquashFS is a highly compressed, read-only filesystem for Linux that provides:
- Random access: Files can be read without decompressing the entire archive.
- Metadata preservation: File permissions, ownership, and timestamps are preserved.
- Efficient storage: SquashFS is designed for maximum compression.
The mksquashfs tool creates the image, and unsquashfs extracts it. The -noappend flag ensures each build produces a fresh image rather than appending to an existing one.
Inside every .xcs archive, there is a metadata.json file at the root that contains the PackageMetadata structure. This allows downstream tools to inspect package properties without extracting the entire archive.
The dependencies() function (backed by externals()) implements a form of automatic dependency bundling powered by the two-phase Binary Reader. The detailed mechanics are as follows:
-
Binary discovery: The function scans
{dest}/system/bin/for executable files. Only files (not directories or symlinks) are processed. -
Phase 1 —
lddinvocation: For each binary,ldd <path>is executed.lddprints the shared library dependencies of the binary, showing which libraries are needed at runtime and where they are currently found on the system. -
Phase 1 — Library filtering: The
lddoutput is parsed line by line. Lines containing=>indicate a dynamic library mapping. The function extracts the absolute path of the library (the part after=>and before any address information). -
Phase 1 — Path validation: Libraries are only copied if:
- The path is non-empty.
- The path starts with
/(absolute path). - The path does not start with
/system(to avoid copying Cudane system libraries that are already part of the target root). - The library is not a core system library (checked via
is_core_system_lib()).
-
Phase 2 — Binary Reader scan: After the
lddpass, the Binary Reader scans all ELF binaries insystem/bin/andsystem/lib/by reading them as byte streams and extracting printable strings. This catches runtimedlopen()calls and other library references not visible toldd. -
Phase 2 — Library resolution: Libraries found by the Binary Reader are looked up on the host system using standard search paths (
/usr/lib,/lib,/system/lib, etc.). If found, they are copied; if not, they are recorded as "not found" dependencies. -
Copy operation: Each valid non-core, non-system library is copied to
{dest}/system/lib/, preserving the original filename. If a library with the same name already exists in the destination, the copy is skipped (preventing redundant overwrites). -
Dependency signature: After all dependencies are resolved, a SHA-256 signature is computed over the sorted list of all external dependencies and stored in the package metadata.
This mechanism ensures that packages carry their own runtime dependencies, making them portable across Cudane installations that may have different sets of system libraries.
The license() function implements a heuristic scanner that works as follows:
-
File enumeration: The function reads all entries in the source directory and checks their names (uppercased) against a list of known license file names:
LICENSE,COPYING,LICENSE.MD,COPYING.MD,MIT-LICENSE,UNLICENSE. -
Content scanning: For each matching file, the content is read and scanned with a regex pattern designed to match common open-source license declarations. The regex is case-insensitive and captures:
- License names: GPL, LGPL, AGPL, Apache, MIT, BSD, MPL, Mozilla Public License, Unlicense, ISC.
- Version and clause information: Version numbers (for example,
v2,version 3), clause specifications (for example,2-clause,3-clause), or bare numbers.
-
Name normalization: The captured license name is normalized:
- Short names (4 characters or fewer) are uppercased:
mitbecomesMIT,gplbecomesGPL. - Longer names are Title Cased:
gnu general public licensebecomesGNU General Public License. - Version information is appended:
GPL v3,MIT,BSD 2-Clause.
- Short names (4 characters or fewer) are uppercased:
-
Fallback: If no regex match is found, the function reads the first non-empty line of the license file, strips comment characters (
*,#,/), and if the result is under 60 characters, returns it as-is. -
Default: If no license files are found or none yield a recognizable string,
"Unknown"is returned.
The index() function maintains a cumulative index of all packages built into a given output directory. The index is stored as index.json and follows this logic:
-
Load existing index: The function reads
{index_root}/index.jsonif it exists. If the file is missing or corrupted, an empty list is used. -
Match by identity: The function searches for an existing entry with the same
pkg_nameandversion. This is a compound key — both fields must match for an entry to be considered the same package. -
Update or append:
- If a matching entry is found and it is identical to the new metadata (using
PartialEq), no changes are made. - If a matching entry is found but different, it is replaced with the new metadata.
- If no matching entry is found, the new metadata is appended.
- If a matching entry is found and it is identical to the new metadata (using
-
Sort: The entries are sorted by
(pkg_name, version)for deterministic ordering. -
Write: The sorted list is serialized to pretty-printed JSON and written to
index.json.
This design allows the index to be incrementally updated as new packages are built, without requiring a full rebuild of the index each time.
Because rLine delegates specialized operations to highly optimized system-level utilities, the build host must have the following tools installed and accessible in $PATH:
| Dependency | Purpose within rLine |
|---|---|
| Rust Toolchain | Required to build the core rLine compiler itself, alongside fallback execution of cargo for rust build-types. |
sh (POSIX Shell) |
Executing custom user-defined build and install command hooks dynamically. |
git |
Managing external source code repositories via rapid depth-restricted clones. |
curl |
Handling remote archive downloads with robust error and status tracking. |
tar |
Extracting source assets, staging layout tracking, and processing intermediate streams. |
mksquashfs / unsquashfs |
Packaging completed build distributions into .xcs files and extracting them. Also used for embedded metadata extraction during component scanning. Part of squashfs-tools. |
zstd |
Compression backend used by mksquashfs (though mksquashfs links against libzstd internally). |
ldd |
Phase 1 of the two-phase dependency scan: captures compile-time DT_NEEDED entries. |
file |
Used by the --inspect flag to determine the file type of a package. |
When building packages intended to run natively inside the Cudane landscape, compilation routines must adhere strictly to the target distribution standardized structural layouts and compiler configurations.
To protect system integrity and match Cudane custom root-directory paradigm, software components should not target conventional paths like /usr or /usr/local. Instead, specify the official base prefix explicitly:
--prefix=/systemCudane eliminates standard GCC assumptions in favor of a modern, strict LLVM and Clang foundation backed by the lightweight musl C library. Every source compilation command block initialized via a manifest must declare the tracking environment variables and direct cross-compilation target parameters:
CC=clang CXX=clang++ -target x86_64-pc-linux-musl --sysroot=$ROOTFS/systemWarning
Make sure that you have been set up the $ROOTFS variable befor start building
CC=clang/CXX=clang++: Explicitly bypasses host system GCC links, forcing unified code generation and deterministic warning and error processing across all distribution builds.-target x86_64-pc-linux-musl: Mandates code generation tailored to the highly performantmuslC library runtime engine instead of standardglibc.--sysroot=$ROOTFS/system: Redirects the Clang link editor and preprocessor header mechanics directly to your isolated target filesystem tree, guaranteeing that compile-time dependencies are pulled entirely from Cudane assets rather than local host system resources.
rLine avoids hardcoded compilation workflows or locked execution loops. By processing commands via raw standard shell execution (sh -c), developers retain absolute toolchain freedom. You can easily build software across any cross-compilation landscape, architecture, or internal system structure simply by passing custom environment flags and wrappers directly into your manifest hooks.
To manually create an .xcs archive from a directory without going through the full build pipeline:
rline -a /path/to/staging/dir /path/to/output_xcsThis runs mksquashfs directly with Zstandard compression level 3.
To extract an .xcs package (or multiple packages from a directory) into a target root:
rline -x /path/to/package.xcs /target/rootOr for all packages in a directory:
rline -x /path/to/packages/dir /target/rootThe extractor first tries unsquashfs (native SquashFS extraction), then falls back to tar --zstd -xf or zstd -dc | tar -xf for compatibility with tar+zstd archives.
To generate a metadata.json for a directory without archiving it:
rline -w /path/to/source/dir /path/to/dest/dirTip
This creates a metadata.json in the destination directory with the package name derived from the source directory filename, version set to "manual", and a SHA-256 checksum of the destination directory. The components, services, profile, features, externals, bundles, and depsig fields are set to empty/None defaults.
To inspect an .xcs package and view its metadata:
rline -i /path/to/package.xcsThis prints:
- The file path and size (in bytes and MB).
- The file type (from the
filecommand). - SquashFS compression specifications (from
unsquashfs -stat). - The embedded
metadata.jsoncontent (fromunsquashfs -cat), including any dependency tracking fields.
Here is the complete lifecycle of a package from manifest to .xcs archive:
-
Validation:
main.rsreads the JSON manifest and deserializes it into aManifeststruct. If the JSON is malformed or missing required fields, an error is returned immediately. -
Short-Circuit Check:
process()checks if the output file<name>-<version>XCSalready exists in the output directory. If it does andRLINE_FORCEis not set, the package is skipped entirely — no fetch, build, or archive operations occur. -
Workspace Isolation: A workspace directory is created at
.rline/<package_name>/withsrc/andpkg/subdirectories. IfRLINE_CLEANis set, any existing workspace for this package is deleted first. -
Source Fetching:
fetch()retrieves the source code intosrc/using the appropriate method (local copy, git clone, or curl+tar download). -
Build Execution:
build()executes the build command in thesrc/directory. For Rust packages with emptybuild_cmd, automaticcargo build --releaseis triggered with Cudane-specific flags. -
Installation:
install()copies built artifacts fromsrc/topkg/. For Rust packages with emptyinstall_cmd, files fromtarget/release/are automatically copied. -
Two-Phase Dependency Resolution:
externals()scans binaries inpkg/system/bin/with bothldd(Phase 1) and the Binary Reader byte stream scanner (Phase 2). External libraries are classified as core system libs (skipped) or supplemental libs (copied intopkg/system/lib/). -
Dependency Signature:
compute()generates a SHA-256 hash of the sorted external dependency list. -
Hashing:
hash()computes a SHA-256 checksum of the entirepkg/directory. -
Component Scanning:
components()recursively scans the staging directory for embedded.xcssub-packages and extracts their metadata. This determines whether the package is a standalone or meta-package. -
Service Detection:
services()checks for asystem/dinit.d/directory and collects the names of any Dinit service files found. -
Profile Construction:
profile()analyzes binaries insystem/bin/andsystem/lib/for keywords related to Wayland (display), networking, and audio to build a sandbox capability profile. -
License Detection:
license()scans thesrc/directory for license files and extracts the license name. -
Metadata Generation: A
PackageMetadatastruct is populated with all collected information — identity fields from the manifest, license from detection, checksum from hashing, services from detection, profile from binary analysis, features from the optimization list, components from scanning, external dependencies from the two-phase scan, bundles dependencies from the copy operation, and the dependency signature — then written topkg/metadata.json. -
Archiving:
archive()compresses thepkg/directory into<name>-<version>XCSusingmksquashfswith Zstandard compression. -
Cleanup: If
RLINE_KEEP_SRCis not set, the workspace directory (.rline/<package_name>/) is deleted, leaving only the final.xcsfile.
Build the tool:
cargo build --releaseIf you are on Cudane:
cargo build --release --target x86_64-unknown-linux-muslRun with a manifest and an output directory:
./target/release/rline manifest.json output_dirOptional flag:
./target/release/rline manifest.json output_dir --no-autoNote
When --no-auto is passed, the program sets the RLINE_NO_AUTO environment variable to disable the auto behaviors for empty build_cmd and install_cmd fields.
[Myden]: Cudane, MCX and rLine Founder - Made with 🤍 and Rust.