diff options
-rw-r--r-- | Makefile | 3 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rwxr-xr-x | priv/templates/bin.dtl | 10 | ||||
-rw-r--r-- | priv/templates/extended_bin.dtl | 129 | ||||
-rw-r--r-- | priv/templates/install_upgrade_escript.dtl | 32 | ||||
-rw-r--r-- | priv/templates/nodetool.dtl | 2 | ||||
-rw-r--r-- | rebar.config | 4 | ||||
-rw-r--r-- | src/relx.erl | 27 | ||||
-rw-r--r-- | src/rlx_app_discovery.erl | 49 | ||||
-rw-r--r-- | src/rlx_cmd_args.erl | 38 | ||||
-rw-r--r-- | src/rlx_config.erl | 64 | ||||
-rw-r--r-- | src/rlx_prv_app_discover.erl (renamed from src/rlx_prv_discover.erl) | 67 | ||||
-rw-r--r-- | src/rlx_prv_archive.erl | 56 | ||||
-rw-r--r-- | src/rlx_prv_assembler.erl | 121 | ||||
-rw-r--r-- | src/rlx_prv_overlay.erl | 118 | ||||
-rw-r--r-- | src/rlx_prv_rel_discover.erl | 92 | ||||
-rw-r--r-- | src/rlx_prv_release.erl | 120 | ||||
-rw-r--r-- | src/rlx_prv_relup.erl | 25 | ||||
-rw-r--r-- | src/rlx_rel_discovery.erl | 4 | ||||
-rw-r--r-- | src/rlx_release.erl | 14 | ||||
-rw-r--r-- | src/rlx_state.erl | 28 | ||||
-rw-r--r-- | src/rlx_topo.erl | 6 | ||||
-rw-r--r-- | test/rlx_archive_SUITE.erl | 132 | ||||
-rw-r--r-- | test/rlx_discover_SUITE.erl | 18 | ||||
-rw-r--r-- | test/rlx_release_SUITE.erl | 628 | ||||
-rw-r--r-- | test/rlx_test_utils.erl | 98 |
26 files changed, 1181 insertions, 708 deletions
@@ -101,7 +101,8 @@ ct: compile clean-common-test-data -logdir $(CURDIR)/logs \ -dir $(CURDIR)/test/ \ -cover cover.spec \ - -suite rlx_command_SUITE rlx_discover_SUITE -suite rlx_release_SUITE + -suite rlx_command_SUITE rlx_discover_SUITE -suite rlx_release_SUITE \ + -suite rlx_archive_SUITE test: compile dialyzer eunit ct @@ -67,13 +67,13 @@ Options | Short | Long | Type | Default | Description | |:-----:|:------------:|:-------:|:------:|------------------------------------------------------------------------------------------- | | -r | --root | string | ./ | Sets the root of the project | -| -n | --name | string | | Name for the release that will be generated | +| -n | --relname | string | | Name for the release that will be generated | | -v | --relvsn | string | | Version for the release | | -g | --goal | string | | A goal for the system. These are usually the OTP apps that are part of the release | | -u | --upfrom | string | | The release to upgrade from. Only valid with relup target | | -o | --output-dir | string | ./ | The output directory for the release | | -l | --lib-dir | string | | Additional dirs to search for OTP apps | -| | --system_libs | string | | Path to a Erlang system libs to use | +| | --system_libs | boolean/string | true | If true include a copy of system libs used to build with, if a path include system libs at that path. If false, do not include system libs | | -p | --path | string | | Additional dirs to add to Erlang code path | | | --default-libs | boolean | true | Whether to use the default system added lib dirs (means you must add them all manually) | | -V | --verbose | integer | 2 | The verbosity level between 0 and 3 | diff --git a/priv/templates/bin.dtl b/priv/templates/bin.dtl index bb83434..3398d63 100755 --- a/priv/templates/bin.dtl +++ b/priv/templates/bin.dtl @@ -17,12 +17,11 @@ find_erts_dir() { ROOTDIR="$RELEASE_ROOT_DIR" else local erl="$(which erl)" - code="io:format(\"~s\", [code:root_dir()]), halt()." - local erl_root="$("$erl" -noshell -eval "$code")" + code="io:format(\"~s\", [code:root_dir()])." + local erl_root="$("$erl" -noshell -eval "$code" -s init stop)" ERTS_DIR="$erl_root/erts-$ERTS_VSN" ROOTDIR="$erl_root" fi - } find_sys_config() { @@ -47,7 +46,8 @@ export BINDIR="$ERTS_DIR/bin" export EMU="beam" export PROGNAME="erl" export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH" - +ERTS_LIB_DIR="$ERTS_DIR/../lib" +[ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start cd "$ROOTDIR" # Save extra arguments @@ -57,7 +57,7 @@ ARGS="$@" set -- "$ERL_OPTS" [ "$SYS_CONFIG" ] && set -- "$@" -config "$SYS_CONFIG" [ "$VM_ARGS" ] && set -- "$@" -args_file "$VM_ARGS" -set -- "$@" -boot "$REL_DIR/$REL_NAME" "$ARGS" +set -- "$@" -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" -boot "$REL_DIR/$BOOTFILE" "$ARGS" # Boot the release $BINDIR/erlexec $@ diff --git a/priv/templates/extended_bin.dtl b/priv/templates/extended_bin.dtl index ec4a8f3..ffff615 100644 --- a/priv/templates/extended_bin.dtl +++ b/priv/templates/extended_bin.dtl @@ -7,6 +7,7 @@ RELEASE_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd)" REL_NAME="{{ rel_name }}" REL_VSN="{{ rel_vsn }}" ERTS_VSN="{{ erts_vsn }}" +CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}" REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN" ERL_OPTS="{{ erl_opts }}" RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-$RELEASE_ROOT_DIR/log}" @@ -25,6 +26,18 @@ find_erts_dir() { fi } +# Get node pid +relx_get_pid() { + if output="$(relx_nodetool rpcterms os getpid)" + then + echo "$output" | sed -e 's/"//g' + return 0 + else + echo "$output" + return 1 + fi +} + # Connect to a remote node relx_rem_sh() { # Generate a unique id used to allow multiple remsh to the same node @@ -36,6 +49,7 @@ relx_rem_sh() { # Setup remote shell command to control node exec "$BINDIR/erl" "$NAME_TYPE" "$id" -remsh "$NAME" -boot start_clean \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ -setcookie "$COOKIE" -kernel net_ticktime $TICKTIME } @@ -59,6 +73,7 @@ relx_escript() { "$ERTS_DIR/bin/escript" "$ROOTDIR/$scriptpath" $@ } + # Output a start command for the last argument of run_erl relx_start_command() { printf "exec \"%s\" \"%s\"" "$RELEASE_ROOT_DIR/bin/$REL_NAME" \ @@ -84,17 +99,17 @@ fi # Make sure log directory exists mkdir -p "$RUNNER_LOG_DIR" -if [ -z "$CONFIG_PATH" ]; then +if [ -z "$RELX_CONFIG_PATH" ]; then if [ -f "$USE_DIR/sys.config" ]; then - CONFIG_PATH="$USE_DIR/sys.config" + RELX_CONFIG_PATH="$USE_DIR/sys.config" else - CONFIG_PATH="$REL_DIR/sys.config" + RELX_CONFIG_PATH="$REL_DIR/sys.config" fi fi if [ $RELX_REPLACE_OS_VARS ]; then - awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < $CONFIG_PATH > $CONFIG_PATH.2.config - CONFIG_PATH=$CONFIG_PATH.2.config + awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < $RELX_CONFIG_PATH > $RELX_CONFIG_PATH.2.config + RELX_CONFIG_PATH=$RELX_CONFIG_PATH.2.config fi # Extract the target node name from node.args @@ -117,7 +132,14 @@ case $NAME in ;; *) # Add @hostname - NAME=$NAME@`hostname -f` + case $NAME_TYPE in + -sname) + NAME=$NAME@`hostname -s` + ;; + -name) + NAME=$NAME@`hostname -f` + ;; + esac ;; esac @@ -139,6 +161,7 @@ export BINDIR="$ERTS_DIR/bin" export EMU="beam" export PROGNAME="erl" export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH" +ERTS_LIB_DIR="$ERTS_DIR/../lib" cd "$ROOTDIR" @@ -184,24 +207,9 @@ case "$1" in stop) # Wait for the node to completely stop... - case $(uname -s) in - Linux|Darwin|FreeBSD|DragonFly|NetBSD|OpenBSD) - # PID COMMAND - PID=$(ps ax -o pid= -o command=| - grep "$RELEASE_ROOT_DIR/.*/[b]eam"|awk '{print $1}') - ;; - SunOS) - # PID COMMAND - PID=$(ps -ef -o pid= -o args=| - grep "$RELEASE_ROOT_DIR/.*/[b]eam"|awk '{print $1}') - ;; - CYGWIN*) - # UID PID PPID TTY STIME COMMAND - PID=$(ps -efw|grep "$RELEASE_ROOT_DIR/.*/[b]eam"|awk '{print $2}') - ;; - esac + PID="$(relx_get_pid)" if ! relx_nodetool "stop"; then - exit $? + exit 1 fi while $(kill -0 "$PID" 2>/dev/null); do @@ -212,37 +220,43 @@ case "$1" in restart) ## Restart the VM without exiting the process if ! relx_nodetool "restart"; then - exit $? + exit 1 fi ;; reboot) ## Restart the VM completely (uses heart to restart it) if ! relx_nodetool "reboot"; then - exit $? + exit 1 + fi + ;; + + pid) + ## Get the VM's pid + if ! relx_get_pid; then + exit 1 fi ;; ping) ## See if the VM is alive if ! relx_nodetool "ping"; then - exit $? + exit 1 fi ;; escript) ## Run an escript under the node's environment if ! relx_escript $@; then - exit $? + exit 1 fi ;; attach) # Make sure a node IS running if ! relx_nodetool "ping" > /dev/null; then - ES="$?" echo "Node is not running!" - exit $ES + exit 1 fi shift @@ -252,9 +266,8 @@ case "$1" in remote_console) # Make sure a node IS running if ! relx_nodetool "ping" > /dev/null; then - ES="$?" echo "Node is not running!" - exit $ES + exit 1 fi shift @@ -271,13 +284,30 @@ case "$1" in # Make sure a node IS running if ! relx_nodetool "ping" > /dev/null; then - ES="$?" echo "Node is not running!" - exit $ES + exit 1 fi exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ - "$REL_NAME" "$NAME" "$COOKIE" "$2" + "install" "$REL_NAME" "$NAME" "$COOKIE" "$2" + ;; + + unpack) + if [ -z "$2" ]; then + echo "Missing package argument" + echo "Usage: $REL_NAME $1 {package base name}" + echo "NOTE {package base name} MUST NOT include the .tar.gz suffix" + exit 1 + fi + + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \ + "unpack" "$REL_NAME" "$NAME" "$COOKIE" "$2" ;; console|console_clean|console_boot) @@ -314,7 +344,8 @@ case "$1" in # Build an array of arguments to pass to exec later on # Build it here because this command will be used for logging. set -- "$BINDIR/erlexec" -boot "$BOOTFILE" \ - -env ERL_LIBS "$REL_DIR/lib" -config "$CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ + -env ERL_LIBS "$REL_DIR/lib" -config "$RELX_CONFIG_PATH" \ -args_file "$VMARGS_PATH" # Dump environment info for logging purposes @@ -349,7 +380,8 @@ case "$1" in # Build an array of arguments to pass to exec later on # Build it here because this command will be used for logging. set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \ - -boot "$REL_DIR/$BOOTFILE" -mode embedded -config "$CONFIG_PATH" \ + -boot "$REL_DIR/$BOOTFILE" -mode "$CODE_LOADING_MODE" -config "$RELX_CONFIG_PATH" \ + -boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \ -args_file "$VMARGS_PATH" # Dump environment info for logging purposes @@ -359,8 +391,31 @@ case "$1" in # Start the VM exec "$@" -- ${1+$ARGS} ;; + rpc) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + shift + + relx_nodetool rpc $@ + ;; + rpcterms) + # Make sure a node IS running + if ! relx_nodetool "ping" > /dev/null; then + echo "Node is not running!" + exit 1 + fi + + shift + + relx_nodetool rpcterms $@ + ;; + *) - echo "Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|escript}" + echo "Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|escript|rpc|rpcterms}" exit 1 ;; esac diff --git a/priv/templates/install_upgrade_escript.dtl b/priv/templates/install_upgrade_escript.dtl index dce2e11..3fb9d04 100644 --- a/priv/templates/install_upgrade_escript.dtl +++ b/priv/templates/install_upgrade_escript.dtl @@ -6,8 +6,36 @@ -define(TIMEOUT, 300000). -define(INFO(Fmt,Args), io:format(Fmt,Args)). -%% Upgrades, to a new tar.gz release -main([RelName, NodeName, Cookie, VersionArg]) -> +%% Unpack or upgrade to a new tar.gz release +main(["unpack", RelName, NodeName, Cookie, VersionArg]) -> + TargetNode = start_distribution(NodeName, Cookie), + WhichReleases = which_releases(TargetNode), + Version = parse_version(VersionArg), + case proplists:get_value(Version, WhichReleases) of + undefined -> + %% not installed, so unpack tarball: + ?INFO("Release ~s not found, attempting to unpack releases/~s/~s.tar.gz~n",[Version,Version,RelName]), + ReleasePackage = Version ++ "/" ++ RelName, + case rpc:call(TargetNode, release_handler, unpack_release, + [ReleasePackage], ?TIMEOUT) of + {ok, Vsn} -> + ?INFO("Unpacked successfully: ~p~n", [Vsn]); + {error, UnpackReason} -> + print_existing_versions(TargetNode), + ?INFO("Unpack failed: ~p~n",[UnpackReason]), + erlang:halt(2) + end; + old -> + %% no need to unpack, has been installed previously + ?INFO("Release ~s is marked old, switching to it.~n",[Version]); + unpacked -> + ?INFO("Release ~s is already unpacked, now installing.~n",[Version]); + current -> + ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]); + permanent -> + ?INFO("Release ~s is already installed, and set permanent.~n",[Version]) + end; +main(["install", RelName, NodeName, Cookie, VersionArg]) -> TargetNode = start_distribution(NodeName, Cookie), WhichReleases = which_releases(TargetNode), Version = parse_version(VersionArg), diff --git a/priv/templates/nodetool.dtl b/priv/templates/nodetool.dtl index 2f46395..dee14b4 100644 --- a/priv/templates/nodetool.dtl +++ b/priv/templates/nodetool.dtl @@ -44,7 +44,7 @@ main(Args) -> end; ["rpcterms", Module, Function | ArgsAsString] -> case rpc:call(TargetNode, list_to_atom(Module), list_to_atom(Function), - consult(ArgsAsString), 60000) of + consult(lists:flatten(ArgsAsString)), 60000) of {badrpc, Reason} -> io:format("RPC to ~p failed: ~p\n", [TargetNode, Reason]), halt(1); diff --git a/rebar.config b/rebar.config index ca9894b..b67d1f8 100644 --- a/rebar.config +++ b/rebar.config @@ -10,7 +10,7 @@ {branch, "master"}}}, {providers, ".*", {git, "https://github.com/tsloughter/providers.git", - {branch, "master"}}}, + {tag, "v1.0.0"}}}, {erlydtl, ".*", {git, "https://github.com/erlydtl/erlydtl.git", {tag, "0.9.0"}}}, @@ -40,3 +40,5 @@ {compiler_options, [report, return, debug_info]}]}. {escript_incl_apps, [getopt, erlware_commons, erlydtl, providers]}. +{escript_emu_args, "%%! +sbtu +A0 -noinput\n"}. + diff --git a/src/relx.erl b/src/relx.erl index 25f3220..eb3b969 100644 --- a/src/relx.erl +++ b/src/relx.erl @@ -26,7 +26,7 @@ do/7, do/8, do/9, - format_error/2, + format_error/1, opt_spec_list/0]). -export_type([error/0]). @@ -216,18 +216,18 @@ opt_spec_list() -> {version, undefined, "version", undefined, "Print relx version"}, {root_dir, $r, "root", string, "The project root directory"}]. --spec format_error(Reason::term(), rlx_state:t()) -> string(). -format_error({invalid_return_value, Provider, Value}, _) -> +-spec format_error(Reason::term()) -> string(). +format_error({invalid_return_value, Provider, Value}) -> io_lib:format(lists:flatten([providers:format(Provider), " returned an invalid value ", io_lib:format("~p", [Value])]), []); -format_error({opt_parse, {invalid_option, Opt}}, _) -> +format_error({opt_parse, {invalid_option, Opt}}) -> io_lib:format("invalid option ~s~n", [Opt]); -format_error({opt_parse, Arg}, _) -> +format_error({opt_parse, Arg}) -> io_lib:format("~p~n", [Arg]); -format_error({error, {relx, Reason}}, State) -> - format_error(Reason, State); -format_error({error, {Module, Reason}}, State) -> - io_lib:format("~s~n", [Module:format_error(Reason, State)]). +format_error({error, {relx, Reason}}) -> + format_error(Reason); +format_error({error, {Module, Reason}}) -> + io_lib:format("~s~n", [Module:format_error(Reason)]). %%============================================================================ %% internal api @@ -298,19 +298,20 @@ run_provider(_ProviderName, Error) -> -spec usage() -> ok. usage() -> - getopt:usage(opt_spec_list(), "relx", "[*release-specification-file*]"). + getopt:usage(opt_spec_list(), "relx", "[<task>]"), + io:format("Several tasks are available:~n~nrelease\t\tCreate release.~nrelup\t\tGenerate a release upgrade.~ntar\t\tCreate tarball archive of a release.~n~n"). -spec report_error(rlx_state:t(), error()) -> none() | error(). report_error(State, Error) -> case Error of {error, {relx, {opt_parse, _}}} -> - io:format(standard_error, format_error(Error, State), []), + io:format(standard_error, format_error(Error), []), usage(); {error, {rlx_cmd_args, _}} -> - io:format(standard_error, format_error(Error, State), []), + io:format(standard_error, format_error(Error), []), usage(); _ -> - io:format(standard_error, format_error(Error, State), []) + io:format(standard_error, format_error(Error), []) end, case rlx_state:caller(State) of command_line -> diff --git a/src/rlx_app_discovery.erl b/src/rlx_app_discovery.erl index 2c5714b..3d58185 100644 --- a/src/rlx_app_discovery.erl +++ b/src/rlx_app_discovery.erl @@ -25,7 +25,7 @@ -module(rlx_app_discovery). -export([do/2, - format_error/2]). + format_error/1]). -include("relx.hrl"). @@ -44,10 +44,10 @@ do(State, LibDirs) -> end), resolve_app_metadata(State, LibDirs). --spec format_error([ErrorDetail::term()], rlx_state:t()) -> iolist(). -format_error(ErrorDetails, State) +-spec format_error([ErrorDetail::term()]) -> iolist(). +format_error(ErrorDetails) when erlang:is_list(ErrorDetails) -> - [[format_detail(ErrorDetail, State), "\n"] || ErrorDetail <- ErrorDetails]. + [[format_detail(ErrorDetail), "\n"] || ErrorDetail <- ErrorDetails]. %%%=================================================================== %%% Internal Functions @@ -85,10 +85,10 @@ get_app_metadata(State, LibDirs) -> {ok, _} = AppMeta -> [AppMeta|Acc]; {warning, W} -> - ec_cmd_log:warn(rlx_state:log(State), format_detail(W, State)), + ec_cmd_log:warn(rlx_state:log(State), format_detail(W)), Acc; {error, E} -> - ec_cmd_log:error(rlx_state:log(State), format_detail(E, State)), + ec_cmd_log:error(rlx_state:log(State), format_detail(E)), Acc; _ -> Acc @@ -111,15 +111,17 @@ resolve_app_metadata(State, LibDirs) -> {error, _} -> true; {warning, W} -> - ec_cmd_log:warn(rlx_state:log(State), format_detail(W, State)), + ec_cmd_log:warn(rlx_state:log(State), format_detail(W)), false; _ -> false end] of [] -> SkipApps = rlx_state:skip_apps(State), - AppMeta1 = [App || {ok, App} <- setup_overrides(State, AppMeta0), - not lists:keymember(rlx_app_info:name(App), 1, SkipApps)], + ExcludeApps = rlx_state:exclude_apps(State), + AppMeta1 = [rm_exclude_apps(App, ExcludeApps) || + {ok, App} <- setup_overrides(State, AppMeta0), + not lists:keymember(rlx_app_info:name(App), 1, SkipApps++ExcludeApps)], ec_cmd_log:debug(rlx_state:log(State), fun() -> ["Resolved the following OTP Applications from the system: \n", @@ -129,6 +131,11 @@ resolve_app_metadata(State, LibDirs) -> Errors -> ?RLX_ERROR(Errors) end. +%% Apps listed in {exclude_apps, [...]} must be removed from applications lists +rm_exclude_apps(App, ExcludeApps) -> + ActiveApps = lists:subtract(rlx_app_info:active_deps(App), ExcludeApps), + LibraryApps = lists:subtract(rlx_app_info:library_deps(App), ExcludeApps), + rlx_app_info:library_deps(rlx_app_info:active_deps(App, ActiveApps), LibraryApps). app_name({warning, _}) -> undefined; @@ -155,30 +162,30 @@ resolve_override(AppName, FileName0) -> {ok, rlx_app_info:link(App, true)} end. --spec format_detail(ErrorDetail::term(), rlx_state:t()) -> iolist(). -format_detail({missing_beam_file, Module, BeamFile}, _) -> +-spec format_detail(ErrorDetail::term()) -> iolist(). +format_detail({missing_beam_file, Module, BeamFile}) -> io_lib:format("Missing beam file ~p ~p", [Module, BeamFile]); -format_detail({error, {invalid_override, AppName, FileName}}, _) -> +format_detail({error, {invalid_override, AppName, FileName}}) -> io_lib:format("Override {~p, ~p} is not a valid OTP App. Perhaps you forgot to build it?", [AppName, FileName]); -format_detail({accessing, File, eaccess}, _) -> +format_detail({accessing, File, eaccess}) -> io_lib:format("permission denied accessing file ~s", [File]); -format_detail({accessing, File, Type}, _) -> +format_detail({accessing, File, Type}) -> io_lib:format("error (~p) accessing file ~s", [Type, File]); -format_detail({no_beam_files, EbinDir}, _) -> +format_detail({no_beam_files, EbinDir}) -> io_lib:format("no beam files found in directory ~s", [EbinDir]); -format_detail({not_a_directory, EbinDir}, _) -> +format_detail({not_a_directory, EbinDir}) -> io_lib:format("~s is not a directory when it should be a directory", [EbinDir]); -format_detail({unable_to_load_app, AppDir, _}, _) -> +format_detail({unable_to_load_app, AppDir, _}) -> io_lib:format("Unable to load the application metadata from ~s", [AppDir]); -format_detail({invalid_app_file, File}, _) -> +format_detail({invalid_app_file, File}) -> io_lib:format("Application metadata file exists but is malformed: ~s", [File]); -format_detail({unversioned_app, AppDir, _AppName}, _) -> +format_detail({unversioned_app, AppDir, _AppName}) -> io_lib:format("Application metadata exists but version is not available: ~s", [AppDir]); -format_detail({app_info_error, {Module, Detail}}, State) -> - Module:format_error(Detail, State). +format_detail({app_info_error, {Module, Detail}}) -> + Module:format_error(Detail). -spec discover_dir([file:name()], directory | file) -> {ok, rlx_app_info:t()} | {error, Reason::term()}. diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl index a47c2ee..2039b43 100644 --- a/src/rlx_cmd_args.erl +++ b/src/rlx_cmd_args.erl @@ -22,7 +22,7 @@ -module(rlx_cmd_args). -export([args2state/2, - format_error/2]). + format_error/1]). -include("relx.hrl"). @@ -48,10 +48,10 @@ args2state(Opts, Targets) -> Error end. --spec format_error(Reason::term(), rlx_state:t()) -> iolist(). -format_error({invalid_targets, Targets}, _) -> +-spec format_error(Reason::term()) -> iolist(). +format_error({invalid_targets, Targets}) -> io_lib:format("One config must be specified! not ~p~n", [Targets]); -format_error({invalid_option_arg, Arg}, _) -> +format_error({invalid_option_arg, Arg}) -> case Arg of {goals, Goal} -> io_lib:format("Invalid Goal argument -g ~p~n", [Goal]); @@ -68,20 +68,20 @@ format_error({invalid_option_arg, Arg}, _) -> {path, Path} -> io_lib:format("Invalid code path argument -n ~p~n", [Path]) end; -format_error({invalid_config_file, Config}, _) -> +format_error({invalid_config_file, Config}) -> io_lib:format("Invalid configuration file specified: ~p", [Config]); -format_error({invalid_caller, Caller}, _) -> +format_error({invalid_caller, Caller}) -> io_lib:format("Invalid caller specified: ~s", [Caller]); -format_error({failed_to_parse, Spec}, _) -> +format_error({failed_to_parse, Spec}) -> io_lib:format("Unable to parse spec ~s", [Spec]); -format_error({failed_to_parse_override, QA}, _) -> +format_error({failed_to_parse_override, QA}) -> io_lib:format("Failed to parse app override ~s", [QA]); -format_error({not_directory, Dir}, _) -> +format_error({not_directory, Dir}) -> io_lib:format("Library directory does not exist: ~s", [Dir]); -format_error({invalid_log_level, LogLevel}, _) -> +format_error({invalid_log_level, LogLevel}) -> io_lib:format("Invalid log level specified -V ~p, log level must be in the" " range 0..3", [LogLevel]); -format_error({invalid_target, Target}, _) -> +format_error({invalid_target, Target}) -> io_lib:format("Invalid action specified: ~s", [Target]). %%%=================================================================== @@ -102,7 +102,7 @@ handle_config(Opts, Targets, CommandLineConfig) -> end end. --spec convert_targets([string()]) -> {ok, release | relup} | relx:error(). +-spec convert_targets([string()]) -> {ok, [rlx_state:action()]} | relx:error(). convert_targets(Targets) -> convert_targets(Targets, []). @@ -111,7 +111,7 @@ convert_targets(Targets) -> convert_targets([], []) -> {ok, [release]}; convert_targets([], Acc) -> - {ok, Acc}; + {ok, lists:reverse(Acc)}; convert_targets(["release" | T], Acc) -> convert_targets(T, [release | Acc]); convert_targets(["relup" | T], Acc) -> @@ -244,8 +244,16 @@ create(vm_args, Opts) -> VmArgs = proplists:get_value(vm_args, Opts, undefined), {vm_args, VmArgs}; create(system_libs, Opts) -> - SystemLibs = proplists:get_value(system_libs, Opts, undefined), - {system_libs, SystemLibs}; + case proplists:get_value(system_libs, Opts, true) of + SystemLibs when SystemLibs =:= true + ; SystemLibs =:= "true" -> + {system_libs, true}; + SystemLibs when SystemLibs =:= false + ; SystemLibs =:= "false" -> + {system_libs, false}; + SystemLibsDir when is_list(SystemLibsDir) -> + {system_libs, SystemLibsDir} + end; create(upfrom, Opts) -> case proplists:get_value(upfrom, Opts, undefined) of undefined -> diff --git a/src/rlx_config.erl b/src/rlx_config.erl index c838c18..df08342 100644 --- a/src/rlx_config.erl +++ b/src/rlx_config.erl @@ -97,25 +97,50 @@ parent_dir([_H], Acc) -> parent_dir([H | T], Acc) -> parent_dir(T, [H | Acc]). +-spec config_script_file(file:filename(), rlx_state:t()) -> file:filename(). +config_script_file(ConfigFile, _State) -> + ConfigFile ++ ".script". + +bs(Vars) -> + lists:foldl(fun({K,V}, Bs) -> + erl_eval:add_binding(K, V, Bs) + end, erl_eval:new_bindings(), Vars). + +-spec apply_config_script(proplists:proplist(), file:filename()) -> + proplists:proplist(). +apply_config_script(ConfigData, ConfigScriptFile) -> + {ok, Config} = file:script(ConfigScriptFile, bs([{'CONFIG', ConfigData}, + {'SCRIPT', ConfigScriptFile}])), + Config. + -spec load_config(file:filename() | proplists:proplist(), rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). load_config(ConfigFile, State) -> {ok, CurrentCwd} = file:get_cwd(), - case filelib:is_regular(ConfigFile) of - true -> - ok = file:set_cwd(filename:dirname(ConfigFile)), - Result = case file:consult(ConfigFile) of - {error, Reason} -> - ?RLX_ERROR({consult, ConfigFile, Reason}); - {ok, Terms} -> - CliTerms = rlx_state:cli_args(State), - lists:foldl(fun load_terms/2, {ok, State}, merge_configs(CliTerms, Terms)) - end, - ok = file:set_cwd(CurrentCwd), - Result; - false -> - CliTerms = rlx_state:cli_args(State), - lists:foldl(fun load_terms/2, {ok, State}, merge_configs(CliTerms, ConfigFile)) + CliTerms = rlx_state:cli_args(State), + Config0 = case filelib:is_regular(ConfigFile) of + true -> + ok = file:set_cwd(filename:dirname(ConfigFile)), + Result = case file:consult(ConfigFile) of + {error, Reason} -> + ?RLX_ERROR({consult, ConfigFile, Reason}); + {ok, Terms} -> merge_configs(CliTerms, Terms) + end, + ok = file:set_cwd(CurrentCwd), + Result; + false -> merge_configs(CliTerms, ConfigFile) + end, + % we now take the merged config and try to apply a config script to it, + % get a new config as a result + case Config0 of + {error, _} = Error -> Error; + _ -> + ConfigScriptFile = config_script_file(ConfigFile, State), + Config1 = case filelib:is_regular(ConfigScriptFile) of + false -> Config0; + true -> apply_config_script(Config0, ConfigScriptFile) + end, + lists:foldl(fun load_terms/2, {ok, State}, Config1) end. -spec load_terms(term(), {ok, rlx_state:t()} | relx:error()) -> @@ -131,11 +156,6 @@ load_terms({default_libs, DefaultLibs}, {ok, State}) -> default_libs, DefaultLibs), {ok, State2}; -load_terms({system_libs, SystemLibs}, {ok, State}) -> - State2 = rlx_state:put(State, - system_libs, - SystemLibs), - {ok, State2}; load_terms({lib_dirs, Dirs}, {ok, State}) -> State2 = rlx_state:add_lib_dirs(State, @@ -162,6 +182,10 @@ load_terms({add_providers, Providers0}, {ok, State0}) -> end; load_terms({skip_apps, SkipApps0}, {ok, State0}) -> {ok, rlx_state:skip_apps(State0, SkipApps0)}; +load_terms({exclude_apps, ExcludeApps0}, {ok, State0}) -> + {ok, rlx_state:exclude_apps(State0, ExcludeApps0)}; +load_terms({debug_info, DebugInfo}, {ok, State0}) -> + {ok, rlx_state:debug_info(State0, DebugInfo)}; load_terms({overrides, Overrides0}, {ok, State0}) -> {ok, rlx_state:overrides(State0, Overrides0)}; load_terms({dev_mode, DevMode}, {ok, State0}) -> diff --git a/src/rlx_prv_discover.erl b/src/rlx_prv_app_discover.erl index 41e3993..eb40f38 100644 --- a/src/rlx_prv_discover.erl +++ b/src/rlx_prv_app_discover.erl @@ -22,16 +22,17 @@ %%% 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 provider behaviour. --module(rlx_prv_discover). +-module(rlx_prv_app_discover). + -behaviour(provider). -export([init/1, do/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). --define(PROVIDER, discover). +-define(PROVIDER, app_discover). -define(DEPS, []). %%============================================================================ @@ -42,48 +43,31 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "build"}, - {short_desc, ""}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. %% @doc recursively dig down into the library directories specified in the state %% looking for OTP Applications -spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). do(State0) -> - LibDirs = get_lib_dirs(State0), + LibDirs = dedup(get_lib_dirs(State0)), case rlx_app_discovery:do(State0, LibDirs) of {ok, AppMeta} -> - case rlx_rel_discovery:do(State0, LibDirs, AppMeta) of - {ok, Releases} -> - State1 = rlx_state:available_apps(State0, AppMeta), - {ok, rlx_state:realized_releases(State1, lists:foldl(fun add/2, - ec_dictionary:new(ec_dict), - Releases))}; - Error -> - Error - end; + State1 = rlx_state:available_apps(State0, AppMeta), + {ok, State1}; Error -> Error end. %% @doc this is here to comply with the signature. However, we do not actually %% produce any errors and so simply return an empty string. --spec format_error(any(), rlx_state:t()) -> iolist(). -format_error(_, _) -> +-spec format_error(any()) -> iolist(). +format_error(_) -> "". %%%=================================================================== %%% Internal Functions %%%=================================================================== -%% @doc only add the release if its not documented in the system -add(Rel, Dict) -> - RelName = rlx_release:name(Rel), - RelVsn = rlx_release:vsn(Rel), - ec_dictionary:add({RelName, RelVsn}, Rel, Dict). get_lib_dirs(State) -> LibDirs0 = rlx_state:lib_dirs(State), @@ -91,10 +75,17 @@ get_lib_dirs(State) -> false -> LibDirs0; true -> + Output = erlang:iolist_to_binary(rlx_state:base_output_dir(State)), + OutputDir = case ec_file:exists(binary_to_list(Output)) of + true -> + Output; + false -> + [] + end, lists:flatten([LibDirs0, add_common_project_dirs(State), add_system_lib_dir(State), - add_release_output_dir(State)]) + OutputDir]) end. -spec add_common_project_dirs(rlx_state:t()) -> [file:name()]. @@ -124,8 +115,8 @@ add_common_project_dirs(State) -> -spec add_system_lib_dir(rlx_state:t()) -> [file:name()]. add_system_lib_dir(State) -> ExcludeSystem = rlx_state:get(State, discover_exclude_system, false), - case rlx_state:get(State, system_libs, undefined) of - undefined -> + case rlx_state:get(State, system_libs, true) of + Atom when is_atom(Atom) -> case ExcludeSystem of true -> []; @@ -136,16 +127,8 @@ add_system_lib_dir(State) -> erlang:iolist_to_binary(SystemLibs) end. -add_release_output_dir(State) -> - case rlx_state:get(State, disable_discover_release_output, false) of - true -> - []; - false -> - Output = erlang:iolist_to_binary(rlx_state:base_output_dir(State)), - case ec_file:exists(erlang:binary_to_list(Output)) of - true -> - Output; - false -> - [] - end - end. +%% Order matters so this slow dedup needs to be used +dedup([]) -> + []; +dedup([H|T]) -> + [H | [X || X <- dedup(T), X /= H]]. diff --git a/src/rlx_prv_archive.erl b/src/rlx_prv_archive.erl index e4cd18f..62cc37c 100644 --- a/src/rlx_prv_archive.erl +++ b/src/rlx_prv_archive.erl @@ -26,7 +26,7 @@ -export([init/1, do/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). @@ -41,12 +41,7 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "tar"}, - {short_desc, ""}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. @@ -57,13 +52,13 @@ do(State) -> OutputDir = rlx_state:output_dir(State), make_tar(State, Release, OutputDir). -format_error({tar_unknown_generation_error, Module, Vsn}, _) -> +format_error({tar_unknown_generation_error, Module, Vsn}) -> io_lib:format("Tarball generation error of ~s ~s", [Module, Vsn]); -format_error({tar_generation_warn, Module, Warnings}, _) -> +format_error({tar_generation_warn, Module, Warnings}) -> io_lib:format("Tarball generation warnings for ~p : ~p", [Module, Warnings]); -format_error({tar_generation_error, Module, Errors}, _) -> +format_error({tar_generation_error, Module, Errors}) -> io_lib:format("Tarball generation error for ~p reason ~p", [Module, Errors]). @@ -72,7 +67,7 @@ make_tar(State, Release, OutputDir) -> Vsn = rlx_release:vsn(Release), ErtsVersion = rlx_release:erts(Release), Opts = [{path, [filename:join([OutputDir, "lib", "*", "ebin"])]}, - {outdir, OutputDir} | + {outdir, OutputDir} | case rlx_state:get(State, include_erts, true) of true -> Prefix = code:root_dir(), @@ -104,14 +99,18 @@ make_tar(State, Release, OutputDir) -> end. update_tar(State, TempDir, OutputDir, Name, Vsn, ErtsVersion) -> + IncludeErts = rlx_state:get(State, include_erts, true), + SystemLibs = rlx_state:get(State, system_libs, true), + {RelName, RelVsn} = rlx_state:default_configured_release(State), + Release = rlx_state:get_realized_release(State, RelName, RelVsn), TarFile = filename:join(OutputDir, Name++"-"++Vsn++".tar.gz"), file:rename(filename:join(OutputDir, Name++".tar.gz"), TarFile), erl_tar:extract(TarFile, [{cwd, TempDir}, compressed]), - OverlayFiles = overlay_files(rlx_state:get(State, overlay, undefined), OutputDir), + OverlayVars = rlx_prv_overlay:generate_overlay_vars(State, Release), + OverlayFiles = overlay_files(OverlayVars, rlx_state:get(State, overlay, undefined), OutputDir), ok = erl_tar:create(TarFile, - [{"lib", filename:join(TempDir, "lib")}, - {"releases", filename:join(TempDir, "releases")}, + [{"releases", filename:join(TempDir, "releases")}, {filename:join(["releases", "start_erl.data"]), filename:join([OutputDir, "releases", "start_erl.data"])}, {filename:join(["releases", "RELEASES"]), @@ -119,21 +118,36 @@ update_tar(State, TempDir, OutputDir, Name, Vsn, ErtsVersion) -> {filename:join(["releases", Vsn, "vm.args"]), filename:join([OutputDir, "releases", Vsn, "vm.args"])}, {"bin", filename:join([OutputDir, "bin"])} | - case rlx_state:get(State, include_erts, true) of + case IncludeErts of false -> - []; + %% Remove system libs from tarball + case SystemLibs of + false -> + Libs = filelib:wildcard("*", filename:join(TempDir, "lib")), + AllSystemLibs = filelib:wildcard("*", code:lib_dir()), + [{filename:join("lib", LibDir), filename:join([TempDir, "lib", LibDir])} || + LibDir <- lists:subtract(Libs, AllSystemLibs)]; + _ -> + [{"lib", filename:join(TempDir, "lib")}] + end; _ -> - [{"erts-"++ErtsVersion, filename:join(OutputDir, "erts-"++ErtsVersion)}] + [{"lib", filename:join(TempDir, "lib")}, + {"erts-"++ErtsVersion, filename:join(OutputDir, "erts-"++ErtsVersion)}] end]++OverlayFiles, [compressed]), ec_cmd_log:info(rlx_state:log(State), - "tarball ~s successfully created!~n", [TarFile]), + "tarball ~s successfully created!~n", [TarFile]), ec_file:remove(TempDir, [recursive]), {ok, State}. -overlay_files(undefined, _) -> +overlay_files(_, undefined, _) -> []; -overlay_files(Overlay, OutputDir) -> - [{to(O), filename:join(OutputDir, to(O))} || O <- Overlay, filter(O)]. +overlay_files(OverlayVars, Overlay, OutputDir) -> + [begin + To = to(O), + ToTemplateName = rlx_prv_overlay:make_template_name("rlx_template_to_template", To), + File = rlx_prv_overlay:render_string(OverlayVars, To, ToTemplateName), + {ec_cnv:to_list(File), ec_cnv:to_list(filename:join(OutputDir, File))} + end || O <- Overlay, filter(O)]. to({copy, _, To}) -> To; diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl index c535485..60cef76 100644 --- a/src/rlx_prv_assembler.erl +++ b/src/rlx_prv_assembler.erl @@ -26,7 +26,7 @@ -export([init/1, do/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). @@ -40,12 +40,7 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "release"}, - {short_desc, ""}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. %% @doc recursively dig down into the library directories specified in the state @@ -60,7 +55,23 @@ do(State) -> ok -> case rlx_release:realized(Release) of true -> - copy_app_directories_to_output(State, Release, OutputDir); + case copy_app_directories_to_output(State, Release, OutputDir) of + {ok, State1} -> + case rlx_state:debug_info(State1) =:= strip + andalso rlx_state:dev_mode(State1) =:= false of + true -> + case beam_lib:strip_release(OutputDir) of + {ok, _} -> + {ok, State1}; + {error, _, Reason} -> + ?RLX_ERROR({strip_release, Reason}) + end; + false -> + {ok, State1} + end; + E -> + E + end; false -> ?RLX_ERROR({unresolved_release, RelName, RelVsn}) end; @@ -68,34 +79,37 @@ do(State) -> Error end. --spec format_error(ErrorDetail::term(), rlx_state:t()) -> iolist(). -format_error({unresolved_release, RelName, RelVsn}, _) -> +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error({unresolved_release, RelName, RelVsn}) -> io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); -format_error({ec_file_error, AppDir, TargetDir, E}, _) -> +format_error({ec_file_error, AppDir, TargetDir, E}) -> io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", [AppDir, TargetDir, E]); -format_error({config_does_not_exist, Path}, _) -> +format_error({config_does_not_exist, Path}) -> io_lib:format("The config file specified for this release (~s) does not exist!", [Path]); -format_error({specified_erts_does_not_exist, ErtsVersion}, _) -> +format_error({specified_erts_does_not_exist, ErtsVersion}) -> io_lib:format("Specified version of erts (~s) does not exist", [ErtsVersion]); -format_error({release_script_generation_error, RelFile}, _) -> +format_error({release_script_generation_error, RelFile}) -> io_lib:format("Unknown internal release error generating the release file to ~s", [RelFile]); -format_error({release_script_generation_warning, Module, Warnings}, _) -> +format_error({release_script_generation_warning, Module, Warnings}) -> ["Warnings generating release \s", rlx_util:indent(2), Module:format_warning(Warnings)]; -format_error({unable_to_create_output_dir, OutputDir}, _) -> +format_error({unable_to_create_output_dir, OutputDir}) -> io_lib:format("Unable to create output directory (possible permissions issue): ~s", [OutputDir]); -format_error({release_script_generation_error, Module, Errors}, State) -> +format_error({release_script_generation_error, Module, Errors}) -> ["Errors generating release \n", - rlx_util:indent(2), Module:format_error(Errors, State)]; -format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}, _) -> + rlx_util:indent(2), Module:format_error(Errors)]; +format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", [AppDir, TargetDir, rlx_util:indent(2), - file:format_error(Reason)]). + file:format_error(Reason)]); +format_error({strip_release, Reason}) -> + io_lib:format("Stripping debug info from release beam files failed becuase ~s", + [beam_lib:format_error(Reason)]). %%%=================================================================== %%% Internal Functions @@ -128,6 +142,7 @@ copy_app_directories_to_output(State, Release, OutputDir) -> LibDir = filename:join([OutputDir, "lib"]), ok = ec_file:mkdir_p(LibDir), IncludeSrc = rlx_state:include_src(State), + IncludeErts = rlx_state:get(State, include_erts, true), Apps = prepare_applications(State, rlx_release:application_details(Release)), Result = lists:filter(fun({error, _}) -> true; @@ -135,7 +150,7 @@ copy_app_directories_to_output(State, Release, OutputDir) -> false end, lists:flatten(ec_plists:map(fun(App) -> - copy_app(LibDir, App, IncludeSrc) + copy_app(LibDir, App, IncludeSrc, IncludeErts) end, Apps))), case Result of [E | _] -> @@ -152,7 +167,7 @@ prepare_applications(State, Apps) -> Apps end. -copy_app(LibDir, App, IncludeSrc) -> +copy_app(LibDir, App, IncludeSrc, IncludeErts) -> AppName = erlang:atom_to_list(rlx_app_info:name(App)), AppVsn = rlx_app_info:original_vsn(App), AppDir = rlx_app_info:dir(App), @@ -163,16 +178,69 @@ copy_app(LibDir, App, IncludeSrc) -> %% a release dir ok; true -> - copy_app(App, AppDir, TargetDir, IncludeSrc) + case IncludeErts of + false -> + case is_erts_lib(AppDir) of + true -> + []; + false -> + copy_app_(App, AppDir, TargetDir, IncludeSrc) + end; + _ -> + copy_app_(App, AppDir, TargetDir, IncludeSrc) + end end. -copy_app(App, AppDir, TargetDir, IncludeSrc) -> +is_erts_lib(Dir) -> + lists:prefix(filename:split(list_to_binary(code:lib_dir())), filename:split(Dir)). + +copy_app_(App, AppDir, TargetDir, IncludeSrc) -> remove_symlink_or_directory(TargetDir), case rlx_app_info:link(App) of true -> - link_directory(AppDir, TargetDir); + link_directory(AppDir, TargetDir), + rewrite_app_file(App, AppDir); false -> - copy_directory(AppDir, TargetDir, IncludeSrc) + copy_directory(AppDir, TargetDir, IncludeSrc), + rewrite_app_file(App, TargetDir) + end. + +%% If excluded apps exist in this App's applications list we must write a new .app +rewrite_app_file(App, TargetDir) -> + Name = rlx_app_info:name(App), + ActiveDeps = rlx_app_info:active_deps(App), + IncludedDeps = rlx_app_info:library_deps(App), + AppFile = filename:join([TargetDir, "ebin", ec_cnv:to_list(Name) ++ ".app"]), + + {ok, [{application, AppName, AppData}]} = file:consult(AppFile), + OldActiveDeps = proplists:get_value(applications, AppData, []), + OldIncludedDeps = proplists:get_value(included_applications, AppData, []), + + case {OldActiveDeps, OldIncludedDeps} of + {ActiveDeps, IncludedDeps} -> + ok; + _ -> + AppData1 = lists:keyreplace(applications + ,1 + ,AppData + ,{applications, ActiveDeps}), + AppData2 = lists:keyreplace(included_applications + ,1 + ,AppData1 + ,{included_applications, IncludedDeps}), + Spec = io_lib:format("~p.\n", [{application, AppName, AppData2}]), + write_file_if_contents_differ(AppFile, Spec) + end. + +write_file_if_contents_differ(Filename, Bytes) -> + ToWrite = iolist_to_binary(Bytes), + case file:read_file(Filename) of + {ok, ToWrite} -> + ok; + {ok, _} -> + file:write_file(Filename, ToWrite); + {error, _} -> + file:write_file(Filename, ToWrite) end. remove_symlink_or_directory(TargetDir) -> @@ -424,6 +492,7 @@ include_erts(State, Release, OutputDir, RelDir) -> make_boot_script(State, Release, OutputDir, RelDir) -> Options = [{path, [RelDir | rlx_util:get_code_paths(Release, OutputDir)]}, {outdir, RelDir}, + {variables, [{"ERTS_LIB_DIR", code:lib_dir()}]}, no_module_tests, silent], Name = erlang:atom_to_list(rlx_release:name(Release)), ReleaseFile = filename:join([RelDir, Name ++ ".rel"]), diff --git a/src/rlx_prv_overlay.erl b/src/rlx_prv_overlay.erl index 6df142b..cdce915 100644 --- a/src/rlx_prv_overlay.erl +++ b/src/rlx_prv_overlay.erl @@ -26,7 +26,11 @@ -export([init/1, do/1, - format_error/2]). + format_error/1]). + +-export([generate_overlay_vars/2, + make_template_name/2, + render_string/3]). -define(DIRECTORY_RE, ".*(\/|\\\\)$"). @@ -45,12 +49,7 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "overlay"}, - {short_desc, ""}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. %% @doc recursively dig down into the library directories specified in the state @@ -61,45 +60,50 @@ do(State) -> Release = rlx_state:get_realized_release(State, RelName, RelVsn), case rlx_release:realized(Release) of true -> - generate_overlay_vars(State, Release); + case generate_overlay_vars(State, Release) of + {error, Reason} -> + {error, Reason}; + OverlayVars -> + do_overlay(State, OverlayVars) + end; false -> ?RLX_ERROR({unresolved_release, RelName, RelVsn}) end. --spec format_error(ErrorDetail::term(), rlx_state:t()) -> iolist(). -format_error({unresolved_release, RelName, RelVsn}, _) -> +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error({unresolved_release, RelName, RelVsn}) -> io_lib:format("The release has not been resolved ~p-~s", [RelName, RelVsn]); -format_error({ec_file_error, AppDir, TargetDir, E}, _) -> +format_error({ec_file_error, AppDir, TargetDir, E}) -> io_lib:format("Unable to copy OTP App from ~s to ~s due to ~p", [AppDir, TargetDir, E]); -format_error({unable_to_read_varsfile, FileName, Reason}, _) -> +format_error({unable_to_read_varsfile, FileName, Reason}) -> io_lib:format("Unable to read vars file (~s) for overlay due to: ~p", [FileName, Reason]); -format_error({overlay_failed, Errors}, State) -> - [[format_error(rlx_util:error_reason(Error), State), "\n"] || Error <- Errors]; -format_error({dir_render_failed, Dir, Error}, _) -> +format_error({overlay_failed, Errors}) -> + [[format_error(rlx_util:error_reason(Error)), "\n"] || Error <- Errors]; +format_error({dir_render_failed, Dir, Error}) -> io_lib:format("rendering mkdir path failed ~s with ~p", [Dir, Error]); -format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}, _) -> +format_error({unable_to_make_symlink, AppDir, TargetDir, Reason}) -> io_lib:format("Unable to symlink directory ~s to ~s because \n~s~s", [AppDir, TargetDir, rlx_util:indent(2), file:format_error(Reason)]); -format_error({copy_failed, FromFile, ToFile, Err}, _) -> +format_error({copy_failed, FromFile, ToFile, Err}) -> io_lib:format("Unable to copy from ~s to ~s because of ~p", [FromFile, ToFile, Err]); -format_error({unable_to_write, ToFile, Reason}, _) -> +format_error({unable_to_write, ToFile, Reason}) -> io_lib:format("Unable to write to ~s because ~p", [ToFile, Reason]); -format_error({unable_to_enclosing_dir, ToFile, Reason}, _) -> +format_error({unable_to_enclosing_dir, ToFile, Reason}) -> io_lib:format("Unable to create enclosing directory for ~s because ~p", [ToFile, Reason]); -format_error({unable_to_render_template, FromFile, Reason}, _) -> +format_error({unable_to_render_template, FromFile, Reason}) -> io_lib:format("Unable to render template ~s because ~p", [FromFile, Reason]); -format_error({unable_to_compile_template, FromFile, Reason}, State) -> +format_error({unable_to_compile_template, FromFile, Reason}) -> io_lib:format("Unable to compile template ~s because \n~s", - [FromFile, [format_errors(F, Es, State) || {F, Es} <- Reason]]); -format_error({unable_to_make_dir, Absolute, Error}, _) -> + [FromFile, [format_errors(F, Es) || {F, Es} <- Reason]]); +format_error({unable_to_make_dir, Absolute, Error}) -> io_lib:format("Unable to make directory ~s because ~p", [Absolute, Error]). @@ -107,39 +111,39 @@ format_error({unable_to_make_dir, Absolute, Error}, _) -> %%% Internal Functions %%%=================================================================== -format_errors(File, [{none, Mod, E}|Es], State) -> +format_errors(File, [{none, Mod, E}|Es]) -> [io_lib:format("~s~s: ~ts~n", [rlx_util:indent(2), File, - Mod:format_error(E, State)]) - |format_errors(File, Es, State)]; -format_errors(File, [{{Line, Col}, Mod, E}|Es], State) -> + Mod:format_error(E)]) + |format_errors(File, Es)]; +format_errors(File, [{{Line, Col}, Mod, E}|Es]) -> [io_lib:format("~s~s:~w:~w: ~ts~n", [rlx_util:indent(2), File, Line, Col, - Mod:format_error(E, State)]) - |format_errors(File, Es, State)]; -format_errors(File, [{Line, Mod, E}|Es], State) -> + Mod:format_error(E)]) + |format_errors(File, Es)]; +format_errors(File, [{Line, Mod, E}|Es]) -> [io_lib:format("~s~s:~w: ~ts~n", [rlx_util:indent(2), File, Line, - Mod:format_error(E, State)]) - |format_errors(File, Es, State)]; -format_errors(_, [], _State) -> []. + Mod:format_error(E)]) + |format_errors(File, Es)]; +format_errors(_, []) -> []. -spec generate_overlay_vars(rlx_state:t(), rlx_release:t()) -> - {ok, rlx_state:t()} | relx:error(). + proplists:proplist() | relx:error(). generate_overlay_vars(State, Release) -> StateVars = generate_state_vars(State), ReleaseVars = generate_release_vars(Release), get_overlay_vars_from_file(State, StateVars ++ ReleaseVars). -spec get_overlay_vars_from_file(rlx_state:t(), proplists:proplist()) -> - {ok, rlx_state:t()} | relx:error(). + proplists:proplist() | relx:error(). get_overlay_vars_from_file(State, OverlayVars) -> case rlx_state:get(State, overlay_vars, undefined) of undefined -> - do_overlay(State, OverlayVars); + OverlayVars; [] -> - do_overlay(State, OverlayVars); + OverlayVars; [H | _]=FileNames when is_list(H) -> read_overlay_vars(State, OverlayVars, FileNames); FileName when is_list(FileName) -> @@ -147,16 +151,31 @@ get_overlay_vars_from_file(State, OverlayVars) -> end. -spec read_overlay_vars(rlx_state:t(), proplists:proplist(), [file:name()]) -> - {ok, rlx_state:t()} | relx:error(). + proplists:proplist() | relx:error(). read_overlay_vars(State, OverlayVars, FileNames) -> Terms = merge_overlay_vars(State, FileNames), case render_overlay_vars(OverlayVars, Terms, []) of {ok, NewTerms} -> - do_overlay(State, OverlayVars ++ NewTerms); + OverlayVars ++ NewTerms; Error -> Error end. +-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist()) -> + proplists:proplist(). +check_overlay_inclusion(State, RelativeRoot, Terms) -> + check_overlay_inclusion(State, RelativeRoot, Terms, []). + +-spec check_overlay_inclusion(rlx_state:t(), string(), proplists:proplist(), proplists:proplist()) -> + proplists:proplist(). +check_overlay_inclusion(State, RelativeRoot, [File|T], Terms) when is_list(File) -> + IncludedTerms = merge_overlay_vars(State, [filename:join(RelativeRoot, File)]), + check_overlay_inclusion(State, RelativeRoot, T, Terms ++ IncludedTerms); +check_overlay_inclusion(State, RelativeRoot, [Tuple|T], Terms) -> + check_overlay_inclusion(State, RelativeRoot, T, Terms ++ [Tuple]); +check_overlay_inclusion(_State, _RelativeRoot, [], Terms) -> + Terms. + -spec merge_overlay_vars(rlx_state:t(), [file:name()]) -> proplists:proplist(). merge_overlay_vars(State, FileNames) -> @@ -165,10 +184,14 @@ merge_overlay_vars(State, FileNames) -> RelativePath = filename:join(RelativeRoot, erlang:iolist_to_binary(FileName)), case file:consult(RelativePath) of {ok, Terms} -> - lists:ukeymerge(1, lists:ukeysort(1, Terms), Acc); + % the location of the included overlay files will be relative + %% to the current one being read + OverlayRelativeRoot = filename:dirname(FileName), + NewTerms = check_overlay_inclusion(State, OverlayRelativeRoot, Terms), + lists:ukeymerge(1, lists:ukeysort(1, NewTerms), Acc); {error, Reason} -> ec_cmd_log:warn(rlx_state:log(State), - format_error({unable_to_read_varsfile, FileName, Reason}, State)), + format_error({unable_to_read_varsfile, FileName, Reason})), Acc end end, [], FileNames). @@ -431,6 +454,19 @@ write_template(OverlayVars, FromFile, ToFile) -> Error end. +render_string(OverlayVars, Data, TemplateName) -> + case erlydtl:compile(erlang:iolist_to_binary(Data), TemplateName, ?ERLYDTL_COMPILE_OPTS) of + {ok, TemplateName} -> + case render(TemplateName, OverlayVars) of + {ok, IoList} -> + erlang:iolist_to_binary(IoList); + {error, Error} -> + ?RLX_ERROR({render_failed, Data, Error}) + end; + {error, Reason, _Warnings} -> + ?RLX_ERROR({unable_to_compile_template, Data, Reason}) + end. + -spec file_render_do(proplists:proplist(), iolist(), module(), fun((term()) -> {ok, rlx_state:t()} | relx:error())) -> {ok, rlx_state:t()} | relx:error(). diff --git a/src/rlx_prv_rel_discover.erl b/src/rlx_prv_rel_discover.erl new file mode 100644 index 0000000..4224e75 --- /dev/null +++ b/src/rlx_prv_rel_discover.erl @@ -0,0 +1,92 @@ +%% -*- 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. +%%% +-module(rlx_prv_rel_discover). +-behaviour(provider). + +-export([init/1, + do/1, + format_error/1]). + +-include("relx.hrl"). + +-define(PROVIDER, rel_discover). +-define(DEPS, [app_discover]). + +%%============================================================================ +%% API +%%============================================================================ + +-spec init(rlx_state:t()) -> {ok, rlx_state:t()}. +init(State) -> + State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, + {module, ?MODULE}, + {deps, ?DEPS}])), + {ok, State1}. + +-spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). +do(State0) -> + LibDirs = get_lib_dirs(State0), + AppMeta = rlx_state:available_apps(State0), + case rlx_rel_discovery:do(State0, LibDirs, AppMeta) of + {ok, Releases} -> + {ok, rlx_state:realized_releases(State0, lists:foldl(fun add/2, + ec_dictionary:new(ec_dict), + Releases))}; + Error -> + Error + end. + +-spec format_error(any()) -> iolist(). +format_error(_) -> + "". + +%%%=================================================================== +%%% Internal Functions +%%%=================================================================== +%% @doc only add the release if its not documented in the system +add(Rel, Dict) -> + RelName = rlx_release:name(Rel), + RelVsn = rlx_release:vsn(Rel), + ec_dictionary:add({RelName, RelVsn}, Rel, Dict). + +get_lib_dirs(State) -> + LibDirs0 = rlx_state:lib_dirs(State), + case rlx_state:get(State, default_libs, true) of + false -> + LibDirs0; + true -> + lists:flatten([LibDirs0, + add_release_output_dir(State)]) + end. + +add_release_output_dir(State) -> + case rlx_state:get(State, disable_discover_release_output, false) of + true -> + []; + false -> + Output = erlang:iolist_to_binary(rlx_state:base_output_dir(State)), + case ec_file:exists(erlang:binary_to_list(Output)) of + true -> + Output; + false -> + [] + end + end. diff --git a/src/rlx_prv_release.erl b/src/rlx_prv_release.erl index bd5acc4..bd58434 100644 --- a/src/rlx_prv_release.erl +++ b/src/rlx_prv_release.erl @@ -18,22 +18,18 @@ %%% @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 provider behaviour. -module(rlx_prv_release). -behaviour(provider). -export([init/1, do/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). -define(PROVIDER, resolve_release). --define(DEPS, [discover]). +-define(DEPS, [app_discover]). %%============================================================================ %% API @@ -43,12 +39,7 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, ""}, - {short_desc, ""}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. %% @doc recursively dig down into the library directories specified in the state @@ -58,26 +49,31 @@ do(State) -> DepGraph = create_dep_graph(State), find_default_release(State, DepGraph). --spec format_error(ErrorDetail::term(), rlx_state:t()) -> iolist(). -format_error(no_goals_specified, _) -> +-spec format_error(ErrorDetail::term()) -> iolist(). +format_error(no_goals_specified) -> "No goals specified for this release ~n"; -format_error({no_release_name, Vsn}, _) -> +format_error({release_erts_error, Dir}) -> + io_lib:format("Unable to find erts in ~s~n", [Dir]); +format_error({no_release_name, Vsn}) -> io_lib:format("A target release version was specified (~s) but no name", [Vsn]); -format_error({invalid_release_info, Info}, _) -> +format_error({invalid_release_info, Info}) -> io_lib:format("Target release information is in an invalid format ~p", [Info]); -format_error({multiple_release_names, RelA, RelB}, _) -> +format_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(no_releases_in_system, _) -> + "releases in the config: ~s, ~s", + [RelA, RelB]); +format_error(no_releases_in_system) -> "No releases have been specified in the system!"; -format_error({no_releases_for, RelName}, _) -> +format_error({no_releases_for, RelName}) -> io_lib:format("No releases exist in the system for ~s!", [RelName]); -format_error({release_not_found, {RelName, RelVsn}}, _) -> +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}, _) -> +format_error({failed_solve, Error}) -> io_lib:format("Failed to solve release:\n ~s", - [rlx_depsolver:format_error({error, Error})]). + [rlx_depsolver:format_error({error, Error})]); +format_error({release_error, Error}) -> + io_lib:format("Failed to resolve release:\n ~p~n", [Error]). + %%%=================================================================== %%% Internal Functions @@ -92,28 +88,30 @@ create_dep_graph(State) -> Deps = rlx_app_info:active_deps(App) ++ rlx_app_info:library_deps(App), rlx_depsolver:add_package_version(Graph1, - AppName, - AppVsn, - Deps) + AppName, + AppVsn, + Deps) end, Graph0, Apps). -spec find_default_release(rlx_state:t(), rlx_depsolver:t()) -> {ok, rlx_state:t()} | relx:error(). find_default_release(State, DepGraph) -> - try rlx_state:default_configured_release(State) of - {undefined, undefined} -> - resolve_default_release(State, DepGraph); - {RelName, undefined} -> - resolve_default_version(State, DepGraph, RelName); - {undefined, Vsn} -> - ?RLX_ERROR({no_release_name, Vsn}); - {RelName, RelVsn} -> - solve_release(State, DepGraph, RelName, RelVsn); - undefined -> - ?RLX_ERROR(no_releases_in_system) + try + case rlx_state:default_configured_release(State) of + {undefined, undefined} -> + resolve_default_release(State, DepGraph); + {RelName, undefined} -> + resolve_default_version(State, DepGraph, RelName); + {undefined, Vsn} -> + ?RLX_ERROR({no_release_name, Vsn}); + {RelName, RelVsn} -> + solve_release(State, DepGraph, RelName, RelVsn); + undefined -> + ?RLX_ERROR(no_releases_in_system) + end catch - {multiple_release_names, _, _}=Error -> + throw:{multiple_release_names, _, _}=Error -> ?RLX_ERROR(Error) end. @@ -156,8 +154,8 @@ release_sort({{RelA, _}, _}, {{RelB, _}, _}) -> solve_release(State0, DepGraph, RelName, RelVsn) -> ec_cmd_log:debug(rlx_state:log(State0), - "Solving Release ~p-~s~n", - [RelName, RelVsn]), + "Solving Release ~p-~s~n", + [RelName, RelVsn]), try Release = case get_realized_release(State0, RelName, RelVsn) of @@ -184,20 +182,32 @@ solve_release(State0, DepGraph, RelName, RelVsn) -> end. set_resolved(State, Release0, Pkgs) -> - case rlx_release:realize(Release0, Pkgs, rlx_state:available_apps(State)) of - {ok, Release1} -> - ec_cmd_log:info(rlx_state:log(State), - "Resolved ~p-~s~n", - [rlx_release:name(Release1), - rlx_release:vsn(Release1)]), - ec_cmd_log:debug(rlx_state:log(State), - fun() -> - rlx_release:format(0, Release1) - end), - {ok, rlx_state:add_realized_release(State, Release1)}; - {error, E} -> - ?RLX_ERROR({release_error, E}) - end. + case rlx_release:realize(Release0, Pkgs, rlx_state:available_apps(State)) of + {ok, Release1} -> + ec_cmd_log:info(rlx_state:log(State), + "Resolved ~p-~s~n", + [rlx_release:name(Release1), + rlx_release:vsn(Release1)]), + ec_cmd_log:debug(rlx_state:log(State), + fun() -> + rlx_release:format(0, Release1) + end), + case rlx_state:get(State, include_erts, undefined) of + IncludeErts when is_atom(IncludeErts) -> + {ok, rlx_state:add_realized_release(State, Release1)}; + ErtsDir -> + try + [Erts | _] = filelib:wildcard(filename:join(ErtsDir, "erts-*")), + [_, ErtsVsn] = string:tokens(filename:basename(Erts), "-"), + {ok, rlx_state:add_realized_release(State, rlx_release:erts(Release1, ErtsVsn))} + catch + _:_ -> + ?RLX_ERROR({release_erts_error, ErtsDir}) + end + end; + {error, E} -> + ?RLX_ERROR({release_error, E}) + end. get_realized_release(State, RelName, RelVsn) -> try diff --git a/src/rlx_prv_relup.erl b/src/rlx_prv_relup.erl index df6f831..6bf5d56 100644 --- a/src/rlx_prv_relup.erl +++ b/src/rlx_prv_relup.erl @@ -26,12 +26,12 @@ -export([init/1, do/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). -define(PROVIDER, relup). --define(DEPS, [release]). +-define(DEPS, [rel_discover, release]). %%============================================================================ %% API @@ -41,12 +41,7 @@ init(State) -> State1 = rlx_state:add_provider(State, providers:create([{name, ?PROVIDER}, {module, ?MODULE}, - {bare, false}, - {deps, ?DEPS}, - {example, "relup"}, - {short_desc, "Builds release upgrade for latest and last release."}, - {desc, ""}, - {opts, []}])), + {deps, ?DEPS}])), {ok, State1}. -spec do(rlx_state:t()) -> {ok, rlx_state:t()} | relx:error(). @@ -55,24 +50,24 @@ do(State) -> Release0 = rlx_state:get_realized_release(State, RelName, RelVsn), make_relup(State, Release0). -format_error({relup_generation_error, CurrentName, UpFromName}, _) -> +format_error({relup_generation_error, CurrentName, UpFromName}) -> io_lib:format("Unknown internal release error generating the relup from ~s to ~s", [UpFromName, CurrentName]); -format_error({relup_generation_warning, Module, Warnings}, _) -> +format_error({relup_generation_warning, Module, Warnings}) -> ["Warnings generating relup \s", rlx_util:indent(2), Module:format_warning(Warnings)]; -format_error({no_upfrom_release_found, undefined}, _) -> +format_error({no_upfrom_release_found, undefined}) -> io_lib:format("No earlier release for relup found", []); -format_error({no_upfrom_release_found, Vsn}, _) -> +format_error({no_upfrom_release_found, Vsn}) -> io_lib:format("Upfrom release version (~s) for relup not found", [Vsn]); format_error({relup_script_generation_error, {relup_script_generation_error, systools_relup, - {missing_sasl, _}}}, _) -> + {missing_sasl, _}}}) -> "Unfortunately, due to requirements in systools, you need to have the sasl application " "in both the current release and the release to upgrade from."; -format_error({relup_script_generation_error, Module, Errors}, State) -> +format_error({relup_script_generation_error, Module, Errors}) -> ["Errors generating relup \n", - rlx_util:indent(2), Module:format_error(Errors, State)]. + rlx_util:indent(2), Module:format_error(Errors)]. make_relup(State, Release) -> Vsn = rlx_state:upfrom(State), diff --git a/src/rlx_rel_discovery.erl b/src/rlx_rel_discovery.erl index b7c15bc..1cfdc87 100644 --- a/src/rlx_rel_discovery.erl +++ b/src/rlx_rel_discovery.erl @@ -18,10 +18,6 @@ %%% @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 provider behaviour. -module(rlx_rel_discovery). -export([do/3, diff --git a/src/rlx_release.erl b/src/rlx_release.erl index 7b82119..1d333bf 100644 --- a/src/rlx_release.erl +++ b/src/rlx_release.erl @@ -41,7 +41,7 @@ canonical_name/1, format/1, format/2, - format_error/2]). + format_error/1]). -export_type([t/0, name/0, @@ -217,14 +217,14 @@ format_goal({Constraint, AppType, AppInc}) -> format_goal(Constraint) -> rlx_depsolver:format_constraint(Constraint). --spec format_error(Reason::term(), rlx_state:t()) -> iolist(). -format_error({topo_error, E}, State) -> - rlx_topo:format_error(E, State); -format_error({failed_to_parse, Con}, _) -> +-spec format_error(Reason::term()) -> iolist(). +format_error({topo_error, E}) -> + rlx_topo:format_error(E); +format_error({failed_to_parse, Con}) -> io_lib:format("Failed to parse constraint ~p", [Con]); -format_error({invalid_constraint, _, Con}, _) -> +format_error({invalid_constraint, _, Con}) -> io_lib:format("Invalid constraint specified ~p", [Con]); -format_error({not_realized, Name, Vsn}, _) -> +format_error({not_realized, Name, Vsn}) -> io_lib:format("Unable to produce metadata release: ~p-~s has not been realized", [Name, Vsn]). diff --git a/src/rlx_state.erl b/src/rlx_state.erl index 550a44a..3bd818a 100644 --- a/src/rlx_state.erl +++ b/src/rlx_state.erl @@ -36,6 +36,10 @@ overrides/2, skip_apps/1, skip_apps/2, + exclude_apps/1, + exclude_apps/2, + debug_info/1, + debug_info/2, goals/1, goals/2, config_file/1, @@ -102,6 +106,8 @@ sys_config :: file:filename() | undefined, overrides=[] :: [{AppName::atom(), Directory::file:filename()}], skip_apps=[] :: [AppName::atom()], + exclude_apps=[] :: [AppName::atom()], + debug_info=keep :: keep | strip, configured_releases :: releases(), realized_releases :: releases(), dev_mode=false :: boolean(), @@ -154,7 +160,7 @@ new(Config, CommandLineConfig, Targets) realized_releases=ec_dictionary:new(ec_dict), config_values=ec_dictionary:new(ec_dict)}, State1 = rlx_state:put(State0, default_libs, true), - State2 = rlx_state:put(State1, system_libs, undefined), + State2 = rlx_state:put(State1, system_libs, true), State3 = rlx_state:put(State2, overlay_vars, []), create_logic_providers(State3). @@ -183,6 +189,23 @@ skip_apps(#state_t{skip_apps=Apps}) -> skip_apps(State, SkipApps) -> State#state_t{skip_apps=SkipApps}. +-spec exclude_apps(t()) -> [AppName::atom()]. +exclude_apps(#state_t{exclude_apps=Apps}) -> + Apps. + +%% @doc the application overrides for the system +-spec exclude_apps(t(), [AppName::atom()]) -> t(). +exclude_apps(State, SkipApps) -> + State#state_t{exclude_apps=SkipApps}. + +-spec debug_info(t()) -> keep | strip. +debug_info(#state_t{debug_info=DebugInfo}) -> + DebugInfo. + +-spec debug_info(t(), keep | strip) -> t(). +debug_info(State, DebugInfo) -> + State#state_t{debug_info=DebugInfo}. + %% @doc get the current log state for the system -spec log(t()) -> ec_cmd_log:t(). log(#state_t{log=LogState}) -> @@ -438,7 +461,8 @@ add_hook(post, {PreHooks, PostHooks}, Hook) -> -spec create_logic_providers(t()) -> t() | relx:error(). create_logic_providers(State) -> - create_all(State, [rlx_prv_discover, + create_all(State, [rlx_prv_app_discover, + rlx_prv_rel_discover, rlx_prv_overlay, rlx_prv_release, rlx_prv_assembler, diff --git a/src/rlx_topo.erl b/src/rlx_topo.erl index 1d5de7e..d24f227 100644 --- a/src/rlx_topo.erl +++ b/src/rlx_topo.erl @@ -34,7 +34,7 @@ -export([sort/1, sort_apps/1, - format_error/2]). + format_error/1]). -include("relx.hrl"). @@ -72,8 +72,8 @@ sort(Pairs) -> iterate(Pairs, [], all(Pairs)). %% @doc nicely format the error from the sort. --spec format_error(Reason::term(), rlx_state:t()) -> iolist(). -format_error({cycle, Pairs}, _) -> +-spec format_error(Reason::term()) -> iolist(). +format_error({cycle, Pairs}) -> ["Cycle detected in dependency graph, this must be resolved " "before we can continue:\n", case Pairs of diff --git a/test/rlx_archive_SUITE.erl b/test/rlx_archive_SUITE.erl new file mode 100644 index 0000000..27cf94a --- /dev/null +++ b/test/rlx_archive_SUITE.erl @@ -0,0 +1,132 @@ +%%% @author Tristan Sloughter <[email protected]> +%%% @copyright (C) 2015, Tristan Sloughter +-module(rlx_archive_SUITE). + +-export([suite/0, + init_per_suite/1, + end_per_suite/1, + init_per_testcase/2, + all/0, + basic_tar/1, + exclude_erts/1]). + +-include_lib("common_test/include/ct.hrl"). +-include_lib("eunit/include/eunit.hrl"). +-include_lib("kernel/include/file.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, rlx_test_utils:create_random_name("lib_dir1_")]), + ok = rlx_util:mkdir_p(LibDir1), + State = rlx_state:new([], [{lib_dirs, [LibDir1]}], [release]), + {ok, State1} = rlx_config:do(State), + [{lib1, LibDir1}, + {state, State1} | Config]. + +all() -> + [basic_tar, exclude_erts]. + +basic_tar(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + rlx_test_utils:create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{rlx_test_utils:create_random_name("lib_app1_"), rlx_test_utils:create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + {ok, State} = relx:do([{relname, foo}, + {relvsn, "0.0.1"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 3}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["release", "tar"]), + + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), + + TarFile = filename:join([OutputDir, "foo", "foo-0.0.1.tar.gz"]), + {ok, Files} = erl_tar:table(TarFile, [compressed]), + ?assert(lists:any(fun(X) -> re:run(X, "lib/stdlib-.*/ebin/.*") =/= nomatch end, Files)), + ?assert(lists:any(fun(X) -> re:run(X, "lib/kernel-.*/ebin/.*") =/= nomatch end, Files)), + ?assert(filelib:is_regular(TarFile)). + +exclude_erts(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> + rlx_test_utils:create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + end)(App) + || + App <- + [{rlx_test_utils:create_random_name("lib_app1_"), rlx_test_utils:create_random_vsn()} + || _ <- lists:seq(1, 100)]], + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + {ok, State} = relx:do([{relname, foo}, + {relvsn, "0.0.1"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 3}, + {output_dir, OutputDir}, + {config, ConfigFile}, + {include_erts, false}, + {system_libs, false}], ["release", "tar"]), + + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)), + + TarFile = filename:join([OutputDir, "foo", "foo-0.0.1.tar.gz"]), + {ok, Files} = erl_tar:table(TarFile, [compressed]), + ?assert(lists:all(fun(X) -> re:run(X, "lib/stdlib-.*/ebin/.*") =:= nomatch end, Files)), + ?assert(lists:all(fun(X) -> re:run(X, "lib/kernel-.*/ebin/.*") =:= nomatch end, Files)), + ?assert(filelib:is_regular(TarFile)). diff --git a/test/rlx_discover_SUITE.erl b/test/rlx_discover_SUITE.erl index 2db8e4d..4a3b817 100644 --- a/test/rlx_discover_SUITE.erl +++ b/test/rlx_discover_SUITE.erl @@ -77,8 +77,8 @@ normal_case(Config) -> || _ <- lists:seq(1, 100)]], State0 = rlx_state:put(proplists:get_value(state, Config), default_libs, false), - {ok, State1} = providers:new(rlx_prv_discover, State0), - DiscoverProvider = providers:get_provider(discover, rlx_state:providers(State1)), + {ok, State1} = providers:new(rlx_prv_app_discover, State0), + DiscoverProvider = providers:get_provider(app_discover, rlx_state:providers(State1)), {ok, State2} = providers:do(DiscoverProvider, State1), lists:foreach(fun(App) -> @@ -114,10 +114,10 @@ no_beam_case(Config) -> AppDir = filename:join([LibDir2, BadName]), write_app_file(AppDir, BadName, BadVsn), State0 = proplists:get_value(state, Config), - %% Deliberately disable release discovery when running `rlx_prv_discover` + %% Deliberately disable release discovery when running `rlx_prv_app_discover` State1 = rlx_state:put(State0, disable_rel_discovery, true), - {ok, State2} = providers:new(rlx_prv_discover, State1), - DiscoverProvider = providers:get_provider(discover, rlx_state:providers(State2)), + {ok, State2} = providers:new(rlx_prv_app_discover, State1), + DiscoverProvider = providers:get_provider(app_discover, rlx_state:providers(State2)), ?assertMatch({ok, _}, providers:do(DiscoverProvider, State2)). @@ -146,8 +146,8 @@ bad_ebin_case(Config) -> ok = filelib:ensure_dir(Filename), ok = ec_file:write_term(Filename, get_bad_app_metadata(BadName, BadVsn)), State0 = proplists:get_value(state, Config), - {ok, State1} = providers:new(rlx_prv_discover, State0), - DiscoverProvider = providers:get_provider(discover, rlx_state:providers(State1)), + {ok, State1} = providers:new(rlx_prv_app_discover, State0), + DiscoverProvider = providers:get_provider(app_discover, rlx_state:providers(State1)), {ok, State2} = providers:do(DiscoverProvider, State1), ?assertMatch([], [App || App <- rlx_state:available_apps(State2), BadName =:= rlx_app_info:name(App)]). @@ -172,8 +172,8 @@ shallow_app_discovery(Config) -> State0 = rlx_state:put(proplists:get_value(state, Config), default_libs, false), State1 = rlx_state:put(State0, enable_shallow_app_discovery, true), - {ok, State2} = providers:new(rlx_prv_discover, State1), - DiscoverProvider = providers:get_provider(discover, rlx_state:providers(State2)), + {ok, State2} = providers:new(rlx_prv_app_discover, State1), + DiscoverProvider = providers:get_provider(app_discover, rlx_state:providers(State2)), {ok, State3} = providers:do(DiscoverProvider, State2), lists:foreach(fun(App) -> ?assertMatch(true, lists:member(App, rlx_state:available_apps(State3))) diff --git a/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl index 112afc8..766eb40 100644 --- a/test/rlx_release_SUITE.erl +++ b/test/rlx_release_SUITE.erl @@ -29,6 +29,7 @@ make_scriptless_release/1, make_overridden_release/1, make_skip_app_release/1, + make_exclude_app_release/1, make_auto_skip_empty_app_release/1, make_app_type_none_release/1, make_rerun_overridden_release/1, @@ -40,12 +41,12 @@ make_relup_release/1, make_relup_release2/1, make_one_app_top_level_release/1, - make_dev_mode_release/1]). + make_dev_mode_release/1, + make_config_script_release/1]). -include_lib("common_test/include/ct.hrl"). -include_lib("eunit/include/eunit.hrl"). -include_lib("kernel/include/file.hrl"). --include_lib("kernel/include/file.hrl"). suite() -> [{timetrap,{seconds,30}}]. @@ -58,7 +59,7 @@ end_per_suite(_Config) -> init_per_testcase(_, Config) -> DataDir = proplists:get_value(data_dir, Config), - LibDir1 = filename:join([DataDir, create_random_name("lib_dir1_")]), + LibDir1 = filename:join([DataDir, rlx_test_utils:create_random_name("lib_dir1_")]), ok = rlx_util:mkdir_p(LibDir1), State = rlx_state:new([], [{lib_dirs, [LibDir1]}], [release]), {ok, State1} = rlx_config:do(State), @@ -68,30 +69,32 @@ init_per_testcase(_, Config) -> all() -> [make_release, make_extend_release, make_scriptless_release, make_overridden_release, make_auto_skip_empty_app_release, - make_skip_app_release, make_app_type_none_release, + make_skip_app_release, make_exclude_app_release, make_app_type_none_release, make_implicit_config_release, make_rerun_overridden_release, overlay_release, make_goalless_release, make_depfree_release, make_invalid_config_release, make_relup_release, make_relup_release2, - make_one_app_top_level_release, make_dev_mode_release]. + make_one_app_top_level_release, make_dev_mode_release, + make_config_script_release]. make_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), + [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) + rlx_test_utils:create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) end)(App) - || + || App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], + [{rlx_test_utils:create_random_name("lib_app1_"), rlx_test_utils:create_random_vsn()} + || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}, @@ -99,7 +102,7 @@ make_release(Config) -> [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile), [{{foo, "0.0.2"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), @@ -114,22 +117,15 @@ make_release(Config) -> make_extend_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}, @@ -137,9 +133,9 @@ make_extend_release(Config) -> [goal_app_2]}, {lib_dirs, [filename:join(LibDir1, "*")]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), - ?assertMatch({multiple_release_names,foo_test,foo}, + ?assertMatch({error, {rlx_prv_release, {multiple_release_names,foo_test,foo}}}, catch relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile)), {ok, State} = relx:do(foo_test, undefined, [], [LibDir1], 3, @@ -156,19 +152,12 @@ make_extend_release(Config) -> make_invalid_config_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), ok = ec_file:write(ConfigFile, @@ -176,35 +165,28 @@ make_invalid_config_release(Config) -> [goal_app_1, goal_app_2,]}"), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {error, {rlx_config, {consult, _, _}}} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile). make_scriptless_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{generate_start_script, false}, {release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile), @@ -223,36 +205,30 @@ make_scriptless_release(Config) -> make_overridden_release(Config) -> DataDir = proplists:get_value(data_dir, Config), - OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), + OverrideDir1 = filename:join([DataDir, rlx_test_utils:create_random_name("override_dir_")]), LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - OverrideApp = create_random_name("override_app"), - OverrideVsn = create_random_vsn(), + + OverrideApp = rlx_test_utils:create_random_name("override_app"), + OverrideVsn = rlx_test_utils:create_random_vsn(), OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" ++ OverrideVsn), OverrideAppName = erlang:list_to_atom(OverrideApp), - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), + rlx_test_utils:create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, erlang:list_to_atom(OverrideApp), goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, OutputDir, [{OverrideAppName, OverrideAppDir}], @@ -273,27 +249,20 @@ make_overridden_release(Config) -> make_skip_app_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1]}, {skip_apps, [goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, OutputDir, [], @@ -308,36 +277,57 @@ make_skip_app_release(Config) -> ?assertNot(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). +%% Test to ensure that an excluded app and its deps are not included in a release +make_exclude_app_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel, non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel, non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1]}, + {exclude_apps, [non_goal_1]}]), + OutputDir = filename:join([proplists:get_value(data_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + {ok, Cwd} = file:get_cwd(), + {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, + OutputDir, [], + ConfigFile), + [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + AppSpecs = rlx_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assertNot(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assertNot(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)). + make_auto_skip_empty_app_release(Config) -> DataDir = proplists:get_value(data_dir, Config), - EmptyAppDir1 = filename:join([DataDir, create_random_name("skip_app_dir_")]), + EmptyAppDir1 = filename:join([DataDir, rlx_test_utils:create_random_name("skip_app_dir_")]), LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - EmptyAppApp = create_random_name("empty_app_app"), - EmptyAppVsn = create_random_vsn(), + + EmptyAppApp = rlx_test_utils:create_random_name("empty_app_app"), + EmptyAppVsn = rlx_test_utils:create_random_vsn(), EmptyAppAppName = erlang:list_to_atom(EmptyAppApp), - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - create_empty_app(EmptyAppDir1, EmptyAppApp, EmptyAppVsn, [stdlib,kernel], []), + rlx_test_utils:create_empty_app(EmptyAppDir1, EmptyAppApp, EmptyAppVsn, [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, OutputDir, [], @@ -355,27 +345,20 @@ make_auto_skip_empty_app_release(Config) -> make_app_type_none_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, {goal_app_2, none}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), {ok, State} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, OutputDir, [], @@ -394,27 +377,20 @@ make_implicit_config_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), FooRoot = filename:join([LibDir1, "foodir1", "foodir2"]), filelib:ensure_dir(filename:join([FooRoot, "tmp"])), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), ok = file:set_cwd(FooRoot), {ok, FooRoot} = file:get_cwd(), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, @@ -432,39 +408,33 @@ make_implicit_config_release(Config) -> make_rerun_overridden_release(Config) -> DataDir = proplists:get_value(data_dir, Config), - OverrideDir1 = filename:join([DataDir, create_random_name("override_dir_")]), + OverrideDir1 = filename:join([DataDir, rlx_test_utils:create_random_name("override_dir_")]), LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - OverrideApp = create_random_name("override_app"), - OverrideVsn = create_random_vsn(), + + OverrideApp = rlx_test_utils:create_random_name("override_app"), + OverrideVsn = rlx_test_utils:create_random_vsn(), OverrideAppDir = filename:join(OverrideDir1, OverrideApp ++ "-" ++ OverrideVsn), OverrideAppName = erlang:list_to_atom(OverrideApp), - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), + rlx_test_utils:create_app(OverrideDir1, OverrideApp, OverrideVsn, [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), OverlayVars = filename:join([LibDir1, "vars1.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1, erlang:list_to_atom(OverrideApp), goal_app_2]}, {overlay_vars, [OverlayVars]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), {ok, _} = relx:do(Cwd, undefined, undefined, [], [LibDir1], 3, OutputDir, [{OverrideAppName, OverrideAppDir}], @@ -491,30 +461,24 @@ make_rerun_overridden_release(Config) -> overlay_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), OverlayVars1 = filename:join([LibDir1, "vars1.config"]), OverlayVars2 = filename:join([LibDir1, "vars2.config"]), + OverlayVars3 = filename:join([LibDir1, "vars3.config"]), Template = filename:join([LibDir1, "test_template"]), TestDir = "first_test_dir", TestFile = "test_file", TestDirFull = filename:join([LibDir1, TestDir]), TestFileFull = filename:join(TestDirFull, TestFile), SecondTestDir = "second_test_dir", - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{overlay_vars, [OverlayVars1, OverlayVars2]}, {overlay, [{mkdir, "{{target_dir}}/fooo"}, {copy, OverlayVars1, @@ -524,31 +488,38 @@ overlay_release(Config) -> {copy, TestDirFull, "{{target_dir}}/"++SecondTestDir++"/"}, {template, Template, - "{{target_dir}}/test_template_resolved"}]}, + "{{target_dir}}/test_template_resolved"}, + {template, Template, + "bin/{{default_release_name}}-{{default_release_version}}"}]}, {release, {foo, "0.0.1"}, [goal_app_1, goal_app_2]}]), VarsFile1 = filename:join([LibDir1, "vars1.config"]), - write_config(VarsFile1, [{yahoo, "yahoo"}, + rlx_test_utils:write_config(VarsFile1, [{yahoo, "yahoo"}, {yahoo2, [{foo, "bar"}]}, {yahoo3, [{bar, "{{yahoo}}/{{yahoo2.foo}}"}]}, {foo_dir, "foodir"}]), VarsFile2 = filename:join([LibDir1, "vars2.config"]), - write_config(VarsFile2, [{google, "yahoo"}, - {yahoo2, [{foo, "foo"}]}]), + rlx_test_utils:write_config(VarsFile2, [{google, "yahoo"}, + {yahoo2, [{foo, "foo"}]}, + OverlayVars3]), + + VarsFile3 = filename:join([LibDir1, "vars3.config"]), + rlx_test_utils:write_config(VarsFile3, [{google, "yahoo"}, + {yahoo4, [{foo, "{{yahoo}}/{{yahoo2.foo}}4"}]}]), ok = rlx_util:mkdir_p(TestDirFull), - ok = file:write_file(TestFileFull, test_template_contents()), + ok = file:write_file(TestFileFull, rlx_test_utils:test_template_contents()), TemplateFile = filename:join([LibDir1, "test_template"]), - ok = file:write_file(TemplateFile, test_template_contents()), + ok = file:write_file(TemplateFile, rlx_test_utils:test_template_contents()), {ok, FileInfo} = file:read_file_info(TemplateFile), ok = file:write_file_info(TemplateFile, FileInfo#file_info{mode=8#00777}), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile), @@ -639,57 +610,45 @@ overlay_release(Config) -> proplists:get_value(foo_dir, TemplateData)), ?assertEqual("yahoo/foo", proplists:get_value(yahoo3, TemplateData)), + ?assertEqual("yahoo/foo4", + proplists:get_value(yahoo4, TemplateData)), ?assertEqual("yahoo", proplists:get_value(google, TemplateData)). make_goalless_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, []}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), ?assertMatch({error,{rlx_prv_release,no_goals_specified}}, relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile)). make_depfree_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [kernel,stdlib], []), - create_app(LibDir1, "non_goal_2", "0.0.1", [kernel,stdlib], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [kernel,stdlib], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [kernel,stdlib], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [kernel,stdlib], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [kernel,stdlib], []), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [kernel,stdlib], []), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [goal_app_1]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile), [{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), @@ -699,29 +658,22 @@ make_depfree_release(Config) -> make_relup_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), - {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), - {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - write_appup_file(GA1, "0.0.2"), - write_appup_file(GA2, "0.0.2"), + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + rlx_test_utils:write_appup_file(GA1, "0.0.2"), + rlx_test_utils:write_appup_file(GA2, "0.0.2"), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [sasl, {goal_app_1, "0.0.1"}, @@ -735,11 +687,18 @@ make_relup_release(Config) -> {goal_app_1, "0.0.3"}, {goal_app_2, "0.0.3"}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, _} = relx:do(foo, "0.0.1", [], [LibDir1], 3, - OutputDir, ConfigFile), + OutputDir, ConfigFile), + {ok, _} = relx:do(foo, "0.0.2", [], [LibDir1], 3, - OutputDir, ConfigFile), + OutputDir, ConfigFile), + + %% Goal apps are removed to simulate a users dev environment where the apps + %% being used in an appup/relup are likely only under _rel/<release>/lib/ + ec_file:remove(filename:join(LibDir1, "goal_app_1-0.0.1"), [recursive]), + ec_file:remove(filename:join(LibDir1, "goal_app_1-0.0.2"), [recursive]), + {ok, State} = relx:do([{relname, foo}, {relvsn, "0.0.3"}, {goals, []}, @@ -779,29 +738,22 @@ make_relup_release(Config) -> make_relup_release2(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), - {ok, GA1} = create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), - {ok, GA2} = create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), - - write_appup_file(GA1, "0.0.1"), - write_appup_file(GA2, "0.0.1"), + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.2", [stdlib,kernel,non_goal_1], []), + {ok, GA1} = rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.3", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.2", [stdlib,kernel,goal_app_1,non_goal_2], []), + {ok, GA2} = rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.3", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + rlx_test_utils:write_appup_file(GA1, "0.0.1"), + rlx_test_utils:write_appup_file(GA2, "0.0.1"), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [sasl, {goal_app_1, "0.0.1"}, @@ -815,19 +767,19 @@ make_relup_release2(Config) -> {goal_app_1, "0.0.3"}, {goal_app_2, "0.0.3"}]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, _} = relx:do(foo, "0.0.1", [], [LibDir1], 3, OutputDir, ConfigFile), {ok, _} = relx:do(foo, "0.0.2", [], [LibDir1], 3, OutputDir, ConfigFile), {ok, State} = relx:do([{relname, foo}, - {relvsn, "0.0.3"}, - {upfrom, "0.0.1"}, - {goals, []}, - {lib_dirs, [LibDir1]}, - {log_level, 3}, - {output_dir, OutputDir}, - {config, ConfigFile}], ["release", "relup"]), + {relvsn, "0.0.3"}, + {upfrom, "0.0.1"}, + {goals, []}, + {lib_dirs, [LibDir1]}, + {log_level, 3}, + {output_dir, OutputDir}, + {config, ConfigFile}], ["release", "relup"]), %% we should have one 'resolved' release and three discovered realized_releases. ?assertMatch([{foo, "0.0.1"}, @@ -860,15 +812,15 @@ make_relup_release2(Config) -> make_one_app_top_level_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - {ok, AppInfo} = create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel], []), + {ok, AppInfo} = rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel], []), AppDir = rlx_app_info:dir(AppInfo), ConfigFile = filename:join([AppDir, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{release, {foo, "0.0.1"}, [{goal_app_1, "0.0.1"}]}]), OutputDir = filename:join([AppDir, - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, Cwd} = file:get_cwd(), ok = file:set_cwd(AppDir), @@ -883,28 +835,21 @@ make_one_app_top_level_release(Config) -> make_dev_mode_release(Config) -> LibDir1 = proplists:get_value(lib1, Config), - [(fun({Name, Vsn}) -> - create_app(LibDir1, Name, Vsn, [kernel, stdlib], []) - end)(App) - || - App <- - [{create_random_name("lib_app1_"), create_random_vsn()} - || _ <- lists:seq(1, 100)]], - create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), - create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), - create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), - create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), - create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), SysConfig = filename:join([LibDir1, "config", "sys.config"]), - write_config(SysConfig, [{this_is_a_test, "yup it is"}]), + rlx_test_utils:write_config(SysConfig, [{this_is_a_test, "yup it is"}]), VmArgs = filename:join([LibDir1, "config", "vm.args"]), ec_file:write(VmArgs, ""), ConfigFile = filename:join([LibDir1, "relx.config"]), - write_config(ConfigFile, + rlx_test_utils:write_config(ConfigFile, [{dev_mode, true}, {sys_config, SysConfig}, {vm_args, VmArgs}, @@ -912,7 +857,7 @@ make_dev_mode_release(Config) -> [goal_app_1, goal_app_2]}]), OutputDir = filename:join([proplists:get_value(data_dir, Config), - create_random_name("relx-output")]), + rlx_test_utils:create_random_name("relx-output")]), {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, OutputDir, ConfigFile), [{{foo, "0.0.1"}, _Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), @@ -927,99 +872,52 @@ make_dev_mode_release(Config) -> ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "releases", "0.0.1", "vm.args"]))). +make_config_script_release(Config) -> + LibDir1 = proplists:get_value(lib1, Config), + FooRoot = filename:join([LibDir1, "foodir1", "foodir2"]), + filelib:ensure_dir(filename:join([FooRoot, "tmp"])), + + rlx_test_utils:create_app(LibDir1, "goal_app_1", "0.0.1", [stdlib,kernel,non_goal_1], []), + rlx_test_utils:create_app(LibDir1, "lib_dep_1", "0.0.1", [stdlib,kernel], []), + rlx_test_utils:create_app(LibDir1, "goal_app_2", "0.0.1", [stdlib,kernel,goal_app_1,non_goal_2], []), + rlx_test_utils:create_app(LibDir1, "non_goal_1", "0.0.1", [stdlib,kernel], [lib_dep_1]), + rlx_test_utils:create_app(LibDir1, "non_goal_2", "0.0.1", [stdlib,kernel], []), + + ConfigFile = filename:join([LibDir1, "relx.config"]), + rlx_test_utils:write_config(ConfigFile, + [{release, {foo, "0.0.1"}, + [goal_app_1, + goal_app_2]}]), + ConfigScriptFile = filename:join([LibDir1, "relx.config.script"]), + ok = file:write_file(ConfigScriptFile, + "case os:getenv(\"RELX_TEST\") of\n" + " \"true\" ->\n" + " {release, {RelName, Version}, Apps} = lists:keyfind(release, 1, CONFIG),\n" + " lists:keyreplace(release, 1, CONFIG, {release, {RelName, \"0.0.2\"}, Apps});\n" + " _ -> CONFIG % env var not defined or anything other than true\n" + "end.\n"), + + OutputDir = filename:join([proplists:get_value(data_dir, Config), + rlx_test_utils:create_random_name("relx-output")]), + ok = file:set_cwd(FooRoot), + {ok, FooRoot} = file:get_cwd(), + + % set the env var that will cause relx.config to be altered by the config script + os:putenv("RELX_TEST", "true"), + + {ok, State} = relx:do(undefined, undefined, [], [LibDir1], 3, + OutputDir, undefined), + [{{foo, "0.0.2"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)), + ?assert(ec_file:exists(OutputDir)), + AppSpecs = rlx_release:applications(Release), + ?assert(lists:keymember(stdlib, 1, AppSpecs)), + ?assert(lists:keymember(kernel, 1, AppSpecs)), + ?assert(lists:member({non_goal_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({non_goal_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)), + ?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)), + ?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)). %%%=================================================================== %%% Helper Functions %%%=================================================================== - -create_app(Dir, Name, Vsn, Deps, LibDeps) -> - AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), - write_app_file(AppDir, Name, Vsn, Deps, LibDeps), - write_beam_file(AppDir, Name), - rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, - Deps, []). - -create_empty_app(Dir, Name, Vsn, Deps, LibDeps) -> - AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), - write_app_file(AppDir, Name, Vsn, Deps, LibDeps), - rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, - Deps, []). - -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_appup_file(AppInfo, DownVsn) -> - Dir = rlx_app_info:dir(AppInfo), - Name = rlx_util:to_string(rlx_app_info:name(AppInfo)), - Vsn = rlx_app_info:vsn_as_string(AppInfo), - Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). - -write_app_file(Dir, Name, Version, Deps, LibDeps) -> - Filename = filename:join([Dir, "ebin", Name ++ ".app"]), - ok = filelib:ensure_dir(Filename), - ok = ec_file:write_term(Filename, get_app_metadata(Name, Version, Deps, LibDeps)). - -get_app_metadata(Name, Vsn, Deps, LibDeps) -> - {application, erlang:list_to_atom(Name), - [{description, ""}, - {vsn, Vsn}, - {modules, []}, - {included_applications, LibDeps}, - {registered, []}, - {applications, Deps}]}. - -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))]). - -write_config(Filename, Values) -> - ok = filelib:ensure_dir(Filename), - ok = ec_file:write(Filename, - [io_lib:format("~p.\n", [Val]) || Val <- Values]). - -test_template_contents() -> - "{erts_vsn, \"{{erts_vsn}}\"}.\n" - "{release_erts_version, \"{{release_erts_version}}\"}.\n" - "{release_name, {{release_name}}}.\n" - "{rel_vsn, \"{{release_version}}\"}.\n" - "{release_version, \"{{release_version}}\"}.\n" - "{release_applications, [{{ release_applications|join:\", \" }}]}.\n" - "{std_version, \"{{release.stdlib.version}}\"}.\n" - "{kernel_version, \"{{release.kernel.version}}\"}.\n" - "{non_goal_1_version, \"{{release.non_goal_1.version}}\"}.\n" - "{non_goal_2_version, \"{{release.non_goal_2.version}}\"}.\n" - "{goal_app_1_version, \"{{release.goal_app_1.version}}\"}.\n" - "{goal_app_2_version, \"{{release.goal_app_2.version}}\"}.\n" - "{lib_dep_1, \"{{release.lib_dep_1.version}}\"}.\n" - "{lib_dep_1_dir, \"{{release.lib_dep_1.dir}}\"}.\n" - "{lib_dep_1_active, [{{ release.lib_dep_1.active_dependencies|join:\", \" }}]}.\n" - "{lib_dep_1_library, [{{ release.lib_dep_1.library_dependencies|join:\", \" }}]}.\n" - "{lib_dep_1_link, \"{{release.lib_dep_1.link}}\"}.\n" - "{log, \"{{log}}\"}.\n" - "{output_dir, \"{{output_dir}}\"}.\n" - "{target_dir, \"{{target_dir}}\"}.\n" - "{overridden, [{{ overridden|join:\", \" }}]}.\n" - "{goals, [\"{{ goals|join:\", \" }}\"]}.\n" - "{lib_dirs, [\"{{ lib_dirs|join:\", \" }}\"]}.\n" - "{config_file, \"{{ config_file }}\"}.\n" - "{providers, [{{ providers|join:\", \" }}]}.\n" - "{sys_config, \"{{sys_config}}\"}.\n" - "{root_dir, \"{{root_dir}}\"}.\n" - "{default_release_name, {{default_release_name}}}.\n" - "{default_release_version, \"{{default_release_version}}\"}.\n" - "{default_release, \"{{default_release}}\"}.\n" - "{yahoo, \"{{yahoo}}\"}.\n" - "{yahoo2_foo, \"{{yahoo2.foo}}\"}.\n" - "{foo_dir, \"{{foo_dir}}\"}.\n" - "{yahoo3, \"{{yahoo3.bar}}\"}.\n" - "{google, \"{{google}}\"}.\n". diff --git a/test/rlx_test_utils.erl b/test/rlx_test_utils.erl new file mode 100644 index 0000000..644c49f --- /dev/null +++ b/test/rlx_test_utils.erl @@ -0,0 +1,98 @@ +%%% @author Tristan Sloughter <[email protected]> +%%% @copyright (C) 2015, Tristan Sloughter +-module(rlx_test_utils). + +-compile(export_all). + +create_app(Dir, Name, Vsn, Deps, LibDeps) -> + AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), + write_app_file(AppDir, Name, Vsn, Deps, LibDeps), + write_beam_file(AppDir, Name), + rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + Deps, []). + +create_empty_app(Dir, Name, Vsn, Deps, LibDeps) -> + AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]), + write_app_file(AppDir, Name, Vsn, Deps, LibDeps), + rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir, + Deps, []). + +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_appup_file(AppInfo, DownVsn) -> + Dir = rlx_app_info:dir(AppInfo), + Name = rlx_util:to_string(rlx_app_info:name(AppInfo)), + Vsn = rlx_app_info:vsn_as_string(AppInfo), + Filename = filename:join([Dir, "ebin", Name ++ ".appup"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}). + +write_app_file(Dir, Name, Version, Deps, LibDeps) -> + Filename = filename:join([Dir, "ebin", Name ++ ".app"]), + ok = filelib:ensure_dir(Filename), + ok = ec_file:write_term(Filename, get_app_metadata(Name, Version, Deps, LibDeps)). + +get_app_metadata(Name, Vsn, Deps, LibDeps) -> + {application, erlang:list_to_atom(Name), + [{description, ""}, + {vsn, Vsn}, + {modules, []}, + {included_applications, LibDeps}, + {registered, []}, + {applications, Deps}]}. + +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))]). + +write_config(Filename, Values) -> + ok = filelib:ensure_dir(Filename), + ok = ec_file:write(Filename, + [io_lib:format("~p.\n", [Val]) || Val <- Values]). + +test_template_contents() -> + "{erts_vsn, \"{{erts_vsn}}\"}.\n" + "{release_erts_version, \"{{release_erts_version}}\"}.\n" + "{release_name, {{release_name}}}.\n" + "{rel_vsn, \"{{release_version}}\"}.\n" + "{release_version, \"{{release_version}}\"}.\n" + "{release_applications, [{{ release_applications|join:\", \" }}]}.\n" + "{std_version, \"{{release.stdlib.version}}\"}.\n" + "{kernel_version, \"{{release.kernel.version}}\"}.\n" + "{non_goal_1_version, \"{{release.non_goal_1.version}}\"}.\n" + "{non_goal_2_version, \"{{release.non_goal_2.version}}\"}.\n" + "{goal_app_1_version, \"{{release.goal_app_1.version}}\"}.\n" + "{goal_app_2_version, \"{{release.goal_app_2.version}}\"}.\n" + "{lib_dep_1, \"{{release.lib_dep_1.version}}\"}.\n" + "{lib_dep_1_dir, \"{{release.lib_dep_1.dir}}\"}.\n" + "{lib_dep_1_active, [{{ release.lib_dep_1.active_dependencies|join:\", \" }}]}.\n" + "{lib_dep_1_library, [{{ release.lib_dep_1.library_dependencies|join:\", \" }}]}.\n" + "{lib_dep_1_link, \"{{release.lib_dep_1.link}}\"}.\n" + "{log, \"{{log}}\"}.\n" + "{output_dir, \"{{output_dir}}\"}.\n" + "{target_dir, \"{{target_dir}}\"}.\n" + "{overridden, [{{ overridden|join:\", \" }}]}.\n" + "{goals, [\"{{ goals|join:\", \" }}\"]}.\n" + "{lib_dirs, [\"{{ lib_dirs|join:\", \" }}\"]}.\n" + "{config_file, \"{{ config_file }}\"}.\n" + "{providers, [{{ providers|join:\", \" }}]}.\n" + "{sys_config, \"{{sys_config}}\"}.\n" + "{root_dir, \"{{root_dir}}\"}.\n" + "{default_release_name, {{default_release_name}}}.\n" + "{default_release_version, \"{{default_release_version}}\"}.\n" + "{default_release, \"{{default_release}}\"}.\n" + "{yahoo, \"{{yahoo}}\"}.\n" + "{yahoo2_foo, \"{{yahoo2.foo}}\"}.\n" + "{foo_dir, \"{{foo_dir}}\"}.\n" + "{yahoo3, \"{{yahoo3.bar}}\"}.\n" + "{yahoo4, \"{{yahoo4.foo}}\"}.\n" + "{google, \"{{google}}\"}.\n". |