hb_features.erl - Compile-Time Feature Flags
Overview
Purpose: Runtime access to compile-time feature flags
Module: hb_features
Pattern: -ifdef macro bridge to runtime
Scope: Build system to runtime environment proxy
This module acts as a bridge between the build system and runtime execution environment, exposing compile-time feature flags as runtime-queryable functions. Features are enabled via -DENABLE_<FEATURE> compiler flags during build.
Feature Flag System
Features are conditionally compiled using Erlang's -ifdef preprocessor directive:
-ifdef(ENABLE_HTTP3).
http3() -> true.
-else.
http3() -> false.
-endif.This allows the build system to control which features are available at runtime without modifying source code.
Dependencies
- HyperBEAM:
hb_maps - Erlang/OTP: Standard library only
Public Functions Overview
%% Query Functions
-spec all() -> FeatureMap.
-spec enabled(Feature) -> boolean().
%% Individual Feature Flags
-spec http3() -> boolean().
-spec rocksdb() -> boolean().
-spec test() -> boolean().
-spec genesis_wasm() -> boolean().
-spec eflame() -> boolean().Public Functions
1. all/0
-spec all() -> FeatureMap
when
FeatureMap :: #{atom() => boolean()}.Description: Returns a map of all available feature flags and their enabled status. Automatically discovers features by introspecting module exports, filtering out utility functions.
Test Code:-module(hb_features_all_test).
-include_lib("eunit/include/eunit.hrl").
all_returns_map_test() ->
Features = hb_features:all(),
?assert(is_map(Features)).
all_contains_known_features_test() ->
Features = hb_features:all(),
?assert(maps:is_key(http3, Features)),
?assert(maps:is_key(rocksdb, Features)),
?assert(maps:is_key(test, Features)),
?assert(maps:is_key(genesis_wasm, Features)),
?assert(maps:is_key(eflame, Features)).
all_values_are_boolean_test() ->
Features = hb_features:all(),
Values = maps:values(Features),
?assert(lists:all(fun(V) -> is_boolean(V) end, Values)).
all_excludes_utility_functions_test() ->
Features = hb_features:all(),
?assertEqual(false, maps:is_key(all, Features)),
?assertEqual(false, maps:is_key(enabled, Features)),
?assertEqual(false, maps:is_key(module_info, Features)).Features = hb_features:all().
% Result: #{
% http3 => false,
% rocksdb => true,
% test => true,
% genesis_wasm => false,
% eflame => false
% }2. enabled/1
-spec enabled(Feature) -> boolean()
when
Feature :: atom().Description: Check if a specific feature flag is enabled. Returns false for unknown features.
-module(hb_features_enabled_test).
-include_lib("eunit/include/eunit.hrl").
enabled_returns_boolean_test() ->
Result = hb_features:enabled(http3),
?assert(is_boolean(Result)).
enabled_known_feature_test() ->
% Test will pass regardless of actual flag status
HTTP3 = hb_features:enabled(http3),
RocksDB = hb_features:enabled(rocksdb),
?assert(is_boolean(HTTP3)),
?assert(is_boolean(RocksDB)).
enabled_unknown_feature_test() ->
Result = hb_features:enabled(nonexistent_feature),
?assertEqual(false, Result).
enabled_matches_all_test() ->
Features = hb_features:all(),
HTTP3FromAll = maps:get(http3, Features),
HTTP3FromEnabled = hb_features:enabled(http3),
?assertEqual(HTTP3FromAll, HTTP3FromEnabled).% Check individual features
case hb_features:enabled(http3) of
true -> start_http3_server();
false -> start_http2_server()
end.
% Guard against missing features
case hb_features:enabled(rocksdb) of
true -> {ok, use_rocksdb_store};
false -> {error, rocksdb_not_enabled}
end.3. http3/0
-spec http3() -> boolean().Description: Check if HTTP/3 support is enabled. Requires ENABLE_HTTP3 compiler flag.
-module(hb_features_http3_test).
-include_lib("eunit/include/eunit.hrl").
http3_returns_boolean_test() ->
Result = hb_features:http3(),
?assert(is_boolean(Result)).
http3_consistent_test() ->
Result1 = hb_features:http3(),
Result2 = hb_features:http3(),
?assertEqual(Result1, Result2).
http3_in_all_test() ->
Features = hb_features:all(),
?assertEqual(hb_features:http3(), maps:get(http3, Features)).# Enable HTTP/3
rebar3 compile -D ENABLE_HTTP3
# Disable HTTP/3 (default)
rebar3 compile4. rocksdb/0
-spec rocksdb() -> boolean().Description: Check if RocksDB storage backend is enabled. Requires ENABLE_ROCKSDB compiler flag.
-module(hb_features_rocksdb_test).
-include_lib("eunit/include/eunit.hrl").
rocksdb_returns_boolean_test() ->
Result = hb_features:rocksdb(),
?assert(is_boolean(Result)).
rocksdb_consistent_test() ->
Result1 = hb_features:rocksdb(),
Result2 = hb_features:rocksdb(),
?assertEqual(Result1, Result2).
rocksdb_store_selection_test() ->
Store = case hb_features:rocksdb() of
true -> hb_store_rocksdb;
false -> hb_store_ets
end,
?assert(is_atom(Store)).# Enable RocksDB
rebar3 compile -D ENABLE_ROCKSDB
# Disable RocksDB (default)
rebar3 compile5. test/0
-spec test() -> boolean().Description: Check if test mode is enabled. Automatically enabled by TEST macro (set by EUnit and rebar3 during test runs).
-module(hb_features_test_test).
-include_lib("eunit/include/eunit.hrl").
test_returns_boolean_test() ->
Result = hb_features:test(),
?assert(is_boolean(Result)).
test_enabled_during_tests_test() ->
% This test runs in test mode, so test() should return true
?assertEqual(true, hb_features:test()).
test_mode_affects_behavior_test() ->
case hb_features:test() of
true ->
% Test mode: use mock data
?assertEqual(test_mode, test_mode);
false ->
% Production mode: use real data
?assertEqual(prod_mode, prod_mode)
end.% During normal compilation
hb_features:test(). % → false
% During test runs (rebar3 eunit)
hb_features:test(). % → true6. genesis_wasm/0
-spec genesis_wasm() -> boolean().Description: Check if Genesis WASM support is enabled. Requires ENABLE_GENESIS_WASM compiler flag.
-module(hb_features_genesis_wasm_test).
-include_lib("eunit/include/eunit.hrl").
genesis_wasm_returns_boolean_test() ->
Result = hb_features:genesis_wasm(),
?assert(is_boolean(Result)).
genesis_wasm_consistent_test() ->
Result1 = hb_features:genesis_wasm(),
Result2 = hb_features:genesis_wasm(),
?assertEqual(Result1, Result2).
genesis_wasm_module_loading_test() ->
case hb_features:genesis_wasm() of
true ->
% Load genesis WASM modules
?assertEqual(available, available);
false ->
% Skip genesis WASM functionality
?assertEqual(unavailable, unavailable)
end.# Enable Genesis WASM
rebar3 compile -D ENABLE_GENESIS_WASM7. eflame/0
-spec eflame() -> boolean().Description: Check if eflame profiler support is enabled. Requires ENABLE_EFLAME compiler flag.
-module(hb_features_eflame_test).
-include_lib("eunit/include/eunit.hrl").
eflame_returns_boolean_test() ->
Result = hb_features:eflame(),
?assert(is_boolean(Result)).
eflame_consistent_test() ->
Result1 = hb_features:eflame(),
Result2 = hb_features:eflame(),
?assertEqual(Result1, Result2).
eflame_profiling_test() ->
case hb_features:eflame() of
true ->
% Profiling available
?assertEqual(can_profile, can_profile);
false ->
% Profiling unavailable
?assertEqual(no_profiling, no_profiling)
end.# Enable eflame
rebar3 compile -D ENABLE_EFLAMECommon Patterns
%% Check all features at startup
Features = hb_features:all(),
io:format("Available features: ~p~n", [Features]).
%% Conditional functionality
case hb_features:enabled(http3) of
true -> start_http3_listener();
false -> io:format("HTTP/3 not available~n")
end.
%% Select storage backend
Store = case hb_features:enabled(rocksdb) of
true -> hb_store_rocksdb;
false -> hb_store_ets
end.
%% Test vs production behavior
DataSource = case hb_features:test() of
true -> mock_data_source;
false -> real_data_source
end.
%% Feature-dependent initialization
init(Opts) ->
State = #{opts => Opts},
State2 = case hb_features:enabled(eflame) of
true -> State#{profiler => eflame};
false -> State
end,
{ok, State2}.
%% Multiple feature checks
validate_environment() ->
Required = [rocksdb, http3],
Enabled = [F || F <- Required, hb_features:enabled(F)],
case Enabled of
Required -> ok;
_ -> {error, {missing_features, Required -- Enabled}}
end.Build Configuration
Enabling Features
Via Compiler Flags:# Single feature
rebar3 compile -D ENABLE_HTTP3
# Multiple features
rebar3 compile -D ENABLE_HTTP3 -D ENABLE_ROCKSDB
# All optional features
rebar3 compile -D ENABLE_HTTP3 -D ENABLE_ROCKSDB -D ENABLE_GENESIS_WASM -D ENABLE_EFLAME{erl_opts, [
{d, 'ENABLE_HTTP3'},
{d, 'ENABLE_ROCKSDB'},
{d, 'ENABLE_EFLAME'}
]}.export ERL_COMPILER_OPTIONS="[{d,'ENABLE_HTTP3'},{d,'ENABLE_ROCKSDB'}]"
rebar3 compileFeature Discovery
The all/0 function automatically discovers features:
all() ->
% Get all exported functions
Exports = ?MODULE:module_info(exports),
% Filter out utility functions
Features = lists:filtermap(
fun({Name, _Arity}) ->
case lists:member(Name, [all, enabled, module_info]) of
true -> false; % Exclude utilities
false -> {true, Name} % Include feature
end
end,
Exports
),
% Call each feature function to get status
hb_maps:from_list([
{Name, ?MODULE:Name()} || Name <- Features
]).Feature Implementation Template
To add a new feature flag:
% 1. Export the function
-export([my_feature/0]).
% 2. Implement with ifdef
-ifdef(ENABLE_MY_FEATURE).
my_feature() -> true.
-else.
my_feature() -> false.
-endif.
% 3. Use in code
case hb_features:enabled(my_feature) of
true -> use_feature();
false -> use_fallback()
end.
% 4. Build with flag
% rebar3 compile -D ENABLE_MY_FEATUREUse Cases
1. Storage Backend Selection
select_store() ->
case hb_features:enabled(rocksdb) of
true ->
{hb_store_rocksdb, #{path => "/data/rocksdb"}};
false ->
{hb_store_ets, #{}}
end.2. Protocol Support
start_server(Port) ->
case hb_features:enabled(http3) of
true ->
http3_server:start_link([{port, Port}]);
false ->
http2_server:start_link([{port, Port}])
end.3. Development Tools
maybe_profile(Fun) ->
case hb_features:enabled(eflame) of
true ->
eflame:apply(Fun, []);
false ->
Fun()
end.4. Test Mode Behavior
get_data() ->
case hb_features:test() of
true -> mock_data();
false -> fetch_from_db()
end.References
- Erlang Preprocessor -
-ifdef,-definedirectives - rebar3 Configuration - Compiler options
- Module Introspection -
module_info/1 - HyperBEAM Build - Build system documentation
Notes
- Compile-Time Only: Features cannot be toggled at runtime
- Clean Rebuild: Changing flags requires
rebar3 clean compile - Test Flag: Automatically set by EUnit, don't set manually
- Feature Discovery:
all/0uses module introspection - Unknown Features:
enabled/1returnsfalsefor unknown features - No Dependencies: Feature checking has zero runtime overhead
- Boolean Only: All feature functions return
trueorfalse - Consistent API: All features follow same naming pattern
- Build System Bridge: Connects compile-time to runtime
- Zero Cost: Disabled features compiled out completely
- Filtered Exports: Utility functions excluded from feature list
- Map Keys: Feature names are atoms in result map
- Idempotent: Multiple calls return same result
- Thread-Safe: No state, all functions pure
- Extensible: Easy to add new features following template