From 6f06c48a9ce7ff9b5a01b9210b7314ba4a3b24e9 Mon Sep 17 00:00:00 2001 From: Eric Date: Tue, 18 Sep 2012 10:08:30 -0700 Subject: support release generation in the system --- src/rcl_prv_release.erl | 189 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 189 insertions(+) create mode 100644 src/rcl_prv_release.erl (limited to 'src/rcl_prv_release.erl') 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 +%%% @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. -- cgit v1.2.3