summaryrefslogtreecommitdiff
path: root/deps
diff options
context:
space:
mode:
authorUlf Wiger <ulf@wiger.net>2015-05-26 20:43:37 +0200
committerUlf Wiger <ulf@feuerlabs.com>2015-06-10 11:28:38 +0200
commitbd4dd9aeec5da35af21b2c996b05a9618ece568d (patch)
treee31087ea2e63b5e5e16635f6977dc6d656b17714 /deps
parent179fbae4c5bc3fa1da7ff6515d0b295fc5de825c (diff)
downloadrvi_core-bd4dd9aeec5da35af21b2c996b05a9618ece568d.tar.gz
w.i.p.
Diffstat (limited to 'deps')
-rw-r--r--deps/setup/Makefile10
-rw-r--r--deps/setup/README.md13
-rw-r--r--deps/setup/doc/README.md13
-rw-r--r--deps/setup/doc/overview.edoc12
-rw-r--r--deps/setup/doc/setup.md151
-rw-r--r--deps/setup/doc/setup_gen.md94
-rw-r--r--deps/setup/rebar.config1
-rw-r--r--deps/setup/src/setup.erl398
-rw-r--r--deps/setup/src/setup_gen.erl152
-rw-r--r--deps/setup/xtest/test.conf24
10 files changed, 761 insertions, 107 deletions
diff --git a/deps/setup/Makefile b/deps/setup/Makefile
index 77c07d5..d86da5c 100644
--- a/deps/setup/Makefile
+++ b/deps/setup/Makefile
@@ -1,4 +1,4 @@
-.PHONY: doc compile test compile_test clean_test run_test escriptize deps
+.PHONY: doc compile test compile_test clean_test run_test escriptize deps eunit
REBAR ?= $(shell which rebar || echo ./rebar)
@@ -30,8 +30,11 @@ clean_test:
done
rm -r xtest/releases
+eunit: compile
+ ${REBAR} eunit
+
test: compile compile_test
- ./setup_gen test xtest/test.conf xtest/releases/1
+ ./setup_gen test xtest/test.conf xtest/releases/1 -pa ${PWD}/ebin
run_test:
erl -boot xtest/releases/1/start -config xtest/releases/1/sys
@@ -49,3 +52,6 @@ clean_plt:
dialyzer: deps compile $(SETUP_PLT)
dialyzer -r ebin --plt $(SETUP_PLT) $(DIALYZER_OPTS)
+
+ci: eunit test dialyzer
+ erl -boot xtest/releases/1/start -config xtest/releases/1/sys -s init stop
diff --git a/deps/setup/README.md b/deps/setup/README.md
index fd51708..63e048a 100644
--- a/deps/setup/README.md
+++ b/deps/setup/README.md
@@ -3,8 +3,11 @@
# The setup application #
__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+
Generic setup utility for Erlang-based systems
+[![Build Status](https://travis-ci.org/uwiger/setup.svg)](https://travis-ci.org/uwiger/setup)
+
## Introduction ##
@@ -93,11 +96,21 @@ erl -sys install -boot install
This boot script will run kernel, stdlib and sasl, then load all other
applications, and finally run the `setup` application, which will find
and execute any setup hooks.
+
If the option `-setup stop_when_done true` is added to the command line,
the setup application will automatically shut down all running nodes after
running the setup hooks. Otherwise (default), it will hand over control to
the shell rather than terminate the Erlang VM.
+See [`setup_gen:run/1`](http://github.com/uwiger/setup/blob/master/doc/setup_gen.md#run-1) for documentation on all supported options.
+
+
+## Variable expansion ##
+`setup` extends the functionality of `application:get_env/[2,3]` by also
+supporting variable expansion, as well as a function for searching all
+applications for instances of a given variable. This functionality is described
+in [`setup`](http://github.com/uwiger/setup/blob/master/doc/setup.md).
+
## Modules ##
diff --git a/deps/setup/doc/README.md b/deps/setup/doc/README.md
index 305520e..fd53029 100644
--- a/deps/setup/doc/README.md
+++ b/deps/setup/doc/README.md
@@ -3,8 +3,11 @@
# The setup application #
__Authors:__ Ulf Wiger ([`ulf@wiger.net`](mailto:ulf@wiger.net)).
+
Generic setup utility for Erlang-based systems
+[![Build Status](https://travis-ci.org/uwiger/setup.svg)](https://travis-ci.org/uwiger/setup)
+
## Introduction ##
@@ -93,11 +96,21 @@ erl -sys install -boot install
This boot script will run kernel, stdlib and sasl, then load all other
applications, and finally run the `setup` application, which will find
and execute any setup hooks.
+
If the option `-setup stop_when_done true` is added to the command line,
the setup application will automatically shut down all running nodes after
running the setup hooks. Otherwise (default), it will hand over control to
the shell rather than terminate the Erlang VM.
+See [`setup_gen:run/1`](setup_gen.md#run-1) for documentation on all supported options.
+
+
+## Variable expansion ##
+`setup` extends the functionality of `application:get_env/[2,3]` by also
+supporting variable expansion, as well as a function for searching all
+applications for instances of a given variable. This functionality is described
+in [`setup`](setup.md).
+
## Modules ##
diff --git a/deps/setup/doc/overview.edoc b/deps/setup/doc/overview.edoc
index 2fc993e..fffb209 100644
--- a/deps/setup/doc/overview.edoc
+++ b/deps/setup/doc/overview.edoc
@@ -1,6 +1,9 @@
@author Ulf Wiger <ulf@wiger.net>
@doc Generic setup utility for Erlang-based systems
+
+[![Build Status](https://travis-ci.org/uwiger/setup.svg)](https://travis-ci.org/uwiger/setup)
+
<h2>Introduction</h2>
While Erlang/OTP comes with many wonderful applications, including the
@@ -85,4 +88,13 @@ the setup application will automatically shut down all running nodes after
running the setup hooks. Otherwise (default), it will hand over control to
the shell rather than terminate the Erlang VM.
+See {@link setup_gen:run/1} for documentation on all supported options.
+
+<h2>Variable expansion</h2>
+
+`setup' extends the functionality of `application:get_env/[2,3]' by also
+supporting variable expansion, as well as a function for searching all
+applications for instances of a given variable. This functionality is described
+in {@link setup}.
+
@end \ No newline at end of file
diff --git a/deps/setup/doc/setup.md b/deps/setup/doc/setup.md
index df22af0..5ef9a83 100644
--- a/deps/setup/doc/setup.md
+++ b/deps/setup/doc/setup.md
@@ -8,13 +8,101 @@
Setup utility for erlang applications.
__Behaviours:__ [`application`](application.md).
-<a name="index"></a>
+<a name="description"></a>
+
+## Description ##
+
+
+
+This API contains:
+* Support functions for system install ([`find_hooks/0`](#find_hooks-0),
+[`run_hooks/0`](#run_hooks-0), [`lib_dirs/0`](#lib_dirs-0)).
+* Functions for managing and inspecting the system environment
+([`home/0`](#home-0), [`log_dir/0`](#log_dir-0), [`data_dir/0`](#data_dir-0),
+[`verify_directories/0`](#verify_directories-0), [`verify_dir/0`](#verify_dir-0)).
+* Support functions for application environments ([`get_env/2`](#get_env-2),
+[`get_all_env/1`](#get_all_env-1), [`find_env_vars/1`](#find_env_vars-1), [`expand_value/2`](#expand_value-2)).
+* Functions for controlling dynamic load/upgrade of applications
+([`find_app/1`](#find_app-1), [`pick_vsn/3`](#pick_vsn-3), [`reload_app/1`](#reload_app-1),
+[`patch_app/1`](#patch_app-1)).
+
+
+
+
+### <a name="Variable_expansion">Variable expansion</a> ###
+
+
+
+Setup supports variable substitution in application environments. It provides
+some global variables, `"$HOME", "$DATA_DIR", "$LOG_DIR"`, corresponding to
+the API functions [`home/0`](#home-0), [`data_dir/0`](#data_dir-0) and [`log_dir`](log_dir.md),
+as well as some application-specific variables, `"$APP", "$PRIV_DIR",
+"$LIB_DIR".
+
+The normal way to use these variables is by embedding them in file names,
+e.g. `{my_logs, "$LOG_DIR/$APP"}`, but a variable can also be referenced as:
+* `{'$value',Var}` - The variable's value is used as-is (which means that
+`{'$value', "$APP"}` expands to an atom corresponding to the current
+app name.)
+* `{'$string', Var}` - The value is represented as a string (list). If the
+value isn't a "string type", `io_lib:format("~w",[Value])` is used.
+* `{'$binary', Var}` - Like `'$string`', but using binary representation.
+
+
+
+Custom variables can be defined by using either:
+* *global scope* - The `setup` environment variable `vars`, containing a
+list of `{VarName, Definition}` tuples
+* *application-local scope* - Defining an application-local environment
+variable `'$setup_vars`', on the same format as above.
+
+
+
+The `VarName` shall be a string, e.g. `"MYVAR"` (no `$` prefix).
+`Definition` can be one of:
+* `{value, Val}` - the value of the variable is exactly `Val`
+* `{expand, Val}` - `Val` is expanded in its turn
+* `{apply, M, F, A}` - Use the return value of `apply(M, F, A)`.
+
+
+
+When using a variable expansion, either insert the variable reference in
+a string (or binary), or use one of the following formats:
+* `'{'$value', Var}`' - Use value as-is
+* `'{'$string', Var}`' - Use the string representation of the value
+* `'{'$binary', Var}`' - Use the binary representation of the value.
+
+
+
+
+### <a name="Customizing_setup">Customizing setup</a> ###
+
+The following environment variables can be used to customize `setup`:
+* `{home, Dir}` - The topmost directory of the running system. This should
+be a writeable area.
+* `{data_dir, Dir}` - A directory where applications are allowed to create
+their own subdirectories and save data. Default is `Home/data.Node`.
+* `{log_dir, Dir}` - A directory for logging. Default is `Home/log.Node`.
+* `{stop_when_done, true|false}` - When invoking `setup` for an install,
+`setup` normally remains running, allowing for other operations to be
+performed from the shell or otherwise. If `{stop_when_done, true}`, the
+node is shut down once `setup` is finished.
+* `{abort_on_error, true|false}` - When running install or upgrade hooks,
+`setup` will normally keep going even if some hooks fail. A more strict
+semantics can be had by setting `{abort_on_error, true}`, in which case
+`setup` will raise an exception if an error occurs.
+* `{mode, atom()}` - Specifies the context for running 'setup'. Default is
+`normal`. The `setup` mode has special significance, since it's the default
+mode for setup hooks, if no other mode is specified. In theory, one may
+specify any atom value, but it's probably wise to stick to the values
+'normal', 'setup' and 'upgrade' as global contexts, and instead trigger
+other mode hooks by explicitly calling [`run_hooks/1`](#run_hooks-1).<a name="index"></a>
## Function Index ##
-<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#data_dir-0">data_dir/0</a></td><td>Returns the configured data dir, or a best guess (<code>home()/data.Node</code>).</td></tr><tr><td valign="top"><a href="#expand_value-2">expand_value/2</a></td><td></td></tr><tr><td valign="top"><a href="#find_app-1">find_app/1</a></td><td>Equivalent to <a href="#find_app-2"><tt>find_app(A, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#find_app-2">find_app/2</a></td><td>Locates application <code>A</code> along LibDirs (see <a href="#lib_dirs-0"><code>lib_dirs/0</code></a> and
-<a href="#lib_dirs-1"><code>lib_dirs/1</code></a>) or under the OTP root, returning all found candidates.</td></tr><tr><td valign="top"><a href="#find_env_vars-1">find_env_vars/1</a></td><td>Searches all loaded apps for instances of the <code>Env</code> environment variable.</td></tr><tr><td valign="top"><a href="#find_hooks-0">find_hooks/0</a></td><td>Finds all custom setup hooks in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-1">find_hooks/1</a></td><td>Find all setup hooks for <code>Mode</code> in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-2">find_hooks/2</a></td><td>Find all setup hooks for <code>Mode</code> in <code>Applications</code>.</td></tr><tr><td valign="top"><a href="#get_env-2">get_env/2</a></td><td></td></tr><tr><td valign="top"><a href="#home-0">home/0</a></td><td>Returns the configured <code>home</code> directory, or a best guess (<code>$CWD</code>).</td></tr><tr><td valign="top"><a href="#lib_dirs-0">lib_dirs/0</a></td><td>Equivalent to <a href="#lib_dirs-1"><tt>lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))</tt></a>.</td></tr><tr><td valign="top"><a href="#lib_dirs-1">lib_dirs/1</a></td><td>Returns an expanded list of application directories under a lib path.</td></tr><tr><td valign="top"><a href="#log_dir-0">log_dir/0</a></td><td>Returns the configured log dir, or a best guess (<code>home()/log.Node</code>).</td></tr><tr><td valign="top"><a href="#ok-1">ok/1</a></td><td></td></tr><tr><td valign="top"><a href="#patch_app-1">patch_app/1</a></td><td>Adds an application's "development" path to a target system.</td></tr><tr><td valign="top"><a href="#pick_vsn-3">pick_vsn/3</a></td><td>Picks the specified version out of a list returned by <a href="#find_app-1"><code>find_app/1</code></a></td></tr><tr><td valign="top"><a href="#read_config_script-3">read_config_script/3</a></td><td></td></tr><tr><td valign="top"><a href="#reload_app-1">reload_app/1</a></td><td>Equivalent to <a href="#reload_app-2"><tt>reload_app(AppName, latest)</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-2">reload_app/2</a></td><td>Equivalent to <a href="#reload_app-3"><tt>reload_app(AppName, latest, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-3">reload_app/3</a></td><td>Loads or upgrades an application to the specified version.</td></tr><tr><td valign="top"><a href="#run_hooks-0">run_hooks/0</a></td><td>Execute all setup hooks for current mode in order.</td></tr><tr><td valign="top"><a href="#run_hooks-1">run_hooks/1</a></td><td>Execute setup hooks for current mode in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#run_hooks-2">run_hooks/2</a></td><td>Execute setup hooks for <code>Mode</code> in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#start-2">start/2</a></td><td>Application start function.</td></tr><tr><td valign="top"><a href="#stop-1">stop/1</a></td><td>Application stop function
+<table width="100%" border="1" cellspacing="0" cellpadding="2" summary="function index"><tr><td valign="top"><a href="#data_dir-0">data_dir/0</a></td><td>Returns the configured data dir, or a best guess (<code>home()/data.Node</code>).</td></tr><tr><td valign="top"><a href="#expand_value-2">expand_value/2</a></td><td>Expand <code>Value</code> using global variables and the variables of <code>App</code></td></tr><tr><td valign="top"><a href="#find_app-1">find_app/1</a></td><td>Equivalent to <a href="#find_app-2"><tt>find_app(A, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#find_app-2">find_app/2</a></td><td>Locates application <code>A</code> along LibDirs (see <a href="#lib_dirs-0"><code>lib_dirs/0</code></a> and
+<a href="#lib_dirs-1"><code>lib_dirs/1</code></a>) or under the OTP root, returning all found candidates.</td></tr><tr><td valign="top"><a href="#find_env_vars-1">find_env_vars/1</a></td><td>Searches all loaded apps for instances of the <code>Env</code> environment variable.</td></tr><tr><td valign="top"><a href="#find_hooks-0">find_hooks/0</a></td><td>Finds all custom setup hooks in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-1">find_hooks/1</a></td><td>Find all setup hooks for <code>Mode</code> in all applications.</td></tr><tr><td valign="top"><a href="#find_hooks-2">find_hooks/2</a></td><td>Find all setup hooks for <code>Mode</code> in <code>Applications</code>.</td></tr><tr><td valign="top"><a href="#get_all_env-1">get_all_env/1</a></td><td>Like <code>application:get_all_env/1</code>, but with variable expansion.</td></tr><tr><td valign="top"><a href="#get_env-2">get_env/2</a></td><td></td></tr><tr><td valign="top"><a href="#get_env-3">get_env/3</a></td><td></td></tr><tr><td valign="top"><a href="#home-0">home/0</a></td><td>Returns the configured <code>home</code> directory, or a best guess (<code>$CWD</code>).</td></tr><tr><td valign="top"><a href="#lib_dirs-0">lib_dirs/0</a></td><td>Equivalent to <a href="#union-2"><tt>union(lib_dirs("ERL_SETUP_LIBS"), lib_dirs("ERL_LIBS"))</tt></a>.</td></tr><tr><td valign="top"><a href="#lib_dirs-1">lib_dirs/1</a></td><td>Returns an expanded list of application directories under a lib path.</td></tr><tr><td valign="top"><a href="#log_dir-0">log_dir/0</a></td><td>Returns the configured log dir, or a best guess (<code>home()/log.Node</code>).</td></tr><tr><td valign="top"><a href="#ok-1">ok/1</a></td><td></td></tr><tr><td valign="top"><a href="#patch_app-1">patch_app/1</a></td><td>Adds an application's "development" path to a target system.</td></tr><tr><td valign="top"><a href="#pick_vsn-3">pick_vsn/3</a></td><td>Picks the specified version out of a list returned by <a href="#find_app-1"><code>find_app/1</code></a></td></tr><tr><td valign="top"><a href="#read_config_script-3">read_config_script/3</a></td><td></td></tr><tr><td valign="top"><a href="#reload_app-1">reload_app/1</a></td><td>Equivalent to <a href="#reload_app-2"><tt>reload_app(AppName, latest)</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-2">reload_app/2</a></td><td>Equivalent to <a href="#reload_app-3"><tt>reload_app(AppName, latest, lib_dirs())</tt></a>.</td></tr><tr><td valign="top"><a href="#reload_app-3">reload_app/3</a></td><td>Loads or upgrades an application to the specified version.</td></tr><tr><td valign="top"><a href="#run_hooks-0">run_hooks/0</a></td><td>Execute all setup hooks for current mode in order.</td></tr><tr><td valign="top"><a href="#run_hooks-1">run_hooks/1</a></td><td>Execute setup hooks for current mode in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#run_hooks-2">run_hooks/2</a></td><td>Execute setup hooks for <code>Mode</code> in <code>Applications</code> in order.</td></tr><tr><td valign="top"><a href="#start-2">start/2</a></td><td>Application start function.</td></tr><tr><td valign="top"><a href="#stop-1">stop/1</a></td><td>Application stop function
end.</td></tr><tr><td valign="top"><a href="#verify_dir-1">verify_dir/1</a></td><td>Ensures that the directory Dir exists and is writable.</td></tr><tr><td valign="top"><a href="#verify_directories-0">verify_directories/0</a></td><td>Ensures that essential directories exist and are writable.</td></tr></table>
@@ -38,9 +126,18 @@ Returns the configured data dir, or a best guess (`home()/data.Node`).
### expand_value/2 ###
-`expand_value(App, Value) -> any()`
+<pre><code>
+expand_value(App::atom(), Value::any()) -&gt; any()
+</code></pre>
+<br />
+
+
+Expand `Value` using global variables and the variables of `App`
+
+The variable expansion is performed according to the rules outlined in
+[Variable expansion](#Variable_expansion).
<a name="find_app-1"></a>
### find_app/1 ###
@@ -80,11 +177,8 @@ find_env_vars(Env) -&gt; [{AppName, Value}]
Searches all loaded apps for instances of the `Env` environment variable.
-The environment variables may contain instances of
-`$APP`, `$PRIV_DIR`, `$LIB_DIR`, `$DATA_DIR`, `$LOG_DIR`, `$HOME`,
-inside strings or binaries, and these will be replaced with actual values
-for the current system (`$APP` simply expands to the name of the current
-application).
+The environment variables are expanded according to the rules outlined in
+[Variable expansion](#Variable_expansion)
<a name="find_hooks-0"></a>
### find_hooks/0 ###
@@ -95,15 +189,12 @@ find_hooks() -&gt; [{PhaseNo, [{M, F, A}]}]
</code></pre>
<br />
+
Finds all custom setup hooks in all applications.
The setup hooks must be of the form
-
-```
-{'$setup_hooks', [{PhaseNo, {M, F, A}}]}
-```
-
-,
+`{'$setup_hooks', [{PhaseNo, {M, F, A}} | {Mode, [{PhaseNo, {M,F,A}}]}]}`,
where PhaseNo should be (but doesn't have to be) an integer.
+If `Mode` is not specified, the hook will pertain to the `setup` mode.
@@ -140,6 +231,22 @@ find_hooks(Mode, Applications) -&gt; [{PhaseNo, [{M, F, A}]}]
<br />
Find all setup hooks for `Mode` in `Applications`.
+<a name="get_all_env-1"></a>
+
+### get_all_env/1 ###
+
+
+<pre><code>
+get_all_env(A::atom()) -&gt; [{atom(), any()}]
+</code></pre>
+<br />
+
+
+Like `application:get_all_env/1`, but with variable expansion.
+
+
+The variable expansion is performed according to the rules outlined in
+[Variable expansion](#Variable_expansion).
<a name="get_env-2"></a>
### get_env/2 ###
@@ -147,6 +254,13 @@ Find all setup hooks for `Mode` in `Applications`.
`get_env(A, Key) -> any()`
+<a name="get_env-3"></a>
+
+### get_env/3 ###
+
+`get_env(A, Key, Default) -> any()`
+
+
<a name="home-0"></a>
### home/0 ###
@@ -168,7 +282,7 @@ lib_dirs() -&gt; [string()]
</code></pre>
<br />
-Equivalent to [`lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))`](#lib_dirs-1).
+Equivalent to [`union(lib_dirs("ERL_SETUP_LIBS"), lib_dirs("ERL_LIBS"))`](#union-2).
<a name="lib_dirs-1"></a>
### lib_dirs/1 ###
@@ -347,7 +461,7 @@ The generated appup script is of the form:
-The purge method used is `brutal_purge` - see [`//sasl/appup`](/Users/uwiger/uw/me/sasl/doc/appup.md).
+The purge method used is `brutal_purge` - see [`//sasl/appup`](http://www.erlang.org/doc/man/appup.html).
For details on how the new version is chosen, see [`find_app/1`](#find_app-1) and
@@ -431,4 +545,5 @@ verify_directories() -&gt; ok
<br />
Ensures that essential directories exist and are writable.
-Currently, only the log directory is verified.
+Currently, the directories corresponding to [`home/0`](#home-0),
+[`log_dir/0`](#log_dir-0) and [`data_dir/0`](#data_dir-0) are verified.
diff --git a/deps/setup/doc/setup_gen.md b/deps/setup/doc/setup_gen.md
index 75762d3..04e84d9 100644
--- a/deps/setup/doc/setup_gen.md
+++ b/deps/setup/doc/setup_gen.md
@@ -61,16 +61,104 @@ Mandatory options:
* `{outdir, Dir}` - Where to put the generated files. Dir is created if not
already present.
* `{conf, Conf}` - Config file listing apps and perhaps other options
+* `{relconf, File}` - can be used instead of `conf`, and identifies a
+reltool.config file (see [`reltool`](http://www.erlang.org/doc/man/index.html)) to be used as
+system description. If a `conf` option is present, it will be used;
+otherwise, a `relconf` option must be present.
+
Additional options:
+
+
+
* `{apps, [App]}` - List of applications to include in the release. Only the
first instance of this option is considered.
* `{add_apps, [App]}` - Adds applications to the ones given in the `apps`
option.
+* `{remove_apps, Apps}` - Remove `Apps` from the list of applications.
+* `{sort_app, App, Before}` - Change the sort order so that `App` comes
+before `Before`.
* `{include, ConfigFile}` - include options from the given file. The file
is processed using `file:script/2`.
* `{include_lib, ConfigFile}` - As above, but ConfigFile is named as with
-the `-include_lib(...)` directive in Erlang
-source code.
-* ...
+the `-include_lib(...)` directive in Erlang source code.
+* `{sys, SysConfigFile}` - Read an existing sys.config file. The environment
+found in this file may be redefined by `env` and `set_env` entries
+(see below).
+* `{env, [{App, [{K,V}]}]}` - Environment variables for the `sys.config`
+file. `setup_gen` will merge all `env` entries, where later entries
+replace earlier entries (based on the environment variable name).
+* `{set_env, [{App, [{KeyPath, V}]}]}` - Modifies existing environment
+structures, where `KeyPath` is a list of names (top name must be
+an atom) describing a path in a tree structure, where each node
+is either a `{Key, SubTree}` or a `{Key, Any, SubTree}` tuple. The
+`set_env` function will continue into `SubTree` and either replace
+the value representing the full `KeyPath` or create the remaining
+subtree.
+* `{target, Dir}` - Where to produce the generated files. The files will
+end up in `Dir/releases/Vsn/`. If a `reltool.config` file is used,
+the `{target_dir, D}` option will be translated into `{target,D}`.
+* `{vsn, Vsn}` - System version, used to determine where to generate the
+files (see `target` above).
+* `{root, RootDir}` - Where to look for applications. Normally, `RootDir`
+should represent either `RootDir/lib/*/ebin`, or `RootDir/*/ebin`,
+but if the option `{wild_roots,true}` is given, it can be either
+an "ebin" directory, or any parent directory to "ebin" directories.
+Multiple `root` options can be given. If `target` is not given
+"boot variables" will be generated for each root directory in turn,
+named `V1 ... Vn`, then generating a relocatable boot script.
+* `{pa, Path}` - Prepends `Path` to the code path. Multiple `pa` options
+can be given.
+* `{pz, Path}` - Appends `Path` to the code path. Multiple `pz` options
+can be given.
+* `{install, true|false}` - Tells setup whether to also build "install"
+scripts and config files. An install script contains the same
+applications as the normal script, but only loads them, starting
+only the `setup` application. This allows a system to be installed
+using "setup hooks", while having all the target system code
+and environment available. An "install.config" file is also created,
+which, if a `{nodes, Ns}` option is given, also configures Erlang
+to wait for all given nodes, and then start the `setup` application
+on the first node.
+* `{verbose, true|false}` - (Default: `false`) Turns on verbose printouts.
+
+
+
+
+### <a name="Application_entries">Application entries</a> ###
+
+
+
+Applications can be represented in a number of different ways:
+* `AppName::atom()` - `setup` will search for the latest version
+along the current code path.
+* `{App::atom(), Vsn::latest|list()}` - where `Vsn` is an explicit version
+identifying the application. `latest` instructs `setup` to pick the
+latest version, if several versions can be found along the path.
+* `{App::atom(), Type::atom()}` - where
+`Type::permanent|temporary|transient|load` is the application start
+type (or, in the case of 'load', no start at all).
+* `{App, Vsn, Type}` - see `App`, `Vsn` and `Type` above
+* `{App, Vsn, Incl}` - where `Incl` is a list of included applications.
+* `{App, Vsn, Type, Incl}`
+
+
+
+
+### <a name="Command-line_options">Command-line options</a> ###
+
+
+The following options can be given on the command line of `setup_gen`:
+* `-target Dir` - Equivalent to `{target, Dir}`
+* `-name Name` - Equivalent to `{name, Name}`
+* `-root Dir` - Equivalent to `{root, Dir}`
+* `-relconf F` - Equivalent to `{relconf, F}`
+* `-conf F` - Equivalent to `{conf, F}`
+* `-install` - Equivalent to `{install, true}`
+* `-sys F` - Equivalent to `{sys, F}`
+* `-vsn V` - Equivalent to `{vsn, V}`
+* `-pa Dir` - Equivalent to `{pa, Dir}`
+* `-pz Dir` - Equivalent to `{pa, Dir}`
+* `-v` - Equivalent to `{verbose, true}`
+
diff --git a/deps/setup/rebar.config b/deps/setup/rebar.config
index e5f2851..3f739dc 100644
--- a/deps/setup/rebar.config
+++ b/deps/setup/rebar.config
@@ -2,6 +2,7 @@
{erl_opts, [debug_info]}.
{deps, [{edown, ".*", {git, "git://github.com/uwiger/edown.git", "HEAD"}}]}.
{edoc_opts, [{doclet, edown_doclet},
+ {app_default, "http://www.erlang.org/doc/man"},
{top_level_readme,
{"./README.md",
"http://github.com/uwiger/setup"}}]}.
diff --git a/deps/setup/src/setup.erl b/deps/setup/src/setup.erl
index c1838a3..54d1ce1 100644
--- a/deps/setup/src/setup.erl
+++ b/deps/setup/src/setup.erl
@@ -16,6 +16,76 @@
%%=============================================================================
%% @doc Setup utility for erlang applications
+%%
+%% This API contains:
+%% * Support functions for system install ({@link find_hooks/0},
+%% {@link run_hooks/0}, {@link lib_dirs/0}).
+%% * Functions for managing and inspecting the system environment
+%% ({@link home/0}, {@link log_dir/0}, {@link data_dir/0},
+%% {@link verify_directories/0}, {@link verify_dir/0}).
+%% * Support functions for application environments ({@link get_env/2},
+%% {@link get_all_env/1}, {@link find_env_vars/1}, {@link expand_value/2}).
+%% * Functions for controlling dynamic load/upgrade of applications
+%% ({@link find_app/1}, {@link pick_vsn/3}, {@link reload_app/1},
+%% {@link patch_app/1}).
+%%
+%% == Variable expansion ==
+%%
+%% Setup supports variable substitution in application environments. It provides
+%% some global variables, `"$HOME", "$DATA_DIR", "$LOG_DIR"', corresponding to
+%% the API functions {@link home/0}, {@link data_dir/0} and {@link log_dir},
+%% as well as some application-specific variables, `"$APP", "$PRIV_DIR",
+%% "$LIB_DIR".
+%%
+%% The normal way to use these variables is by embedding them in file names,
+%% e.g. `{my_logs, "$LOG_DIR/$APP"}', but a variable can also be referenced as:
+%% * ``{'$value',Var}'' - The variable's value is used as-is (which means that
+%% ``{'$value', "$APP"}'' expands to an atom corresponding to the current
+%% app name.)
+%% * ``{'$string', Var}'' - The value is represented as a string (list). If the
+%% value isn't a "string type", `io_lib:format("~w",[Value])' is used.
+%% * ``{'$binary', Var}'' - Like ``'$string''', but using binary representation.
+%%
+%% Custom variables can be defined by using either:
+%% * *global scope* - The `setup' environment variable `vars', containing a
+%% list of `{VarName, Definition}' tuples
+%% * *application-local scope* - Defining an application-local environment
+%% variable ``'$setup_vars''', on the same format as above.
+%%
+%% The `VarName' shall be a string, e.g. `"MYVAR"' (no `$' prefix).
+%% `Definition' can be one of:
+%% * `{value, Val}' - the value of the variable is exactly `Val'
+%% * `{expand, Val}' - `Val' is expanded in its turn
+%% * `{apply, M, F, A}' - Use the return value of `apply(M, F, A)'.
+%%
+%% When using a variable expansion, either insert the variable reference in
+%% a string (or binary), or use one of the following formats:
+%% * ``'{'$value', Var}''' - Use value as-is
+%% * ``'{'$string', Var}''' - Use the string representation of the value
+%% * ``'{'$binary', Var}''' - Use the binary representation of the value.
+%%
+%% == Customizing setup ==
+%% The following environment variables can be used to customize `setup':
+%% * `{home, Dir}' - The topmost directory of the running system. This should
+%% be a writeable area.
+%% * `{data_dir, Dir}' - A directory where applications are allowed to create
+%% their own subdirectories and save data. Default is `Home/data.Node'.
+%% * `{log_dir, Dir}' - A directory for logging. Default is `Home/log.Node'.
+%% * `{stop_when_done, true|false}' - When invoking `setup' for an install,
+%% `setup' normally remains running, allowing for other operations to be
+%% performed from the shell or otherwise. If `{stop_when_done, true}', the
+%% node is shut down once `setup' is finished.
+%% * `{abort_on_error, true|false}' - When running install or upgrade hooks,
+%% `setup' will normally keep going even if some hooks fail. A more strict
+%% semantics can be had by setting `{abort_on_error, true}', in which case
+%% `setup' will raise an exception if an error occurs.
+%% * `{mode, atom()}' - Specifies the context for running 'setup'. Default is
+%% `normal'. The `setup' mode has special significance, since it's the default
+%% mode for setup hooks, if no other mode is specified. In theory, one may
+%% specify any atom value, but it's probably wise to stick to the values
+%% 'normal', 'setup' and 'upgrade' as global contexts, and instead trigger
+%% other mode hooks by explicitly calling {@link run_hooks/1}.
+%% @end
-module(setup).
-behaviour(application).
@@ -27,10 +97,12 @@
data_dir/0,
verify_directories/0,
verify_dir/1,
+ mode/0,
find_hooks/0, find_hooks/1, find_hooks/2,
run_hooks/0, run_hooks/1, run_hooks/2,
find_env_vars/1,
- get_env/2,
+ get_env/2, get_env/3,
+ get_all_env/1,
expand_value/2,
patch_app/1,
find_app/1, find_app/2,
@@ -46,6 +118,10 @@
-include_lib("kernel/include/file.hrl").
+-ifdef(TEST).
+-include_lib("eunit/include/eunit.hrl").
+-endif.
+
%% @spec start(Type, Args) -> {ok, pid()}
%% @doc Application start function.
%% @end
@@ -65,10 +141,10 @@ stop(_) ->
%% @end
%%
home() ->
- case application:get_env(setup, home) of
+ case app_get_env(setup, home) of
U when U == {ok, undefined};
U == undefined ->
- {ok, CWD} = file:get_cwd(),
+ CWD = cwd(),
D = filename:absname(CWD),
application:set_env(setup, home, D),
D;
@@ -92,7 +168,7 @@ data_dir() ->
setup_dir(data_dir, "data." ++ atom_to_list(node())).
setup_dir(Key, Default) ->
- case application:get_env(setup, Key) of
+ case app_get_env(setup, Key) of
U when U == {ok, undefined};
U == undefined ->
D = filename:absname(filename:join(home(), Default)),
@@ -104,7 +180,8 @@ setup_dir(Key, Default) ->
%% @spec verify_directories() -> ok
%% @doc Ensures that essential directories exist and are writable.
-%% Currently, only the log directory is verified.
+%% Currently, the directories corresponding to {@link home/0},
+%% {@link log_dir/0} and {@link data_dir/0} are verified.
%% @end
%%
verify_directories() ->
@@ -130,19 +207,16 @@ ok(Other) ->
%% @spec find_env_vars(Env) -> [{AppName, Value}]
%% @doc Searches all loaded apps for instances of the `Env' environment variable.
%%
-%% The environment variables may contain instances of
-%% `$APP', `$PRIV_DIR', `$LIB_DIR', `$DATA_DIR', `$LOG_DIR', `$HOME',
-%% inside strings or binaries, and these will be replaced with actual values
-%% for the current system (`$APP' simply expands to the name of the current
-%% application).
+%% The environment variables are expanded according to the rules outlined in
+%% {@section Variable expansion}
%% @end
find_env_vars(Env) ->
GEnv = global_env(),
lists:flatmap(
fun({A,_,_}) ->
- case application:get_env(A, Env) of
+ case app_get_env(A, Env) of
{ok, Val} when Val =/= undefined ->
- NewEnv = GEnv ++ private_env(A),
+ NewEnv = private_env(A, GEnv),
[{A, expand_env(NewEnv, Val)}];
_ ->
[]
@@ -150,22 +224,94 @@ find_env_vars(Env) ->
end, application:loaded_applications()).
get_env(A, Key) ->
- case application:get_env(A, Key) of
+ case app_get_env(A, Key) of
{ok, Val} ->
{ok, expand_value(A, Val)};
Other ->
Other
end.
-expand_value(App, Value) ->
- expand_env(global_env() ++ private_env(App), Value).
+get_env(A, Key, Default) ->
+ case get_env(A, Key) of
+ {ok, Val} ->
+ Val;
+ _ ->
+ Default
+ end.
+
+-spec get_all_env(atom()) -> [{atom(), any()}].
+%% @doc Like `application:get_all_env/1', but with variable expansion.
+%%
+%% The variable expansion is performed according to the rules outlined in
+%% {@section Variable expansion}.
+%% @end
+get_all_env(A) ->
+ Vars = private_env(A),
+ [{K, expand_env(Vars, V)} ||
+ {K, V} <- application:get_all_env(A)].
+-spec expand_value(atom(), any()) -> any().
+%% @doc Expand `Value' using global variables and the variables of `App'
+%%
+%% The variable expansion is performed according to the rules outlined in
+%% {@section Variable expansion}.
+%% @end
+expand_value(App, Value) ->
+ expand_env(private_env(App), Value).
global_env() ->
- [{K, env_value(K)} || K <- ["DATA_DIR", "LOG_DIR", "HOME"]].
+ Acc = [{K, fun() -> env_value(K) end} ||
+ K <- ["DATA_DIR", "LOG_DIR", "HOME"]],
+ custom_global_env(Acc).
+
+custom_global_env(Acc) ->
+ lists:foldl(fun custom_env1/2, Acc,
+ [{K,V} || {K,V} <- app_get_env(setup, vars, []),
+ is_list(K)]).
+
private_env(A) ->
- [{K, env_value(K, A)} || K <- ["APP", "PRIV_DIR", "LIB_DIR"]].
+ private_env(A, global_env()).
+
+private_env(A, GEnv) ->
+ Acc = [{K, fun() -> env_value(K, A) end} ||
+ K <- ["APP", "PRIV_DIR", "LIB_DIR"]],
+ custom_private_env(A, Acc ++ GEnv).
+
+custom_private_env(A, Acc) ->
+ lists:foldl(fun custom_env1/2, Acc,
+ [{K, V} ||
+ {K,V} <- app_get_env(A, '$setup_vars', []),
+ is_list(K)]).
+
+%% Wrapped for tracing purposes
+app_get_env(A, K) ->
+ application:get_env(A, K).
+
+%% Wrapped for tracing purposes
+app_get_env(A, K, Default) ->
+ %% Apparently, some still use setup on R15B ...
+ case application:get_env(A, K) of
+ {ok, Val} -> Val;
+ _ ->
+ Default
+ end.
+%% Wrapped for tracing purposes
+app_get_key(A, K) ->
+ application:get_key(A, K).
+
+custom_env1({K, V}, Acc) ->
+ [{K, fun() -> custom_env_value(K, V, Acc) end} | Acc].
+
+expand_env(Vs, {T,"$" ++ S}) when T=='$value'; T=='$string'; T=='$binary' ->
+ case {lists:keyfind(S, 1, Vs), T} of
+ {false, '$value'} -> undefined;
+ {false, '$string'} -> "";
+ {false, '$binary'} -> <<"">>;
+ {{_,V}, '$value'} -> V();
+ {{_,V}, '$string'} -> binary_to_list(stringify(V()));
+ {{_,V}, '$binary'} -> stringify(V())
+ end;
expand_env(Vs, T) when is_tuple(T) ->
list_to_tuple([expand_env(Vs, X) || X <- tuple_to_list(T)]);
expand_env(Vs, L) when is_list(L) ->
@@ -182,17 +328,38 @@ expand_env(_, X) ->
do_expand_env(X, Vs, Type) ->
lists:foldl(fun({K, Val}, Xx) ->
- re:replace(Xx, [$\\, $$ | K], Val, [{return,Type}])
+ re:replace(Xx, [$\\, $$ | K],
+ stringify(Val()), [{return,Type}])
end, X, Vs).
env_value("LOG_DIR") -> log_dir();
env_value("DATA_DIR") -> data_dir();
env_value("HOME") -> home().
-env_value("APP", A) -> atom_to_list(A);
+env_value("APP", A) -> A;
env_value("PRIV_DIR", A) -> priv_dir(A);
env_value("LIB_DIR" , A) -> lib_dir(A).
+custom_env_value(_K, {value, V}, _Vs) ->
+ V;
+custom_env_value(_K, {expand, V}, Vs) ->
+ expand_env(Vs, V);
+custom_env_value(K, {apply, M, F, A}, _Vs) ->
+ %% Not ideal, but don't want to introduce exceptions in get_env()
+ try apply(M, F, A)
+ catch
+ error:_ ->
+ {error, {custom_setup_env, K}}
+ end.
+
+%% This function is more general than to_string/1 below
+stringify(V) ->
+ try iolist_to_binary(V)
+ catch
+ error:badarg ->
+ iolist_to_binary(io_lib:format("~w", [V]))
+ end.
+
priv_dir(A) ->
case code:priv_dir(A) of
{error, bad_name} ->
@@ -276,7 +443,7 @@ patch_app(A, Vsn, LibDirs) ->
pick_vsn(_, Dirs, latest) ->
lists:last(Dirs);
pick_vsn(A, Dirs, next) ->
- case application:get_key(A, vsn) of
+ case app_get_key(A, vsn) of
{ok, Cur} ->
case lists:dropwhile(fun({V, _}) -> V =/= Cur end, Dirs) of
[_, {_, _} = Next |_] -> Next;
@@ -410,10 +577,10 @@ reload_app(A, ToVsn) ->
%% {@link pick_vsn/3}.
%% @end
reload_app(A, ToVsn0, LibDirs) ->
- case application:get_key(A, vsn) of
+ case app_get_key(A, vsn) of
undefined ->
ok = application:load(A),
- {ok, Modules} = application:get_key(A, modules),
+ {ok, Modules} = app_get_key(A, modules),
_ = [c:l(M) || M <- Modules],
{ok, []};
{ok, FromVsn} ->
@@ -478,7 +645,7 @@ make_appup_script(A, OldVsn, NewPath) ->
{NewVsn, Script} ->
{NewVsn, Script, NewApp};
false ->
- {ok, OldMods} = application:get_key(A, modules),
+ {ok, OldMods} = app_get_key(A, modules),
{modules, NewMods} = lists:keyfind(modules, 1, NewAppTerms),
{vsn, NewVsn} = lists:keyfind(vsn, 1, NewAppTerms),
{DelMods,AddMods,ChgMods} = {OldMods -- NewMods,
@@ -570,8 +737,8 @@ run_setup_(Parent, _Args) ->
Hooks = find_hooks(Mode),
run_selected_hooks(Hooks),
io:fwrite("Setup finished processing hooks ...~n", []),
- case application:get_env(stop_when_done) of
- {ok, true} ->
+ case app_get_env(setup, stop_when_done) of
+ {ok, true} when Mode =/= normal ->
io:fwrite("Setup stopping...~n", []),
timer:sleep(timer:seconds(5)),
rpc:eval_everywhere(init,stop,[0]);
@@ -582,8 +749,9 @@ run_setup_(Parent, _Args) ->
%% @spec find_hooks() -> [{PhaseNo, [{M,F,A}]}]
%% @doc Finds all custom setup hooks in all applications.
%% The setup hooks must be of the form
-%% <pre>{'$setup_hooks', [{PhaseNo, {M, F, A}}]}</pre>,
+%% ``{'$setup_hooks', [{PhaseNo, {M, F, A}} | {Mode, [{PhaseNo, {M,F,A}}]}]}'',
%% where PhaseNo should be (but doesn't have to be) an integer.
+%% If `Mode' is not specified, the hook will pertain to the `setup' mode.
%%
%% The hooks will be called in order:
%% - The phase numbers will be sorted.
@@ -613,30 +781,23 @@ find_hooks(Mode) when is_atom(Mode) ->
find_hooks(Mode, Applications) ->
lists:foldl(
fun(A, Acc) ->
- case application:get_env(A, '$setup_hooks') of
+ case app_get_env(A, '$setup_hooks') of
{ok, Hooks} ->
lists:foldl(
- fun({Mode1, L}, Acc1) when is_atom(Mode1), is_list(L) ->
+ fun({Mode1, [{_, {_,_,_}}|_] = L}, Acc1)
+ when Mode1 =:= Mode ->
+ find_hooks_(Mode, A, L, Acc1);
+ ({Mode1, [{_, [{_, _, _}|_]}|_] = L}, Acc1)
+ when Mode1 =:= Mode ->
+ find_hooks_(Mode, A, L, Acc1);
+ ({N, {_, _, _} = MFA}, Acc1) when Mode=:=setup ->
+ orddict:append(N, MFA, Acc1);
+ ({N, [{_, _, _}|_] = L}, Acc1)
+ when Mode=:=setup ->
lists:foldl(
- fun({N, {_,_,_} = MFA}, Acc2) ->
- orddict:append(N, MFA, Acc2);
- ({N, MFAs}, Acc2) when is_list(MFAs) ->
- lists:foldl(
- fun({_,_,_} = MFA1, Acc3) ->
- orddict:append(
- N, MFA1, Acc3);
- (Other1, Acc3) ->
- io:fwrite(
- "Invalid hook: ~p~n"
- " App : ~p~n"
- " Mode : ~p~n"
- " Phase: ~p~n",
- [Other1, A, Mode1, N]),
- Acc3
- end, Acc2, MFAs)
+ fun(MFA, Acc2) ->
+ orddict:append(N, MFA, Acc2)
end, Acc1, L);
- ({N, {_, _, _} = MFA}, Acc1) when Mode==setup ->
- orddict:append(N, MFA, Acc1);
(_, Acc1) ->
Acc1
end, Acc, Hooks);
@@ -645,8 +806,35 @@ find_hooks(Mode, Applications) ->
end
end, orddict:new(), Applications).
+find_hooks_(Mode, A, L, Acc1) ->
+ lists:foldl(
+ fun({N, {_,_,_} = MFA}, Acc2) ->
+ orddict:append(N, MFA, Acc2);
+ ({N, [{_,_,_}|_] = MFAs}, Acc2) when is_list(MFAs) ->
+ lists:foldl(
+ fun({_,_,_} = MFA1, Acc3) ->
+ orddict:append(
+ N, MFA1, Acc3);
+ (Other1, Acc3) ->
+ io:fwrite(
+ "Invalid hook: ~p~n"
+ " App : ~p~n"
+ " Mode : ~p~n"
+ " Phase: ~p~n",
+ [Other1, A, Mode, N]),
+ Acc3
+ end, Acc2, MFAs)
+ end, Acc1, L).
+
+-spec mode() -> normal | atom().
+%% @doc Returns the current "setup mode".
+%%
+%% The mode can be defined using the `setup' environment variable `mode'.
+%% The default value is `normal'. The mode is used to select which setup
+%% hooks to execute when starting the `setup' application.
+%% @end
mode() ->
- case application:get_env(mode) of
+ case app_get_env(setup, mode) of
{ok, M} ->
M;
_ ->
@@ -655,18 +843,26 @@ mode() ->
%% @spec run_hooks() -> ok
%% @doc Execute all setup hooks for current mode in order.
+%%
+%% See {@link find_hooks/0} for details on the order of execution.
%% @end
run_hooks() ->
run_hooks(applications()).
%% @spec run_hooks(Applications) -> ok
%% @doc Execute setup hooks for current mode in `Applications' in order.
+%%
+%% See {@link find_hooks/0} for details on the order of execution.
%% @end
run_hooks(Apps) ->
run_hooks(mode(), Apps).
%% @spec run_hooks(Mode, Applications) -> ok
%% @doc Execute setup hooks for `Mode' in `Applications' in order
+%%
+%% Note that no assumptions can be made about which process each setup hook
+%% runs in, nor whether it runs in the same process as the previous hook.
+%% See {@link find_hooks/0} for details on the order of execution.
%% @end
run_hooks(Mode, Apps) ->
Hooks = find_hooks(Mode, Apps),
@@ -682,7 +878,7 @@ run_hooks(Mode, Apps) ->
%% @end
%%
run_selected_hooks(Hooks) ->
- AbortOnError = case application:get_env(setup, abort_on_error) of
+ AbortOnError = case app_get_env(setup, abort_on_error) of
{ok, F} when is_boolean(F) -> F;
{ok, Other} ->
io:fwrite("Invalid abort_on_error flag (~p)~n"
@@ -755,21 +951,7 @@ format_arg(A) ->
%% @end
%%
applications() ->
- Apps = case init:get_argument(boot) of
- {ok, [[Boot]]} ->
- Script = Boot ++ ".script",
- case file:consult(Script) of
- {ok, [{script, _, Commands}]} ->
- [A || {apply, {application, load, [{application, A, _}]}}
- <- Commands];
- Error ->
- error_logger:format("Unable to read boot script (~s): ~p~n",
- [Script, Error]),
- [A || {A, _, _} <- application:loaded_applications()]
- end;
- _ ->
- [A || {A, _, _} <- application:loaded_applications()]
- end,
+ Apps = [A || {A, _, _} <- application:loaded_applications()],
group_applications(Apps).
%% Sort apps in preorder traversal order.
@@ -777,19 +959,32 @@ applications() ->
%% next top application. Normally, there will be no included apps, in which
%% case the list will maintain its original order.
%%
-group_applications([H | T]) ->
- case application:get_key(H, included_applications) of
+group_applications(Apps) ->
+ group_applications(Apps, []).
+
+group_applications([H | T], Acc) ->
+ case app_get_key(H, included_applications) of
{ok, []} ->
- [H | group_applications(T)];
+ group_applications(T, [{H,[]}|Acc]);
{ok, Incls} ->
AllIncls = all_included(Incls),
- [H | AllIncls] ++ group_applications(T -- AllIncls)
+ group_applications(T -- AllIncls,
+ [{H, AllIncls}
+ | lists:foldl(
+ fun(A,Acc1) ->
+ lists:keydelete(A,1,Acc1)
+ end, Acc, AllIncls)])
end;
-group_applications([]) ->
+group_applications([], Acc) ->
+ unfold(lists:reverse(Acc)).
+
+unfold([{A,Incl}|T]) ->
+ [A|Incl] ++ unfold(T);
+unfold([]) ->
[].
all_included([H | T]) ->
- case application:get_key(H, included_applications) of
+ case app_get_key(H, included_applications) of
{ok, []} ->
[H | all_included(T)];
{ok, Incls} ->
@@ -814,7 +1009,7 @@ keep_release(RelVsn) ->
OnlyLoaded = LoadedNames -- RunningNames,
Included = lists:flatmap(
fun(A) ->
- case application:get_key(A, included_applications) of
+ case app_get_key(A, included_applications) of
{ok, []} ->
[];
{ok, As} ->
@@ -880,7 +1075,7 @@ otp_root() ->
%% Modified from code_server:get_user_lib_dirs():
%% @spec lib_dirs() -> [string()]
-%% @equiv lib_dirs(concat("ERL_SETUP_LIBS", "ERL_LIBS"))
+%% @equiv union(lib_dirs("ERL_SETUP_LIBS"), lib_dirs("ERL_LIBS"))
lib_dirs() ->
A = lib_dirs("ERL_SETUP_LIBS"),
B = lib_dirs("ERL_LIBS"),
@@ -1114,3 +1309,64 @@ script_vars(Vs) ->
lists:foldl(fun({K,V}, Acc) ->
erl_eval:add_binding(K, V, Acc)
end, erl_eval:new_bindings(), Vs).
+
+
+%% Unit tests
+-ifdef(TEST).
+
+setup_test_() ->
+ {foreach,
+ fun() ->
+ application:load(setup)
+ end,
+ fun(_) ->
+ application:stop(setup),
+ application:unload(setup)
+ end,
+ [
+ ?_test(t_find_hooks()),
+ ?_test(t_expand_vars())
+ ]}.
+
+t_find_hooks() ->
+ application:set_env(setup, '$setup_hooks',
+ [{100, [{a, hook, [100,1]},
+ {a, hook, [100,2]}]},
+ {200, [{a, hook, [200,1]}]},
+ {upgrade, [{100, [{a, upgrade_hook, [100,1]}]}]},
+ {setup, [{100, [{a, hook, [100,3]}]}]},
+ {normal, [{300, {a, normal_hook, [300,1]}}]}
+ ]),
+ NormalHooks = find_hooks(normal),
+ [{300, [{a, normal_hook, [300,1]}]}] = NormalHooks,
+ UpgradeHooks = find_hooks(upgrade),
+ [{100, [{a, upgrade_hook, [100,1]}]}] = UpgradeHooks,
+ SetupHooks = find_hooks(setup),
+ [{100, [{a,hook,[100,1]},
+ {a,hook,[100,2]},
+ {a,hook,[100,3]}]},
+ {200, [{a,hook,[200,1]}]}] = SetupHooks,
+ ok.
+
+t_expand_vars() ->
+ %% global env
+ application:set_env(setup, vars, [{"PLUS", {apply,erlang,'+',[1,2]}},
+ {"FOO", {value, {foo,1}}}]),
+ %% private env, stdlib
+ application:set_env(stdlib, '$setup_vars',
+ [{"MINUS", {apply,erlang,'-',[4,3]}},
+ {"BAR", {value, "bar"}}]),
+ application:set_env(setup, v1, "/$BAR/$PLUS/$MINUS/$FOO"),
+ application:set_env(setup, v2, {'$value', "$FOO"}),
+ application:set_env(stdlib, v1, {'$string', "$FOO"}),
+ application:set_env(stdlib, v2, {'$binary', "$FOO"}),
+ application:set_env(stdlib, v3, {"$PLUS", "$MINUS", "$BAR"}),
+ %% $BAR and $MINUS are not in setup's context
+ {ok, "/$BAR/3/$MINUS/{foo,1}"} = setup:get_env(setup, v1),
+ {ok, {foo,1}} = setup:get_env(setup, v2),
+ {ok, "{foo,1}"} = setup:get_env(stdlib, v1),
+ {ok, <<"{foo,1}">>} = setup:get_env(stdlib,v2),
+ {ok, {"3", "1", "bar"}} = setup:get_env(stdlib,v3),
+ ok.
+
+-endif.
diff --git a/deps/setup/src/setup_gen.erl b/deps/setup/src/setup_gen.erl
index 80c4269..c85adf5 100644
--- a/deps/setup/src/setup_gen.erl
+++ b/deps/setup/src/setup_gen.erl
@@ -70,18 +70,94 @@ help() ->
%% * `{outdir, Dir}' - Where to put the generated files. Dir is created if not
%% already present.
%% * `{conf, Conf}' - Config file listing apps and perhaps other options
+%% * `{relconf, File}' - can be used instead of `conf', and identifies a
+%% reltool.config file (see {@link //reltool. `reltool'}) to be used as
+%% system description. If a `conf' option is present, it will be used;
+%% otherwise, a `relconf' option must be present.
%%
%% Additional options:
+%%
%% * `{apps, [App]}' - List of applications to include in the release. Only the
%% first instance of this option is considered.
%% * `{add_apps, [App]}' - Adds applications to the ones given in the `apps'
%% option.
+%% * `{remove_apps, Apps}' - Remove `Apps' from the list of applications.
+%% * `{sort_app, App, Before}' - Change the sort order so that `App' comes
+%% before `Before'.
%% * `{include, ConfigFile}' - include options from the given file. The file
%% is processed using `file:script/2'.
%% * `{include_lib, ConfigFile}' - As above, but ConfigFile is named as with
-%% the `-include_lib(...)' directive in Erlang
-%% source code.
-%% * ...
+%% the `-include_lib(...)' directive in Erlang source code.
+%% * `{sys, SysConfigFile}' - Read an existing sys.config file. The environment
+%% found in this file may be redefined by `env' and `set_env' entries
+%% (see below).
+%% * `{env, [{App, [{K,V}]}]}' - Environment variables for the `sys.config'
+%% file. `setup_gen' will merge all `env' entries, where later entries
+%% replace earlier entries (based on the environment variable name).
+%% * `{set_env, [{App, [{KeyPath, V}]}]}' - Modifies existing environment
+%% structures, where `KeyPath' is a list of names (top name must be
+%% an atom) describing a path in a tree structure, where each node
+%% is either a `{Key, SubTree}' or a `{Key, Any, SubTree}' tuple. The
+%% `set_env' function will continue into `SubTree' and either replace
+%% the value representing the full `KeyPath' or create the remaining
+%% subtree.
+%% * `{target, Dir}' - Where to produce the generated files. The files will
+%% end up in `Dir/releases/Vsn/'. If a `reltool.config' file is used,
+%% the `{target_dir, D}' option will be translated into `{target,D}'.
+%% * `{vsn, Vsn}' - System version, used to determine where to generate the
+%% files (see `target' above).
+%% * `{root, RootDir}' - Where to look for applications. Normally, `RootDir'
+%% should represent either `RootDir/lib/*/ebin', or `RootDir/*/ebin',
+%% but if the option `{wild_roots,true}' is given, it can be either
+%% an "ebin" directory, or any parent directory to "ebin" directories.
+%% Multiple `root' options can be given. If `target' is not given
+%% "boot variables" will be generated for each root directory in turn,
+%% named `V1 ... Vn', then generating a relocatable boot script.
+%% * `{pa, Path}' - Prepends `Path' to the code path. Multiple `pa' options
+%% can be given.
+%% * `{pz, Path}' - Appends `Path' to the code path. Multiple `pz' options
+%% can be given.
+%% * `{install, true|false}' - Tells setup whether to also build "install"
+%% scripts and config files. An install script contains the same
+%% applications as the normal script, but only loads them, starting
+%% only the `setup' application. This allows a system to be installed
+%% using "setup hooks", while having all the target system code
+%% and environment available. An "install.config" file is also created,
+%% which, if a `{nodes, Ns}' option is given, also configures Erlang
+%% to wait for all given nodes, and then start the `setup' application
+%% on the first node.
+%% * `{verbose, true|false}' - (Default: `false') Turns on verbose printouts.
+%%
+%% == Application entries ==
+%%
+%% Applications can be represented in a number of different ways:
+%% * `AppName::atom()' - `setup' will search for the latest version
+%% along the current code path.
+%% * `{App::atom(), Vsn::latest|list()}' - where `Vsn' is an explicit version
+%% identifying the application. `latest' instructs `setup' to pick the
+%% latest version, if several versions can be found along the path.
+%% * `{App::atom(), Type::atom()}' - where
+%% `Type::permanent|temporary|transient|load' is the application start
+%% type (or, in the case of 'load', no start at all).
+%% * `{App, Vsn, Type}' - see `App', `Vsn' and `Type' above
+%% * `{App, Vsn, Incl}' - where `Incl' is a list of included applications.
+%% * `{App, Vsn, Type, Incl}'
+%%
+%% == Command-line options ==
+%%
+%% The following options can be given on the command line of `setup_gen':
+%% * `-target Dir' - Equivalent to `{target, Dir}'
+%% * `-name Name' - Equivalent to `{name, Name}'
+%% * `-root Dir' - Equivalent to `{root, Dir}'
+%% * `-relconf F' - Equivalent to `{relconf, F}'
+%% * `-conf F' - Equivalent to `{conf, F}'
+%% * `-install' - Equivalent to `{install, true}'
+%% * `-sys F' - Equivalent to `{sys, F}'
+%% * `-vsn V' - Equivalent to `{vsn, V}'
+%% * `-pa Dir' - Equivalent to `{pa, Dir}'
+%% * `-pz Dir' - Equivalent to `{pa, Dir}'
+%% * `-v' - Equivalent to `{verbose, true}'
+%%
%% @end
%%
run(Options) ->
@@ -343,13 +419,12 @@ env_vars(Options) ->
"~p~n", [Sys, Reason])
end
end,
- SetupEnv = if_install(Options, fun() -> [{setup,
- [{conf,Options}]}]
+ SetupEnv = if_install(Options, fun() -> [{env, [{setup,
+ [{conf,Options}]}]}]
end, []),
- lists:foldl(
- fun(E, Acc) ->
- merge_env(E, Acc)
- end, Env0, [E || {env, E} <- Options] ++ [SetupEnv]).
+ lists:foldl(fun merge_env/2,
+ Env0, [{K,E} || {K, E} <- Options ++ SetupEnv,
+ K == env orelse K == set_env]).
install_env(Env, Options) ->
Dist =
@@ -374,7 +449,7 @@ install_env(Env, Options) ->
lists:keyreplace(kernel, 1, Env, {kernel, Env1})
end.
-merge_env(E, Env) ->
+merge_env({env, E}, Env) ->
lists:foldl(
fun({App, AEnv}, Acc) ->
case lists:keyfind(App, 1, Env) of
@@ -383,12 +458,63 @@ merge_env(E, Env) ->
{_, AEnv1} ->
New = {App, lists:foldl(
fun({K,V}, Acc1) ->
- lists:keystore(K,1,Acc1,{K,V})
+ lists:keystore(K,1,Acc1,{K,V});
+ ({K,W,V}, Acc1) ->
+ lists:keystore(K,1,Acc1,{K,W,V})
end, AEnv1, AEnv)},
lists:keyreplace(App, 1, Acc, New)
end
+ end, Env, E);
+merge_env({set_env, E}, Env) ->
+ lists:foldl(
+ fun({App, AEnv}, Acc) ->
+ case lists:keyfind(App, 1, Env) of
+ false ->
+ Acc ++ [{App, merge_set_env(AEnv, [])}];
+ {_, AEnv1} ->
+ New = {App, merge_set_env(AEnv, AEnv1)},
+ lists:keyreplace(App, 1, Acc, New)
+ end
end, Env, E).
+merge_set_env(E, E0) ->
+ lists:foldl(
+ fun({K, V}, Acc) when is_atom(K) ->
+ lists:keystore(K, 1, Acc, {K, V});
+ ({[H|T], V}, Acc) ->
+ case lists:keyfind(H, 1, Acc) of
+ false ->
+ Acc ++ [{H, mktree(T, V)}];
+ {_, Tree} ->
+ lists:keyreplace(H, 1, Acc, {H, into_tree(T, Tree, V)});
+ {_, V1, Tree} ->
+ lists:keyreplace(
+ H, 1, Acc, {H, V1, into_tree(T, Tree, V)})
+ end;
+ ({[], _}, Acc) ->
+ %% allow, but ignore
+ Acc
+ end, E0, E).
+
+mktree([K], V) ->
+ {K, V};
+mktree([H|T], V) ->
+ {H, [mktree(T, V)]}.
+
+into_tree([K], T0, V) ->
+ lists:keystore(K, 1, T0, {K, V});
+into_tree([H|T] = L, T0, V) when is_list(T0) ->
+ case lists:keyfind(H, 1, T0) of
+ false ->
+ T0 ++ [mktree(L, V)];
+ {_, T1} ->
+ lists:keystore(H, 1, T0, {H, into_tree(T, T1, V)});
+ {_, V1, T1} ->
+ lists:keystore(H, 1, T0, {H, V1, into_tree(T, T1, V)})
+ end;
+into_tree([_|_] = L, _, V) ->
+ [mktree(L, V)].
+
mandatory(K, Conf) ->
case lists:keymember(K, 1, Conf) of
@@ -600,7 +726,9 @@ add_paths(Roots, Opts) ->
filename:join(R, "lib/*/ebin")) of
[] ->
filelib:wildcard(
- filename:join(R, "*/ebin"))
+ filename:join(R, "*/ebin"));
+ Ds ->
+ Ds
end
end, Roots))
end,
diff --git a/deps/setup/xtest/test.conf b/deps/setup/xtest/test.conf
index 6212fbb..4c11b93 100644
--- a/deps/setup/xtest/test.conf
+++ b/deps/setup/xtest/test.conf
@@ -25,6 +25,28 @@ RelDir = filename:join(CWD, "releases").
[kernel,
stdlib,
sasl,
+ setup,
+ {snmp,load},
{testapp, "1", [snmp]}
- ]}
+ ]},
+ {env, [{setup, [{vars, [{"INT122", {value, 122}},
+ {"SHELL", {apply, os, getenv, ["SHELL"]}}]}
+ ]}]},
+ {env, [{testapp, [{a, 1},
+ {'$setup_vars', [{"INT124", {value, 124}}]}
+ ]}]},
+ {env, [{testapp, [{b, "$INT122"},
+ {c, 0},
+ {d, [{a, 1},
+ {b, whatever, [{a, [{a, 1},
+ {b, 2}]}]},
+ {c, 3}]},
+ {e, {'$value', "$INT124"}},
+ {shell, "$SHELL"},
+ {myhome, "$HOME/me"},
+ {mypriv, "$PRIV_DIR/me"}
+ ]}
+ ]},
+ {set_env, [{testapp, [{[d,b,a,b], 17},
+ {[c,a,b], 5}]}]}
].