pitchfork-cli 2.29.0

Daemons with DX
Documentation
//! Build script for pitchfork
//!
//! Settings are declared directly on the structs in `src/settings.rs` via
//! `#[derive(usage_rs::Config)]`; nothing is generated here any more. This
//! script tracks the embedded web UI assets, keeps a plain `cargo build` working
//! when the UI has not been built, and, for release builds, links the executable
//! non-PIE.

use std::path::Path;

/// Marks the page written by [`ensure_web_ui`], so a later build can tell it from
/// a real UI build and keep warning until the real one replaces it.
const PLACEHOLDER_MARKER: &str = "<!-- pitchfork-web-ui-placeholder -->";

fn main() {
    println!("cargo:rerun-if-changed=ui/dist");
    ensure_web_ui();
    link_without_pie();
}

/// The web UI is embedded from `ui/dist`, which is generated by `mise run build:ui`
/// and not checked in, so a fresh checkout has nothing to embed and `cargo build`
/// (or `cargo install --git`) failed with an opaque `RustEmbed` error.
///
/// Debug builds embed a placeholder page instead, so the supervisor, CLI and TUI
/// build without Node.js. Release builds ship to users and must not silently
/// contain an empty dashboard, so they stop and say how to build it.
fn ensure_web_ui() {
    let dist = Path::new("ui/dist");
    let index = dist.join("index.html");
    let placeholder = std::fs::read_to_string(&index)
        .map(|html| html.contains(PLACEHOLDER_MARKER))
        .unwrap_or(false);
    if index.is_file() && !placeholder {
        return;
    }
    if std::env::var("PROFILE").as_deref() == Ok("release") {
        panic!(
            "the web UI has not been built: ui/dist/index.html is {}. \
             Run `mise run build:ui` first; release builds embed it.",
            if placeholder {
                "a placeholder"
            } else {
                "missing"
            }
        );
    }
    if !placeholder {
        let html = format!(
            "<!doctype html><html><head><meta charset=\"utf-8\"><title>pitchfork</title></head>\
             <body>{PLACEHOLDER_MARKER}<p>This pitchfork build has no web UI. \
             Run <code>mise run build:ui</code> and rebuild to include it.</p></body></html>\n"
        );
        std::fs::create_dir_all(dist)
            .and_then(|()| std::fs::write(&index, html))
            .expect("failed to write the placeholder ui/dist/index.html");
    }
    // `mise run build:ui` skips itself when its outputs are newer than its
    // sources. A placeholder with a recent modification time would look up to date
    // and keep the real UI from ever being built, so date it before any source.
    // Done for an existing placeholder too, in case it was touched or an earlier
    // attempt to date it failed.
    if let Err(err) = std::fs::File::options()
        .write(true)
        .open(&index)
        .and_then(|file| file.set_modified(std::time::UNIX_EPOCH))
    {
        println!(
            "cargo:warning=could not back-date the placeholder ui/dist/index.html ({err}); \
             delete ui/dist before running `mise run build:ui`"
        );
    }
    println!(
        "cargo:warning=ui/dist has no built web UI; embedding a placeholder page. \
         Run `mise run build:ui` to include the real one."
    );
}

/// Release builds for Linux GNU set `PITCHFORK_NO_PIE=1` (see
/// .github/workflows/release.yml) to link the executable at a fixed address. As
/// a position-independent executable, pitchfork makes the dynamic loader patch
/// about 50k pointers on every launch, which copies hundreds of pages before
/// `main` runs. Linked non-PIE, those pointers are final in the file.
/// Dependencies are still compiled position-independent; the flag reaches only
/// bin targets, so no shared library is linked with it. musl is left out on
/// purpose: its static-PIE start code crashes when linked with `-no-pie`, and
/// the alternative, `-C relocation-model=static`, is not something a build
/// script can set.
fn link_without_pie() {
    println!("cargo:rerun-if-env-changed=PITCHFORK_NO_PIE");
    let linux_gnu = std::env::var("CARGO_CFG_TARGET_OS").as_deref() == Ok("linux")
        && std::env::var("CARGO_CFG_TARGET_ENV").as_deref() == Ok("gnu");
    if linux_gnu && std::env::var("PITCHFORK_NO_PIE").as_deref() == Ok("1") {
        println!("cargo:rustc-link-arg-bins=-no-pie");
    }
}