aboutsummaryrefslogtreecommitdiffstats
path: root/src/rcl_prv_release.erl
diff options
context:
space:
mode:
Diffstat (limited to 'src/rcl_prv_release.erl')
-rw-r--r--src/rcl_prv_release.erl189
1 files changed, 189 insertions, 0 deletions
diff --git a/src/rcl_prv_release.erl b/src/rcl_prv_release.erl
new file mode 100644
index 0000000..7429e47
--- /dev/null
+++ b/src/rcl_prv_release.erl
@@ -0,0 +1,189 @@
+%% -*- 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 provider uses the lib_dir setting of the state. It searches the
+%%% Lib Dirs looking for all OTP Applications that are available. When it finds
+%%% those OTP Applications it loads the information about them and adds them to
+%%% the state of available apps. This implements the rcl_provider behaviour.
+-module(rcl_prv_release).
+
+-behaviour(rcl_provider).
+
+-export([init/1,
+ do/1,
+ format_error/1]).
+
+%%============================================================================
+%% API
+%%============================================================================
+-spec init(rcl_state:t()) -> {ok, rcl_state:t()}.
+init(State) ->
+ {ok, State}.
+
+%% @doc recursively dig down into the library directories specified in the state
+%% looking for OTP Applications
+-spec do(rcl_state:t()) -> {error, Reason::term()} | {ok, rcl_state:t()}.
+do(State) ->
+ DepGraph = create_dep_graph(State),
+ find_default_release(State, DepGraph).
+
+-spec format_error({error, ErrorDetail::term()}) -> iolist().
+format_error({error, {no_release_name, Vsn}}) ->
+ io_lib:format("A target release version was specified (~s) but no name", [Vsn]);
+format_error({error, {invalid_release_info, Info}}) ->
+ io_lib:format("Target release information is in an invalid format ~p", [Info]);
+format_error({error, {multiple_release_names, RelA, RelB}}) ->
+ io_lib:format("No default release name was specified and there are multiple "
+ "releases in the config: ~s, ~s",
+ [RelA, RelB]);
+format_error({error, no_releases_in_system}) ->
+ "No releases have been specified in the system!";
+format_error({error, {no_releases_for, RelName}}) ->
+ io_lib:format("No releases exist in the system for ~s!", [RelName]);
+format_error({release_not_found, {RelName, RelVsn}}) ->
+ io_lib:format("No releases exist in the system for ~p:~s!", [RelName, RelVsn]);
+format_error({failed_solve, Error}) ->
+ io_lib:format("Failed to solve release:\n ~s",
+ [depsolver:format_error({error, Error})]).
+
+%%%===================================================================
+%%% Internal Functions
+%%%===================================================================
+-spec create_dep_graph(rcl_state:t()) -> depsolver:t().
+create_dep_graph(State) ->
+ Apps = rcl_state:available_apps(State),
+ Graph0 = depsolver:new_graph(),
+ lists:foldl(fun(App, Graph1) ->
+ AppName = rcl_app_info:name(App),
+ AppVsn = rcl_app_info:vsn(App),
+ Deps = rcl_app_info:active_deps(App) ++
+ rcl_app_info:library_deps(App),
+ depsolver:add_package_version(Graph1,
+ AppName,
+ AppVsn,
+ Deps)
+ end, Graph0, Apps).
+
+
+-spec find_default_release(rcl_state:t(), depsolver:t()) ->
+ {ok, rcl_state:t()} | {error, Reason::term()}.
+find_default_release(State, DepGraph) ->
+ case rcl_state:default_release(State) of
+ {undefined, undefined} ->
+ resolve_default_release(State, DepGraph);
+ {RelName, undefined} ->
+ resolve_default_version(State, DepGraph, RelName);
+ {undefined, Vsn} ->
+ {error, {no_release_name, Vsn}};
+ {RelName, RelVsn} ->
+ solve_release(State, DepGraph, RelName, RelVsn)
+ end.
+
+resolve_default_release(State0, DepGraph) ->
+ %% Here we will just get the lastest version and run that.
+ case lists:sort(fun release_sort/2,
+ ec_dictionary:to_list(rcl_state:releases(State0))) of
+ All = [{{RelName, RelVsn}, _} | _] ->
+ State1 = rcl_state:default_release(State0, RelName, RelVsn),
+ lists:foldl(fun({{RN, RV}, _}, {ok, State2}) ->
+ solve_release(State2,
+ DepGraph, RN, RV);
+ (_, E) ->
+ E
+ end, {ok, State1}, All);
+ [] ->
+ ?RCL_ERROR(no_releases_in_system)
+ end.
+
+resolve_default_version(State0, DepGraph, RelName) ->
+ %% Here we will just get the lastest version and run that.
+ AllReleases = ec_dictionary:to_list(rcl_state:releases(State0)),
+ SpecificReleases = [Rel || Rel={{PossibleRelName, _}, _} <- AllReleases,
+ PossibleRelName =:= RelName],
+ case lists:sort(fun release_sort/2, SpecificReleases) of
+ All = [{{RelName, RelVsn}, _} | _] ->
+ State1 = rcl_state:default_release(State0, RelName, RelVsn),
+ lists:foldl(fun({RN, RV}, {ok, State2}) ->
+ solve_release(State2,
+ DepGraph, RN, RV);
+ (_, E) ->
+ E
+ end, {ok, State1}, All);
+ [] ->
+ ?RCL_ERROR({no_releases_for, RelName})
+ end.
+
+
+-spec release_sort({{rcl_release:name(),rcl_release:vsn()}, term()},
+ {{rcl_release:name(),rcl_release:vsn()}, term()}) ->
+ boolean().
+release_sort({{RelName, RelVsnA}, _},
+ {{RelName, RelVsnB}, _}) ->
+ ec_semver:lte(RelVsnA, RelVsnB);
+release_sort({{RelNameA, RelVsnA}, _}, {{RelNameB, RelVsnB}, _}) ->
+ %% The release names are different. When the releases are named differently
+ %% we can not just take the lastest version. You *must* provide a default
+ %% release name at least. So we throw an error here that the top can catch
+ %% and return
+ erlang:atom_to_list(RelNameA) =< erlang:atom_to_list(RelNameB) andalso
+ ec_semver:lte(RelVsnA, RelVsnB).
+
+
+
+solve_release(State0, DepGraph, RelName, RelVsn) ->
+ try
+ Release = rcl_state:get_release(State0, RelName, RelVsn),
+ Goals = rcl_release:goals(Release),
+ case depsolver:solve(DepGraph, Goals) of
+ {ok, Pkgs} ->
+ set_resolved(State0, Release, Pkgs);
+ {error, Error} ->
+ {error, {failed_solve, Error}}
+ end
+ catch
+ throw:not_found ->
+ {error, {release_not_found, RelName, RelVsn}}
+ end.
+
+set_resolved(State, Release0, Pkgs) ->
+ case rcl_release:realize(Release0, Pkgs, rcl_state:available_apps(State)) of
+ {ok, Release1} ->
+ rcl_log:info(rcl_state:log(State),
+ "Resolved ~p-~s",
+ [rcl_release:name(Release1),
+ rcl_release:vsn(Release1)]),
+ rcl_log:debug(rcl_state:log(State),
+ fun() ->
+ rcl_release:format(1, Release1)
+ end),
+ {ok, rcl_state:update_release(State, Release1)};
+ {error, E} ->
+ {error, {release_error, E}}
+ end.
+
+
+%%%===================================================================
+%%% Test Functions
+%%%===================================================================
+
+-ifndef(NOTEST).
+-include_lib("eunit/include/eunit.hrl").
+
+-endif.