aboutsummaryrefslogtreecommitdiffstats
path: root/src/rlx_state.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rlx_state.erl')
-rw-r--r--src/rlx_state.erl354
1 files changed, 354 insertions, 0 deletions
diff --git a/src/rlx_state.erl b/src/rlx_state.erl
new file mode 100644
index 0000000..9b0811f
--- /dev/null
+++ b/src/rlx_state.erl
@@ -0,0 +1,354 @@
+%% -*- erlang-indent-level: 4; indent-tabs-mode: nil; fill-column: 80 -*-
+%%% 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 Provides state management services for the relx tool. Generally,
+%%% those things that are fixed have a direct api. Those things that are mutable
+%%% have a more mutable api.
+-module(rlx_state).
+
+-export([new/2,
+ log/1,
+ action/1,
+ output_dir/1,
+ lib_dirs/1,
+ overrides/1,
+ overrides/2,
+ skip_apps/1,
+ skip_apps/2,
+ goals/1,
+ config_file/1,
+ config_file/2,
+ providers/1,
+ providers/2,
+ sys_config/1,
+ sys_config/2,
+ root_dir/1,
+ root_dir/2,
+ add_configured_release/2,
+ get_configured_release/3,
+ configured_releases/1,
+ realized_releases/1,
+ realized_releases/2,
+ add_realized_release/2,
+ get_realized_release/3,
+ update_realized_release/2,
+ default_configured_release/1,
+ default_configured_release/3,
+ available_apps/1,
+ available_apps/2,
+ get/2,
+ get/3,
+ put/3,
+ caller/1,
+ caller/2,
+ upfrom/1,
+ format/1,
+ format/2]).
+
+
+-export_type([t/0,
+ releases/0,
+ cmd_args/0]).
+
+-record(state_t, {log :: rlx_log:t(),
+ root_dir :: file:name(),
+ caller :: caller(),
+ action :: atom(),
+ output_dir :: file:name(),
+ lib_dirs=[] :: [file:name()],
+ config_file=[] :: file:filename() | undefined,
+ goals=[] :: [rlx_depsolver:constraint()],
+ providers = [] :: [rlx_provider:t()],
+ available_apps = [] :: [rlx_app_info:t()],
+ default_configured_release :: {rlx_release:name(), rlx_release:vsn()},
+ sys_config :: file:filename() | undefined,
+ overrides :: [{AppName::atom(), Directory::file:filename()}],
+ skip_apps = [] :: [AppName::atom()],
+ configured_releases :: releases(),
+ realized_releases :: releases(),
+ upfrom :: string() | binary() | undefined,
+ config_values :: ec_dictionary:dictionary(Key::atom(),
+ Value::term())}).
+
+%%============================================================================
+%% types
+%%============================================================================
+
+-type releases() :: ec_dictionary:dictionary({rlx_release:name(),
+ rlx_release:vsn()},
+ rlx_release:t()).
+-type cmd_args() :: proplists:proplist().
+-type caller() :: command_line | api.
+
+-opaque t() :: record(state_t).
+
+%%============================================================================
+%% API
+%%============================================================================
+%% @doc Create a new 'log level' for the system
+-spec new(proplists:proplist(), atom()) -> t().
+new(PropList, Target)
+ when erlang:is_list(PropList),
+ erlang:is_atom(Target) ->
+ {ok, Root} = file:get_cwd(),
+ State0 =
+ #state_t{log = proplists:get_value(log, PropList, rlx_log:new(error)),
+ output_dir=proplists:get_value(output_dir, PropList, ""),
+ lib_dirs=[to_binary(Dir) || Dir <- proplists:get_value(lib_dirs, PropList, [])],
+ config_file=proplists:get_value(config, PropList, undefined),
+ action = Target,
+ caller = proplists:get_value(caller, PropList, api),
+ goals=proplists:get_value(goals, PropList, []),
+ providers = [],
+ configured_releases=ec_dictionary:new(ec_dict),
+ realized_releases=ec_dictionary:new(ec_dict),
+ config_values=ec_dictionary:new(ec_dict),
+ overrides = proplists:get_value(overrides, PropList, []),
+ root_dir = proplists:get_value(root_dir, PropList, Root),
+ upfrom = proplists:get_value(upfrom, PropList, undefined),
+ default_configured_release={proplists:get_value(relname, PropList, undefined),
+ proplists:get_value(relvsn, PropList, undefined)}},
+ rlx_state:put(create_logic_providers(State0),
+ disable_default_libs,
+ proplists:get_value(disable_default_libs, PropList, false)).
+
+%% @doc the action targeted for this system
+-spec action(t()) -> atom().
+action(#state_t{action=Action}) ->
+ Action.
+
+%% @doc the application overrides for the system
+-spec overrides(t()) -> [{AppName::atom(), Directory::file:filename()}].
+overrides(#state_t{overrides=Overrides}) ->
+ Overrides.
+
+%% @doc the application overrides for the system
+-spec overrides(t(), [{AppName::atom(), Directory::file:filename()}]) -> t().
+overrides(State, Overrides) ->
+ State#state_t{overrides=Overrides}.
+
+
+-spec skip_apps(t()) -> [AppName::atom()].
+skip_apps(#state_t{skip_apps=Apps}) ->
+ Apps.
+
+%% @doc the application overrides for the system
+-spec skip_apps(t(), [AppName::atom()]) -> t().
+skip_apps(State, SkipApps) ->
+ State#state_t{skip_apps=SkipApps}.
+
+%% @doc get the current log state for the system
+-spec log(t()) -> rlx_log:t().
+log(#state_t{log=LogState}) ->
+ LogState.
+
+-spec output_dir(t()) -> file:name().
+output_dir(#state_t{output_dir=OutDir}) ->
+ OutDir.
+
+-spec lib_dirs(t()) -> [file:name()].
+lib_dirs(#state_t{lib_dirs=LibDir}) ->
+ LibDir.
+
+-spec goals(t()) -> [rlx_depsolver:constraint()].
+goals(#state_t{goals=TS}) ->
+ TS.
+
+-spec config_file(t()) -> file:filename() | undefined.
+config_file(#state_t{config_file=ConfigFiles}) ->
+ ConfigFiles.
+
+-spec config_file(t(), file:filename() | undefined) -> t().
+config_file(State, ConfigFiles) ->
+ State#state_t{config_file=ConfigFiles}.
+
+-spec providers(t()) -> [rlx_provider:t()].
+providers(#state_t{providers=Providers}) ->
+ Providers.
+
+-spec sys_config(t()) -> file:filename() | undefined.
+sys_config(#state_t{sys_config=SysConfig}) ->
+ SysConfig.
+
+-spec sys_config(t(), file:filename()) -> t().
+sys_config(State, SysConfig) ->
+ State#state_t{sys_config=SysConfig}.
+
+-spec root_dir(t()) -> file:filename() | undefined.
+root_dir(#state_t{root_dir=RootDir}) ->
+ RootDir.
+
+-spec root_dir(t(), file:filename()) -> t().
+root_dir(State, RootDir) ->
+ State#state_t{root_dir=RootDir}.
+
+-spec providers(t(), [rlx_provider:t()]) -> t().
+providers(M, NewProviders) ->
+ M#state_t{providers=NewProviders}.
+
+-spec add_configured_release(t(), rlx_release:t()) -> t().
+add_configured_release(M=#state_t{configured_releases=Releases}, Release) ->
+ M#state_t{configured_releases=ec_dictionary:add({rlx_release:name(Release),
+ rlx_release:vsn(Release)},
+ Release,
+ Releases)}.
+
+-spec get_configured_release(t(), rlx_release:name(), rlx_release:vsn()) -> rlx_release:t().
+get_configured_release(#state_t{configured_releases=Releases}, Name, Vsn) ->
+ ec_dictionary:get({Name, Vsn}, Releases).
+
+-spec configured_releases(t()) -> releases().
+configured_releases(#state_t{configured_releases=Releases}) ->
+ Releases.
+
+-spec realized_releases(t()) -> releases().
+realized_releases(#state_t{realized_releases=Releases}) ->
+ Releases.
+
+-spec realized_releases(t(), releases()) -> t().
+realized_releases(State, Releases) ->
+ State#state_t{realized_releases=Releases}.
+
+-spec add_realized_release(t(), rlx_release:t()) -> t().
+add_realized_release(State = #state_t{realized_releases=Releases}, Release) ->
+ NewReleases = ec_dictionary:add({rlx_release:name(Release), rlx_release:vsn(Release)},
+ Release, Releases),
+ State#state_t{realized_releases=NewReleases}.
+
+-spec get_realized_release(t(), rlx_release:name(), rlx_release:vsn()) -> rlx_release:t().
+get_realized_release(#state_t{realized_releases=Releases}, Name, Vsn) ->
+ ec_dictionary:get({Name, Vsn}, Releases).
+
+-spec update_realized_release(t(), rlx_release:t()) ->
+ t().
+update_realized_release(M=#state_t{realized_releases=Releases}, Release) ->
+ M#state_t{realized_releases=ec_dictionary:add({rlx_release:name(Release),
+ rlx_release:vsn(Release)},
+ Release,
+ Releases)}.
+
+-spec default_configured_release(t()) ->
+ {rlx_release:name() | undefined, rlx_release:vsn() | undefined}.
+default_configured_release(#state_t{default_configured_release=Def}) ->
+ Def.
+
+-spec default_configured_release(t(), rlx_release:name(), rlx_release:vsn()) -> t().
+default_configured_release(M, Name, Vsn) ->
+ M#state_t{default_configured_release={Name, Vsn}}.
+
+-spec available_apps(t()) -> [rlx_app_info:t()].
+available_apps(#state_t{available_apps=Apps}) ->
+ Apps.
+
+-spec available_apps(t(), [rlx_app_info:t()]) -> t().
+available_apps(M, NewApps) ->
+ M#state_t{available_apps=NewApps}.
+
+-spec get(t(), atom()) -> term().
+get(#state_t{config_values=Config}, Key)
+ when erlang:is_atom(Key) ->
+ ec_dictionary:get(Key, Config).
+
+-spec get(t(), atom(), DefaultValue::term()) -> term().
+get(#state_t{config_values=Config}, Key, DefaultValue)
+ when erlang:is_atom(Key) ->
+ try
+ ec_dictionary:get(Key, Config)
+ catch
+ throw:not_found ->
+ DefaultValue
+ end.
+
+-spec put(t(), atom(), term()) ->t().
+put(M=#state_t{config_values=Config}, Key, Value)
+ when erlang:is_atom(Key) ->
+ M#state_t{config_values=ec_dictionary:add(Key, Value, Config)}.
+
+-spec caller(t()) -> caller().
+caller(#state_t{caller=Caller}) ->
+ Caller.
+
+-spec caller(t(), caller()) -> t().
+caller(S, Caller) ->
+ S#state_t{caller=Caller}.
+
+-spec upfrom(t()) -> string() | binary() | undefined.
+upfrom(#state_t{upfrom=UpFrom}) ->
+ UpFrom.
+
+-spec format(t()) -> iolist().
+format(Mod) ->
+ format(Mod, 0).
+
+-spec format(t(), non_neg_integer()) -> iolist().
+format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs,
+ caller=Caller, config_values=Values0,
+ goals=Goals, config_file=ConfigFile,
+ providers=Providers},
+ Indent) ->
+ Values1 = ec_dictionary:to_list(Values0),
+ [rlx_util:indent(Indent),
+ <<"state(">>, erlang:atom_to_list(Caller), <<"):\n">>,
+ rlx_util:indent(Indent + 1), <<"log: ">>, rlx_log:format(LogState), <<",\n">>,
+ rlx_util:indent(Indent + 1), "config file: ", rlx_util:optional_to_string(ConfigFile), "\n",
+ rlx_util:indent(Indent + 1), "goals: \n",
+ [[rlx_util:indent(Indent + 2), rlx_depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals],
+ rlx_util:indent(Indent + 1), "output_dir: ", OutDir, "\n",
+ rlx_util:indent(Indent + 1), "lib_dirs: \n",
+ [[rlx_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs],
+ rlx_util:indent(Indent + 1), "providers: \n",
+ [[rlx_util:indent(Indent + 2), rlx_provider:format(Provider), ",\n"] || Provider <- Providers],
+ rlx_util:indent(Indent + 1), "provider config values: \n",
+ [[rlx_util:indent(Indent + 2), io_lib:format("~p", [Value]), ",\n"] || Value <- Values1]].
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+
+-spec create_logic_providers(t()) -> t().
+create_logic_providers(State0) ->
+ {ConfigProvider, {ok, State1}} = rlx_provider:new(rlx_prv_config, State0),
+ {DiscoveryProvider, {ok, State2}} = rlx_provider:new(rlx_prv_discover, State1),
+ {ReleaseProvider, {ok, State3}} = rlx_provider:new(rlx_prv_release, State2),
+ {OverlayProvider, {ok, State4}} = rlx_provider:new(rlx_prv_overlay, State3),
+ {AssemblerProvider, {ok, State5}} = rlx_provider:new(rlx_prv_assembler, State4),
+ State5#state_t{providers=[ConfigProvider, DiscoveryProvider,
+ ReleaseProvider, OverlayProvider, AssemblerProvider]}.
+
+to_binary(Dir)
+ when erlang:is_list(Dir) ->
+ erlang:list_to_binary(Dir);
+to_binary(Dir)
+ when erlang:is_binary(Dir) ->
+ Dir.
+
+%%%===================================================================
+%%% Test Functions
+%%%===================================================================
+
+-ifndef(NOTEST).
+-include_lib("eunit/include/eunit.hrl").
+
+new_test() ->
+ LogState = rlx_log:new(error),
+ RCLState = new([{log, LogState}], release),
+ ?assertMatch(LogState, log(RCLState)).
+
+-endif.