aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/rcl_release.erl183
1 files changed, 183 insertions, 0 deletions
diff --git a/src/rcl_release.erl b/src/rcl_release.erl
new file mode 100644
index 0000000..889c0ee
--- /dev/null
+++ b/src/rcl_release.erl
@@ -0,0 +1,183 @@
+%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
+%%% Copyright 2012 Erlware, LLC. All Rights Reserved.
+%%%
+%%% This file is provided to you under the Apache License,
+%%% Version 2.0 (the "License"); you may not use this file
+%%% except in compliance with the License. You may obtain
+%%% a copy of the License at
+%%%
+%%% http://www.apache.org/licenses/LICENSE-2.0
+%%%
+%%% Unless required by applicable law or agreed to in writing,
+%%% software distributed under the License is distributed on an
+%%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+%%% KIND, either express or implied. See the License for the
+%%% specific language governing permissions and limitations
+%%% under the License.
+%%%---------------------------------------------------------------------------
+%%% @author Eric Merritt <[email protected]>
+%%% @copyright (C) 2012 Erlware, LLC.
+%%%
+%%% @doc This module represents a release and its metadata and is used to
+%%% manipulate the release metadata.
+-module(rcl_release).
+
+-export([new/2,
+ erts/2,
+ erts/1,
+ goals/2,
+ goals/1,
+ name/1,
+ vsn/1,
+ format/1,
+ format/2]).
+
+-export_type([t/0,
+ name/0,
+ vsn/0,
+ app_name/0,
+ app_vsn/0,
+ app_type/0,
+ application_spec/0,
+ application_goal/0]).
+
+-record(release_t, {name :: atom(),
+ vsn :: ec_semver:any_version(),
+ erts :: ec_semver:any_version(),
+ goals = [] :: [depsolver:constraint()],
+ realized = false :: boolean(),
+ annotations = undefined :: ec_dictionary:dictionary(app_name(),
+ app_type() |
+ incl_apps() |
+ {app_type(), incl_apps()}),
+ applications = []:: [application_spec()]}).
+
+%%============================================================================
+%% types
+%%============================================================================
+-type name() :: atom().
+-type vsn() :: string().
+-type app_name() :: atom().
+-type app_vsn() :: string().
+-type app_type() :: permanent | transient | temporary | load | none.
+-type incl_apps() :: [app_name()].
+
+-type application_spec() :: {app_name(), app_vsn()} |
+ {app_name(), app_vsn(), app_type() | incl_apps()} |
+ {app_name(), app_vsn(), app_type(), incl_apps()}.
+
+-type application_goal() :: depsolver:constraint() |
+ {depsolver:constraint(), app_type() | incl_apps()} |
+ {depsolver:constraint(), app_type(), incl_apps()}.
+
+-opaque t() :: record(release_t).
+
+%%============================================================================
+%% API
+%%============================================================================
+-spec new(atom(), string()) -> t().
+new(ReleaseName, ReleaseVsn) ->
+ #release_t{name=ReleaseName, vsn=ReleaseVsn,
+ annotations=ec_dictionary:new(ec_dict)}.
+
+-spec name(t()) -> atom().
+name(#release_t{name=Name}) ->
+ Name.
+
+-spec vsn(t()) -> string().
+vsn(#release_t{vsn=Vsn}) ->
+ Vsn.
+
+-spec erts(t(), app_vsn()) -> t().
+erts(Release, Vsn) ->
+ Release#release_t{erts=Vsn}.
+
+-spec erts(t()) -> app_vsn().
+erts(#release_t{erts=Vsn}) ->
+ Vsn.
+
+-spec goals(t(), [application_goal()]) -> {ok, t()} | {error, Reason::term()}.
+goals(Release, Goals0) ->
+ lists:foldl(fun parse_goal0/2,
+ {ok, Release}, Goals0).
+
+-spec goals(t()) -> [application_goal()].
+goals(#release_t{goals=Goals}) ->
+ Goals.
+
+-spec format(t()) -> iolist().
+format(Release) ->
+ format(0, Release).
+
+-spec format(non_neg_integer(), t()) -> iolist().
+format(Indent, #release_t{name=Name, vsn=Vsn, erts=ErtsVsn, realized=Realized,
+ goals = Goals, applications = Apps}) ->
+ BaseIndent = rcl_util:indent(Indent),
+ [BaseIndent, "release: ", erlang:atom_to_list(Name), "-", Vsn,
+ " erts-", ErtsVsn, ", realized = ", erlang:atom_to_list(Realized), "\n",
+ BaseIndent, "goals: \n",
+ [[rcl_util:indent(Indent + 1), format_goal(Goal), ",\n"] || Goal <- Goals],
+ case Realized of
+ true ->
+ [BaseIndent, "applications: \n",
+ [[rcl_util:indent(Indent + 1), io_lib:format("~p", [App]), ",\n"]
+ || App <- Apps]];
+ false ->
+ []
+ end].
+-spec format_goal(application_goal()) -> iolist().
+format_goal({Constraint, AppType}) ->
+ io_lib:format("~p", [{depsolver:format_constraint(Constraint), AppType}]);
+format_goal({Constraint, AppType, AppInc}) ->
+ io_lib:format("~p", [{depsolver:format_constraint(Constraint), AppType, AppInc}]);
+format_goal(Constraint) ->
+ depsolver:format_constraint(Constraint).
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+-spec parse_goal0(application_goal(), {ok, t()} | {error, Reason::term()}) ->
+ {ok, t()} | {error, Reason::term()}.
+parse_goal0({Constraint0, Annots}, {ok, Release}) ->
+ parse_goal1(Release, Constraint0, Annots);
+parse_goal0({Constraint0, AnnotsA, AnnotsB}, {ok, Release}) ->
+ parse_goal1(Release, Constraint0, {AnnotsA, AnnotsB});
+parse_goal0(Constraint0, {ok, Release = #release_t{goals=Goals}}) ->
+ case rcl_goal:parse(Constraint0) of
+ E = {error, _} ->
+ E;
+ Constraint1 ->
+ {ok, Release#release_t{goals = [Constraint1 | Goals]}}
+ end;
+parse_goal0(_, E = {error, _}) ->
+ E.
+
+-spec parse_goal1(t(), depsolver:constraint() | string(),
+ app_type() | incl_apps() | {app_type(), incl_apps()}) ->
+ {ok, t()} | {error, Reason::term()}.
+parse_goal1(Release = #release_t{annotations=Annots, goals=Goals},
+ Constraint0, NewAnnots) ->
+ case rcl_goal:parse(Constraint0) of
+ E0 = {error, _} ->
+ E0;
+ Constraint1 ->
+ case get_app_name(Constraint1) of
+ E1 = {error, _} ->
+ E1;
+ AppName ->
+ {ok,
+ Release#release_t{annotations=ec_dictionary:add(AppName, NewAnnots, Annots),
+ goals = [Constraint1 | Goals]}}
+ end
+ end.
+
+-spec get_app_name(depsolver:constraint()) ->
+ AppName::atom() | {error, {invalid_constraint, term()}}.
+get_app_name(AppName) when erlang:is_atom(AppName) ->
+ AppName;
+get_app_name({AppName, _, _}) when erlang:is_atom(AppName) ->
+ AppName;
+get_app_name({AppName, _, _, _}) when erlang:is_atom(AppName) ->
+ AppName;
+get_app_name(V) ->
+ {error, {invalid_constraint, V}}.