Adding C to a package

Adding C to a package

Declaring the build

C sources are listed explicitly in the manifest, not discovered. The convention is c/<subsystem>/.

[native]
sources = ["c/random/random.c"]
include = ["c/random"]

[native.flags]
all = ["-O2", "-fno-strict-aliasing"]

[native.link]
linux = ["-lpthread", "-lm"]
android = ["-llog"]

flags and link are keyed by all or a platform name; every matching key contributes. An unrecognized key is an error.

peko.h is always on the include path, so a header only needs #include <peko.h>.

A complete module

The smallest real example in the standard library is std::random. It has three parts.

The C implementation, c/random/random.c, an ordinary source file.

The header, c/random/random.peko.h:

#include <peko.h>

PEKO_BEGIN

/* Fast non-cryptographic CMWC PRNG primitives. Scalars only cross the
   boundary; the Rng handle is an unmanaged malloc pointer the caller owns
   and frees. */

p_fn void   peko_random_seed(p_i32 seed);
p_fn p_i32  peko_random_int(p_i32 min, p_i32 max);
p_fn p_f32  peko_random_float();

p_fn p_opaque peko_rng_new();
p_fn void     peko_rng_free(p_opaque rng);
p_fn p_i32    peko_rng_int(p_opaque rng, p_i32 min, p_i32 max);

PEKO_END

The PekoScript half, random.peko:

import c::random::random as cmwc;

[public] fn next_int(min: number, max: number) => number {
    let lo: i32 = danger_cast<i32>(min.to_raw())
    let hi: i32 = danger_cast<i32>(max.to_raw())
    let result: i32 = cmwc::peko_random_int(lo, hi)
    return new number(danger_cast<f64>(result))
}

[public] class Rng {
    [private] handle: opaque

    constructor() {
        this.handle = cmwc::peko_rng_new()
    }

    [public] fn free() {
        cmwc::peko_rng_free(this.handle)
    }
}

That is the whole idiom: unbox with .to_raw() and danger_cast, call the raw function, re-box the result, and hold C state as opaque with an explicit free.

How it builds

Each source is compiled separately with the target's clang. C++ sources get the toolchain's C++ standard; C and Objective-C do not. Objective-C sources get the Objective-C flags as well.

Compilation is incremental by modification time, and there is no header dependency tracking. Editing a .h does not rebuild the .c files that include it. Touch the sources or run peko clean.

Object files are named with the package and version leading, so two packages can each ship an alloc.c without colliding.

Toolchain flags first, then the package's [native.link] arguments, then the objects: your compiled PekoScript, your native objects, your vendored archives, and finally the toolchain's runtime objects. A package's archives come after that package's own objects, so their members resolve the symbols those objects reference.