Skip to main content

rebar.config

{erl_opts, [debug_info]}.

%% OTP floor: the device resource type registers a `down` callback via
%% enif_init_resource_type (the ErlNifResourceTypeInit members/dyncall form),
%% an OTP-24 API. midiio cannot build on OTP < 24.
{minimum_otp_vsn, "24"}.

{deps, []}.

{project_plugins, [rebar3_hex]}.

%% ── NIF build wiring (port_compiler) ───────────────────────────────────────
%% Compiles the single NIF translation unit into priv/midiio_nif.so. On Darwin
%% pc links the bundle that erlang:load_nif/2 resolves from "midiio_nif" (no
%% extension); the per-OS link flags pull in the platform MIDI backend.
{plugins, [pc]}.

{provider_hooks, [
    {pre, [
        {compile, {pc, compile}},
        {clean, {pc, clean}}
    ]}
]}.

{port_specs, [
    {"darwin", "priv/midiio_nif.so", ["c_src/midiio_nif.c"]},
    {"linux",  "priv/midiio_nif.so", ["c_src/midiio_nif.c"]}
]}.

{port_env, [
    {"darwin", "CFLAGS", "$CFLAGS -std=c11 -Wall -Wextra"},
    {"darwin", "LDFLAGS", "$LDFLAGS -framework CoreMIDI"},
    %% _GNU_SOURCE: strict -std=c11 hides POSIX symbols (clock_gettime,
    %% CLOCK_MONOTONIC) and makes ALSA's <global.h> redefine struct timespec.
    %% Opt back into the POSIX/GNU feature set so <alsa/asoundlib.h> compiles.
    {"linux", "CFLAGS", "$CFLAGS -std=c11 -D_GNU_SOURCE -Wall -Wextra"},
    {"linux", "LDFLAGS", "$LDFLAGS -lasound -lpthread"}
]}.

{artifacts, ["priv/midiio_nif.so"]}.

%% ── Coverage strategy (NIF-aware) ──────────────────────────────────────────
%% `midiio` is a NIF-binding module: nearly every function is a `?NOT_LOADED`
%% stub whose Erlang body is unreachable once the .so loads (the C NIF replaces
%% it). Line coverage on it is structurally near-zero and *decays* as each slice
%% adds NIFs (slice 1: 33%, slice 3: 22%). So it is excluded from the cover
%% metric — its surface is verified by **eunit + the ASan harness**, not by
%% `midiio.beam` line %. The `min_coverage` floor (below) therefore gates the
%% real-logic Erlang modules; there are none yet (the per-device gen_server +
%% helpers land in arc 2/3), so the gate is dormant now and becomes a true gate
%% automatically when they arrive — without ever forcing the floor down again.
{cover_excl_mods, [midiio]}.

{xref_checks, [
    undefined_function_calls, undefined_functions, locals_not_used,
    deprecated_function_calls, deprecated_functions
]}.

{dialyzer, [
    {warnings, [unknown]}
    %% plt_extra_apps is scoped to the test profile (below): only the `as test`
    %% path analyses the eunit/proper test modules and needs those apps in the
    %% PLT. A bare `rebar3 dialyzer` (default profile) sees only src and must NOT
    %% reference proper (a test-only dep), or it fails "Could not find
    %% application: proper" (slice-4 finding #2 / F3).
]}.

{profiles, [
    {test, [
        {deps, [
            {proper, {git, "https://github.com/proper-testing/proper", {tag, "v1.3"}}}
        ]},
        {plugins, [
            {rebar3_proper, {git, "https://github.com/ferd/rebar3_proper", {tag, "0.12.1"}}}
        ]},
        {eunit_opts, [verbose]},
        %% Run eunit cover-instrumented so the `coverage` step has real coverdata
        %% for midiio.beam (the NIF upgrade callback makes the module reloadable).
        {cover_enabled, true},
        %% Test-profile-only: the `as test` dialyzer/check path also analyses the
        %% eunit/proper test modules, so teach the PLT about those apps here (not
        %% top-level, so a bare default-profile `rebar3 dialyzer` stays clean).
        {dialyzer, [{plt_extra_apps, [eunit, proper]}]}
        %% F2/Fix-2 (gate seam_roundtrip + the other test NIFs behind -DMIDIIO_TEST)
        %% was attempted again in the slice-2 S2 remediation and reverted — the same
        %% L18 hazard: pc builds one shared priv/*.so keyed on source mtime (not
        %% CFLAGS), so a profile macro doesn't trigger a rebuild. Forcing a rebuild
        %% via a compile pre_hook (rm the .so/.o) breaks rebar3's {artifacts,...}
        %% check ("Missing artifact priv/midiio_nif.so"). Unexported-NIF gating also
        %% fails — load_nif requires the NIF exported ({bad_lib,"Function not
        %% found"}). So the test NIFs stay in the surface; they are MEMORY-SAFE
        %% (seam_roundtrip self-defends on length, Fix 1, ASan-proven). Re-entry:
        %% a per-profile .so artifact path. S3 hygiene; safety is closed.
    ]},
    {dev, [
        {deps, [lfe]},
        {plugins, [rebar3_lfe]}
    ]},
    {maintainer, [
        {plugins, [rebar3_hex]}
    ]}
]}.

{alias, [
    {coverage, [
        {proper, "-c"},
        %% Real target for real-logic modules (see cover_excl_mods above). With
        %% the NIF-binding module excluded there are no logic modules yet, so the
        %% gate is dormant; this floor becomes a true gate when arc-2/3 modules
        %% land. Adding another NIF no longer forces the floor down.
        {cover, "-v --min_coverage=80"}
    ]},
    {check, [
        compile,
        xref,
        dialyzer,
        eunit,
        coverage
    ]},
    {publish, [
        compile,
        {hex, "publish package"}
    ]}
]}.