aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.travis.yml35
-rw-r--r--README.md4
-rw-r--r--bootstrap.cmd2
-rwxr-xr-xpr2relnotes.sh26
-rwxr-xr-xpriv/templates/bin2
-rw-r--r--priv/templates/bin_windows25
-rw-r--r--priv/templates/builtin_hook_pid12
-rw-r--r--priv/templates/builtin_hook_status3
-rw-r--r--priv/templates/builtin_hook_wait_for_process17
-rw-r--r--priv/templates/builtin_hook_wait_for_vm_start7
-rwxr-xr-xpriv/templates/extended_bin474
-rw-r--r--priv/templates/extended_bin_windows40
-rw-r--r--priv/templates/install_upgrade_escript298
-rw-r--r--priv/templates/vm_args11
-rw-r--r--rebar.config25
-rw-r--r--rebar.lock15
-rw-r--r--src/rlx_app_discovery.erl15
-rw-r--r--src/rlx_app_info.erl14
-rw-r--r--src/rlx_cmd_args.erl3
-rw-r--r--src/rlx_config.erl18
-rw-r--r--src/rlx_depsolver.erl2
-rw-r--r--src/rlx_goal.erl2
-rw-r--r--src/rlx_prv_assembler.erl263
-rw-r--r--src/rlx_prv_overlay.erl30
-rw-r--r--src/rlx_prv_relup.erl37
-rw-r--r--src/rlx_release.erl11
-rw-r--r--src/rlx_state.erl28
-rw-r--r--src/rlx_topo.erl190
-rw-r--r--test/rlx_archive_SUITE.erl33
-rw-r--r--test/rlx_eunit_SUITE.erl8
-rw-r--r--test/rlx_extended_bin_SUITE.erl1240
-rw-r--r--test/rlx_release_SUITE.erl205
-rw-r--r--test/rlx_test_utils.erl133
33 files changed, 2891 insertions, 337 deletions
diff --git a/.travis.yml b/.travis.yml
index 40dcc41..2377edd 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,17 +1,40 @@
language: erlang
-otp_release:
- - 19.0
- - 18.3
- - 17.0
- - R16B03-1
- - R15B03
+matrix:
+ include:
+ - os: linux
+ sudo: required
+ otp_release: R15B03
+ - os: linux
+ sudo: required
+ otp_release: R16B03-1
+ - os: linux
+ sudo: required
+ otp_release: 17.5
+ - os: linux
+ sudo: required
+ otp_release: 18.3
+ - os: linux
+ sudo: required
+ otp_release: 19.3
+ - os: linux
+ sudo: required
+ otp_release: 20.0
+ - os: osx
+ sudo: required
+ language: generic
before_script:
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update; fi
+ ## should eventually use a tap that has previous erlang versions here
+ ## as this only uses the latest erlang available via brew
+ - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install erlang; fi
- wget https://s3.amazonaws.com/rebar3/rebar3
- chmod +x rebar3
script: "./rebar3 update && ./rebar3 ct"
branches:
only:
- master
+addons:
+ hostname: travis.dev
notifications:
email:
diff --git a/README.md b/README.md
index b9e1073..728a210 100644
--- a/README.md
+++ b/README.md
@@ -28,9 +28,9 @@ Building
To build relx and generate a standalone escript executable:
$ ./rebar3 update
- $ ./rebar3 escriptize
+ $ ./rebar3 as escript escriptize
-This creates the executable `_build/default/bin/relx`.
+This creates the executable `_build/escript/bin/relx`.
Building on Windows
-------------------
diff --git a/bootstrap.cmd b/bootstrap.cmd
index 4deb2a3..1bbd00d 100644
--- a/bootstrap.cmd
+++ b/bootstrap.cmd
@@ -3,7 +3,7 @@
:: Get dependencies, compile and escriptize relx
@cmd /c @rebar3 update
-@cmd /c @rebar3 escriptize
+@cmd /c @rebar3 as escript escriptize
:: Create a shortcut file for running the relx command
@set relx_cmd=relx.cmd
diff --git a/pr2relnotes.sh b/pr2relnotes.sh
new file mode 100755
index 0000000..2e78478
--- /dev/null
+++ b/pr2relnotes.sh
@@ -0,0 +1,26 @@
+#!/usr/bin/env sh
+
+if [ -z $1 ]
+then
+ echo "pr2relnotes.sh: prints list of pull requests merged since <tag>"
+ echo " usage: $0 <tag> [pull-request-url (default: https://github.com/erlware/relx/pull/)]"
+ exit 0
+fi
+export url=${2:-"https://github.com/erlware/relx/pull/"}
+
+git log --merges --pretty=medium $1..HEAD | \
+awk -v url=$url '
+ # first line of a merge commit entry
+ /^commit / {mode="new"}
+
+ # merge commit default message
+ / +Merge pull request/ {
+ page_id=substr($4, 2, length($4)-1);
+ mode="started";
+ next;
+ }
+
+ # line of content including title
+ mode=="started" && / [^ ]+/ {
+ print "- [" substr($0, 5) "](" url page_id ")"; mode="done"
+ }'
diff --git a/priv/templates/bin b/priv/templates/bin
index 8bb6890..523b88d 100755
--- a/priv/templates/bin
+++ b/priv/templates/bin
@@ -22,7 +22,7 @@ find_erts_dir() {
else
__erl="$(which erl)"
code="io:format(\"~s\", [code:root_dir()]), halt()."
- __erl_root="$("$__erl" -noshell -eval "$code")"
+ __erl_root="$("$__erl" -boot no_dot_erlang -noshell -eval "$code")"
ERTS_DIR="$__erl_root/erts-$ERTS_VSN"
ROOTDIR="$__erl_root"
fi
diff --git a/priv/templates/bin_windows b/priv/templates/bin_windows
index 06303f5..b3ce796 100644
--- a/priv/templates/bin_windows
+++ b/priv/templates/bin_windows
@@ -11,14 +11,14 @@
@for %%A in ("%script_dir%\..") do (
set "release_root_dir=%%~fA"
)
-@set rel_dir=%release_root_dir%\releases\%rel_vsn%
+@set "rel_dir=%release_root_dir%\releases\%rel_vsn%"
@call :find_erts_dir
@call :find_sys_config
@call :set_boot_script_var
-@set rootdir=%release_root_dir%
-@set bindir=%erts_dir%\bin
+@set "rootdir=%release_root_dir%"
+@set "bindir=%erts_dir%\bin"
@set progname=erl
@set erl=%bindir%\erl
@@ -41,7 +41,7 @@ cd %rootdir%
:: Find the ERTS dir
:find_erts_dir
-@set erts_dir=%release_root_dir%\erts-%erts_vsn%
+@set "erts_dir=%release_root_dir%\erts-%erts_vsn%"
@if exist %erts_dir% (
goto :set_erts_dir_from_default
) else (
@@ -60,34 +60,27 @@ cd %rootdir%
@for /f "delims=" %%i in ('where erl') do (
set erl=%%i
)
-@set dir_cmd="%erl%" -noshell -eval "io:format(\"~s\", [filename:nativename(code:root_dir())])." -s init stop
+@set dir_cmd="%erl%" -boot no_dot_erlang -noshell -eval "io:format(\"~s\", [filename:nativename(code:root_dir())])." -s init stop
@for /f "delims=" %%i in ('%%dir_cmd%%') do (
set erl_root=%%i
)
-@set erts_dir=%erl_root%\erts-%erts_vsn%
+@set "erts_dir=%erl_root%\erts-%erts_vsn%"
@set rootdir=%erl_root%
@goto :eof
:: Find the sys.config file
:find_sys_config
-@set possible_sys=%rel_dir%\sys.config
+@set "possible_sys=%rel_dir%\sys.config"
@if exist "%possible_sys%" (
set sys_config=-config "%possible_sys%"
)
-@if exist "%possible_sys%".orig (
- ren "%possible_sys%".orig "%possible_sys%"
- set sys_config=-config "%possible_sys%"
-)
-@if exist "%rel_dir%\vm.args".orig (
- ren "%rel_dir%\vm.args" ".orig %rel_dir%\vm.args"
-)
@goto :eof
:: set boot_script variable
:set_boot_script_var
@if exist "%rel_dir%\%rel_name%.boot" (
- set boot_script=%rel_dir%\%rel_name%
+ set "boot_script=%rel_dir%\%rel_name%"
) else (
- set boot_script=%rel_dir%\start
+ set "boot_script=%rel_dir%\start"
)
@goto :eof
diff --git a/priv/templates/builtin_hook_pid b/priv/templates/builtin_hook_pid
new file mode 100644
index 0000000..0151631
--- /dev/null
+++ b/priv/templates/builtin_hook_pid
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# loop until the VM starts responding to pings
+while ! $(relx_nodetool "ping">/dev/null)
+do
+ sleep 1
+done
+
+# get the beam pid and write it to the file passed as
+# argument
+PID="$(relx_get_pid)"
+echo $PID > $1
diff --git a/priv/templates/builtin_hook_status b/priv/templates/builtin_hook_status
new file mode 100644
index 0000000..e5dd792
--- /dev/null
+++ b/priv/templates/builtin_hook_status
@@ -0,0 +1,3 @@
+#!/bin/bash
+
+echo $(relx_nodetool eval "application:which_applications().")
diff --git a/priv/templates/builtin_hook_wait_for_process b/priv/templates/builtin_hook_wait_for_process
new file mode 100644
index 0000000..af5994d
--- /dev/null
+++ b/priv/templates/builtin_hook_wait_for_process
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# loop until the VM starts responding to pings
+while ! $(relx_nodetool "ping">/dev/null)
+do
+ sleep 1
+done
+
+# loop until the name provided as argument gets
+# registered
+while true
+do
+ if [ "$(relx_nodetool eval "whereis($1).")" != "undefined" ]
+ then
+ break
+ fi
+done
diff --git a/priv/templates/builtin_hook_wait_for_vm_start b/priv/templates/builtin_hook_wait_for_vm_start
new file mode 100644
index 0000000..6b9ee12
--- /dev/null
+++ b/priv/templates/builtin_hook_wait_for_vm_start
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+# loop until the VM starts responding to pings
+while ! $(relx_nodetool "ping">/dev/null)
+do
+ sleep 1
+done
diff --git a/priv/templates/extended_bin b/priv/templates/extended_bin
index 7a9f0c7..0abf38b 100755
--- a/priv/templates/extended_bin
+++ b/priv/templates/extended_bin
@@ -2,13 +2,36 @@
set -e
-SCRIPT=$(readlink $0 || true)
-if [ -z $SCRIPT ]; then
- SCRIPT=$0
-fi;
+# http://erlang.org/doc/man/run_erl.html
+# If defined, disables input and output flow control for the pty
+# opend by run_erl. Useful if you want to remove any risk of accidentally
+# blocking the flow control by using Ctrl-S (instead of Ctrl-D to detach),
+# which can result in blocking of the entire Beam process, and in the case
+# of running heart as supervisor even the heart process becomes blocked
+# when writing log message to terminal, leaving the heart process unable
+# to do its work.
+RUN_ERL_DISABLE_FLOWCNTRL=${RUN_ERL_DISABLE_FLOWCNTRL:-true}
+export $RUN_ERL_DISABLE_FLOWCNTRL
+
+if [ "$TERM" = "dumb" -o -z "$TERM" ]; then
+ export TERM=screen
+fi
+
+# OSX does not support readlink '-f' flag, work
+# around that
+case $OSTYPE in
+ darwin*)
+ SCRIPT=$(readlink $0 || true)
+ ;;
+ *)
+ SCRIPT=$(readlink -f $0 || true)
+ ;;
+esac
+[ -z $SCRIPT ] && SCRIPT=$0
SCRIPT_DIR="$(cd `dirname "$SCRIPT"` && pwd -P)"
RELEASE_ROOT_DIR="$(cd "$SCRIPT_DIR/.." && pwd -P)"
-REL_NAME="{{ rel_name }}"
+# Make the value available to variable substitution calls below
+export REL_NAME="{{ rel_name }}"
REL_VSN="{{ rel_vsn }}"
ERTS_VSN="{{ erts_vsn }}"
CODE_LOADING_MODE="${CODE_LOADING_MODE:-embedded}"
@@ -16,6 +39,79 @@ REL_DIR="$RELEASE_ROOT_DIR/releases/$REL_VSN"
ERL_OPTS="{{ erl_opts }}"
RUNNER_LOG_DIR="${RUNNER_LOG_DIR:-$RELEASE_ROOT_DIR/log}"
+# start/stop/install/upgrade pre/post hooks
+PRE_START_HOOKS="{{{ pre_start_hooks }}}"
+POST_START_HOOKS="{{{ post_start_hooks }}}"
+PRE_STOP_HOOKS="{{{ pre_stop_hooks }}}"
+POST_STOP_HOOKS="{{{ post_stop_hooks }}}"
+PRE_INSTALL_UPGRADE_HOOKS="{{{ pre_install_upgrade_hooks }}}"
+POST_INSTALL_UPGRADE_HOOKS="{{{ post_install_upgrade_hooks }}}"
+STATUS_HOOK="{{{ status_hook }}}"
+
+relx_usage() {
+ command="$1"
+
+ case "$command" in
+ unpack)
+ echo "Usage: $REL_NAME unpack [VERSION]"
+ echo "Unpacks a release package VERSION, it assumes that this"
+ echo "release package tarball has already been deployed at one"
+ echo "of the following locations:"
+ echo " releases/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>.tar.gz"
+ ;;
+ install)
+ echo "Usage: $REL_NAME install [VERSION]"
+ echo "Installs a release package VERSION, it assumes that this"
+ echo "release package tarball has already been deployed at one"
+ echo "of the following locations:"
+ echo " releases/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>.tar.gz"
+ echo ""
+ echo " --no-permanent Install release package VERSION but"
+ echo " don't make it permanent"
+ ;;
+ uninstall)
+ echo "Usage: $REL_NAME uninstall [VERSION]"
+ echo "Uninstalls a release VERSION, it will only accept"
+ echo "versions that are not currently in use"
+ ;;
+ upgrade)
+ echo "Usage: $REL_NAME upgrade [VERSION]"
+ echo "Upgrades the currently running release to VERSION, it assumes"
+ echo "that a release package tarball has already been deployed at one"
+ echo "of the following locations:"
+ echo " releases/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>.tar.gz"
+ echo ""
+ echo " --no-permanent Install release package VERSION but"
+ echo " don't make it permanent"
+ ;;
+ downgrade)
+ echo "Usage: $REL_NAME downgrade [VERSION]"
+ echo "Downgrades the currently running release to VERSION, it assumes"
+ echo "that a release package tarball has already been deployed at one"
+ echo "of the following locations:"
+ echo " releases/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>-<version>.tar.gz"
+ echo " releases/<version>/<relname>.tar.gz"
+ echo ""
+ echo " --no-permanent Install release package VERSION but"
+ echo " don't make it permanent"
+ ;;
+ status)
+ echo "Usage: $REL_NAME status"
+ echo "Obtains node status information."
+ ;;
+ *)
+ echo "Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|downgrade|install|uninstall|versions|escript|rpc|rpcterms|eval|status}"
+ ;;
+ esac
+}
+
find_erts_dir() {
__erts_dir="$RELEASE_ROOT_DIR/erts-$ERTS_VSN"
if [ -d "$__erts_dir" ]; then
@@ -24,7 +120,7 @@ find_erts_dir() {
else
__erl="$(which erl)"
code="io:format(\"~s\", [code:root_dir()]), halt()."
- __erl_root="$("$__erl" -noshell -eval "$code")"
+ __erl_root="$("$__erl" -boot no_dot_erlang -sasl errlog_type error -noshell -eval "$code")"
ERTS_DIR="$__erl_root/erts-$ERTS_VSN"
ROOTDIR="$__erl_root"
fi
@@ -44,7 +140,9 @@ relx_get_pid() {
relx_get_nodename() {
id="longname$(relx_gen_id)-${NAME}"
- "$BINDIR/erl" -boot start_clean -eval '[Host] = tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n", [Host]), halt()' -noshell ${NAME_TYPE} $id
+ "$BINDIR/erl" -boot start_clean \
+ -eval '[H]=tl(string:tokens(atom_to_list(node()),"@")), io:format("~s~n",[H]), halt()' \
+ -noshell ${NAME_TYPE} $id
}
# Connect to a remote node
@@ -89,76 +187,206 @@ relx_start_command() {
"$START_OPTION"
}
-# Use $CWD/vm.args if exists, otherwise releases/VSN/vm.args
-if [ -z "$VMARGS_PATH" ]; then
- if [ -f "$RELEASE_ROOT_DIR/vm.args" ]; then
- VMARGS_PATH="$RELEASE_ROOT_DIR/vm.args"
+relx_get_code_paths() {
+ code="{ok, [{release,_,_,Apps}]} = file:consult(\"$REL_DIR/$REL_NAME.rel\"),"\
+"lists:foreach(fun(A) ->"\
+" io:fwrite(\"$ROOTDIR/lib/~p-~s/ebin \", [element(1, A), element(2, A)]) "\
+"end, Apps),"\
+"halt()."
+
+ "$BINDIR/erl" -noshell -boot start_clean -eval "$code"
+}
+
+make_out_file_path() {
+ # Use output directory provided in the RELX_OUT_FILE_PATH environment variable
+ # (default to the current location of vm.args and sys.config)
+ DIR=$(dirname $1)
+ [ -d "${RELX_OUT_FILE_PATH}" ] && DIR="${RELX_OUT_FILE_PATH}"
+ FILE=$(basename $1)
+ IN="${DIR}/${FILE}"
+
+ PFX=$(echo $IN | awk '{sub(/\.[^.]+$/, "", $0)}1')
+ SFX=$(echo $FILE | awk -F . '{if (NF>1) print $NF}')
+ if [ $RELX_MULTI_NODE ]; then
+ echo "${PFX}.${NAME}.${SFX}"
else
- VMARGS_PATH="$REL_DIR/vm.args"
+ echo "${PFX}.${SFX}"
fi
-fi
+}
-orig_vmargs_path="$VMARGS_PATH.orig"
-if [ $RELX_REPLACE_OS_VARS ]; then
- #Make sure we don't break dev mode by keeping the symbolic link to
- #the user's vm.args
- if [ ! -L "$orig_vmargs_path" ]; then
- #we're in copy mode, rename the vm.args file to vm.args.orig
- mv "$VMARGS_PATH" "$orig_vmargs_path"
- fi
+# Replace environment variables
+replace_os_vars() {
+ awk '{
+ while(match($0,"[$]{[^}]*}")) {
+ var=substr($0,RSTART+2,RLENGTH -3)
+ gsub("[$]{"var"}",ENVIRON[var])
+ }
+ }1' < "$1" > "$2"
+}
- awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < "$orig_vmargs_path" > "$VMARGS_PATH"
- else
- #We don't need to replace env. vars, just rename the
- #symlink vm.args.orig to vm.args, and keep it as a
- #symlink.
- if [ -L "$orig_vmargs_path" ]; then
- mv "$orig_vmargs_path" "$VMARGS_PATH"
+add_path() {
+ # Use $CWD/$1 if exists, otherwise releases/VSN/$1
+ IN_FILE_PATH=$2
+ if [ -z "$IN_FILE_PATH" ]; then
+ if [ -f "$RELEASE_ROOT_DIR/$1" ]; then
+ IN_FILE_PATH="$RELEASE_ROOT_DIR/$1"
+ else
+ IN_FILE_PATH="$REL_DIR/$1"
+ fi
fi
-fi
+ echo $IN_FILE_PATH
+}
-# Make sure log directory exists
-mkdir -p "$RUNNER_LOG_DIR"
+check_replace_os_vars() {
+ IN_FILE_PATH=$(add_path $1 $2)
+ OUT_FILE_PATH="$IN_FILE_PATH"
+ ORIG_FILE_PATH="$IN_FILE_PATH.orig"
+ if [ $RELX_REPLACE_OS_VARS ]; then
+ # Create a new file in the same location as original
+ OUT_FILE_PATH=$(make_out_file_path $IN_FILE_PATH)
+ # If vm.args.orig or sys.config.orig is present then use that
+ if [ -f "$ORIG_FILE_PATH" ]; then
+ IN_FILE_PATH="$ORIG_FILE_PATH"
+ fi
-# Use $CWD/sys.config if exists, otherwise releases/VSN/sys.config
-if [ -z "$RELX_CONFIG_PATH" ]; then
- if [ -f "$RELEASE_ROOT_DIR/sys.config" ]; then
- RELX_CONFIG_PATH="$RELEASE_ROOT_DIR/sys.config"
+ # apply the environment variable substitution to $IN_FILE_PATH
+ # the result is saved to $OUT_FILE_PATH
+ # if they are both the same, then ensure that we don't clobber
+ # the file by saving a backup with the .orig extension
+ if [ "$IN_FILE_PATH" = "$OUT_FILE_PATH" ]; then
+ cp "$IN_FILE_PATH" "$ORIG_FILE_PATH"
+ replace_os_vars "$ORIG_FILE_PATH" "$OUT_FILE_PATH"
+ else
+ replace_os_vars "$IN_FILE_PATH" "$OUT_FILE_PATH"
+ fi
else
- RELX_CONFIG_PATH="$REL_DIR/sys.config"
+ # If vm.arg.orig or sys.config.orig is present then use that
+ if [ -f "$ORIG_FILE_PATH" ]; then
+ cp "$ORIG_FILE_PATH" "$OUT_FILE_PATH"
+ fi
fi
-fi
+ echo $OUT_FILE_PATH
+}
-orig_relx_config_path="$RELX_CONFIG_PATH.orig"
-if [ $RELX_REPLACE_OS_VARS ]; then
- #Make sure we don't break dev mode by keeping the symbolic link to
- #the user's sys.config
- if [ ! -L "$orig_relx_config_path" ]; then
- #We're in copy mode, rename sys.config to sys.config.orig
- mv "$RELX_CONFIG_PATH" "$orig_relx_config_path"
- fi
+relx_run_hooks() {
+ HOOKS=$1
+ for hook in $HOOKS
+ do
+ # the scripts arguments at this point are separated
+ # from each other by | , we now replace these
+ # by empty spaces and give them to the `set`
+ # command in order to be able to extract them
+ # separately
+ set `echo "$hook" | sed -e 's/|/ /g'`
+ HOOK_SCRIPT=$1; shift
+ # all hook locations are expected to be
+ # relative to the start script location
+ [ "$SCRIPT_DIR/$HOOK_SCRIPT" ] && . "$SCRIPT_DIR/$HOOK_SCRIPT" $@
+ done
+}
- awk '{while(match($0,"[$]{[^}]*}")) {var=substr($0,RSTART+2,RLENGTH -3);gsub("[$]{"var"}",ENVIRON[var])}}1' < "$orig_relx_config_path" > "$RELX_CONFIG_PATH"
- else
- #We don't need to replace env. vars, just rename the
- #symlink sys.config.orig to sys.config. Keep it as
- #a symlink.
- if [ -L "$orig_relx_config_path" ]; then
- mv "$orig_relx_config_path" "$RELX_CONFIG_PATH"
- fi
-fi
+find_erts_dir
+export ROOTDIR="$RELEASE_ROOT_DIR"
+export BINDIR="$ERTS_DIR/bin"
+export EMU="beam"
+export PROGNAME="erl"
+export LD_LIBRARY_PATH="$ERTS_DIR/lib:$LD_LIBRARY_PATH"
+ERTS_LIB_DIR="$(dirname "$ERTS_DIR")/lib"
+
+VMARGS_PATH=$(add_path vm.args $VMARGS_PATH)
+
+# Check vm.args and other files referenced via -args_file parameters for:
+# - nonexisting -args_files
+# - circular dependencies of -args_files
+# - relative paths in -args_file parameters
+# - multiple/mixed occurences of -name and -sname parameters
+# - missing -name or -sname parameters
+# If all checks pass, extract the target node name
+set +e
+TMP_NAME_ARG=$(awk 'function check_name(file)
+{
+ if (system("test -f "file)) {
+ print file" not found"
+ exit 3
+ }
+ if (system("test -r "file)) {
+ print file" not readable"
+ exit 3
+ }
+ while ((getline line<file)>0) {
+ if (line~/^-args_file +/) {
+ gsub(/^-args_file +| *$/, "", line)
+ if (!(line~/^\//)) {
+ print "relative path "line" encountered in "file
+ exit 4
+ }
+ if (line in files) {
+ print "circular reference to "line" encountered in "file
+ exit 5
+ }
+ files[line]=line
+ check_name(line)
+ }
+ else if (line~/^-s?name +/) {
+ if (name!="") {
+ print "\""line"\" parameter found in "file" but already specified as \""name"\""
+ exit 2
+ }
+ name=line
+ }
+ }
+}
-# Extract the target node name from node.args
-NAME_ARG=$(egrep '^-s?name' "$VMARGS_PATH" || true)
-if [ -z "$NAME_ARG" ]; then
- echo "vm.args needs to have either -name or -sname parameter."
- exit 1
-fi
+BEGIN {
+ split("", files)
+ name=""
+}
+
+{
+ files[FILENAME]=FILENAME
+ check_name(FILENAME)
+ if (name=="") {
+ print "need to have exactly one of either -name or -sname parameters but none found"
+ exit 1
+ }
+ print name
+ exit 0
+}' "$VMARGS_PATH")
+TMP_NAME_ARG_RC=$?
+case $TMP_NAME_ARG_RC in
+ 0) NAME_ARG="$TMP_NAME_ARG";;
+ *) echo "$TMP_NAME_ARG"
+ exit $TMP_NAME_ARG_RC;;
+esac
+unset TMP_NAME_ARG
+unset TMP_NAME_ARG_RC
+set -e
+
+# Perform replacement of variables in ${NAME_ARG}
+NAME_ARG=$(eval echo "${NAME_ARG}")
# Extract the name type and name from the NAME_ARG for REMSH
NAME_TYPE="$(echo "$NAME_ARG" | awk '{print $1}')"
NAME="$(echo "$NAME_ARG" | awk '{print $2}')"
+# User can specify an sname without @hostname
+# This will fail when creating remote shell
+# So here we check for @ and add @hostname if missing
+case "${NAME}" in
+ *@*) ;; # Nothing to do
+ *) NAME=${NAME}@$(relx_get_nodename);; # Add @hostname
+esac
+
+# Export the variable so that it's available in the 'eval' calls
+export NAME
+
+VMARGS_PATH=$(check_replace_os_vars vm.args $VMARGS_PATH)
+RELX_CONFIG_PATH=$(check_replace_os_vars sys.config $RELX_CONFIG_PATH)
+
+# Make sure log directory exists
+mkdir -p "$RUNNER_LOG_DIR"
+
+test -z "$PIPE_DIR" && PIPE_BASE_DIR='/tmp/erl_pipes/'
PIPE_DIR="${PIPE_DIR:-/tmp/erl_pipes/$NAME/}"
# Extract the target cookie
@@ -176,28 +404,8 @@ else
COOKIE="$(echo "$COOKIE_ARG" | awk '{print $2}')"
fi
-find_erts_dir
-export ROOTDIR="$RELEASE_ROOT_DIR"
-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"
-# User can specify an sname without @hostname
-# This will fail when creating remote shell
-# So here we check for @ and add @hostname if missing
-case $NAME in
- *@*)
- # Nothing to do
- ;;
- *)
- NAME=$NAME@$(relx_get_nodename)
- ;;
-esac
-
# Check the first argument for instructions
case "$1" in
start|start_boot)
@@ -232,13 +440,17 @@ case "$1" in
HEART_COMMAND="$RELEASE_ROOT_DIR/bin/$REL_NAME $CMD"
export HEART_COMMAND
+ test -z "$PIPE_BASE_DIR" || mkdir -m 1777 -p "$PIPE_BASE_DIR"
mkdir -p "$PIPE_DIR"
+ relx_run_hooks "$PRE_START_HOOKS"
"$BINDIR/run_erl" -daemon "$PIPE_DIR" "$RUNNER_LOG_DIR" \
"$(relx_start_command)"
+ relx_run_hooks "$POST_START_HOOKS"
;;
stop)
+ relx_run_hooks "$PRE_STOP_HOOKS"
# Wait for the node to completely stop...
PID="$(relx_get_pid)"
if ! relx_nodetool "stop"; then
@@ -248,6 +460,7 @@ case "$1" in
do
sleep 1
done
+ relx_run_hooks "$POST_STOP_HOOKS"
;;
restart)
@@ -307,43 +520,45 @@ case "$1" in
relx_rem_sh
;;
- upgrade|downgrade|install)
+ upgrade|downgrade|install|unpack|uninstall)
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"
+ echo "Missing version argument"
+ echo "Usage: $REL_NAME $1 {version}"
exit 1
fi
+ COMMAND="$1"; shift
+
# Make sure a node IS running
if ! relx_nodetool "ping" > /dev/null; then
echo "Node is not running!"
exit 1
fi
+ relx_run_hooks "$PRE_INSTALL_UPGRADE_HOOKS"
+
exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
- "install" "$REL_NAME" "$NAME_TYPE" "$NAME" "$COOKIE" "$2"
- ;;
+ "$COMMAND" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
- 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
+ relx_run_hooks "$POST_INSTALL_UPGRADE_HOOKS"
+ ;;
+ versions)
# Make sure a node IS running
if ! relx_nodetool "ping" > /dev/null; then
echo "Node is not running!"
exit 1
fi
+ COMMAND="$1"; shift
+
exec "$BINDIR/escript" "$ROOTDIR/bin/install_upgrade.escript" \
- "unpack" "$REL_NAME" "$NAME_TYPE" "$NAME" "$COOKIE" "$2"
+ "versions" "{'$REL_NAME', \"$NAME_TYPE\", '$NAME', '$COOKIE'}" "$@"
;;
- console|console_clean|console_boot)
+ console|console_clean|console_boot|foreground)
+ __code_paths=""
+ FOREGROUNDOPTIONS=""
# .boot file typically just $REL_NAME (ie, the app name)
# however, for debugging, sometimes start_clean.boot is useful.
# For e.g. 'setup', one may even want to name another boot script.
@@ -355,7 +570,18 @@ case "$1" in
BOOTFILE="$REL_DIR/start"
fi
;;
+ foreground)
+ # start up the release in the foreground for use by runit
+ # or other supervision services
+ if [ -f "$REL_DIR/$REL_NAME.boot" ]; then
+ BOOTFILE="$REL_DIR/$REL_NAME"
+ else
+ BOOTFILE="$REL_DIR/start"
+ fi
+ FOREGROUNDOPTIONS="-noshell -noinput +Bd"
+ ;;
console_clean)
+ __code_paths=$(relx_get_code_paths)
BOOTFILE="$ROOTDIR/bin/start_clean"
;;
console_boot)
@@ -376,10 +602,12 @@ 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" -mode "$CODE_LOADING_MODE" \
+ set -- "$BINDIR/erlexec" $FOREGROUNDOPTIONS \
+ -boot "$BOOTFILE" -mode "$CODE_LOADING_MODE" \
-boot_var ERTS_LIB_DIR "$ERTS_LIB_DIR" \
-config "$RELX_CONFIG_PATH" \
- -args_file "$VMARGS_PATH"
+ -args_file "$VMARGS_PATH" \
+ -pa ${__code_paths}
# Dump environment info for logging purposes
echo "Exec: $@" -- ${1+$ARGS}
@@ -392,38 +620,6 @@ case "$1" in
# Start the VM
exec "$@" -- ${1+$ARGS}
;;
-
- foreground)
- # start up the release in the foreground for use by runit
- # or other supervision services
-
- [ -f "$REL_DIR/$REL_NAME.boot" ] && BOOTFILE="$REL_NAME" || BOOTFILE=start
- FOREGROUNDOPTIONS="-noshell -noinput +Bd"
-
- # Setup beam-required vars
- EMU=beam
- PROGNAME="${0#*/}"
-
- export EMU
- export PROGNAME
-
- # Store passed arguments since they will be erased by `set`
- ARGS="$@"
-
- # 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 "$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
- echo "Exec: $@" -- ${1+$ARGS}
- echo "Root: $ROOTDIR"
-
- # Start the VM
- exec "$@" -- ${1+$ARGS}
- ;;
rpc)
# Make sure a node IS running
if ! relx_nodetool "ping" > /dev/null; then
@@ -456,8 +652,26 @@ case "$1" in
shift
relx_nodetool "eval" $@
;;
+ status)
+ # Make sure a node IS running
+ if ! relx_nodetool "ping" > /dev/null; then
+ echo "Node is not running!"
+ exit 1
+ fi
+
+ [ ! -z "${STATUS_HOOK}" ] && [ "$SCRIPT_DIR/$STATUS_HOOK" ] && . "$SCRIPT_DIR/$STATUS_HOOK" $@
+ ;;
+ help)
+ if [ -z "$2" ]; then
+ relx_usage
+ exit 1
+ fi
+
+ TOPIC="$2"; shift
+ relx_usage $TOPIC
+ ;;
*)
- echo "Usage: $REL_NAME {start|start_boot <file>|foreground|stop|restart|reboot|pid|ping|console|console_clean|console_boot <file>|attach|remote_console|upgrade|downgrade|install|escript|rpc|rpcterms|eval}"
+ relx_usage
exit 1
;;
esac
diff --git a/priv/templates/extended_bin_windows b/priv/templates/extended_bin_windows
index d892ea6..d0c41d8 100644
--- a/priv/templates/extended_bin_windows
+++ b/priv/templates/extended_bin_windows
@@ -25,7 +25,7 @@
@for %%A in ("%script_dir%\..") do @(
set release_root_dir=%%~fA
)
-@set rel_dir=%release_root_dir%\releases\%rel_vsn%
+@set "rel_dir=%release_root_dir%\releases\%rel_vsn%"
@call :find_erts_dir
@call :find_sys_config
@@ -56,6 +56,19 @@
:: Write the erl.ini file to set up paths relative to this script
@call :write_ini
+:: Collect any additional VM args into erl_opts
+@setlocal EnableDelayedExpansion
+@for /f "usebackq tokens=1-2" %%I in (`findstr /r "^[^#]" "%vm_args%"`) do @(
+ if not "%%I" == "-name" (
+ if not "%%I" == "-sname" (
+ if not "%%I" == "-setcookie" (
+ set erl_opts=!erl_opts! %%I %%J
+ )
+ )
+ )
+)
+@endlocal && set erl_opts=%erl_opts%
+
:: If a start.boot file is not present, copy one from the named .boot file
@if not exist "%rel_dir%\start.boot" (
copy "%rel_dir%\%rel_name%.boot" "%rel_dir%\start.boot" >nul
@@ -79,7 +92,7 @@
:: Find the ERTS dir
:find_erts_dir
-@set possible_erts_dir=%release_root_dir%\erts-%erts_vsn%
+@set "possible_erts_dir=%release_root_dir%\erts-%erts_vsn%"
@if exist "%possible_erts_dir%" (
call :set_erts_dir_from_default
) else (
@@ -89,8 +102,8 @@
:: Set the ERTS dir from the passed in erts_vsn
:set_erts_dir_from_default
-@set erts_dir=%possible_erts_dir%
-@set rootdir=%release_root_dir%
+@set "erts_dir=%possible_erts_dir%"
+@set "rootdir=%release_root_dir%"
@goto :eof
:: Set the ERTS dir from erl
@@ -98,17 +111,17 @@
@for /f "delims=" %%i in ('where erl') do @(
set erl=%%i
)
-@set dir_cmd="%erl%" -noshell -eval "io:format(\"~s\", [filename:nativename(code:root_dir())])." -s init stop
+@set dir_cmd="%erl%" -boot no_dot_erlang -noshell -eval "io:format(\"~s\", [filename:nativename(code:root_dir())])." -s init stop
@for /f "delims=" %%i in ('%%dir_cmd%%') do @(
set erl_root=%%i
)
-@set erts_dir=%erl_root%\erts-%erts_vsn%
-@set rootdir=%erl_root%
+@set "erts_dir=%erl_root%\erts-%erts_vsn%"
+@set "rootdir=%erl_root%"
@goto :eof
:: Find the sys.config file
:find_sys_config
-@set possible_sys=%rel_dir%\sys.config
+@set "possible_sys=%rel_dir%\sys.config"
@if exist %possible_sys% (
set sys_config=-config "%possible_sys%"
)
@@ -117,9 +130,9 @@
:: set boot_script variable
:set_boot_script_var
@if exist "%rel_dir%\%rel_name%.boot" (
- set boot_script=%rel_dir%\%rel_name%
+ set "boot_script=%rel_dir%\%rel_name%"
) else (
- set boot_script=%rel_dir%\start
+ set "boot_script=%rel_dir%\start"
)
@goto :eof
@@ -175,13 +188,12 @@ set description=Erlang node %node_name% in %rootdir%
:: Relup and reldown
:relup
@if "" == "%2" (
- echo Missing package argument
- echo Usage: %rel_name% %1 {package base name}
- echo NOTE {package base name} MUST NOT include the .tar.gz suffix
+ echo Missing version argument
+ echo Usage: %rel_name% %1 {version}
set ERRORLEVEL=1
exit /b %ERRORLEVEL%
)
-@%escript% "%rootdir%/bin/install_upgrade.escript" "%rel_name%" "%node_name%" "%cookie%" "%2"
+@%escript% "%rootdir%/bin/install_upgrade.escript" "install" "%rel_name%" "%node_name%" "%cookie%" "%2"
@goto :eof
:: Start a console
diff --git a/priv/templates/install_upgrade_escript b/priv/templates/install_upgrade_escript
index 0910c38..47521c6 100644
--- a/priv/templates/install_upgrade_escript
+++ b/priv/templates/install_upgrade_escript
@@ -6,85 +6,236 @@
-define(TIMEOUT, 300000).
-define(INFO(Fmt,Args), io:format(Fmt,Args)).
-%% Unpack or upgrade to a new tar.gz release
-main(["unpack", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) ->
+main([Command0, DistInfoStr | CommandArgs]) ->
+ %% convert the distribution info arguments string to an erlang term
+ {ok, Tokens, _} = erl_scan:string(DistInfoStr ++ "."),
+ {ok, DistInfo} = erl_parse:parse_term(Tokens),
+ %% convert arguments into a proplist
+ Opts = parse_arguments(CommandArgs),
+ %% invoke the command passed as argument
+ F = case Command0 of
+ "install" -> fun(A, B) -> install(A, B) end;
+ "unpack" -> fun(A, B) -> unpack(A, B) end;
+ "upgrade" -> fun(A, B) -> upgrade(A, B) end;
+ "downgrade" -> fun(A, B) -> downgrade(A, B) end;
+ "uninstall" -> fun(A, B) -> uninstall(A, B) end;
+ "versions" -> fun(A, B) -> versions(A, B) end
+ end,
+ F(DistInfo, Opts);
+main(Args) ->
+ ?INFO("unknown args: ~p\n", [Args]),
+ erlang:halt(1).
+
+unpack({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
TargetNode = start_distribution(NodeName, NameTypeArg, 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;
+ Version = proplists:get_value(version, Opts),
+ case unpack_release(RelName, TargetNode, Version) of
+ {ok, Vsn} ->
+ ?INFO("Unpacked successfully: ~p~n", [Vsn]);
old ->
%% no need to unpack, has been installed previously
- ?INFO("Release ~s is marked old, switching to it.~n",[Version]);
+ ?INFO("Release ~s is marked old.~n",[Version]);
unpacked ->
- ?INFO("Release ~s is already unpacked, now installing.~n",[Version]);
+ ?INFO("Release ~s is already unpacked.~n",[Version]);
current ->
- ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]);
+ ?INFO("Release ~s is already installed and current.~n",[Version]);
permanent ->
- ?INFO("Release ~s is already installed, and set permanent.~n",[Version])
+ ?INFO("Release ~s is already installed and set permanent.~n",[Version]);
+ {error, Reason} ->
+ ?INFO("Unpack failed: ~p~n",[Reason]),
+ print_existing_versions(TargetNode),
+ erlang:halt(2)
end;
-main(["install", RelName, NameTypeArg, NodeName, Cookie, VersionArg]) ->
+unpack(_, Args) ->
+ ?INFO("unpack: unknown args ~p\n", [Args]).
+
+install({RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
TargetNode = start_distribution(NodeName, NameTypeArg, 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]),
- install_and_permafy(TargetNode, RelName, Vsn);
- {error, UnpackReason} ->
- print_existing_versions(TargetNode),
- ?INFO("Unpack failed: ~p~n",[UnpackReason]),
- erlang:halt(2)
- end;
+ Version = proplists:get_value(version, Opts),
+ case unpack_release(RelName, TargetNode, Version) of
+ {ok, Vsn} ->
+ ?INFO("Unpacked successfully: ~p~n", [Vsn]),
+ check_and_install(TargetNode, Vsn),
+ maybe_permafy(TargetNode, RelName, Vsn, Opts);
old ->
%% no need to unpack, has been installed previously
?INFO("Release ~s is marked old, switching to it.~n",[Version]),
- install_and_permafy(TargetNode, RelName, Version);
+ check_and_install(TargetNode, Version),
+ maybe_permafy(TargetNode, RelName, Version, Opts);
unpacked ->
?INFO("Release ~s is already unpacked, now installing.~n",[Version]),
- install_and_permafy(TargetNode, RelName, Version);
- current -> %% installed and in-use, just needs to be permanent
- ?INFO("Release ~s is already installed and current. Making permanent.~n",[Version]),
- permafy(TargetNode, RelName, Version);
+ check_and_install(TargetNode, Version),
+ maybe_permafy(TargetNode, RelName, Version, Opts);
+ current ->
+ case proplists:get_value(permanent, Opts, true) of
+ true ->
+ ?INFO("Release ~s is already installed and current, making permanent.~n",
+ [Version]),
+ permafy(TargetNode, RelName, Version);
+ false ->
+ ?INFO("Release ~s is already installed and current.~n",
+ [Version])
+ end;
permanent ->
- ?INFO("Release ~s is already installed, and set permanent.~n",[Version])
+ %% this release is marked permanent, however it might not the
+ %% one currently running
+ case current_release_version(TargetNode) of
+ Version ->
+ ?INFO("Release ~s is already installed, running and set permanent.~n",
+ [Version]);
+ CurrentVersion ->
+ ?INFO("Release ~s is the currently running version.~n",
+ [CurrentVersion]),
+ check_and_install(TargetNode, Version),
+ maybe_permafy(TargetNode, RelName, Version, Opts)
+ end;
+ {error, Reason} ->
+ ?INFO("Unpack failed: ~p~n",[Reason]),
+ print_existing_versions(TargetNode),
+ erlang:halt(2)
end;
-main(_) ->
- erlang:halt(1).
+install(_, Args) ->
+ ?INFO("install: unknown args ~p\n", [Args]).
+
+upgrade(DistInfo, Args) ->
+ install(DistInfo, Args).
+
+downgrade(DistInfo, Args) ->
+ install(DistInfo, Args).
+
+uninstall({_RelName, NameTypeArg, NodeName, Cookie}, Opts) ->
+ TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
+ WhichReleases = which_releases(TargetNode),
+ Version = proplists:get_value(version, Opts),
+ case proplists:get_value(Version, WhichReleases) of
+ undefined ->
+ ?INFO("Release ~s is already uninstalled.~n", [Version]);
+ old ->
+ ?INFO("Release ~s is marked old, uninstalling it.~n", [Version]),
+ remove_release(TargetNode, Version);
+ unpacked ->
+ ?INFO("Release ~s is marked unpacked, uninstalling it~n",
+ [Version]),
+ remove_release(TargetNode, Version);
+ current ->
+ ?INFO("Uninstall failed: Release ~s is marked current.~n", [Version]),
+ erlang:halt(2);
+ permanent ->
+ ?INFO("Uninstall failed: Release ~s is running.~n", [Version]),
+ erlang:halt(2)
+ end;
+uninstall(_, Args) ->
+ ?INFO("uninstall: unknown args ~p\n", [Args]).
+
+versions({_RelName, NameTypeArg, NodeName, Cookie}, []) ->
+ TargetNode = start_distribution(NodeName, NameTypeArg, Cookie),
+ print_existing_versions(TargetNode).
+
+parse_arguments(Args) ->
+ parse_arguments(Args, []).
+
+parse_arguments([], Acc) -> Acc;
+parse_arguments(["--no-permanent"|Rest], Acc) ->
+ parse_arguments(Rest, [{permanent, false}] ++ Acc);
+parse_arguments([VersionStr|Rest], Acc) ->
+ Version = parse_version(VersionStr),
+ parse_arguments(Rest, [{version, Version}] ++ Acc).
+
+unpack_release(RelName, TargetNode, Version) ->
+ WhichReleases = which_releases(TargetNode),
+ case proplists:get_value(Version, WhichReleases) of
+ undefined ->
+ %% not installed, so unpack tarball:
+ %% look for a release package with the intended version in the following order:
+ %% releases/<relname>-<version>.tar.gz
+ %% releases/<version>/<relname>-<version>.tar.gz
+ %% releases/<version>/<relname>.tar.gz
+ case find_and_link_release_package(Version, RelName) of
+ {_, undefined} ->
+ {error, release_package_not_found};
+ {ReleasePackage, ReleasePackageLink} ->
+ ?INFO("Release ~s not found, attempting to unpack ~s~n",
+ [Version, ReleasePackage]),
+ case rpc:call(TargetNode, release_handler, unpack_release,
+ [ReleasePackageLink], ?TIMEOUT) of
+ {ok, Vsn} -> {ok, Vsn};
+ {error, _} = Error -> Error
+ end
+ end;
+ Other -> Other
+ end.
+
+%% 1. look for a release package tarball with the provided version in the following order:
+%% releases/<relname>-<version>.tar.gz
+%% releases/<version>/<relname>-<version>.tar.gz
+%% releases/<version>/<relname>.tar.gz
+%% 2. create a symlink from a fixed location (ie. releases/<version>/<relname>.tar.gz)
+%% to the release package tarball found in 1.
+%% 3. return a tuple with the paths to the release package and
+%% to the symlink that is to be provided to release handler
+find_and_link_release_package(Version, RelName) ->
+ RelNameStr = atom_to_list(RelName),
+ %% regardless of the location of the release package, we'll
+ %% always give release handler the same path which is the symlink
+ %% the path to the package link is relative to "releases/" because
+ %% that's what release handler is expecting
+ ReleaseHandlerPackageLink = filename:join(Version, RelNameStr),
+ %% this is the symlink name we'll create once
+ %% we've found where the actual release package is located
+ ReleaseLink = filename:join(["releases", Version,
+ RelNameStr ++ ".tar.gz"]),
+ case first_value(fun filelib:is_file/1,
+ [filename:join(["releases",
+ RelNameStr ++ "-" ++ Version ++ ".tar.gz"]),
+ filename:join(["releases", Version,
+ RelNameStr ++ "-" ++ Version ++ ".tar.gz"]),
+ filename:join(["releases", Version,
+ RelNameStr ++ ".tar.gz"])]) of
+ no_value ->
+ {undefined, undefined};
+ %% no need to create the link since the release package we
+ %% found is located in the same place as the link would be
+ {ok, Filename} when is_list(Filename) andalso
+ Filename =:= ReleaseLink ->
+ {Filename, ReleaseHandlerPackageLink};
+ {ok, Filename} when is_list(Filename) ->
+ %% we now have the location of the release package, however
+ %% release handler expects a fixed nomenclature (<relname>.tar.gz)
+ %% so give it just that by creating a symlink to the tarball
+ %% we found.
+ %% make sure that the dir where we're creating the link in exists
+ ok = filelib:ensure_dir(filename:join([filename:dirname(ReleaseLink), "dummy"])),
+ %% create the symlink pointing to the full path name of the
+ %% release package we found
+ ok = file:make_symlink(filename:absname(Filename), ReleaseLink),
+ {Filename, ReleaseHandlerPackageLink}
+ end.
+
+first_value(_Fun, []) -> no_value;
+first_value(Fun, [Value | Rest]) ->
+ case Fun(Value) of
+ false ->
+ first_value(Fun, Rest);
+ true ->
+ {ok, Value}
+ end.
parse_version(V) when is_list(V) ->
hd(string:tokens(V,"/")).
-install_and_permafy(TargetNode, RelName, Vsn) ->
- case rpc:call(TargetNode, release_handler, check_install_release, [Vsn], ?TIMEOUT) of
+check_and_install(TargetNode, Vsn) ->
+ case rpc:call(TargetNode, release_handler,
+ check_install_release, [Vsn], ?TIMEOUT) of
{ok, _OtherVsn, _Desc} ->
ok;
{error, Reason} ->
?INFO("ERROR: release_handler:check_install_release failed: ~p~n",[Reason]),
erlang:halt(3)
end,
- case rpc:call(TargetNode, release_handler, install_release, [Vsn], ?TIMEOUT) of
+ case rpc:call(TargetNode, release_handler, install_release,
+ [Vsn, [{update_paths, true}]], ?TIMEOUT) of
{ok, _, _} ->
?INFO("Installed Release: ~s~n", [Vsn]),
- permafy(TargetNode, RelName, Vsn),
ok;
{error, {no_such_release, Vsn}} ->
VerList =
@@ -106,28 +257,55 @@ install_and_permafy(TargetNode, RelName, Vsn) ->
erlang:halt(4)
end.
+maybe_permafy(TargetNode, RelName, Vsn, Opts) ->
+ case proplists:get_value(permanent, Opts, true) of
+ true ->
+ permafy(TargetNode, RelName, Vsn);
+ false -> ok
+ end.
+
permafy(TargetNode, RelName, Vsn) ->
- ok = rpc:call(TargetNode, release_handler, make_permanent, [Vsn], ?TIMEOUT),
- file:copy(filename:join(["bin", RelName++"-"++Vsn]),
- filename:join(["bin", RelName])),
+ ok = rpc:call(TargetNode, release_handler,
+ make_permanent, [Vsn], ?TIMEOUT),
+ file:copy(filename:join(["bin", atom_to_list(RelName)++"-"++Vsn]),
+ filename:join(["bin", atom_to_list(RelName)])),
?INFO("Made release permanent: ~p~n", [Vsn]),
ok.
+remove_release(TargetNode, Vsn) ->
+ case rpc:call(TargetNode, release_handler, remove_release, [Vsn], ?TIMEOUT) of
+ ok ->
+ ?INFO("Uninstalled Release: ~s~n", [Vsn]),
+ ok;
+ {error, Reason} ->
+ ?INFO("ERROR: release_handler:remove_release failed: ~p~n", [Reason]),
+ erlang:halt(3)
+ end.
+
which_releases(TargetNode) ->
R = rpc:call(TargetNode, release_handler, which_releases, [], ?TIMEOUT),
[ {V, S} || {_,V,_, S} <- R ].
+%% the running release version is either the only one marked `current´
+%% or, if none exists, the one marked `permanent`
+current_release_version(TargetNode) ->
+ R = rpc:call(TargetNode, release_handler, which_releases,
+ [], ?TIMEOUT),
+ Versions = [ {S, V} || {_,V,_, S} <- R ],
+ %% current version takes priority over the permanent
+ proplists:get_value(current, Versions,
+ proplists:get_value(permanent, Versions)).
+
print_existing_versions(TargetNode) ->
VerList = iolist_to_binary([
io_lib:format("* ~s\t~s~n",[V,S])
|| {V,S} <- which_releases(TargetNode) ]),
?INFO("Installed versions:~n~s", [VerList]).
-start_distribution(NodeName, NameTypeArg, Cookie) ->
- MyNode = make_script_node(NodeName),
+start_distribution(TargetNode, NameTypeArg, Cookie) ->
+ MyNode = make_script_node(TargetNode),
{ok, _Pid} = net_kernel:start([MyNode, get_name_type(NameTypeArg)]),
- erlang:set_cookie(node(), list_to_atom(Cookie)),
- TargetNode = list_to_atom(NodeName),
+ erlang:set_cookie(node(), Cookie),
case {net_kernel:connect_node(TargetNode),
net_adm:ping(TargetNode)} of
{true, pong} ->
@@ -141,7 +319,7 @@ start_distribution(NodeName, NameTypeArg, Cookie) ->
TargetNode.
make_script_node(Node) ->
- [Name, Host] = string:tokens(Node, "@"),
+ [Name, Host] = string:tokens(atom_to_list(Node), "@"),
list_to_atom(lists:concat([Name, "_upgrader_", os:getpid(), "@", Host])).
%% get name type from arg
diff --git a/priv/templates/vm_args b/priv/templates/vm_args
index 02841bd..f65c7dc 100644
--- a/priv/templates/vm_args
+++ b/priv/templates/vm_args
@@ -17,3 +17,14 @@
## Tweak GC to run more often
##-env ERL_FULLSWEEP_AFTER 10
+
+# +B [c | d | i]
+# Option c makes Ctrl-C interrupt the current shell instead of invoking the emulator break
+# handler. Option d (same as specifying +B without an extra option) disables the break handler. # Option i makes the emulator ignore any break signal.
+# If option c is used with oldshell on Unix, Ctrl-C will restart the shell process rather than
+# interrupt it.
+# Disable the emulator break handler
+# it easy to accidentally type ctrl-c when trying
+# to reach for ctrl-d. ctrl-c on a live node can
+# have very undesirable results
+##+Bi
diff --git a/rebar.config b/rebar.config
index bb179ea..762d6df 100644
--- a/rebar.config
+++ b/rebar.config
@@ -1,9 +1,9 @@
%% -*- mode: Erlang; fill-column: 80; comment-column: 75; -*-
%% Dependencies ================================================================
-{deps, [{erlware_commons, "0.21.0"},
+{deps, [{erlware_commons, "1.0.0"},
{providers, "1.6.0"},
{getopt, "0.8.2"},
- {cf, "0.2.1"},
+ {cf, "0.2.2"},
{bbmustache, "1.0.4"}
]}.
@@ -17,10 +17,12 @@
[{platform_define, "^[0-9]+", namespaced_types},
{platform_define, "^1[8|9]", rand_module},
{platform_define, "^2", rand_module},
- no_debug_info,
warnings_as_errors,
inline]}.
+%% Use OTP 18+ when dialyzing relx
+{dialyzer, [{warnings, [unknown]}]}.
+
%% EUnit =======================================================================
{eunit_opts,
[{report, {eunit_surefire, [{dir, "."}]}}]}.
@@ -29,7 +31,22 @@
{profiles, [{dev, [{plugins, [rebar3_neotoma_plugin]}]},
- {test, [{erl_opts, [debug_info]}]}
+ {test, [{erl_opts, [nowarn_export_all, debug_info]}]},
+
+ {dialyze, [{overrides, [{add, erlware_commons, [{erl_opts, [debug_info]}]},
+ {add, providers, [{erl_opts, [debug_info]}]},
+ {add, getopt, [{erl_opts, [debug_info]}]},
+ {add, bbmustache, [{erl_opts, [debug_info]}]},
+ {add, cf, [{erl_opts, [debug_info]}]}]},
+ {erl_opts, [debug_info]}]},
+ {escript, [
+ {overrides, [{add, erlware_commons, [{erl_opts, [no_debug_info]}]},
+ {add, providers, [{erl_opts, [no_debug_info]}]},
+ {add, getopt, [{erl_opts, [no_debug_info]}]},
+ {add, bbmustache, [{erl_opts, [no_debug_info]}]},
+ {add, cf, [{erl_opts, [no_debug_info]}]}]},
+ {erl_opts, [no_debug_info]}
+ ]}
]}.
{overrides, [{override, erlware_commons, [
diff --git a/rebar.lock b/rebar.lock
index f14a68f..81f0013 100644
--- a/rebar.lock
+++ b/rebar.lock
@@ -1,5 +1,14 @@
+{"1.1.0",
[{<<"bbmustache">>,{pkg,<<"bbmustache">>,<<"1.0.4">>},0},
- {<<"cf">>,{pkg,<<"cf">>,<<"0.2.1">>},0},
- {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"0.21.0">>},0},
+ {<<"cf">>,{pkg,<<"cf">>,<<"0.2.2">>},0},
+ {<<"erlware_commons">>,{pkg,<<"erlware_commons">>,<<"1.0.0">>},0},
{<<"getopt">>,{pkg,<<"getopt">>,<<"0.8.2">>},0},
- {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}].
+ {<<"providers">>,{pkg,<<"providers">>,<<"1.6.0">>},0}]}.
+[
+{pkg_hash,[
+ {<<"bbmustache">>, <<"7BA94F971C5AFD7B6617918A4BB74705E36CAB36EB84B19B6A1B7EE06427AA38">>},
+ {<<"cf">>, <<"7F2913FFF90ABCABD0F489896CFEB0B0674F6C8DF6C10B17A83175448029896C">>},
+ {<<"erlware_commons">>, <<"087467DE5833C0BB5B3CCDD387F9E9C1FB816A75B7A709629BF24B5ED3246C51">>},
+ {<<"getopt">>, <<"B17556DB683000BA50370B16C0619DF1337E7AF7ECBF7D64FBF8D1D6BCE3109B">>},
+ {<<"providers">>, <<"DB0E2F9043AE60C0155205FCD238D68516331D0E5146155E33D1E79DC452964A">>}]}
+].
diff --git a/src/rlx_app_discovery.erl b/src/rlx_app_discovery.erl
index dcd2604..0414a0a 100644
--- a/src/rlx_app_discovery.erl
+++ b/src/rlx_app_discovery.erl
@@ -290,13 +290,24 @@ get_vsn(AppDir, AppName, AppDetail) ->
end
end.
--spec get_deps(file:name(), atom(), string(), proplists:proplist()) ->
+-spec get_deps(binary(), atom(), string(), proplists:proplist()) ->
{ok, rlx_app_info:t()} | {error, Reason::term()}.
get_deps(AppDir, AppName, AppVsn, AppDetail) ->
- ActiveApps = proplists:get_value(applications, AppDetail, []),
+ %% ensure that at least stdlib and kernel are defined as application deps
+ ActiveApps = ensure_stdlib_kernel(AppName,
+ proplists:get_value(applications, AppDetail, [])),
LibraryApps = proplists:get_value(included_applications, AppDetail, []),
rlx_app_info:new(AppName, AppVsn, AppDir, ActiveApps, LibraryApps).
+-spec ensure_stdlib_kernel(AppName :: atom(),
+ Apps :: list(atom())) -> list(atom()).
+ensure_stdlib_kernel(kernel, Deps) -> Deps;
+ensure_stdlib_kernel(stdlib, Deps) -> Deps;
+ensure_stdlib_kernel(_AppName, []) ->
+ %% minimum required deps are kernel and stdlib
+ [kernel, stdlib];
+ensure_stdlib_kernel(_AppName, Deps) -> Deps.
+
%%%===================================================================
%%% Test Functions
%%%===================================================================
diff --git a/src/rlx_app_info.erl b/src/rlx_app_info.erl
index b3402d0..f44dbb5 100644
--- a/src/rlx_app_info.erl
+++ b/src/rlx_app_info.erl
@@ -61,9 +61,9 @@
-include("relx.hrl").
-record(app_info_t, {name :: atom(),
- original_vsn :: string(),
- vsn :: ec_semver:semver(),
- dir :: binary(),
+ original_vsn :: undefined | string(),
+ vsn :: undefined | ec_semver:semver(),
+ dir :: undefined | binary(),
link=false :: boolean(),
active_deps=[]:: [atom()],
library_deps=[] :: [atom()]}).
@@ -83,13 +83,13 @@ new() ->
{ok, #app_info_t{}}.
%% @doc build a complete version of the app info with all fields set.
--spec new(atom(), string(), file:name(), [atom()], [atom()]) ->
+-spec new(atom(), string(), binary(), [atom()], [atom()]) ->
{ok, t()} | relx:error().
new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps) ->
new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, false).
%% @doc build a complete version of the app info with all fields set.
--spec new(atom(), string(), file:name(), [atom()], [atom()], boolean()) ->
+-spec new(atom(), string(), binary(), [atom()], [atom()], boolean()) ->
{ok, t()} | relx:error().
new(AppName, Vsn, Dir, ActiveDeps, LibraryDeps, Link)
when erlang:is_atom(AppName),
@@ -138,10 +138,10 @@ vsn(AppInfo=#app_info_t{name=AppName}, AppVsn)
{ok, AppInfo#app_info_t{vsn=ParsedVsn}}
end.
--spec dir(t()) -> file:name().
+-spec dir(t()) -> binary().
dir(#app_info_t{dir=Dir}) ->
Dir.
--spec dir(t(), file:name()) -> t().
+-spec dir(t(), binary()) -> t().
dir(AppInfo=#app_info_t{}, Dir) ->
AppInfo#app_info_t{dir=Dir}.
diff --git a/src/rlx_cmd_args.erl b/src/rlx_cmd_args.erl
index 7f3f39b..b20344c 100644
--- a/src/rlx_cmd_args.erl
+++ b/src/rlx_cmd_args.erl
@@ -282,6 +282,9 @@ create(include_erts, Opts) ->
Erts when is_list(Erts) ->
{include_erts, Erts}
end;
+create(warnings_as_errors, Opts) ->
+ WarningsAsErrors = proplists:get_value(warnings_as_errors, Opts, false),
+ {warnings_as_errors, WarningsAsErrors};
create(_, _) ->
[].
diff --git a/src/rlx_config.erl b/src/rlx_config.erl
index d18f5f1..b5ef51b 100644
--- a/src/rlx_config.erl
+++ b/src/rlx_config.erl
@@ -173,6 +173,8 @@ 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({exclude_modules, ExcludeModules0}, {ok, State0}) ->
+ {ok, rlx_state:exclude_modules(State0, ExcludeModules0)};
load_terms({debug_info, DebugInfo}, {ok, State0}) ->
{ok, rlx_state:debug_info(State0, DebugInfo)};
load_terms({overrides, Overrides0}, {ok, State0}) ->
@@ -265,8 +267,10 @@ load_terms({output_dir, OutputDir}, {ok, State}) ->
load_terms({overlay_vars, OverlayVars}, {ok, State}) ->
CurrentOverlayVars = rlx_state:get(State, overlay_vars),
NewOverlayVars0 = list_of_overlay_vars_files(OverlayVars),
- NewOverlayVars1 = lists:umerge(lists:usort(NewOverlayVars0), lists:usort(CurrentOverlayVars)),
+ NewOverlayVars1 = CurrentOverlayVars ++ NewOverlayVars0,
{ok, rlx_state:put(State, overlay_vars, NewOverlayVars1)};
+load_terms({warnings_as_errors, WarningsAsErrors}, {ok, State}) ->
+ {ok, rlx_state:warnings_as_errors(State, WarningsAsErrors)};
load_terms({Name, Value}, {ok, State})
when erlang:is_atom(Name) ->
{ok, rlx_state:put(State, Name, Value)};
@@ -310,7 +314,6 @@ merge_configs([{Key, Value} | CliTerms], ConfigTerms) ->
case Key of
X when X =:= lib_dirs
; X =:= goals
- ; X =:= overlay_vars
; X =:= overrides ->
case lists:keyfind(Key, 1, ConfigTerms) of
{Key, Value2} ->
@@ -319,6 +322,17 @@ merge_configs([{Key, Value} | CliTerms], ConfigTerms) ->
false ->
merge_configs(CliTerms, ConfigTerms++[{Key, Value}])
end;
+ overlay_vars ->
+ case lists:keyfind(overlay_vars, 1, ConfigTerms) of
+ {_, [H | _] = Vars} when is_list(H) ->
+ MergedValue = Vars ++ Value,
+ merge_configs(CliTerms, lists:keyreplace(overlay_vars, 1, ConfigTerms, {Key, MergedValue}));
+ {_, Vars} when is_list(Vars) ->
+ MergedValue = [Vars | Value],
+ merge_configs(CliTerms, lists:keyreplace(overlay_vars, 1, ConfigTerms, {Key, MergedValue}));
+ false ->
+ merge_configs(CliTerms, ConfigTerms++[{Key, Value}])
+ end;
_ ->
merge_configs(CliTerms, lists:reverse(lists:keystore(Key, 1, lists:reverse(ConfigTerms), {Key, Value})))
end.
diff --git a/src/rlx_depsolver.erl b/src/rlx_depsolver.erl
index fd26145..9e34a2c 100644
--- a/src/rlx_depsolver.erl
+++ b/src/rlx_depsolver.erl
@@ -113,7 +113,7 @@
%% type
%%============================================================================
-ifdef(namespaced_types).
--type dep_graph() :: gb_tree:tree().
+-type dep_graph() :: gb_trees:tree().
-else.
-type dep_graph() :: gb_tree().
-endif.
diff --git a/src/rlx_goal.erl b/src/rlx_goal.erl
index 354aa48..07126d5 100644
--- a/src/rlx_goal.erl
+++ b/src/rlx_goal.erl
@@ -10,8 +10,6 @@
-define(p_seq,true).
-define(p_string,true).
-
--compile(export_all).
-spec file(file:name()) -> any().
file(Filename) -> case file:read_file(Filename) of {ok,Bin} -> parse(Bin); Err -> Err end.
diff --git a/src/rlx_prv_assembler.erl b/src/rlx_prv_assembler.erl
index e942b64..2088de6 100644
--- a/src/rlx_prv_assembler.erl
+++ b/src/rlx_prv_assembler.erl
@@ -121,7 +121,10 @@ format_error({start_clean_script_generation_error, Module, Errors}) ->
rlx_util:indent(2), Module:format_error(Errors)];
format_error({strip_release, Reason}) ->
io_lib:format("Stripping debug info from release beam files failed becuase ~s",
- [beam_lib:format_error(Reason)]).
+ [beam_lib:format_error(Reason)]);
+format_error({rewrite_app_file, AppFile, Error}) ->
+ io_lib:format("Unable to rewrite .app file ~s due to ~p",
+ [AppFile, Error]).
%%%===================================================================
%%% Internal Functions
@@ -162,7 +165,7 @@ copy_app_directories_to_output(State, Release, OutputDir) ->
false
end,
lists:flatten(ec_plists:map(fun(App) ->
- copy_app(LibDir, App, IncludeSrc, IncludeErts)
+ copy_app(State, LibDir, App, IncludeSrc, IncludeErts)
end, Apps))),
case Result of
[E | _] ->
@@ -179,7 +182,7 @@ prepare_applications(State, Apps) ->
Apps
end.
-copy_app(LibDir, App, IncludeSrc, IncludeErts) ->
+copy_app(State, 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),
@@ -196,57 +199,75 @@ copy_app(LibDir, App, IncludeSrc, IncludeErts) ->
true ->
[];
false ->
- copy_app_(App, AppDir, TargetDir, IncludeSrc)
+ copy_app_(State, App, AppDir, TargetDir, IncludeSrc)
end;
_ ->
- copy_app_(App, AppDir, TargetDir, IncludeSrc)
+ copy_app_(State, App, AppDir, TargetDir, IncludeSrc)
end
end.
is_erts_lib(Dir) ->
lists:prefix(filename:split(list_to_binary(code:lib_dir())), filename:split(Dir)).
-copy_app_(App, AppDir, TargetDir, IncludeSrc) ->
+copy_app_(State, App, AppDir, TargetDir, IncludeSrc) ->
remove_symlink_or_directory(TargetDir),
case rlx_app_info:link(App) of
true ->
link_directory(AppDir, TargetDir),
- rewrite_app_file(App, AppDir);
+ rewrite_app_file(State, App, AppDir);
false ->
- copy_directory(AppDir, TargetDir, IncludeSrc),
- rewrite_app_file(App, TargetDir)
+ copy_directory(State, App, AppDir, TargetDir, IncludeSrc),
+ rewrite_app_file(State, App, TargetDir)
end.
%% If excluded apps exist in this App's applications list we must write a new .app
-rewrite_app_file(App, TargetDir) ->
+rewrite_app_file(State, 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)
+ {ok, [{application, AppName, AppData0}]} = file:consult(AppFile),
+ OldActiveDeps = proplists:get_value(applications, AppData0, []),
+ OldIncludedDeps = proplists:get_value(included_applications, AppData0, []),
+ OldModules = proplists:get_value(modules, AppData0, []),
+ ExcludedModules = proplists:get_value(Name,
+ rlx_state:exclude_modules(State), []),
+
+ %% maybe replace excluded apps
+ AppData2 =
+ case {OldActiveDeps, OldIncludedDeps} of
+ {ActiveDeps, IncludedDeps} ->
+ AppData0;
+ _ ->
+ AppData1 = lists:keyreplace(applications
+ ,1
+ ,AppData0
+ ,{applications, ActiveDeps}),
+ lists:keyreplace(included_applications
+ ,1
+ ,AppData1
+ ,{included_applications, IncludedDeps})
+ end,
+ %% maybe replace excluded modules
+ AppData3 =
+ case ExcludedModules of
+ [] -> AppData2;
+ _ ->
+ lists:keyreplace(modules
+ ,1
+ ,AppData2
+ ,{modules, OldModules -- ExcludedModules})
+ end,
+ Spec = [{application, AppName, AppData3}],
+ case write_file_if_contents_differ(AppFile, Spec) of
+ ok -> ok;
+ Error -> ?RLX_ERROR({rewrite_app_file, AppFile, Error})
end.
-write_file_if_contents_differ(Filename, Bytes) ->
- ToWrite = iolist_to_binary(Bytes),
- case file:read_file(Filename) of
- {ok, ToWrite} ->
+write_file_if_contents_differ(Filename, Spec) ->
+ ToWrite = io_lib:format("~p.\n", Spec),
+ case file:consult(Filename) of
+ {ok, Spec} ->
ok;
{ok, _} ->
file:write_file(Filename, ToWrite);
@@ -275,8 +296,8 @@ link_directory(AppDir, TargetDir) ->
ok
end.
-copy_directory(AppDir, TargetDir, IncludeSrc) ->
- [copy_dir(AppDir, TargetDir, SubDir)
+copy_directory(State, App, AppDir, TargetDir, IncludeSrc) ->
+ [copy_dir(State, App, AppDir, TargetDir, SubDir)
|| SubDir <- ["ebin",
"include",
"priv",
@@ -289,13 +310,20 @@ copy_directory(AppDir, TargetDir, IncludeSrc) ->
[]
end]].
-copy_dir(AppDir, TargetDir, SubDir) ->
+copy_dir(State, App, AppDir, TargetDir, SubDir) ->
SubSource = filename:join(AppDir, SubDir),
SubTarget = filename:join(TargetDir, SubDir),
case ec_file:is_dir(SubSource) of
true ->
ok = rlx_util:mkdir_p(SubTarget),
- case ec_file:copy(SubSource, SubTarget, [recursive]) of
+ %% get a list of the modules to be excluded from this app
+ AppName = rlx_app_info:name(App),
+ ExcludedModules = proplists:get_value(AppName, rlx_state:exclude_modules(State),
+ []),
+ ExcludedFiles = [filename:join([binary_to_list(SubSource),
+ atom_to_list(M) ++ ".beam"]) ||
+ M <- ExcludedModules],
+ case copy_dir(SubSource, SubTarget, ExcludedFiles) of
{error, E} ->
?RLX_ERROR({ec_file_error, AppDir, SubTarget, E});
ok ->
@@ -305,6 +333,22 @@ copy_dir(AppDir, TargetDir, SubDir) ->
ok
end.
+%% no files are excluded, just copy the whole dir
+copy_dir(SourceDir, TargetDir, []) ->
+ case ec_file:copy(SourceDir, TargetDir, [recursive]) of
+ {error, E} -> {error, E};
+ ok ->
+ ok
+ end;
+copy_dir(SourceDir, TargetDir, ExcludeFiles) ->
+ SourceFiles = filelib:wildcard(
+ filename:join([binary_to_list(SourceDir), "*"])),
+ lists:foreach(fun(F) ->
+ ok = ec_file:copy(F,
+ filename:join([TargetDir,
+ filename:basename(F)]))
+ end, SourceFiles -- ExcludeFiles).
+
create_release_info(State0, Release0, OutputDir) ->
RelName = atom_to_list(rlx_release:name(Release0)),
ReleaseDir = rlx_util:release_output_dir(State0, Release0),
@@ -349,13 +393,17 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
rlx_release:erts(Release),
ErlOpts);
true ->
- case rlx_state:get(State, extended_start_script, false) of
- true ->
- include_nodetool(BinDir);
- false ->
- ok
- end,
- extended_bin_file_contents(OsFamily, RelName, RelVsn, rlx_release:erts(Release), ErlOpts)
+ %% extended start script needs nodetool so it's
+ %% always included
+ include_nodetool(BinDir),
+ Hooks = expand_hooks(BinDir,
+ rlx_state:get(State,
+ extended_start_script_hooks,
+ []),
+ State),
+ extended_bin_file_contents(OsFamily, RelName, RelVsn,
+ rlx_release:erts(Release), ErlOpts,
+ Hooks)
end,
%% We generate the start script by default, unless the user
%% tells us not too
@@ -386,6 +434,98 @@ write_bin_file(State, Release, OutputDir, RelDir) ->
E
end.
+expand_hooks(_Bindir, [], _State) -> [];
+expand_hooks(BinDir, Hooks, _State) ->
+ expand_hooks(BinDir, Hooks, [], _State).
+
+expand_hooks(_BinDir, [], Acc, _State) -> Acc;
+expand_hooks(BinDir, [{Phase, Hooks0} | Rest], Acc, State) ->
+ %% filter and expand hooks to their respective shell scripts
+ Hooks =
+ lists:foldl(
+ fun(Hook, Acc0) ->
+ case validate_hook(Phase, Hook) of
+ true ->
+ %% all hooks are relative to the bin dir
+ HookScriptFilename = filename:join([BinDir,
+ hook_filename(Hook)]),
+ %% write the hook script file to it's proper location
+ ok = render_hook(hook_template(Hook), HookScriptFilename, State),
+ %% and return the invocation that's to be templated in the
+ %% extended script
+ Acc0 ++ [hook_invocation(Hook)];
+ false ->
+ ec_cmd_log:error(
+ rlx_state:log(State),
+ io_lib:format("~p hook is not allowed in the ~p phase, ignoring it", [Hook, Phase])
+ ),
+
+ Acc0
+ end
+ end, [], Hooks0),
+ expand_hooks(BinDir, Rest, Acc ++ [{Phase, Hooks}], State).
+
+%% the pid script hook is only allowed in the
+%% post_start phase
+%% with args
+validate_hook(post_start, {pid, _}) -> true;
+%% and without args
+validate_hook(post_start, pid) -> true;
+%% same for wait_for_vm_start, wait_for_process script
+validate_hook(post_start, wait_for_vm_start) -> true;
+validate_hook(post_start, {wait_for_process, _}) -> true;
+%% custom hooks are allowed in all phases
+validate_hook(_Phase, {custom, _}) -> true;
+%% as well as status hooks
+validate_hook(status, _) -> true;
+%% deny all others
+validate_hook(_, _) -> false.
+
+hook_filename({custom, CustomScript}) -> CustomScript;
+hook_filename(pid) -> "hooks/builtin/pid";
+hook_filename({pid, _}) -> "hooks/builtin/pid";
+hook_filename(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
+hook_filename({wait_for_process, _}) -> "hooks/builtin/wait_for_process";
+hook_filename(builtin_status) -> "hooks/builtin/status".
+
+hook_invocation({custom, CustomScript}) -> CustomScript;
+%% the pid builtin hook with no arguments writes to pid file
+%% at /var/run/{{ rel_name }}.pid
+hook_invocation(pid) -> string:join(["hooks/builtin/pid",
+ "/var/run/$REL_NAME.pid"], "|");
+hook_invocation({pid, PidFile}) -> string:join(["hooks/builtin/pid",
+ PidFile], "|");
+hook_invocation(wait_for_vm_start) -> "hooks/builtin/wait_for_vm_start";
+hook_invocation({wait_for_process, Name}) ->
+ %% wait_for_process takes an atom as argument
+ %% which is the process name to wait for
+ string:join(["hooks/builtin/wait_for_process",
+ atom_to_list(Name)], "|");
+hook_invocation(builtin_status) -> "hooks/builtin/status".
+
+hook_template({custom, _}) -> custom;
+hook_template(pid) -> builtin_hook_pid;
+hook_template({pid, _}) -> builtin_hook_pid;
+hook_template(wait_for_vm_start) -> builtin_hook_wait_for_vm_start;
+hook_template({wait_for_process, _}) -> builtin_hook_wait_for_process;
+hook_template(builtin_status) -> builtin_hook_status.
+
+%% custom hooks are not rendered, they should
+%% be copied by the release overlays
+render_hook(custom, _, _) -> ok;
+render_hook(TemplateName, Script, State) ->
+ ec_cmd_log:info(
+ rlx_state:log(State),
+ "rendering ~p hook to ~p~n",
+ [TemplateName, Script]
+ ),
+
+ Template = render(TemplateName),
+ ok = filelib:ensure_dir(Script),
+ _ = ec_file:remove(Script),
+ ok = file:write_file(Script, Template),
+ ok = file:change_mode(Script, 8#755).
+
include_nodetool(BinDir) ->
NodeToolFile = nodetool_contents(),
InstallUpgradeFile = install_upgrade_escript_contents(),
@@ -456,7 +596,7 @@ copy_or_symlink_config_file(State, ConfigPath, RelConfPath) ->
ensure_not_exist(RelConfPath),
case rlx_state:dev_mode(State) of
true ->
- ok = rlx_util:symlink_or_copy(ConfigPath, RelConfPath ++ ".orig");
+ ok = rlx_util:symlink_or_copy(ConfigPath, RelConfPath);
_ ->
ok = ec_file:copy(ConfigPath, RelConfPath)
end.
@@ -502,6 +642,18 @@ include_erts(State, Release, OutputDir, RelDir) ->
ok = file:write_file(ErlIni, erl_ini(OutputDir, ErtsVersion))
end,
+ %% delete erts src if the user requested it not be included
+ case rlx_state:include_src(State) of
+ true -> ok;
+ false ->
+ SrcDir = filename:join([LocalErts, "src"]),
+ %% ensure the src folder exists before deletion
+ case ec_file:exists(SrcDir) of
+ true -> ok = ec_file:remove(SrcDir, [recursive]);
+ false -> ok
+ end
+ end,
+
case rlx_state:get(State, extended_start_script, false) of
true ->
@@ -559,7 +711,7 @@ make_boot_script_variables(State) ->
% (dictated by erl.ini [erlang] Rootdir=) and so a boot variable is made
% pointing to the release directory
% On non-Windows, $ROOT is set by the ROOTDIR environment variable as the
- % release directory, so a boot variable is made pointing to the erts
+ % release directory, so a boot variable is made pointing to the erts
% directory.
% NOTE the boot variable can point to either the release/erts root directory
% or the release/erts lib directory, as long as the usage here matches the
@@ -635,13 +787,30 @@ bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
{erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
-extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts) ->
+extended_bin_file_contents(OsFamily, RelName, RelVsn, ErtsVsn, ErlOpts, Hooks) ->
Template = case OsFamily of
unix -> extended_bin;
win32 -> extended_bin_windows
end,
+ %% turn all the hook lists into space separated strings
+ PreStartHooks = string:join(proplists:get_value(pre_start, Hooks, []), " "),
+ PostStartHooks = string:join(proplists:get_value(post_start, Hooks, []), " "),
+ PreStopHooks = string:join(proplists:get_value(pre_stop, Hooks, []), " "),
+ PostStopHooks = string:join(proplists:get_value(post_stop, Hooks, []), " "),
+ PreInstallUpgradeHooks = string:join(proplists:get_value(pre_install_upgrade,
+ Hooks, []), " "),
+ PostInstallUpgradeHooks = string:join(proplists:get_value(post_install_upgrade,
+ Hooks, []), " "),
+ StatusHook = string:join(proplists:get_value(status, Hooks, []), " "),
render(Template, [{rel_name, RelName}, {rel_vsn, RelVsn},
- {erts_vsn, ErtsVsn}, {erl_opts, ErlOpts}]).
+ {erts_vsn, ErtsVsn}, {erl_opts, ErlOpts},
+ {pre_start_hooks, PreStartHooks},
+ {post_start_hooks, PostStartHooks},
+ {pre_stop_hooks, PreStopHooks},
+ {post_stop_hooks, PostStopHooks},
+ {pre_install_upgrade_hooks, PreInstallUpgradeHooks},
+ {post_install_upgrade_hooks, PostInstallUpgradeHooks},
+ {status_hook, StatusHook}]).
erl_ini(OutputDir, ErtsVsn) ->
ErtsDirName = string:concat("erts-", ErtsVsn),
diff --git a/src/rlx_prv_overlay.erl b/src/rlx_prv_overlay.erl
index 71aca97..dc57326 100644
--- a/src/rlx_prv_overlay.erl
+++ b/src/rlx_prv_overlay.erl
@@ -308,6 +308,28 @@ handle_errors(State, Result) ->
-spec do_individual_overlay(rlx_state:t(), list(), proplists:proplist(),
OverlayDirective::term()) ->
{ok, rlx_state:t()} | relx:error().
+do_individual_overlay(State, _Files, OverlayVars, {chmod, Mode, Path}) ->
+ % mode can be specified directly as an integer value, or if it is
+ % not an integer we assume it's a template, which we render and convert
+ % blindly to an integer. So this will crash with an exception if for
+ % some reason something other than an integer is used
+ NewMode =
+ case is_integer(Mode) of
+ true -> Mode;
+ false -> erlang:list_to_integer(erlang:binary_to_list(render_string (OverlayVars, Mode)))
+ end,
+
+ Root = rlx_state:output_dir(State),
+ file_render_do(OverlayVars, Path,
+ fun(NewPath) ->
+ Absolute = absolutize(State,
+ filename:join(Root,erlang:iolist_to_binary (NewPath))),
+ case file:change_mode(Absolute, NewMode) of
+ {error, Error} ->
+ ?RLX_ERROR({unable_to_chmod, NewMode, NewPath, Error});
+ ok -> ok
+ end
+ end);
do_individual_overlay(State, _Files, OverlayVars, {mkdir, Dir}) ->
case rlx_util:render(erlang:iolist_to_binary(Dir), OverlayVars) of
{ok, IoList} ->
@@ -461,6 +483,14 @@ write_template(OverlayVars, FromFile, ToFile) ->
{ok, IoData} ->
case filelib:ensure_dir(ToFile) of
ok ->
+ %% we were asked to render a template
+ %% onto a symlink, this would cause an overwrite
+ %% of the original file, so we delete the symlink
+ %% and go ahead with the template render
+ case ec_file:is_symlink(ToFile) of
+ true -> ec_file:remove(ToFile);
+ false -> ok
+ end,
case file:write_file(ToFile, IoData) of
ok ->
{ok, FileInfo} = file:read_file_info(FromFile),
diff --git a/src/rlx_prv_relup.erl b/src/rlx_prv_relup.erl
index 9ac2135..1f8a950 100644
--- a/src/rlx_prv_relup.erl
+++ b/src/rlx_prv_relup.erl
@@ -65,6 +65,15 @@ format_error({relup_script_generation_error,
{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_warn, systools_relup,
+ [{erts_vsn_changed, _},
+ {erts_vsn_changed, _}]}) ->
+ "It has been detected that the ERTS version changed while generating the relup between versions, "
+ "please be aware that an instruction that will automatically restart the VM will be inserted in "
+ "this case";
+format_error({relup_script_generation_warn, Module, Warnings}) ->
+ ["Warnings generating relup \n",
+ rlx_util:indent(2), Module:format_warning(Warnings)];
format_error({relup_script_generation_error, Module, Errors}) ->
["Errors generating relup \n",
rlx_util:indent(2), Module:format_error(Errors)].
@@ -119,10 +128,20 @@ get_up_release(State, Release, Vsn) ->
make_upfrom_script(State, Release, UpFrom) ->
OutputDir = rlx_state:output_dir(State),
+ WarningsAsErrors = rlx_state:warnings_as_errors(State),
Options = [{outdir, OutputDir},
{path, rlx_util:get_code_paths(Release, OutputDir) ++
rlx_util:get_code_paths(UpFrom, OutputDir)},
silent],
+ %% the following block can be uncommented
+ %% when systools:make_relup/4 returns
+ %% {error,Module,Errors} instead of error
+ %% when taking the warnings_as_errors option
+ %% ++
+ %% case WarningsAsErrors of
+ %% true -> [warnings_as_errors];
+ %% false -> []
+ % end,
CurrentRel = strip_rel(rlx_release:relfile(Release)),
UpFromRel = strip_rel(rlx_release:relfile(UpFrom)),
ec_cmd_log:debug(rlx_state:log(State),
@@ -138,14 +157,26 @@ make_upfrom_script(State, Release, UpFrom) ->
[UpFromRel, CurrentRel]),
{ok, State};
error ->
- ?RLX_ERROR({relup_script_generation_error, CurrentRel, UpFromRel});
+ ?RLX_ERROR({relup_generation_error, CurrentRel, UpFromRel});
{ok, RelUp, _, []} ->
write_relup_file(State, Release, RelUp),
ec_cmd_log:info(rlx_state:log(State),
"relup successfully created!"),
{ok, State};
- {ok,_, Module,Warnings} ->
- ?RLX_ERROR({relup_script_generation_warn, Module, Warnings});
+ {ok, RelUp, Module,Warnings} ->
+ case WarningsAsErrors of
+ true ->
+ %% since we don't pass the warnings_as_errors option
+ %% the relup file gets generated anyway, we need to delete
+ %% it
+ file:delete(filename:join([OutputDir, "relup"])),
+ ?RLX_ERROR({relup_script_generation_warn, Module, Warnings});
+ false ->
+ write_relup_file(State, Release, RelUp),
+ ec_cmd_log:warn(rlx_state:log(State),
+ format_error({relup_script_generation_warn, Module, Warnings})),
+ {ok, State}
+ end;
{error,Module,Errors} ->
?RLX_ERROR({relup_script_generation_error, Module, Errors})
end.
diff --git a/src/rlx_release.erl b/src/rlx_release.erl
index dc39e34..5765079 100644
--- a/src/rlx_release.erl
+++ b/src/rlx_release.erl
@@ -59,7 +59,7 @@
-record(release_t, {name :: atom(),
vsn :: ec_semver:any_version(),
- erts :: ec_semver:any_version(),
+ erts :: undefined | ec_semver:any_version(),
goals = [] :: [rlx_depsolver:constraint()],
realized = false :: boolean(),
annotations = undefined :: annotations(),
@@ -144,7 +144,12 @@ goals(#release_t{goals=Goals}) ->
{ok, t()}.
realize(Rel, Pkgs0, World0) ->
World1 = subset_world(Pkgs0, World0),
- process_specs(realize_erts(Rel), World1).
+ case rlx_topo:sort_apps(World1) of
+ {ok, Pkgs1} ->
+ process_specs(realize_erts(Rel), Pkgs1);
+ Error={error, _} ->
+ Error
+ end.
%% @doc this gives the application specs for the release. This can only be
%% populated by the 'realize' call in this module.
@@ -239,6 +244,8 @@ format_goal(Constraint) ->
rlx_depsolver:format_constraint(Constraint).
-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}) ->
diff --git a/src/rlx_state.erl b/src/rlx_state.erl
index 6974d52..5032628 100644
--- a/src/rlx_state.erl
+++ b/src/rlx_state.erl
@@ -82,8 +82,11 @@
upfrom/1,
upfrom/2,
format/1,
- format/2]).
-
+ format/2,
+ exclude_modules/1,
+ exclude_modules/2,
+ warnings_as_errors/1,
+ warnings_as_errors/2]).
-export_type([t/0,
releases/0,
@@ -107,6 +110,7 @@
overrides=[] :: [{AppName::atom(), Directory::file:filename()}],
skip_apps=[] :: [AppName::atom()],
exclude_apps=[] :: [AppName::atom()],
+ exclude_modules=[] :: [{App::atom(), [Module::atom()]}],
debug_info=keep :: keep | strip,
configured_releases :: releases(),
realized_releases :: releases(),
@@ -114,7 +118,8 @@
include_src=true :: boolean(),
upfrom :: string() | binary() | undefined,
config_values :: ec_dictionary:dictionary(Key::atom(),
- Value::term())}).
+ Value::term()),
+ warnings_as_errors=false :: boolean()}).
%%============================================================================
%% types
@@ -200,6 +205,15 @@ exclude_apps(#state_t{exclude_apps=Apps}) ->
exclude_apps(State, SkipApps) ->
State#state_t{exclude_apps=SkipApps}.
+-spec exclude_modules(t()) -> [{App::atom(), [Module::atom()]}].
+exclude_modules(#state_t{exclude_modules=Modules}) ->
+ Modules.
+
+%% @doc modules to be excluded from the release
+-spec exclude_modules(t(), [{App::atom(), [Module::atom()]}]) -> t().
+exclude_modules(State, SkipModules) ->
+ State#state_t{exclude_modules=SkipModules}.
+
-spec debug_info(t()) -> keep | strip.
debug_info(#state_t{debug_info=DebugInfo}) ->
DebugInfo.
@@ -442,6 +456,14 @@ hooks(_State=#state_t{providers=Providers}, Target) ->
Provider = providers:get_provider(Target, Providers),
providers:hooks(Provider).
+-spec warnings_as_errors(t()) -> boolean().
+warnings_as_errors(#state_t{warnings_as_errors=WarningsAsErrors}) ->
+ WarningsAsErrors.
+
+-spec warnings_as_errors(t(), boolean()) -> t().
+warnings_as_errors(State, WarningsAsErrors) ->
+ State#state_t{warnings_as_errors=WarningsAsErrors}.
+
%% ===================================================================
%% Internal functions
%% ===================================================================
diff --git a/src/rlx_topo.erl b/src/rlx_topo.erl
new file mode 100644
index 0000000..f8fc5ad
--- /dev/null
+++ b/src/rlx_topo.erl
@@ -0,0 +1,190 @@
+%% -*- 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 Joe Armstrong
+%%% @author Eric Merritt
+%%% @author Konstantin Tcepliaev
+%%% @doc
+%%% This is a pretty simple topological sort for erlang. It was
+%%% originally written for ermake by Joe Armstrong back in '98. It
+%%% has been pretty heavily modified by Eric Merritt since '06 and modified again for Relx.
+%%% Konstantin Tcepliaev rewrote the algorithm in 2017.
+%%%
+%%% A partial order on the set S is a set of pairs {Xi,Xj} such that
+%%% some relation between Xi and Xj is obeyed.
+%%%
+%%% A topological sort of a partial order is a sequence of elements
+%%% [X1, X2, X3 ...] such that if whenever {Xi, Xj} is in the partial
+%%% order i &lt; j
+%%%
+%%% This particular implementation guarantees that nodes closer to
+%%% the top level of the graph will be put as close as possible to
+%%% the beginning of the resulting list - this ensures that dependencies
+%%% are started as late as possible, and top-level apps are started
+%%% as early as possible.
+%%% @end
+%%%-------------------------------------------------------------------
+-module(rlx_topo).
+
+-export([sort_apps/1,
+ format_error/1]).
+
+-include("relx.hrl").
+
+%%====================================================================
+%% API
+%%====================================================================
+
+%% @doc This only does a topo sort on the list of applications and
+%% assumes that there is only *one* version of each app in the list of
+%% applications. This implies that you have already done the
+%% constraint solve before you pass the list of apps here to be
+%% sorted.
+-spec sort_apps([rlx_app_info:t()]) ->
+ {ok, [rlx_app_info:t()]} |
+ relx:error().
+sort_apps(Apps) ->
+ AppDeps = [{rlx_app_info:name(App),
+ rlx_app_info:active_deps(App) ++ rlx_app_info:library_deps(App)}
+ || App <- Apps],
+ {AppNames, _} = lists:unzip(AppDeps),
+ case lists:foldl(fun iterator/2, {ok, [], AppDeps, []}, AppNames) of
+ {ok, Names, _, _} ->
+ {ok, names_to_apps(lists:reverse(Names), Apps)};
+ E ->
+ E
+ end.
+
+%% @doc nicely format the error from the sort.
+-spec format_error(Reason::term()) -> iolist().
+format_error({cycle, App, Path}) ->
+ ["Cycle detected in dependency graph, this must be resolved "
+ "before we can continue:\n",
+ rlx_util:indent(2),
+ [[erlang:atom_to_list(A), " -> "] || A <- lists:reverse(Path)],
+ erlang:atom_to_list(App)].
+
+%%====================================================================
+%% Internal Functions
+%%====================================================================
+
+-type name() :: AppName::atom().
+-type app_dep() :: {AppName::name(), [DepName::name()]}.
+-type iterator_state() :: {ok, [Acc::name()],
+ [Apps::app_dep()],
+ [Path::name()]}.
+
+-spec iterator(name(), iterator_state() | relx:error()) ->
+ iterator_state() | relx:error().
+iterator(App, {ok, Acc, Apps, Path}) ->
+ case lists:member(App, Acc) of
+ false ->
+ %% haven't seen this app yet
+ case lists:keytake(App, 1, Apps) of
+ {value, {App, Deps}, NewApps} ->
+ DepInit = {ok, Acc, NewApps, [App | Path]},
+ %% recurse over deps
+ case lists:foldl(fun iterator/2, DepInit, Deps) of
+ {ok, DepAcc, DepApps, _} ->
+ {ok, [App | DepAcc], DepApps, Path};
+ Error ->
+ Error
+ end;
+ false ->
+ %% we have visited this app before,
+ %% that means there's a cycle
+ ?RLX_ERROR({cycle, App, Path})
+ end;
+ true ->
+ %% this app and its deps were already processed
+ {ok, Acc, Apps, Path}
+ end;
+iterator(_, Error) ->
+ Error.
+
+-spec names_to_apps([atom()], [rlx_app_info:t()]) -> [rlx_app_info:t()].
+names_to_apps(Names, Apps) ->
+ [find_app_by_name(Name, Apps) || Name <- Names].
+
+-spec find_app_by_name(atom(), [rlx_app_info:t()]) -> rlx_app_info:t().
+find_app_by_name(Name, Apps) ->
+ {ok, App1} =
+ ec_lists:find(fun(App) ->
+ rlx_app_info:name(App) =:= Name
+ end, Apps),
+ App1.
+
+%%====================================================================
+%% Tests
+%%====================================================================
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+
+topo_apps_cycle_test() ->
+ {ok, App1} = rlx_app_info:new(app1, "0.1", "/no-dir", [app2], [stdlib]),
+ {ok, App2} = rlx_app_info:new(app2, "0.1", "/no-dir", [app1], []),
+ Apps = [App1, App2],
+ ?assertMatch({error, {_, {cycle, app1, [app2, app1]}}},
+ sort_apps(Apps)).
+
+topo_apps_good_test() ->
+ Apps = [App ||
+ {ok, App} <-
+ [rlx_app_info:new(app1, "0.1", "/no-dir", [app2, zapp1], [stdlib, kernel]),
+ rlx_app_info:new(app2, "0.1", "/no-dir", [app3], []),
+ rlx_app_info:new(app3, "0.1", "/no-dir", [kernel], []),
+ rlx_app_info:new(zapp1, "0.1", "/no-dir", [app2,app3,zapp2], []),
+ rlx_app_info:new(stdlib, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(kernel, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(zapp2, "0.1", "/no-dir", [], [])]],
+ {ok, Sorted} = sort_apps(Apps),
+ ?assertMatch([kernel, app3, app2, zapp2, zapp1, stdlib, app1],
+ [rlx_app_info:name(App) || App <- Sorted]).
+
+topo_apps_1_test() ->
+ Apps = [App ||
+ {ok, App} <-
+ [rlx_app_info:new(app0, "0.1", "/no-dir", [], [stdlib, dep1, dep2, dep3]),
+ rlx_app_info:new(app1, "0.1", "/no-dir", [], [stdlib, kernel]),
+ rlx_app_info:new(dep1, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(dep2, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(dep3, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(stdlib, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(kernel, "0.1", "/no-dir", [], [])]],
+ {ok, Sorted} = sort_apps(Apps),
+ ?assertMatch([stdlib, dep1, dep2, dep3, app0, kernel, app1],
+ [rlx_app_info:name(App) || App <- Sorted]).
+
+topo_apps_2_test() ->
+ Apps = [App ||
+ {ok, App} <-
+ [rlx_app_info:new(app1, "0.1", "/no-dir", [app2, app3, app4, app5,
+ stdlib, kernel],
+ []),
+ rlx_app_info:new(app2, "0.1", "/no-dir", [stdlib, kernel], []),
+ rlx_app_info:new(app3, "0.1", "/no-dir", [stdlib, kernel], []),
+ rlx_app_info:new(app4, "0.1", "/no-dir", [stdlib, kernel], []),
+ rlx_app_info:new(app5, "0.1", "/no-dir", [stdlib, kernel], []),
+ rlx_app_info:new(stdlib, "0.1", "/no-dir", [], []),
+ rlx_app_info:new(kernel, "0.1", "/no-dir", [], [])
+ ]],
+ {ok, Sorted} = sort_apps(Apps),
+ ?assertMatch([stdlib, kernel, app2,
+ app3, app4, app5, app1],
+ [rlx_app_info:name(App) || App <- Sorted]).
+
+-endif.
diff --git a/test/rlx_archive_SUITE.erl b/test/rlx_archive_SUITE.erl
index 08da2b8..5122c11 100644
--- a/test/rlx_archive_SUITE.erl
+++ b/test/rlx_archive_SUITE.erl
@@ -249,6 +249,8 @@ overlay_archive(Config) ->
TestDirFull = filename:join([LibDir1, TestDir]),
TestFileFull = filename:join(TestDirFull, TestFile),
SecondTestDir = "second_test_dir",
+ TestScript = "test_script",
+ TestScript2 = "test_script2",
rlx_test_utils:write_config(ConfigFile,
[{overlay_vars, [OverlayVars1, OverlayVars2]},
{overlay, [{mkdir, "{{target_dir}}/fooo"},
@@ -260,9 +262,17 @@ overlay_archive(Config) ->
"{{target_dir}}/{{yahoo}}/vars.link.config"},
{copy, TestDirFull,
"{{target_dir}}/"++SecondTestDir++"/"},
+ {copy, TestScript,
+ "{{target_dir}}/"++SecondTestDir++"/"++TestScript},
+ {chmod, 8#00700,
+ "{{target_dir}}/"++SecondTestDir++"/"++TestScript},
+ {copy, TestScript2,
+ "{{target_dir}}/"++SecondTestDir++"/"++TestScript2},
+ {chmod, "{{test_script_perm}}",
+ "{{target_dir}}/"++SecondTestDir++"/"++TestScript2},
{template, Template,
"{{target_dir}}/test_template_resolved"},
- {template, Template,
+ {template, Template,
"bin/{{default_release_name}}-{{default_release_version}}"}]},
{release, {foo, "0.0.1"},
[goal_app_1,
@@ -272,7 +282,8 @@ overlay_archive(Config) ->
rlx_test_utils:write_config(VarsFile1, [{yahoo, "yahoo"},
{yahoo2, [{foo, "bar"}]},
{foo_yahoo, "foo_{{yahoo}}"},
- {foo_dir, "foodir"}]),
+ {foo_dir, "foodir"},
+ {test_script_perm,8#00770}]),
VarsFile2 = filename:join([LibDir1, "vars2.config"]),
rlx_test_utils:write_config(VarsFile2, [{google, "yahoo"},
@@ -283,6 +294,11 @@ overlay_archive(Config) ->
rlx_test_utils:write_config(VarsFile3, [{google, "yahoo"},
{yahoo4, "{{yahoo}}/{{yahoo2}}4"}]),
+ TestScriptFile = filename:join([LibDir1,TestScript]),
+ ok = file:write_file(TestScriptFile, <<"#!/bin/sh\necho \"hello world\"">>),
+ TestScriptFile2 = filename:join([LibDir1,TestScript2]),
+ ok = file:write_file(TestScriptFile2, <<"#!/bin/sh\necho \"hello world 2\"">>),
+
ok = rlx_util:mkdir_p(TestDirFull),
ok = file:write_file(TestFileFull, rlx_test_utils:test_template_contents()),
@@ -312,6 +328,19 @@ overlay_archive(Config) ->
?assert(lists:member({goal_app_2, "0.0.1"}, AppSpecs)),
?assert(lists:member({lib_dep_1, "0.0.1", load}, AppSpecs)),
+ % check that the chmod of our file worked
+ ChmodedFile = filename:join([OutputDir,"foo",SecondTestDir,TestScript]),
+ {ok, ChmodedInfo} = file:read_file_info (ChmodedFile),
+ % mode from file_info is a bitmask which might have other bits set, but
+ % if we mask those we care about and check we should get true, see details
+ % here http://stackoverflow.com/questions/13183838/how-to-use-erlang-fileread-file-info-permissions-mode-info
+ ?assert(ChmodedInfo#file_info.mode band 8#00700 =:= 8#00700),
+
+ % check that the templated chmod of our file worked
+ ChmodedFile2 = filename:join([OutputDir,"foo",SecondTestDir,TestScript2]),
+ {ok, ChmodedInfo2} = file:read_file_info (ChmodedFile2),
+ ?assert(ChmodedInfo2#file_info.mode band 8#00770 =:= 8#00770),
+
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-.*/src/.*") =/= nomatch end, Files)),
diff --git a/test/rlx_eunit_SUITE.erl b/test/rlx_eunit_SUITE.erl
index d429f36..874e5a6 100644
--- a/test/rlx_eunit_SUITE.erl
+++ b/test/rlx_eunit_SUITE.erl
@@ -23,7 +23,8 @@
all/0,
depsolver/1,
goal/1,
- app_info/1]).
+ app_info/1,
+ topo/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -38,7 +39,7 @@ end_per_suite(_Config) ->
ok.
all() ->
- [depsolver, goal, app_info].
+ [depsolver, goal, app_info, topo].
depsolver(_Config) ->
ok = eunit:test(rlx_depsolver).
@@ -48,3 +49,6 @@ goal(_Config) ->
app_info(_Config) ->
ok = eunit:test(rlx_app_info).
+
+topo(_Config) ->
+ ok = eunit:test(rlx_topo).
diff --git a/test/rlx_extended_bin_SUITE.erl b/test/rlx_extended_bin_SUITE.erl
index ce72437..c2e6bc2 100644
--- a/test/rlx_extended_bin_SUITE.erl
+++ b/test/rlx_extended_bin_SUITE.erl
@@ -22,13 +22,34 @@
end_per_suite/1,
init_per_testcase/2,
all/0,
+ start_sname_in_other_argsfile/1,
+ start_fail_when_no_name/1,
+ start_fail_when_multiple_names/1,
+ start_fail_when_missing_argsfile/1,
+ start_fail_when_nonreadable_argsfile/1,
+ start_fail_when_relative_argsfile/1,
+ start_fail_when_circular_argsfiles/1,
ping/1,
+ shortname_ping/1,
+ longname_ping/1,
attach/1,
pid/1,
restart/1,
reboot/1,
escript/1,
- remote_console/1]).
+ remote_console/1,
+ replace_os_vars/1,
+ replace_os_vars_multi_node/1,
+ replace_os_vars_included_config/1,
+ replace_os_vars_custom_location/1,
+ replace_os_vars_dev_mode/1,
+ replace_os_vars_twice/1,
+ custom_start_script_hooks/1,
+ builtin_wait_for_vm_start_script_hook/1,
+ builtin_pid_start_script_hook/1,
+ builtin_wait_for_process_start_script_hook/1,
+ mixed_custom_and_builtin_start_script_hooks/1,
+ builtin_status_script/1, custom_status_script/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -53,8 +74,15 @@ init_per_testcase(_, Config) ->
{state, State1} | Config].
all() ->
- [ping, attach, pid, restart, reboot, escript,
- remote_console].
+ [start_sname_in_other_argsfile, start_fail_when_no_name, start_fail_when_multiple_names,
+ start_fail_when_missing_argsfile, start_fail_when_nonreadable_argsfile,
+ start_fail_when_relative_argsfile, start_fail_when_circular_argsfiles,
+ ping, shortname_ping, longname_ping, attach, pid, restart, reboot, escript,
+ remote_console, replace_os_vars, replace_os_vars_multi_node, replace_os_vars_included_config,
+ replace_os_vars_custom_location, replace_os_vars_dev_mode, replace_os_vars_twice, custom_start_script_hooks,
+ builtin_wait_for_vm_start_script_hook, builtin_pid_start_script_hook,
+ builtin_wait_for_process_start_script_hook, mixed_custom_and_builtin_start_script_hooks,
+ builtin_status_script, custom_status_script].
ping(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -87,7 +115,85 @@ ping(Config) ->
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
%% a ping should fail after stopping a node
- {error, 1} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+
+shortname_ping(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ ec_file:write(VmArgs, "-sname foo\n\n"
+ "-setcookie cookie\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ %% now start/stop the release to make sure the extended script is working
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ %% a ping should fail after stopping a node
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+
+longname_ping(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-setcookie cookie\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ %% now start/stop the release to make sure the extended script is working
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ %% a ping should fail after stopping a node
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
attach(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -121,7 +227,7 @@ attach(Config) ->
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo attach", "&"])),
timer:sleep(2000),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
- {error, 1} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
pid(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -154,7 +260,7 @@ pid(Config) ->
{ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
{ok, _Pid} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
- {error, 1} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
restart(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -191,7 +297,7 @@ restart(Config) ->
timer:sleep(2000),
{ok, Pid2} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
- {error, 1} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
?assertEqual(Pid1, Pid2).
reboot(Config) ->
@@ -231,7 +337,7 @@ reboot(Config) ->
timer:sleep(2000),
{ok, Pid2} = sh(filename:join([OutputDir, "foo", "bin", "foo pid"])),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
- {error, 1} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
?assertNotEqual(Pid1, Pid2).
escript(Config) ->
@@ -307,6 +413,1118 @@ remote_console(Config) ->
?assertEqual(1, length(Nodes)),
{ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])).
+replace_os_vars(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [{var1, "${VAR1}"}]}]]),
+ ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
+ "-setcookie ${COOKIE}\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, _Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+
+ %% start the node again but this time with different env variables to replace
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"},
+ {"VAR1", "v2"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, _Node2} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ ok.
+
+replace_os_vars_multi_node(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [{var1, "${VAR1}"}]}]]),
+ ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
+ "-setcookie ${COOKIE}\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys." ++
+ rlx_test_utils:unescape_string(Node1) ++
+ ".config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+
+ %% start the node again but this time with different env variables to replace
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"},
+ {"VAR1", "v2"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, Node2} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys." ++
+ rlx_test_utils:unescape_string(Node2) ++
+ ".config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_MULTI_NODE", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ ok.
+
+replace_os_vars_included_config(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ IncludedConfig = filename:join([LibDir1, "config", "included.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {overlay, [
+ {mkdir, "releases/{{release_version}}/config"},
+ {template, "config/included.config", "releases/{{release_version}}/config/included.config"}
+ ]}
+ ]),
+
+ rlx_test_utils:write_config(IncludedConfig,
+ [[{goal_app, [
+ {var1_included, "${VAR1}"}]
+ }]
+ ]),
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [
+ {var1, "${VAR1}"}]
+ },
+ "releases/0.0.1/config/included.config"]
+ ]),
+ ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
+ "-setcookie ${COOKIE}\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, _Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+
+ %% start the node again but this time with different env variables to replace
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"},
+ {"VAR1", "v2"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, _Node2} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ ok.
+
+replace_os_vars_custom_location(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [{var1, "${VAR1}"}]}]]),
+ ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
+ "-setcookie ${COOKIE}\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, _Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join(["/", "tmp",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+
+ %% start the node again but this time with different env variables to replace
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"},
+ {"VAR1", "v2"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, _Node2} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join(["/", "tmp",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"RELX_OUT_FILE_PATH", "/tmp"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ ok.
+
+replace_os_vars_twice(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [{var1, "${VAR1}"}]}]]),
+ ec_file:write(VmArgs, "-sname node\n\n"
+ "-setcookie cookie\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+ {ok, "\"node\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+ {ok, "cookie"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+ {ok, _Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"}]),
+
+ %% start the node again but this time don't replace env variables
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {ok, "\"${VAR1}\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"])),
+ {ok, "\"node\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"])),
+ {ok, "cookie"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"])),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ ok.
+
+replace_os_vars_dev_mode(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ SysConfig = filename:join([LibDir1, "sys.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {dev_mode, true},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ rlx_test_utils:write_config(SysConfig,
+ [[{goal_app, [{var1, "${VAR1}"}]}]]),
+ ec_file:write(VmArgs, "-sname ${NODENAME}\n\n"
+ "-setcookie ${COOKIE}\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"},
+ {"VAR1", "v1"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"v1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "\"node1\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, "cookie1"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ {ok, _Node1} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node1"},
+ {"COOKIE", "cookie1"}]),
+
+ %% start the node again but this time with different env variables to replace
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"},
+ {"VAR1", "v2"}]),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"v2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '{ok, V} = application:get_env(goal_app, var1), V.'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "\"node2\""} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval '[Node,_] = re:split(atom_to_list(node()), \"@\"),binary_to_list(Node).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, "cookie2"} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'erlang:get_cookie().'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ {ok, _Node2} = sh(filename:join([OutputDir, "foo", "bin",
+ "foo eval 'atom_to_list(node()).'"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ %% check that the replaced files have been created in the right place
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"]),
+ [{"RELX_REPLACE_OS_VARS", "1"},
+ {"NODENAME", "node2"},
+ {"COOKIE", "cookie2"}]),
+ ok.
+
+custom_start_script_hooks(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {pre_start, [
+ {custom, "hooks/pre_start"}
+ ]},
+ {post_start, [
+ {custom, "hooks/post_start"}
+ ]},
+ {pre_stop, [
+ {custom, "hooks/pre_stop"}
+ ]},
+ {post_stop, [
+ {custom, "hooks/post_stop"}
+ ]}
+ ]},
+ {mkdir, "scripts"},
+ {overlay, [{copy, "./pre_start", "bin/hooks/pre_start"},
+ {copy, "./post_start", "bin/hooks/post_start"},
+ {copy, "./pre_stop", "bin/hooks/pre_stop"},
+ {copy, "./post_stop", "bin/hooks/post_stop"}]}
+ ]),
+
+ %% write the hook scripts, each of them will write an erlang term to a file
+ %% that will later be consulted
+ ok = file:write_file(filename:join([LibDir1, "./pre_start"]),
+ "#!/bin/bash\n# $*\necho \\{pre_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./post_start"]),
+ "#!/bin/bash\n# $*\necho \\{post_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./pre_stop"]),
+ "#!/bin/bash\n# $*\necho \\{pre_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./post_stop"]),
+ "#!/bin/bash\n# $*\necho \\{post_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ %% now start/stop the release to make sure the script hooks are really getting
+ %% executed
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ %% now check that the output file contains the expected format
+ {ok,[{pre_start, foo, _, foo},
+ {post_start, foo, _, foo},
+ {pre_stop, foo, _, foo},
+ {post_stop, foo, _, foo}]} = file:consult(filename:join([OutputDir, "foo", "test"])).
+
+builtin_pid_start_script_hook(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {post_start, [
+ {pid, filename:join([OutputDir, "foo.pid"])}
+ ]}
+ ]}
+ ]),
+
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ %% now start/stop the release to make sure the script hooks are really getting
+ %% executed
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ %% check that the pid file really was created
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo.pid"]))),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ ok.
+
+builtin_wait_for_vm_start_script_hook(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {post_start, [wait_for_vm_start]}
+ ]}
+ ]),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ %% now start/stop the release to make sure the script hooks are really getting
+ %% executed
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ % this run doesn't need the sleep because the wait_for_vm_start
+ % start script makes it unnecessary
+ %timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ ok.
+
+builtin_wait_for_process_start_script_hook(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
+ [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {post_start, [wait_for_vm_start,
+ {wait_for_process, goal_app_srv_signal}]}
+ ]}
+ ]),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ %% now start/stop the release to make sure the script hooks are really getting
+ %% executed
+ %% get the current time, we'll measure how long it took for the node to
+ %% start, it must be at least 3 seconds which is the time it takes the
+ %% goal_app_srv to register the signal
+ T1 = os:timestamp(),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ T2 = timer:now_diff(os:timestamp(), T1),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ ?assert((T2 div 1000) > 3000),
+ ok.
+
+mixed_custom_and_builtin_start_script_hooks(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
+ [stdlib,kernel], []),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {pre_start, [
+ {custom, "hooks/pre_start"}
+ ]},
+ {post_start, [
+ wait_for_vm_start,
+ {pid, filename:join([OutputDir, "foo.pid"])},
+ {wait_for_process, goal_app_srv_signal},
+ {custom, "hooks/post_start"}
+ ]},
+ {pre_stop, [
+ {custom, "hooks/pre_stop"}
+ ]},
+ {post_stop, [
+ {custom, "hooks/post_stop"}
+ ]}
+ ]},
+ {mkdir, "scripts"},
+ {overlay, [{copy, "./pre_start", "bin/hooks/pre_start"},
+ {copy, "./post_start", "bin/hooks/post_start"},
+ {copy, "./pre_stop", "bin/hooks/pre_stop"},
+ {copy, "./post_stop", "bin/hooks/post_stop"}]}
+ ]),
+
+ %% write the hook scripts, each of them will write an erlang term to a file
+ %% that will later be consulted
+ ok = file:write_file(filename:join([LibDir1, "./pre_start"]),
+ "#!/bin/bash\n# $*\necho \\{pre_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./post_start"]),
+ "#!/bin/bash\n# $*\necho \\{post_start, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./pre_stop"]),
+ "#!/bin/bash\n# $*\necho \\{pre_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+ ok = file:write_file(filename:join([LibDir1, "./post_stop"]),
+ "#!/bin/bash\n# $*\necho \\{post_stop, $REL_NAME, \\'$NAME\\', $COOKIE\\}. >> test"),
+
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ %% now start/stop the release to make sure the script hooks are really getting
+ %% executed
+ %% get the current time, we'll measure how long it took for the node to
+ %% start, it must be at least 3 seconds which is the time it takes the
+ %% goal_app_srv to register the signal
+ T1 = os:timestamp(),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ % this run doesn't need the sleep because the wait_for_vm_start
+ % start script makes it unnecessary
+ T2 = timer:now_diff(os:timestamp(), T1),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ ?assert((T2 div 1000) > 3000),
+ %% check that the pid file really was created
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo.pid"]))),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ %% now check that the output file contains the expected format
+ {ok,[{pre_start, foo, _, foo},
+ {post_start, foo, _, foo},
+ {pre_stop, foo, _, foo},
+ {post_stop, foo, _, foo}]} = file:consult(filename:join([OutputDir, "foo", "test"])).
+
+builtin_status_script(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
+ [stdlib,kernel], []),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ %% write the status to a file
+ {ok, ""} = sh(filename:join([OutputDir, "foo", "bin", "foo status"])).
+
+custom_status_script(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_full_app(LibDir1, "goal_app", "0.0.1",
+ [stdlib,kernel], []),
+
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ rlx_test_utils:create_random_name("relx-output")]),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {generate_start_script, true},
+ {extended_start_script, true},
+ {extended_start_script_hooks, [
+ {status, [
+ {custom, "hooks/status"}
+ ]}
+ ]},
+ {overlay, [
+ {copy, "./status", "bin/hooks/status"}]}
+ ]),
+
+ %% write the hook status script
+ ok = file:write_file(filename:join([LibDir1, "./status"]),
+ "#!/bin/bash\n# $*\necho \\{status, $REL_NAME, \\'$NAME\\', $COOKIE\\}."),
+
+ {ok, _State} = relx:do(foo, undefined, [], [LibDir1], 3,
+ OutputDir, ConfigFile),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ %% write the status to a file
+ {ok, StatusStr} = sh(filename:join([OutputDir, "foo", "bin", "foo status"])),
+ ec_file:write(filename:join([OutputDir, "status.txt"]), StatusStr),
+ os:cmd(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ {ok, [Status]} = file:consult(filename:join([OutputDir, "status.txt"])),
+ {ok, {status, foo, _, foo} = Status}.
+
+start_sname_in_other_argsfile(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ VmArgs2 = VmArgs ++ ".2",
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ ec_file:write(VmArgs, "-args_file " ++ VmArgs2 ++ "\n\n"
+ "-setcookie cookie\n"),
+
+ ec_file:write(VmArgs2, "-sname foo\n"),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ %% now start/stop the release to make sure the extended script is working
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])),
+ timer:sleep(2000),
+ {ok, "pong"} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])),
+ {ok, _} = sh(filename:join([OutputDir, "foo", "bin", "foo stop"])),
+ %% a ping should fail after stopping a node
+ {error, 1, _} = sh(filename:join([OutputDir, "foo", "bin", "foo ping"])).
+
+start_fail_when_no_name(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ ec_file:write(VmArgs, "-setcookie cookie\n"),
+ start_fail_with_vmargs(Config, VmArgs, 1).
+
+start_fail_when_multiple_names(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-name bar\n\n"
+ "-setcookie cookie\n"),
+ start_fail_with_vmargs(Config, VmArgs, 2).
+
+start_fail_when_missing_argsfile(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-args_file " ++ VmArgs ++ ".nonexistent\n\n"
+ "-setcookie cookie\n"),
+ start_fail_with_vmargs(Config, VmArgs, 3).
+
+start_fail_when_nonreadable_argsfile(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ VmArgs2 = VmArgs ++ ".nonreadable",
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-args_file " ++ VmArgs2 ++ "\n\n"
+ "-setcookie cookie\n"),
+ ec_file:write(VmArgs2, ""),
+ file:change_mode(VmArgs2, 8#00333),
+ start_fail_with_vmargs(Config, VmArgs, 3).
+
+start_fail_when_relative_argsfile(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-args_file vm.args.relative\n\n"
+ "-setcookie cookie\n"),
+ start_fail_with_vmargs(Config, VmArgs, 4).
+
+start_fail_when_circular_argsfiles(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+ VmArgs = filename:join([LibDir1, "vm.args"]),
+ VmArgs2 = VmArgs ++ ".2",
+ VmArgs3 = VmArgs ++ ".3",
+ ec_file:write(VmArgs, "-name foo\n\n"
+ "-args_file " ++ VmArgs2 ++ "\n\n"
+ "-setcookie cookie\n"),
+ ec_file:write(VmArgs2, "-args_file " ++ VmArgs3 ++ "\n"),
+ ec_file:write(VmArgs3, "-args_file " ++ VmArgs2 ++ "\n"),
+ start_fail_with_vmargs(Config, VmArgs, 5).
+
+%%-------------------------------------------------------------------
+%% Helper Function for start_fail_when_* tests
+%%-------------------------------------------------------------------
+start_fail_with_vmargs(Config, VmArgs, ExpectedCode) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ rlx_test_utils:create_app(LibDir1, "goal_app", "0.0.1", [stdlib,kernel], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app]},
+ {lib_dirs, [filename:join(LibDir1, "*")]},
+ {vm_args, VmArgs},
+ {generate_start_script, true},
+ {extended_start_script, true}
+ ]),
+
+ OutputDir = filename:join([proplists:get_value(priv_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"]),
+
+ %% now start/stop the release to make sure the extended script is working
+ {error, ExpectedCode, _} = sh(filename:join([OutputDir, "foo", "bin", "foo start"])).
+
%%%===================================================================
%%% Helper Functions
%%%===================================================================
@@ -327,8 +1545,8 @@ sh(Command, Env, Dir) ->
case sh_loop(Port) of
{ok, Ret} ->
{ok, Ret};
- {error, Rc} ->
- {error, Rc}
+ {error, Rc, Msg} ->
+ {error, Rc, Msg}
end.
sh_loop(Port) ->
@@ -341,7 +1559,7 @@ sh_loop(Port, Acc) ->
{Port, {exit_status, 0}} ->
{ok, Acc};
{Port, {exit_status, Rc}} ->
- {error, Rc}
+ {error, Rc, Acc}
end.
get_cwd() ->
diff --git a/test/rlx_release_SUITE.erl b/test/rlx_release_SUITE.erl
index 2b30923..c9430fd 100644
--- a/test/rlx_release_SUITE.erl
+++ b/test/rlx_release_SUITE.erl
@@ -46,12 +46,17 @@
make_relup_release2/1,
make_one_app_top_level_release/1,
make_dev_mode_release/1,
+ make_dev_mode_template_release/1,
make_config_script_release/1,
make_release_twice/1,
make_release_twice_dev_mode/1,
make_erts_release/1,
make_erts_config_release/1,
- make_included_nodetool_release/1]).
+ make_included_nodetool_release/1,
+ make_not_included_nodetool_release/1,
+ make_src_release/1,
+ make_excluded_src_release/1,
+ make_exclude_modules_release/1]).
-include_lib("common_test/include/ct.hrl").
-include_lib("eunit/include/eunit.hrl").
@@ -83,9 +88,11 @@ all() ->
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_dev_mode_template_release,
make_config_script_release, make_release_twice, make_release_twice_dev_mode,
- make_erts_release, make_erts_config_release, make_included_nodetool_release].
+ make_erts_release, make_erts_config_release,
+ make_included_nodetool_release, make_not_included_nodetool_release,
+ make_src_release, make_excluded_src_release, make_exclude_modules_release].
add_providers(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
@@ -1007,9 +1014,9 @@ make_dev_mode_release(Config) ->
?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "goal_app_2-0.0.1"]))),
?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "lib_dep_1-0.0.1"]))),
?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "releases", "0.0.1",
- "sys.config.orig"]))),
+ "sys.config"]))),
?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "releases", "0.0.1",
- "vm.args.orig"])));
+ "vm.args"])));
{win32, _} ->
?assert(filelib:is_dir(filename:join([OutputDir, "foo", "lib", "non_goal_1-0.0.1"]))),
?assert(filelib:is_dir(filename:join([OutputDir, "foo", "lib", "non_goal_2-0.0.1"]))),
@@ -1022,6 +1029,66 @@ make_dev_mode_release(Config) ->
"vm.args"])))
end.
+make_dev_mode_template_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, "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"]),
+ SysConfigTerm = [{this_is_a_test, "yup it is"},
+ {this_is_an_overlay_var, "{{var1}}"}],
+ rlx_test_utils:write_config(SysConfig, SysConfigTerm),
+
+ VmArgs = filename:join([LibDir1, "config", "vm.args"]),
+ ec_file:write(VmArgs, "-sname {{nodename}}"),
+
+ VarsFile1 = filename:join([LibDir1, "config", "vars1.config"]),
+ rlx_test_utils:write_config(VarsFile1, [{var1, "indeed it is"},
+ {nodename, "testnode"}]),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{dev_mode, true},
+ {sys_config, SysConfig},
+ {vm_args, VmArgs},
+ {overlay_vars, [VarsFile1]},
+ {overlay, [
+ {template, "config/sys.config",
+ "releases/{{release_version}}/sys.config"},
+ {template, "config/vm.args",
+ "releases/{{release_version}}/vm.args"}]},
+ {release, {foo, "0.0.1"},
+ [goal_app_1,
+ goal_app_2]}]),
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ 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)),
+
+ ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "non_goal_1-0.0.1"]))),
+ ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "non_goal_2-0.0.1"]))),
+ ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "goal_app_1-0.0.1"]))),
+ ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "goal_app_2-0.0.1"]))),
+ ?assert(ec_file:is_symlink(filename:join([OutputDir, "foo", "lib", "lib_dep_1-0.0.1"]))),
+ ?assert(not ec_file:is_symlink(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "sys.config"]))),
+ ?assert(not ec_file:is_symlink(filename:join([OutputDir, "foo", "releases", "0.0.1",
+ "vm.args"]))),
+ %% ensure that the original sys.config didn't get overwritten
+ ?assertMatch({ok, SysConfigTerm}, file:consult(SysConfig)),
+ %% ensure that the original vm.args didn't get overwritten
+ ?assertMatch({ok, <<"-sname {{nodename}}">>}, ec_file:read(VmArgs)).
+
make_config_script_release(Config) ->
LibDir1 = proplists:get_value(lib1, Config),
FooRoot = filename:join([LibDir1, "foodir1", "foodir2"]),
@@ -1243,10 +1310,138 @@ make_included_nodetool_release(Config) ->
OutputDir, ConfigFile),
[{{foo, "0.0.1"}, Release}] = ec_dictionary:to_list(rlx_state:realized_releases(State)),
AppSpecs = rlx_release:applications(Release),
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "bin", "nodetool"]))),
+ ?assert(lists:keymember(stdlib, 1, AppSpecs)),
+ ?assert(lists:keymember(kernel, 1, AppSpecs)),
+ ?assertEqual(ErtsVsn, rlx_release:erts(Release)).
+
+make_not_included_nodetool_release(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ 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], []),
+
+ ErtsVsn = erlang:system_info(version),
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"}, {erts, ErtsVsn},
+ [goal_app_1]},
+ {extended_start_script, true},
+ {include_nodetool, false}]),
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ 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)),
+ AppSpecs = rlx_release:applications(Release),
+ %% extended start script needs nodetool to work, so the
+ %% {include_nodetool, false} option is simply ignored
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "bin", "nodetool"]))),
?assert(lists:keymember(stdlib, 1, AppSpecs)),
?assert(lists:keymember(kernel, 1, AppSpecs)),
?assertEqual(ErtsVsn, rlx_release:erts(Release)).
+make_src_release(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ 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], []),
+
+ ErtsVsn = erlang:system_info(version),
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app_1]},
+ {extended_start_script, true},
+ {include_erts, true},
+ {include_src, true}]),
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ 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)),
+ AppSpecs = rlx_release:applications(Release),
+ ?assert(lists:keymember(stdlib, 1, AppSpecs)),
+ ?assert(lists:keymember(kernel, 1, AppSpecs)),
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "erts-"++ErtsVsn, "src"]))),
+ ?assert(ec_file:exists(filename:join([OutputDir, "foo", "lib",
+ "goal_app_1-0.0.1", "src"]))).
+
+make_excluded_src_release(Config) ->
+ LibDir1 = proplists:get_value(lib1, Config),
+
+ 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], []),
+
+ ErtsVsn = erlang:system_info(version),
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app_1]},
+ {extended_start_script, true},
+ {include_erts, true},
+ {include_src, false}]),
+ OutputDir = filename:join([proplists:get_value(priv_dir, Config),
+ 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)),
+ AppSpecs = rlx_release:applications(Release),
+ ?assert(lists:keymember(stdlib, 1, AppSpecs)),
+ ?assert(lists:keymember(kernel, 1, AppSpecs)),
+ ?assert(not ec_file:exists(filename:join([OutputDir, "foo", "erts-"++ErtsVsn, "src"]))),
+ ?assert(not ec_file:exists(filename:join([OutputDir, "foo", "lib",
+ "goal_app_1-0.0.1", "src"]))).
+
+%% Test to ensure that excluded modules don't end up in the release
+make_exclude_modules_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], []),
+
+ ConfigFile = filename:join([LibDir1, "relx.config"]),
+ rlx_test_utils:write_config(ConfigFile,
+ [{release, {foo, "0.0.1"},
+ [goal_app_1]},
+ {exclude_modules, [{non_goal_1, [a_real_beamnon_goal_1]}]}]),
+ OutputDir = filename:join([proplists:get_value(priv_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)),
+ ?assert(lists:member({goal_app_1, "0.0.1"}, AppSpecs)),
+ %% ensure that the excluded module beam file didn't get copied
+ ?assert(not ec_file:exists(filename:join([OutputDir, "foo", "lib",
+ "non_goal_1-0.0.1", "ebin",
+ "a_real_beamnon_goal_1.beam"]))),
+
+ ?assertMatch({ok, [{application,non_goal_1,
+ [{description,[]},
+ {vsn,"0.0.1"},
+ {modules,[]},
+ {included_applications,[]},
+ {registered,[]},
+ {applications,[stdlib,kernel]}]}]},
+ file:consult(filename:join([OutputDir, "foo", "lib",
+ "non_goal_1-0.0.1", "ebin",
+ "non_goal_1.app"]))).
+
+
%%%===================================================================
%%% Helper Functions
%%%===================================================================
diff --git a/test/rlx_test_utils.erl b/test/rlx_test_utils.erl
index 2e6045f..37d9c7a 100644
--- a/test/rlx_test_utils.erl
+++ b/test/rlx_test_utils.erl
@@ -6,21 +6,33 @@
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),
+ write_app_file(AppDir, Name, Vsn, app_modules(Name), Deps, LibDeps),
+ write_src_file(AppDir, Name),
+ compile_src_files(AppDir),
+ rlx_app_info:new(erlang:list_to_atom(Name), Vsn, AppDir,
+ Deps, []).
+
+create_full_app(Dir, Name, Vsn, Deps, LibDeps) ->
+ AppDir = filename:join([Dir, Name ++ "-" ++ Vsn]),
+ write_full_app_files(AppDir, Name, Vsn, Deps, LibDeps),
+ compile_src_files(AppDir),
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),
+ 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).
+app_modules(Name) ->
+ [list_to_atom(M ++ Name) ||
+ M <- ["a_real_beam"]].
+
+write_src_file(Dir, Name) ->
+ Src = filename:join([Dir, "src", "a_real_beam" ++ Name ++ ".erl"]),
+ ok = filelib:ensure_dir(Src),
+ ok = file:write_file(Src, beam_file_contents("a_real_beam"++Name)).
write_appup_file(AppInfo, DownVsn) ->
Dir = rlx_app_info:dir(AppInfo),
@@ -30,20 +42,112 @@ write_appup_file(AppInfo, DownVsn) ->
ok = filelib:ensure_dir(Filename),
ok = ec_file:write_term(Filename, {Vsn, [{DownVsn, []}], [{DownVsn, []}]}).
-write_app_file(Dir, Name, Version, Deps, LibDeps) ->
+write_app_file(Dir, Name, Version, Modules, 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)).
+ ok = ec_file:write_term(Filename, get_app_metadata(Name, Version, Modules,
+ Deps, LibDeps)).
+
+compile_src_files(Dir) ->
+ %% compile all *.erl files in src to ebin
+ SrcDir = filename:join([Dir, "src"]),
+ OutputDir = filename:join([Dir, "ebin"]),
+ lists:foreach(fun(SrcFile) ->
+ {ok, _} = compile:file(SrcFile, [{outdir, OutputDir},
+ return_errors])
+ end, ec_file:find(SrcDir, "\\.erl")),
+ ok.
-get_app_metadata(Name, Vsn, Deps, LibDeps) ->
+get_app_metadata(Name, Vsn, Modules, Deps, LibDeps) ->
{application, erlang:list_to_atom(Name),
[{description, ""},
{vsn, Vsn},
- {modules, []},
+ {modules, Modules},
{included_applications, LibDeps},
{registered, []},
{applications, Deps}]}.
+write_full_app_files(Dir, Name, Vsn, Deps, LibDeps) ->
+ %% write out the .app file
+ AppFilename = filename:join([Dir, "ebin", Name ++ ".app"]),
+ ok = filelib:ensure_dir(AppFilename),
+ ok = ec_file:write_term(AppFilename,
+ get_full_app_metadata(Name, Vsn, Deps, LibDeps)),
+ %% write out the _app.erl file
+ ApplicationFilename = filename:join([Dir, "src", Name ++ "_app.erl"]),
+ ok = filelib:ensure_dir(ApplicationFilename),
+ ok = file:write_file(ApplicationFilename, full_application_contents(Name)),
+ %% write out the supervisor
+ SupervisorFilename = filename:join([Dir, "src", Name ++ "_sup.erl"]),
+ ok = filelib:ensure_dir(SupervisorFilename),
+ ok = file:write_file(SupervisorFilename, supervisor_contents(Name)),
+ %% and finally the gen_server
+ GenServerFilename = filename:join([Dir, "src", Name ++ "_srv.erl"]),
+ ok = filelib:ensure_dir(GenServerFilename),
+ ok = file:write_file(GenServerFilename, gen_server_contents(Name)),
+ ok.
+
+get_full_app_metadata(Name, Vsn, Deps, LibDeps) ->
+ {application, erlang:list_to_atom(Name),
+ [{description, ""},
+ {vsn, Vsn},
+ {modules, [goal_app_app,goal_app_sup,goal_app_srv]},
+ {mod, {erlang:list_to_atom(Name ++ "_app"),
+ []}},
+ {included_applications, LibDeps},
+ {registered, []},
+ {applications, Deps}]}.
+
+full_application_contents(Name) ->
+ "-module("++Name++"_app).\n"
+ "-behaviour(application).\n"
+ "-export([start/2, stop/1]).\n"
+ "start(_StartType, _StartArgs) ->\n"
+ " "++Name++"_sup:start_link().\n"
+ "stop(_State) ->\n"
+ " ok.\n".
+
+supervisor_contents(Name) ->
+ "-module("++Name++"_sup).\n"
+ "-behaviour(supervisor).\n"
+ "-export([start_link/0]).\n"
+ "-export([init/1]).\n"
+ "-define(SERVER, ?MODULE).\n"
+ "start_link() ->\n"
+ " supervisor:start_link({local, ?SERVER}, ?MODULE, []).\n"
+ "init([]) ->\n"
+ " {ok, { {one_for_all, 0, 1},\n"
+ " [{"++Name++"_srv, {"++Name++"_srv, start_link, []},\n"
+ " transient, 5000, worker, ["++Name++"_srv]}\n"
+ " ]\n"
+ " }}.\n".
+
+gen_server_contents(Name) ->
+ "-module("++Name++"_srv).\n"
+ "-behaviour(gen_server).\n"
+ "-record(state, {}).\n"
+ "-export([start_link/0]).\n"
+ "-export([init/1,handle_call/3,handle_cast/2,\n"
+ " handle_info/2,terminate/2,code_change/3]).\n"
+ "start_link() ->\n"
+ " gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).\n"
+ "init([]) ->\n"
+ " erlang:send_after(4000, self(), register_signal),"
+ " {ok, #state{}}.\n"
+ "handle_call(_Event, _From, State) ->\n"
+ " {reply, ok, State}.\n"
+ "handle_cast(_Event, State) ->\n"
+ " {noreply, State}.\n"
+ "handle_info(register_signal, State) ->\n"
+ " erlang:register(goal_app_srv_signal, spawn(fun() -> timer:sleep(200000) end)),\n"
+ " {noreply, State};\n"
+ "handle_info(_Info, State) ->\n"
+ " {noreply, State}.\n"
+ "terminate(_Reason, _State) ->\n"
+ " ok.\n"
+ "code_change(_OldVsn, State, _Extra) ->\n"
+ " {ok, State}.\n".
+
create_random_name(Name) ->
Name ++ erlang:integer_to_list(random_uniform(1000000)).
@@ -57,6 +161,9 @@ write_config(Filename, Values) ->
ok = ec_file:write(Filename,
[io_lib:format("~p.\n", [Val]) || Val <- Values]).
+beam_file_contents(Name) ->
+ "-module("++Name++").".
+
test_template_contents() ->
"{erts_vsn, \"{{erts_vsn}}\"}.\n"
"{release_erts_version, \"{{release_erts_version}}\"}.\n"
@@ -104,3 +211,7 @@ list_to_term(String) ->
{error, Error} ->
Error
end.
+
+unescape_string(String) ->
+ re:replace(String, "\"", "",
+ [global, {return, list}]).