diff options
-rw-r--r-- | src/rcl_release.erl | 183 |
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}}. |