From 2311bc5cbe06dcf2cb03d9b0a93392e1d9095bbb Mon Sep 17 00:00:00 2001 From: Eric Date: Wed, 12 Sep 2012 16:27:41 -0500 Subject: support OTP App discovery in the system --- .gitignore | 3 +- src/rcl_app_info.erl | 134 ++++++++++++++++++++++++++++++ src/rcl_prv_discover.erl | 175 ++++++++++++++++++++++++++++++++++++++ src/rcl_state.erl | 120 ++++++++++++++++++++++---- test/rclt_discover_SUITE.erl | 194 +++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 609 insertions(+), 17 deletions(-) create mode 100644 src/rcl_app_info.erl create mode 100644 src/rcl_prv_discover.erl create mode 100644 test/rclt_discover_SUITE.erl diff --git a/.gitignore b/.gitignore index 61eb48f..ed62865 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ ebin/* relcool # This is a generated file that should be ignored src/rcl_goal.erl -logs \ No newline at end of file +logs +test/*_data \ No newline at end of file diff --git a/src/rcl_app_info.erl b/src/rcl_app_info.erl new file mode 100644 index 0000000..eaf3302 --- /dev/null +++ b/src/rcl_app_info.erl @@ -0,0 +1,134 @@ +%% -*- 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 module represents useful, relevant information about an +%%% application. The relevant information is. +%%% +%%% +%%% +-module(rcl_app_info). + +-export([new/0, + new/5, + name/1, + name/2, + vsn/1, + vsn/2, + dir/1, + dir/2, + active_deps/1, + active_deps/2, + library_deps/1, + library_deps/2]). + +-export_type([t/0]). + +-record(app_info_t, {name :: atom(), + vsn :: string(), + dir :: file:name(), + active_deps :: [atom()], + library_deps :: [atom()]}). + +%%============================================================================ +%% types +%%============================================================================ +-opaque t() :: record(app_info_t). + +%%============================================================================ +%% API +%% ============================================================================ +%% @doc Build a new, empty, app info value. This is not of a lot of use and you +%% probably wont be doing this much. +-spec new() -> t(). +new() -> + #app_info_t{}. + +%% @doc build a complete version of the app info with all fields set. +-spec new(atom(), string(), file:name(), [atom()], [atom()]) -> t(). +new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) + when erlang:is_atom(AppName), + erlang:is_list(Vsn), + erlang:is_list(Dir), + erlang:is_list(ActiveDeps), + erlang:is_list(LibraryDeps) -> + #app_info_t{name=AppName, vsn=Vsn, dir=Dir, + active_deps=ActiveDeps, + library_deps=LibraryDeps}. + +-spec name(t()) -> atom(). +name(#app_info_t{name=Name}) -> + Name. + +-spec name(t(), atom()) -> t(). +name(AppInfo=#app_info_t{}, AppName) + when erlang:is_atom(AppName) -> + AppInfo#app_info_t{name=AppName}. + +-spec vsn(t()) -> string(). +vsn(#app_info_t{vsn=Vsn}) -> + Vsn. + +-spec vsn(t(), string()) -> t(). +vsn(AppInfo=#app_info_t{}, AppVsn) + when erlang:is_list(AppVsn) -> + AppInfo#app_info_t{vsn=AppVsn}. + +-spec dir(t()) -> file:name(). +dir(#app_info_t{dir=Dir}) -> + Dir. +-spec dir(t(), file:name()) -> t(). +dir(AppInfo=#app_info_t{}, Dir) -> + AppInfo#app_info_t{dir=Dir}. + +-spec active_deps(t()) -> [atom()]. +active_deps(#app_info_t{active_deps=Deps}) -> + Deps. +-spec active_deps(t(), [atom()]) -> t(). +active_deps(AppInfo=#app_info_t{}, ActiveDeps) + when erlang:is_list(ActiveDeps) -> + AppInfo#app_info_t{active_deps=ActiveDeps}. + +-spec library_deps(t()) -> [atom()]. +library_deps(#app_info_t{library_deps=Deps}) -> + Deps. + +-spec library_deps(t(), [atom()]) -> t(). +library_deps(AppInfo=#app_info_t{}, LibraryDeps) + when erlang:is_list(LibraryDeps) -> + AppInfo#app_info_t{library_deps=LibraryDeps}. + + + + + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== diff --git a/src/rcl_prv_discover.erl b/src/rcl_prv_discover.erl new file mode 100644 index 0000000..e9a6dea --- /dev/null +++ b/src/rcl_prv_discover.erl @@ -0,0 +1,175 @@ +%% -*- 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_discover). + +-behaviour(rcl_provider). + +-export([init/1, + do/1, + format/1]). + +%%============================================================================ +%% API +%%============================================================================ +-spec init(rcl_state:t()) -> {ok, rcl_state:t()}. +init(State) -> + {ok, State}. + +-spec do(rcl_state:t()) -> {error, Reason::term()} | {ok, rcl_state:t()}. +do(State) -> + LibDirs = rcl_state:lib_dirs(State), + AppMeta = lists:flatten(ec_plists:map(fun discover_dir/1, LibDirs)), + Errors = [case El of + {error, Ret} -> Ret; + _ -> El + end + || El <- AppMeta, + case El of + {error, _} -> + true; + _ -> + false + end], + + lists:filter(fun({error, _}) -> true; + (_) -> false + end, AppMeta), + case Errors of + [] -> + {ok, rcl_state:available_apps(State, lists:flatten(AppMeta))}; + _ -> + {error, Errors} + end. + +-spec format({error, [ErrorDetail::term()]}) -> iolist(). +format({error, ErrorDetails}) -> + [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({accessing, File, eaccess}) -> + io_lib:format("permission denied accessing file ~s", [File]); +format_detail({accessing, File, Type}) -> + io_lib:format("error (~p) accessing file ~s", [Type, File]); +format_detail({error, {no_beam_files, EbinDir}}) -> + io_lib:format("no beam files found in directory ~s", [EbinDir]); +format_detail({error, {not_a_directory, EbinDir}}) -> + io_lib:format("~s is not a directory when it should be a directory", [EbinDir]); +format_detail({error, {unable_to_load_app, AppDir, _}}) -> + io_lib:format("Unable to load the application metadata from ~s", [AppDir]); +format_detail({error, {invalid_app_file, File}}) -> + io_lib:format("Application metadata file exists but is malformed: ~s", + [File]); +format_detail({error, {unversioned_app, AppDir, _AppName}}) -> + io_lib:format("Application metadata exists but version is not available: ~s", + [AppDir]). + +-spec discover_dir(file:name()) -> + [rcl_app_info:t() | {error, Reason::term()}] | + rcl_app_info:t() | {error, Reason::term()}. +discover_dir(File) -> + case filelib:is_dir(File) of + true -> + case file:list_dir(File) of + {error, Reason} -> + {error, {accessing, File, Reason}}; + {ok, List} -> + ec_plists:map(fun discover_dir/1, [filename:join([File, Dir]) || Dir <- List]) + end; + false -> + is_valid_otp_app(File) + end. + +-spec is_valid_otp_app(file:name()) -> [rcl_app_info:t() | {error, Reason::term()} | []]. +is_valid_otp_app(File) -> + %% Is this an ebin dir? + EbinDir = filename:dirname(File), + case filename:basename(EbinDir) of + "ebin" -> + case lists:suffix(".app", File) of + true -> + has_at_least_one_beam(EbinDir, File); + false -> + [] + end; + _ -> + [] + end. + +-spec has_at_least_one_beam(file:name(), file:filename()) -> + rcl_app_info:t() | {error, Reason::term()}. +has_at_least_one_beam(EbinDir, File) -> + case file:list_dir(EbinDir) of + {ok, List} -> + case lists:any(fun(NFile) -> lists:suffix(".beam", NFile) end, List) of + true -> + gather_application_info(EbinDir, File); + false -> + {error, {no_beam_files, EbinDir}} + end; + _ -> + {error, {not_a_directory, EbinDir}} + end. + +-spec gather_application_info(file:name(), file:filename()) -> + rcl_app_info:t() | {error, Reason::term()}. +gather_application_info(EbinDir, File) -> + AppDir = filename:dirname(EbinDir), + case file:consult(File) of + {ok, [{application, AppName, AppDetail}]} -> + get_vsn(AppDir, AppName, AppDetail); + {error, Reason} -> + {error, {unable_to_load_app, AppDir, Reason}}; + _ -> + {error, {invalid_app_file, File}} + end. + +-spec get_vsn(file:name(), atom(), proplists:proplist()) -> + rcl_app_info:t() | {error, Reason::term()}. +get_vsn(AppDir, AppName, AppDetail) -> + case proplists:get_value(vsn, AppDetail) of + undefined -> + {error, {unversioned_app, AppDir, AppName}}; + AppVsn -> + get_deps(AppDir, AppName, AppVsn, AppDetail) + end. + +-spec get_deps(file:name(), atom(), string(), proplists:proplist()) -> + rcl_app_info:t(). +get_deps(AppDir, AppName, AppVsn, AppDetail) -> + ActiveApps = proplists:get_value(applications, AppDetail, []), + LibraryApps = proplists:get_value(included_applications, AppDetail, []), + rcl_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps). + +%%%=================================================================== +%%% Test Functions +%%%=================================================================== + +-ifndef(NOTEST). +-include_lib("eunit/include/eunit.hrl"). + +-endif. diff --git a/src/rcl_state.erl b/src/rcl_state.erl index 1b25ce3..c399b42 100644 --- a/src/rcl_state.erl +++ b/src/rcl_state.erl @@ -29,24 +29,47 @@ lib_dirs/1, goals/1, config_files/1, + providers/1, + providers/2, + add_release/2, + get_release/3, + releases/1, + default_release/1, + default_release/3, + available_apps/1, + available_apps/2, format/1, format/2]). + -export_type([t/0, + app_descriptor/0, + releases/0, cmd_args/0]). --record(?MODULE, {log :: rcl_log:t(), +-record(state_t, {log :: rcl_log:t(), output_dir :: file:name(), lib_dirs=[] :: [file:name()], config_files=[] :: [file:filename()], - goals=[] :: [depsolver:constraint()]}). + goals=[] :: [depsolver:constraint()], + providers = [] :: [rcl_provider:t()], + available_apps = [] :: [app_descriptor()], + default_release :: {rcl_release:name(), rcl_release:vsn()}, + releases :: ec_dictionary:dictionary({ReleaseName::atom(), + ReleaseVsn::string()}, + rcl_release:t())}). %%============================================================================ %% types %%============================================================================ +-type app_descriptor() :: {rcl_release:app_name(), rcl_release:app_vsn(), file:name()}. + +-type releases() :: ec_dictionary:dictionary({rcl_release:name(), + rcl_release:vsn()}, + rcl_release:t()). -type cmd_args() :: proplists:proplist(). --opaque t() :: record(?MODULE). +-opaque t() :: record(state_t). %%============================================================================ %% API @@ -54,40 +77,84 @@ %% @doc Create a new 'log level' for the system -spec new(proplists:proplist(), [file:filename()]) -> t(). new(PropList, Targets) when erlang:is_list(PropList) -> - #?MODULE{log = proplists:get_value(log, PropList, rcl_log:new(error)), - output_dir=proplists:get_value(output_dir, PropList, ""), - lib_dirs=proplists:get_value(lib_dirs, PropList, []), - config_files=Targets, - goals=proplists:get_value(goals, PropList, [])}. + State0 = + #state_t{log = proplists:get_value(log, PropList, rcl_log:new(error)), + output_dir=proplists:get_value(output_dir, PropList, ""), + lib_dirs=get_lib_dirs(proplists:get_value(lib_dirs, PropList, [])), + config_files=Targets, + goals=proplists:get_value(goals, PropList, []), + providers = [], + releases=ec_dictionary:new(ec_dict)}, + create_logic_providers(State0). %% @doc get the current log state for the system -spec log(t()) -> rcl_log:t(). -log(#?MODULE{log=LogState}) -> +log(#state_t{log=LogState}) -> LogState. -spec output_dir(t()) -> file:name(). -output_dir(#?MODULE{output_dir=OutDir}) -> +output_dir(#state_t{output_dir=OutDir}) -> OutDir. -spec lib_dirs(t()) -> [file:name()]. -lib_dirs(#?MODULE{lib_dirs=LibDir}) -> +lib_dirs(#state_t{lib_dirs=LibDir}) -> LibDir. -spec goals(t()) -> [depsolver:constraints()]. -goals(#?MODULE{goals=TS}) -> +goals(#state_t{goals=TS}) -> TS. -spec config_files(t()) -> [file:filename()]. -config_files(#?MODULE{config_files=ConfigFiles}) -> +config_files(#state_t{config_files=ConfigFiles}) -> ConfigFiles. +-spec providers(t()) -> [rcl_provider:t()]. +providers(#state_t{providers=Providers}) -> + Providers. + +-spec providers(t(), [rcl_provider:t()]) -> t(). +providers(M, NewProviders) -> + M#state_t{providers=NewProviders}. + +-spec add_release(t(), rcl_release:t()) -> t(). +add_release(M=#state_t{releases=Releases}, Release) -> + M#state_t{releases=ec_dictionary:add({rcl_release:name(Release), + rcl_release:vsn(Release)}, + Release, + Releases)}. + +-spec get_release(t(), rcl_release:name(), rcl_release:vsn()) -> rcl_release:t(). +get_release(#state_t{releases=Releases}, Name, Vsn) -> + ec_dictionary:get({Name, Vsn}, Releases). + +-spec releases(t()) -> releases(). +releases(#state_t{releases=Releases}) -> + Releases. + +-spec default_release(t()) -> {rcl_release:name(), rcl_release:vsn()}. +default_release(#state_t{default_release=Def}) -> + Def. + +-spec default_release(t(), rcl_release:name(), rcl_release:vsn()) -> t(). +default_release(M, Name, Vsn) -> + M#state_t{default_release={Name, Vsn}}. + +-spec available_apps(t()) -> [app_descriptor()]. +available_apps(#state_t{available_apps=Apps}) -> + Apps. + +-spec available_apps(t(), [app_descriptor()]) -> t(). +available_apps(M, NewApps) -> + M#state_t{available_apps=NewApps}. + -spec format(t()) -> iolist(). format(Mod) -> format(Mod, 0). -spec format(t(), non_neg_integer()) -> iolist(). -format(#?MODULE{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, - goals=Goals, config_files=ConfigFiles}, +format(#state_t{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, + goals=Goals, config_files=ConfigFiles, + providers=Providers}, Indent) -> [rcl_util:indent(Indent), <<"state:\n">>, @@ -98,7 +165,28 @@ format(#?MODULE{log=LogState, output_dir=OutDir, lib_dirs=LibDirs, [[rcl_util:indent(Indent + 2), depsolver:format_constraint(Goal), ",\n"] || Goal <- Goals], rcl_util:indent(Indent + 1), "output_dir: ", OutDir, "\n", rcl_util:indent(Indent + 1), "lib_dirs: \n", - [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs]]. + [[rcl_util:indent(Indent + 2), LibDir, ",\n"] || LibDir <- LibDirs], + rcl_util:indent(Indent + 1), "providers: \n", + [[rcl_util:indent(Indent + 2), rcl_provider:format(Provider), ",\n"] || Provider <- Providers]]. + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +-spec get_lib_dirs([file:name()]) -> [file:name()]. +get_lib_dirs(CmdDirs) -> + case os:getenv("ERL_LIBS") of + false -> + CmdDirs; + EnvString -> + [Lib || Lib <- re:split(EnvString, ":|;"), + filelib:is_dir(Lib)] ++ CmdDirs + end. + +-spec create_logic_providers(t()) -> t(). +create_logic_providers(State0) -> + {ConfigProvider, {ok, State1}} = rcl_provider:new(rcl_prv_config, State0), + {DiscoveryProvider, {ok, State2}} = rcl_provider:new(rcl_prv_discover, State1), + State2#state_t{providers=[ConfigProvider, DiscoveryProvider]}. %%%=================================================================== %%% Test Functions diff --git a/test/rclt_discover_SUITE.erl b/test/rclt_discover_SUITE.erl new file mode 100644 index 0000000..718fd03 --- /dev/null +++ b/test/rclt_discover_SUITE.erl @@ -0,0 +1,194 @@ +%%% -*- 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 Merrit +%%% @copyright (C) 2012, Eric Merrit +-module(rclt_discover_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + all/0, + normal_case/1, + no_beam_case/1, + bad_ebin_case/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). + +suite() -> + [{timetrap,{seconds,30}}]. + +init_per_suite(Config) -> + Config. + +end_per_suite(_Config) -> + ok. + +init_per_testcase(_, Config) -> + DataDir = proplists:get_value(data_dir, Config), + LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), + LibDir2 = filename:join([DataDir, create_random_name("lib_dir2_")]), + ok = rcl_util:mkdir_p(LibDir1), + ok = rcl_util:mkdir_p(LibDir2), + State = rcl_state:new([{lib_dirs, [LibDir1, LibDir2]}], []), + [{lib1, LibDir1}, + {lib2, LibDir2}, + {state, State} | Config]. + + +all() -> + [normal_case, no_beam_case, bad_ebin_case]. + +normal_case(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + State0 = proplists:get_value(state, Config), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + {ok, State2} = rcl_provider:do(DiscoverProvider, State1), + lists:foreach(fun(App) -> + ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + end, Apps1), + + lists:foreach(fun(App) -> + ?assertMatch(true, lists:member(App, rcl_state:available_apps(State2))) + end, Apps2), + Length = erlang:length(Apps2) + + erlang:length(Apps2), + ?assertMatch(Length, erlang:length(rcl_state:available_apps(State2))). + +no_beam_case(Config) -> + %% We silently ignore apps with no beams + LibDir1 = proplists:get_value(lib1, Config), + _Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + _Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + BadName = create_random_name("error_bad"), + BadVsn = create_random_vsn(), + AppDir = filename:join([LibDir2, BadName]), + write_app_file(AppDir, BadName, BadVsn), + State0 = proplists:get_value(state, Config), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + EbinDir = filename:join([LibDir2, BadName, "ebin"]), + %% Ignore apps that do not contain any beam files + ?assertMatch({error, [{no_beam_files, EbinDir}]}, + rcl_provider:do(DiscoverProvider, State1)). + +bad_ebin_case(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + _Apps1 = [(fun({Name, Vsn}) -> + create_app(LibDir1, Name, Vsn) + end)(App) + || + App <- + [{create_random_name("lib_app1_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + LibDir2 = proplists:get_value(lib2, Config), + _Apps2 = [(fun({Name, Vsn}) -> + create_app(LibDir2, Name, Vsn) + end)(App) + || App <- + [{create_random_name("lib_app2_"), create_random_vsn()} + || _ <- lists:seq(1, 100)]], + BadName = create_random_name("error_bad"), + BadVsn = create_random_vsn(), + AppDir = filename:join([LibDir2, BadName]), + Filename = filename:join([AppDir, "ebin", BadName ++ ".app"]), + io:format("BAD -> ~p~n", [Filename]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_bad_app_metadata(BadName, BadVsn)), + write_beam_file(AppDir, BadName), + State0 = proplists:get_value(state, Config), + {DiscoverProvider, {ok, State1}} = rcl_provider:new(rcl_prv_discover, State0), + + %% Ignore apps that do not contain any beam files + ?assertMatch({error, [{invalid_app_file, Filename}]}, + rcl_provider:do(DiscoverProvider, State1)). + + + +%%% API +%%%=================================================================== +create_app(Dir, Name, Vsn) -> + AppDir = filename:join([Dir, Name]), + write_app_file(AppDir, Name, Vsn), + write_beam_file(AppDir, Name), + rcl_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + [kernel, stdlib], []). + +write_beam_file(Dir, Name) -> + Beam = filename:join([Dir, "ebin", "not_a_real_beam" ++ Name ++ ".beam"]), + ok = filelib:ensure_dir(Beam), + ok = ec_file:write_term(Beam, testing_purposes_only). + +write_app_file(Dir, Name, Version) -> + Filename = filename:join([Dir, "ebin", Name ++ ".app"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_app_metadata(Name, Version)). + +get_app_metadata(Name, Vsn) -> + {application, erlang:list_to_atom(Name), + [{description, ""}, + {vsn, Vsn}, + {modules, []}, + {applications, [kernel, stdlib]}]}. + +get_bad_app_metadata(Name, Vsn) -> + ["{application, ", Name, ", + [{description, \"\"}, + {vsn, \"", Vsn, "\"}, + {modules, [], + {applications, [kernel, stdlib]}]}."]. + + +create_random_name(Name) -> + random:seed(erlang:now()), + Name ++ erlang:integer_to_list(random:uniform(1000000)). + +create_random_vsn() -> + random:seed(erlang:now()), + lists:flatten([erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100)), + ".", erlang:integer_to_list(random:uniform(100))]). -- cgit v1.2.3