leafbuild-ml

What is a middle layer in leafbuild?

Most C/C++ build systems are isolated, in the sense that you cannot use multiple build systems in the same codebase, and when you have a dependency that you need to build which uses another build system than the one you use, it gets a lot harder to get them to talk to each other.

So a middle layer is a layer that goes between leafbuild and some other build systems. Read this document carefully if you want to write your own.

Conventions

  1. Across build system boundaries, the build directory at least somewhat resembles the directory structure of the source directory.Across build system boundaries, output directories of sibling projects & modules are always siblings, output directories of parent / child projects and modules have to at least somewhat resemble that parent / child relationship. More about this here.
  2. Across build system boundaries, the build system that manages the outer directory is responsible for creating a directory for the build system that manages the inner directory in it’s own designated space.
  3. If you recognize an added subdirectory as your own, you should handle it. Only and only otherwise should you invoke the middle layers to handle it.

Output directory resemblance

TBD

How leafbuild-ml works

It simply exposes a trait: MiddleLayer that other crates implement, and perform a little linker magic to make leafbuild-ml aware of those implementations.

The MiddleLayer trait is pretty simple:

use leafbuild_core::lf_buildsys::LfBuildsys;
use std::collections::HashMap;
use std::path::{Path, PathBuf};

/// A simple result type with all the possible errors a middle layer might throw
pub type Result<T> = std::result::Result<T, MiddleLayerError>;

/// A middle layer trait
pub trait MiddleLayer {
    /// Try to recognize a given `add_subdir()`-ed directory.
    /// This usually should just check that `path` contains a
    /// given file meaningful to the build system of this middle
    /// layer.
    ///
    /// For example, for cmake: check that `path/CMakeLists.txt` exists.
    /// For meson, check that `path/meson.build` exists, etc.
    fn recognize(&self, path: &Path) -> RecognizeResult;

    /// Invokes the inner build system and returns the changes that should be made
    /// to the [`LfBuildsys`]
    /// # Errors
    /// Anything that can go wrong. Though the build will fail if this returns `Err`.
    fn handle<'buildsys>(
        &self,
        buildsys: &'buildsys LfBuildsys<'buildsys>,
        boundary_details: BuildsysBoundaryDetails,
    ) -> Result<BuildsysChanges>;
}

/// Whether the middle layer recognizes the directory or not
#[derive(Debug, Copy, Clone)]
pub enum RecognizeResult {
    /// When it was recognized
    Recognized,
    /// When it was not recognized
    NotRecognized,
}

/// The data passed across a
/// [build system boundary](https://leafbuild.github.com/dev/terminology.html#build-system-boundary)
#[derive(Debug)]
pub struct BuildsysBoundaryDetails<'boundary> {
    /// The source root
    pub source_root: &'boundary Path,
    /// The output root
    pub output_root: &'boundary Path,

    /// The source folder that was `add_subdir()`-ed, and checked
    /// previously with [`MiddleLayer::recognize`]
    pub source_folder: PathBuf,
    /// The output folder assigned to this subdirectory for the
    /// inner build system to write files to.
    pub output_folder: PathBuf,

    /// The "arguments" passed to the inner build system.
    /// In cmake they are variables that should
    /// be `set()` before invoking the `CMakeLists.txt`,
    /// while in meson they are global variables.
    ///
    /// Since meson takes types *somewhat* seriously,
    /// they should be converted.
    pub arguments: HashMap<String, String>,
    // will maybe add more
}

/// The build system changes that are to be applied to
/// a [`LfBuildsys`] after [`MiddleLayer::handle`]
/// executed.
#[derive(Copy, Clone, Debug, Default)]
pub struct BuildsysChanges {}

/// Errors that can occur during a middle layer's execution.
#[derive(Error, Debug)]
pub enum MiddleLayerError {
    /// Any other error
    #[error("Other error: {0}")]
    Other(#[from] Box<dyn std::error::Error>),
}