From b28d1e9c6cdbd6453337f201c07cddee51d33c65 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 3 Sep 2024 11:09:48 -0400 Subject: [PATCH 01/15] minor: Remove outdated TODO --- src/seshat.erl | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 9a75192..5fa1f2d 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -161,8 +161,6 @@ register_counter(Group, Name, Ref, Fields) -> new_counter(Group, Name, Fields, FieldsSpec) -> Size = length(Fields), - %% TODO: validate that positions are correct, i.e. not out of range - %% or duplicated ExpectedPositions = lists:seq(1, Size), Positions = lists:sort([P || {_, P, _, _} <- Fields]), case ExpectedPositions == Positions of From 2660b1721f31d45aefcadc4baf467875e93370a5 Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 3 Sep 2024 11:35:59 -0400 Subject: [PATCH 02/15] Add `seshat:new/4` to attach extra info to a counter Currently the extra info is only returned by `seshat:format/1`: it's useful to attach information you might use for Prometheus labels. Previously we used the counter's name as passed to `seshat:new/3` in the place of this label. Attaching extra info is useful for QQs in RabbitMQ for example. They need to attach the queue name and vhost so that info can be exposed to Prometheus. --- src/seshat.erl | 48 ++++++++++++++++++++++++++++---------------- test/seshat_test.erl | 15 ++++++++++++++ 2 files changed, 46 insertions(+), 17 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 5fa1f2d..49fb1fb 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -10,6 +10,7 @@ -export([new_group/1, delete_group/1, new/3, + new/4, fetch/2, overview/1, overview/2, @@ -31,13 +32,16 @@ -type format_result() :: #{FieldName :: atom() => #{type => counter | gauge, help => string(), - values => #{name() => integer()}}}. + values => #{label() => integer()}}}. + +-type label() :: term(). -export_type([name/0, group/0, group_ref/0, field_spec/0, - fields_spec/0]). + fields_spec/0, + label/0]). -spec new_group(group()) -> group_ref(). new_group(Group) -> @@ -49,14 +53,23 @@ delete_group(Group) -> -spec new(group(), name(), fields_spec()) -> counters:counters_ref(). + new(Group, Name, Fields) when is_list(Fields) -> - new_counter(Group, Name, Fields, Fields); -new(Group, Name, {persistent_term, PTerm} = FieldsSpec) -> + new(Group, Name, Fields, Name); +new(Group, Name, {persistent_term, _PTerm} = FieldsSpec) -> + new(Group, Name, FieldsSpec, Name). + +-spec new(group(), name(), fields_spec(), label()) -> + counters:counters_ref(). + +new(Group, Name, Fields, Label) when is_list(Fields) -> + new_counter(Group, Name, Fields, Fields, Label); +new(Group, Name, {persistent_term, PTerm} = FieldsSpec, Label) -> case persistent_term:get(PTerm, undefined) of undefined -> error({non_existent_fields_spec, FieldsSpec}); Fields -> - new_counter(Group, Name, Fields, FieldsSpec) + new_counter(Group, Name, Fields, FieldsSpec, Label) end. %% fetch/2 is NOT meant to be called for every counter update. @@ -83,7 +96,7 @@ delete(Group, Name) -> #{name() => #{atom() => integer()}}. overview(Group) -> ets:foldl( - fun({Name, Ref, Fields0}, Acc) -> + fun({Name, Ref, Fields0, _Label}, Acc) -> Fields = resolve_fields(Fields0), Counters = lists:foldl( fun ({Key, Index, _Type, _Description}, Acc0) -> @@ -101,7 +114,7 @@ overview(Group, Name) -> #{atom() => integer()} | undefined. counters(Group, Name) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0}] -> + [{Name, Ref, Fields0, _Label}] -> Fields = resolve_fields(Fields0), lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> Acc0#{Key => counters:get(Ref, Index)} @@ -114,7 +127,7 @@ counters(Group, Name) -> #{atom() => integer()} | undefined. counters(Group, Name, FieldNames) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0}] -> + [{Name, Ref, Fields0, _Label}] -> Fields = resolve_fields(Fields0), lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> case lists:member(Key, FieldNames) of @@ -130,19 +143,20 @@ counters(Group, Name, FieldNames) -> -spec format(group()) -> format_result(). format(Group) -> - ets:foldl(fun({Labels, Ref, Fields0}, Acc) -> + ets:foldl(fun({_Name, Ref, Fields0, Label}, Acc) -> Fields = resolve_fields(Fields0), lists:foldl( - fun ({Name, Index, Type, Help}, Acc0) -> + fun ({MetricName, Index, Type, Help}, Acc0) -> InitialMetric = #{type => Type, help => Help, values => #{}}, - Metric = maps:get(Name, Acc0, InitialMetric), + Metric = maps:get(MetricName, Acc0, + InitialMetric), Values = maps:get(values, Metric), Counter = counters:get(Ref, Index), - Values1 = Values#{Labels => Counter}, + Values1 = Values#{Label => Counter}, Metric1 = Metric#{values => Values1}, - Acc0#{Name => Metric1} + Acc0#{MetricName => Metric1} end, Acc, Fields) end, #{}, seshat_counters_server:get_table(Group)). @@ -154,19 +168,19 @@ resolve_fields({persistent_term, PTerm}) -> %% TODO error handling persistent_term:get(PTerm). -register_counter(Group, Name, Ref, Fields) -> +register_counter(Group, Name, Ref, Fields, Label) -> TRef = seshat_counters_server:get_table(Group), - true = ets:insert(TRef, {Name, Ref, Fields}), + true = ets:insert(TRef, {Name, Ref, Fields, Label}), ok. -new_counter(Group, Name, Fields, FieldsSpec) -> +new_counter(Group, Name, Fields, FieldsSpec, Label) -> Size = length(Fields), ExpectedPositions = lists:seq(1, Size), Positions = lists:sort([P || {_, P, _, _} <- Fields]), case ExpectedPositions == Positions of true -> CRef = counters:new(Size, [write_concurrency]), - ok = register_counter(Group, Name, CRef, FieldsSpec), + ok = register_counter(Group, Name, CRef, FieldsSpec, Label), CRef; false -> error(invalid_field_specification) diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 46d0829..797780d 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -21,6 +21,7 @@ test_suite_test_() -> [ fun overview/0, fun counters_with_persistent_term_field_spec/0, fun prometheus_format_multiple_names/0, + fun prometheus_format_with_labels/0, fun invalid_fields/0 ]}. overview() -> @@ -102,6 +103,20 @@ prometheus_format_multiple_names() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. +prometheus_format_with_labels() -> + Group = people, + Counters = [{foo, 1, counter, "Total foos given"}], + seshat:new_group(Group), + seshat:new(Group, {name, you}, Counters, #{name => "Monet"}), + seshat:new(Group, {name, me}, Counters, #{name => "Manet"}), + PrometheusFormat = seshat:format(Group), + ExpectedPrometheusFormat = #{foo => #{type => counter, + help => "Total foos given", + values => #{#{name => "Monet"} => 0, + #{name => "Manet"} => 0}}}, + ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + ok. + invalid_fields() -> Group = people, Fields = [{foo, 1, counter, "Total foos given"}, From a44e20bc03d896c94fc9696d7aae91c4e94f3a4b Mon Sep 17 00:00:00 2001 From: Michael Davis Date: Tue, 3 Sep 2024 11:49:39 -0400 Subject: [PATCH 03/15] Add `seshat:format/2` to format only for a given name This will be useful in RabbitMQ to expose Khepri metrics for example: we want to select just Khepri's metrics out of the `ra` group. --- src/seshat.erl | 41 +++++++++++++++++++++++++++-------------- test/seshat_test.erl | 14 ++++++++++++++ 2 files changed, 41 insertions(+), 14 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 49fb1fb..673cf89 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -17,7 +17,8 @@ counters/2, counters/3, delete/2, - format/1 + format/1, + format/2 ]). -type group() :: term(). @@ -145,21 +146,20 @@ counters(Group, Name, FieldNames) -> format(Group) -> ets:foldl(fun({_Name, Ref, Fields0, Label}, Acc) -> Fields = resolve_fields(Fields0), - lists:foldl( - fun ({MetricName, Index, Type, Help}, Acc0) -> - InitialMetric = #{type => Type, - help => Help, - values => #{}}, - Metric = maps:get(MetricName, Acc0, - InitialMetric), - Values = maps:get(values, Metric), - Counter = counters:get(Ref, Index), - Values1 = Values#{Label => Counter}, - Metric1 = Metric#{values => Values1}, - Acc0#{MetricName => Metric1} - end, Acc, Fields) + format_fields(Fields, Ref, Label, Acc) end, #{}, seshat_counters_server:get_table(Group)). +-spec format(group(), name()) -> format_result(). + +format(Group, Name) -> + case ets:lookup(seshat_counters_server:get_table(Group), Name) of + [{Name, Ref, Fields0, Label}] -> + Fields = resolve_fields(Fields0), + format_fields(Fields, Ref, Label, #{}); + _ -> + #{} + end. + %% internal resolve_fields(Fields) when is_list(Fields) -> @@ -186,3 +186,16 @@ new_counter(Group, Name, Fields, FieldsSpec, Label) -> error(invalid_field_specification) end. +format_fields(Fields, Ref, Label, Acc) -> + lists:foldl( + fun ({MetricName, Index, Type, Help}, Acc0) -> + InitialMetric = #{type => Type, + help => Help, + values => #{}}, + Metric = maps:get(MetricName, Acc0, InitialMetric), + Values = maps:get(values, Metric), + Counter = counters:get(Ref, Index), + Values1 = Values#{Label => Counter}, + Metric1 = Metric#{values => Values1}, + Acc0#{MetricName => Metric1} + end, Acc, Fields). diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 797780d..9fe48a2 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -21,6 +21,7 @@ test_suite_test_() -> [ fun overview/0, fun counters_with_persistent_term_field_spec/0, fun prometheus_format_multiple_names/0, + fun prometheus_format_single_name/0, fun prometheus_format_with_labels/0, fun invalid_fields/0 ]}. @@ -103,6 +104,19 @@ prometheus_format_multiple_names() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. +prometheus_format_single_name() -> + Group = people, + Counters = [{foo, 1, counter, "Total foos given"}], + seshat:new_group(Group), + seshat:new(Group, {name, you}, Counters), + seshat:new(Group, {name, me}, Counters), + PrometheusFormat = seshat:format(Group, {name, me}), + ExpectedPrometheusFormat = #{foo => #{type => counter, + help => "Total foos given", + values => #{{name, me} => 0}}}, + ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + ok. + prometheus_format_with_labels() -> Group = people, Counters = [{foo, 1, counter, "Total foos given"}], From 8819ee85e008df88b22c2617c93e50bbbea150c6 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Mon, 3 Mar 2025 17:28:31 +0100 Subject: [PATCH 04/15] Make the label set an optional a map --- src/seshat.erl | 50 +++++++++++++++++++++++++++----------------------- 1 file changed, 27 insertions(+), 23 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 673cf89..ed5590e 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -33,16 +33,20 @@ -type format_result() :: #{FieldName :: atom() => #{type => counter | gauge, help => string(), - values => #{label() => integer()}}}. + values => #{labels() => number()}}}. + +-type label_name() :: atom(). +-type label_value() :: term(). + +-type labels() :: #{label_name() => label_value()}. --type label() :: term(). -export_type([name/0, group/0, group_ref/0, field_spec/0, fields_spec/0, - label/0]). + labels/0]). -spec new_group(group()) -> group_ref(). new_group(Group) -> @@ -56,21 +60,21 @@ delete_group(Group) -> counters:counters_ref(). new(Group, Name, Fields) when is_list(Fields) -> - new(Group, Name, Fields, Name); + new(Group, Name, Fields, #{}); new(Group, Name, {persistent_term, _PTerm} = FieldsSpec) -> - new(Group, Name, FieldsSpec, Name). + new(Group, Name, FieldsSpec, #{}). --spec new(group(), name(), fields_spec(), label()) -> +-spec new(group(), name(), fields_spec(), labels()) -> counters:counters_ref(). -new(Group, Name, Fields, Label) when is_list(Fields) -> - new_counter(Group, Name, Fields, Fields, Label); -new(Group, Name, {persistent_term, PTerm} = FieldsSpec, Label) -> +new(Group, Name, Fields, Labels) when is_list(Fields) -> + new_counter(Group, Name, Fields, Fields, Labels); +new(Group, Name, {persistent_term, PTerm} = FieldsSpec, Labels) -> case persistent_term:get(PTerm, undefined) of undefined -> error({non_existent_fields_spec, FieldsSpec}); Fields -> - new_counter(Group, Name, Fields, FieldsSpec, Label) + new_counter(Group, Name, Fields, FieldsSpec, Labels) end. %% fetch/2 is NOT meant to be called for every counter update. @@ -97,7 +101,7 @@ delete(Group, Name) -> #{name() => #{atom() => integer()}}. overview(Group) -> ets:foldl( - fun({Name, Ref, Fields0, _Label}, Acc) -> + fun({Name, Ref, Fields0, _Labels}, Acc) -> Fields = resolve_fields(Fields0), Counters = lists:foldl( fun ({Key, Index, _Type, _Description}, Acc0) -> @@ -115,7 +119,7 @@ overview(Group, Name) -> #{atom() => integer()} | undefined. counters(Group, Name) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, _Label}] -> + [{Name, Ref, Fields0, _Labels}] -> Fields = resolve_fields(Fields0), lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> Acc0#{Key => counters:get(Ref, Index)} @@ -128,7 +132,7 @@ counters(Group, Name) -> #{atom() => integer()} | undefined. counters(Group, Name, FieldNames) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, _Label}] -> + [{Name, Ref, Fields0, _Labels}] -> Fields = resolve_fields(Fields0), lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> case lists:member(Key, FieldNames) of @@ -144,18 +148,18 @@ counters(Group, Name, FieldNames) -> -spec format(group()) -> format_result(). format(Group) -> - ets:foldl(fun({_Name, Ref, Fields0, Label}, Acc) -> + ets:foldl(fun({_Name, Ref, Fields0, Labels}, Acc) -> Fields = resolve_fields(Fields0), - format_fields(Fields, Ref, Label, Acc) + format_fields(Fields, Ref, Labels, Acc) end, #{}, seshat_counters_server:get_table(Group)). -spec format(group(), name()) -> format_result(). format(Group, Name) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, Label}] -> + [{Name, Ref, Fields0, Labels}] -> Fields = resolve_fields(Fields0), - format_fields(Fields, Ref, Label, #{}); + format_fields(Fields, Ref, Labels, #{}); _ -> #{} end. @@ -168,25 +172,25 @@ resolve_fields({persistent_term, PTerm}) -> %% TODO error handling persistent_term:get(PTerm). -register_counter(Group, Name, Ref, Fields, Label) -> +register_counter(Group, Name, Ref, Fields, Labels) -> TRef = seshat_counters_server:get_table(Group), - true = ets:insert(TRef, {Name, Ref, Fields, Label}), + true = ets:insert(TRef, {Name, Ref, Fields, Labels}), ok. -new_counter(Group, Name, Fields, FieldsSpec, Label) -> +new_counter(Group, Name, Fields, FieldsSpec, Labels) -> Size = length(Fields), ExpectedPositions = lists:seq(1, Size), Positions = lists:sort([P || {_, P, _, _} <- Fields]), case ExpectedPositions == Positions of true -> CRef = counters:new(Size, [write_concurrency]), - ok = register_counter(Group, Name, CRef, FieldsSpec, Label), + ok = register_counter(Group, Name, CRef, FieldsSpec, Labels), CRef; false -> error(invalid_field_specification) end. -format_fields(Fields, Ref, Label, Acc) -> +format_fields(Fields, Ref, Labels, Acc) -> lists:foldl( fun ({MetricName, Index, Type, Help}, Acc0) -> InitialMetric = #{type => Type, @@ -195,7 +199,7 @@ format_fields(Fields, Ref, Label, Acc) -> Metric = maps:get(MetricName, Acc0, InitialMetric), Values = maps:get(values, Metric), Counter = counters:get(Ref, Index), - Values1 = Values#{Label => Counter}, + Values1 = Values#{Labels => Counter}, Metric1 = Metric#{values => Values1}, Acc0#{MetricName => Metric1} end, Acc, Fields). From 1cbb02c0987a3947885ebfcccc913a8d4db3df1e Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 4 Mar 2025 10:23:01 +0100 Subject: [PATCH 05/15] Only format metrics with labels A metrics without labels has a name which could be used as the label value, but it's not clear what label key should be in this case. For example, if a metric is declared like this: seshat:new(Group, ghost, [{foo, 1, counter, "Total foos given"}]), then what it'd look like in the Prometheus endpoint? ``` foo{...="ghost"} N ``` --- src/seshat.erl | 7 +++++-- test/seshat_test.erl | 24 +++++++++++++----------- 2 files changed, 18 insertions(+), 13 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index ed5590e..9bfd0a9 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -148,7 +148,10 @@ counters(Group, Name, FieldNames) -> -spec format(group()) -> format_result(). format(Group) -> - ets:foldl(fun({_Name, Ref, Fields0, Labels}, Acc) -> + ets:foldl(fun + ({_Name, _Ref, _Fields0, Labels}, Acc) when map_size(Labels) == 0 -> + Acc; + ({_Name, Ref, Fields0, Labels}, Acc) -> Fields = resolve_fields(Fields0), format_fields(Fields, Ref, Labels, Acc) end, #{}, seshat_counters_server:get_table(Group)). @@ -157,7 +160,7 @@ format(Group) -> format(Group, Name) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, Labels}] -> + [{Name, Ref, Fields0, Labels}] when map_size(Labels) > 0 -> Fields = resolve_fields(Fields0), format_fields(Fields, Ref, Labels, #{}); _ -> diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 9fe48a2..e5584bd 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -20,8 +20,8 @@ test_suite_test_() -> fun cleanup/1, [ fun overview/0, fun counters_with_persistent_term_field_spec/0, - fun prometheus_format_multiple_names/0, - fun prometheus_format_single_name/0, + fun prometheus_format_group/0, + fun prometheus_format_name_from_group/0, fun prometheus_format_with_labels/0, fun invalid_fields/0 ]}. @@ -90,30 +90,32 @@ counters_with_persistent_term_field_spec() -> ok. -prometheus_format_multiple_names() -> +prometheus_format_group() -> Group = people, Counters = [{foo, 1, counter, "Total foos given"}], seshat:new_group(Group), - seshat:new(Group, {name, you}, Counters), - seshat:new(Group, {name, me}, Counters), + seshat:new(Group, you, Counters, #{name => you}), + seshat:new(Group, me, Counters, #{name => me}), + seshat:new(Group, ghost, Counters), % no labels, will be omitted PrometheusFormat = seshat:format(Group), ExpectedPrometheusFormat = #{foo => #{type => counter, help => "Total foos given", - values => #{{name, me} => 0, - {name, you} => 0}}}, + values => #{#{name => me} => 0, + #{name => you} => 0}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_single_name() -> +prometheus_format_name_from_group() -> Group = people, Counters = [{foo, 1, counter, "Total foos given"}], seshat:new_group(Group), - seshat:new(Group, {name, you}, Counters), - seshat:new(Group, {name, me}, Counters), + seshat:new(Group, {name, you}, Counters, #{name => you}), + seshat:new(Group, {name, me}, Counters, #{name => me}), + seshat:new(Group, ghost, Counters), % no labels, will be omitted PrometheusFormat = seshat:format(Group, {name, me}), ExpectedPrometheusFormat = #{foo => #{type => counter, help => "Total foos given", - values => #{{name, me} => 0}}}, + values => #{#{name => me} => 0}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. From c0e478720d416ce38a13b124862f040dafd1e67d Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Mon, 3 Mar 2025 16:20:29 +0100 Subject: [PATCH 06/15] Add ratio support --- src/seshat.erl | 14 ++++++++++++-- test/seshat_test.erl | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 9bfd0a9..20d975c 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -26,7 +26,7 @@ -type name() :: term(). -type field_spec() :: {Name :: atom(), Position :: pos_integer(), - Type :: counter | gauge, Description :: string()}. + Type :: counter | gauge | ratio, Description :: string()}. -type fields_spec() :: [field_spec()] | {persistent_term, term()}. @@ -195,7 +195,17 @@ new_counter(Group, Name, Fields, FieldsSpec, Labels) -> format_fields(Fields, Ref, Labels, Acc) -> lists:foldl( - fun ({MetricName, Index, Type, Help}, Acc0) -> + fun ({MetricName, Index, ratio, Help}, Acc0) -> + InitialMetric = #{type => gauge, + help => Help, + values => #{}}, + Metric = maps:get(MetricName, Acc0, InitialMetric), + Values = maps:get(values, Metric), + Counter = counters:get(Ref, Index) / 100, + Values1 = Values#{Labels => Counter}, + Metric1 = Metric#{values => Values1}, + Acc0#{MetricName => Metric1}; + ({MetricName, Index, Type, Help}, Acc0) -> InitialMetric = #{type => Type, help => Help, values => #{}}, diff --git a/test/seshat_test.erl b/test/seshat_test.erl index e5584bd..2afdece 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -23,6 +23,7 @@ test_suite_test_() -> fun prometheus_format_group/0, fun prometheus_format_name_from_group/0, fun prometheus_format_with_labels/0, + fun prometheus_format_ratio/0, fun invalid_fields/0 ]}. overview() -> @@ -143,3 +144,38 @@ invalid_fields() -> ok. +prometheus_format_ratio() -> + Group = ratios, + Counters = [{zero, 1, ratio, "Some ratio that happens to be 0%"}, + {seventeen, 2, ratio, "Some ratio that happens to be 17%"}, + {third, 3, ratio, "Some ratio that happens to be 33%"}, + {all, 4, ratio, "Some ratio that happens to be 100%"}], + seshat:new_group(Group), + seshat:new(Group, {name, test}, Counters, #{name => test}), + Ref = seshat:fetch(Group, {name, test}), + counters:put(Ref, 1, 0), + counters:put(Ref, 2, 17), + counters:put(Ref, 3, 33), + counters:put(Ref, 4, 100), + + PrometheusFormat = seshat:format(Group), + ExpectedPrometheusFormat = #{zero => #{type => gauge, + help => "Some ratio that happens to be 0%", + values => #{#{name => test} => 0.0}}, + seventeen => #{type => gauge, + help => "Some ratio that happens to be 17%", + values => #{#{name => test} => 0.17}}, + third => #{type => gauge, + help => "Some ratio that happens to be 33%", + values => #{#{name => test} => 0.33}}, + all => #{type => gauge, + help => "Some ratio that happens to be 100%", + values => #{#{name => test} => 1.0}}}, + + maps:foreach( + fun (Name, Value) -> + ?assertEqual(Value, maps:get(Name, PrometheusFormat)) + end, + ExpectedPrometheusFormat), + ok. + From c5d5f8b6874cc05c31284dc201bf0c87f1ba2940 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 4 Mar 2025 14:44:20 +0100 Subject: [PATCH 07/15] Allow formatting only selected fields (metrics) --- src/seshat.erl | 21 ++++++++++++++++----- test/seshat_test.erl | 25 ++++++++++++++++++++++++- 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 20d975c..23ba976 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -18,7 +18,8 @@ counters/3, delete/2, format/1, - format/2 + format/2, + format_one/2 ]). -type group() :: term(). @@ -35,8 +36,8 @@ help => string(), values => #{labels() => number()}}}. --type label_name() :: atom(). --type label_value() :: term(). +-type label_name() :: atom() | unicode:chardata(). +-type label_value() :: atom() | unicode:chardata(). -type labels() :: #{label_name() => label_value()}. @@ -156,9 +157,19 @@ format(Group) -> format_fields(Fields, Ref, Labels, Acc) end, #{}, seshat_counters_server:get_table(Group)). --spec format(group(), name()) -> format_result(). +-spec format(group(), [atom()]) -> format_result(). +format(Group, FieldNames) -> + ets:foldl(fun + ({_Id, _Ref, _Fields0, Labels}, Acc) when map_size(Labels) == 0 -> + Acc; + ({_Id, Ref, Fields0, Labels}, Acc) -> + Fields1 = resolve_fields(Fields0), + Fields = lists:filter(fun (F) -> lists:member(element(1, F), FieldNames) end, Fields1), + format_fields(Fields, Ref, Labels, Acc) + end, #{}, seshat_counters_server:get_table(Group)). -format(Group, Name) -> +-spec format_one(group(), name()) -> format_result(). +format_one(Group, Name) -> case ets:lookup(seshat_counters_server:get_table(Group), Name) of [{Name, Ref, Fields0, Labels}] when map_size(Labels) > 0 -> Fields = resolve_fields(Fields0), diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 2afdece..cb05b35 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -24,6 +24,7 @@ test_suite_test_() -> fun prometheus_format_name_from_group/0, fun prometheus_format_with_labels/0, fun prometheus_format_ratio/0, + fun prometheus_foobar/0, fun invalid_fields/0 ]}. overview() -> @@ -113,7 +114,7 @@ prometheus_format_name_from_group() -> seshat:new(Group, {name, you}, Counters, #{name => you}), seshat:new(Group, {name, me}, Counters, #{name => me}), seshat:new(Group, ghost, Counters), % no labels, will be omitted - PrometheusFormat = seshat:format(Group, {name, me}), + PrometheusFormat = seshat:format_one(Group, {name, me}), ExpectedPrometheusFormat = #{foo => #{type => counter, help => "Total foos given", values => #{#{name => me} => 0}}}, @@ -134,6 +135,28 @@ prometheus_format_with_labels() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. +prometheus_foobar() -> + Group = things, + Counters = [ + {foo, 1, counter, "Total foos"}, + {bar, 2, counter, "Total bars"}, + {baz, 3, counter, "Total bazs"} + ], + seshat:new_group(Group), + seshat:new(Group, thing1, Counters, #{name => "thing1"}), + seshat:new(Group, thing2, Counters, #{name => "thing2"}), + PrometheusFormat = seshat:format(Group, [foo, bar]), + ExpectedPrometheusFormat = #{foo => #{type => counter, + help => "Total foos", + values => #{#{name => "thing1"} => 0, + #{name => "thing2"} => 0}}, + bar => #{type => counter, + help => "Total bars", + values => #{#{name => "thing1"} => 0, + #{name => "thing2"} => 0}}}, + ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + ok. + invalid_fields() -> Group = people, Fields = [{foo, 1, counter, "Total foos given"}, From 719e6e44295d49a114d3060cf458256adc829fb3 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 4 Mar 2025 17:49:18 +0100 Subject: [PATCH 08/15] Refactor tests Rename things to more resemble how Seshat is used in real life. --- test/seshat_test.erl | 166 ++++++++++++++++++++++--------------------- 1 file changed, 86 insertions(+), 80 deletions(-) diff --git a/test/seshat_test.erl b/test/seshat_test.erl index cb05b35..e476c19 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -21,10 +21,10 @@ test_suite_test_() -> [ fun overview/0, fun counters_with_persistent_term_field_spec/0, fun prometheus_format_group/0, - fun prometheus_format_name_from_group/0, - fun prometheus_format_with_labels/0, + fun prometheus_format_one/0, + fun prometheus_format_with_many_labels/0, fun prometheus_format_ratio/0, - fun prometheus_foobar/0, + fun prometheus_format_selected_metrics/0, fun invalid_fields/0 ]}. overview() -> @@ -41,9 +41,8 @@ overview() -> ], seshat:new_group(Group), seshat:new(Group, "rabbit", Counters), - Ref = seshat:fetch(Group, "rabbit"), - counters:add(Ref, 1, 3), - counters:add(Ref, 2, 1), + set_value(Group, "rabbit", carrots_eaten_total, 3), + set_value(Group, "rabbit", holes_dug_total, 1), Overview = seshat:overview(Group), ?assertEqual( #{"rabbit" => #{carrots_eaten_total => 3, @@ -74,9 +73,8 @@ counters_with_persistent_term_field_spec() -> persistent_term:put(pets_field_spec, Counters), seshat:new_group(Group), seshat:new(Group, "rabbit", {persistent_term, pets_field_spec}), - Ref = seshat:fetch(Group, "rabbit"), - counters:add(Ref, 1, 3), - counters:add(Ref, 2, 1), + set_value(Group, "rabbit", carrots_eaten_total, 3), + set_value(Group, "rabbit", holes_dug_total, 1), Overview = seshat:overview(Group), ?assertEqual( #{"rabbit" => #{carrots_eaten_total => 3, @@ -93,74 +91,75 @@ counters_with_persistent_term_field_spec() -> ok. prometheus_format_group() -> - Group = people, - Counters = [{foo, 1, counter, "Total foos given"}], + Group = widgets, + Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), - seshat:new(Group, you, Counters, #{name => you}), - seshat:new(Group, me, Counters, #{name => me}), - seshat:new(Group, ghost, Counters), % no labels, will be omitted + seshat:new(Group, widget1, Counters, #{component => widget1}), + seshat:new(Group, widget2, Counters, #{component => widget2}), + seshat:new(Group, screw, Counters), % no labels, will be omitted PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{foo => #{type => counter, - help => "Total foos given", - values => #{#{name => me} => 0, - #{name => you} => 0}}}, + ExpectedPrometheusFormat = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => widget1} => 0, + #{component => widget2} => 0}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_name_from_group() -> - Group = people, - Counters = [{foo, 1, counter, "Total foos given"}], +prometheus_format_one() -> + Group = widgets, + Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), - seshat:new(Group, {name, you}, Counters, #{name => you}), - seshat:new(Group, {name, me}, Counters, #{name => me}), - seshat:new(Group, ghost, Counters), % no labels, will be omitted - PrometheusFormat = seshat:format_one(Group, {name, me}), - ExpectedPrometheusFormat = #{foo => #{type => counter, - help => "Total foos given", - values => #{#{name => me} => 0}}}, + seshat:new(Group, widget1, Counters, #{component => widget1}), + seshat:new(Group, widget2, Counters, #{component => widget2}), + PrometheusFormat = seshat:format_one(Group, widget2), + ExpectedPrometheusFormat = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => widget2} => 0}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_with_labels() -> - Group = people, - Counters = [{foo, 1, counter, "Total foos given"}], +prometheus_format_with_many_labels() -> + Group = widgets, + Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), - seshat:new(Group, {name, you}, Counters, #{name => "Monet"}), - seshat:new(Group, {name, me}, Counters, #{name => "Manet"}), + seshat:new(Group, widget1, Counters, #{component => "widget1", status => up}), + seshat:new(Group, widget2, Counters, #{component => "widget2", status => down}), + set_value(Group, widget1, reads, 1), + set_value(Group, widget2, reads, 2), PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{foo => #{type => counter, - help => "Total foos given", - values => #{#{name => "Monet"} => 0, - #{name => "Manet"} => 0}}}, + ExpectedPrometheusFormat = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => "widget1", status => up} => 1, + #{component => "widget2", status => down} => 2}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_foobar() -> - Group = things, +prometheus_format_selected_metrics() -> + Group = widgets, Counters = [ - {foo, 1, counter, "Total foos"}, - {bar, 2, counter, "Total bars"}, - {baz, 3, counter, "Total bazs"} + {reads, 1, counter, "Total reads"}, + {writes, 2, counter, "Total writes"}, + {lookups, 3, counter, "Total lookups"} ], seshat:new_group(Group), - seshat:new(Group, thing1, Counters, #{name => "thing1"}), - seshat:new(Group, thing2, Counters, #{name => "thing2"}), - PrometheusFormat = seshat:format(Group, [foo, bar]), - ExpectedPrometheusFormat = #{foo => #{type => counter, - help => "Total foos", - values => #{#{name => "thing1"} => 0, - #{name => "thing2"} => 0}}, - bar => #{type => counter, - help => "Total bars", - values => #{#{name => "thing1"} => 0, - #{name => "thing2"} => 0}}}, + seshat:new(Group, thing1, Counters, #{component => "thing1"}), + seshat:new(Group, thing2, Counters, #{component => "thing2"}), + PrometheusFormat = seshat:format(Group, [reads, writes]), + ExpectedPrometheusFormat = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => "thing1"} => 0, + #{component => "thing2"} => 0}}, + writes => #{type => counter, + help => "Total writes", + values => #{#{component => "thing1"} => 0, + #{component => "thing2"} => 0}}}, ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. invalid_fields() -> - Group = people, - Fields = [{foo, 1, counter, "Total foos given"}, - {boo, 3, counter, "Total boos given"}], + Group = widgets, + Fields = [{reads, 1, counter, "Total reads"}, + {writes, 3, counter, "Total writes"}], seshat:new_group(Group), ?assertError(invalid_field_specification, seshat:new(Group, invalid_fields, Fields)), @@ -168,37 +167,44 @@ invalid_fields() -> ok. prometheus_format_ratio() -> - Group = ratios, - Counters = [{zero, 1, ratio, "Some ratio that happens to be 0%"}, - {seventeen, 2, ratio, "Some ratio that happens to be 17%"}, - {third, 3, ratio, "Some ratio that happens to be 33%"}, - {all, 4, ratio, "Some ratio that happens to be 100%"}], + Group = widgets, + Counters = [{pings, 1, ratio, "Some ratio that happens to be 0%"}, + {pongs, 2, ratio, "Some ratio that happens to be 17%"}, + {pangs, 3, ratio, "Some ratio that happens to be 33%"}, + {rings, 4, ratio, "Some ratio that happens to be 100%"}], seshat:new_group(Group), - seshat:new(Group, {name, test}, Counters, #{name => test}), - Ref = seshat:fetch(Group, {name, test}), - counters:put(Ref, 1, 0), - counters:put(Ref, 2, 17), - counters:put(Ref, 3, 33), - counters:put(Ref, 4, 100), + seshat:new(Group, test_component, Counters, #{component => test}), + set_value(Group, test_component, pings, 0), + set_value(Group, test_component, pongs, 17), + set_value(Group, test_component, pangs, 33), + set_value(Group, test_component, rings, 100), PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{zero => #{type => gauge, + ExpectedPrometheusFormat = #{pings => #{type => gauge, help => "Some ratio that happens to be 0%", - values => #{#{name => test} => 0.0}}, - seventeen => #{type => gauge, + values => #{#{component => test} => 0.0}}, + pongs => #{type => gauge, help => "Some ratio that happens to be 17%", - values => #{#{name => test} => 0.17}}, - third => #{type => gauge, + values => #{#{component => test} => 0.17}}, + pangs => #{type => gauge, help => "Some ratio that happens to be 33%", - values => #{#{name => test} => 0.33}}, - all => #{type => gauge, + values => #{#{component => test} => 0.33}}, + rings => #{type => gauge, help => "Some ratio that happens to be 100%", - values => #{#{name => test} => 1.0}}}, + values => #{#{component => test} => 1.0}}}, - maps:foreach( - fun (Name, Value) -> - ?assertEqual(Value, maps:get(Name, PrometheusFormat)) - end, - ExpectedPrometheusFormat), + ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. +%% helpers + +set_value(Group, Id, Metric, Value) -> + [{Id, Ref, MetricDefs0, _Labels}] = ets:lookup(seshat_counters_server:get_table(Group), Id), + MetricDefs = resolve_fields(MetricDefs0), + {Metric, MetricId, _Type, _Help} = lists:keyfind(Metric, 1, MetricDefs), + counters:put(Ref, MetricId, Value). + +resolve_fields(Fields) when is_list(Fields) -> + Fields; +resolve_fields({persistent_term, PTerm}) -> + persistent_term:get(PTerm). From 755ef3f18db1ce9343f005e0374fcfc8beaefe90 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Thu, 6 Mar 2025 11:17:33 +0100 Subject: [PATCH 09/15] Refactor and add edocs Refactor to make variable names consistent: * Name sometimes referred to the metric name, and sometimes to the object emiting the metrics; going forward, it's either Id (object) or Name (metric) * counter index was sometimes referred to as Position (now, always Index) * metric help was sometiems referred to as Description (now, always Help) * Fields/FieldSpec/FieldName were used inconsistently (because Fields and FieldSpec can be the same thing); hopefully it's more clear now * counter referece was sometimes Ref and sometimes CRef (now, always CRef) --- src/seshat.erl | 259 ++++++++++++++++++++++++++++--------------- test/seshat_test.erl | 16 +-- 2 files changed, 180 insertions(+), 95 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 23ba976..05cd941 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -26,12 +26,12 @@ -opaque group_ref() :: ets:tid(). -type name() :: term(). --type field_spec() :: {Name :: atom(), Position :: pos_integer(), - Type :: counter | gauge | ratio, Description :: string()}. +-type field_spec() :: {Name :: atom(), Index :: pos_integer(), + Type :: counter | gauge | ratio, Help :: string()}. -type fields_spec() :: [field_spec()] | {persistent_term, term()}. --type format_result() :: #{FieldName :: atom() => +-type format_result() :: #{Name :: atom() => #{type => counter | gauge, help => string(), values => #{labels() => number()}}}. @@ -41,7 +41,6 @@ -type labels() :: #{label_name() => label_value()}. - -export_type([name/0, group/0, group_ref/0, @@ -49,96 +48,151 @@ fields_spec/0, labels/0]). +%% @doc Create a new empty group of metrics. +%% Each group is completely isolated. +%% +%% @param Group the name of the group +%% @returns a reference to the new group -spec new_group(group()) -> group_ref(). new_group(Group) -> seshat_counters_server:create_table(Group). +%% @doc Delete an existing group. +%% +%% @param Group the name of the group +%% @returns 'ok' -spec delete_group(group()) -> ok. delete_group(Group) -> seshat_counters_server:delete_table(Group). +%% @doc Create a new set of metrics. +%% A set of metrics is stored in a counter (@see counters) +%% +%% @param Group the name of an existing group +%% @param Id the id of an object these metrics are assosiated with +%% @param FieldSpec metadata for the values stored in the counter +%% @returns a reference to the counter -spec new(group(), name(), fields_spec()) -> counters:counters_ref(). +new(Group, Id, FieldSpec) -> + new(Group, Id, FieldSpec, #{}). -new(Group, Name, Fields) when is_list(Fields) -> - new(Group, Name, Fields, #{}); -new(Group, Name, {persistent_term, _PTerm} = FieldsSpec) -> - new(Group, Name, FieldsSpec, #{}). - +%% @doc Create a new set of metrics. +%% A set of metrics is stored in a counter (@see counters) +%% +%% @param Group the name of an existing group +%% @param Id the id of an object these metrics are assosiated with +%% @param FieldSpec metadata for the values stored in the counter +%% @param Labels key-value pairs describing the object +%% @returns a reference to the counter -spec new(group(), name(), fields_spec(), labels()) -> counters:counters_ref(). - -new(Group, Name, Fields, Labels) when is_list(Fields) -> - new_counter(Group, Name, Fields, Fields, Labels); -new(Group, Name, {persistent_term, PTerm} = FieldsSpec, Labels) -> +new(Group, Id, Fields = FieldSpec, Labels) when is_list(Fields) -> + new_counter(Group, Id, Fields, FieldSpec, Labels); +new(Group, Id, {persistent_term, PTerm} = FieldsSpec, Labels) -> case persistent_term:get(PTerm, undefined) of undefined -> error({non_existent_fields_spec, FieldsSpec}); Fields -> - new_counter(Group, Name, Fields, FieldsSpec, Labels) + new_counter(Group, Id, Fields, FieldsSpec, Labels) end. +%% @doc Return a reference to an existing set of metrics. %% fetch/2 is NOT meant to be called for every counter update. -%% Instead, for higher performance, the consuming application should store the returned counters_ref -%% in a stateful Erlang module or in persistent_term (see persistent:term_put/2). +%% Instead, for higher performance, the consuming application should +%% store the returned counters_ref in a stateful Erlang module or in +%% persistent_term (@see persistent:term_put/2). +%% +%% @param Group the name of an existing group +%% @param Id the id of an existing object +%% @returns a reference to the counter -spec fetch(group(), name()) -> undefined | counters:counters_ref(). -fetch(Group, Name) -> +fetch(Group, Id) -> TRef = seshat_counters_server:get_table(Group), try - ets:lookup_element(TRef, Name, 2) + ets:lookup_element(TRef, Id, 2) catch error:badarg -> undefined end. +%% @doc Delete a metric set +%% +%% @param Group the name of an existing group +%% @param Id the id of an existing object +%% @returns 'ok' -spec delete(group(), name()) -> ok. -delete(Group, Name) -> +delete(Group, Id) -> TRef = seshat_counters_server:get_table(Group), - true = ets:delete(TRef, Name), + true = ets:delete(TRef, Id), ok. -%%% Use counters/2 +%% @doc Return a map with all metrics of all objects in the group +%% The returned map has the following structure: +%% #{Id => #{Name => Value}} +%% +%% @param Group the name of an existing group -spec overview(group()) -> #{name() => #{atom() => integer()}}. overview(Group) -> ets:foldl( - fun({Name, Ref, Fields0, _Labels}, Acc) -> - Fields = resolve_fields(Fields0), + fun({Id, CRef, FieldSpec, _Labels}, Acc) -> + Fields = resolve_fields_spec(FieldSpec), Counters = lists:foldl( - fun ({Key, Index, _Type, _Description}, Acc0) -> - Acc0#{Key => counters:get(Ref, Index)} + fun ({Name, Index, _Type, _Help}, Acc0) -> + Acc0#{Name => counters:get(CRef, Index)} end, #{}, Fields), - Acc#{Name => Counters} + Acc#{Id => Counters} end, #{}, seshat_counters_server:get_table(Group)). +%% @doc Return a map with all metrics for the object +%% The returned map has the following structure: +%% #{Name => Value} +%% +%% @param Group the name of an existing group +%% @param Id the name of an existing object +%% @deprecated Use counters/2 instead -spec overview(group(), name()) -> #{atom() => integer()} | undefined. -overview(Group, Name) -> - counters(Group, Name). +overview(Group, Id) -> + counters(Group, Id). +%% @doc Return a map with all metrics for the object +%% The returned map has the following structure: +%% #{Name => Value} +%% +%% @param Group the name of an existing group +%% @param Id the name of an existing object -spec counters(group(), name()) -> #{atom() => integer()} | undefined. -counters(Group, Name) -> - case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, _Labels}] -> - Fields = resolve_fields(Fields0), - lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> - Acc0#{Key => counters:get(Ref, Index)} +counters(Group, Id) -> + case ets:lookup(seshat_counters_server:get_table(Group), Id) of + [{Id, CRef, FieldSpec, _Labels}] -> + Fields = resolve_fields_spec(FieldSpec), + lists:foldl(fun ({Key, Index, _Type, _Help}, Acc0) -> + Acc0#{Key => counters:get(CRef, Index)} end, #{}, Fields); _ -> undefined end. +%% @doc Return a map with selected metrics for the object +%% The returned map has the following structure: +%% #{Name => Value} +%% +%% @param Group the name of an existing group +%% @param Id the name of an existing object +%% @param Names the list of metrics to return -spec counters(group(), name(), [atom()]) -> #{atom() => integer()} | undefined. -counters(Group, Name, FieldNames) -> - case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, _Labels}] -> - Fields = resolve_fields(Fields0), - lists:foldl(fun ({Key, Index, _Type, _Description}, Acc0) -> - case lists:member(Key, FieldNames) of +counters(Group, Id, Names) -> + case ets:lookup(seshat_counters_server:get_table(Group), Id) of + [{Id, CRef, FieldSpec, _Labels}] -> + Fields = resolve_fields_spec(FieldSpec), + lists:foldl(fun ({Name, Index, _Type, _Help}, Acc0) -> + case lists:member(Name, Names) of true -> - Acc0#{Key => counters:get(Ref, Index)}; + Acc0#{Name => counters:get(CRef, Index)}; false -> Acc0 end @@ -147,83 +201,114 @@ counters(Group, Name, FieldNames) -> undefined end. +%% @doc Return a map with all metrics for all objects in the group +%% The returned map has the following structure: +%% #{Name => #{Labels => Value}} +%% This structure is similar to what Prometheus expects, +%% with label sets associated with metric values. +%% +%% @param Group the name of an existing group -spec format(group()) -> format_result(). format(Group) -> ets:foldl(fun - ({_Name, _Ref, _Fields0, Labels}, Acc) when map_size(Labels) == 0 -> + ({_Name, _CRef, _FieldSpec, Labels}, Acc) when map_size(Labels) == 0 -> Acc; - ({_Name, Ref, Fields0, Labels}, Acc) -> - Fields = resolve_fields(Fields0), - format_fields(Fields, Ref, Labels, Acc) + ({_Name, CRef, FieldsSpec, Labels}, Acc) -> + Fields = resolve_fields_spec(FieldsSpec), + format_fields(Fields, CRef, Labels, Acc) end, #{}, seshat_counters_server:get_table(Group)). +%% @doc Return a map with selected metrics for all objects +%% The returned map has the following structure: +%% #{Name => #{Labels => Value}} +%% This structure is similar to what Prometheus expects, +%% with label sets associated with metric values. +%% +%% @param Group the name of an existing group +%% @param Names the list of metrics to return -spec format(group(), [atom()]) -> format_result(). -format(Group, FieldNames) -> +format(Group, Names) when is_list(Names) -> ets:foldl(fun - ({_Id, _Ref, _Fields0, Labels}, Acc) when map_size(Labels) == 0 -> + ({_Name, _CRef, _FieldSpec, Labels}, Acc) when map_size(Labels) == 0 -> Acc; - ({_Id, Ref, Fields0, Labels}, Acc) -> - Fields1 = resolve_fields(Fields0), - Fields = lists:filter(fun (F) -> lists:member(element(1, F), FieldNames) end, Fields1), - format_fields(Fields, Ref, Labels, Acc) + ({_Name, CRef, FieldSpec, Labels}, Acc) -> + Fields0 = resolve_fields_spec(FieldSpec), + Fields = lists:filter(fun (F) -> lists:member(element(1, F), Names) end, Fields0), + format_fields(Fields, CRef, Labels, Acc) end, #{}, seshat_counters_server:get_table(Group)). +%% @doc Return a map with all metrics for the selected object +%% The returned map has the following structure: +%% #{Name => #{Labels => Value}} +%% This structure is similar to what Prometheus expects, +%% with label sets associated with metric values. +%% +%% @param Group the name of an existing group +%% @param Names the list of metrics to return -spec format_one(group(), name()) -> format_result(). -format_one(Group, Name) -> - case ets:lookup(seshat_counters_server:get_table(Group), Name) of - [{Name, Ref, Fields0, Labels}] when map_size(Labels) > 0 -> - Fields = resolve_fields(Fields0), - format_fields(Fields, Ref, Labels, #{}); +format_one(Group, Id) -> + case ets:lookup(seshat_counters_server:get_table(Group), Id) of + [{Id, CRef, FieldSpec, Labels}] when map_size(Labels) > 0 -> + Fields = resolve_fields_spec(FieldSpec), + format_fields(Fields, CRef, Labels, #{}); _ -> #{} end. %% internal -resolve_fields(Fields) when is_list(Fields) -> +%% @doc Return the metadata for the fields +%% When creating a set of metrics with seshat:new/3 or seshat:new/4, +%% metadata about the metrics/counters has to be provided either +%% as a list or as a reference to a persistent term with the list. +%% The latter is recommended when a lot of similar metrics need to be stored +%% (eg. many instances of the same component emit the same set of metrics) +-spec resolve_fields_spec(fields_spec()) -> [field_spec()]. +resolve_fields_spec(Fields = FieldSpec) when is_list(FieldSpec) -> Fields; -resolve_fields({persistent_term, PTerm}) -> +resolve_fields_spec({persistent_term, PTerm}) -> %% TODO error handling persistent_term:get(PTerm). -register_counter(Group, Name, Ref, Fields, Labels) -> +-spec register_counter(group(), name(), counters:counters_ref(), fields_spec(), labels()) -> + ok. +register_counter(Group, Id, CRef, FieldSpec, Labels) -> TRef = seshat_counters_server:get_table(Group), - true = ets:insert(TRef, {Name, Ref, Fields, Labels}), + true = ets:insert(TRef, {Id, CRef, FieldSpec, Labels}), ok. -new_counter(Group, Name, Fields, FieldsSpec, Labels) -> +-spec new_counter(group(), name(), [field_spec()], fields_spec(), labels()) -> + counters:counters_ref(). +new_counter(Group, Id, Fields, FieldsSpec, Labels) -> Size = length(Fields), - ExpectedPositions = lists:seq(1, Size), - Positions = lists:sort([P || {_, P, _, _} <- Fields]), - case ExpectedPositions == Positions of + ExpectedIndexes = lists:seq(1, Size), + Indexes = lists:sort([P || {_, P, _, _} <- Fields]), + case ExpectedIndexes == Indexes of true -> CRef = counters:new(Size, [write_concurrency]), - ok = register_counter(Group, Name, CRef, FieldsSpec, Labels), + ok = register_counter(Group, Id, CRef, FieldsSpec, Labels), CRef; false -> error(invalid_field_specification) end. -format_fields(Fields, Ref, Labels, Acc) -> +format_fields(Fields, CRef, Labels, Acc) -> lists:foldl( - fun ({MetricName, Index, ratio, Help}, Acc0) -> - InitialMetric = #{type => gauge, - help => Help, - values => #{}}, - Metric = maps:get(MetricName, Acc0, InitialMetric), - Values = maps:get(values, Metric), - Counter = counters:get(Ref, Index) / 100, - Values1 = Values#{Labels => Counter}, - Metric1 = Metric#{values => Values1}, - Acc0#{MetricName => Metric1}; - ({MetricName, Index, Type, Help}, Acc0) -> - InitialMetric = #{type => Type, - help => Help, - values => #{}}, - Metric = maps:get(MetricName, Acc0, InitialMetric), - Values = maps:get(values, Metric), - Counter = counters:get(Ref, Index), - Values1 = Values#{Labels => Counter}, - Metric1 = Metric#{values => Values1}, - Acc0#{MetricName => Metric1} - end, Acc, Fields). + fun ({Name, Index, Type, Help}, Acc0) -> + ComputedType = case Type of + ratio -> gauge; + Other -> Other + end, + InitialMetric = #{type => ComputedType, + help => Help, + values => #{}}, + MetricAcc = maps:get(Name, Acc0, InitialMetric), + ValuesAcc = maps:get(values, MetricAcc), + ComputedValue = case Type of + ratio -> counters:get(CRef, Index) / 100; + _ -> counters:get(CRef, Index) + end, + ValuesAcc1 = ValuesAcc#{Labels => ComputedValue}, + MetricAcc1 = MetricAcc#{values => ValuesAcc1}, + Acc0#{Name => MetricAcc1} + end, Acc, Fields). diff --git a/test/seshat_test.erl b/test/seshat_test.erl index e476c19..5c8fcd4 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -196,15 +196,15 @@ prometheus_format_ratio() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -%% helpers +%% test helpers -set_value(Group, Id, Metric, Value) -> - [{Id, Ref, MetricDefs0, _Labels}] = ets:lookup(seshat_counters_server:get_table(Group), Id), - MetricDefs = resolve_fields(MetricDefs0), - {Metric, MetricId, _Type, _Help} = lists:keyfind(Metric, 1, MetricDefs), - counters:put(Ref, MetricId, Value). +set_value(Group, Id, Name, Value) -> + [{Id, CRef, FieldSpec, _Labels}] = ets:lookup(seshat_counters_server:get_table(Group), Id), + Fields = resolve_fieldspec(FieldSpec), + {Name, Index, _Type, _Help} = lists:keyfind(Name, 1, Fields), + counters:put(CRef, Index, Value). -resolve_fields(Fields) when is_list(Fields) -> +resolve_fieldspec(Fields = FieldSpec) when is_list(FieldSpec) -> Fields; -resolve_fields({persistent_term, PTerm}) -> +resolve_fieldspec({persistent_term, PTerm}) -> persistent_term:get(PTerm). From 5e5fee24a2a1ad8e7162ef39fe988e0cb2f0aa66 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Fri, 7 Mar 2025 13:31:38 +0100 Subject: [PATCH 10/15] Add support to generate a Promethus exposition format output seshat:text_format/3 produces a binary that contains the text representation of the metrics in the Prometheus exposition format, so that it can be returned as-is by a Prometheus endpoint --- rebar3.crashdump | 23 +++++++++++++ src/seshat.erl | 82 ++++++++++++++++++++++++++++++++++++++++++++ test/seshat_test.erl | 61 ++++++++++++++++++++++++++------ 3 files changed, 156 insertions(+), 10 deletions(-) create mode 100644 rebar3.crashdump diff --git a/rebar3.crashdump b/rebar3.crashdump new file mode 100644 index 0000000..453e319 --- /dev/null +++ b/rebar3.crashdump @@ -0,0 +1,23 @@ +Error: terminated +[{io,format, + ["rebar ~ts on Erlang/OTP ~ts Erts ~ts~n", + ["3.24.0+build.5437.ref5495da14","27","15.2.3"]], + [{file,"io.erl"}, + {line,198}, + {error_info,#{cause => {io,terminated},module => erl_stdlib_errors}}]}, + {rebar_prv_version,do,1, + [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar_prv_version.erl"}, + {line,36}]}, + {rebar_core,do,2, + [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar_core.erl"}, + {line,155}]}, + {rebar3,run_aux,2, + [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar3.erl"}, + {line,212}]}, + {rebar3,main,1, + [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar3.erl"}, + {line,66}]}, + {escript,run,2,[{file,"escript.erl"},{line,904}]}, + {escript,start,1,[{file,"escript.erl"},{line,418}]}, + {init,start_it,1,[]}] + diff --git a/src/seshat.erl b/src/seshat.erl index 05cd941..90cd90c 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -19,6 +19,7 @@ delete/2, format/1, format/2, + text_format/3, format_one/2 ]). @@ -312,3 +313,84 @@ format_fields(Fields, CRef, Labels, Acc) -> MetricAcc1 = MetricAcc#{values => ValuesAcc1}, Acc0#{Name => MetricAcc1} end, Acc, Fields). + +%% @doc Return a Prometheus-formated text (as a binary), +%% which can be directly returned by a Prometheus endpoint. +%% The returned binary has the following structure: +%% prefix_name_unit{label_key="value_value",...} Value +%% +%% Units are automatically appended based on the metric type. +%% +%% @param Group the name of an existing group +%% @param Names the list of metrics to return +%% +-spec text_format(group(), string(), [atom()]) -> binary(). +text_format(Group, Prefix, Names) when is_list(Names) -> + PrefixBin = list_to_binary(Prefix ++ "_"), + Metrics = ets:foldl(fun + ({_Name, _CRef, _FieldSpec, Labels}, Acc) when map_size(Labels) == 0 -> + %% skip metrics with no labels; at some point we might want to export them + Acc; + ({_Name, CRef, FieldSpec, Labels}, Acc) -> + Fields0 = resolve_fields_spec(FieldSpec), + Fields = lists:filter(fun (F) -> lists:member(element(1, F), Names) end, Fields0), + text_format_fields(Fields, CRef, Labels, PrefixBin, Acc) + end, #{}, seshat_counters_server:get_table(Group)), + MetricsBin = maps:fold(fun(_Name, Lines, Acc) -> + <>/binary>> + end, <<>>, Metrics), + MetricsBin. + +text_format_fields(Fields, CRef, Labels, PrefixBin, Acc) -> + LabelsList = maps:to_list(Labels), + % transform the map of labels into "label1=value1,label2=value2" + LabelsBin0 = lists:foldl( + fun ({Name, Value}, LabelsAcc) -> + LabelKey = atom_to_binary(Name, utf8), + LabelValue = label_value_to_binary(Value), + <> + end, <<>>, LabelsList), + % remove the final comma + LabelsBin = case LabelsBin0 of + <<>> -> <<>>; + _ -> binary:part(LabelsBin0, 0, byte_size(LabelsBin0) - 1) + end, + % produce the lines for each metric; we are itearting by object, but Prometheus + % output should be sorted by metric name with one HELP and one TYPE line for + % a given metric, so we accumulate in a map, with the metric name as key + lists:foldl( + fun ({Name, Index, Type, Help}, Acc0) -> + ComputedType = case Type of + ratio -> gauge; + Other -> Other + end, + ComputedUnit = case Type of + ratio -> <<"_ratio">>; + _ -> <<"">> + end, + ComputedValue = case Type of + ratio -> Ratio = counters:get(CRef, Index) / 100, + Ratio1= float_to_list(Ratio, [{decimals, 2}, compact]), + list_to_binary(Ratio1) + ; + _ -> integer_to_binary(counters:get(CRef, Index)) + end, + NameBin = <>, + Line = <>, + case maps:get(Name, Acc0, <<>>) of + <<>> -> + HelpLine = <<"# HELP ", NameBin/binary, <<" ">>/binary, (list_to_binary(Help))/binary>>, + TypeBin = atom_to_binary(ComputedType, utf8), + TypeLine = <<"# TYPE ", NameBin/binary, <<" ">>/binary, TypeBin/binary>>, + Acc0#{Name => <>/binary, TypeLine/binary, <<"\n">>/binary, Line/binary>>}; + Lines -> + Acc0#{Name => <>/binary, Line/binary>>} + end + end, Acc, Fields). + +label_value_to_binary(Value) when is_atom(Value) -> + atom_to_binary(Value, utf8); +label_value_to_binary(Value) when is_list(Value) -> + list_to_binary(Value); +label_value_to_binary(Value) when is_binary(Value) -> + Value. diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 5c8fcd4..96a2d16 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -20,11 +20,12 @@ test_suite_test_() -> fun cleanup/1, [ fun overview/0, fun counters_with_persistent_term_field_spec/0, - fun prometheus_format_group/0, - fun prometheus_format_one/0, - fun prometheus_format_with_many_labels/0, - fun prometheus_format_ratio/0, - fun prometheus_format_selected_metrics/0, + fun format_group/0, + fun format_one/0, + fun format_with_many_labels/0, + fun format_ratio/0, + fun format_selected_metrics/0, + fun text_format_selected_metrics/0, fun invalid_fields/0 ]}. overview() -> @@ -90,7 +91,7 @@ counters_with_persistent_term_field_spec() -> ok. -prometheus_format_group() -> +format_group() -> Group = widgets, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), @@ -105,7 +106,7 @@ prometheus_format_group() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_one() -> +format_one() -> Group = widgets, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), @@ -118,7 +119,7 @@ prometheus_format_one() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_with_many_labels() -> +format_with_many_labels() -> Group = widgets, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), @@ -134,7 +135,7 @@ prometheus_format_with_many_labels() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. -prometheus_format_selected_metrics() -> +format_selected_metrics() -> Group = widgets, Counters = [ {reads, 1, counter, "Total reads"}, @@ -166,7 +167,7 @@ invalid_fields() -> ok. -prometheus_format_ratio() -> +format_ratio() -> Group = widgets, Counters = [{pings, 1, ratio, "Some ratio that happens to be 0%"}, {pongs, 2, ratio, "Some ratio that happens to be 17%"}, @@ -196,6 +197,46 @@ prometheus_format_ratio() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. +text_format_selected_metrics() -> + Group = widgets, + Counters = [ + {reads, 1, counter, "Total reads"}, + {writes, 2, counter, "Total writes"}, + {cached, 3, ratio, "Ratio of things served from cache"} + ], + seshat:new_group(Group), + seshat:new(Group, thing1, Counters, #{component => "thing1", version => "1.2.3"}), + seshat:new(Group, thing2, Counters, #{component => "thing2", some_atom => atom_value}), + seshat:new(Group, thing3, Counters, #{component => "thing3", some_binary => <<"binary_value">>}), + set_value(Group, thing1, reads, 1), + set_value(Group, thing1, writes, 2), + set_value(Group, thing1, cached, 10), + set_value(Group, thing2, reads, 3), + set_value(Group, thing2, writes, 4), + set_value(Group, thing2, cached, 100), + set_value(Group, thing3, reads, 1234), + set_value(Group, thing3, writes, 4321), + set_value(Group, thing3, cached, 17), + PrometheusFormat = binary_to_list(seshat:text_format(Group, "acme", [reads, writes, cached])), + ExpectedPrometheusFormat = "# HELP acme_reads Total reads\n" + "# TYPE acme_reads counter\n" + "acme_reads{version=\"1.2.3\",component=\"thing1\"} 1\n" + "acme_reads{component=\"thing2\",some_atom=\"atom_value\"} 3\n" + "acme_reads{component=\"thing3\",some_binary=\"binary_value\"} 1234\n" + "# HELP acme_writes Total writes\n" + "# TYPE acme_writes counter\n" + "acme_writes{version=\"1.2.3\",component=\"thing1\"} 2\n" + "acme_writes{component=\"thing2\",some_atom=\"atom_value\"} 4\n" + "acme_writes{component=\"thing3\",some_binary=\"binary_value\"} 4321\n" + "# HELP acme_cached_ratio Ratio of things served from cache\n" + "# TYPE acme_cached_ratio gauge\n" + "acme_cached_ratio{version=\"1.2.3\",component=\"thing1\"} 0.1\n" + "acme_cached_ratio{component=\"thing2\",some_atom=\"atom_value\"} 1.0\n" + "acme_cached_ratio{component=\"thing3\",some_binary=\"binary_value\"} 0.17\n", + + ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + ok. + %% test helpers set_value(Group, Id, Name, Value) -> From 3b631e2ee9b6222e7144e3bef77e061afab421ed Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Fri, 18 Apr 2025 10:13:58 +0200 Subject: [PATCH 11/15] Support for time_s and time_ms --- src/seshat.erl | 66 ++++++++++++++++++++-------- test/seshat_test.erl | 101 ++++++++++++++++++++++++++++++++++++++----- 2 files changed, 138 insertions(+), 29 deletions(-) diff --git a/src/seshat.erl b/src/seshat.erl index 90cd90c..a3bd4a9 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -27,13 +27,17 @@ -opaque group_ref() :: ets:tid(). -type name() :: term(). +-type metric_type() :: counter | gauge | ratio | time_s | time_ms. + +-type prometheus_type() :: counter | gauge. + -type field_spec() :: {Name :: atom(), Index :: pos_integer(), - Type :: counter | gauge | ratio, Help :: string()}. + Type :: metric_type(), Help :: string()}. -type fields_spec() :: [field_spec()] | {persistent_term, term()}. -type format_result() :: #{Name :: atom() => - #{type => counter | gauge, + #{type => prometheus_type(), help => string(), values => #{labels() => number()}}}. @@ -298,6 +302,8 @@ format_fields(Fields, CRef, Labels, Acc) -> fun ({Name, Index, Type, Help}, Acc0) -> ComputedType = case Type of ratio -> gauge; + time_s -> gauge; + time_ms -> gauge; Other -> Other end, InitialMetric = #{type => ComputedType, @@ -306,8 +312,14 @@ format_fields(Fields, CRef, Labels, Acc) -> MetricAcc = maps:get(Name, Acc0, InitialMetric), ValuesAcc = maps:get(values, MetricAcc), ComputedValue = case Type of - ratio -> counters:get(CRef, Index) / 100; - _ -> counters:get(CRef, Index) + ratio -> + counters:get(CRef, Index) / 100; + time_ms -> + counters:get(CRef, Index) / 1000; % ms to s + time_s -> + counters:get(CRef, Index) * 1.0; % ensure float + _ -> + counters:get(CRef, Index) end, ValuesAcc1 = ValuesAcc#{Labels => ComputedValue}, MetricAcc1 = MetricAcc#{values => ValuesAcc1}, @@ -360,27 +372,30 @@ text_format_fields(Fields, CRef, Labels, PrefixBin, Acc) -> % a given metric, so we accumulate in a map, with the metric name as key lists:foldl( fun ({Name, Index, Type, Help}, Acc0) -> - ComputedType = case Type of - ratio -> gauge; - Other -> Other - end, - ComputedUnit = case Type of - ratio -> <<"_ratio">>; - _ -> <<"">> - end, + PromType = prometheus_type(Type), + Suffix = prometheus_suffix(Type), ComputedValue = case Type of - ratio -> Ratio = counters:get(CRef, Index) / 100, - Ratio1= float_to_list(Ratio, [{decimals, 2}, compact]), - list_to_binary(Ratio1) - ; - _ -> integer_to_binary(counters:get(CRef, Index)) + ratio -> + Value = counters:get(CRef, Index) / 100, + Formatted = float_to_list(Value, [{decimals, 2}, compact]), + list_to_binary(Formatted); + time_ms -> + Value = counters:get(CRef, Index) / 1000, % ms to s + Formatted = float_to_list(Value, [{decimals, 3}, compact]), + list_to_binary(Formatted); + time_s -> + Value = counters:get(CRef, Index) * 1.0, % ensure float + Formatted = float_to_list(Value, [{decimals, 1}, compact]), + list_to_binary(Formatted); + _ -> % counter or gauge + integer_to_binary(counters:get(CRef, Index)) end, - NameBin = <>, + NameBin = <>, Line = <>, case maps:get(Name, Acc0, <<>>) of <<>> -> HelpLine = <<"# HELP ", NameBin/binary, <<" ">>/binary, (list_to_binary(Help))/binary>>, - TypeBin = atom_to_binary(ComputedType, utf8), + TypeBin = atom_to_binary(PromType, utf8), TypeLine = <<"# TYPE ", NameBin/binary, <<" ">>/binary, TypeBin/binary>>, Acc0#{Name => <>/binary, TypeLine/binary, <<"\n">>/binary, Line/binary>>}; Lines -> @@ -388,6 +403,19 @@ text_format_fields(Fields, CRef, Labels, PrefixBin, Acc) -> end end, Acc, Fields). +-spec prometheus_type(metric_type()) -> prometheus_type(). +prometheus_type(counter) -> counter; +prometheus_type(gauge) -> gauge; +prometheus_type(ratio) -> gauge; +prometheus_type(time_s) -> gauge; +prometheus_type(time_ms) -> gauge. + +-spec prometheus_suffix(metric_type()) -> binary(). +prometheus_suffix(ratio) -> <<"_ratio">>; +prometheus_suffix(time_s) -> <<"_seconds">>; +prometheus_suffix(time_ms) -> <<"_seconds">>; +prometheus_suffix(_) -> <<"">>. + label_value_to_binary(Value) when is_atom(Value) -> atom_to_binary(Value, utf8); label_value_to_binary(Value) when is_list(Value) -> diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 96a2d16..637a4f1 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -24,6 +24,7 @@ test_suite_test_() -> fun format_one/0, fun format_with_many_labels/0, fun format_ratio/0, + fun format_time_metrics/0, fun format_selected_metrics/0, fun text_format_selected_metrics/0, fun invalid_fields/0 ]}. @@ -92,7 +93,7 @@ counters_with_persistent_term_field_spec() -> ok. format_group() -> - Group = widgets, + Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => widget1}), @@ -107,7 +108,7 @@ format_group() -> ok. format_one() -> - Group = widgets, + Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => widget1}), @@ -120,7 +121,7 @@ format_one() -> ok. format_with_many_labels() -> - Group = widgets, + Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => "widget1", status => up}), @@ -136,7 +137,7 @@ format_with_many_labels() -> ok. format_selected_metrics() -> - Group = widgets, + Group = ?FUNCTION_NAME, Counters = [ {reads, 1, counter, "Total reads"}, {writes, 2, counter, "Total writes"}, @@ -168,7 +169,7 @@ invalid_fields() -> ok. format_ratio() -> - Group = widgets, + Group = ?FUNCTION_NAME, Counters = [{pings, 1, ratio, "Some ratio that happens to be 0%"}, {pongs, 2, ratio, "Some ratio that happens to be 17%"}, {pangs, 3, ratio, "Some ratio that happens to be 33%"}, @@ -197,13 +198,73 @@ format_ratio() -> ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), ok. + format_time_metrics() -> + Group = ?FUNCTION_NAME, + Counters = [ + {job_duration, 2, time_s, "Job duration"}, + {short_latency, 3, time_ms, "Short latency"}, + {long_latency, 1, time_ms, "Request latency"} + ], + seshat:new_group(Group), + Labels = #{component => test}, + seshat:new(Group, test_component, Counters, Labels), + + % Set values (1500 ms, 30 s, 5 ms) + set_value(Group, test_component, job_duration, 30), + set_value(Group, test_component, short_latency, 5), + set_value(Group, test_component, long_latency, 1500), + + MapFormat = seshat:format(Group), + ExpectedMapFormat = #{ + job_duration => #{type => gauge, + help => "Job duration", + values => #{Labels => 30.0}}, + short_latency => #{type => gauge, + help => "Short latency", + values => #{Labels => 0.005}}, + long_latency => #{type => gauge, + help => "Request latency", + values => #{Labels => 1.5}} + }, + ?assertEqual(ExpectedMapFormat, MapFormat), + + Prefix = "myapp", + MetricNames = [job_duration, short_latency, long_latency], % Added new metric name + ResultAsList = binary_to_list(seshat:text_format(Group, Prefix, MetricNames)), + + % Expected format needs sorting because order isn't guaranteed + ExpectedLines = [ + "# HELP myapp_job_duration_seconds Job duration", + "# TYPE myapp_job_duration_seconds gauge", + "myapp_job_duration_seconds{component=\"test\"} 30.0", + "# HELP myapp_short_latency_seconds Short latency", + "# TYPE myapp_short_latency_seconds gauge", + "myapp_short_latency_seconds{component=\"test\"} 0.005", + "# HELP myapp_long_latency_seconds Request latency", + "# TYPE myapp_long_latency_seconds gauge", + "myapp_long_latency_seconds{component=\"test\"} 1.5" + ], + ExpectedSortedText = lists:sort(ExpectedLines), + + % Split and sort the actual result for comparison + ResultLines = string:split(ResultAsList, "\n", all), + FilteredResultLines = [Line || Line <- ResultLines, Line /= ""], + SortedResultText = lists:sort(FilteredResultLines), + + ?assertEqual(ExpectedSortedText, SortedResultText), + + ok. + text_format_selected_metrics() -> Group = widgets, Counters = [ {reads, 1, counter, "Total reads"}, {writes, 2, counter, "Total writes"}, - {cached, 3, ratio, "Ratio of things served from cache"} - ], + {cached, 3, ratio, "Ratio of things served from cache"}, + {latency, 4, time_ms, "Latency"}, + {duration, 5, time_s, "Duration"}, + {npc, 6, gauge, "A metric we don't request in a call to text_format/3"} + ], seshat:new_group(Group), seshat:new(Group, thing1, Counters, #{component => "thing1", version => "1.2.3"}), seshat:new(Group, thing2, Counters, #{component => "thing2", some_atom => atom_value}), @@ -211,13 +272,23 @@ text_format_selected_metrics() -> set_value(Group, thing1, reads, 1), set_value(Group, thing1, writes, 2), set_value(Group, thing1, cached, 10), + set_value(Group, thing1, latency, 5), + set_value(Group, thing1, duration, 123), + set_value(Group, thing1, npc, 1), % to be ignored set_value(Group, thing2, reads, 3), set_value(Group, thing2, writes, 4), set_value(Group, thing2, cached, 100), + set_value(Group, thing2, latency, 6), + set_value(Group, thing2, duration, 234), + set_value(Group, thing2, npc, 1), % to be ignored set_value(Group, thing3, reads, 1234), set_value(Group, thing3, writes, 4321), set_value(Group, thing3, cached, 17), - PrometheusFormat = binary_to_list(seshat:text_format(Group, "acme", [reads, writes, cached])), + set_value(Group, thing3, latency, 7), + set_value(Group, thing3, duration, 345), + set_value(Group, thing3, npc, 1), % to be ignored + + ResultAsList = binary_to_list(seshat:text_format(Group, "acme", [reads, writes, cached, latency, duration])), ExpectedPrometheusFormat = "# HELP acme_reads Total reads\n" "# TYPE acme_reads counter\n" "acme_reads{version=\"1.2.3\",component=\"thing1\"} 1\n" @@ -232,9 +303,19 @@ text_format_selected_metrics() -> "# TYPE acme_cached_ratio gauge\n" "acme_cached_ratio{version=\"1.2.3\",component=\"thing1\"} 0.1\n" "acme_cached_ratio{component=\"thing2\",some_atom=\"atom_value\"} 1.0\n" - "acme_cached_ratio{component=\"thing3\",some_binary=\"binary_value\"} 0.17\n", + "acme_cached_ratio{component=\"thing3\",some_binary=\"binary_value\"} 0.17\n" + "# HELP acme_latency_seconds Latency\n" + "# TYPE acme_latency_seconds gauge\n" + "acme_latency_seconds{version=\"1.2.3\",component=\"thing1\"} 0.005\n" + "acme_latency_seconds{component=\"thing2\",some_atom=\"atom_value\"} 0.006\n" + "acme_latency_seconds{component=\"thing3\",some_binary=\"binary_value\"} 0.007\n" + "# HELP acme_duration_seconds Duration\n" + "# TYPE acme_duration_seconds gauge\n" + "acme_duration_seconds{version=\"1.2.3\",component=\"thing1\"} 123.0\n" + "acme_duration_seconds{component=\"thing2\",some_atom=\"atom_value\"} 234.0\n" + "acme_duration_seconds{component=\"thing3\",some_binary=\"binary_value\"} 345.0\n", - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + ?assertEqual(ExpectedPrometheusFormat, ResultAsList), ok. %% test helpers From c7edce1e7c5a4e1812b138de16377d6c3169479c Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 22 Apr 2025 10:25:11 +0200 Subject: [PATCH 12/15] Delete accidentally commited file --- rebar3.crashdump | 23 ----------------------- 1 file changed, 23 deletions(-) delete mode 100644 rebar3.crashdump diff --git a/rebar3.crashdump b/rebar3.crashdump deleted file mode 100644 index 453e319..0000000 --- a/rebar3.crashdump +++ /dev/null @@ -1,23 +0,0 @@ -Error: terminated -[{io,format, - ["rebar ~ts on Erlang/OTP ~ts Erts ~ts~n", - ["3.24.0+build.5437.ref5495da14","27","15.2.3"]], - [{file,"io.erl"}, - {line,198}, - {error_info,#{cause => {io,terminated},module => erl_stdlib_errors}}]}, - {rebar_prv_version,do,1, - [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar_prv_version.erl"}, - {line,36}]}, - {rebar_core,do,2, - [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar_core.erl"}, - {line,155}]}, - {rebar3,run_aux,2, - [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar3.erl"}, - {line,212}]}, - {rebar3,main,1, - [{file,"/Users/mkuratczyk/workspace/rebar3/apps/rebar/src/rebar3.erl"}, - {line,66}]}, - {escript,run,2,[{file,"escript.erl"},{line,904}]}, - {escript,start,1,[{file,"escript.erl"},{line,418}]}, - {init,start_it,1,[]}] - From 98da67c957056beca21d387bfb88c8d8b372f053 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 22 Apr 2025 10:46:08 +0200 Subject: [PATCH 13/15] Update erlang.mk --- Makefile | 2 + erlang.mk | 4728 +++++++++++------------------------------------------ 2 files changed, 971 insertions(+), 3759 deletions(-) diff --git a/Makefile b/Makefile index 9b42e3d..e8d6221 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,8 @@ define PROJECT_ENV ] endef +dep_eunit_formatters = git https://github.com/seancribbs/eunit_formatters main + LOCAL_DEPS = sasl crypto # TEST_DEPS=eunit_formatters diff --git a/erlang.mk b/erlang.mk index 6c58ea8..e6e7ea4 100644 --- a/erlang.mk +++ b/erlang.mk @@ -17,7 +17,7 @@ ERLANG_MK_FILENAME := $(realpath $(lastword $(MAKEFILE_LIST))) export ERLANG_MK_FILENAME -ERLANG_MK_VERSION = 16d60fa +ERLANG_MK_VERSION = e13b4c7 ERLANG_MK_WITHOUT = # Make 3.81 and 3.82 are deprecated. @@ -36,7 +36,7 @@ PROJECT ?= $(notdir $(CURDIR)) PROJECT := $(strip $(PROJECT)) PROJECT_VERSION ?= rolling -PROJECT_MOD ?= $(PROJECT)_app +PROJECT_MOD ?= PROJECT_ENV ?= [] # Verbosity. @@ -47,7 +47,7 @@ verbose_0 = @ verbose_2 = set -x; verbose = $(verbose_$(V)) -ifeq ($(V),3) +ifeq ($V,3) SHELL := $(SHELL) -x endif @@ -66,7 +66,7 @@ export ERLANG_MK_TMP # "erl" command. -ERL = erl +A1 -noinput -boot no_dot_erlang +ERL = erl -noinput -boot no_dot_erlang -kernel start_distribution false +P 1024 +Q 1024 # Platform detection. @@ -162,7 +162,7 @@ define newline endef define comma_list -$(subst $(space),$(comma),$(strip $(1))) +$(subst $(space),$(comma),$(strip $1)) endef define escape_dquotes @@ -180,22 +180,23 @@ else core_native_path = $1 endif -core_http_get = curl -Lf$(if $(filter-out 0,$(V)),,s)o $(call core_native_path,$1) $2 +core_http_get = curl -Lf$(if $(filter-out 0,$V),,s)o $(call core_native_path,$1) $2 -core_eq = $(and $(findstring $(1),$(2)),$(findstring $(2),$(1))) +core_eq = $(and $(findstring $1,$2),$(findstring $2,$1)) -# We skip files that contain spaces or '#' because they end up causing issues. -core_find = $(if $(wildcard $1),$(shell find $(1:%/=%) \( -type l -o -type f \) -name $(subst *,\*,$2) -not -name "*[ \#]*")) +# We skip files that contain spaces because they end up causing issues. +# Files that begin with a dot are already ignored by the wildcard function. +core_find = $(foreach f,$(wildcard $(1:%/=%)/*),$(if $(wildcard $f/.),$(call core_find,$f,$2),$(if $(filter $(subst *,%,$2),$f),$(if $(wildcard $f),$f)))) -core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$(1))))))))))))))))))))))))))) +core_lc = $(subst A,a,$(subst B,b,$(subst C,c,$(subst D,d,$(subst E,e,$(subst F,f,$(subst G,g,$(subst H,h,$(subst I,i,$(subst J,j,$(subst K,k,$(subst L,l,$(subst M,m,$(subst N,n,$(subst O,o,$(subst P,p,$(subst Q,q,$(subst R,r,$(subst S,s,$(subst T,t,$(subst U,u,$(subst V,v,$(subst W,w,$(subst X,x,$(subst Y,y,$(subst Z,z,$1)))))))))))))))))))))))))) -core_ls = $(filter-out $(1),$(shell echo $(1))) +core_ls = $(filter-out $1,$(shell echo $1)) # @todo Use a solution that does not require using perl. core_relpath = $(shell perl -e 'use File::Spec; print File::Spec->abs2rel(@ARGV) . "\n"' $1 $2) define core_render - printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $(1))))))\n' > $(2) + printf -- '$(subst $(newline),\n,$(subst %,%%,$(subst ','\'',$(subst $(tab),$(WS),$(call $1)))))\n' > $2 endef # Automated update. @@ -245,10 +246,10 @@ KERL_MAKEFLAGS ?= OTP_GIT ?= https://github.com/erlang/otp define kerl_otp_target -$(KERL_INSTALL_DIR)/$(1): $(KERL) +$(KERL_INSTALL_DIR)/$1: $(KERL) $(verbose) if [ ! -d $$@ ]; then \ - MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $(1) $(1); \ - $(KERL) install $(1) $(KERL_INSTALL_DIR)/$(1); \ + MAKEFLAGS="$(KERL_MAKEFLAGS)" $(KERL) build git $(OTP_GIT) $1 $1; \ + $(KERL) install $1 $(KERL_INSTALL_DIR)/$1; \ fi endef @@ -290,54 +291,6 @@ endif endif -PACKAGES += aberth -pkg_aberth_name = aberth -pkg_aberth_description = Generic BERT-RPC server in Erlang -pkg_aberth_homepage = https://github.com/a13x/aberth -pkg_aberth_fetch = git -pkg_aberth_repo = https://github.com/a13x/aberth -pkg_aberth_commit = master - -PACKAGES += active -pkg_active_name = active -pkg_active_description = Active development for Erlang: rebuild and reload source/binary files while the VM is running -pkg_active_homepage = https://github.com/proger/active -pkg_active_fetch = git -pkg_active_repo = https://github.com/proger/active -pkg_active_commit = master - -PACKAGES += aleppo -pkg_aleppo_name = aleppo -pkg_aleppo_description = Alternative Erlang Pre-Processor -pkg_aleppo_homepage = https://github.com/ErlyORM/aleppo -pkg_aleppo_fetch = git -pkg_aleppo_repo = https://github.com/ErlyORM/aleppo -pkg_aleppo_commit = master - -PACKAGES += alog -pkg_alog_name = alog -pkg_alog_description = Simply the best logging framework for Erlang -pkg_alog_homepage = https://github.com/siberian-fast-food/alogger -pkg_alog_fetch = git -pkg_alog_repo = https://github.com/siberian-fast-food/alogger -pkg_alog_commit = master - -PACKAGES += annotations -pkg_annotations_name = annotations -pkg_annotations_description = Simple code instrumentation utilities -pkg_annotations_homepage = https://github.com/hyperthunk/annotations -pkg_annotations_fetch = git -pkg_annotations_repo = https://github.com/hyperthunk/annotations -pkg_annotations_commit = master - -PACKAGES += apns -pkg_apns_name = apns -pkg_apns_description = Apple Push Notification Server for Erlang -pkg_apns_homepage = http://inaka.github.com/apns4erl -pkg_apns_fetch = git -pkg_apns_repo = https://github.com/inaka/apns4erl -pkg_apns_commit = master - PACKAGES += asciideck pkg_asciideck_name = asciideck pkg_asciideck_description = Asciidoc for Erlang. @@ -346,421 +299,13 @@ pkg_asciideck_fetch = git pkg_asciideck_repo = https://github.com/ninenines/asciideck pkg_asciideck_commit = master -PACKAGES += backoff -pkg_backoff_name = backoff -pkg_backoff_description = Simple exponential backoffs in Erlang -pkg_backoff_homepage = https://github.com/ferd/backoff -pkg_backoff_fetch = git -pkg_backoff_repo = https://github.com/ferd/backoff -pkg_backoff_commit = master - -PACKAGES += barrel_tcp -pkg_barrel_tcp_name = barrel_tcp -pkg_barrel_tcp_description = barrel is a generic TCP acceptor pool with low latency in Erlang. -pkg_barrel_tcp_homepage = https://github.com/benoitc-attic/barrel_tcp -pkg_barrel_tcp_fetch = git -pkg_barrel_tcp_repo = https://github.com/benoitc-attic/barrel_tcp -pkg_barrel_tcp_commit = master - -PACKAGES += basho_bench -pkg_basho_bench_name = basho_bench -pkg_basho_bench_description = A load-generation and testing tool for basically whatever you can write a returning Erlang function for. -pkg_basho_bench_homepage = https://github.com/basho/basho_bench -pkg_basho_bench_fetch = git -pkg_basho_bench_repo = https://github.com/basho/basho_bench -pkg_basho_bench_commit = master - -PACKAGES += bcrypt -pkg_bcrypt_name = bcrypt -pkg_bcrypt_description = Bcrypt Erlang / C library -pkg_bcrypt_homepage = https://github.com/erlangpack/bcrypt -pkg_bcrypt_fetch = git -pkg_bcrypt_repo = https://github.com/erlangpack/bcrypt.git -pkg_bcrypt_commit = master - -PACKAGES += beam -pkg_beam_name = beam -pkg_beam_description = BEAM emulator written in Erlang -pkg_beam_homepage = https://github.com/tonyrog/beam -pkg_beam_fetch = git -pkg_beam_repo = https://github.com/tonyrog/beam -pkg_beam_commit = master - -PACKAGES += bear -pkg_bear_name = bear -pkg_bear_description = a set of statistics functions for erlang -pkg_bear_homepage = https://github.com/boundary/bear -pkg_bear_fetch = git -pkg_bear_repo = https://github.com/boundary/bear -pkg_bear_commit = master - -PACKAGES += bertconf -pkg_bertconf_name = bertconf -pkg_bertconf_description = Make ETS tables out of statc BERT files that are auto-reloaded -pkg_bertconf_homepage = https://github.com/ferd/bertconf -pkg_bertconf_fetch = git -pkg_bertconf_repo = https://github.com/ferd/bertconf -pkg_bertconf_commit = master - -PACKAGES += bifrost -pkg_bifrost_name = bifrost -pkg_bifrost_description = Erlang FTP Server Framework -pkg_bifrost_homepage = https://github.com/thorstadt/bifrost -pkg_bifrost_fetch = git -pkg_bifrost_repo = https://github.com/thorstadt/bifrost -pkg_bifrost_commit = master - -PACKAGES += binpp -pkg_binpp_name = binpp -pkg_binpp_description = Erlang Binary Pretty Printer -pkg_binpp_homepage = https://github.com/jtendo/binpp -pkg_binpp_fetch = git -pkg_binpp_repo = https://github.com/jtendo/binpp -pkg_binpp_commit = master - -PACKAGES += bisect -pkg_bisect_name = bisect -pkg_bisect_description = Ordered fixed-size binary dictionary in Erlang -pkg_bisect_homepage = https://github.com/knutin/bisect -pkg_bisect_fetch = git -pkg_bisect_repo = https://github.com/knutin/bisect -pkg_bisect_commit = master - -PACKAGES += bitcask -pkg_bitcask_name = bitcask -pkg_bitcask_description = because you need another a key/value storage engine -pkg_bitcask_homepage = https://github.com/basho/bitcask -pkg_bitcask_fetch = git -pkg_bitcask_repo = https://github.com/basho/bitcask -pkg_bitcask_commit = develop - -PACKAGES += bootstrap -pkg_bootstrap_name = bootstrap -pkg_bootstrap_description = A simple, yet powerful Erlang cluster bootstrapping application. -pkg_bootstrap_homepage = https://github.com/schlagert/bootstrap -pkg_bootstrap_fetch = git -pkg_bootstrap_repo = https://github.com/schlagert/bootstrap -pkg_bootstrap_commit = master - -PACKAGES += boss -pkg_boss_name = boss -pkg_boss_description = Erlang web MVC, now featuring Comet -pkg_boss_homepage = https://github.com/ChicagoBoss/ChicagoBoss -pkg_boss_fetch = git -pkg_boss_repo = https://github.com/ChicagoBoss/ChicagoBoss -pkg_boss_commit = master - -PACKAGES += boss_db -pkg_boss_db_name = boss_db -pkg_boss_db_description = BossDB: a sharded, caching, pooling, evented ORM for Erlang -pkg_boss_db_homepage = https://github.com/ErlyORM/boss_db -pkg_boss_db_fetch = git -pkg_boss_db_repo = https://github.com/ErlyORM/boss_db -pkg_boss_db_commit = master - -PACKAGES += brod -pkg_brod_name = brod -pkg_brod_description = Kafka client in Erlang -pkg_brod_homepage = https://github.com/klarna/brod -pkg_brod_fetch = git -pkg_brod_repo = https://github.com/klarna/brod.git -pkg_brod_commit = master - -PACKAGES += bson -pkg_bson_name = bson -pkg_bson_description = BSON documents in Erlang, see bsonspec.org -pkg_bson_homepage = https://github.com/comtihon/bson-erlang -pkg_bson_fetch = git -pkg_bson_repo = https://github.com/comtihon/bson-erlang -pkg_bson_commit = master - -PACKAGES += bullet -pkg_bullet_name = bullet -pkg_bullet_description = Simple, reliable, efficient streaming for Cowboy. -pkg_bullet_homepage = http://ninenines.eu -pkg_bullet_fetch = git -pkg_bullet_repo = https://github.com/ninenines/bullet -pkg_bullet_commit = master - -PACKAGES += cache -pkg_cache_name = cache -pkg_cache_description = Erlang in-memory cache -pkg_cache_homepage = https://github.com/fogfish/cache -pkg_cache_fetch = git -pkg_cache_repo = https://github.com/fogfish/cache -pkg_cache_commit = master - -PACKAGES += cake -pkg_cake_name = cake -pkg_cake_description = Really simple terminal colorization -pkg_cake_homepage = https://github.com/darach/cake-erl -pkg_cake_fetch = git -pkg_cake_repo = https://github.com/darach/cake-erl -pkg_cake_commit = master - -PACKAGES += cberl -pkg_cberl_name = cberl -pkg_cberl_description = NIF based Erlang bindings for Couchbase -pkg_cberl_homepage = https://github.com/chitika/cberl -pkg_cberl_fetch = git -pkg_cberl_repo = https://github.com/chitika/cberl -pkg_cberl_commit = master - -PACKAGES += cecho -pkg_cecho_name = cecho -pkg_cecho_description = An ncurses library for Erlang -pkg_cecho_homepage = https://github.com/mazenharake/cecho -pkg_cecho_fetch = git -pkg_cecho_repo = https://github.com/mazenharake/cecho -pkg_cecho_commit = master - -PACKAGES += cferl -pkg_cferl_name = cferl -pkg_cferl_description = Rackspace / Open Stack Cloud Files Erlang Client -pkg_cferl_homepage = https://github.com/ddossot/cferl -pkg_cferl_fetch = git -pkg_cferl_repo = https://github.com/ddossot/cferl -pkg_cferl_commit = master - -PACKAGES += chaos_monkey -pkg_chaos_monkey_name = chaos_monkey -pkg_chaos_monkey_description = This is The CHAOS MONKEY. It will kill your processes. -pkg_chaos_monkey_homepage = https://github.com/dLuna/chaos_monkey -pkg_chaos_monkey_fetch = git -pkg_chaos_monkey_repo = https://github.com/dLuna/chaos_monkey -pkg_chaos_monkey_commit = master - -PACKAGES += check_node -pkg_check_node_name = check_node -pkg_check_node_description = Nagios Scripts for monitoring Riak -pkg_check_node_homepage = https://github.com/basho-labs/riak_nagios -pkg_check_node_fetch = git -pkg_check_node_repo = https://github.com/basho-labs/riak_nagios -pkg_check_node_commit = master - -PACKAGES += chronos -pkg_chronos_name = chronos -pkg_chronos_description = Timer module for Erlang that makes it easy to abstract time out of the tests. -pkg_chronos_homepage = https://github.com/lehoff/chronos -pkg_chronos_fetch = git -pkg_chronos_repo = https://github.com/lehoff/chronos -pkg_chronos_commit = master - -PACKAGES += chumak -pkg_chumak_name = chumak -pkg_chumak_description = Pure Erlang implementation of ZeroMQ Message Transport Protocol. -pkg_chumak_homepage = http://choven.ca -pkg_chumak_fetch = git -pkg_chumak_repo = https://github.com/chovencorp/chumak -pkg_chumak_commit = master - -PACKAGES += cl -pkg_cl_name = cl -pkg_cl_description = OpenCL binding for Erlang -pkg_cl_homepage = https://github.com/tonyrog/cl -pkg_cl_fetch = git -pkg_cl_repo = https://github.com/tonyrog/cl -pkg_cl_commit = master - -PACKAGES += clique -pkg_clique_name = clique -pkg_clique_description = CLI Framework for Erlang -pkg_clique_homepage = https://github.com/basho/clique -pkg_clique_fetch = git -pkg_clique_repo = https://github.com/basho/clique -pkg_clique_commit = develop - -PACKAGES += cloudi_core -pkg_cloudi_core_name = cloudi_core -pkg_cloudi_core_description = CloudI internal service runtime -pkg_cloudi_core_homepage = http://cloudi.org/ -pkg_cloudi_core_fetch = git -pkg_cloudi_core_repo = https://github.com/CloudI/cloudi_core -pkg_cloudi_core_commit = master - -PACKAGES += cloudi_service_api_requests -pkg_cloudi_service_api_requests_name = cloudi_service_api_requests -pkg_cloudi_service_api_requests_description = CloudI Service API requests (JSON-RPC/Erlang-term support) -pkg_cloudi_service_api_requests_homepage = http://cloudi.org/ -pkg_cloudi_service_api_requests_fetch = git -pkg_cloudi_service_api_requests_repo = https://github.com/CloudI/cloudi_service_api_requests -pkg_cloudi_service_api_requests_commit = master - -PACKAGES += cloudi_service_db_mysql -pkg_cloudi_service_db_mysql_name = cloudi_service_db_mysql -pkg_cloudi_service_db_mysql_description = MySQL CloudI Service -pkg_cloudi_service_db_mysql_homepage = http://cloudi.org/ -pkg_cloudi_service_db_mysql_fetch = git -pkg_cloudi_service_db_mysql_repo = https://github.com/CloudI/cloudi_service_db_mysql -pkg_cloudi_service_db_mysql_commit = master - -PACKAGES += cloudi_service_db_pgsql -pkg_cloudi_service_db_pgsql_name = cloudi_service_db_pgsql -pkg_cloudi_service_db_pgsql_description = PostgreSQL CloudI Service -pkg_cloudi_service_db_pgsql_homepage = http://cloudi.org/ -pkg_cloudi_service_db_pgsql_fetch = git -pkg_cloudi_service_db_pgsql_repo = https://github.com/CloudI/cloudi_service_db_pgsql -pkg_cloudi_service_db_pgsql_commit = master - -PACKAGES += cloudi_service_filesystem -pkg_cloudi_service_filesystem_name = cloudi_service_filesystem -pkg_cloudi_service_filesystem_description = Filesystem CloudI Service -pkg_cloudi_service_filesystem_homepage = http://cloudi.org/ -pkg_cloudi_service_filesystem_fetch = git -pkg_cloudi_service_filesystem_repo = https://github.com/CloudI/cloudi_service_filesystem -pkg_cloudi_service_filesystem_commit = master - -PACKAGES += cloudi_service_http_client -pkg_cloudi_service_http_client_name = cloudi_service_http_client -pkg_cloudi_service_http_client_description = HTTP client CloudI Service -pkg_cloudi_service_http_client_homepage = http://cloudi.org/ -pkg_cloudi_service_http_client_fetch = git -pkg_cloudi_service_http_client_repo = https://github.com/CloudI/cloudi_service_http_client -pkg_cloudi_service_http_client_commit = master - -PACKAGES += cloudi_service_http_cowboy -pkg_cloudi_service_http_cowboy_name = cloudi_service_http_cowboy -pkg_cloudi_service_http_cowboy_description = cowboy HTTP/HTTPS CloudI Service -pkg_cloudi_service_http_cowboy_homepage = http://cloudi.org/ -pkg_cloudi_service_http_cowboy_fetch = git -pkg_cloudi_service_http_cowboy_repo = https://github.com/CloudI/cloudi_service_http_cowboy -pkg_cloudi_service_http_cowboy_commit = master - -PACKAGES += cloudi_service_http_elli -pkg_cloudi_service_http_elli_name = cloudi_service_http_elli -pkg_cloudi_service_http_elli_description = elli HTTP CloudI Service -pkg_cloudi_service_http_elli_homepage = http://cloudi.org/ -pkg_cloudi_service_http_elli_fetch = git -pkg_cloudi_service_http_elli_repo = https://github.com/CloudI/cloudi_service_http_elli -pkg_cloudi_service_http_elli_commit = master - -PACKAGES += cloudi_service_map_reduce -pkg_cloudi_service_map_reduce_name = cloudi_service_map_reduce -pkg_cloudi_service_map_reduce_description = Map/Reduce CloudI Service -pkg_cloudi_service_map_reduce_homepage = http://cloudi.org/ -pkg_cloudi_service_map_reduce_fetch = git -pkg_cloudi_service_map_reduce_repo = https://github.com/CloudI/cloudi_service_map_reduce -pkg_cloudi_service_map_reduce_commit = master - -PACKAGES += cloudi_service_oauth1 -pkg_cloudi_service_oauth1_name = cloudi_service_oauth1 -pkg_cloudi_service_oauth1_description = OAuth v1.0 CloudI Service -pkg_cloudi_service_oauth1_homepage = http://cloudi.org/ -pkg_cloudi_service_oauth1_fetch = git -pkg_cloudi_service_oauth1_repo = https://github.com/CloudI/cloudi_service_oauth1 -pkg_cloudi_service_oauth1_commit = master - -PACKAGES += cloudi_service_queue -pkg_cloudi_service_queue_name = cloudi_service_queue -pkg_cloudi_service_queue_description = Persistent Queue Service -pkg_cloudi_service_queue_homepage = http://cloudi.org/ -pkg_cloudi_service_queue_fetch = git -pkg_cloudi_service_queue_repo = https://github.com/CloudI/cloudi_service_queue -pkg_cloudi_service_queue_commit = master - -PACKAGES += cloudi_service_quorum -pkg_cloudi_service_quorum_name = cloudi_service_quorum -pkg_cloudi_service_quorum_description = CloudI Quorum Service -pkg_cloudi_service_quorum_homepage = http://cloudi.org/ -pkg_cloudi_service_quorum_fetch = git -pkg_cloudi_service_quorum_repo = https://github.com/CloudI/cloudi_service_quorum -pkg_cloudi_service_quorum_commit = master - -PACKAGES += cloudi_service_router -pkg_cloudi_service_router_name = cloudi_service_router -pkg_cloudi_service_router_description = CloudI Router Service -pkg_cloudi_service_router_homepage = http://cloudi.org/ -pkg_cloudi_service_router_fetch = git -pkg_cloudi_service_router_repo = https://github.com/CloudI/cloudi_service_router -pkg_cloudi_service_router_commit = master - -PACKAGES += cloudi_service_tcp -pkg_cloudi_service_tcp_name = cloudi_service_tcp -pkg_cloudi_service_tcp_description = TCP CloudI Service -pkg_cloudi_service_tcp_homepage = http://cloudi.org/ -pkg_cloudi_service_tcp_fetch = git -pkg_cloudi_service_tcp_repo = https://github.com/CloudI/cloudi_service_tcp -pkg_cloudi_service_tcp_commit = master - -PACKAGES += cloudi_service_udp -pkg_cloudi_service_udp_name = cloudi_service_udp -pkg_cloudi_service_udp_description = UDP CloudI Service -pkg_cloudi_service_udp_homepage = http://cloudi.org/ -pkg_cloudi_service_udp_fetch = git -pkg_cloudi_service_udp_repo = https://github.com/CloudI/cloudi_service_udp -pkg_cloudi_service_udp_commit = master - -PACKAGES += cloudi_service_validate -pkg_cloudi_service_validate_name = cloudi_service_validate -pkg_cloudi_service_validate_description = CloudI Validate Service -pkg_cloudi_service_validate_homepage = http://cloudi.org/ -pkg_cloudi_service_validate_fetch = git -pkg_cloudi_service_validate_repo = https://github.com/CloudI/cloudi_service_validate -pkg_cloudi_service_validate_commit = master - -PACKAGES += cloudi_service_zeromq -pkg_cloudi_service_zeromq_name = cloudi_service_zeromq -pkg_cloudi_service_zeromq_description = ZeroMQ CloudI Service -pkg_cloudi_service_zeromq_homepage = http://cloudi.org/ -pkg_cloudi_service_zeromq_fetch = git -pkg_cloudi_service_zeromq_repo = https://github.com/CloudI/cloudi_service_zeromq -pkg_cloudi_service_zeromq_commit = master - -PACKAGES += cluster_info -pkg_cluster_info_name = cluster_info -pkg_cluster_info_description = Fork of Hibari's nifty cluster_info OTP app -pkg_cluster_info_homepage = https://github.com/basho/cluster_info -pkg_cluster_info_fetch = git -pkg_cluster_info_repo = https://github.com/basho/cluster_info -pkg_cluster_info_commit = master - -PACKAGES += color -pkg_color_name = color -pkg_color_description = ANSI colors for your Erlang -pkg_color_homepage = https://github.com/julianduque/erlang-color -pkg_color_fetch = git -pkg_color_repo = https://github.com/julianduque/erlang-color -pkg_color_commit = master - -PACKAGES += confetti -pkg_confetti_name = confetti -pkg_confetti_description = Erlang configuration provider / application:get_env/2 on steroids -pkg_confetti_homepage = https://github.com/jtendo/confetti -pkg_confetti_fetch = git -pkg_confetti_repo = https://github.com/jtendo/confetti -pkg_confetti_commit = master - -PACKAGES += couchbeam -pkg_couchbeam_name = couchbeam -pkg_couchbeam_description = Apache CouchDB client in Erlang -pkg_couchbeam_homepage = https://github.com/benoitc/couchbeam -pkg_couchbeam_fetch = git -pkg_couchbeam_repo = https://github.com/benoitc/couchbeam -pkg_couchbeam_commit = master - -PACKAGES += covertool -pkg_covertool_name = covertool -pkg_covertool_description = Tool to convert Erlang cover data files into Cobertura XML reports -pkg_covertool_homepage = https://github.com/idubrov/covertool -pkg_covertool_fetch = git -pkg_covertool_repo = https://github.com/idubrov/covertool -pkg_covertool_commit = master - PACKAGES += cowboy pkg_cowboy_name = cowboy pkg_cowboy_description = Small, fast and modular HTTP server. pkg_cowboy_homepage = http://ninenines.eu pkg_cowboy_fetch = git pkg_cowboy_repo = https://github.com/ninenines/cowboy -pkg_cowboy_commit = 1.0.4 - -PACKAGES += cowdb -pkg_cowdb_name = cowdb -pkg_cowdb_description = Pure Key/Value database library for Erlang Applications -pkg_cowdb_homepage = https://github.com/refuge/cowdb -pkg_cowdb_fetch = git -pkg_cowdb_repo = https://github.com/refuge/cowdb -pkg_cowdb_commit = master +pkg_cowboy_commit = master PACKAGES += cowlib pkg_cowlib_name = cowlib @@ -768,600 +313,16 @@ pkg_cowlib_description = Support library for manipulating Web protocols. pkg_cowlib_homepage = http://ninenines.eu pkg_cowlib_fetch = git pkg_cowlib_repo = https://github.com/ninenines/cowlib -pkg_cowlib_commit = 1.0.2 - -PACKAGES += cpg -pkg_cpg_name = cpg -pkg_cpg_description = CloudI Process Groups -pkg_cpg_homepage = https://github.com/okeuday/cpg -pkg_cpg_fetch = git -pkg_cpg_repo = https://github.com/okeuday/cpg -pkg_cpg_commit = master - -PACKAGES += cqerl -pkg_cqerl_name = cqerl -pkg_cqerl_description = Native Erlang CQL client for Cassandra -pkg_cqerl_homepage = https://matehat.github.io/cqerl/ -pkg_cqerl_fetch = git -pkg_cqerl_repo = https://github.com/matehat/cqerl -pkg_cqerl_commit = master - -PACKAGES += cr -pkg_cr_name = cr -pkg_cr_description = Chain Replication -pkg_cr_homepage = https://synrc.com/apps/cr/doc/cr.htm -pkg_cr_fetch = git -pkg_cr_repo = https://github.com/spawnproc/cr -pkg_cr_commit = master - -PACKAGES += cuttlefish -pkg_cuttlefish_name = cuttlefish -pkg_cuttlefish_description = cuttlefish configuration abstraction -pkg_cuttlefish_homepage = https://github.com/Kyorai/cuttlefish -pkg_cuttlefish_fetch = git -pkg_cuttlefish_repo = https://github.com/Kyorai/cuttlefish -pkg_cuttlefish_commit = master - -PACKAGES += damocles -pkg_damocles_name = damocles -pkg_damocles_description = Erlang library for generating adversarial network conditions for QAing distributed applications/systems on a single Linux box. -pkg_damocles_homepage = https://github.com/lostcolony/damocles -pkg_damocles_fetch = git -pkg_damocles_repo = https://github.com/lostcolony/damocles -pkg_damocles_commit = master - -PACKAGES += debbie -pkg_debbie_name = debbie -pkg_debbie_description = .DEB Built In Erlang -pkg_debbie_homepage = https://github.com/crownedgrouse/debbie -pkg_debbie_fetch = git -pkg_debbie_repo = https://github.com/crownedgrouse/debbie -pkg_debbie_commit = master - -PACKAGES += decimal -pkg_decimal_name = decimal -pkg_decimal_description = An Erlang decimal arithmetic library -pkg_decimal_homepage = https://github.com/egobrain/decimal -pkg_decimal_fetch = git -pkg_decimal_repo = https://github.com/egobrain/decimal -pkg_decimal_commit = master - -PACKAGES += detergent -pkg_detergent_name = detergent -pkg_detergent_description = An emulsifying Erlang SOAP library -pkg_detergent_homepage = https://github.com/devinus/detergent -pkg_detergent_fetch = git -pkg_detergent_repo = https://github.com/devinus/detergent -pkg_detergent_commit = master - -PACKAGES += dh_date -pkg_dh_date_name = dh_date -pkg_dh_date_description = Date formatting / parsing library for erlang -pkg_dh_date_homepage = https://github.com/daleharvey/dh_date -pkg_dh_date_fetch = git -pkg_dh_date_repo = https://github.com/daleharvey/dh_date -pkg_dh_date_commit = master - -PACKAGES += dirbusterl -pkg_dirbusterl_name = dirbusterl -pkg_dirbusterl_description = DirBuster successor in Erlang -pkg_dirbusterl_homepage = https://github.com/silentsignal/DirBustErl -pkg_dirbusterl_fetch = git -pkg_dirbusterl_repo = https://github.com/silentsignal/DirBustErl -pkg_dirbusterl_commit = master - -PACKAGES += dispcount -pkg_dispcount_name = dispcount -pkg_dispcount_description = Erlang task dispatcher based on ETS counters. -pkg_dispcount_homepage = https://github.com/ferd/dispcount -pkg_dispcount_fetch = git -pkg_dispcount_repo = https://github.com/ferd/dispcount -pkg_dispcount_commit = master - -PACKAGES += dlhttpc -pkg_dlhttpc_name = dlhttpc -pkg_dlhttpc_description = dispcount-based lhttpc fork for massive amounts of requests to limited endpoints -pkg_dlhttpc_homepage = https://github.com/ferd/dlhttpc -pkg_dlhttpc_fetch = git -pkg_dlhttpc_repo = https://github.com/ferd/dlhttpc -pkg_dlhttpc_commit = master - -PACKAGES += dns -pkg_dns_name = dns -pkg_dns_description = Erlang DNS library -pkg_dns_homepage = https://github.com/aetrion/dns_erlang -pkg_dns_fetch = git -pkg_dns_repo = https://github.com/aetrion/dns_erlang -pkg_dns_commit = main - -PACKAGES += dynamic_compile -pkg_dynamic_compile_name = dynamic_compile -pkg_dynamic_compile_description = compile and load erlang modules from string input -pkg_dynamic_compile_homepage = https://github.com/jkvor/dynamic_compile -pkg_dynamic_compile_fetch = git -pkg_dynamic_compile_repo = https://github.com/jkvor/dynamic_compile -pkg_dynamic_compile_commit = master - -PACKAGES += e2 -pkg_e2_name = e2 -pkg_e2_description = Library to simply writing correct OTP applications. -pkg_e2_homepage = http://e2project.org -pkg_e2_fetch = git -pkg_e2_repo = https://github.com/gar1t/e2 -pkg_e2_commit = master - -PACKAGES += eamf -pkg_eamf_name = eamf -pkg_eamf_description = eAMF provides Action Message Format (AMF) support for Erlang -pkg_eamf_homepage = https://github.com/mrinalwadhwa/eamf -pkg_eamf_fetch = git -pkg_eamf_repo = https://github.com/mrinalwadhwa/eamf -pkg_eamf_commit = master - -PACKAGES += eavro -pkg_eavro_name = eavro -pkg_eavro_description = Apache Avro encoder/decoder -pkg_eavro_homepage = https://github.com/SIfoxDevTeam/eavro -pkg_eavro_fetch = git -pkg_eavro_repo = https://github.com/SIfoxDevTeam/eavro -pkg_eavro_commit = master - -PACKAGES += ecapnp -pkg_ecapnp_name = ecapnp -pkg_ecapnp_description = Cap'n Proto library for Erlang -pkg_ecapnp_homepage = https://github.com/kaos/ecapnp -pkg_ecapnp_fetch = git -pkg_ecapnp_repo = https://github.com/kaos/ecapnp -pkg_ecapnp_commit = master - -PACKAGES += econfig -pkg_econfig_name = econfig -pkg_econfig_description = simple Erlang config handler using INI files -pkg_econfig_homepage = https://github.com/benoitc/econfig -pkg_econfig_fetch = git -pkg_econfig_repo = https://github.com/benoitc/econfig -pkg_econfig_commit = master - -PACKAGES += edate -pkg_edate_name = edate -pkg_edate_description = date manipulation library for erlang -pkg_edate_homepage = https://github.com/dweldon/edate -pkg_edate_fetch = git -pkg_edate_repo = https://github.com/dweldon/edate -pkg_edate_commit = master - -PACKAGES += edgar -pkg_edgar_name = edgar -pkg_edgar_description = Erlang Does GNU AR -pkg_edgar_homepage = https://github.com/crownedgrouse/edgar -pkg_edgar_fetch = git -pkg_edgar_repo = https://github.com/crownedgrouse/edgar -pkg_edgar_commit = master - -PACKAGES += edns -pkg_edns_name = edns -pkg_edns_description = Erlang/OTP DNS server -pkg_edns_homepage = https://github.com/hcvst/erlang-dns -pkg_edns_fetch = git -pkg_edns_repo = https://github.com/hcvst/erlang-dns -pkg_edns_commit = master - -PACKAGES += edown -pkg_edown_name = edown -pkg_edown_description = EDoc extension for generating Github-flavored Markdown -pkg_edown_homepage = https://github.com/uwiger/edown -pkg_edown_fetch = git -pkg_edown_repo = https://github.com/uwiger/edown -pkg_edown_commit = master - -PACKAGES += eep -pkg_eep_name = eep -pkg_eep_description = Erlang Easy Profiling (eep) application provides a way to analyze application performance and call hierarchy -pkg_eep_homepage = https://github.com/virtan/eep -pkg_eep_fetch = git -pkg_eep_repo = https://github.com/virtan/eep -pkg_eep_commit = master - -PACKAGES += eep_app -pkg_eep_app_name = eep_app -pkg_eep_app_description = Embedded Event Processing -pkg_eep_app_homepage = https://github.com/darach/eep-erl -pkg_eep_app_fetch = git -pkg_eep_app_repo = https://github.com/darach/eep-erl -pkg_eep_app_commit = master - -PACKAGES += efene -pkg_efene_name = efene -pkg_efene_description = Alternative syntax for the Erlang Programming Language focusing on simplicity, ease of use and programmer UX -pkg_efene_homepage = https://github.com/efene/efene -pkg_efene_fetch = git -pkg_efene_repo = https://github.com/efene/efene -pkg_efene_commit = master - -PACKAGES += egeoip -pkg_egeoip_name = egeoip -pkg_egeoip_description = Erlang IP Geolocation module, currently supporting the MaxMind GeoLite City Database. -pkg_egeoip_homepage = https://github.com/mochi/egeoip -pkg_egeoip_fetch = git -pkg_egeoip_repo = https://github.com/mochi/egeoip -pkg_egeoip_commit = master - -PACKAGES += ehsa -pkg_ehsa_name = ehsa -pkg_ehsa_description = Erlang HTTP server basic and digest authentication modules -pkg_ehsa_homepage = https://github.com/a12n/ehsa -pkg_ehsa_fetch = git -pkg_ehsa_repo = https://github.com/a12n/ehsa -pkg_ehsa_commit = master - -PACKAGES += ej -pkg_ej_name = ej -pkg_ej_description = Helper module for working with Erlang terms representing JSON -pkg_ej_homepage = https://github.com/seth/ej -pkg_ej_fetch = git -pkg_ej_repo = https://github.com/seth/ej -pkg_ej_commit = master - -PACKAGES += ejabberd -pkg_ejabberd_name = ejabberd -pkg_ejabberd_description = Robust, ubiquitous and massively scalable Jabber / XMPP Instant Messaging platform -pkg_ejabberd_homepage = https://github.com/processone/ejabberd -pkg_ejabberd_fetch = git -pkg_ejabberd_repo = https://github.com/processone/ejabberd -pkg_ejabberd_commit = master - -PACKAGES += ejwt -pkg_ejwt_name = ejwt -pkg_ejwt_description = erlang library for JSON Web Token -pkg_ejwt_homepage = https://github.com/artefactop/ejwt -pkg_ejwt_fetch = git -pkg_ejwt_repo = https://github.com/artefactop/ejwt -pkg_ejwt_commit = master - -PACKAGES += ekaf -pkg_ekaf_name = ekaf -pkg_ekaf_description = A minimal, high-performance Kafka client in Erlang. -pkg_ekaf_homepage = https://github.com/helpshift/ekaf -pkg_ekaf_fetch = git -pkg_ekaf_repo = https://github.com/helpshift/ekaf -pkg_ekaf_commit = master - -PACKAGES += elarm -pkg_elarm_name = elarm -pkg_elarm_description = Alarm Manager for Erlang. -pkg_elarm_homepage = https://github.com/esl/elarm -pkg_elarm_fetch = git -pkg_elarm_repo = https://github.com/esl/elarm -pkg_elarm_commit = master - -PACKAGES += eleveldb -pkg_eleveldb_name = eleveldb -pkg_eleveldb_description = Erlang LevelDB API -pkg_eleveldb_homepage = https://github.com/basho/eleveldb -pkg_eleveldb_fetch = git -pkg_eleveldb_repo = https://github.com/basho/eleveldb -pkg_eleveldb_commit = develop +pkg_cowlib_commit = master PACKAGES += elixir pkg_elixir_name = elixir -pkg_elixir_description = Elixir is a dynamic, functional language designed for building scalable and maintainable applications -pkg_elixir_homepage = https://elixir-lang.org/ +pkg_elixir_description = Elixir is a dynamic, functional language for building scalable and maintainable applications. +pkg_elixir_homepage = https://elixir-lang.org pkg_elixir_fetch = git pkg_elixir_repo = https://github.com/elixir-lang/elixir pkg_elixir_commit = main -PACKAGES += elli -pkg_elli_name = elli -pkg_elli_description = Simple, robust and performant Erlang web server -pkg_elli_homepage = https://github.com/elli-lib/elli -pkg_elli_fetch = git -pkg_elli_repo = https://github.com/elli-lib/elli -pkg_elli_commit = main - -PACKAGES += elvis -pkg_elvis_name = elvis -pkg_elvis_description = Erlang Style Reviewer -pkg_elvis_homepage = https://github.com/inaka/elvis -pkg_elvis_fetch = git -pkg_elvis_repo = https://github.com/inaka/elvis -pkg_elvis_commit = master - -PACKAGES += emagick -pkg_emagick_name = emagick -pkg_emagick_description = Wrapper for Graphics/ImageMagick command line tool. -pkg_emagick_homepage = https://github.com/kivra/emagick -pkg_emagick_fetch = git -pkg_emagick_repo = https://github.com/kivra/emagick -pkg_emagick_commit = master - -PACKAGES += enm -pkg_enm_name = enm -pkg_enm_description = Erlang driver for nanomsg -pkg_enm_homepage = https://github.com/basho/enm -pkg_enm_fetch = git -pkg_enm_repo = https://github.com/basho/enm -pkg_enm_commit = master - -PACKAGES += entop -pkg_entop_name = entop -pkg_entop_description = A top-like tool for monitoring an Erlang node -pkg_entop_homepage = https://github.com/mazenharake/entop -pkg_entop_fetch = git -pkg_entop_repo = https://github.com/mazenharake/entop -pkg_entop_commit = master - -PACKAGES += epcap -pkg_epcap_name = epcap -pkg_epcap_description = Erlang packet capture interface using pcap -pkg_epcap_homepage = https://github.com/msantos/epcap -pkg_epcap_fetch = git -pkg_epcap_repo = https://github.com/msantos/epcap -pkg_epcap_commit = master - -PACKAGES += eper -pkg_eper_name = eper -pkg_eper_description = Erlang performance and debugging tools. -pkg_eper_homepage = https://github.com/massemanet/eper -pkg_eper_fetch = git -pkg_eper_repo = https://github.com/massemanet/eper -pkg_eper_commit = master - -PACKAGES += epgsql -pkg_epgsql_name = epgsql -pkg_epgsql_description = Erlang PostgreSQL client library. -pkg_epgsql_homepage = https://github.com/epgsql/epgsql -pkg_epgsql_fetch = git -pkg_epgsql_repo = https://github.com/epgsql/epgsql -pkg_epgsql_commit = master - -PACKAGES += episcina -pkg_episcina_name = episcina -pkg_episcina_description = A simple non intrusive resource pool for connections -pkg_episcina_homepage = https://github.com/erlware/episcina -pkg_episcina_fetch = git -pkg_episcina_repo = https://github.com/erlware/episcina -pkg_episcina_commit = master - -PACKAGES += eplot -pkg_eplot_name = eplot -pkg_eplot_description = A plot engine written in erlang. -pkg_eplot_homepage = https://github.com/psyeugenic/eplot -pkg_eplot_fetch = git -pkg_eplot_repo = https://github.com/psyeugenic/eplot -pkg_eplot_commit = master - -PACKAGES += epocxy -pkg_epocxy_name = epocxy -pkg_epocxy_description = Erlang Patterns of Concurrency -pkg_epocxy_homepage = https://github.com/duomark/epocxy -pkg_epocxy_fetch = git -pkg_epocxy_repo = https://github.com/duomark/epocxy -pkg_epocxy_commit = master - -PACKAGES += epubnub -pkg_epubnub_name = epubnub -pkg_epubnub_description = Erlang PubNub API -pkg_epubnub_homepage = https://github.com/tsloughter/epubnub -pkg_epubnub_fetch = git -pkg_epubnub_repo = https://github.com/tsloughter/epubnub -pkg_epubnub_commit = master - -PACKAGES += eqm -pkg_eqm_name = eqm -pkg_eqm_description = Erlang pub sub with supply-demand channels -pkg_eqm_homepage = https://github.com/loucash/eqm -pkg_eqm_fetch = git -pkg_eqm_repo = https://github.com/loucash/eqm -pkg_eqm_commit = master - -PACKAGES += eredis -pkg_eredis_name = eredis -pkg_eredis_description = Erlang Redis client -pkg_eredis_homepage = https://github.com/wooga/eredis -pkg_eredis_fetch = git -pkg_eredis_repo = https://github.com/wooga/eredis -pkg_eredis_commit = master - -PACKAGES += erl_streams -pkg_erl_streams_name = erl_streams -pkg_erl_streams_description = Streams in Erlang -pkg_erl_streams_homepage = https://github.com/epappas/erl_streams -pkg_erl_streams_fetch = git -pkg_erl_streams_repo = https://github.com/epappas/erl_streams -pkg_erl_streams_commit = master - -PACKAGES += erlang_localtime -pkg_erlang_localtime_name = erlang_localtime -pkg_erlang_localtime_description = Erlang library for conversion from one local time to another -pkg_erlang_localtime_homepage = https://github.com/dmitryme/erlang_localtime -pkg_erlang_localtime_fetch = git -pkg_erlang_localtime_repo = https://github.com/dmitryme/erlang_localtime -pkg_erlang_localtime_commit = master - -PACKAGES += erlang_smtp -pkg_erlang_smtp_name = erlang_smtp -pkg_erlang_smtp_description = Erlang SMTP and POP3 server code. -pkg_erlang_smtp_homepage = https://github.com/tonyg/erlang-smtp -pkg_erlang_smtp_fetch = git -pkg_erlang_smtp_repo = https://github.com/tonyg/erlang-smtp -pkg_erlang_smtp_commit = master - -PACKAGES += erlang_term -pkg_erlang_term_name = erlang_term -pkg_erlang_term_description = Erlang Term Info -pkg_erlang_term_homepage = https://github.com/okeuday/erlang_term -pkg_erlang_term_fetch = git -pkg_erlang_term_repo = https://github.com/okeuday/erlang_term -pkg_erlang_term_commit = master - -PACKAGES += erlastic_search -pkg_erlastic_search_name = erlastic_search -pkg_erlastic_search_description = An Erlang app for communicating with Elastic Search's rest interface. -pkg_erlastic_search_homepage = https://github.com/tsloughter/erlastic_search -pkg_erlastic_search_fetch = git -pkg_erlastic_search_repo = https://github.com/tsloughter/erlastic_search -pkg_erlastic_search_commit = master - -PACKAGES += erlbrake -pkg_erlbrake_name = erlbrake -pkg_erlbrake_description = Erlang Airbrake notification client -pkg_erlbrake_homepage = https://github.com/kenpratt/erlbrake -pkg_erlbrake_fetch = git -pkg_erlbrake_repo = https://github.com/kenpratt/erlbrake -pkg_erlbrake_commit = master - -PACKAGES += erlcloud -pkg_erlcloud_name = erlcloud -pkg_erlcloud_description = Cloud Computing library for erlang (Amazon EC2, S3, SQS, SimpleDB, Mechanical Turk, ELB) -pkg_erlcloud_homepage = https://github.com/gleber/erlcloud -pkg_erlcloud_fetch = git -pkg_erlcloud_repo = https://github.com/gleber/erlcloud -pkg_erlcloud_commit = master - -PACKAGES += erlcron -pkg_erlcron_name = erlcron -pkg_erlcron_description = Erlang cronish system -pkg_erlcron_homepage = https://github.com/erlware/erlcron -pkg_erlcron_fetch = git -pkg_erlcron_repo = https://github.com/erlware/erlcron -pkg_erlcron_commit = master - -PACKAGES += erldb -pkg_erldb_name = erldb -pkg_erldb_description = ORM (Object-relational mapping) application implemented in Erlang -pkg_erldb_homepage = http://erldb.org -pkg_erldb_fetch = git -pkg_erldb_repo = https://github.com/erldb/erldb -pkg_erldb_commit = master - -PACKAGES += erldis -pkg_erldis_name = erldis -pkg_erldis_description = redis erlang client library -pkg_erldis_homepage = https://github.com/cstar/erldis -pkg_erldis_fetch = git -pkg_erldis_repo = https://github.com/cstar/erldis -pkg_erldis_commit = master - -PACKAGES += erldns -pkg_erldns_name = erldns -pkg_erldns_description = DNS server, in erlang. -pkg_erldns_homepage = https://github.com/aetrion/erl-dns -pkg_erldns_fetch = git -pkg_erldns_repo = https://github.com/aetrion/erl-dns -pkg_erldns_commit = main - -PACKAGES += erldocker -pkg_erldocker_name = erldocker -pkg_erldocker_description = Docker Remote API client for Erlang -pkg_erldocker_homepage = https://github.com/proger/erldocker -pkg_erldocker_fetch = git -pkg_erldocker_repo = https://github.com/proger/erldocker -pkg_erldocker_commit = master - -PACKAGES += erlfsmon -pkg_erlfsmon_name = erlfsmon -pkg_erlfsmon_description = Erlang filesystem event watcher for Linux and OSX -pkg_erlfsmon_homepage = https://github.com/proger/erlfsmon -pkg_erlfsmon_fetch = git -pkg_erlfsmon_repo = https://github.com/proger/erlfsmon -pkg_erlfsmon_commit = master - -PACKAGES += erlgit -pkg_erlgit_name = erlgit -pkg_erlgit_description = Erlang convenience wrapper around git executable -pkg_erlgit_homepage = https://github.com/gleber/erlgit -pkg_erlgit_fetch = git -pkg_erlgit_repo = https://github.com/gleber/erlgit -pkg_erlgit_commit = master - -PACKAGES += erlguten -pkg_erlguten_name = erlguten -pkg_erlguten_description = ErlGuten is a system for high-quality typesetting, written purely in Erlang. -pkg_erlguten_homepage = https://github.com/richcarl/erlguten -pkg_erlguten_fetch = git -pkg_erlguten_repo = https://github.com/richcarl/erlguten -pkg_erlguten_commit = master - -PACKAGES += erlmc -pkg_erlmc_name = erlmc -pkg_erlmc_description = Erlang memcached binary protocol client -pkg_erlmc_homepage = https://github.com/jkvor/erlmc -pkg_erlmc_fetch = git -pkg_erlmc_repo = https://github.com/jkvor/erlmc -pkg_erlmc_commit = master - -PACKAGES += erlmongo -pkg_erlmongo_name = erlmongo -pkg_erlmongo_description = Record based Erlang driver for MongoDB with gridfs support -pkg_erlmongo_homepage = https://github.com/SergejJurecko/erlmongo -pkg_erlmongo_fetch = git -pkg_erlmongo_repo = https://github.com/SergejJurecko/erlmongo -pkg_erlmongo_commit = master - -PACKAGES += erlog -pkg_erlog_name = erlog -pkg_erlog_description = Prolog interpreter in and for Erlang -pkg_erlog_homepage = https://github.com/rvirding/erlog -pkg_erlog_fetch = git -pkg_erlog_repo = https://github.com/rvirding/erlog -pkg_erlog_commit = master - -PACKAGES += erlpass -pkg_erlpass_name = erlpass -pkg_erlpass_description = A library to handle password hashing and changing in a safe manner, independent from any kind of storage whatsoever. -pkg_erlpass_homepage = https://github.com/ferd/erlpass -pkg_erlpass_fetch = git -pkg_erlpass_repo = https://github.com/ferd/erlpass -pkg_erlpass_commit = master - -PACKAGES += erlsh -pkg_erlsh_name = erlsh -pkg_erlsh_description = Erlang shell tools -pkg_erlsh_homepage = https://github.com/proger/erlsh -pkg_erlsh_fetch = git -pkg_erlsh_repo = https://github.com/proger/erlsh -pkg_erlsh_commit = master - -PACKAGES += erlsha2 -pkg_erlsha2_name = erlsha2 -pkg_erlsha2_description = SHA-224, SHA-256, SHA-384, SHA-512 implemented in Erlang NIFs. -pkg_erlsha2_homepage = https://github.com/vinoski/erlsha2 -pkg_erlsha2_fetch = git -pkg_erlsha2_repo = https://github.com/vinoski/erlsha2 -pkg_erlsha2_commit = master - -PACKAGES += erlsom -pkg_erlsom_name = erlsom -pkg_erlsom_description = XML parser for Erlang -pkg_erlsom_homepage = https://github.com/willemdj/erlsom -pkg_erlsom_fetch = git -pkg_erlsom_repo = https://github.com/willemdj/erlsom -pkg_erlsom_commit = master - -PACKAGES += erlubi -pkg_erlubi_name = erlubi -pkg_erlubi_description = Ubigraph Erlang Client (and Process Visualizer) -pkg_erlubi_homepage = https://github.com/krestenkrab/erlubi -pkg_erlubi_fetch = git -pkg_erlubi_repo = https://github.com/krestenkrab/erlubi -pkg_erlubi_commit = master - -PACKAGES += erlvolt -pkg_erlvolt_name = erlvolt -pkg_erlvolt_description = VoltDB Erlang Client Driver -pkg_erlvolt_homepage = https://github.com/VoltDB/voltdb-client-erlang -pkg_erlvolt_fetch = git -pkg_erlvolt_repo = https://github.com/VoltDB/voltdb-client-erlang -pkg_erlvolt_commit = master - -PACKAGES += erlware_commons -pkg_erlware_commons_name = erlware_commons -pkg_erlware_commons_description = Erlware Commons is an Erlware project focused on all aspects of reusable Erlang components. -pkg_erlware_commons_homepage = https://github.com/erlware/erlware_commons -pkg_erlware_commons_fetch = git -pkg_erlware_commons_repo = https://github.com/erlware/erlware_commons -pkg_erlware_commons_commit = master - PACKAGES += erlydtl pkg_erlydtl_name = erlydtl pkg_erlydtl_description = Django Template Language for Erlang. @@ -1370,406 +331,6 @@ pkg_erlydtl_fetch = git pkg_erlydtl_repo = https://github.com/erlydtl/erlydtl pkg_erlydtl_commit = master -PACKAGES += errd -pkg_errd_name = errd -pkg_errd_description = Erlang RRDTool library -pkg_errd_homepage = https://github.com/archaelus/errd -pkg_errd_fetch = git -pkg_errd_repo = https://github.com/archaelus/errd -pkg_errd_commit = master - -PACKAGES += erserve -pkg_erserve_name = erserve -pkg_erserve_description = Erlang/Rserve communication interface -pkg_erserve_homepage = https://github.com/del/erserve -pkg_erserve_fetch = git -pkg_erserve_repo = https://github.com/del/erserve -pkg_erserve_commit = master - -PACKAGES += escalus -pkg_escalus_name = escalus -pkg_escalus_description = An XMPP client library in Erlang for conveniently testing XMPP servers -pkg_escalus_homepage = https://github.com/esl/escalus -pkg_escalus_fetch = git -pkg_escalus_repo = https://github.com/esl/escalus -pkg_escalus_commit = master - -PACKAGES += esh_mk -pkg_esh_mk_name = esh_mk -pkg_esh_mk_description = esh template engine plugin for erlang.mk -pkg_esh_mk_homepage = https://github.com/crownedgrouse/esh.mk -pkg_esh_mk_fetch = git -pkg_esh_mk_repo = https://github.com/crownedgrouse/esh.mk.git -pkg_esh_mk_commit = master - -PACKAGES += espec -pkg_espec_name = espec -pkg_espec_description = ESpec: Behaviour driven development framework for Erlang -pkg_espec_homepage = https://github.com/lucaspiller/espec -pkg_espec_fetch = git -pkg_espec_repo = https://github.com/lucaspiller/espec -pkg_espec_commit = master - -PACKAGES += estatsd -pkg_estatsd_name = estatsd -pkg_estatsd_description = Erlang stats aggregation app that periodically flushes data to graphite -pkg_estatsd_homepage = https://github.com/RJ/estatsd -pkg_estatsd_fetch = git -pkg_estatsd_repo = https://github.com/RJ/estatsd -pkg_estatsd_commit = master - -PACKAGES += etap -pkg_etap_name = etap -pkg_etap_description = etap is a simple erlang testing library that provides TAP compliant output. -pkg_etap_homepage = https://github.com/ngerakines/etap -pkg_etap_fetch = git -pkg_etap_repo = https://github.com/ngerakines/etap -pkg_etap_commit = master - -PACKAGES += etest -pkg_etest_name = etest -pkg_etest_description = A lightweight, convention over configuration test framework for Erlang -pkg_etest_homepage = https://github.com/wooga/etest -pkg_etest_fetch = git -pkg_etest_repo = https://github.com/wooga/etest -pkg_etest_commit = master - -PACKAGES += etest_http -pkg_etest_http_name = etest_http -pkg_etest_http_description = etest Assertions around HTTP (client-side) -pkg_etest_http_homepage = https://github.com/wooga/etest_http -pkg_etest_http_fetch = git -pkg_etest_http_repo = https://github.com/wooga/etest_http -pkg_etest_http_commit = master - -PACKAGES += etoml -pkg_etoml_name = etoml -pkg_etoml_description = TOML language erlang parser -pkg_etoml_homepage = https://github.com/kalta/etoml -pkg_etoml_fetch = git -pkg_etoml_repo = https://github.com/kalta/etoml -pkg_etoml_commit = master - -PACKAGES += eunit -pkg_eunit_name = eunit -pkg_eunit_description = The EUnit lightweight unit testing framework for Erlang - this is the canonical development repository. -pkg_eunit_homepage = https://github.com/richcarl/eunit -pkg_eunit_fetch = git -pkg_eunit_repo = https://github.com/richcarl/eunit -pkg_eunit_commit = master - -PACKAGES += eunit_formatters -pkg_eunit_formatters_name = eunit_formatters -pkg_eunit_formatters_description = Because eunit's output sucks. Let's make it better. -pkg_eunit_formatters_homepage = https://github.com/seancribbs/eunit_formatters -pkg_eunit_formatters_fetch = git -pkg_eunit_formatters_repo = https://github.com/seancribbs/eunit_formatters -pkg_eunit_formatters_commit = master - -PACKAGES += euthanasia -pkg_euthanasia_name = euthanasia -pkg_euthanasia_description = Merciful killer for your Erlang processes -pkg_euthanasia_homepage = https://github.com/doubleyou/euthanasia -pkg_euthanasia_fetch = git -pkg_euthanasia_repo = https://github.com/doubleyou/euthanasia -pkg_euthanasia_commit = master - -PACKAGES += evum -pkg_evum_name = evum -pkg_evum_description = Spawn Linux VMs as Erlang processes in the Erlang VM -pkg_evum_homepage = https://github.com/msantos/evum -pkg_evum_fetch = git -pkg_evum_repo = https://github.com/msantos/evum -pkg_evum_commit = master - -PACKAGES += exec -pkg_exec_name = erlexec -pkg_exec_description = Execute and control OS processes from Erlang/OTP. -pkg_exec_homepage = http://saleyn.github.com/erlexec -pkg_exec_fetch = git -pkg_exec_repo = https://github.com/saleyn/erlexec -pkg_exec_commit = master - -PACKAGES += exml -pkg_exml_name = exml -pkg_exml_description = XML parsing library in Erlang -pkg_exml_homepage = https://github.com/paulgray/exml -pkg_exml_fetch = git -pkg_exml_repo = https://github.com/paulgray/exml -pkg_exml_commit = master - -PACKAGES += exometer -pkg_exometer_name = exometer -pkg_exometer_description = Basic measurement objects and probe behavior -pkg_exometer_homepage = https://github.com/Feuerlabs/exometer -pkg_exometer_fetch = git -pkg_exometer_repo = https://github.com/Feuerlabs/exometer -pkg_exometer_commit = master - -PACKAGES += exs1024 -pkg_exs1024_name = exs1024 -pkg_exs1024_description = Xorshift1024star pseudo random number generator for Erlang. -pkg_exs1024_homepage = https://github.com/jj1bdx/exs1024 -pkg_exs1024_fetch = git -pkg_exs1024_repo = https://github.com/jj1bdx/exs1024 -pkg_exs1024_commit = master - -PACKAGES += exsplus116 -pkg_exsplus116_name = exsplus116 -pkg_exsplus116_description = Xorshift116plus for Erlang -pkg_exsplus116_homepage = https://github.com/jj1bdx/exsplus116 -pkg_exsplus116_fetch = git -pkg_exsplus116_repo = https://github.com/jj1bdx/exsplus116 -pkg_exsplus116_commit = master - -PACKAGES += ezmtp -pkg_ezmtp_name = ezmtp -pkg_ezmtp_description = ZMTP protocol in pure Erlang. -pkg_ezmtp_homepage = https://github.com/a13x/ezmtp -pkg_ezmtp_fetch = git -pkg_ezmtp_repo = https://github.com/a13x/ezmtp -pkg_ezmtp_commit = master - -PACKAGES += fast_disk_log -pkg_fast_disk_log_name = fast_disk_log -pkg_fast_disk_log_description = Pool-based asynchronous Erlang disk logger -pkg_fast_disk_log_homepage = https://github.com/lpgauth/fast_disk_log -pkg_fast_disk_log_fetch = git -pkg_fast_disk_log_repo = https://github.com/lpgauth/fast_disk_log -pkg_fast_disk_log_commit = master - -PACKAGES += feeder -pkg_feeder_name = feeder -pkg_feeder_description = Stream parse RSS and Atom formatted XML feeds. -pkg_feeder_homepage = https://github.com/michaelnisi/feeder -pkg_feeder_fetch = git -pkg_feeder_repo = https://github.com/michaelnisi/feeder -pkg_feeder_commit = master - -PACKAGES += find_crate -pkg_find_crate_name = find_crate -pkg_find_crate_description = Find Rust libs and exes in Erlang application priv directory -pkg_find_crate_homepage = https://github.com/goertzenator/find_crate -pkg_find_crate_fetch = git -pkg_find_crate_repo = https://github.com/goertzenator/find_crate -pkg_find_crate_commit = master - -PACKAGES += fix -pkg_fix_name = fix -pkg_fix_description = http://fixprotocol.org/ implementation. -pkg_fix_homepage = https://github.com/maxlapshin/fix -pkg_fix_fetch = git -pkg_fix_repo = https://github.com/maxlapshin/fix -pkg_fix_commit = master - -PACKAGES += flower -pkg_flower_name = flower -pkg_flower_description = FlowER - a Erlang OpenFlow development platform -pkg_flower_homepage = https://github.com/travelping/flower -pkg_flower_fetch = git -pkg_flower_repo = https://github.com/travelping/flower -pkg_flower_commit = master - -PACKAGES += fn -pkg_fn_name = fn -pkg_fn_description = Function utilities for Erlang -pkg_fn_homepage = https://github.com/reiddraper/fn -pkg_fn_fetch = git -pkg_fn_repo = https://github.com/reiddraper/fn -pkg_fn_commit = master - -PACKAGES += folsom -pkg_folsom_name = folsom -pkg_folsom_description = Expose Erlang Events and Metrics -pkg_folsom_homepage = https://github.com/boundary/folsom -pkg_folsom_fetch = git -pkg_folsom_repo = https://github.com/boundary/folsom -pkg_folsom_commit = master - -PACKAGES += folsom_cowboy -pkg_folsom_cowboy_name = folsom_cowboy -pkg_folsom_cowboy_description = A Cowboy based Folsom HTTP Wrapper. -pkg_folsom_cowboy_homepage = https://github.com/boundary/folsom_cowboy -pkg_folsom_cowboy_fetch = git -pkg_folsom_cowboy_repo = https://github.com/boundary/folsom_cowboy -pkg_folsom_cowboy_commit = master - -PACKAGES += fs -pkg_fs_name = fs -pkg_fs_description = Erlang FileSystem Listener -pkg_fs_homepage = https://github.com/synrc/fs -pkg_fs_fetch = git -pkg_fs_repo = https://github.com/synrc/fs -pkg_fs_commit = master - -PACKAGES += fuse -pkg_fuse_name = fuse -pkg_fuse_description = A Circuit Breaker for Erlang -pkg_fuse_homepage = https://github.com/jlouis/fuse -pkg_fuse_fetch = git -pkg_fuse_repo = https://github.com/jlouis/fuse -pkg_fuse_commit = master - -PACKAGES += gcm -pkg_gcm_name = gcm -pkg_gcm_description = An Erlang application for Google Cloud Messaging -pkg_gcm_homepage = https://github.com/pdincau/gcm-erlang -pkg_gcm_fetch = git -pkg_gcm_repo = https://github.com/pdincau/gcm-erlang -pkg_gcm_commit = master - -PACKAGES += gcprof -pkg_gcprof_name = gcprof -pkg_gcprof_description = Garbage Collection profiler for Erlang -pkg_gcprof_homepage = https://github.com/knutin/gcprof -pkg_gcprof_fetch = git -pkg_gcprof_repo = https://github.com/knutin/gcprof -pkg_gcprof_commit = master - -PACKAGES += geas -pkg_geas_name = geas -pkg_geas_description = Guess Erlang Application Scattering -pkg_geas_homepage = https://github.com/crownedgrouse/geas -pkg_geas_fetch = git -pkg_geas_repo = https://github.com/crownedgrouse/geas -pkg_geas_commit = master - -PACKAGES += geef -pkg_geef_name = geef -pkg_geef_description = Git NEEEEF (Erlang NIF) -pkg_geef_homepage = https://github.com/carlosmn/geef -pkg_geef_fetch = git -pkg_geef_repo = https://github.com/carlosmn/geef -pkg_geef_commit = master - -PACKAGES += gen_coap -pkg_gen_coap_name = gen_coap -pkg_gen_coap_description = Generic Erlang CoAP Client/Server -pkg_gen_coap_homepage = https://github.com/gotthardp/gen_coap -pkg_gen_coap_fetch = git -pkg_gen_coap_repo = https://github.com/gotthardp/gen_coap -pkg_gen_coap_commit = master - -PACKAGES += gen_cycle -pkg_gen_cycle_name = gen_cycle -pkg_gen_cycle_description = Simple, generic OTP behaviour for recurring tasks -pkg_gen_cycle_homepage = https://github.com/aerosol/gen_cycle -pkg_gen_cycle_fetch = git -pkg_gen_cycle_repo = https://github.com/aerosol/gen_cycle -pkg_gen_cycle_commit = develop - -PACKAGES += gen_icmp -pkg_gen_icmp_name = gen_icmp -pkg_gen_icmp_description = Erlang interface to ICMP sockets -pkg_gen_icmp_homepage = https://github.com/msantos/gen_icmp -pkg_gen_icmp_fetch = git -pkg_gen_icmp_repo = https://github.com/msantos/gen_icmp -pkg_gen_icmp_commit = master - -PACKAGES += gen_leader -pkg_gen_leader_name = gen_leader -pkg_gen_leader_description = leader election behavior -pkg_gen_leader_homepage = https://github.com/garret-smith/gen_leader_revival -pkg_gen_leader_fetch = git -pkg_gen_leader_repo = https://github.com/garret-smith/gen_leader_revival -pkg_gen_leader_commit = master - -PACKAGES += gen_nb_server -pkg_gen_nb_server_name = gen_nb_server -pkg_gen_nb_server_description = OTP behavior for writing non-blocking servers -pkg_gen_nb_server_homepage = https://github.com/kevsmith/gen_nb_server -pkg_gen_nb_server_fetch = git -pkg_gen_nb_server_repo = https://github.com/kevsmith/gen_nb_server -pkg_gen_nb_server_commit = master - -PACKAGES += gen_paxos -pkg_gen_paxos_name = gen_paxos -pkg_gen_paxos_description = An Erlang/OTP-style implementation of the PAXOS distributed consensus protocol -pkg_gen_paxos_homepage = https://github.com/gburd/gen_paxos -pkg_gen_paxos_fetch = git -pkg_gen_paxos_repo = https://github.com/gburd/gen_paxos -pkg_gen_paxos_commit = master - -PACKAGES += gen_rpc -pkg_gen_rpc_name = gen_rpc -pkg_gen_rpc_description = A scalable RPC library for Erlang-VM based languages -pkg_gen_rpc_homepage = https://github.com/priestjim/gen_rpc.git -pkg_gen_rpc_fetch = git -pkg_gen_rpc_repo = https://github.com/priestjim/gen_rpc.git -pkg_gen_rpc_commit = master - -PACKAGES += gen_smtp -pkg_gen_smtp_name = gen_smtp -pkg_gen_smtp_description = A generic Erlang SMTP server and client that can be extended via callback modules -pkg_gen_smtp_homepage = https://github.com/Vagabond/gen_smtp -pkg_gen_smtp_fetch = git -pkg_gen_smtp_repo = https://github.com/Vagabond/gen_smtp -pkg_gen_smtp_commit = master - -PACKAGES += gen_tracker -pkg_gen_tracker_name = gen_tracker -pkg_gen_tracker_description = supervisor with ets handling of children and their metadata -pkg_gen_tracker_homepage = https://github.com/erlyvideo/gen_tracker -pkg_gen_tracker_fetch = git -pkg_gen_tracker_repo = https://github.com/erlyvideo/gen_tracker -pkg_gen_tracker_commit = master - -PACKAGES += gen_unix -pkg_gen_unix_name = gen_unix -pkg_gen_unix_description = Erlang Unix socket interface -pkg_gen_unix_homepage = https://github.com/msantos/gen_unix -pkg_gen_unix_fetch = git -pkg_gen_unix_repo = https://github.com/msantos/gen_unix -pkg_gen_unix_commit = master - -PACKAGES += geode -pkg_geode_name = geode -pkg_geode_description = geohash/proximity lookup in pure, uncut erlang. -pkg_geode_homepage = https://github.com/bradfordw/geode -pkg_geode_fetch = git -pkg_geode_repo = https://github.com/bradfordw/geode -pkg_geode_commit = master - -PACKAGES += getopt -pkg_getopt_name = getopt -pkg_getopt_description = Module to parse command line arguments using the GNU getopt syntax -pkg_getopt_homepage = https://github.com/jcomellas/getopt -pkg_getopt_fetch = git -pkg_getopt_repo = https://github.com/jcomellas/getopt -pkg_getopt_commit = master - -PACKAGES += gettext -pkg_gettext_name = gettext -pkg_gettext_description = Erlang internationalization library. -pkg_gettext_homepage = https://github.com/etnt/gettext -pkg_gettext_fetch = git -pkg_gettext_repo = https://github.com/etnt/gettext -pkg_gettext_commit = master - -PACKAGES += giallo -pkg_giallo_name = giallo -pkg_giallo_description = Small and flexible web framework on top of Cowboy -pkg_giallo_homepage = https://github.com/kivra/giallo -pkg_giallo_fetch = git -pkg_giallo_repo = https://github.com/kivra/giallo -pkg_giallo_commit = master - -PACKAGES += gin -pkg_gin_name = gin -pkg_gin_description = The guards and for Erlang parse_transform -pkg_gin_homepage = https://github.com/mad-cocktail/gin -pkg_gin_fetch = git -pkg_gin_repo = https://github.com/mad-cocktail/gin -pkg_gin_commit = master - -PACKAGES += gitty -pkg_gitty_name = gitty -pkg_gitty_description = Git access in erlang -pkg_gitty_homepage = https://github.com/maxlapshin/gitty -pkg_gitty_fetch = git -pkg_gitty_repo = https://github.com/maxlapshin/gitty -pkg_gitty_commit = master - PACKAGES += gpb pkg_gpb_name = gpb pkg_gpb_description = A Google Protobuf implementation for Erlang @@ -1778,38 +339,6 @@ pkg_gpb_fetch = git pkg_gpb_repo = https://github.com/tomas-abrahamsson/gpb pkg_gpb_commit = master -PACKAGES += gproc -pkg_gproc_name = gproc -pkg_gproc_description = Extended process registry for Erlang -pkg_gproc_homepage = https://github.com/uwiger/gproc -pkg_gproc_fetch = git -pkg_gproc_repo = https://github.com/uwiger/gproc -pkg_gproc_commit = master - -PACKAGES += grapherl -pkg_grapherl_name = grapherl -pkg_grapherl_description = Create graphs of Erlang systems and programs -pkg_grapherl_homepage = https://github.com/eproxus/grapherl -pkg_grapherl_fetch = git -pkg_grapherl_repo = https://github.com/eproxus/grapherl -pkg_grapherl_commit = master - -PACKAGES += grpc -pkg_grpc_name = grpc -pkg_grpc_description = gRPC server in Erlang -pkg_grpc_homepage = https://github.com/Bluehouse-Technology/grpc -pkg_grpc_fetch = git -pkg_grpc_repo = https://github.com/Bluehouse-Technology/grpc -pkg_grpc_commit = master - -PACKAGES += grpc_client -pkg_grpc_client_name = grpc_client -pkg_grpc_client_description = gRPC client in Erlang -pkg_grpc_client_homepage = https://github.com/Bluehouse-Technology/grpc_client -pkg_grpc_client_fetch = git -pkg_grpc_client_repo = https://github.com/Bluehouse-Technology/grpc_client -pkg_grpc_client_commit = master - PACKAGES += gun pkg_gun_name = gun pkg_gun_description = Asynchronous SPDY, HTTP and Websocket client written in Erlang. @@ -1818,861 +347,14 @@ pkg_gun_fetch = git pkg_gun_repo = https://github.com/ninenines/gun pkg_gun_commit = master -PACKAGES += hackney -pkg_hackney_name = hackney -pkg_hackney_description = simple HTTP client in Erlang -pkg_hackney_homepage = https://github.com/benoitc/hackney -pkg_hackney_fetch = git -pkg_hackney_repo = https://github.com/benoitc/hackney -pkg_hackney_commit = master - -PACKAGES += hamcrest -pkg_hamcrest_name = hamcrest -pkg_hamcrest_description = Erlang port of Hamcrest -pkg_hamcrest_homepage = https://github.com/hyperthunk/hamcrest-erlang -pkg_hamcrest_fetch = git -pkg_hamcrest_repo = https://github.com/hyperthunk/hamcrest-erlang -pkg_hamcrest_commit = master - -PACKAGES += hottub -pkg_hottub_name = hottub -pkg_hottub_description = Permanent Erlang Worker Pool -pkg_hottub_homepage = https://github.com/bfrog/hottub -pkg_hottub_fetch = git -pkg_hottub_repo = https://github.com/bfrog/hottub -pkg_hottub_commit = master - -PACKAGES += hpack -pkg_hpack_name = hpack -pkg_hpack_description = HPACK Implementation for Erlang -pkg_hpack_homepage = https://github.com/joedevivo/hpack -pkg_hpack_fetch = git -pkg_hpack_repo = https://github.com/joedevivo/hpack -pkg_hpack_commit = master - -PACKAGES += hyper -pkg_hyper_name = hyper -pkg_hyper_description = Erlang implementation of HyperLogLog -pkg_hyper_homepage = https://github.com/GameAnalytics/hyper -pkg_hyper_fetch = git -pkg_hyper_repo = https://github.com/GameAnalytics/hyper -pkg_hyper_commit = master - -PACKAGES += i18n -pkg_i18n_name = i18n -pkg_i18n_description = International components for unicode from Erlang (unicode, date, string, number, format, locale, localization, transliteration, icu4e) -pkg_i18n_homepage = https://github.com/erlang-unicode/i18n -pkg_i18n_fetch = git -pkg_i18n_repo = https://github.com/erlang-unicode/i18n -pkg_i18n_commit = master - -PACKAGES += ibrowse -pkg_ibrowse_name = ibrowse -pkg_ibrowse_description = Erlang HTTP client -pkg_ibrowse_homepage = https://github.com/cmullaparthi/ibrowse -pkg_ibrowse_fetch = git -pkg_ibrowse_repo = https://github.com/cmullaparthi/ibrowse -pkg_ibrowse_commit = master - -PACKAGES += idna -pkg_idna_name = idna -pkg_idna_description = Erlang IDNA lib -pkg_idna_homepage = https://github.com/benoitc/erlang-idna -pkg_idna_fetch = git -pkg_idna_repo = https://github.com/benoitc/erlang-idna -pkg_idna_commit = master - -PACKAGES += irc_lib -pkg_irc_lib_name = irc_lib -pkg_irc_lib_description = Erlang irc client library -pkg_irc_lib_homepage = https://github.com/OtpChatBot/irc_lib -pkg_irc_lib_fetch = git -pkg_irc_lib_repo = https://github.com/OtpChatBot/irc_lib -pkg_irc_lib_commit = master - -PACKAGES += ircd -pkg_ircd_name = ircd -pkg_ircd_description = A pluggable IRC daemon application/library for Erlang. -pkg_ircd_homepage = https://github.com/tonyg/erlang-ircd -pkg_ircd_fetch = git -pkg_ircd_repo = https://github.com/tonyg/erlang-ircd -pkg_ircd_commit = master - -PACKAGES += iris -pkg_iris_name = iris -pkg_iris_description = Iris Erlang binding -pkg_iris_homepage = https://github.com/project-iris/iris-erl -pkg_iris_fetch = git -pkg_iris_repo = https://github.com/project-iris/iris-erl -pkg_iris_commit = master - -PACKAGES += iso8601 -pkg_iso8601_name = iso8601 -pkg_iso8601_description = Erlang ISO 8601 date formatter/parser -pkg_iso8601_homepage = https://github.com/seansawyer/erlang_iso8601 -pkg_iso8601_fetch = git -pkg_iso8601_repo = https://github.com/seansawyer/erlang_iso8601 -pkg_iso8601_commit = master - -PACKAGES += jamdb_sybase -pkg_jamdb_sybase_name = jamdb_sybase -pkg_jamdb_sybase_description = Erlang driver for SAP Sybase ASE -pkg_jamdb_sybase_homepage = https://github.com/erlangbureau/jamdb_sybase -pkg_jamdb_sybase_fetch = git -pkg_jamdb_sybase_repo = https://github.com/erlangbureau/jamdb_sybase -pkg_jamdb_sybase_commit = master - -PACKAGES += jesse -pkg_jesse_name = jesse -pkg_jesse_description = jesse (JSon Schema Erlang) is an implementation of a json schema validator for Erlang. -pkg_jesse_homepage = https://github.com/for-GET/jesse -pkg_jesse_fetch = git -pkg_jesse_repo = https://github.com/for-GET/jesse -pkg_jesse_commit = master - -PACKAGES += jiffy -pkg_jiffy_name = jiffy -pkg_jiffy_description = JSON NIFs for Erlang. -pkg_jiffy_homepage = https://github.com/davisp/jiffy -pkg_jiffy_fetch = git -pkg_jiffy_repo = https://github.com/davisp/jiffy -pkg_jiffy_commit = master - -PACKAGES += jiffy_v -pkg_jiffy_v_name = jiffy_v -pkg_jiffy_v_description = JSON validation utility -pkg_jiffy_v_homepage = https://github.com/shizzard/jiffy-v -pkg_jiffy_v_fetch = git -pkg_jiffy_v_repo = https://github.com/shizzard/jiffy-v -pkg_jiffy_v_commit = master - -PACKAGES += jobs -pkg_jobs_name = jobs -pkg_jobs_description = Job scheduler for load regulation -pkg_jobs_homepage = https://github.com/uwiger/jobs -pkg_jobs_fetch = git -pkg_jobs_repo = https://github.com/uwiger/jobs -pkg_jobs_commit = master - -PACKAGES += joxa -pkg_joxa_name = joxa -pkg_joxa_description = A Modern Lisp for the Erlang VM -pkg_joxa_homepage = https://github.com/joxa/joxa -pkg_joxa_fetch = git -pkg_joxa_repo = https://github.com/joxa/joxa -pkg_joxa_commit = master - -PACKAGES += json_rec -pkg_json_rec_name = json_rec -pkg_json_rec_description = JSON to erlang record -pkg_json_rec_homepage = https://github.com/justinkirby/json_rec -pkg_json_rec_fetch = git -pkg_json_rec_repo = https://github.com/justinkirby/json_rec -pkg_json_rec_commit = master - -PACKAGES += jsone -pkg_jsone_name = jsone -pkg_jsone_description = An Erlang library for encoding, decoding JSON data. -pkg_jsone_homepage = https://github.com/sile/jsone.git -pkg_jsone_fetch = git -pkg_jsone_repo = https://github.com/sile/jsone.git -pkg_jsone_commit = master - -PACKAGES += jsonpath -pkg_jsonpath_name = jsonpath -pkg_jsonpath_description = Fast Erlang JSON data retrieval and updates via javascript-like notation -pkg_jsonpath_homepage = https://github.com/GeneStevens/jsonpath -pkg_jsonpath_fetch = git -pkg_jsonpath_repo = https://github.com/GeneStevens/jsonpath -pkg_jsonpath_commit = master - -PACKAGES += jsonx -pkg_jsonx_name = jsonx -pkg_jsonx_description = JSONX is an Erlang library for efficient decode and encode JSON, written in C. -pkg_jsonx_homepage = https://github.com/iskra/jsonx -pkg_jsonx_fetch = git -pkg_jsonx_repo = https://github.com/iskra/jsonx -pkg_jsonx_commit = master - -PACKAGES += jsx -pkg_jsx_name = jsx -pkg_jsx_description = An Erlang application for consuming, producing and manipulating JSON. -pkg_jsx_homepage = https://github.com/talentdeficit/jsx -pkg_jsx_fetch = git -pkg_jsx_repo = https://github.com/talentdeficit/jsx -pkg_jsx_commit = main - -PACKAGES += kafka_protocol -pkg_kafka_protocol_name = kafka_protocol -pkg_kafka_protocol_description = Kafka protocol Erlang library -pkg_kafka_protocol_homepage = https://github.com/kafka4beam/kafka_protocol -pkg_kafka_protocol_fetch = git -pkg_kafka_protocol_repo = https://github.com/kafka4beam/kafka_protocol -pkg_kafka_protocol_commit = master - -PACKAGES += kai -pkg_kai_name = kai -pkg_kai_description = DHT storage by Takeshi Inoue -pkg_kai_homepage = https://github.com/synrc/kai -pkg_kai_fetch = git -pkg_kai_repo = https://github.com/synrc/kai -pkg_kai_commit = master - -PACKAGES += katja -pkg_katja_name = katja -pkg_katja_description = A simple Riemann client written in Erlang. -pkg_katja_homepage = https://github.com/nifoc/katja -pkg_katja_fetch = git -pkg_katja_repo = https://github.com/nifoc/katja -pkg_katja_commit = master - -PACKAGES += key2value -pkg_key2value_name = key2value -pkg_key2value_description = Erlang 2-way map -pkg_key2value_homepage = https://github.com/okeuday/key2value -pkg_key2value_fetch = git -pkg_key2value_repo = https://github.com/okeuday/key2value -pkg_key2value_commit = master - -PACKAGES += keys1value -pkg_keys1value_name = keys1value -pkg_keys1value_description = Erlang set associative map for key lists -pkg_keys1value_homepage = https://github.com/okeuday/keys1value -pkg_keys1value_fetch = git -pkg_keys1value_repo = https://github.com/okeuday/keys1value -pkg_keys1value_commit = master - -PACKAGES += kinetic -pkg_kinetic_name = kinetic -pkg_kinetic_description = Erlang Kinesis Client -pkg_kinetic_homepage = https://github.com/AdRoll/kinetic -pkg_kinetic_fetch = git -pkg_kinetic_repo = https://github.com/AdRoll/kinetic -pkg_kinetic_commit = main - -PACKAGES += kjell -pkg_kjell_name = kjell -pkg_kjell_description = Erlang Shell -pkg_kjell_homepage = https://github.com/karlll/kjell -pkg_kjell_fetch = git -pkg_kjell_repo = https://github.com/karlll/kjell -pkg_kjell_commit = master - -PACKAGES += kraken -pkg_kraken_name = kraken -pkg_kraken_description = Distributed Pubsub Server for Realtime Apps -pkg_kraken_homepage = https://github.com/Asana/kraken -pkg_kraken_fetch = git -pkg_kraken_repo = https://github.com/Asana/kraken -pkg_kraken_commit = master - -PACKAGES += kucumberl -pkg_kucumberl_name = kucumberl -pkg_kucumberl_description = A pure-erlang, open-source, implementation of Cucumber -pkg_kucumberl_homepage = https://github.com/openshine/kucumberl -pkg_kucumberl_fetch = git -pkg_kucumberl_repo = https://github.com/openshine/kucumberl -pkg_kucumberl_commit = master - -PACKAGES += kvc -pkg_kvc_name = kvc -pkg_kvc_description = KVC - Key Value Coding for Erlang data structures -pkg_kvc_homepage = https://github.com/etrepum/kvc -pkg_kvc_fetch = git -pkg_kvc_repo = https://github.com/etrepum/kvc -pkg_kvc_commit = master - -PACKAGES += kvlists -pkg_kvlists_name = kvlists -pkg_kvlists_description = Lists of key-value pairs (decoded JSON) in Erlang -pkg_kvlists_homepage = https://github.com/jcomellas/kvlists -pkg_kvlists_fetch = git -pkg_kvlists_repo = https://github.com/jcomellas/kvlists -pkg_kvlists_commit = master - -PACKAGES += kvs -pkg_kvs_name = kvs -pkg_kvs_description = Container and Iterator -pkg_kvs_homepage = https://github.com/synrc/kvs -pkg_kvs_fetch = git -pkg_kvs_repo = https://github.com/synrc/kvs -pkg_kvs_commit = master - -PACKAGES += lager -pkg_lager_name = lager -pkg_lager_description = A logging framework for Erlang/OTP. -pkg_lager_homepage = https://github.com/erlang-lager/lager -pkg_lager_fetch = git -pkg_lager_repo = https://github.com/erlang-lager/lager -pkg_lager_commit = master - -PACKAGES += lager_syslog -pkg_lager_syslog_name = lager_syslog -pkg_lager_syslog_description = Syslog backend for lager -pkg_lager_syslog_homepage = https://github.com/erlang-lager/lager_syslog -pkg_lager_syslog_fetch = git -pkg_lager_syslog_repo = https://github.com/erlang-lager/lager_syslog -pkg_lager_syslog_commit = master - -PACKAGES += lasse -pkg_lasse_name = lasse -pkg_lasse_description = SSE handler for Cowboy -pkg_lasse_homepage = https://github.com/inaka/lasse -pkg_lasse_fetch = git -pkg_lasse_repo = https://github.com/inaka/lasse -pkg_lasse_commit = master - -PACKAGES += ldap -pkg_ldap_name = ldap -pkg_ldap_description = LDAP server written in Erlang -pkg_ldap_homepage = https://github.com/spawnproc/ldap -pkg_ldap_fetch = git -pkg_ldap_repo = https://github.com/spawnproc/ldap -pkg_ldap_commit = master - -PACKAGES += lfe -pkg_lfe_name = lfe -pkg_lfe_description = Lisp Flavoured Erlang (LFE) -pkg_lfe_homepage = https://github.com/rvirding/lfe -pkg_lfe_fetch = git -pkg_lfe_repo = https://github.com/rvirding/lfe -pkg_lfe_commit = master - -PACKAGES += live -pkg_live_name = live -pkg_live_description = Automated module and configuration reloader. -pkg_live_homepage = http://ninenines.eu -pkg_live_fetch = git -pkg_live_repo = https://github.com/ninenines/live -pkg_live_commit = master - -PACKAGES += locker -pkg_locker_name = locker -pkg_locker_description = Atomic distributed 'check and set' for short-lived keys -pkg_locker_homepage = https://github.com/wooga/locker -pkg_locker_fetch = git -pkg_locker_repo = https://github.com/wooga/locker -pkg_locker_commit = master - -PACKAGES += locks -pkg_locks_name = locks -pkg_locks_description = A scalable, deadlock-resolving resource locker -pkg_locks_homepage = https://github.com/uwiger/locks -pkg_locks_fetch = git -pkg_locks_repo = https://github.com/uwiger/locks -pkg_locks_commit = master - -PACKAGES += log4erl -pkg_log4erl_name = log4erl -pkg_log4erl_description = A logger for erlang in the spirit of Log4J. -pkg_log4erl_homepage = https://github.com/ahmednawras/log4erl -pkg_log4erl_fetch = git -pkg_log4erl_repo = https://github.com/ahmednawras/log4erl -pkg_log4erl_commit = master - -PACKAGES += lol -pkg_lol_name = lol -pkg_lol_description = Lisp on erLang, and programming is fun again -pkg_lol_homepage = https://github.com/b0oh/lol -pkg_lol_fetch = git -pkg_lol_repo = https://github.com/b0oh/lol -pkg_lol_commit = master - -PACKAGES += lucid -pkg_lucid_name = lucid -pkg_lucid_description = HTTP/2 server written in Erlang -pkg_lucid_homepage = https://github.com/tatsuhiro-t/lucid -pkg_lucid_fetch = git -pkg_lucid_repo = https://github.com/tatsuhiro-t/lucid -pkg_lucid_commit = master - -PACKAGES += luerl -pkg_luerl_name = luerl -pkg_luerl_description = Lua in Erlang -pkg_luerl_homepage = https://github.com/rvirding/luerl -pkg_luerl_fetch = git -pkg_luerl_repo = https://github.com/rvirding/luerl -pkg_luerl_commit = develop - -PACKAGES += lux -pkg_lux_name = lux -pkg_lux_description = Lux (LUcid eXpect scripting) simplifies test automation and provides an Expect-style execution of commands -pkg_lux_homepage = https://github.com/hawk/lux -pkg_lux_fetch = git -pkg_lux_repo = https://github.com/hawk/lux -pkg_lux_commit = master - -PACKAGES += mad -pkg_mad_name = mad -pkg_mad_description = Small and Fast Rebar Replacement -pkg_mad_homepage = https://github.com/synrc/mad -pkg_mad_fetch = git -pkg_mad_repo = https://github.com/synrc/mad -pkg_mad_commit = master - -PACKAGES += marina -pkg_marina_name = marina -pkg_marina_description = Non-blocking Erlang Cassandra CQL3 client -pkg_marina_homepage = https://github.com/lpgauth/marina -pkg_marina_fetch = git -pkg_marina_repo = https://github.com/lpgauth/marina -pkg_marina_commit = master - -PACKAGES += mavg -pkg_mavg_name = mavg -pkg_mavg_description = Erlang :: Exponential moving average library -pkg_mavg_homepage = https://github.com/EchoTeam/mavg -pkg_mavg_fetch = git -pkg_mavg_repo = https://github.com/EchoTeam/mavg -pkg_mavg_commit = master - -PACKAGES += meck -pkg_meck_name = meck -pkg_meck_description = A mocking library for Erlang -pkg_meck_homepage = https://github.com/eproxus/meck -pkg_meck_fetch = git -pkg_meck_repo = https://github.com/eproxus/meck -pkg_meck_commit = master - -PACKAGES += mekao -pkg_mekao_name = mekao -pkg_mekao_description = SQL constructor -pkg_mekao_homepage = https://github.com/ddosia/mekao -pkg_mekao_fetch = git -pkg_mekao_repo = https://github.com/ddosia/mekao -pkg_mekao_commit = master - -PACKAGES += merl -pkg_merl_name = merl -pkg_merl_description = Metaprogramming in Erlang -pkg_merl_homepage = https://github.com/richcarl/merl -pkg_merl_fetch = git -pkg_merl_repo = https://github.com/richcarl/merl -pkg_merl_commit = master - -PACKAGES += mimerl -pkg_mimerl_name = mimerl -pkg_mimerl_description = library to handle mimetypes -pkg_mimerl_homepage = https://github.com/benoitc/mimerl -pkg_mimerl_fetch = git -pkg_mimerl_repo = https://github.com/benoitc/mimerl -pkg_mimerl_commit = master - -PACKAGES += mimetypes -pkg_mimetypes_name = mimetypes -pkg_mimetypes_description = Erlang MIME types library -pkg_mimetypes_homepage = https://github.com/spawngrid/mimetypes -pkg_mimetypes_fetch = git -pkg_mimetypes_repo = https://github.com/spawngrid/mimetypes -pkg_mimetypes_commit = master - -PACKAGES += mixer -pkg_mixer_name = mixer -pkg_mixer_description = Mix in functions from other modules -pkg_mixer_homepage = https://github.com/chef/mixer -pkg_mixer_fetch = git -pkg_mixer_repo = https://github.com/chef/mixer -pkg_mixer_commit = main - -PACKAGES += mochiweb -pkg_mochiweb_name = mochiweb -pkg_mochiweb_description = MochiWeb is an Erlang library for building lightweight HTTP servers. -pkg_mochiweb_homepage = https://github.com/mochi/mochiweb -pkg_mochiweb_fetch = git -pkg_mochiweb_repo = https://github.com/mochi/mochiweb -pkg_mochiweb_commit = main - -PACKAGES += mochiweb_xpath -pkg_mochiweb_xpath_name = mochiweb_xpath -pkg_mochiweb_xpath_description = XPath support for mochiweb's html parser -pkg_mochiweb_xpath_homepage = https://github.com/retnuh/mochiweb_xpath -pkg_mochiweb_xpath_fetch = git -pkg_mochiweb_xpath_repo = https://github.com/retnuh/mochiweb_xpath -pkg_mochiweb_xpath_commit = master - -PACKAGES += mockgyver -pkg_mockgyver_name = mockgyver -pkg_mockgyver_description = A mocking library for Erlang -pkg_mockgyver_homepage = https://github.com/klajo/mockgyver -pkg_mockgyver_fetch = git -pkg_mockgyver_repo = https://github.com/klajo/mockgyver -pkg_mockgyver_commit = master - -PACKAGES += modlib -pkg_modlib_name = modlib -pkg_modlib_description = Web framework based on Erlang's inets httpd -pkg_modlib_homepage = https://github.com/gar1t/modlib -pkg_modlib_fetch = git -pkg_modlib_repo = https://github.com/gar1t/modlib -pkg_modlib_commit = master - -PACKAGES += mongodb -pkg_mongodb_name = mongodb -pkg_mongodb_description = MongoDB driver for Erlang -pkg_mongodb_homepage = https://github.com/comtihon/mongodb-erlang -pkg_mongodb_fetch = git -pkg_mongodb_repo = https://github.com/comtihon/mongodb-erlang -pkg_mongodb_commit = master - -PACKAGES += mongooseim -pkg_mongooseim_name = mongooseim -pkg_mongooseim_description = Jabber / XMPP server with focus on performance and scalability, by Erlang Solutions -pkg_mongooseim_homepage = https://www.erlang-solutions.com/products/mongooseim-massively-scalable-ejabberd-platform -pkg_mongooseim_fetch = git -pkg_mongooseim_repo = https://github.com/esl/MongooseIM -pkg_mongooseim_commit = master - -PACKAGES += moyo -pkg_moyo_name = moyo -pkg_moyo_description = Erlang utility functions library -pkg_moyo_homepage = https://github.com/dwango/moyo -pkg_moyo_fetch = git -pkg_moyo_repo = https://github.com/dwango/moyo -pkg_moyo_commit = master - -PACKAGES += msgpack -pkg_msgpack_name = msgpack -pkg_msgpack_description = MessagePack (de)serializer implementation for Erlang -pkg_msgpack_homepage = https://github.com/msgpack/msgpack-erlang -pkg_msgpack_fetch = git -pkg_msgpack_repo = https://github.com/msgpack/msgpack-erlang -pkg_msgpack_commit = master - -PACKAGES += mu2 -pkg_mu2_name = mu2 -pkg_mu2_description = Erlang mutation testing tool -pkg_mu2_homepage = https://github.com/ramsay-t/mu2 -pkg_mu2_fetch = git -pkg_mu2_repo = https://github.com/ramsay-t/mu2 -pkg_mu2_commit = master - -PACKAGES += mustache -pkg_mustache_name = mustache -pkg_mustache_description = Mustache template engine for Erlang. -pkg_mustache_homepage = https://github.com/mojombo/mustache.erl -pkg_mustache_fetch = git -pkg_mustache_repo = https://github.com/mojombo/mustache.erl -pkg_mustache_commit = master - -PACKAGES += myproto -pkg_myproto_name = myproto -pkg_myproto_description = MySQL Server Protocol in Erlang -pkg_myproto_homepage = https://github.com/altenwald/myproto -pkg_myproto_fetch = git -pkg_myproto_repo = https://github.com/altenwald/myproto -pkg_myproto_commit = master - -PACKAGES += mysql -pkg_mysql_name = mysql -pkg_mysql_description = MySQL client library for Erlang/OTP -pkg_mysql_homepage = https://github.com/mysql-otp/mysql-otp -pkg_mysql_fetch = git -pkg_mysql_repo = https://github.com/mysql-otp/mysql-otp -pkg_mysql_commit = 1.7.0 - -PACKAGES += n2o -pkg_n2o_name = n2o -pkg_n2o_description = WebSocket Application Server -pkg_n2o_homepage = https://github.com/5HT/n2o -pkg_n2o_fetch = git -pkg_n2o_repo = https://github.com/5HT/n2o -pkg_n2o_commit = master - -PACKAGES += nat_upnp -pkg_nat_upnp_name = nat_upnp -pkg_nat_upnp_description = Erlang library to map your internal port to an external using UNP IGD -pkg_nat_upnp_homepage = https://github.com/benoitc/nat_upnp -pkg_nat_upnp_fetch = git -pkg_nat_upnp_repo = https://github.com/benoitc/nat_upnp -pkg_nat_upnp_commit = master - -PACKAGES += neo4j -pkg_neo4j_name = neo4j -pkg_neo4j_description = Erlang client library for Neo4J. -pkg_neo4j_homepage = https://github.com/dmitriid/neo4j-erlang -pkg_neo4j_fetch = git -pkg_neo4j_repo = https://github.com/dmitriid/neo4j-erlang -pkg_neo4j_commit = master - -PACKAGES += neotoma -pkg_neotoma_name = neotoma -pkg_neotoma_description = Erlang library and packrat parser-generator for parsing expression grammars. -pkg_neotoma_homepage = https://github.com/seancribbs/neotoma -pkg_neotoma_fetch = git -pkg_neotoma_repo = https://github.com/seancribbs/neotoma -pkg_neotoma_commit = master - -PACKAGES += nifty -pkg_nifty_name = nifty -pkg_nifty_description = Erlang NIF wrapper generator -pkg_nifty_homepage = https://github.com/parapluu/nifty -pkg_nifty_fetch = git -pkg_nifty_repo = https://github.com/parapluu/nifty -pkg_nifty_commit = master - -PACKAGES += nitrogen_core -pkg_nitrogen_core_name = nitrogen_core -pkg_nitrogen_core_description = The core Nitrogen library. -pkg_nitrogen_core_homepage = http://nitrogenproject.com/ -pkg_nitrogen_core_fetch = git -pkg_nitrogen_core_repo = https://github.com/nitrogen/nitrogen_core -pkg_nitrogen_core_commit = master - -PACKAGES += nkpacket -pkg_nkpacket_name = nkpacket -pkg_nkpacket_description = Generic Erlang transport layer -pkg_nkpacket_homepage = https://github.com/Nekso/nkpacket -pkg_nkpacket_fetch = git -pkg_nkpacket_repo = https://github.com/Nekso/nkpacket -pkg_nkpacket_commit = master - -PACKAGES += nksip -pkg_nksip_name = nksip -pkg_nksip_description = Erlang SIP application server -pkg_nksip_homepage = https://github.com/kalta/nksip -pkg_nksip_fetch = git -pkg_nksip_repo = https://github.com/kalta/nksip -pkg_nksip_commit = master - -PACKAGES += nodefinder -pkg_nodefinder_name = nodefinder -pkg_nodefinder_description = automatic node discovery via UDP multicast -pkg_nodefinder_homepage = https://github.com/erlanger/nodefinder -pkg_nodefinder_fetch = git -pkg_nodefinder_repo = https://github.com/okeuday/nodefinder -pkg_nodefinder_commit = master - -PACKAGES += nprocreg -pkg_nprocreg_name = nprocreg -pkg_nprocreg_description = Minimal Distributed Erlang Process Registry -pkg_nprocreg_homepage = http://nitrogenproject.com/ -pkg_nprocreg_fetch = git -pkg_nprocreg_repo = https://github.com/nitrogen/nprocreg -pkg_nprocreg_commit = master - -PACKAGES += oauth -pkg_oauth_name = oauth -pkg_oauth_description = An Erlang OAuth 1.0 implementation -pkg_oauth_homepage = https://github.com/tim/erlang-oauth -pkg_oauth_fetch = git -pkg_oauth_repo = https://github.com/tim/erlang-oauth -pkg_oauth_commit = main - -PACKAGES += oauth2 -pkg_oauth2_name = oauth2 -pkg_oauth2_description = Erlang Oauth2 implementation -pkg_oauth2_homepage = https://github.com/kivra/oauth2 -pkg_oauth2_fetch = git -pkg_oauth2_repo = https://github.com/kivra/oauth2 -pkg_oauth2_commit = master - -PACKAGES += observer_cli -pkg_observer_cli_name = observer_cli -pkg_observer_cli_description = Visualize Erlang/Elixir Nodes On The Command Line -pkg_observer_cli_homepage = http://zhongwencool.github.io/observer_cli -pkg_observer_cli_fetch = git -pkg_observer_cli_repo = https://github.com/zhongwencool/observer_cli -pkg_observer_cli_commit = master - -PACKAGES += octopus -pkg_octopus_name = octopus -pkg_octopus_description = Small and flexible pool manager written in Erlang -pkg_octopus_homepage = https://github.com/erlangbureau/octopus -pkg_octopus_fetch = git -pkg_octopus_repo = https://github.com/erlangbureau/octopus -pkg_octopus_commit = master - -PACKAGES += openflow -pkg_openflow_name = openflow -pkg_openflow_description = An OpenFlow controller written in pure erlang -pkg_openflow_homepage = https://github.com/renatoaguiar/erlang-openflow -pkg_openflow_fetch = git -pkg_openflow_repo = https://github.com/renatoaguiar/erlang-openflow -pkg_openflow_commit = master - -PACKAGES += openid -pkg_openid_name = openid -pkg_openid_description = Erlang OpenID -pkg_openid_homepage = https://github.com/brendonh/erl_openid -pkg_openid_fetch = git -pkg_openid_repo = https://github.com/brendonh/erl_openid -pkg_openid_commit = master - -PACKAGES += openpoker -pkg_openpoker_name = openpoker -pkg_openpoker_description = Genesis Texas hold'em Game Server -pkg_openpoker_homepage = https://github.com/hpyhacking/openpoker -pkg_openpoker_fetch = git -pkg_openpoker_repo = https://github.com/hpyhacking/openpoker -pkg_openpoker_commit = master - -PACKAGES += otpbp -pkg_otpbp_name = otpbp -pkg_otpbp_description = Parse transformer for use new OTP functions in old Erlang/OTP releases (R15, R16, 17, 18, 19) -pkg_otpbp_homepage = https://github.com/Ledest/otpbp -pkg_otpbp_fetch = git -pkg_otpbp_repo = https://github.com/Ledest/otpbp -pkg_otpbp_commit = master - -PACKAGES += pal -pkg_pal_name = pal -pkg_pal_description = Pragmatic Authentication Library -pkg_pal_homepage = https://github.com/manifest/pal -pkg_pal_fetch = git -pkg_pal_repo = https://github.com/manifest/pal -pkg_pal_commit = master - -PACKAGES += parse_trans -pkg_parse_trans_name = parse_trans -pkg_parse_trans_description = Parse transform utilities for Erlang -pkg_parse_trans_homepage = https://github.com/uwiger/parse_trans -pkg_parse_trans_fetch = git -pkg_parse_trans_repo = https://github.com/uwiger/parse_trans -pkg_parse_trans_commit = master - -PACKAGES += parsexml -pkg_parsexml_name = parsexml -pkg_parsexml_description = Simple DOM XML parser with convenient and very simple API -pkg_parsexml_homepage = https://github.com/maxlapshin/parsexml -pkg_parsexml_fetch = git -pkg_parsexml_repo = https://github.com/maxlapshin/parsexml -pkg_parsexml_commit = master - -PACKAGES += partisan -pkg_partisan_name = partisan -pkg_partisan_description = High-performance, high-scalability distributed computing with Erlang and Elixir. -pkg_partisan_homepage = http://partisan.cloud -pkg_partisan_fetch = git -pkg_partisan_repo = https://github.com/lasp-lang/partisan -pkg_partisan_commit = master - -PACKAGES += pegjs -pkg_pegjs_name = pegjs -pkg_pegjs_description = An implementation of PEG.js grammar for Erlang. -pkg_pegjs_homepage = https://github.com/dmitriid/pegjs -pkg_pegjs_fetch = git -pkg_pegjs_repo = https://github.com/dmitriid/pegjs -pkg_pegjs_commit = master - -PACKAGES += percept2 -pkg_percept2_name = percept2 -pkg_percept2_description = Concurrent profiling tool for Erlang -pkg_percept2_homepage = https://github.com/huiqing/percept2 -pkg_percept2_fetch = git -pkg_percept2_repo = https://github.com/huiqing/percept2 -pkg_percept2_commit = master - -PACKAGES += pgo -pkg_pgo_name = pgo -pkg_pgo_description = Erlang Postgres client and connection pool -pkg_pgo_homepage = https://github.com/erleans/pgo.git -pkg_pgo_fetch = git -pkg_pgo_repo = https://github.com/erleans/pgo.git -pkg_pgo_commit = main - -PACKAGES += pgsql -pkg_pgsql_name = pgsql -pkg_pgsql_description = Erlang PostgreSQL driver -pkg_pgsql_homepage = https://github.com/semiocast/pgsql -pkg_pgsql_fetch = git -pkg_pgsql_repo = https://github.com/semiocast/pgsql -pkg_pgsql_commit = master - -PACKAGES += pkgx -pkg_pkgx_name = pkgx -pkg_pkgx_description = Build .deb packages from Erlang releases -pkg_pkgx_homepage = https://github.com/arjan/pkgx -pkg_pkgx_fetch = git -pkg_pkgx_repo = https://github.com/arjan/pkgx -pkg_pkgx_commit = master - -PACKAGES += pkt -pkg_pkt_name = pkt -pkg_pkt_description = Erlang network protocol library -pkg_pkt_homepage = https://github.com/msantos/pkt -pkg_pkt_fetch = git -pkg_pkt_repo = https://github.com/msantos/pkt -pkg_pkt_commit = master - -PACKAGES += plain_fsm -pkg_plain_fsm_name = plain_fsm -pkg_plain_fsm_description = A behaviour/support library for writing plain Erlang FSMs. -pkg_plain_fsm_homepage = https://github.com/uwiger/plain_fsm -pkg_plain_fsm_fetch = git -pkg_plain_fsm_repo = https://github.com/uwiger/plain_fsm -pkg_plain_fsm_commit = master - -PACKAGES += pmod_transform -pkg_pmod_transform_name = pmod_transform -pkg_pmod_transform_description = Parse transform for parameterized modules -pkg_pmod_transform_homepage = https://github.com/erlang/pmod_transform -pkg_pmod_transform_fetch = git -pkg_pmod_transform_repo = https://github.com/erlang/pmod_transform -pkg_pmod_transform_commit = master - -PACKAGES += pobox -pkg_pobox_name = pobox -pkg_pobox_description = External buffer processes to protect against mailbox overflow in Erlang -pkg_pobox_homepage = https://github.com/ferd/pobox -pkg_pobox_fetch = git -pkg_pobox_repo = https://github.com/ferd/pobox -pkg_pobox_commit = master - -PACKAGES += ponos -pkg_ponos_name = ponos -pkg_ponos_description = ponos is a simple yet powerful load generator written in erlang -pkg_ponos_homepage = https://github.com/klarna/ponos -pkg_ponos_fetch = git -pkg_ponos_repo = https://github.com/klarna/ponos -pkg_ponos_commit = master - -PACKAGES += poolboy -pkg_poolboy_name = poolboy -pkg_poolboy_description = A hunky Erlang worker pool factory -pkg_poolboy_homepage = https://github.com/devinus/poolboy -pkg_poolboy_fetch = git -pkg_poolboy_repo = https://github.com/devinus/poolboy -pkg_poolboy_commit = master - -PACKAGES += pooler -pkg_pooler_name = pooler -pkg_pooler_description = An OTP Process Pool Application -pkg_pooler_homepage = https://github.com/seth/pooler -pkg_pooler_fetch = git -pkg_pooler_repo = https://github.com/seth/pooler -pkg_pooler_commit = master - -PACKAGES += pqueue -pkg_pqueue_name = pqueue -pkg_pqueue_description = Erlang Priority Queues -pkg_pqueue_homepage = https://github.com/okeuday/pqueue -pkg_pqueue_fetch = git -pkg_pqueue_repo = https://github.com/okeuday/pqueue -pkg_pqueue_commit = master - -PACKAGES += procket -pkg_procket_name = procket -pkg_procket_description = Erlang interface to low level socket operations -pkg_procket_homepage = http://blog.listincomprehension.com/search/label/procket -pkg_procket_fetch = git -pkg_procket_repo = https://github.com/msantos/procket -pkg_procket_commit = master - -PACKAGES += prometheus -pkg_prometheus_name = prometheus -pkg_prometheus_description = Prometheus.io client in Erlang -pkg_prometheus_homepage = https://github.com/deadtrickster/prometheus.erl -pkg_prometheus_fetch = git -pkg_prometheus_repo = https://github.com/deadtrickster/prometheus.erl -pkg_prometheus_commit = master - -PACKAGES += prop -pkg_prop_name = prop -pkg_prop_description = An Erlang code scaffolding and generator system. -pkg_prop_homepage = https://github.com/nuex/prop -pkg_prop_fetch = git -pkg_prop_repo = https://github.com/nuex/prop -pkg_prop_commit = master +PACKAGES += hex_core +pkg_hex_core_name = hex_core +pkg_hex_core_description = Reference implementation of Hex specifications +pkg_hex_core_homepage = https://github.com/hexpm/hex_core +pkg_hex_core_fetch = git +HEX_CORE_GIT ?= https://github.com/hexpm/hex_core +pkg_hex_core_repo = $(HEX_CORE_GIT) +pkg_hex_core_commit = e57b4fb15cde710b3ae09b1d18f148f6999a63cc PACKAGES += proper pkg_proper_name = proper @@ -2682,181 +364,13 @@ pkg_proper_fetch = git pkg_proper_repo = https://github.com/manopapad/proper pkg_proper_commit = master -PACKAGES += props -pkg_props_name = props -pkg_props_description = Property structure library -pkg_props_homepage = https://github.com/greyarea/props -pkg_props_fetch = git -pkg_props_repo = https://github.com/greyarea/props -pkg_props_commit = master - -PACKAGES += protobuffs -pkg_protobuffs_name = protobuffs -pkg_protobuffs_description = An implementation of Google's Protocol Buffers for Erlang, based on ngerakines/erlang_protobuffs. -pkg_protobuffs_homepage = https://github.com/basho/erlang_protobuffs -pkg_protobuffs_fetch = git -pkg_protobuffs_repo = https://github.com/basho/erlang_protobuffs -pkg_protobuffs_commit = master - -PACKAGES += psycho -pkg_psycho_name = psycho -pkg_psycho_description = HTTP server that provides a WSGI-like interface for applications and middleware. -pkg_psycho_homepage = https://github.com/gar1t/psycho -pkg_psycho_fetch = git -pkg_psycho_repo = https://github.com/gar1t/psycho -pkg_psycho_commit = master - -PACKAGES += purity -pkg_purity_name = purity -pkg_purity_description = A side-effect analyzer for Erlang -pkg_purity_homepage = https://github.com/mpitid/purity -pkg_purity_fetch = git -pkg_purity_repo = https://github.com/mpitid/purity -pkg_purity_commit = master - -PACKAGES += qdate -pkg_qdate_name = qdate -pkg_qdate_description = Date, time, and timezone parsing, formatting, and conversion for Erlang. -pkg_qdate_homepage = https://github.com/choptastic/qdate -pkg_qdate_fetch = git -pkg_qdate_repo = https://github.com/choptastic/qdate -pkg_qdate_commit = master - -PACKAGES += qrcode -pkg_qrcode_name = qrcode -pkg_qrcode_description = QR Code encoder in Erlang -pkg_qrcode_homepage = https://github.com/komone/qrcode -pkg_qrcode_fetch = git -pkg_qrcode_repo = https://github.com/komone/qrcode -pkg_qrcode_commit = master - -PACKAGES += quest -pkg_quest_name = quest -pkg_quest_description = Learn Erlang through this set of challenges. An interactive system for getting to know Erlang. -pkg_quest_homepage = https://github.com/eriksoe/ErlangQuest -pkg_quest_fetch = git -pkg_quest_repo = https://github.com/eriksoe/ErlangQuest -pkg_quest_commit = master - -PACKAGES += quickrand -pkg_quickrand_name = quickrand -pkg_quickrand_description = Quick Erlang Random Number Generation -pkg_quickrand_homepage = https://github.com/okeuday/quickrand -pkg_quickrand_fetch = git -pkg_quickrand_repo = https://github.com/okeuday/quickrand -pkg_quickrand_commit = master - -PACKAGES += rabbit_exchange_type_riak -pkg_rabbit_exchange_type_riak_name = rabbit_exchange_type_riak -pkg_rabbit_exchange_type_riak_description = Custom RabbitMQ exchange type for sticking messages in Riak -pkg_rabbit_exchange_type_riak_homepage = https://github.com/jbrisbin/riak-exchange -pkg_rabbit_exchange_type_riak_fetch = git -pkg_rabbit_exchange_type_riak_repo = https://github.com/jbrisbin/riak-exchange -pkg_rabbit_exchange_type_riak_commit = master - -PACKAGES += rack -pkg_rack_name = rack -pkg_rack_description = Rack handler for erlang -pkg_rack_homepage = https://github.com/erlyvideo/rack -pkg_rack_fetch = git -pkg_rack_repo = https://github.com/erlyvideo/rack -pkg_rack_commit = master - -PACKAGES += radierl -pkg_radierl_name = radierl -pkg_radierl_description = RADIUS protocol stack implemented in Erlang. -pkg_radierl_homepage = https://github.com/vances/radierl -pkg_radierl_fetch = git -pkg_radierl_repo = https://github.com/vances/radierl -pkg_radierl_commit = master - PACKAGES += ranch pkg_ranch_name = ranch pkg_ranch_description = Socket acceptor pool for TCP protocols. pkg_ranch_homepage = http://ninenines.eu pkg_ranch_fetch = git pkg_ranch_repo = https://github.com/ninenines/ranch -pkg_ranch_commit = 1.2.1 - -PACKAGES += rbeacon -pkg_rbeacon_name = rbeacon -pkg_rbeacon_description = LAN discovery and presence in Erlang. -pkg_rbeacon_homepage = https://github.com/refuge/rbeacon -pkg_rbeacon_fetch = git -pkg_rbeacon_repo = https://github.com/refuge/rbeacon -pkg_rbeacon_commit = master - -PACKAGES += re2 -pkg_re2_name = re2 -pkg_re2_description = Erlang NIF bindings for RE2 regex library -pkg_re2_homepage = https://github.com/dukesoferl/re2 -pkg_re2_fetch = git -pkg_re2_repo = https://github.com/dukesoferl/re2 -pkg_re2_commit = master - -PACKAGES += rebus -pkg_rebus_name = rebus -pkg_rebus_description = A stupid simple, internal, pub/sub event bus written in- and for Erlang. -pkg_rebus_homepage = https://github.com/olle/rebus -pkg_rebus_fetch = git -pkg_rebus_repo = https://github.com/olle/rebus -pkg_rebus_commit = master - -PACKAGES += rec2json -pkg_rec2json_name = rec2json -pkg_rec2json_description = Compile erlang record definitions into modules to convert them to/from json easily. -pkg_rec2json_homepage = https://github.com/lordnull/rec2json -pkg_rec2json_fetch = git -pkg_rec2json_repo = https://github.com/lordnull/rec2json -pkg_rec2json_commit = master - -PACKAGES += recon -pkg_recon_name = recon -pkg_recon_description = Collection of functions and scripts to debug Erlang in production. -pkg_recon_homepage = https://github.com/ferd/recon -pkg_recon_fetch = git -pkg_recon_repo = https://github.com/ferd/recon -pkg_recon_commit = master - -PACKAGES += record_info -pkg_record_info_name = record_info -pkg_record_info_description = Convert between record and proplist -pkg_record_info_homepage = https://github.com/bipthelin/erlang-record_info -pkg_record_info_fetch = git -pkg_record_info_repo = https://github.com/bipthelin/erlang-record_info -pkg_record_info_commit = master - -PACKAGES += redgrid -pkg_redgrid_name = redgrid -pkg_redgrid_description = automatic Erlang node discovery via redis -pkg_redgrid_homepage = https://github.com/jkvor/redgrid -pkg_redgrid_fetch = git -pkg_redgrid_repo = https://github.com/jkvor/redgrid -pkg_redgrid_commit = master - -PACKAGES += redo -pkg_redo_name = redo -pkg_redo_description = pipelined erlang redis client -pkg_redo_homepage = https://github.com/jkvor/redo -pkg_redo_fetch = git -pkg_redo_repo = https://github.com/jkvor/redo -pkg_redo_commit = master - -PACKAGES += reload_mk -pkg_reload_mk_name = reload_mk -pkg_reload_mk_description = Live reload plugin for erlang.mk. -pkg_reload_mk_homepage = https://github.com/bullno1/reload.mk -pkg_reload_mk_fetch = git -pkg_reload_mk_repo = https://github.com/bullno1/reload.mk -pkg_reload_mk_commit = master - -PACKAGES += reltool_util -pkg_reltool_util_name = reltool_util -pkg_reltool_util_description = Erlang reltool utility functionality application -pkg_reltool_util_homepage = https://github.com/okeuday/reltool_util -pkg_reltool_util_fetch = git -pkg_reltool_util_repo = https://github.com/okeuday/reltool_util -pkg_reltool_util_commit = master +pkg_ranch_commit = master PACKAGES += relx pkg_relx_name = relx @@ -2866,470 +380,6 @@ pkg_relx_fetch = git pkg_relx_repo = https://github.com/erlware/relx pkg_relx_commit = main -PACKAGES += resource_discovery -pkg_resource_discovery_name = resource_discovery -pkg_resource_discovery_description = An application used to dynamically discover resources present in an Erlang node cluster. -pkg_resource_discovery_homepage = http://erlware.org/ -pkg_resource_discovery_fetch = git -pkg_resource_discovery_repo = https://github.com/erlware/resource_discovery -pkg_resource_discovery_commit = master - -PACKAGES += restc -pkg_restc_name = restc -pkg_restc_description = Erlang Rest Client -pkg_restc_homepage = https://github.com/kivra/restclient -pkg_restc_fetch = git -pkg_restc_repo = https://github.com/kivra/restclient -pkg_restc_commit = master - -PACKAGES += rfc4627_jsonrpc -pkg_rfc4627_jsonrpc_name = rfc4627_jsonrpc -pkg_rfc4627_jsonrpc_description = Erlang RFC4627 (JSON) codec and JSON-RPC server implementation. -pkg_rfc4627_jsonrpc_homepage = https://github.com/tonyg/erlang-rfc4627 -pkg_rfc4627_jsonrpc_fetch = git -pkg_rfc4627_jsonrpc_repo = https://github.com/tonyg/erlang-rfc4627 -pkg_rfc4627_jsonrpc_commit = master - -PACKAGES += riak_core -pkg_riak_core_name = riak_core -pkg_riak_core_description = Distributed systems infrastructure used by Riak. -pkg_riak_core_homepage = https://github.com/basho/riak_core -pkg_riak_core_fetch = git -pkg_riak_core_repo = https://github.com/basho/riak_core -pkg_riak_core_commit = develop - -PACKAGES += riak_dt -pkg_riak_dt_name = riak_dt -pkg_riak_dt_description = Convergent replicated datatypes in Erlang -pkg_riak_dt_homepage = https://github.com/basho/riak_dt -pkg_riak_dt_fetch = git -pkg_riak_dt_repo = https://github.com/basho/riak_dt -pkg_riak_dt_commit = master - -PACKAGES += riak_ensemble -pkg_riak_ensemble_name = riak_ensemble -pkg_riak_ensemble_description = Multi-Paxos framework in Erlang -pkg_riak_ensemble_homepage = https://github.com/basho/riak_ensemble -pkg_riak_ensemble_fetch = git -pkg_riak_ensemble_repo = https://github.com/basho/riak_ensemble -pkg_riak_ensemble_commit = develop - -PACKAGES += riak_kv -pkg_riak_kv_name = riak_kv -pkg_riak_kv_description = Riak Key/Value Store -pkg_riak_kv_homepage = https://github.com/basho/riak_kv -pkg_riak_kv_fetch = git -pkg_riak_kv_repo = https://github.com/basho/riak_kv -pkg_riak_kv_commit = develop - -PACKAGES += riak_pipe -pkg_riak_pipe_name = riak_pipe -pkg_riak_pipe_description = Riak Pipelines -pkg_riak_pipe_homepage = https://github.com/basho/riak_pipe -pkg_riak_pipe_fetch = git -pkg_riak_pipe_repo = https://github.com/basho/riak_pipe -pkg_riak_pipe_commit = develop - -PACKAGES += riak_sysmon -pkg_riak_sysmon_name = riak_sysmon -pkg_riak_sysmon_description = Simple OTP app for managing Erlang VM system_monitor event messages -pkg_riak_sysmon_homepage = https://github.com/basho/riak_sysmon -pkg_riak_sysmon_fetch = git -pkg_riak_sysmon_repo = https://github.com/basho/riak_sysmon -pkg_riak_sysmon_commit = master - -PACKAGES += riakc -pkg_riakc_name = riakc -pkg_riakc_description = Erlang clients for Riak. -pkg_riakc_homepage = https://github.com/basho/riak-erlang-client -pkg_riakc_fetch = git -pkg_riakc_repo = https://github.com/basho/riak-erlang-client -pkg_riakc_commit = master - -PACKAGES += rlimit -pkg_rlimit_name = rlimit -pkg_rlimit_description = Magnus Klaar's rate limiter code from etorrent -pkg_rlimit_homepage = https://github.com/jlouis/rlimit -pkg_rlimit_fetch = git -pkg_rlimit_repo = https://github.com/jlouis/rlimit -pkg_rlimit_commit = master - -PACKAGES += rust_mk -pkg_rust_mk_name = rust_mk -pkg_rust_mk_description = Build Rust crates in an Erlang application -pkg_rust_mk_homepage = https://github.com/goertzenator/rust.mk -pkg_rust_mk_fetch = git -pkg_rust_mk_repo = https://github.com/goertzenator/rust.mk -pkg_rust_mk_commit = master - -PACKAGES += safetyvalve -pkg_safetyvalve_name = safetyvalve -pkg_safetyvalve_description = A safety valve for your erlang node -pkg_safetyvalve_homepage = https://github.com/jlouis/safetyvalve -pkg_safetyvalve_fetch = git -pkg_safetyvalve_repo = https://github.com/jlouis/safetyvalve -pkg_safetyvalve_commit = master - -PACKAGES += seestar -pkg_seestar_name = seestar -pkg_seestar_description = The Erlang client for Cassandra 1.2+ binary protocol -pkg_seestar_homepage = https://github.com/iamaleksey/seestar -pkg_seestar_fetch = git -pkg_seestar_repo = https://github.com/iamaleksey/seestar -pkg_seestar_commit = master - -PACKAGES += setup -pkg_setup_name = setup -pkg_setup_description = Generic setup utility for Erlang-based systems -pkg_setup_homepage = https://github.com/uwiger/setup -pkg_setup_fetch = git -pkg_setup_repo = https://github.com/uwiger/setup -pkg_setup_commit = master - -PACKAGES += sext -pkg_sext_name = sext -pkg_sext_description = Sortable Erlang Term Serialization -pkg_sext_homepage = https://github.com/uwiger/sext -pkg_sext_fetch = git -pkg_sext_repo = https://github.com/uwiger/sext -pkg_sext_commit = master - -PACKAGES += sfmt -pkg_sfmt_name = sfmt -pkg_sfmt_description = SFMT pseudo random number generator for Erlang. -pkg_sfmt_homepage = https://github.com/jj1bdx/sfmt-erlang -pkg_sfmt_fetch = git -pkg_sfmt_repo = https://github.com/jj1bdx/sfmt-erlang -pkg_sfmt_commit = master - -PACKAGES += sgte -pkg_sgte_name = sgte -pkg_sgte_description = A simple Erlang Template Engine -pkg_sgte_homepage = https://github.com/filippo/sgte -pkg_sgte_fetch = git -pkg_sgte_repo = https://github.com/filippo/sgte -pkg_sgte_commit = master - -PACKAGES += sheriff -pkg_sheriff_name = sheriff -pkg_sheriff_description = Parse transform for type based validation. -pkg_sheriff_homepage = http://ninenines.eu -pkg_sheriff_fetch = git -pkg_sheriff_repo = https://github.com/extend/sheriff -pkg_sheriff_commit = master - -PACKAGES += shotgun -pkg_shotgun_name = shotgun -pkg_shotgun_description = better than just a gun -pkg_shotgun_homepage = https://github.com/inaka/shotgun -pkg_shotgun_fetch = git -pkg_shotgun_repo = https://github.com/inaka/shotgun -pkg_shotgun_commit = master - -PACKAGES += sidejob -pkg_sidejob_name = sidejob -pkg_sidejob_description = Parallel worker and capacity limiting library for Erlang -pkg_sidejob_homepage = https://github.com/basho/sidejob -pkg_sidejob_fetch = git -pkg_sidejob_repo = https://github.com/basho/sidejob -pkg_sidejob_commit = develop - -PACKAGES += sieve -pkg_sieve_name = sieve -pkg_sieve_description = sieve is a simple TCP routing proxy (layer 7) in erlang -pkg_sieve_homepage = https://github.com/benoitc/sieve -pkg_sieve_fetch = git -pkg_sieve_repo = https://github.com/benoitc/sieve -pkg_sieve_commit = master - -PACKAGES += simhash -pkg_simhash_name = simhash -pkg_simhash_description = Simhashing for Erlang -- hashing algorithm to find near-duplicates in binary data. -pkg_simhash_homepage = https://github.com/ferd/simhash -pkg_simhash_fetch = git -pkg_simhash_repo = https://github.com/ferd/simhash -pkg_simhash_commit = master - -PACKAGES += simple_bridge -pkg_simple_bridge_name = simple_bridge -pkg_simple_bridge_description = A simple, standardized interface library to Erlang HTTP Servers. -pkg_simple_bridge_homepage = https://github.com/nitrogen/simple_bridge -pkg_simple_bridge_fetch = git -pkg_simple_bridge_repo = https://github.com/nitrogen/simple_bridge -pkg_simple_bridge_commit = master - -PACKAGES += simple_oauth2 -pkg_simple_oauth2_name = simple_oauth2 -pkg_simple_oauth2_description = Simple erlang OAuth2 client module for any http server framework (Google, Facebook, Yandex, Vkontakte are preconfigured) -pkg_simple_oauth2_homepage = https://github.com/virtan/simple_oauth2 -pkg_simple_oauth2_fetch = git -pkg_simple_oauth2_repo = https://github.com/virtan/simple_oauth2 -pkg_simple_oauth2_commit = master - -PACKAGES += skel -pkg_skel_name = skel -pkg_skel_description = A Streaming Process-based Skeleton Library for Erlang -pkg_skel_homepage = https://github.com/ParaPhrase/skel -pkg_skel_fetch = git -pkg_skel_repo = https://github.com/ParaPhrase/skel -pkg_skel_commit = master - -PACKAGES += slack -pkg_slack_name = slack -pkg_slack_description = Minimal slack notification OTP library. -pkg_slack_homepage = https://github.com/DonBranson/slack -pkg_slack_fetch = git -pkg_slack_repo = https://github.com/DonBranson/slack.git -pkg_slack_commit = master - -PACKAGES += snappyer -pkg_snappyer_name = snappyer -pkg_snappyer_description = Snappy as nif for Erlang -pkg_snappyer_homepage = https://github.com/zmstone/snappyer -pkg_snappyer_fetch = git -pkg_snappyer_repo = https://github.com/zmstone/snappyer.git -pkg_snappyer_commit = master - -PACKAGES += social -pkg_social_name = social -pkg_social_description = Cowboy handler for social login via OAuth2 providers -pkg_social_homepage = https://github.com/dvv/social -pkg_social_fetch = git -pkg_social_repo = https://github.com/dvv/social -pkg_social_commit = master - -PACKAGES += sqerl -pkg_sqerl_name = sqerl -pkg_sqerl_description = An Erlang-flavoured SQL DSL -pkg_sqerl_homepage = https://github.com/hairyhum/sqerl -pkg_sqerl_fetch = git -pkg_sqerl_repo = https://github.com/hairyhum/sqerl -pkg_sqerl_commit = master - -PACKAGES += srly -pkg_srly_name = srly -pkg_srly_description = Native Erlang Unix serial interface -pkg_srly_homepage = https://github.com/msantos/srly -pkg_srly_fetch = git -pkg_srly_repo = https://github.com/msantos/srly -pkg_srly_commit = master - -PACKAGES += sshrpc -pkg_sshrpc_name = sshrpc -pkg_sshrpc_description = Erlang SSH RPC module (experimental) -pkg_sshrpc_homepage = https://github.com/jj1bdx/sshrpc -pkg_sshrpc_fetch = git -pkg_sshrpc_repo = https://github.com/jj1bdx/sshrpc -pkg_sshrpc_commit = master - -PACKAGES += stable -pkg_stable_name = stable -pkg_stable_description = Library of assorted helpers for Cowboy web server. -pkg_stable_homepage = https://github.com/dvv/stable -pkg_stable_fetch = git -pkg_stable_repo = https://github.com/dvv/stable -pkg_stable_commit = master - -PACKAGES += statebox -pkg_statebox_name = statebox -pkg_statebox_description = Erlang state monad with merge/conflict-resolution capabilities. Useful for Riak. -pkg_statebox_homepage = https://github.com/mochi/statebox -pkg_statebox_fetch = git -pkg_statebox_repo = https://github.com/mochi/statebox -pkg_statebox_commit = master - -PACKAGES += statman -pkg_statman_name = statman -pkg_statman_description = Efficiently collect massive volumes of metrics inside the Erlang VM -pkg_statman_homepage = https://github.com/knutin/statman -pkg_statman_fetch = git -pkg_statman_repo = https://github.com/knutin/statman -pkg_statman_commit = master - -PACKAGES += statsderl -pkg_statsderl_name = statsderl -pkg_statsderl_description = StatsD client (erlang) -pkg_statsderl_homepage = https://github.com/lpgauth/statsderl -pkg_statsderl_fetch = git -pkg_statsderl_repo = https://github.com/lpgauth/statsderl -pkg_statsderl_commit = master - -PACKAGES += stdinout_pool -pkg_stdinout_pool_name = stdinout_pool -pkg_stdinout_pool_description = stdinout_pool : stuff goes in, stuff goes out. there's never any miscommunication. -pkg_stdinout_pool_homepage = https://github.com/mattsta/erlang-stdinout-pool -pkg_stdinout_pool_fetch = git -pkg_stdinout_pool_repo = https://github.com/mattsta/erlang-stdinout-pool -pkg_stdinout_pool_commit = master - -PACKAGES += stockdb -pkg_stockdb_name = stockdb -pkg_stockdb_description = Database for storing Stock Exchange quotes in erlang -pkg_stockdb_homepage = https://github.com/maxlapshin/stockdb -pkg_stockdb_fetch = git -pkg_stockdb_repo = https://github.com/maxlapshin/stockdb -pkg_stockdb_commit = master - -PACKAGES += subproc -pkg_subproc_name = subproc -pkg_subproc_description = unix subprocess manager with {active,once|false} modes -pkg_subproc_homepage = http://dozzie.jarowit.net/trac/wiki/subproc -pkg_subproc_fetch = git -pkg_subproc_repo = https://github.com/dozzie/subproc -pkg_subproc_commit = v0.1.0 - -PACKAGES += supervisor3 -pkg_supervisor3_name = supervisor3 -pkg_supervisor3_description = OTP supervisor with additional strategies -pkg_supervisor3_homepage = https://github.com/klarna/supervisor3 -pkg_supervisor3_fetch = git -pkg_supervisor3_repo = https://github.com/klarna/supervisor3.git -pkg_supervisor3_commit = master - -PACKAGES += swab -pkg_swab_name = swab -pkg_swab_description = General purpose buffer handling module -pkg_swab_homepage = https://github.com/crownedgrouse/swab -pkg_swab_fetch = git -pkg_swab_repo = https://github.com/crownedgrouse/swab -pkg_swab_commit = master - -PACKAGES += swarm -pkg_swarm_name = swarm -pkg_swarm_description = Fast and simple acceptor pool for Erlang -pkg_swarm_homepage = https://github.com/jeremey/swarm -pkg_swarm_fetch = git -pkg_swarm_repo = https://github.com/jeremey/swarm -pkg_swarm_commit = master - -PACKAGES += switchboard -pkg_switchboard_name = switchboard -pkg_switchboard_description = A framework for processing email using worker plugins. -pkg_switchboard_homepage = https://github.com/thusfresh/switchboard -pkg_switchboard_fetch = git -pkg_switchboard_repo = https://github.com/thusfresh/switchboard -pkg_switchboard_commit = master - -PACKAGES += syn -pkg_syn_name = syn -pkg_syn_description = A global Process Registry and Process Group manager for Erlang. -pkg_syn_homepage = https://github.com/ostinelli/syn -pkg_syn_fetch = git -pkg_syn_repo = https://github.com/ostinelli/syn -pkg_syn_commit = master - -PACKAGES += sync -pkg_sync_name = sync -pkg_sync_description = On-the-fly recompiling and reloading in Erlang. -pkg_sync_homepage = https://github.com/rustyio/sync -pkg_sync_fetch = git -pkg_sync_repo = https://github.com/rustyio/sync -pkg_sync_commit = master - -PACKAGES += syntaxerl -pkg_syntaxerl_name = syntaxerl -pkg_syntaxerl_description = Syntax checker for Erlang -pkg_syntaxerl_homepage = https://github.com/ten0s/syntaxerl -pkg_syntaxerl_fetch = git -pkg_syntaxerl_repo = https://github.com/ten0s/syntaxerl -pkg_syntaxerl_commit = master - -PACKAGES += syslog -pkg_syslog_name = syslog -pkg_syslog_description = Erlang port driver for interacting with syslog via syslog(3) -pkg_syslog_homepage = https://github.com/Vagabond/erlang-syslog -pkg_syslog_fetch = git -pkg_syslog_repo = https://github.com/Vagabond/erlang-syslog -pkg_syslog_commit = master - -PACKAGES += taskforce -pkg_taskforce_name = taskforce -pkg_taskforce_description = Erlang worker pools for controlled parallelisation of arbitrary tasks. -pkg_taskforce_homepage = https://github.com/g-andrade/taskforce -pkg_taskforce_fetch = git -pkg_taskforce_repo = https://github.com/g-andrade/taskforce -pkg_taskforce_commit = master - -PACKAGES += tddreloader -pkg_tddreloader_name = tddreloader -pkg_tddreloader_description = Shell utility for recompiling, reloading, and testing code as it changes -pkg_tddreloader_homepage = https://github.com/version2beta/tddreloader -pkg_tddreloader_fetch = git -pkg_tddreloader_repo = https://github.com/version2beta/tddreloader -pkg_tddreloader_commit = master - -PACKAGES += tempo -pkg_tempo_name = tempo -pkg_tempo_description = NIF-based date and time parsing and formatting for Erlang. -pkg_tempo_homepage = https://github.com/selectel/tempo -pkg_tempo_fetch = git -pkg_tempo_repo = https://github.com/selectel/tempo -pkg_tempo_commit = master - -PACKAGES += tinymq -pkg_tinymq_name = tinymq -pkg_tinymq_description = TinyMQ - a diminutive, in-memory message queue -pkg_tinymq_homepage = https://github.com/ChicagoBoss/tinymq -pkg_tinymq_fetch = git -pkg_tinymq_repo = https://github.com/ChicagoBoss/tinymq -pkg_tinymq_commit = master - -PACKAGES += tinymt -pkg_tinymt_name = tinymt -pkg_tinymt_description = TinyMT pseudo random number generator for Erlang. -pkg_tinymt_homepage = https://github.com/jj1bdx/tinymt-erlang -pkg_tinymt_fetch = git -pkg_tinymt_repo = https://github.com/jj1bdx/tinymt-erlang -pkg_tinymt_commit = master - -PACKAGES += tirerl -pkg_tirerl_name = tirerl -pkg_tirerl_description = Erlang interface to Elastic Search -pkg_tirerl_homepage = https://github.com/inaka/tirerl -pkg_tirerl_fetch = git -pkg_tirerl_repo = https://github.com/inaka/tirerl -pkg_tirerl_commit = master - -PACKAGES += toml -pkg_toml_name = toml -pkg_toml_description = TOML (0.4.0) config parser -pkg_toml_homepage = http://dozzie.jarowit.net/trac/wiki/TOML -pkg_toml_fetch = git -pkg_toml_repo = https://github.com/dozzie/toml -pkg_toml_commit = v0.2.0 - -PACKAGES += traffic_tools -pkg_traffic_tools_name = traffic_tools -pkg_traffic_tools_description = Simple traffic limiting library -pkg_traffic_tools_homepage = https://github.com/systra/traffic_tools -pkg_traffic_tools_fetch = git -pkg_traffic_tools_repo = https://github.com/systra/traffic_tools -pkg_traffic_tools_commit = master - -PACKAGES += trails -pkg_trails_name = trails -pkg_trails_description = A couple of improvements over Cowboy Routes -pkg_trails_homepage = http://inaka.github.io/cowboy-trails/ -pkg_trails_fetch = git -pkg_trails_repo = https://github.com/inaka/cowboy-trails -pkg_trails_commit = master - -PACKAGES += trane -pkg_trane_name = trane -pkg_trane_description = SAX style broken HTML parser in Erlang -pkg_trane_homepage = https://github.com/massemanet/trane -pkg_trane_fetch = git -pkg_trane_repo = https://github.com/massemanet/trane -pkg_trane_commit = master - -PACKAGES += trie -pkg_trie_name = trie -pkg_trie_description = Erlang Trie Implementation -pkg_trie_homepage = https://github.com/okeuday/trie -pkg_trie_fetch = git -pkg_trie_repo = https://github.com/okeuday/trie -pkg_trie_commit = master - PACKAGES += triq pkg_triq_name = triq pkg_triq_description = Trifork QuickCheck @@ -3338,182 +388,6 @@ pkg_triq_fetch = git pkg_triq_repo = https://gitlab.com/triq/triq.git pkg_triq_commit = master -PACKAGES += tunctl -pkg_tunctl_name = tunctl -pkg_tunctl_description = Erlang TUN/TAP interface -pkg_tunctl_homepage = https://github.com/msantos/tunctl -pkg_tunctl_fetch = git -pkg_tunctl_repo = https://github.com/msantos/tunctl -pkg_tunctl_commit = master - -PACKAGES += unicorn -pkg_unicorn_name = unicorn -pkg_unicorn_description = Generic configuration server -pkg_unicorn_homepage = https://github.com/shizzard/unicorn -pkg_unicorn_fetch = git -pkg_unicorn_repo = https://github.com/shizzard/unicorn -pkg_unicorn_commit = master - -PACKAGES += unsplit -pkg_unsplit_name = unsplit -pkg_unsplit_description = Resolves conflicts in Mnesia after network splits -pkg_unsplit_homepage = https://github.com/uwiger/unsplit -pkg_unsplit_fetch = git -pkg_unsplit_repo = https://github.com/uwiger/unsplit -pkg_unsplit_commit = master - -PACKAGES += uuid -pkg_uuid_name = uuid -pkg_uuid_description = Erlang UUID Implementation -pkg_uuid_homepage = https://github.com/okeuday/uuid -pkg_uuid_fetch = git -pkg_uuid_repo = https://github.com/okeuday/uuid -pkg_uuid_commit = master - -PACKAGES += ux -pkg_ux_name = ux -pkg_ux_description = Unicode eXtention for Erlang (Strings, Collation) -pkg_ux_homepage = https://github.com/erlang-unicode/ux -pkg_ux_fetch = git -pkg_ux_repo = https://github.com/erlang-unicode/ux -pkg_ux_commit = master - -PACKAGES += verx -pkg_verx_name = verx -pkg_verx_description = Erlang implementation of the libvirtd remote protocol -pkg_verx_homepage = https://github.com/msantos/verx -pkg_verx_fetch = git -pkg_verx_repo = https://github.com/msantos/verx -pkg_verx_commit = master - -PACKAGES += vmq_bridge -pkg_vmq_bridge_name = vmq_bridge -pkg_vmq_bridge_description = Component of VerneMQ: A distributed MQTT message broker -pkg_vmq_bridge_homepage = https://verne.mq/ -pkg_vmq_bridge_fetch = git -pkg_vmq_bridge_repo = https://github.com/erlio/vmq_bridge -pkg_vmq_bridge_commit = master - -PACKAGES += vmstats -pkg_vmstats_name = vmstats -pkg_vmstats_description = tiny Erlang app that works in conjunction with statsderl in order to generate information on the Erlang VM for graphite logs. -pkg_vmstats_homepage = https://github.com/ferd/vmstats -pkg_vmstats_fetch = git -pkg_vmstats_repo = https://github.com/ferd/vmstats -pkg_vmstats_commit = master - -PACKAGES += walrus -pkg_walrus_name = walrus -pkg_walrus_description = Walrus - Mustache-like Templating -pkg_walrus_homepage = https://github.com/devinus/walrus -pkg_walrus_fetch = git -pkg_walrus_repo = https://github.com/devinus/walrus -pkg_walrus_commit = master - -PACKAGES += webmachine -pkg_webmachine_name = webmachine -pkg_webmachine_description = A REST-based system for building web applications. -pkg_webmachine_homepage = https://github.com/basho/webmachine -pkg_webmachine_fetch = git -pkg_webmachine_repo = https://github.com/basho/webmachine -pkg_webmachine_commit = master - -PACKAGES += websocket_client -pkg_websocket_client_name = websocket_client -pkg_websocket_client_description = Erlang websocket client (ws and wss supported) -pkg_websocket_client_homepage = https://github.com/jeremyong/websocket_client -pkg_websocket_client_fetch = git -pkg_websocket_client_repo = https://github.com/jeremyong/websocket_client -pkg_websocket_client_commit = master - -PACKAGES += worker_pool -pkg_worker_pool_name = worker_pool -pkg_worker_pool_description = a simple erlang worker pool -pkg_worker_pool_homepage = https://github.com/inaka/worker_pool -pkg_worker_pool_fetch = git -pkg_worker_pool_repo = https://github.com/inaka/worker_pool -pkg_worker_pool_commit = main - -PACKAGES += wrangler -pkg_wrangler_name = wrangler -pkg_wrangler_description = Import of the Wrangler svn repository. -pkg_wrangler_homepage = http://www.cs.kent.ac.uk/projects/wrangler/Home.html -pkg_wrangler_fetch = git -pkg_wrangler_repo = https://github.com/RefactoringTools/wrangler -pkg_wrangler_commit = master - -PACKAGES += wsock -pkg_wsock_name = wsock -pkg_wsock_description = Erlang library to build WebSocket clients and servers -pkg_wsock_homepage = https://github.com/madtrick/wsock -pkg_wsock_fetch = git -pkg_wsock_repo = https://github.com/madtrick/wsock -pkg_wsock_commit = master - -PACKAGES += xhttpc -pkg_xhttpc_name = xhttpc -pkg_xhttpc_description = Extensible HTTP Client for Erlang -pkg_xhttpc_homepage = https://github.com/seriyps/xhttpc -pkg_xhttpc_fetch = git -pkg_xhttpc_repo = https://github.com/seriyps/xhttpc -pkg_xhttpc_commit = master - -PACKAGES += xref_runner -pkg_xref_runner_name = xref_runner -pkg_xref_runner_description = Erlang Xref Runner (inspired in rebar xref) -pkg_xref_runner_homepage = https://github.com/inaka/xref_runner -pkg_xref_runner_fetch = git -pkg_xref_runner_repo = https://github.com/inaka/xref_runner -pkg_xref_runner_commit = master - -PACKAGES += yamerl -pkg_yamerl_name = yamerl -pkg_yamerl_description = YAML 1.2 parser in pure Erlang -pkg_yamerl_homepage = https://github.com/yakaz/yamerl -pkg_yamerl_fetch = git -pkg_yamerl_repo = https://github.com/yakaz/yamerl -pkg_yamerl_commit = master - -PACKAGES += yamler -pkg_yamler_name = yamler -pkg_yamler_description = libyaml-based yaml loader for Erlang -pkg_yamler_homepage = https://github.com/goertzenator/yamler -pkg_yamler_fetch = git -pkg_yamler_repo = https://github.com/goertzenator/yamler -pkg_yamler_commit = master - -PACKAGES += yaws -pkg_yaws_name = yaws -pkg_yaws_description = Yaws webserver -pkg_yaws_homepage = http://yaws.hyber.org -pkg_yaws_fetch = git -pkg_yaws_repo = https://github.com/klacke/yaws -pkg_yaws_commit = master - -PACKAGES += zippers -pkg_zippers_name = zippers -pkg_zippers_description = A library for functional zipper data structures in Erlang. Read more on zippers -pkg_zippers_homepage = https://github.com/ferd/zippers -pkg_zippers_fetch = git -pkg_zippers_repo = https://github.com/ferd/zippers -pkg_zippers_commit = master - -PACKAGES += zlists -pkg_zlists_name = zlists -pkg_zlists_description = Erlang lazy lists library. -pkg_zlists_homepage = https://github.com/vjache/erlang-zlists -pkg_zlists_fetch = git -pkg_zlists_repo = https://github.com/vjache/erlang-zlists -pkg_zlists_commit = master - -PACKAGES += zucchini -pkg_zucchini_name = zucchini -pkg_zucchini_description = An Erlang INI parser -pkg_zucchini_homepage = https://github.com/devinus/zucchini -pkg_zucchini_fetch = git -pkg_zucchini_repo = https://github.com/devinus/zucchini -pkg_zucchini_commit = master - # Copyright (c) 2015-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -3521,7 +395,7 @@ pkg_zucchini_commit = master define pkg_print $(verbose) printf "%s\n" \ - $(if $(call core_eq,$(1),$(pkg_$(1)_name)),,"Pkg name: $(1)") \ + $(if $(call core_eq,$1,$(pkg_$(1)_name)),,"Pkg name: $1") \ "App name: $(pkg_$(1)_name)" \ "Description: $(pkg_$(1)_description)" \ "Home page: $(pkg_$(1)_homepage)" \ @@ -3535,10 +409,10 @@ endef search: ifdef q $(foreach p,$(PACKAGES), \ - $(if $(findstring $(call core_lc,$(q)),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \ - $(call pkg_print,$(p)))) + $(if $(findstring $(call core_lc,$q),$(call core_lc,$(pkg_$(p)_name) $(pkg_$(p)_description))), \ + $(call pkg_print,$p))) else - $(foreach p,$(PACKAGES),$(call pkg_print,$(p))) + $(foreach p,$(PACKAGES),$(call pkg_print,$p)) endif # Copyright (c) 2013-2016, Loïc Hoguin @@ -3564,24 +438,35 @@ export DEPS_DIR REBAR_DEPS_DIR = $(DEPS_DIR) export REBAR_DEPS_DIR +# When testing Erlang.mk and updating these, make sure +# to delete test/test_rebar_git before running tests again. REBAR3_GIT ?= https://github.com/erlang/rebar3 -REBAR3_COMMIT ?= 06aaecd51b0ce828b66bb65a74d3c1fd7833a4ba # 3.22.1 + OTP-27 fixes +REBAR3_COMMIT ?= bde4b54248d16280b2c70a244aca3bb7566e2033 # 3.23.0 CACHE_DEPS ?= 0 CACHE_DIR ?= $(if $(XDG_CACHE_HOME),$(XDG_CACHE_HOME),$(HOME)/.cache)/erlang.mk export CACHE_DIR +HEX_CONFIG ?= + +define hex_config.erl + begin + Config0 = hex_core:default_config(), + Config0$(HEX_CONFIG) + end +endef + # External "early" plugins (see core/plugins.mk for regular plugins). # They both use the core_dep_plugin macro. define core_dep_plugin -ifeq ($(2),$(PROJECT)) --include $$(patsubst $(PROJECT)/%,%,$(1)) +ifeq ($2,$(PROJECT)) +-include $$(patsubst $(PROJECT)/%,%,$1) else --include $(DEPS_DIR)/$(1) +-include $(DEPS_DIR)/$1 -$(DEPS_DIR)/$(1): $(DEPS_DIR)/$(2) ; +$(DEPS_DIR)/$1: $(DEPS_DIR)/$2 ; endif endef @@ -3594,44 +479,42 @@ $(foreach p,$(DEP_EARLY_PLUGINS),\ # Query functions. -query_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$(1))) -_qfm_dep = $(if $(dep_fetch_$(1)),$(1),$(if $(IS_DEP),legacy,fail)) +query_fetch_method = $(if $(dep_$(1)),$(call _qfm_dep,$(word 1,$(dep_$(1)))),$(call _qfm_pkg,$1)) +_qfm_dep = $(if $(dep_fetch_$(1)),$1,fail) _qfm_pkg = $(if $(pkg_$(1)_fetch),$(pkg_$(1)_fetch),fail) -query_name = $(if $(dep_$(1)),$(1),$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$(1))) +query_name = $(if $(dep_$(1)),$1,$(if $(pkg_$(1)_name),$(pkg_$(1)_name),$1)) -query_repo = $(call _qr,$(1),$(call query_fetch_method,$(1))) -_qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$(1)),$(call dep_repo,$(1))) +query_repo = $(call _qr,$1,$(call query_fetch_method,$1)) +_qr = $(if $(query_repo_$(2)),$(call query_repo_$(2),$1),$(call query_repo_git,$1)) query_repo_default = $(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_repo)) -query_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$(1))) -query_repo_git-subfolder = $(call query_repo_git,$(1)) +query_repo_git = $(patsubst git://github.com/%,https://github.com/%,$(call query_repo_default,$1)) +query_repo_git-subfolder = $(call query_repo_git,$1) query_repo_git-submodule = - -query_repo_hg = $(call query_repo_default,$(1)) -query_repo_svn = $(call query_repo_default,$(1)) -query_repo_cp = $(call query_repo_default,$(1)) -query_repo_ln = $(call query_repo_default,$(1)) -query_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$(1)) +query_repo_hg = $(call query_repo_default,$1) +query_repo_svn = $(call query_repo_default,$1) +query_repo_cp = $(call query_repo_default,$1) +query_repo_ln = $(call query_repo_default,$1) +query_repo_hex = https://hex.pm/packages/$(if $(word 3,$(dep_$(1))),$(word 3,$(dep_$(1))),$1) query_repo_fail = - -query_repo_legacy = - -query_version = $(call _qv,$(1),$(call query_fetch_method,$(1))) -_qv = $(if $(query_version_$(2)),$(call query_version_$(2),$(1)),$(call dep_commit,$(1))) +query_version = $(call _qv,$1,$(call query_fetch_method,$1)) +_qv = $(if $(query_version_$(2)),$(call query_version_$(2),$1),$(call query_version_default,$1)) query_version_default = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 3,$(dep_$(1))),$(pkg_$(1)_commit))) -query_version_git = $(call query_version_default,$(1)) -query_version_git-subfolder = $(call query_version_git,$(1)) +query_version_git = $(call query_version_default,$1) +query_version_git-subfolder = $(call query_version_default,$1) query_version_git-submodule = - -query_version_hg = $(call query_version_default,$(1)) +query_version_hg = $(call query_version_default,$1) query_version_svn = - query_version_cp = - query_version_ln = - query_version_hex = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(word 2,$(dep_$(1))),$(pkg_$(1)_commit))) query_version_fail = - -query_version_legacy = - -query_extra = $(call _qe,$(1),$(call query_fetch_method,$(1))) -_qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$(1)),-) +query_extra = $(call _qe,$1,$(call query_fetch_method,$1)) +_qe = $(if $(query_extra_$(2)),$(call query_extra_$(2),$1),-) query_extra_git = - query_extra_git-subfolder = $(if $(dep_$(1)),subfolder=$(word 4,$(dep_$(1))),-) @@ -3642,18 +525,19 @@ query_extra_cp = - query_extra_ln = - query_extra_hex = $(if $(dep_$(1)),package-name=$(word 3,$(dep_$(1))),-) query_extra_fail = - -query_extra_legacy = - -query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$(1))) +query_absolute_path = $(addprefix $(DEPS_DIR)/,$(call query_name,$1)) -# Deprecated legacy query functions. -dep_fetch = $(call query_fetch_method,$(1)) +# Deprecated legacy query function. Used by RabbitMQ and its third party plugins. +# Can be removed once RabbitMQ has been updated and enough time has passed. dep_name = $(call query_name,$(1)) -dep_repo = $(call query_repo_git,$(1)) -dep_commit = $(if $(dep_$(1)_commit),$(dep_$(1)_commit),$(if $(dep_$(1)),$(if $(filter hex,$(word 1,$(dep_$(1)))),$(word 2,$(dep_$(1))),$(word 3,$(dep_$(1)))),$(pkg_$(1)_commit))) -LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$(a)),$(APPS_DIR)/$(a))) -ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call dep_name,$(dep)))) +# Application directories. + +LOCAL_DEPS_DIRS = $(foreach a,$(LOCAL_DEPS),$(if $(wildcard $(APPS_DIR)/$a),$(APPS_DIR)/$a)) +# Elixir is handled specially as it must be built before all other deps +# when Mix autopatching is necessary. +ALL_DEPS_DIRS = $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(BUILD_DEPS) $(DEPS)),$(call query_name,$(dep)))) # When we are calling an app directly we don't want to include it here # otherwise it'll be treated both as an apps and a top-level project. @@ -3677,7 +561,7 @@ export NO_AUTOPATCH # Verbosity. -dep_verbose_0 = @echo " DEP $1 ($(call dep_commit,$1))"; +dep_verbose_0 = @echo " DEP $1 ($(call query_version,$1))"; dep_verbose_2 = set -x; dep_verbose = $(dep_verbose_$(V)) @@ -3745,9 +629,11 @@ endif ifneq ($(SKIP_DEPS),) deps:: else -deps:: $(ALL_DEPS_DIRS) apps clean-tmp-deps.log | $(ERLANG_MK_TMP) -ifneq ($(ALL_DEPS_DIRS),) - $(verbose) set -e; for dep in $(ALL_DEPS_DIRS); do \ +ALL_DEPS_DIRS_TO_BUILD = $(if $(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(filter-out $(DEPS_DIR)/elixir,$(ALL_DEPS_DIRS)),$(ALL_DEPS_DIRS)) + +deps:: $(ALL_DEPS_DIRS_TO_BUILD) apps clean-tmp-deps.log | $(ERLANG_MK_TMP) +ifneq ($(ALL_DEPS_DIRS_TO_BUILD),) + $(verbose) set -e; for dep in $(ALL_DEPS_DIRS_TO_BUILD); do \ if grep -qs ^$$dep$$ $(ERLANG_MK_TMP)/deps.log; then \ :; \ else \ @@ -3771,51 +657,80 @@ endif # Deps related targets. -# @todo rename GNUmakefile and makefile into Makefile first, if they exist -# While Makefile file could be GNUmakefile or makefile, -# in practice only Makefile is needed so far. -define dep_autopatch - if [ -f $(DEPS_DIR)/$(1)/erlang.mk ]; then \ - rm -rf $(DEPS_DIR)/$1/ebin/; \ - $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ - $(call dep_autopatch_erlang_mk,$(1)); \ - elif [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ - if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \ - $(call dep_autopatch2,$1); \ - elif [ 0 != `grep -c "include ../\w*\.mk" $(DEPS_DIR)/$(1)/Makefile` ]; then \ - $(call dep_autopatch2,$(1)); \ - elif [ 0 != `grep -ci "^[^#].*rebar" $(DEPS_DIR)/$(1)/Makefile` ]; then \ - $(call dep_autopatch2,$(1)); \ - elif [ -n "`find $(DEPS_DIR)/$(1)/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;`" ]; then \ - $(call dep_autopatch2,$(1)); \ +autopatch_verbose_0 = @echo " PATCH " $(subst autopatch-,,$@) "(method: $(AUTOPATCH_METHOD))"; +autopatch_verbose_2 = set -x; +autopatch_verbose = $(autopatch_verbose_$(V)) + +define dep_autopatch_detect + if [ -f $(DEPS_DIR)/$1/erlang.mk ]; then \ + echo erlang.mk; \ + elif [ -f $(DEPS_DIR)/$1/mix.exs -a -d $(DEPS_DIR)/$1/lib ]; then \ + if [ "$(ELIXIR)" != "disable" ]; then \ + echo mix; \ + elif [ -f $(DEPS_DIR)/$1/rebar.lock -o -f $(DEPS_DIR)/$1/rebar.config ]; then \ + echo rebar3; \ + elif [ -f $(DEPS_DIR)/$1/Makefile ]; then \ + echo noop; \ + else \ + exit 99; \ fi \ - else \ - if [ ! -d $(DEPS_DIR)/$(1)/src/ ]; then \ - $(call dep_autopatch_noop,$(1)); \ + elif [ -f $(DEPS_DIR)/$1/Makefile ]; then \ + if [ -f $(DEPS_DIR)/$1/rebar.lock ]; then \ + echo rebar3; \ + elif [ 0 != \`grep -c "include ../\w*\.mk" $(DEPS_DIR)/$1/Makefile\` ]; then \ + echo rebar3; \ + elif [ 0 != \`grep -ci "^[^#].*rebar" $(DEPS_DIR)/$1/Makefile\` ]; then \ + echo rebar3; \ + elif [ -n "\`find $(DEPS_DIR)/$1/ -type f -name \*.mk -not -name erlang.mk -exec grep -i "^[^#].*rebar" '{}' \;\`" ]; then \ + echo rebar3; \ else \ - $(call dep_autopatch2,$(1)); \ + echo noop; \ fi \ + elif [ ! -d $(DEPS_DIR)/$1/src/ ]; then \ + echo noop; \ + else \ + echo rebar3; \ fi endef -define dep_autopatch2 +define dep_autopatch_for_erlang.mk + rm -rf $(DEPS_DIR)/$1/ebin/; \ + $(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \ + $(call dep_autopatch_erlang_mk,$1) +endef + +define dep_autopatch_for_rebar3 ! test -f $(DEPS_DIR)/$1/ebin/$1.app || \ mv -n $(DEPS_DIR)/$1/ebin/$1.app $(DEPS_DIR)/$1/src/$1.app.src; \ rm -f $(DEPS_DIR)/$1/ebin/$1.app; \ if [ -f $(DEPS_DIR)/$1/src/$1.app.src.script ]; then \ - $(call erlang,$(call dep_autopatch_appsrc_script.erl,$(1))); \ + $(call erlang,$(call dep_autopatch_appsrc_script.erl,$1)); \ fi; \ - $(call erlang,$(call dep_autopatch_appsrc.erl,$(1))); \ - if [ -f $(DEPS_DIR)/$(1)/rebar -o -f $(DEPS_DIR)/$(1)/rebar.config -o -f $(DEPS_DIR)/$(1)/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \ + $(call erlang,$(call dep_autopatch_appsrc.erl,$1)); \ + if [ -f $(DEPS_DIR)/$1/rebar -o -f $(DEPS_DIR)/$1/rebar.config -o -f $(DEPS_DIR)/$1/rebar.config.script -o -f $(DEPS_DIR)/$1/rebar.lock ]; then \ $(call dep_autopatch_fetch_rebar); \ - $(call dep_autopatch_rebar,$(1)); \ + $(call dep_autopatch_rebar,$1); \ else \ - $(call dep_autopatch_gen,$(1)); \ + $(call dep_autopatch_gen,$1); \ fi endef -define dep_autopatch_noop - printf "noop:\n" > $(DEPS_DIR)/$(1)/Makefile +define dep_autopatch_for_mix + $(call dep_autopatch_mix,$1) +endef + +define dep_autopatch_for_noop + test -f $(DEPS_DIR)/$1/Makefile || printf "noop:\n" > $(DEPS_DIR)/$1/Makefile +endef + +define maybe_flock + if command -v flock >/dev/null; then \ + flock $1 sh -c "$2"; \ + elif command -v lockf >/dev/null; then \ + lockf $1 sh -c "$2"; \ + else \ + $2; \ + fi endef # Replace "include erlang.mk" with a line that will load the parent Erlang.mk @@ -3837,18 +752,12 @@ endif define dep_autopatch_gen printf "%s\n" \ "ERLC_OPTS = +debug_info" \ - "include ../../erlang.mk" > $(DEPS_DIR)/$(1)/Makefile + "include ../../erlang.mk" > $(DEPS_DIR)/$1/Makefile endef # We use flock/lockf when available to avoid concurrency issues. define dep_autopatch_fetch_rebar - if command -v flock >/dev/null; then \ - flock $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \ - elif command -v lockf >/dev/null; then \ - lockf $(ERLANG_MK_TMP)/rebar.lock sh -c "$(call dep_autopatch_fetch_rebar2)"; \ - else \ - $(call dep_autopatch_fetch_rebar2); \ - fi + $(call maybe_flock,$(ERLANG_MK_TMP)/rebar.lock,$(call dep_autopatch_fetch_rebar2)) endef define dep_autopatch_fetch_rebar2 @@ -3862,11 +771,11 @@ define dep_autopatch_fetch_rebar2 endef define dep_autopatch_rebar - if [ -f $(DEPS_DIR)/$(1)/Makefile ]; then \ - mv $(DEPS_DIR)/$(1)/Makefile $(DEPS_DIR)/$(1)/Makefile.orig.mk; \ + if [ -f $(DEPS_DIR)/$1/Makefile ]; then \ + mv $(DEPS_DIR)/$1/Makefile $(DEPS_DIR)/$1/Makefile.orig.mk; \ fi; \ - $(call erlang,$(call dep_autopatch_rebar.erl,$(1))); \ - rm -f $(DEPS_DIR)/$(1)/ebin/$(1).app + $(call erlang,$(call dep_autopatch_rebar.erl,$1)); \ + rm -f $(DEPS_DIR)/$1/ebin/$1.app endef define dep_autopatch_rebar.erl @@ -3932,7 +841,6 @@ define dep_autopatch_rebar.erl GetHexVsn2 = fun(N, NP) -> case file:consult("$(call core_native_path,$(DEPS_DIR)/$1/rebar.lock)") of {ok, Lock} -> - io:format("~p~n", [Lock]), LockPkgs = case lists:keyfind("1.2.0", 1, Lock) of {_, LP} -> LP; @@ -3946,10 +854,8 @@ define dep_autopatch_rebar.erl end, if is_list(LockPkgs) -> - io:format("~p~n", [LockPkgs]), case lists:keyfind(atom_to_binary(N, latin1), 1, LockPkgs) of {_, {pkg, _, Vsn}, _} -> - io:format("~p~n", [Vsn]), {N, {hex, NP, binary_to_list(Vsn)}}; _ -> false @@ -3985,6 +891,12 @@ define dep_autopatch_rebar.erl GetHexVsn3Common(N, NP, S0); (N, NP, S) -> {N, {hex, NP, S}} end, + ConvertCommit = fun + ({branch, C}) -> C; + ({ref, C}) -> C; + ({tag, C}) -> C; + (C) -> C + end, fun() -> File = case lists:keyfind(deps, 1, Conf) of false -> []; @@ -4000,16 +912,15 @@ define dep_autopatch_rebar.erl _ -> false end of false -> ok; + {Name, {git_subdir, Repo, Commit, SubDir}} -> + Write(io_lib:format("DEPS += ~s\ndep_~s = git-subfolder ~s ~s ~s~n", [Name, Name, Repo, ConvertCommit(Commit), SubDir])); {Name, Source} -> {Method, Repo, Commit} = case Source of {hex, NPV, V} -> {hex, V, NPV}; {git, R} -> {git, R, master}; - {M, R, {branch, C}} -> {M, R, C}; - {M, R, {ref, C}} -> {M, R, C}; - {M, R, {tag, C}} -> {M, R, C}; {M, R, C} -> {M, R, C} end, - Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, Commit])) + Write(io_lib:format("DEPS += ~s\ndep_~s = ~s ~s ~s~n", [Name, Name, Method, Repo, ConvertCommit(Commit)])) end end || Dep <- Deps] end end(), @@ -4239,7 +1150,7 @@ define dep_autopatch_appsrc.erl case filelib:is_regular(AppSrcIn) of false -> ok; true -> - {ok, [{application, $(1), L0}]} = file:consult(AppSrcIn), + {ok, [{application, $1, L0}]} = file:consult(AppSrcIn), L1 = lists:keystore(modules, 1, L0, {modules, []}), L2 = case lists:keyfind(vsn, 1, L1) of {_, git} -> lists:keyreplace(vsn, 1, L1, {vsn, lists:droplast(os:cmd("git -C $(DEPS_DIR)/$1 describe --dirty --tags --always"))}); @@ -4247,7 +1158,7 @@ define dep_autopatch_appsrc.erl _ -> L1 end, L3 = case lists:keyfind(registered, 1, L2) of false -> [{registered, []}|L2]; _ -> L2 end, - ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $(1), L3}])), + ok = file:write_file(AppSrcOut, io_lib:format("~p.~n", [{application, $1, L3}])), case AppSrcOut of AppSrcIn -> ok; _ -> ok = file:delete(AppSrcIn) end end, halt() @@ -4257,45 +1168,46 @@ ifeq ($(CACHE_DEPS),1) define dep_cache_fetch_git mkdir -p $(CACHE_DIR)/git; \ - if test -d "$(join $(CACHE_DIR)/git/,$(call dep_name,$1))"; then \ - cd $(join $(CACHE_DIR)/git/,$(call dep_name,$1)); \ - if ! git checkout -q $(call dep_commit,$1); then \ - git remote set-url origin $(call dep_repo,$1) && \ + if test -d "$(join $(CACHE_DIR)/git/,$(call query_name,$1))"; then \ + cd $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \ + if ! git checkout -q $(call query_version,$1); then \ + git remote set-url origin $(call query_repo_git,$1) && \ git pull --all && \ - git cat-file -e $(call dep_commit,$1) 2>/dev/null; \ + git cat-file -e $(call query_version_git,$1) 2>/dev/null; \ fi; \ else \ - git clone -q -n -- $(call dep_repo,$1) $(join $(CACHE_DIR)/git/,$(call dep_name,$1)); \ + git clone -q -n -- $(call query_repo_git,$1) $(join $(CACHE_DIR)/git/,$(call query_name,$1)); \ fi; \ - git clone -q --branch $(call dep_commit,$1) --single-branch -- $(join $(CACHE_DIR)/git/,$(call dep_name,$1)) $2 + git clone -q --single-branch -- $(join $(CACHE_DIR)/git/,$(call query_name,$1)) $2; \ + cd $2 && git checkout -q $(call query_version_git,$1) endef define dep_fetch_git - $(call dep_cache_fetch_git,$1,$(DEPS_DIR)/$(call dep_name,$1)); + $(call dep_cache_fetch_git,$1,$(DEPS_DIR)/$(call query_name,$1)); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ - $(call dep_cache_fetch_git,$1,$(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)); \ - ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$1)) \ - $(DEPS_DIR)/$(call dep_name,$1); + $(call dep_cache_fetch_git,$1,$(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)); \ + ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \ + $(DEPS_DIR)/$(call query_name,$1); endef else define dep_fetch_git - git clone -q -n -- $(call dep_repo,$1) $(DEPS_DIR)/$(call dep_name,$1); \ - cd $(DEPS_DIR)/$(call dep_name,$1) && git checkout -q $(call dep_commit,$1); + git clone -q -n -- $(call query_repo_git,$1) $(DEPS_DIR)/$(call query_name,$1); \ + cd $(DEPS_DIR)/$(call query_name,$1) && git checkout -q $(call query_version_git,$1); endef define dep_fetch_git-subfolder mkdir -p $(ERLANG_MK_TMP)/git-subfolder; \ - git clone -q -n -- $(call dep_repo,$1) \ - $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1); \ - cd $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1) \ - && git checkout -q $(call dep_commit,$1); \ - ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call dep_name,$1)/$(word 4,$(dep_$1)) \ - $(DEPS_DIR)/$(call dep_name,$1); + git clone -q -n -- $(call query_repo_git-subfolder,$1) \ + $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1); \ + cd $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1) \ + && git checkout -q $(call query_version_git-subfolder,$1); \ + ln -s $(ERLANG_MK_TMP)/git-subfolder/$(call query_name,$1)/$(word 4,$(dep_$1)) \ + $(DEPS_DIR)/$(call query_name,$1); endef endif @@ -4305,20 +1217,34 @@ define dep_fetch_git-submodule endef define dep_fetch_hg - hg clone -q -U $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); \ - cd $(DEPS_DIR)/$(call dep_name,$(1)) && hg update -q $(call dep_commit,$(1)); + hg clone -q -U $(call query_repo_hg,$1) $(DEPS_DIR)/$(call query_name,$1); \ + cd $(DEPS_DIR)/$(call query_name,$1) && hg update -q $(call query_version_hg,$1); endef define dep_fetch_svn - svn checkout -q $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); + svn checkout -q $(call query_repo_svn,$1) $(DEPS_DIR)/$(call query_name,$1); endef define dep_fetch_cp - cp -R $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); + cp -R $(call query_repo_cp,$1) $(DEPS_DIR)/$(call query_name,$1); endef define dep_fetch_ln - ln -s $(call dep_repo,$(1)) $(DEPS_DIR)/$(call dep_name,$(1)); + ln -s $(call query_repo_ln,$1) $(DEPS_DIR)/$(call query_name,$1); +endef + +define hex_get_tarball.erl + {ok, _} = application:ensure_all_started(ssl), + {ok, _} = application:ensure_all_started(inets), + Config = $(hex_config.erl), + case hex_repo:get_tarball(Config, <<"$1">>, <<"$(strip $2)">>) of + {ok, {200, _, Tarball}} -> + ok = file:write_file("$(call core_native_path,$3)", Tarball), + halt(0); + {ok, {Status, _, Errors}} -> + io:format("Error ~b: ~0p~n", [Status, Errors]), + halt(79) + end endef ifeq ($(CACHE_DEPS),1) @@ -4326,9 +1252,10 @@ ifeq ($(CACHE_DEPS),1) # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(CACHE_DIR)/hex $(DEPS_DIR)/$1; \ - $(eval hex_tar_name=$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar) \ - $(if $(wildcard $(CACHE_DIR)/hex/$(hex_tar_name)),,$(call core_http_get,$(CACHE_DIR)/hex/$(hex_tar_name),\ - https://repo.hex.pm/tarballs/$(hex_tar_name);)) \ + $(eval hex_pkg_name := $(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)) \ + $(eval hex_tar_name := $(hex_pkg_name)-$(strip $(word 2,$(dep_$1))).tar) \ + $(if $(wildcard $(CACHE_DIR)/hex/$(hex_tar_name)),,\ + $(call erlang,$(call hex_get_tarball.erl,$(hex_pkg_name),$(word 2,$(dep_$1)),$(CACHE_DIR)/hex/$(hex_tar_name)));) \ tar -xOf $(CACHE_DIR)/hex/$(hex_tar_name) contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef @@ -4337,58 +1264,76 @@ else # Hex only has a package version. No need to look in the Erlang.mk packages. define dep_fetch_hex mkdir -p $(ERLANG_MK_TMP)/hex $(DEPS_DIR)/$1; \ - $(call core_http_get,$(ERLANG_MK_TMP)/hex/$1.tar,\ - https://repo.hex.pm/tarballs/$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1)-$(strip $(word 2,$(dep_$1))).tar); \ + $(call erlang,$(call hex_get_tarball.erl,$(if $(word 3,$(dep_$1)),$(word 3,$(dep_$1)),$1),$(word 2,$(dep_$1)),$(ERLANG_MK_TMP)/hex/$1.tar)); \ tar -xOf $(ERLANG_MK_TMP)/hex/$1.tar contents.tar.gz | tar -C $(DEPS_DIR)/$1 -xzf -; endef endif define dep_fetch_fail - echo "Error: Unknown or invalid dependency: $(1)." >&2; \ + echo "Error: Unknown or invalid dependency: $1." >&2; \ exit 78; endef -# Kept for compatibility purposes with older Erlang.mk configuration. -define dep_fetch_legacy - $(warning WARNING: '$(1)' dependency configuration uses deprecated format.) \ - git clone -q -n -- $(word 1,$(dep_$(1))) $(DEPS_DIR)/$(1); \ - cd $(DEPS_DIR)/$(1) && git checkout -q $(if $(word 2,$(dep_$(1))),$(word 2,$(dep_$(1))),master); -endef - define dep_target -$(DEPS_DIR)/$(call dep_name,$1): | $(ERLANG_MK_TMP) - $(eval DEP_NAME := $(call dep_name,$1)) +$(DEPS_DIR)/$(call query_name,$1): $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),$(if $(filter-out elixir,$1),$(DEPS_DIR)/elixir/ebin/dep_built)) $(if $(filter hex,$(call query_fetch_method,$1)),$(if $(wildcard $(DEPS_DIR)/$(call query_name,$1)),,$(DEPS_DIR)/hex_core/ebin/dep_built)) | $(ERLANG_MK_TMP) + $(eval DEP_NAME := $(call query_name,$1)) $(eval DEP_STR := $(if $(filter $1,$(DEP_NAME)),$1,"$1 ($(DEP_NAME))")) $(verbose) if test -d $(APPS_DIR)/$(DEP_NAME); then \ echo "Error: Dependency" $(DEP_STR) "conflicts with application found in $(APPS_DIR)/$(DEP_NAME)." >&2; \ exit 17; \ fi $(verbose) mkdir -p $(DEPS_DIR) - $(dep_verbose) $(call dep_fetch_$(strip $(call dep_fetch,$(1))),$(1)) - $(verbose) if [ -f $(DEPS_DIR)/$(1)/configure.ac -o -f $(DEPS_DIR)/$(1)/configure.in ] \ - && [ ! -f $(DEPS_DIR)/$(1)/configure ]; then \ + $(dep_verbose) $(call dep_fetch_$(strip $(call query_fetch_method,$1)),$1) + $(verbose) if [ -f $(DEPS_DIR)/$1/configure.ac -o -f $(DEPS_DIR)/$1/configure.in ] \ + && [ ! -f $(DEPS_DIR)/$1/configure ]; then \ echo " AUTO " $(DEP_STR); \ - cd $(DEPS_DIR)/$(1) && autoreconf -Wall -vif -I m4; \ + cd $(DEPS_DIR)/$1 && autoreconf -Wall -vif -I m4; \ fi - $(verbose) if [ -f $(DEPS_DIR)/$(DEP_NAME)/configure ]; then \ echo " CONF " $(DEP_STR); \ cd $(DEPS_DIR)/$(DEP_NAME) && ./configure; \ fi -ifeq ($(filter $(1),$(NO_AUTOPATCH)),) - $(verbose) $$(MAKE) --no-print-directory autopatch-$(DEP_NAME) +ifeq ($(filter $1,$(NO_AUTOPATCH)),) + $(verbose) AUTOPATCH_METHOD=`$(call dep_autopatch_detect,$1)`; \ + if [ $$$$? -eq 99 ]; then \ + echo "Elixir is currently disabled. Please set 'ELIXIR = system' in the Makefile to enable"; \ + exit 99; \ + fi; \ + $$(MAKE) --no-print-directory autopatch-$(DEP_NAME) AUTOPATCH_METHOD=$$$$AUTOPATCH_METHOD endif -.PHONY: autopatch-$(call dep_name,$1) +.PHONY: autopatch-$(call query_name,$1) -autopatch-$(call dep_name,$1):: - $(verbose) if [ "$1" = "elixir" -a "$(ELIXIR_PATCH)" ]; then \ - ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/; \ - else \ - $$(call dep_autopatch,$(call dep_name,$1)) \ - fi +ifeq ($1,elixir) +autopatch-elixir:: + $$(verbose) ln -s lib/elixir/ebin $(DEPS_DIR)/elixir/ +else +autopatch-$(call query_name,$1):: + $$(autopatch_verbose) $$(call dep_autopatch_for_$(AUTOPATCH_METHOD),$(call query_name,$1)) +endif endef +# We automatically depend on hex_core when the project isn't already. +$(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\ + $(eval $(call dep_target,hex_core))) + +$(DEPS_DIR)/hex_core/ebin/dep_built: | $(ERLANG_MK_TMP) + $(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/hex_core.lock,\ + if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \ + $(MAKE) $(DEPS_DIR)/hex_core; \ + $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \ + touch $(DEPS_DIR)/hex_core/ebin/dep_built; \ + fi) + +$(DEPS_DIR)/elixir/ebin/dep_built: | $(ERLANG_MK_TMP) + $(verbose) $(call maybe_flock,$(ERLANG_MK_TMP)/elixir.lock,\ + if [ ! -e $(DEPS_DIR)/elixir/ebin/dep_built ]; then \ + $(MAKE) $(DEPS_DIR)/elixir; \ + $(MAKE) -C $(DEPS_DIR)/elixir; \ + touch $(DEPS_DIR)/elixir/ebin/dep_built; \ + fi) + $(foreach dep,$(BUILD_DEPS) $(DEPS),$(eval $(call dep_target,$(dep)))) ifndef IS_APP @@ -4439,6 +1384,49 @@ ERLANG_MK_QUERY_REL_DEPS_FILE = $(ERLANG_MK_TMP)/query-rel-deps.log ERLANG_MK_QUERY_TEST_DEPS_FILE = $(ERLANG_MK_TMP)/query-test-deps.log ERLANG_MK_QUERY_SHELL_DEPS_FILE = $(ERLANG_MK_TMP)/query-shell-deps.log +# Copyright (c) 2024, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +.PHONY: beam-cache-restore-app beam-cache-restore-test clean-beam-cache distclean-beam-cache + +BEAM_CACHE_DIR ?= $(ERLANG_MK_TMP)/beam-cache +PROJECT_BEAM_CACHE_DIR = $(BEAM_CACHE_DIR)/$(PROJECT) + +clean:: clean-beam-cache + +clean-beam-cache: + $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR) + +distclean:: distclean-beam-cache + +$(PROJECT_BEAM_CACHE_DIR): + $(verbose) mkdir -p $(PROJECT_BEAM_CACHE_DIR) + +distclean-beam-cache: + $(gen_verbose) rm -rf $(BEAM_CACHE_DIR) + +beam-cache-restore-app: | $(PROJECT_BEAM_CACHE_DIR) + $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-test +ifneq ($(wildcard ebin/),) + $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-test +endif +ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-app),) + $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-app ebin/ +else + $(verbose) $(MAKE) --no-print-directory clean-app +endif + +beam-cache-restore-test: | $(PROJECT_BEAM_CACHE_DIR) + $(verbose) rm -rf $(PROJECT_BEAM_CACHE_DIR)/ebin-app +ifneq ($(wildcard ebin/),) + $(verbose) mv ebin/ $(PROJECT_BEAM_CACHE_DIR)/ebin-app +endif +ifneq ($(wildcard $(PROJECT_BEAM_CACHE_DIR)/ebin-test),) + $(gen_verbose) mv $(PROJECT_BEAM_CACHE_DIR)/ebin-test ebin/ +else + $(verbose) $(MAKE) --no-print-directory clean-app +endif + # Copyright (c) 2013-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -4490,42 +1478,29 @@ mib_verbose_0 = @echo " MIB " $(filter %.bin %.mib,$(?F)); mib_verbose_2 = set -x; mib_verbose = $(mib_verbose_$(V)) -ifneq ($(wildcard src/),) +ifneq ($(wildcard src/)$(wildcard lib/),) # Targets. -app:: $(if $(wildcard ebin/test),clean) deps +app:: $(if $(wildcard ebin/test),beam-cache-restore-app) deps $(verbose) $(MAKE) --no-print-directory $(PROJECT).d $(verbose) $(MAKE) --no-print-directory app-build -ifeq ($(wildcard src/$(PROJECT_MOD).erl),) -define app_file -{application, '$(PROJECT)', [ - {description, "$(PROJECT_DESCRIPTION)"}, - {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), - {id$(comma)$(space)"$(1)"}$(comma)) - {modules, [$(call comma_list,$(2))]}, - {registered, []}, - {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, - {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]}, - {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) -]}. -endef -else +PROJECT_MOD := $(if $(PROJECT_MOD),$(PROJECT_MOD),$(if $(wildcard src/$(PROJECT)_app.erl),$(PROJECT)_app)) + define app_file {application, '$(PROJECT)', [ {description, "$(PROJECT_DESCRIPTION)"}, {vsn, "$(PROJECT_VERSION)"},$(if $(IS_DEP), - {id$(comma)$(space)"$(1)"}$(comma)) - {modules, [$(call comma_list,$(2))]}, - {registered, [$(call comma_list,$(PROJECT)_sup $(PROJECT_REGISTERED))]}, - {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call dep_name,$(dep))))]}, - {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]}, - {mod, {$(PROJECT_MOD), []}}, + {id$(comma)$(space)"$1"}$(comma)) + {modules, [$(call comma_list,$2)]}, + {registered, [$(if $(PROJECT_MOD),$(call comma_list,$(if $(filter $(PROJECT_MOD),$(PROJECT)_app),$(PROJECT)_sup) $(PROJECT_REGISTERED)))]}, + {applications, [$(call comma_list,kernel stdlib $(OTP_DEPS) $(LOCAL_DEPS) $(OPTIONAL_DEPS) $(foreach dep,$(DEPS),$(call query_name,$(dep))))]}, + {optional_applications, [$(call comma_list,$(OPTIONAL_DEPS))]},$(if $(PROJECT_MOD), + {mod$(comma)$(space){$(patsubst %,'%',$(PROJECT_MOD))$(comma)$(space)[]}}$(comma)) {env, $(subst \,\\,$(PROJECT_ENV))}$(if $(findstring {,$(PROJECT_APP_EXTRA_KEYS)),$(comma)$(newline)$(tab)$(subst \,\\,$(PROJECT_APP_EXTRA_KEYS)),) ]}. endef -endif app-build: ebin/$(PROJECT).app $(verbose) : @@ -4537,6 +1512,9 @@ ALL_SRC_FILES := $(sort $(call core_find,src/,*)) ERL_FILES := $(filter %.erl,$(ALL_SRC_FILES)) CORE_FILES := $(filter %.core,$(ALL_SRC_FILES)) +ALL_LIB_FILES := $(sort $(call core_find,lib/,*)) +EX_FILES := $(filter-out lib/mix/%,$(filter %.ex,$(ALL_SRC_FILES) $(ALL_LIB_FILES))) + # ASN.1 files. ifneq ($(wildcard asn1/),) @@ -4545,7 +1523,7 @@ ERL_FILES += $(addprefix src/,$(patsubst %.asn1,%.erl,$(notdir $(ASN1_FILES)))) define compile_asn1 $(verbose) mkdir -p include/ - $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $(1) + $(asn1_verbose) erlc -v -I include/ -o asn1/ +noobj $(ERLC_ASN1_OPTS) $1 $(verbose) mv asn1/*.erl src/ -$(verbose) mv asn1/*.hrl include/ $(verbose) mv asn1/*.asn1db include/ @@ -4707,26 +1685,26 @@ define makedep.erl [233] -> unicode:characters_to_binary(Output0); _ -> Output0 end, - ok = file:write_file("$(1)", Output), + ok = file:write_file("$1", Output), halt() endef ifeq ($(if $(NO_MAKEDEP),$(wildcard $(PROJECT).d),),) -$(PROJECT).d:: $(ERL_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST) +$(PROJECT).d:: $(ERL_FILES) $(EX_FILES) $(call core_find,include/,*.hrl) $(MAKEFILE_LIST) $(makedep_verbose) $(call erlang,$(call makedep.erl,$@)) endif ifeq ($(IS_APP)$(IS_DEP),) -ifneq ($(words $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES)),0) +ifneq ($(words $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES)),0) # Rebuild everything when the Makefile changes. $(ERLANG_MK_TMP)/last-makefile-change: $(MAKEFILE_LIST) | $(ERLANG_MK_TMP) $(verbose) if test -f $@; then \ - touch $(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES); \ + touch $(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES) $(EX_FILES); \ touch -c $(PROJECT).d; \ fi $(verbose) touch $@ -$(ERL_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change +$(ERL_FILES) $(EX_FILES) $(CORE_FILES) $(ASN1_FILES) $(MIB_FILES) $(XRL_FILES) $(YRL_FILES):: $(ERLANG_MK_TMP)/last-makefile-change ebin/$(PROJECT).app:: $(ERLANG_MK_TMP)/last-makefile-change endif endif @@ -4743,7 +1721,7 @@ ebin/: define compile_erl $(erlc_verbose) erlc -v $(if $(IS_DEP),$(filter-out -Werror,$(ERLC_OPTS)),$(ERLC_OPTS)) -o ebin/ \ - -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $(1)) + -pa ebin/ -I include/ $(filter-out $(ERLC_EXCLUDE_PATHS),$(COMPILE_FIRST_PATHS) $1) endef define validate_app_file @@ -4753,13 +1731,16 @@ define validate_app_file end endef -ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) - $(eval FILES_TO_COMPILE := $(filter-out src/$(PROJECT).app.src,$?)) +ebin/$(PROJECT).app:: $(ERL_FILES) $(CORE_FILES) $(wildcard src/$(PROJECT).app.src) $(EX_FILES) + $(eval FILES_TO_COMPILE := $(filter-out $(EX_FILES) src/$(PROJECT).app.src,$?)) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_erl,$(FILES_TO_COMPILE))) + $(if $(filter $(ELIXIR),disable),,$(if $(filter $?,$(EX_FILES)),$(elixirc_verbose) $(eval MODULES := $(shell $(call erlang,$(call compile_ex.erl,$(EX_FILES))))))) + $(eval ELIXIR_COMP_FAILED := $(if $(filter _ERROR_,$(firstword $(MODULES))),true,false)) # Older git versions do not have the --first-parent flag. Do without in that case. + $(verbose) if $(ELIXIR_COMP_FAILED); then exit 1; fi $(eval GITDESCRIBE := $(shell git describe --dirty --abbrev=7 --tags --always --first-parent 2>/dev/null \ || git describe --dirty --abbrev=7 --tags --always 2>/dev/null || true)) - $(eval MODULES := $(patsubst %,'%',$(sort $(notdir $(basename \ + $(eval MODULES := $(MODULES) $(patsubst %,'%',$(sort $(notdir $(basename \ $(filter-out $(ERLC_EXCLUDE_PATHS),$(ERL_FILES) $(CORE_FILES) $(BEAM_FILES))))))) ifeq ($(wildcard src/$(PROJECT).app.src),) $(app_verbose) printf '$(subst %,%%,$(subst $(newline),\n,$(subst ','\'',$(call app_file,$(GITDESCRIBE),$(MODULES)))))' \ @@ -4793,6 +1774,213 @@ clean-app: endif +# Copyright (c) 2024, Tyler Hughes +# Copyright (c) 2024, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. + +# Elixir is automatically enabled in all cases except when +# an Erlang project uses an Elixir dependency. In that case +# $(ELIXIR) must be set explicitly. +ELIXIR ?= $(if $(filter elixir,$(BUILD_DEPS) $(DEPS)),dep,$(if $(EX_FILES),system,disable)) +export ELIXIR + +ifeq ($(ELIXIR),system) +# We expect 'elixir' to be on the path. +ELIXIR_BIN ?= $(shell readlink -f `which elixir`) +ELIXIR_LIBS ?= $(abspath $(dir $(ELIXIR_BIN))/../lib) +# Fallback in case 'elixir' is a shim. +ifeq ($(wildcard $(ELIXIR_LIBS)/elixir/),) +ELIXIR_LIBS = $(abspath $(shell elixir -e 'IO.puts(:code.lib_dir(:elixir))')/../) +endif +ELIXIR_LIBS := $(ELIXIR_LIBS) +export ELIXIR_LIBS +ERL_LIBS := $(ERL_LIBS):$(ELIXIR_LIBS) +else +ifeq ($(ELIXIR),dep) +ERL_LIBS := $(ERL_LIBS):$(DEPS_DIR)/elixir/lib/ +endif +endif + +elixirc_verbose_0 = @echo " EXC $(words $(EX_FILES)) files"; +elixirc_verbose_2 = set -x; +elixirc_verbose = $(elixirc_verbose_$(V)) + +# Unfortunately this currently requires Elixir. +# https://github.com/jelly-beam/verl is a good choice +# for an Erlang implementation, but we already have to +# pull hex_core and Rebar3 so adding yet another pull +# is annoying, especially one that would be necessary +# every time we autopatch Rebar projects. Wait and see. +define hex_version_resolver.erl + HexVersionResolve = fun(Name, Req) -> + application:ensure_all_started(ssl), + application:ensure_all_started(inets), + Config = $(hex_config.erl), + case hex_repo:get_package(Config, atom_to_binary(Name)) of + {ok, {200, _RespHeaders, Package}} -> + #{releases := List} = Package, + {value, #{version := Version}} = lists:search(fun(#{version := Vsn}) -> + M = list_to_atom("Elixir.Version"), + F = list_to_atom("match?"), + M:F(Vsn, Req) + end, List), + {ok, Version}; + {ok, {Status, _, Errors}} -> + {error, Status, Errors} + end + end, + HexVersionResolveAndPrint = fun(Name, Req) -> + case HexVersionResolve(Name, Req) of + {ok, Version} -> + io:format("~s", [Version]), + halt(0); + {error, Status, Errors} -> + io:format("Error ~b: ~0p~n", [Status, Errors]), + halt(77) + end + end +endef + +define dep_autopatch_mix.erl + $(call hex_version_resolver.erl), + {ok, _} = application:ensure_all_started(elixir), + {ok, _} = application:ensure_all_started(mix), + MixFile = <<"$(call core_native_path,$(DEPS_DIR)/$1/mix.exs)">>, + {Mod, Bin} = + case elixir_compiler:file(MixFile, fun(_File, _LexerPid) -> ok end) of + [{T = {_, _}, _CheckerPid}] -> T; + [T = {_, _}] -> T + end, + {module, Mod} = code:load_binary(Mod, binary_to_list(MixFile), Bin), + Project = Mod:project(), + Application = try Mod:application() catch error:undef -> [] end, + StartMod = case lists:keyfind(mod, 1, Application) of + {mod, {StartMod0, _StartArgs}} -> + atom_to_list(StartMod0); + _ -> + "" + end, + Write = fun (Text) -> + file:write_file("$(call core_native_path,$(DEPS_DIR)/$1/Makefile)", Text, [append]) + end, + Write([ + "PROJECT = ", atom_to_list(proplists:get_value(app, Project)), "\n" + "PROJECT_DESCRIPTION = ", proplists:get_value(description, Project, ""), "\n" + "PROJECT_VERSION = ", proplists:get_value(version, Project, ""), "\n" + "PROJECT_MOD = ", StartMod, "\n" + "define PROJECT_ENV\n", + io_lib:format("~p", [proplists:get_value(env, Application, [])]), "\n" + "endef\n\n"]), + ExtraApps = lists:usort([eex, elixir, logger, mix] ++ proplists:get_value(extra_applications, Application, [])), + Write(["LOCAL_DEPS += ", lists:join(" ", [atom_to_list(App) || App <- ExtraApps]), "\n\n"]), + Deps = proplists:get_value(deps, Project, []) -- [elixir_make], + IsRequiredProdDep = fun(Opts) -> + (proplists:get_value(optional, Opts) =/= true) + andalso + case proplists:get_value(only, Opts, prod) of + prod -> true; + L when is_list(L) -> lists:member(prod, L); + _ -> false + end + end, + lists:foreach(fun + ({Name, Req}) when is_binary(Req) -> + {ok, Vsn} = HexVersionResolve(Name, Req), + Write(["DEPS += ", atom_to_list(Name), "\n"]), + Write(["dep_", atom_to_list(Name), " = hex ", Vsn, " ", atom_to_list(Name), "\n"]); + ({Name, Opts}) when is_list(Opts) -> + Path = proplists:get_value(path, Opts), + case IsRequiredProdDep(Opts) of + true when Path =/= undefined -> + Write(["DEPS += ", atom_to_list(Name), "\n"]), + Write(["dep_", atom_to_list(Name), " = ln ", Path, "\n"]); + true when Path =:= undefined -> + Write(["DEPS += ", atom_to_list(Name), "\n"]), + io:format(standard_error, "Warning: No version given for ~p.", [Name]); + false -> + ok + end; + ({Name, Req, Opts}) -> + case IsRequiredProdDep(Opts) of + true -> + {ok, Vsn} = HexVersionResolve(Name, Req), + Write(["DEPS += ", atom_to_list(Name), "\n"]), + Write(["dep_", atom_to_list(Name), " = hex ", Vsn, " ", atom_to_list(Name), "\n"]); + false -> + ok + end; + (_) -> + ok + end, Deps), + case lists:member(elixir_make, proplists:get_value(compilers, Project, [])) of + false -> + ok; + true -> + Write("# https://hexdocs.pm/elixir_make/Mix.Tasks.Compile.ElixirMake.html\n"), + MakeVal = fun(Key, Proplist, DefaultVal, DefaultReplacement) -> + case proplists:get_value(Key, Proplist, DefaultVal) of + DefaultVal -> DefaultReplacement; + Value -> Value + end + end, + MakeMakefile = binary_to_list(MakeVal(make_makefile, Project, default, <<"Makefile">>)), + MakeExe = MakeVal(make_executable, Project, default, "$$\(MAKE)"), + MakeCwd = MakeVal(make_cwd, Project, undefined, <<".">>), + MakeTargets = MakeVal(make_targets, Project, [], []), + MakeArgs = MakeVal(make_args, Project, undefined, []), + case file:rename("$(DEPS_DIR)/$1/" ++ MakeMakefile, "$(DEPS_DIR)/$1/elixir_make.mk") of + ok -> ok; + Err = {error, _} -> + io:format(standard_error, "Failed to copy Makefile with error ~p~n", [Err]), + halt(90) + end, + Write(["app::\n" + "\t", MakeExe, " -C ", MakeCwd, " -f $(DEPS_DIR)/$1/elixir_make.mk", + lists:join(" ", MakeTargets), + lists:join(" ", MakeArgs), + "\n\n"]), + case MakeVal(make_clean, Project, nil, undefined) of + undefined -> + ok; + Clean -> + Write(["clean::\n\t", Clean, "\n\n"]) + end + end, + Write("ERLC_OPTS = +debug_info\n\n"), + Write("include $$\(if $$\(ERLANG_MK_FILENAME),$$\(ERLANG_MK_FILENAME),erlang.mk)"), + halt() +endef + +define dep_autopatch_mix + sed 's|\(defmodule.*do\)|\1\n try do\n Code.compiler_options(on_undefined_variable: :warn)\n rescue _ -> :ok\n end\n|g' -i $(DEPS_DIR)/$(1)/mix.exs; \ + $(MAKE) $(DEPS_DIR)/hex_core/ebin/dep_built; \ + MIX_ENV="$(if $(MIX_ENV),$(strip $(MIX_ENV)),prod)" \ + $(call erlang,$(call dep_autopatch_mix.erl,$1)) +endef + +# We change the group leader so the Elixir io:format output +# isn't captured as we need to either print the modules on +# success, or print _ERROR_ on failure. +define compile_ex.erl + {ok, _} = application:ensure_all_started(elixir), + {ok, _} = application:ensure_all_started(mix), + ModCode = list_to_atom("Elixir.Code"), + ModCode:put_compiler_option(ignore_module_conflict, true), + ModComp = list_to_atom("Elixir.Kernel.ParallelCompiler"), + ModMixProject = list_to_atom("Elixir.Mix.Project"), + erlang:group_leader(whereis(standard_error), self()), + ModMixProject:in_project($(PROJECT), ".", [], fun(_MixFile) -> + case ModComp:compile_to_path([$(call comma_list,$(patsubst %,<<"%">>,$1))], <<"ebin/">>) of + {ok, Modules, _} -> + lists:foreach(fun(E) -> io:format(user, "~p ", [E]) end, Modules), + halt(0); + {error, _ErroredModules, _WarnedModules} -> + io:format(user, "_ERROR_", []), + halt(1) + end + end) +endef + # Copyright (c) 2016, Loïc Hoguin # Copyright (c) 2015, Viktor Söderqvist # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -4877,18 +2065,21 @@ test_erlc_verbose = $(test_erlc_verbose_$(V)) define compile_test_erl $(test_erlc_verbose) erlc -v $(TEST_ERLC_OPTS) -o $(TEST_DIR) \ - -pa ebin/ -I include/ $(1) + -pa ebin/ -I include/ $1 endef ERL_TEST_FILES = $(call core_find,$(TEST_DIR)/,*.erl) + $(ERLANG_MK_TMP)/$(PROJECT).last-testdir-build: $(ERL_TEST_FILES) $(MAKEFILE_LIST) - $(eval FILES_TO_COMPILE := $(if $(filter $(MAKEFILE_LIST),$?),$(filter $(ERL_TEST_FILES),$^),$?)) +# When we have to recompile files in src/ the .d file always gets rebuilt. +# Therefore we want to ignore it when rebuilding test files. + $(eval FILES_TO_COMPILE := $(if $(filter $(filter-out $(PROJECT).d,$(MAKEFILE_LIST)),$?),$(filter $(ERL_TEST_FILES),$^),$(filter $(ERL_TEST_FILES),$?))) $(if $(strip $(FILES_TO_COMPILE)),$(call compile_test_erl,$(FILES_TO_COMPILE)) && touch $@) endif test-build:: IS_TEST=1 test-build:: ERLC_OPTS=$(TEST_ERLC_OPTS) -test-build:: $(if $(wildcard src),$(if $(wildcard ebin/test),,clean)) $(if $(IS_APP),,deps test-deps) +test-build:: $(if $(wildcard src),$(if $(wildcard ebin/test),,beam-cache-restore-test)) $(if $(IS_APP),,deps test-deps) # We already compiled everything when IS_APP=1. ifndef IS_APP ifneq ($(wildcard src),) @@ -4928,6 +2119,8 @@ endif .PHONY: rebar.config +compat_ref = {$(shell (git -C $(DEPS_DIR)/$1 show-ref -q --verify "refs/heads/$2" && echo branch) || (git -C $(DEPS_DIR)/$1 show-ref -q --verify "refs/tags/$2" && echo tag) || echo ref),"$2"} + # We strip out -Werror because we don't want to fail due to # warnings when used as a dependency. @@ -4946,231 +2139,208 @@ endef define compat_rebar_config {deps, [ $(call comma_list,$(foreach d,$(DEPS),\ - $(if $(filter hex,$(call dep_fetch,$d)),\ - {$(call dep_name,$d)$(comma)"$(call dep_repo,$d)"},\ - {$(call dep_name,$d)$(comma)".*"$(comma){git,"$(call dep_repo,$d)"$(comma)"$(call dep_commit,$d)"}}))) + $(if $(filter hex,$(call query_fetch_method,$d)),\ + {$(call query_name,$d)$(comma)"$(call query_version_hex,$d)"},\ + {$(call query_name,$d)$(comma)".*"$(comma){git,"$(call query_repo,$d)"$(comma)$(call compat_ref,$(call query_name,$d),$(call query_version,$d))}}))) ]}. {erl_opts, $(call compat_erlc_opts_to_list,$(ERLC_OPTS))}. endef -rebar.config: +rebar.config: deps $(gen_verbose) $(call core_render,compat_rebar_config,rebar.config) -# Copyright (c) 2015-2016, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. +define tpl_application.app.src +{application, project_name, [ + {description, ""}, + {vsn, "0.1.0"}, + {id, "git"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]}, + {mod, {project_name_app, []}}, + {env, []} +]}. +endef -ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck) +define tpl_application +-module(project_name_app). +-behaviour(application). -.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual +-export([start/2]). +-export([stop/1]). -# Core targets. +start(_Type, _Args) -> + project_name_sup:start_link(). -docs:: asciidoc +stop(_State) -> + ok. +endef -distclean:: distclean-asciidoc-guide distclean-asciidoc-manual +define tpl_apps_Makefile +PROJECT = project_name +PROJECT_DESCRIPTION = New project +PROJECT_VERSION = 0.1.0 +template_sp +# Make sure we know where the applications are located. +ROOT_DIR ?= rel_root_dir +APPS_DIR ?= .. +DEPS_DIR ?= rel_deps_dir -# Plugin-specific targets. +include rel_root_dir/erlang.mk +endef -asciidoc: asciidoc-guide asciidoc-manual +define tpl_cowboy_http_h +-module(template_name). +-behaviour(cowboy_http_handler). -# User guide. +-export([init/3]). +-export([handle/2]). +-export([terminate/3]). -ifeq ($(wildcard doc/src/guide/book.asciidoc),) -asciidoc-guide: -else -asciidoc-guide: distclean-asciidoc-guide doc-deps - a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf - a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/ +-record(state, { +}). -distclean-asciidoc-guide: - $(gen_verbose) rm -rf doc/html/ doc/guide.pdf -endif +init(_, Req, _Opts) -> + {ok, Req, #state{}}. -# Man pages. +handle(Req, State=#state{}) -> + {ok, Req2} = cowboy_req:reply(200, Req), + {ok, Req2, State}. -ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc) +terminate(_Reason, _Req, _State) -> + ok. +endef -ifeq ($(ASCIIDOC_MANUAL_FILES),) -asciidoc-manual: -else +define tpl_cowboy_loop_h +-module(template_name). +-behaviour(cowboy_loop_handler). -# Configuration. +-export([init/3]). +-export([info/3]). +-export([terminate/3]). -MAN_INSTALL_PATH ?= /usr/local/share/man -MAN_SECTIONS ?= 3 7 -MAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\U&\E/') -MAN_VERSION ?= $(PROJECT_VERSION) +-record(state, { +}). -# Plugin-specific targets. +init(_, Req, _Opts) -> + {loop, Req, #state{}, 5000, hibernate}. -define asciidoc2man.erl -try - [begin - io:format(" ADOC ~s~n", [F]), - ok = asciideck:to_manpage(asciideck:parse_file(F), #{ - compress => gzip, - outdir => filename:dirname(F), - extra2 => "$(MAN_PROJECT) $(MAN_VERSION)", - extra3 => "$(MAN_PROJECT) Function Reference" - }) - end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]], - halt(0) -catch C:E$(if $V,:S) -> - io:format("Exception: ~p:~p~n$(if $V,Stacktrace: ~p~n)", [C, E$(if $V,$(comma) S)]), - halt(1) -end. +info(_Info, Req, State) -> + {loop, Req, State, hibernate}. + +terminate(_Reason, _Req, _State) -> + ok. endef -asciidoc-manual:: doc-deps +define tpl_cowboy_rest_h +-module(template_name). -asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES) - $(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?)) - $(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;) +-export([init/3]). +-export([content_types_provided/2]). +-export([get_html/2]). -install-docs:: install-asciidoc +init(_, _Req, _Opts) -> + {upgrade, protocol, cowboy_rest}. -install-asciidoc: asciidoc-manual - $(foreach s,$(MAN_SECTIONS),\ - mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \ - install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;) +content_types_provided(Req, State) -> + {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. -distclean-asciidoc-manual: - $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS)) -endif -endif +get_html(Req, State) -> + {<<"This is REST!">>, Req, State}. +endef -# Copyright (c) 2014-2016, Loïc Hoguin -# This file is part of erlang.mk and subject to the terms of the ISC License. +define tpl_cowboy_websocket_h +-module(template_name). +-behaviour(cowboy_websocket_handler). -.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates +-export([init/3]). +-export([websocket_init/3]). +-export([websocket_handle/3]). +-export([websocket_info/3]). +-export([websocket_terminate/3]). -# Core targets. +-record(state, { +}). -help:: - $(verbose) printf "%s\n" "" \ - "Bootstrap targets:" \ - " bootstrap Generate a skeleton of an OTP application" \ - " bootstrap-lib Generate a skeleton of an OTP library" \ - " bootstrap-rel Generate the files needed to build a release" \ - " new-app in=NAME Create a new local OTP application NAME" \ - " new-lib in=NAME Create a new local OTP library NAME" \ - " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ - " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ - " list-templates List available templates" +init(_, _, _) -> + {upgrade, protocol, cowboy_websocket}. -# Bootstrap templates. +websocket_init(_, Req, _Opts) -> + Req2 = cowboy_req:compact(Req), + {ok, Req2, #state{}}. -define bs_appsrc -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]}, - {mod, {$p_app, []}}, - {env, []} -]}. -endef +websocket_handle({text, Data}, Req, State) -> + {reply, {text, Data}, Req, State}; +websocket_handle({binary, Data}, Req, State) -> + {reply, {binary, Data}, Req, State}; +websocket_handle(_Frame, Req, State) -> + {ok, Req, State}. -define bs_appsrc_lib -{application, $p, [ - {description, ""}, - {vsn, "0.1.0"}, - {id, "git"}, - {modules, []}, - {registered, []}, - {applications, [ - kernel, - stdlib - ]} -]}. -endef +websocket_info(_Info, Req, State) -> + {ok, Req, State}. -# To prevent autocompletion issues with ZSH, we add "include erlang.mk" -# separately during the actual bootstrap. -define bs_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.1.0 -$(if $(SP), -# Whitespace to be used when creating files from templates. -SP = $(SP) -) +websocket_terminate(_Reason, _Req, _State) -> + ok. endef -define bs_apps_Makefile -PROJECT = $p -PROJECT_DESCRIPTION = New project -PROJECT_VERSION = 0.1.0 -$(if $(SP), -# Whitespace to be used when creating files from templates. -SP = $(SP) -) -# Make sure we know where the applications are located. -ROOT_DIR ?= $(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app) -APPS_DIR ?= .. -DEPS_DIR ?= $(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app) +define tpl_gen_fsm +-module(template_name). +-behaviour(gen_fsm). -include $$(ROOT_DIR)/erlang.mk -endef +%% API. +-export([start_link/0]). -define bs_app --module($p_app). --behaviour(application). +%% gen_fsm. +-export([init/1]). +-export([state_name/2]). +-export([handle_event/3]). +-export([state_name/3]). +-export([handle_sync_event/4]). +-export([handle_info/3]). +-export([terminate/3]). +-export([code_change/4]). --export([start/2]). --export([stop/1]). +-record(state, { +}). -start(_Type, _Args) -> - $p_sup:start_link(). +%% API. -stop(_State) -> - ok. -endef +-spec start_link() -> {ok, pid()}. +start_link() -> + gen_fsm:start_link(?MODULE, [], []). -define bs_relx_config -{release, {$p_release, "1"}, [$p, sasl, runtime_tools]}. -{dev_mode, false}. -{include_erts, true}. -{extended_start_script, true}. -{sys_config, "config/sys.config"}. -{vm_args, "config/vm.args"}. -endef +%% gen_fsm. -define bs_sys_config -[ -]. -endef +init([]) -> + {ok, state_name, #state{}}. -define bs_vm_args --name $p@127.0.0.1 --setcookie $p --heart -endef +state_name(_Event, StateData) -> + {next_state, state_name, StateData}. -# Normal templates. +handle_event(_Event, StateName, StateData) -> + {next_state, StateName, StateData}. -define tpl_supervisor --module($(n)). --behaviour(supervisor). +state_name(_Event, _From, StateData) -> + {reply, ignored, state_name, StateData}. --export([start_link/0]). --export([init/1]). +handle_sync_event(_Event, _From, StateName, StateData) -> + {reply, ignored, StateName, StateData}. -start_link() -> - supervisor:start_link({local, ?MODULE}, ?MODULE, []). +handle_info(_Info, StateName, StateData) -> + {next_state, StateName, StateData}. -init([]) -> - Procs = [], - {ok, {{one_for_one, 1, 5}, Procs}}. +terminate(_Reason, _StateName, _StateData) -> + ok. + +code_change(_OldVsn, StateName, StateData, _Extra) -> + {ok, StateName, StateData}. endef define tpl_gen_server --module($(n)). +-module(template_name). -behaviour(gen_server). %% API. @@ -5214,47 +2384,18 @@ code_change(_OldVsn, State, _Extra) -> {ok, State}. endef -define tpl_module --module($(n)). --export([]). -endef - -define tpl_cowboy_http --module($(n)). --behaviour(cowboy_http_handler). - --export([init/3]). --export([handle/2]). --export([terminate/3]). - --record(state, { -}). - -init(_, Req, _Opts) -> - {ok, Req, #state{}}. - -handle(Req, State=#state{}) -> - {ok, Req2} = cowboy_req:reply(200, Req), - {ok, Req2, State}. - -terminate(_Reason, _Req, _State) -> - ok. -endef - -define tpl_gen_fsm --module($(n)). --behaviour(gen_fsm). +define tpl_gen_statem +-module(template_name). +-behaviour(gen_statem). %% API. -export([start_link/0]). -%% gen_fsm. +%% gen_statem. +-export([callback_mode/0]). -export([init/1]). --export([state_name/2]). --export([handle_event/3]). -export([state_name/3]). --export([handle_sync_event/4]). --export([handle_info/3]). +-export([handle_event/4]). -export([terminate/3]). -export([code_change/4]). @@ -5265,26 +2406,20 @@ define tpl_gen_fsm -spec start_link() -> {ok, pid()}. start_link() -> - gen_fsm:start_link(?MODULE, [], []). + gen_statem:start_link(?MODULE, [], []). -%% gen_fsm. +%% gen_statem. + +callback_mode() -> + state_functions. init([]) -> {ok, state_name, #state{}}. -state_name(_Event, StateData) -> +state_name(_EventType, _EventData, StateData) -> {next_state, state_name, StateData}. -handle_event(_Event, StateName, StateData) -> - {next_state, StateName, StateData}. - -state_name(_Event, _From, StateData) -> - {reply, ignored, state_name, StateData}. - -handle_sync_event(_Event, _From, StateName, StateData) -> - {reply, ignored, StateName, StateData}. - -handle_info(_Info, StateName, StateData) -> +handle_event(_EventType, _EventData, StateName, StateData) -> {next_state, StateName, StateData}. terminate(_Reason, _StateName, _StateData) -> @@ -5294,150 +2429,198 @@ code_change(_OldVsn, StateName, StateData, _Extra) -> {ok, StateName, StateData}. endef -define tpl_gen_statem --module($(n)). --behaviour(gen_statem). +define tpl_library.app.src +{application, project_name, [ + {description, ""}, + {vsn, "0.1.0"}, + {id, "git"}, + {modules, []}, + {registered, []}, + {applications, [ + kernel, + stdlib + ]} +]}. +endef + +define tpl_module +-module(template_name). +-export([]). +endef + +define tpl_ranch_protocol +-module(template_name). +-behaviour(ranch_protocol). + +-export([start_link/4]). +-export([init/4]). + +-type opts() :: []. +-export_type([opts/0]). + +-record(state, { + socket :: inet:socket(), + transport :: module() +}). + +start_link(Ref, Socket, Transport, Opts) -> + Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), + {ok, Pid}. + +-spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. +init(Ref, Socket, Transport, _Opts) -> + ok = ranch:accept_ack(Ref), + loop(#state{socket=Socket, transport=Transport}). + +loop(State) -> + loop(State). +endef + +define tpl_relx.config +{release, {project_name_release, "1"}, [project_name, sasl, runtime_tools]}. +{dev_mode, false}. +{include_erts, true}. +{extended_start_script, true}. +{sys_config, "config/sys.config"}. +{vm_args, "config/vm.args"}. +endef + +define tpl_supervisor +-module(template_name). +-behaviour(supervisor). -%% API. -export([start_link/0]). - -%% gen_statem. --export([callback_mode/0]). -export([init/1]). --export([state_name/3]). --export([handle_event/4]). --export([terminate/3]). --export([code_change/4]). - --record(state, { -}). - -%% API. --spec start_link() -> {ok, pid()}. start_link() -> - gen_statem:start_link(?MODULE, [], []). - -%% gen_statem. - -callback_mode() -> - state_functions. + supervisor:start_link({local, ?MODULE}, ?MODULE, []). init([]) -> - {ok, state_name, #state{}}. - -state_name(_EventType, _EventData, StateData) -> - {next_state, state_name, StateData}. + Procs = [], + {ok, {{one_for_one, 1, 5}, Procs}}. +endef -handle_event(_EventType, _EventData, StateName, StateData) -> - {next_state, StateName, StateData}. +define tpl_sys.config +[ +]. +endef -terminate(_Reason, _StateName, _StateData) -> - ok. +define tpl_top_Makefile +PROJECT = project_name +PROJECT_DESCRIPTION = New project +PROJECT_VERSION = 0.1.0 +template_sp +include erlang.mk +endef -code_change(_OldVsn, StateName, StateData, _Extra) -> - {ok, StateName, StateData}. +define tpl_vm.args +-name project_name@127.0.0.1 +-setcookie project_name +-heart endef -define tpl_cowboy_loop --module($(n)). --behaviour(cowboy_loop_handler). --export([init/3]). --export([info/3]). --export([terminate/3]). +# Copyright (c) 2015-2016, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. --record(state, { -}). +ifeq ($(filter asciideck,$(DEPS) $(DOC_DEPS)),asciideck) -init(_, Req, _Opts) -> - {loop, Req, #state{}, 5000, hibernate}. +.PHONY: asciidoc asciidoc-guide asciidoc-manual install-asciidoc distclean-asciidoc-guide distclean-asciidoc-manual -info(_Info, Req, State) -> - {loop, Req, State, hibernate}. +# Core targets. -terminate(_Reason, _Req, _State) -> - ok. -endef +docs:: asciidoc -define tpl_cowboy_rest --module($(n)). +distclean:: distclean-asciidoc-guide distclean-asciidoc-manual --export([init/3]). --export([content_types_provided/2]). --export([get_html/2]). +# Plugin-specific targets. -init(_, _Req, _Opts) -> - {upgrade, protocol, cowboy_rest}. +asciidoc: asciidoc-guide asciidoc-manual -content_types_provided(Req, State) -> - {[{{<<"text">>, <<"html">>, '*'}, get_html}], Req, State}. +# User guide. -get_html(Req, State) -> - {<<"This is REST!">>, Req, State}. -endef +ifeq ($(wildcard doc/src/guide/book.asciidoc),) +asciidoc-guide: +else +asciidoc-guide: distclean-asciidoc-guide doc-deps + a2x -v -f pdf doc/src/guide/book.asciidoc && mv doc/src/guide/book.pdf doc/guide.pdf + a2x -v -f chunked doc/src/guide/book.asciidoc && mv doc/src/guide/book.chunked/ doc/html/ -define tpl_cowboy_ws --module($(n)). --behaviour(cowboy_websocket_handler). +distclean-asciidoc-guide: + $(gen_verbose) rm -rf doc/html/ doc/guide.pdf +endif --export([init/3]). --export([websocket_init/3]). --export([websocket_handle/3]). --export([websocket_info/3]). --export([websocket_terminate/3]). +# Man pages. --record(state, { -}). +ASCIIDOC_MANUAL_FILES := $(wildcard doc/src/manual/*.asciidoc) -init(_, _, _) -> - {upgrade, protocol, cowboy_websocket}. +ifeq ($(ASCIIDOC_MANUAL_FILES),) +asciidoc-manual: +else -websocket_init(_, Req, _Opts) -> - Req2 = cowboy_req:compact(Req), - {ok, Req2, #state{}}. +# Configuration. -websocket_handle({text, Data}, Req, State) -> - {reply, {text, Data}, Req, State}; -websocket_handle({binary, Data}, Req, State) -> - {reply, {binary, Data}, Req, State}; -websocket_handle(_Frame, Req, State) -> - {ok, Req, State}. +MAN_INSTALL_PATH ?= /usr/local/share/man +MAN_SECTIONS ?= 3 7 +MAN_PROJECT ?= $(shell echo $(PROJECT) | sed 's/^./\U&\E/') +MAN_VERSION ?= $(PROJECT_VERSION) -websocket_info(_Info, Req, State) -> - {ok, Req, State}. +# Plugin-specific targets. -websocket_terminate(_Reason, _Req, _State) -> - ok. +define asciidoc2man.erl +try + [begin + io:format(" ADOC ~s~n", [F]), + ok = asciideck:to_manpage(asciideck:parse_file(F), #{ + compress => gzip, + outdir => filename:dirname(F), + extra2 => "$(MAN_PROJECT) $(MAN_VERSION)", + extra3 => "$(MAN_PROJECT) Function Reference" + }) + end || F <- [$(shell echo $(addprefix $(comma)\",$(addsuffix \",$1)) | sed 's/^.//')]], + halt(0) +catch C:E$(if $V,:S) -> + io:format("Exception: ~p:~p~n$(if $V,Stacktrace: ~p~n)", [C, E$(if $V,$(comma) S)]), + halt(1) +end. endef -define tpl_ranch_protocol --module($(n)). --behaviour(ranch_protocol). +asciidoc-manual:: doc-deps --export([start_link/4]). --export([init/4]). +asciidoc-manual:: $(ASCIIDOC_MANUAL_FILES) + $(gen_verbose) $(call erlang,$(call asciidoc2man.erl,$?)) + $(verbose) $(foreach s,$(MAN_SECTIONS),mkdir -p doc/man$s/ && mv doc/src/manual/*.$s.gz doc/man$s/;) --type opts() :: []. --export_type([opts/0]). +install-docs:: install-asciidoc --record(state, { - socket :: inet:socket(), - transport :: module() -}). +install-asciidoc: asciidoc-manual + $(foreach s,$(MAN_SECTIONS),\ + mkdir -p $(MAN_INSTALL_PATH)/man$s/ && \ + install -g `id -g` -o `id -u` -m 0644 doc/man$s/*.gz $(MAN_INSTALL_PATH)/man$s/;) -start_link(Ref, Socket, Transport, Opts) -> - Pid = spawn_link(?MODULE, init, [Ref, Socket, Transport, Opts]), - {ok, Pid}. +distclean-asciidoc-manual: + $(gen_verbose) rm -rf $(addprefix doc/man,$(MAN_SECTIONS)) +endif +endif --spec init(ranch:ref(), inet:socket(), module(), opts()) -> ok. -init(Ref, Socket, Transport, _Opts) -> - ok = ranch:accept_ack(Ref), - loop(#state{socket=Socket, transport=Transport}). +# Copyright (c) 2014-2016, Loïc Hoguin +# This file is part of erlang.mk and subject to the terms of the ISC License. -loop(State) -> - loop(State). -endef +.PHONY: bootstrap bootstrap-lib bootstrap-rel new list-templates + +# Core targets. + +help:: + $(verbose) printf "%s\n" "" \ + "Bootstrap targets:" \ + " bootstrap Generate a skeleton of an OTP application" \ + " bootstrap-lib Generate a skeleton of an OTP library" \ + " bootstrap-rel Generate the files needed to build a release" \ + " new-app in=NAME Create a new local OTP application NAME" \ + " new-lib in=NAME Create a new local OTP library NAME" \ + " new t=TPL n=NAME Generate a module NAME based on the template TPL" \ + " new t=T n=N in=APP Generate a module NAME based on the template TPL in APP" \ + " list-templates List available templates" # Plugin-specific targets. @@ -5449,6 +2632,26 @@ WS = $(tab) endif endif +ifdef SP +define template_sp + +# By default templates indent with a single tab per indentation +# level. Set this variable to the number of spaces you prefer: +SP = $(SP) + +endef +else +template_sp = +endif + +# @todo Additional template placeholders could be added. +subst_template = $(subst rel_root_dir,$(call core_relpath,$(dir $(ERLANG_MK_FILENAME)),$(APPS_DIR)/app),$(subst rel_deps_dir,$(call core_relpath,$(DEPS_DIR),$(APPS_DIR)/app),$(subst template_sp,$(template_sp),$(subst project_name,$p,$(subst template_name,$n,$1))))) + +define core_render_template + $(eval define _tpl_$(1)$(newline)$(call subst_template,$(tpl_$(1)))$(newline)endef) + $(verbose) $(call core_render,_tpl_$(1),$2) +endef + bootstrap: ifneq ($(wildcard src/),) $(error Error: src/ directory already exists) @@ -5457,14 +2660,13 @@ endif $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(eval n := $(PROJECT)_sup) - $(verbose) $(call core_render,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile + $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc,src/$(PROJECT).app.src) + $(verbose) $(call core_render_template,application.app.src,src/$(PROJECT).app.src) endif - $(verbose) $(call core_render,bs_app,src/$(PROJECT)_app.erl) - $(verbose) $(call core_render,tpl_supervisor,src/$(PROJECT)_sup.erl) + $(verbose) $(call core_render_template,application,src/$(PROJECT)_app.erl) + $(verbose) $(call core_render_template,supervisor,src/$(PROJECT)_sup.erl) bootstrap-lib: ifneq ($(wildcard src/),) @@ -5473,11 +2675,10 @@ endif $(eval p := $(PROJECT)) $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) - $(verbose) $(call core_render,bs_Makefile,Makefile) - $(verbose) echo "include erlang.mk" >> Makefile + $(verbose) $(call core_render_template,top_Makefile,Makefile) $(verbose) mkdir src/ ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc_lib,src/$(PROJECT).app.src) + $(verbose) $(call core_render_template,library.app.src,src/$(PROJECT).app.src) endif bootstrap-rel: @@ -5488,11 +2689,11 @@ ifneq ($(wildcard config/),) $(error Error: config/ directory already exists) endif $(eval p := $(PROJECT)) - $(verbose) $(call core_render,bs_relx_config,relx.config) + $(verbose) $(call core_render_template,relx.config,relx.config) $(verbose) mkdir config/ - $(verbose) $(call core_render,bs_sys_config,config/sys.config) - $(verbose) $(call core_render,bs_vm_args,config/vm.args) - $(verbose) awk '/^include erlang.mk/ && !ins {print "BUILD_DEPS += relx";ins=1};{print}' Makefile > Makefile.bak + $(verbose) $(call core_render_template,sys.config,config/sys.config) + $(verbose) $(call core_render_template,vm.args,config/vm.args) + $(verbose) awk '/^include erlang.mk/ && !ins {print "REL_DEPS += relx";ins=1};{print}' Makefile > Makefile.bak $(verbose) mv Makefile.bak Makefile new-app: @@ -5507,12 +2708,12 @@ endif $(error Error: Invalid characters in the application name)) $(eval n := $(in)_sup) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) + $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc,$(APPS_DIR)/$p/src/$p.app.src) + $(verbose) $(call core_render_template,application.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif - $(verbose) $(call core_render,bs_app,$(APPS_DIR)/$p/src/$p_app.erl) - $(verbose) $(call core_render,tpl_supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) + $(verbose) $(call core_render_template,application,$(APPS_DIR)/$p/src/$p_app.erl) + $(verbose) $(call core_render_template,supervisor,$(APPS_DIR)/$p/src/$p_sup.erl) new-lib: ifndef in @@ -5525,30 +2726,40 @@ endif $(if $(shell echo $p | LC_ALL=C grep -x "[a-z0-9_]*"),,\ $(error Error: Invalid characters in the application name)) $(verbose) mkdir -p $(APPS_DIR)/$p/src/ - $(verbose) $(call core_render,bs_apps_Makefile,$(APPS_DIR)/$p/Makefile) + $(verbose) $(call core_render_template,apps_Makefile,$(APPS_DIR)/$p/Makefile) ifdef LEGACY - $(verbose) $(call core_render,bs_appsrc_lib,$(APPS_DIR)/$p/src/$p.app.src) + $(verbose) $(call core_render_template,library.app.src,$(APPS_DIR)/$p/src/$p.app.src) endif +# These are not necessary because we don't expose those as "normal" templates. +BOOTSTRAP_TEMPLATES = apps_Makefile top_Makefile \ + application.app.src library.app.src application \ + relx.config sys.config vm.args + +# Templates may override the path they will be written to when using 'new'. +# Only special template paths must be listed. Default is src/template_name.erl +# Substitution is also applied to the paths. Examples: +# +#tplp_top_Makefile = Makefile +#tplp_application.app.src = src/project_name.app.src +#tplp_application = src/project_name_app.erl +#tplp_relx.config = relx.config + +# Erlang.mk bundles its own templates at build time into the erlang.mk file. + new: -ifeq ($(wildcard src/)$(in),) - $(error Error: src/ directory does not exist) -endif -ifndef t - $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) -endif -ifndef n - $(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP]) -endif -ifdef in - $(verbose) $(call core_render,tpl_$(t),$(APPS_DIR)/$(in)/src/$(n).erl) -else - $(verbose) $(call core_render,tpl_$(t),src/$(n).erl) -endif + $(if $(t),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) + $(if $(n),,$(error Usage: $(MAKE) new t=TEMPLATE n=NAME [in=APP])) + $(if $(tpl_$(t)),,$(error Error: $t template does not exist; try $(Make) list-templates)) + $(eval dest := $(if $(in),$(APPS_DIR)/$(in)/)$(call subst_template,$(if $(tplp_$(t)),$(tplp_$(t)),src/template_name.erl))) + $(if $(wildcard $(dir $(dest))),,$(error Error: $(dir $(dest)) directory does not exist)) + $(if $(wildcard $(dest)),$(error Error: The file $(dest) already exists)) + $(eval p := $(PROJECT)) + $(call core_render_template,$(t),$(dest)) list-templates: $(verbose) @echo Available templates: - $(verbose) printf " %s\n" $(sort $(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES)))) + $(verbose) printf " %s\n" $(sort $(filter-out $(BOOTSTRAP_TEMPLATES),$(patsubst tpl_%,%,$(filter tpl_%,$(.VARIABLES))))) # Copyright (c) 2014-2016, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. @@ -5845,7 +3056,7 @@ ci-setup:: ci-extra:: $(verbose) : -ci_verbose_0 = @echo " CI " $(1); +ci_verbose_0 = @echo " CI " $1; ci_verbose = $(ci_verbose_$(V)) define ci_target @@ -6242,17 +3453,45 @@ help:: # Plugin-specific targets. -escript-zip:: FULL=1 -escript-zip:: deps app +ALL_ESCRIPT_DEPS_DIRS = $(LOCAL_DEPS_DIRS) $(addprefix $(DEPS_DIR)/,$(foreach dep,$(filter-out $(IGNORE_DEPS),$(DEPS)),$(call query_name,$(dep)))) + +ESCRIPT_RUNTIME_DEPS_FILE ?= $(ERLANG_MK_TMP)/escript-deps.log + +escript-list-runtime-deps: +ifeq ($(IS_DEP),) + $(verbose) rm -f $(ESCRIPT_RUNTIME_DEPS_FILE) +endif + $(verbose) touch $(ESCRIPT_RUNTIME_DEPS_FILE) + $(verbose) set -e; for dep in $(ALL_ESCRIPT_DEPS_DIRS) ; do \ + if ! grep -qs ^$$dep$$ $(ESCRIPT_RUNTIME_DEPS_FILE); then \ + echo $$dep >> $(ESCRIPT_RUNTIME_DEPS_FILE); \ + if grep -qs -E "^[[:blank:]]*include[[:blank:]]+(erlang\.mk|.*/erlang\.mk|.*ERLANG_MK_FILENAME.*)$$" \ + $$dep/GNUmakefile $$dep/makefile $$dep/Makefile; then \ + $(MAKE) -C $$dep escript-list-runtime-deps \ + IS_DEP=1 \ + ESCRIPT_RUNTIME_DEPS_FILE=$(ESCRIPT_RUNTIME_DEPS_FILE); \ + fi \ + fi \ + done +ifeq ($(IS_DEP),) + $(verbose) sort < $(ESCRIPT_RUNTIME_DEPS_FILE) | uniq > $(ESCRIPT_RUNTIME_DEPS_FILE).sorted + $(verbose) mv $(ESCRIPT_RUNTIME_DEPS_FILE).sorted $(ESCRIPT_RUNTIME_DEPS_FILE) +endif + +escript-prepare: deps app + $(MAKE) escript-list-runtime-deps + +escript-zip:: escript-prepare $(verbose) mkdir -p $(dir $(abspath $(ESCRIPT_ZIP_FILE))) $(verbose) rm -f $(abspath $(ESCRIPT_ZIP_FILE)) - $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(PROJECT)/ebin/* + $(gen_verbose) cd .. && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) $(notdir $(CURDIR))/ebin/* ifneq ($(DEPS),) $(verbose) cd $(DEPS_DIR) && $(ESCRIPT_ZIP) $(abspath $(ESCRIPT_ZIP_FILE)) \ $(subst $(DEPS_DIR)/,,$(addsuffix /*,$(wildcard \ - $(addsuffix /ebin,$(shell cat $(ERLANG_MK_TMP)/deps.log))))) + $(addsuffix /ebin,$(shell cat $(ESCRIPT_RUNTIME_DEPS_FILE)))))) endif +# @todo Only generate the zip file if there were changes. escript:: escript-zip $(gen_verbose) printf "%s\n" \ "#!$(ESCRIPT_SHEBANG)" \ @@ -6270,6 +3509,11 @@ distclean-escript: .PHONY: eunit apps-eunit +# Eunit can be disabled by setting this to any other value. +EUNIT ?= system + +ifeq ($(EUNIT),system) + # Configuration EUNIT_OPTS ?= @@ -6328,40 +3572,11 @@ apps-eunit: test-build endif endif +endif + # Copyright (c) 2020, Loïc Hoguin # This file is part of erlang.mk and subject to the terms of the ISC License. -HEX_CORE_GIT ?= https://github.com/hexpm/hex_core -HEX_CORE_COMMIT ?= v0.7.0 - -PACKAGES += hex_core -pkg_hex_core_name = hex_core -pkg_hex_core_description = Reference implementation of Hex specifications -pkg_hex_core_homepage = $(HEX_CORE_GIT) -pkg_hex_core_fetch = git -pkg_hex_core_repo = $(HEX_CORE_GIT) -pkg_hex_core_commit = $(HEX_CORE_COMMIT) - -# We automatically depend on hex_core when the project isn't already. -$(if $(filter hex_core,$(DEPS) $(BUILD_DEPS) $(DOC_DEPS) $(REL_DEPS) $(TEST_DEPS)),,\ - $(eval $(call dep_target,hex_core))) - -hex-core: $(DEPS_DIR)/hex_core - $(verbose) if [ ! -e $(DEPS_DIR)/hex_core/ebin/dep_built ]; then \ - $(MAKE) -C $(DEPS_DIR)/hex_core IS_DEP=1; \ - touch $(DEPS_DIR)/hex_core/ebin/dep_built; \ - fi - -# @todo This must also apply to fetching. -HEX_CONFIG ?= - -define hex_config.erl - begin - Config0 = hex_core:default_config(), - Config0$(HEX_CONFIG) - end -endef - define hex_user_create.erl {ok, _} = application:ensure_all_started(ssl), {ok, _} = application:ensure_all_started(inets), @@ -6380,7 +3595,7 @@ define hex_user_create.erl endef # The $(info ) call inserts a new line after the password prompt. -hex-user-create: hex-core +hex-user-create: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(if $(HEX_EMAIL),,$(eval HEX_EMAIL := $(shell read -p "Email: " email; echo $$email))) @@ -6410,7 +3625,7 @@ define hex_key_add.erl end endef -hex-key-add: hex-core +hex-key-add: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_USERNAME),,$(eval HEX_USERNAME := $(shell read -p "Username: " username; echo $$username))) $(if $(HEX_PASSWORD),,$(eval HEX_PASSWORD := $(shell stty -echo; read -p "Password: " password; stty echo; echo $$password) $(info ))) $(gen_verbose) $(call erlang,$(call hex_key_add.erl,$(HEX_USERNAME),$(HEX_PASSWORD),\ @@ -6432,7 +3647,7 @@ HEX_TARBALL_FILES ?= \ $(sort $(call core_find,priv/,*)) \ $(wildcard README*) \ $(wildcard rebar.config) \ - $(sort $(call core_find,src/,*)) + $(sort $(if $(LEGACY),$(filter-out src/$(PROJECT).app.src,$(call core_find,src/,*)),$(call core_find,src/,*))) HEX_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT).tar @@ -6452,7 +3667,7 @@ define hex_tarball_create.erl <<"$(if $(subst hex,,$(call query_fetch_method,$d)),$d,$(if $(word 3,$(dep_$d)),$(word 3,$(dep_$d)),$d))">> => #{ <<"app">> => <<"$d">>, <<"optional">> => false, - <<"requirement">> => <<"$(call query_version,$d)">> + <<"requirement">> => <<"$(if $(hex_req_$d),$(strip $(hex_req_$d)),$(call query_version,$d))">> },) $(if $(DEPS),dummy => dummy) }, @@ -6488,7 +3703,7 @@ hex_tar_verbose_0 = @echo " TAR $(notdir $(ERLANG_MK_TMP))/$(@F)"; hex_tar_verbose_2 = set -x; hex_tar_verbose = $(hex_tar_verbose_$(V)) -$(HEX_TARBALL_OUTPUT_FILE): hex-core app +$(HEX_TARBALL_OUTPUT_FILE): $(DEPS_DIR)/hex_core/ebin/dep_built app $(hex_tar_verbose) $(call erlang,$(call hex_tarball_create.erl)) hex-tarball-create: $(HEX_TARBALL_OUTPUT_FILE) @@ -6539,14 +3754,14 @@ define hex_release_publish.erl end endef -hex-release-tarball: hex-core $(HEX_TARBALL_OUTPUT_FILE) +hex-release-tarball: $(DEPS_DIR)/hex_core/ebin/dep_built $(HEX_TARBALL_OUTPUT_FILE) $(verbose) $(call erlang,$(call hex_release_publish_summary.erl)) -hex-release-publish: hex-core hex-release-tarball +hex-release-publish: $(DEPS_DIR)/hex_core/ebin/dep_built hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),false)) -hex-release-replace: hex-core hex-release-tarball +hex-release-replace: $(DEPS_DIR)/hex_core/ebin/dep_built hex-release-tarball $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_publish.erl,$(HEX_SECRET),true)) @@ -6565,7 +3780,7 @@ define hex_release_delete.erl end endef -hex-release-delete: hex-core +hex-release-delete: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_delete.erl,$(HEX_SECRET))) @@ -6585,7 +3800,7 @@ define hex_release_retire.erl end endef -hex-release-retire: hex-core +hex-release-retire: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_retire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)),\ @@ -6607,7 +3822,7 @@ define hex_release_unretire.erl end endef -hex-release-unretire: hex-core +hex-release-unretire: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_release_unretire.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) @@ -6616,7 +3831,7 @@ HEX_DOCS_DOC_DIR ?= doc/ HEX_DOCS_TARBALL_FILES ?= $(sort $(call core_find,$(HEX_DOCS_DOC_DIR),*)) HEX_DOCS_TARBALL_OUTPUT_FILE ?= $(ERLANG_MK_TMP)/$(PROJECT)-docs.tar.gz -$(HEX_DOCS_TARBALL_OUTPUT_FILE): hex-core app docs +$(HEX_DOCS_TARBALL_OUTPUT_FILE): $(DEPS_DIR)/hex_core/ebin/dep_built app docs $(hex_tar_verbose) tar czf $(HEX_DOCS_TARBALL_OUTPUT_FILE) -C $(HEX_DOCS_DOC_DIR) \ $(HEX_DOCS_TARBALL_FILES:$(HEX_DOCS_DOC_DIR)%=%) @@ -6640,7 +3855,7 @@ define hex_docs_publish.erl end endef -hex-docs-publish: hex-core hex-docs-tarball-create +hex-docs-publish: $(DEPS_DIR)/hex_core/ebin/dep_built hex-docs-tarball-create $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_publish.erl,$(HEX_SECRET))) @@ -6660,7 +3875,7 @@ define hex_docs_delete.erl end endef -hex-docs-delete: hex-core +hex-docs-delete: $(DEPS_DIR)/hex_core/ebin/dep_built $(if $(HEX_SECRET),,$(eval HEX_SECRET := $(shell stty -echo; read -p "Secret: " secret; stty echo; echo $$secret) $(info ))) $(gen_verbose) $(call erlang,$(call hex_docs_delete.erl,$(HEX_SECRET),\ $(if $(HEX_VERSION),$(HEX_VERSION),$(PROJECT_VERSION)))) @@ -6891,17 +4106,13 @@ endef relx-rel: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(verbose) $(MAKE) relx-post-rel -ifeq ($(RELX_TAR),1) - $(call erlang,$(call relx_tar.erl),-pa ebin/) -endif + $(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/)) relx-relup: rel-deps app $(call erlang,$(call relx_release.erl),-pa ebin/) $(MAKE) relx-post-rel $(call erlang,$(call relx_relup.erl),-pa ebin/) -ifeq ($(RELX_TAR),1) - $(call erlang,$(call relx_tar.erl),-pa ebin/) -endif + $(if $(filter-out 0,$(RELX_TAR)),$(call erlang,$(call relx_tar.erl),-pa ebin/)) distclean-relx-rel: $(gen_verbose) rm -rf $(RELX_OUTPUT_DIR) @@ -6944,6 +4155,7 @@ ifeq ($(PLATFORM),msys2) RELX_REL_EXT := .cmd endif +run:: RELX_TAR := 0 run:: all $(verbose) $(RELX_OUTPUT_DIR)/$(RELX_REL_NAME)/bin/$(RELX_REL_NAME)$(RELX_REL_EXT) $(RELX_REL_CMD) @@ -7672,9 +4884,7 @@ endif ifeq ($(IS_APP)$(IS_DEP),) $(verbose) sort < $(ERLANG_MK_RECURSIVE_TMP_LIST) | \ uniq > $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted - $(verbose) cmp -s $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ \ - || mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ - $(verbose) rm -f $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted + $(verbose) mv $(ERLANG_MK_RECURSIVE_TMP_LIST).sorted $@ $(verbose) rm $(ERLANG_MK_RECURSIVE_TMP_LIST) endif endif # ifneq ($(SKIP_DEPS),) @@ -7701,14 +4911,14 @@ list-deps list-doc-deps list-rel-deps list-test-deps list-shell-deps: QUERY ?= name fetch_method repo version define query_target -$(1): $(2) clean-tmp-query.log +$1: $2 clean-tmp-query.log ifeq ($(IS_APP)$(IS_DEP),) - $(verbose) rm -f $(4) + $(verbose) rm -f $4 endif - $(verbose) $(foreach dep,$(3),\ - echo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $(4) ;) - $(if $(filter-out query-deps,$(1)),,\ - $(verbose) set -e; for dep in $(3) ; do \ + $(verbose) $(foreach dep,$3,\ + echo $(PROJECT): $(foreach q,$(QUERY),$(call query_$(q),$(dep))) >> $4 ;) + $(if $(filter-out query-deps,$1),,\ + $(verbose) set -e; for dep in $3 ; do \ if grep -qs ^$$$$dep$$$$ $(ERLANG_MK_TMP)/query.log; then \ :; \ else \ @@ -7717,8 +4927,8 @@ endif fi \ done) ifeq ($(IS_APP)$(IS_DEP),) - $(verbose) touch $(4) - $(verbose) cat $(4) + $(verbose) touch $4 + $(verbose) cat $4 endif endef From 95a14d95defdd5893cbb064e2deff2d11f78ca57 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Tue, 22 Apr 2025 12:08:23 +0200 Subject: [PATCH 14/15] Refactor tests --- test/seshat_test.erl | 173 +++++++++++++++++++++---------------------- 1 file changed, 84 insertions(+), 89 deletions(-) diff --git a/test/seshat_test.erl b/test/seshat_test.erl index 637a4f1..17a2645 100644 --- a/test/seshat_test.erl +++ b/test/seshat_test.erl @@ -99,12 +99,12 @@ format_group() -> seshat:new(Group, widget1, Counters, #{component => widget1}), seshat:new(Group, widget2, Counters, #{component => widget2}), seshat:new(Group, screw, Counters), % no labels, will be omitted - PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{reads => #{type => counter, - help => "Total reads", - values => #{#{component => widget1} => 0, - #{component => widget2} => 0}}}, - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + Result = seshat:format(Group), + ExpectedMap = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => widget1} => 0, + #{component => widget2} => 0}}}, + ?assertEqual(ExpectedMap, Result), ok. format_one() -> @@ -113,11 +113,11 @@ format_one() -> seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => widget1}), seshat:new(Group, widget2, Counters, #{component => widget2}), - PrometheusFormat = seshat:format_one(Group, widget2), - ExpectedPrometheusFormat = #{reads => #{type => counter, - help => "Total reads", - values => #{#{component => widget2} => 0}}}, - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + Result = seshat:format_one(Group, widget2), + ExpectedMap = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => widget2} => 0}}}, + ?assertEqual(ExpectedMap, Result), ok. format_with_many_labels() -> @@ -128,12 +128,12 @@ format_with_many_labels() -> seshat:new(Group, widget2, Counters, #{component => "widget2", status => down}), set_value(Group, widget1, reads, 1), set_value(Group, widget2, reads, 2), - PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{reads => #{type => counter, - help => "Total reads", - values => #{#{component => "widget1", status => up} => 1, - #{component => "widget2", status => down} => 2}}}, - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + Result = seshat:format(Group), + ExpectedMap = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => "widget1", status => up} => 1, + #{component => "widget2", status => down} => 2}}}, + ?assertEqual(ExpectedMap, Result), ok. format_selected_metrics() -> @@ -146,16 +146,16 @@ format_selected_metrics() -> seshat:new_group(Group), seshat:new(Group, thing1, Counters, #{component => "thing1"}), seshat:new(Group, thing2, Counters, #{component => "thing2"}), - PrometheusFormat = seshat:format(Group, [reads, writes]), - ExpectedPrometheusFormat = #{reads => #{type => counter, - help => "Total reads", - values => #{#{component => "thing1"} => 0, - #{component => "thing2"} => 0}}, - writes => #{type => counter, - help => "Total writes", - values => #{#{component => "thing1"} => 0, - #{component => "thing2"} => 0}}}, - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + Result = seshat:format(Group, [reads, writes]), + ExpectedMap = #{reads => #{type => counter, + help => "Total reads", + values => #{#{component => "thing1"} => 0, + #{component => "thing2"} => 0}}, + writes => #{type => counter, + help => "Total writes", + values => #{#{component => "thing1"} => 0, + #{component => "thing2"} => 0}}}, + ?assertEqual(ExpectedMap, Result), ok. invalid_fields() -> @@ -181,21 +181,20 @@ format_ratio() -> set_value(Group, test_component, pangs, 33), set_value(Group, test_component, rings, 100), - PrometheusFormat = seshat:format(Group), - ExpectedPrometheusFormat = #{pings => #{type => gauge, - help => "Some ratio that happens to be 0%", - values => #{#{component => test} => 0.0}}, - pongs => #{type => gauge, - help => "Some ratio that happens to be 17%", - values => #{#{component => test} => 0.17}}, - pangs => #{type => gauge, - help => "Some ratio that happens to be 33%", - values => #{#{component => test} => 0.33}}, - rings => #{type => gauge, - help => "Some ratio that happens to be 100%", - values => #{#{component => test} => 1.0}}}, - - ?assertEqual(ExpectedPrometheusFormat, PrometheusFormat), + Result = seshat:format(Group), + ExpectedMap = #{pings => #{type => gauge, + help => "Some ratio that happens to be 0%", + values => #{#{component => test} => 0.0}}, + pongs => #{type => gauge, + help => "Some ratio that happens to be 17%", + values => #{#{component => test} => 0.17}}, + pangs => #{type => gauge, + help => "Some ratio that happens to be 33%", + values => #{#{component => test} => 0.33}}, + rings => #{type => gauge, + help => "Some ratio that happens to be 100%", + values => #{#{component => test} => 1.0}}}, + ?assertEqual(ExpectedMap, Result), ok. format_time_metrics() -> @@ -214,25 +213,24 @@ format_ratio() -> set_value(Group, test_component, short_latency, 5), set_value(Group, test_component, long_latency, 1500), - MapFormat = seshat:format(Group), - ExpectedMapFormat = #{ + MapResult = seshat:format(Group), + ExpectedMap = #{ job_duration => #{type => gauge, - help => "Job duration", - values => #{Labels => 30.0}}, + help => "Job duration", + values => #{Labels => 30.0}}, short_latency => #{type => gauge, - help => "Short latency", - values => #{Labels => 0.005}}, + help => "Short latency", + values => #{Labels => 0.005}}, long_latency => #{type => gauge, - help => "Request latency", - values => #{Labels => 1.5}} + help => "Request latency", + values => #{Labels => 1.5}} }, - ?assertEqual(ExpectedMapFormat, MapFormat), + ?assertEqual(ExpectedMap, MapResult), Prefix = "myapp", MetricNames = [job_duration, short_latency, long_latency], % Added new metric name - ResultAsList = binary_to_list(seshat:text_format(Group, Prefix, MetricNames)), + TextResult = seshat:text_format(Group, Prefix, MetricNames), - % Expected format needs sorting because order isn't guaranteed ExpectedLines = [ "# HELP myapp_job_duration_seconds Job duration", "# TYPE myapp_job_duration_seconds gauge", @@ -244,15 +242,9 @@ format_ratio() -> "# TYPE myapp_long_latency_seconds gauge", "myapp_long_latency_seconds{component=\"test\"} 1.5" ], - ExpectedSortedText = lists:sort(ExpectedLines), - - % Split and sort the actual result for comparison - ResultLines = string:split(ResultAsList, "\n", all), - FilteredResultLines = [Line || Line <- ResultLines, Line /= ""], - SortedResultText = lists:sort(FilteredResultLines), - - ?assertEqual(ExpectedSortedText, SortedResultText), + ExpectedResult = list_to_binary(string:join(ExpectedLines, "\n") ++ "\n"), + ?assertEqual(ExpectedResult, TextResult), ok. text_format_selected_metrics() -> @@ -288,34 +280,37 @@ text_format_selected_metrics() -> set_value(Group, thing3, duration, 345), set_value(Group, thing3, npc, 1), % to be ignored - ResultAsList = binary_to_list(seshat:text_format(Group, "acme", [reads, writes, cached, latency, duration])), - ExpectedPrometheusFormat = "# HELP acme_reads Total reads\n" - "# TYPE acme_reads counter\n" - "acme_reads{version=\"1.2.3\",component=\"thing1\"} 1\n" - "acme_reads{component=\"thing2\",some_atom=\"atom_value\"} 3\n" - "acme_reads{component=\"thing3\",some_binary=\"binary_value\"} 1234\n" - "# HELP acme_writes Total writes\n" - "# TYPE acme_writes counter\n" - "acme_writes{version=\"1.2.3\",component=\"thing1\"} 2\n" - "acme_writes{component=\"thing2\",some_atom=\"atom_value\"} 4\n" - "acme_writes{component=\"thing3\",some_binary=\"binary_value\"} 4321\n" - "# HELP acme_cached_ratio Ratio of things served from cache\n" - "# TYPE acme_cached_ratio gauge\n" - "acme_cached_ratio{version=\"1.2.3\",component=\"thing1\"} 0.1\n" - "acme_cached_ratio{component=\"thing2\",some_atom=\"atom_value\"} 1.0\n" - "acme_cached_ratio{component=\"thing3\",some_binary=\"binary_value\"} 0.17\n" - "# HELP acme_latency_seconds Latency\n" - "# TYPE acme_latency_seconds gauge\n" - "acme_latency_seconds{version=\"1.2.3\",component=\"thing1\"} 0.005\n" - "acme_latency_seconds{component=\"thing2\",some_atom=\"atom_value\"} 0.006\n" - "acme_latency_seconds{component=\"thing3\",some_binary=\"binary_value\"} 0.007\n" - "# HELP acme_duration_seconds Duration\n" - "# TYPE acme_duration_seconds gauge\n" - "acme_duration_seconds{version=\"1.2.3\",component=\"thing1\"} 123.0\n" - "acme_duration_seconds{component=\"thing2\",some_atom=\"atom_value\"} 234.0\n" - "acme_duration_seconds{component=\"thing3\",some_binary=\"binary_value\"} 345.0\n", - - ?assertEqual(ExpectedPrometheusFormat, ResultAsList), + Result = seshat:text_format(Group, "acme", [reads, writes, cached, latency, duration]), + ExpectedLines = [ + "# HELP acme_reads Total reads", + "# TYPE acme_reads counter", + "acme_reads{version=\"1.2.3\",component=\"thing1\"} 1", + "acme_reads{component=\"thing2\",some_atom=\"atom_value\"} 3", + "acme_reads{component=\"thing3\",some_binary=\"binary_value\"} 1234", + "# HELP acme_writes Total writes", + "# TYPE acme_writes counter", + "acme_writes{version=\"1.2.3\",component=\"thing1\"} 2", + "acme_writes{component=\"thing2\",some_atom=\"atom_value\"} 4", + "acme_writes{component=\"thing3\",some_binary=\"binary_value\"} 4321", + "# HELP acme_cached_ratio Ratio of things served from cache", + "# TYPE acme_cached_ratio gauge", + "acme_cached_ratio{version=\"1.2.3\",component=\"thing1\"} 0.1", + "acme_cached_ratio{component=\"thing2\",some_atom=\"atom_value\"} 1.0", + "acme_cached_ratio{component=\"thing3\",some_binary=\"binary_value\"} 0.17", + "# HELP acme_latency_seconds Latency", + "# TYPE acme_latency_seconds gauge", + "acme_latency_seconds{version=\"1.2.3\",component=\"thing1\"} 0.005", + "acme_latency_seconds{component=\"thing2\",some_atom=\"atom_value\"} 0.006", + "acme_latency_seconds{component=\"thing3\",some_binary=\"binary_value\"} 0.007", + "# HELP acme_duration_seconds Duration", + "# TYPE acme_duration_seconds gauge", + "acme_duration_seconds{version=\"1.2.3\",component=\"thing1\"} 123.0", + "acme_duration_seconds{component=\"thing2\",some_atom=\"atom_value\"} 234.0", + "acme_duration_seconds{component=\"thing3\",some_binary=\"binary_value\"} 345.0" + ], + ExpectedResult = list_to_binary(string:join(ExpectedLines, "\n") ++ "\n"), + + ?assertEqual(ExpectedResult, Result), ok. %% test helpers From 2890f5b1b4be04bc6dfb36f8d752e726866c1c34 Mon Sep 17 00:00:00 2001 From: Michal Kuratczyk Date: Wed, 7 May 2025 15:23:01 +0200 Subject: [PATCH 15/15] eunit->CT (seshat_test -> seshat_SUITE) --- src/seshat.erl | 3 +- test/{seshat_test.erl => seshat_SUITE.erl} | 234 +++++++++------------ 2 files changed, 103 insertions(+), 134 deletions(-) rename test/{seshat_test.erl => seshat_SUITE.erl} (65%) diff --git a/src/seshat.erl b/src/seshat.erl index a3bd4a9..1a2dafb 100644 --- a/src/seshat.erl +++ b/src/seshat.erl @@ -20,7 +20,8 @@ format/1, format/2, text_format/3, - format_one/2 + format_one/2, + resolve_fields_spec/1 ]). -type group() :: term(). diff --git a/test/seshat_test.erl b/test/seshat_SUITE.erl similarity index 65% rename from test/seshat_test.erl rename to test/seshat_SUITE.erl index 17a2645..70dd8a6 100644 --- a/test/seshat_test.erl +++ b/test/seshat_SUITE.erl @@ -5,97 +5,85 @@ %% Copyright (c) 2007-2023 Broadcom. All Rights Reserved. The term Broadcom refers to Broadcom Inc. and/or its subsidiaries. %% --module(seshat_test). +-module(seshat_SUITE). -include_lib("eunit/include/eunit.hrl"). -setup() -> - {ok, _} = application:ensure_all_started(seshat). +-compile(nowarn_export_all). +-compile(export_all). + +all() -> + [overview, + counters_with_persistent_term_field_spec, + format_group, + format_one, + format_with_many_labels, + format_ratio, + format_time_metrics, + format_selected_metrics, + text_format_selected_metrics, + invalid_fields]. + +init_per_suite(Config) -> + {ok, _} = application:ensure_all_started(seshat), + Config. + +end_per_suite(_Config) -> + ok = application:stop(seshat), + ok. -cleanup(_) -> - ok = application:stop(seshat). +init_per_testcase(TestCaseName, Config) -> + Group = TestCaseName, + seshat:new_group(Group), + [{group, Group} | Config]. -test_suite_test_() -> - {foreach, - fun setup/0, - fun cleanup/1, - [ fun overview/0, - fun counters_with_persistent_term_field_spec/0, - fun format_group/0, - fun format_one/0, - fun format_with_many_labels/0, - fun format_ratio/0, - fun format_time_metrics/0, - fun format_selected_metrics/0, - fun text_format_selected_metrics/0, - fun invalid_fields/0 ]}. +end_per_testcase(TestCaseName, _Config) -> + seshat:delete_group(TestCaseName), + ok. -overview() -> +%% Test cases +overview(_Config) -> Group = ?FUNCTION_NAME, Counters = [ - { - carrots_eaten_total, 1, counter, - "Total number of carrots eaten on a meal" - }, - { - holes_dug_total, 2, counter, - "Total number of holes dug in an afternoon" - } + {carrots_eaten_total, 1, counter, "Total number of carrots eaten on a meal"}, + {holes_dug_total, 2, counter, "Total number of holes dug in an afternoon"} ], - seshat:new_group(Group), seshat:new(Group, "rabbit", Counters), set_value(Group, "rabbit", carrots_eaten_total, 3), set_value(Group, "rabbit", holes_dug_total, 1), Overview = seshat:overview(Group), ?assertEqual( - #{"rabbit" => #{carrots_eaten_total => 3, - holes_dug_total => 1}}, + #{"rabbit" => #{carrots_eaten_total => 3, holes_dug_total => 1}}, Overview), - - ?assertMatch(#{carrots_eaten_total := 3, - holes_dug_total := 1}, + ?assertMatch(#{carrots_eaten_total := 3, holes_dug_total := 1}, seshat:overview(Group, "rabbit")), - ?assertMatch(#{holes_dug_total := 1}, seshat:counters(Group, "rabbit", [holes_dug_total])), ok. -counters_with_persistent_term_field_spec() -> +counters_with_persistent_term_field_spec(_Config) -> Group = ?FUNCTION_NAME, Counters = [ - { - carrots_eaten_total, 1, counter, - "Total number of carrots eaten on a meal" - }, - { - holes_dug_total, 2, counter, - "Total number of holes dug in an afternoon" - } + {carrots_eaten_total, 1, counter, "Total number of carrots eaten on a meal"}, + {holes_dug_total, 2, counter, "Total number of holes dug in an afternoon"} ], - persistent_term:put(pets_field_spec, Counters), - seshat:new_group(Group), seshat:new(Group, "rabbit", {persistent_term, pets_field_spec}), set_value(Group, "rabbit", carrots_eaten_total, 3), set_value(Group, "rabbit", holes_dug_total, 1), Overview = seshat:overview(Group), ?assertEqual( - #{"rabbit" => #{carrots_eaten_total => 3, - holes_dug_total => 1}}, + #{"rabbit" => #{carrots_eaten_total => 3, holes_dug_total => 1}}, Overview), - - ?assertMatch(#{carrots_eaten_total := 3, - holes_dug_total := 1}, + ?assertMatch(#{carrots_eaten_total := 3, holes_dug_total := 1}, seshat:overview(Group, "rabbit")), - ?assertMatch(#{holes_dug_total := 1}, seshat:counters(Group, "rabbit", [holes_dug_total])), - + persistent_term:erase(pets_field_spec), ok. -format_group() -> +format_group(_Config) -> Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], - seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => widget1}), seshat:new(Group, widget2, Counters, #{component => widget2}), seshat:new(Group, screw, Counters), % no labels, will be omitted @@ -107,10 +95,9 @@ format_group() -> ?assertEqual(ExpectedMap, Result), ok. -format_one() -> +format_one(_Config) -> Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], - seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => widget1}), seshat:new(Group, widget2, Counters, #{component => widget2}), Result = seshat:format_one(Group, widget2), @@ -120,10 +107,9 @@ format_one() -> ?assertEqual(ExpectedMap, Result), ok. -format_with_many_labels() -> +format_with_many_labels(_Config) -> Group = ?FUNCTION_NAME, Counters = [{reads, 1, counter, "Total reads"}], - seshat:new_group(Group), seshat:new(Group, widget1, Counters, #{component => "widget1", status => up}), seshat:new(Group, widget2, Counters, #{component => "widget2", status => down}), set_value(Group, widget1, reads, 1), @@ -136,14 +122,13 @@ format_with_many_labels() -> ?assertEqual(ExpectedMap, Result), ok. -format_selected_metrics() -> +format_selected_metrics(_Config) -> Group = ?FUNCTION_NAME, Counters = [ {reads, 1, counter, "Total reads"}, {writes, 2, counter, "Total writes"}, {lookups, 3, counter, "Total lookups"} ], - seshat:new_group(Group), seshat:new(Group, thing1, Counters, #{component => "thing1"}), seshat:new(Group, thing2, Counters, #{component => "thing2"}), Result = seshat:format(Group, [reads, writes]), @@ -158,29 +143,25 @@ format_selected_metrics() -> ?assertEqual(ExpectedMap, Result), ok. -invalid_fields() -> - Group = widgets, +invalid_fields(_Config) -> + Group = ?FUNCTION_NAME, Fields = [{reads, 1, counter, "Total reads"}, - {writes, 3, counter, "Total writes"}], - seshat:new_group(Group), + {writes, 3, counter, "Total writes"}], % Index 2 is missing ?assertError(invalid_field_specification, seshat:new(Group, invalid_fields, Fields)), - ok. -format_ratio() -> +format_ratio(_Config) -> Group = ?FUNCTION_NAME, Counters = [{pings, 1, ratio, "Some ratio that happens to be 0%"}, {pongs, 2, ratio, "Some ratio that happens to be 17%"}, {pangs, 3, ratio, "Some ratio that happens to be 33%"}, {rings, 4, ratio, "Some ratio that happens to be 100%"}], - seshat:new_group(Group), seshat:new(Group, test_component, Counters, #{component => test}), set_value(Group, test_component, pings, 0), set_value(Group, test_component, pongs, 17), set_value(Group, test_component, pangs, 33), set_value(Group, test_component, rings, 100), - Result = seshat:format(Group), ExpectedMap = #{pings => #{type => gauge, help => "Some ratio that happens to be 0%", @@ -197,58 +178,46 @@ format_ratio() -> ?assertEqual(ExpectedMap, Result), ok. - format_time_metrics() -> - Group = ?FUNCTION_NAME, - Counters = [ - {job_duration, 2, time_s, "Job duration"}, - {short_latency, 3, time_ms, "Short latency"}, - {long_latency, 1, time_ms, "Request latency"} - ], - seshat:new_group(Group), - Labels = #{component => test}, - seshat:new(Group, test_component, Counters, Labels), - - % Set values (1500 ms, 30 s, 5 ms) - set_value(Group, test_component, job_duration, 30), - set_value(Group, test_component, short_latency, 5), - set_value(Group, test_component, long_latency, 1500), - - MapResult = seshat:format(Group), - ExpectedMap = #{ - job_duration => #{type => gauge, - help => "Job duration", - values => #{Labels => 30.0}}, - short_latency => #{type => gauge, - help => "Short latency", - values => #{Labels => 0.005}}, - long_latency => #{type => gauge, - help => "Request latency", - values => #{Labels => 1.5}} - }, - ?assertEqual(ExpectedMap, MapResult), - - Prefix = "myapp", - MetricNames = [job_duration, short_latency, long_latency], % Added new metric name - TextResult = seshat:text_format(Group, Prefix, MetricNames), - - ExpectedLines = [ - "# HELP myapp_job_duration_seconds Job duration", - "# TYPE myapp_job_duration_seconds gauge", - "myapp_job_duration_seconds{component=\"test\"} 30.0", - "# HELP myapp_short_latency_seconds Short latency", - "# TYPE myapp_short_latency_seconds gauge", - "myapp_short_latency_seconds{component=\"test\"} 0.005", - "# HELP myapp_long_latency_seconds Request latency", - "# TYPE myapp_long_latency_seconds gauge", - "myapp_long_latency_seconds{component=\"test\"} 1.5" - ], - ExpectedResult = list_to_binary(string:join(ExpectedLines, "\n") ++ "\n"), +format_time_metrics(_Config) -> + Group = ?FUNCTION_NAME, + Counters = [ + {job_duration, 2, time_s, "Job duration"}, + {short_latency, 3, time_ms, "Short latency"}, + {long_latency, 1, time_ms, "Request latency"} + ], + Labels = #{component => test}, + seshat:new(Group, test_component, Counters, Labels), + set_value(Group, test_component, job_duration, 30), + set_value(Group, test_component, short_latency, 5), + set_value(Group, test_component, long_latency, 1500), + MapResult = seshat:format(Group), + ExpectedMap = #{ + job_duration => #{type => gauge, help => "Job duration", values => #{Labels => 30.0}}, + short_latency => #{type => gauge, help => "Short latency", values => #{Labels => 0.005}}, + long_latency => #{type => gauge, help => "Request latency", values => #{Labels => 1.5}} + }, + ?assertEqual(ExpectedMap, MapResult), + Prefix = "myapp", + MetricNames = [job_duration, short_latency, long_latency], + TextResult = seshat:text_format(Group, Prefix, MetricNames), + ExpectedLines = [ + "# HELP myapp_job_duration_seconds Job duration", + "# TYPE myapp_job_duration_seconds gauge", + "myapp_job_duration_seconds{component=\"test\"} 30.0", + "# HELP myapp_short_latency_seconds Short latency", + "# TYPE myapp_short_latency_seconds gauge", + "myapp_short_latency_seconds{component=\"test\"} 0.005", + "# HELP myapp_long_latency_seconds Request latency", + "# TYPE myapp_long_latency_seconds gauge", + "myapp_long_latency_seconds{component=\"test\"} 1.5" + ], + ExpectedResult = list_to_binary(string:join(ExpectedLines, "\n") ++ "\n"), - ?assertEqual(ExpectedResult, TextResult), - ok. + assertEqualIgnoringOrder(ExpectedResult, TextResult), + ok. -text_format_selected_metrics() -> - Group = widgets, +text_format_selected_metrics(_Config) -> + Group = ?FUNCTION_NAME, Counters = [ {reads, 1, counter, "Total reads"}, {writes, 2, counter, "Total writes"}, @@ -257,7 +226,6 @@ text_format_selected_metrics() -> {duration, 5, time_s, "Duration"}, {npc, 6, gauge, "A metric we don't request in a call to text_format/3"} ], - seshat:new_group(Group), seshat:new(Group, thing1, Counters, #{component => "thing1", version => "1.2.3"}), seshat:new(Group, thing2, Counters, #{component => "thing2", some_atom => atom_value}), seshat:new(Group, thing3, Counters, #{component => "thing3", some_binary => <<"binary_value">>}), @@ -266,19 +234,19 @@ text_format_selected_metrics() -> set_value(Group, thing1, cached, 10), set_value(Group, thing1, latency, 5), set_value(Group, thing1, duration, 123), - set_value(Group, thing1, npc, 1), % to be ignored + set_value(Group, thing1, npc, 1), % to be ignored set_value(Group, thing2, reads, 3), set_value(Group, thing2, writes, 4), set_value(Group, thing2, cached, 100), set_value(Group, thing2, latency, 6), set_value(Group, thing2, duration, 234), - set_value(Group, thing2, npc, 1), % to be ignored + set_value(Group, thing2, npc, 1), % to be ignored set_value(Group, thing3, reads, 1234), set_value(Group, thing3, writes, 4321), set_value(Group, thing3, cached, 17), set_value(Group, thing3, latency, 7), set_value(Group, thing3, duration, 345), - set_value(Group, thing3, npc, 1), % to be ignored + set_value(Group, thing3, npc, 1), % to be ignored Result = seshat:text_format(Group, "acme", [reads, writes, cached, latency, duration]), ExpectedLines = [ @@ -310,18 +278,18 @@ text_format_selected_metrics() -> ], ExpectedResult = list_to_binary(string:join(ExpectedLines, "\n") ++ "\n"), - ?assertEqual(ExpectedResult, Result), + assertEqualIgnoringOrder(ExpectedResult, Result), ok. -%% test helpers - +%% Helpers set_value(Group, Id, Name, Value) -> - [{Id, CRef, FieldSpec, _Labels}] = ets:lookup(seshat_counters_server:get_table(Group), Id), - Fields = resolve_fieldspec(FieldSpec), + Table = seshat_counters_server:get_table(Group), + [{Id, CRef, FieldSpec, _Labels}] = ets:lookup(Table, Id), + Fields = seshat:resolve_fields_spec(FieldSpec), {Name, Index, _Type, _Help} = lists:keyfind(Name, 1, Fields), - counters:put(CRef, Index, Value). + ok = counters:put(CRef, Index, Value). -resolve_fieldspec(Fields = FieldSpec) when is_list(FieldSpec) -> - Fields; -resolve_fieldspec({persistent_term, PTerm}) -> - persistent_term:get(PTerm). +assertEqualIgnoringOrder(Expected, Actual) -> + ExpectedSorted = lists:sort(binary:split(Expected, <<"\n">>, [global, trim])), + ActualSorted = lists:sort(binary:split(Actual, <<"\n">>, [global, trim])), + ?assertEqual(ExpectedSorted, ActualSorted).