diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8e56353..b55d175 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,11 +14,19 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - otp: [21, 22, 23, 24, 25] + otp: [25, 26, 27] fail-fast: false container: image: erlang:${{ matrix.otp }} steps: - uses: actions/checkout@v3 - - name: Test + + - name: Mark Git directory as safe + run: | + git config --global --add safe.directory "$GITHUB_WORKSPACE" + git fetch --unshallow + git fetch --tags + + - name: Run CI + shell: bash run: make ci diff --git a/.gitignore b/.gitignore index dd16358..d21fe31 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ deps *.beam -ebin/*.app +ebin/*.beam setup_gen xtest/releases xtest/testapp-*/ebin diff --git a/Emakefile b/Emakefile new file mode 100644 index 0000000..68c7b67 --- /dev/null +++ b/Emakefile @@ -0,0 +1 @@ +{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}. diff --git a/LICENSE b/LICENSE index e454a52..aca9c04 100644 --- a/LICENSE +++ b/LICENSE @@ -176,3 +176,28 @@ END OF TERMS AND CONDITIONS + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2025 Ulf Wiger + + + Licensed 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. diff --git a/Makefile b/Makefile index 6f41d33..9365eb2 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ eunit: compile ${REBAR3} eunit test: eunit compile_test - ERL_LIBS=${PWD}/_build/test/lib ./setup_gen test xtest/test.conf xtest/releases/1 + ERL_LIBS=${PWD}/_build/test/lib ./setup_gen -v -name test -conf xtest/test.conf -out xtest/releases/1 run_test: erl -boot xtest/releases/1/start -config xtest/releases/1/sys diff --git a/README.md b/README.md index 519718c..151388e 100644 --- a/README.md +++ b/README.md @@ -119,5 +119,6 @@ in [`setup`](http://github.com/uwiger/setup/blob/master/doc/setup.md). setup setup_file setup_gen -setup_lib +setup_lib +setup_zomp diff --git a/doc/README.md b/doc/README.md index 6a9304b..fb9c700 100644 --- a/doc/README.md +++ b/doc/README.md @@ -119,5 +119,6 @@ in [`setup`](setup.md). setup setup_file setup_gen -setup_lib +setup_lib +setup_zomp diff --git a/doc/edoc-info b/doc/edoc-info index e64913e..5d19415 100644 --- a/doc/edoc-info +++ b/doc/edoc-info @@ -1,3 +1,3 @@ %% encoding: UTF-8 {application,setup}. -{modules,[setup,setup_file,setup_gen,setup_lib]}. +{modules,[setup,setup_file,setup_gen,setup_lib,setup_zomp]}. diff --git a/doc/setup.md b/doc/setup.md index 92cf4f1..a43539e 100644 --- a/doc/setup.md +++ b/doc/setup.md @@ -104,6 +104,17 @@ Example: ``` +### Running in Zomp (zx) ### + +Setup detects if it's running in a Zomp context, and then maps +the directory names to sensible `zx` defaults: +* `home()` : `ZompDir/etc/Realm/App/Vsn` +* `data_dir()`: `ZompDir/var/Realm/App/Vsn/setup.data` +* `log_dir() : `ZompDir/log/Realm/App` + +By default, setup will not automatically verify these directories. + + ### Customizing setup ### The following environment variables can be used to customize `setup`: @@ -135,7 +146,7 @@ other mode hooks by explicitly calling [`run_hooks/1`](#run_hooks-1). the directories used by setup actually exist. This behavior can be disabled through the environment variable `{verify_directories, false}`. This can be desirable if setup is used mainly e.g. for environment variable expansion, but not for -disk storage. +disk storage. If running in a Zomp context, the default is `false`. * `{run_timeout, Millisecs}` - Set a time limit for how long it may take for setup to process the setup hooks. Default is `infinity`. If the timeout is exceeded, the application start sequence will be aborted, which will diff --git a/doc/setup_file.md b/doc/setup_file.md index 182f6e0..2bde944 100644 --- a/doc/setup_file.md +++ b/doc/setup_file.md @@ -9,7 +9,7 @@ ## Function Index ## -
close/1
consult/1Like file:consult/1, but supports paths into zip and escript archives.
consult_binary/1Like file:consult/1, but operates directly on a binary.
eval_binary/1Like file:script/1, but operates directly on a binary.
eval_binary/2Like file:script/2, but operates directly on a binary.
list_dir/1Like file:list_dir/1, but supports paths into zip and escript archives.
open/2
read_file/1Like file:read_file/1, but supports paths into zip and escript archives.
script/1Like file:script/1, but supports paths into zip and escript archives.
script/2Like file:script/2, but supports paths into zip and escript archives.
+
close/1
consult/1Like file:consult/1, but supports paths into zip and escript archives.
consult_binary/1Like file:consult/1, but operates directly on a binary.
eval_binary/1Like file:script/1, but operates directly on a binary.
eval_binary/2Like file:script/2, but operates directly on a binary.
list_dir/1Like file:list_dir/1, but supports paths into zip and escript archives.
open/2
path_consult/2
path_open/3
path_script/2
path_script/3
read_file/1Like file:read_file/1, but supports paths into zip and escript archives.
script/1Like file:script/1, but supports paths into zip and escript archives.
script/2Like file:script/2, but supports paths into zip and escript archives.
@@ -93,6 +93,46 @@ producing a result. `open(File, Opts) -> any()` + + +### path_consult/2 ### + +

+path_consult(Path, Filename) -> {ok, Terms, FullName} | {error, Reason}
+
+ + + + + +### path_open/3 ### + +

+path_open(Path, Filename, Modes) -> {ok, IoDevice, FullName} | {error, Reason}
+
+ + + + + +### path_script/2 ### + +

+path_script(Path, Filename) -> {ok, Value, FullName} | {error, Reason}
+
+ + + + + +### path_script/3 ### + +

+path_script(Path, Filename, Bindings) -> {ok, Value, FullName} | {error, Reason}
+
+ + + ### read_file/1 ### diff --git a/doc/setup_zomp.md b/doc/setup_zomp.md new file mode 100644 index 0000000..67c8c68 --- /dev/null +++ b/doc/setup_zomp.md @@ -0,0 +1,42 @@ + + +# Module setup_zomp # +* [Function Index](#index) +* [Function Details](#functions) + + + +## Function Index ## + + +
default_dir/1
is_zomp_context/0
setup_conf_path/0
update_env/0
+ + + + +## Function Details ## + + + +### default_dir/1 ### + +`default_dir(X1) -> any()` + + + +### is_zomp_context/0 ### + +`is_zomp_context() -> any()` + + + +### setup_conf_path/0 ### + +`setup_conf_path() -> any()` + + + +### update_env/0 ### + +`update_env() -> any()` + diff --git a/ebin/setup.app b/ebin/setup.app new file mode 100644 index 0000000..032d159 --- /dev/null +++ b/ebin/setup.app @@ -0,0 +1,16 @@ +{application,setup, + [{description,"Generic setup utility for Erlang-based systems"}, + {vsn,"3.0.0"}, + {registered,[]}, + {applications,[kernel,stdlib]}, + {mod,{setup_app,[]}}, + {start_phases,[{run_setup,[]}]}, + {env,[]}, + {maintainers,["Ulf Wiger"]}, + {licenses,["Apache 2.0"]}, + {links,[{"Github","https://github.com/uwiger/setup"}]}, + {files,["src","c_src","include","rebar.config.script","priv", + "rebar.config","rebar.lock","README*","readme*", + "LICENSE*","license*","NOTICE","Makefile"]}, + {modules,[setup,setup_app,setup_file,setup_file_io_server, + setup_gen,setup_lib,setup_srv,setup_sup]}]}. diff --git a/rebar.config b/rebar.config index 2fa99bb..8fbc0f1 100644 --- a/rebar.config +++ b/rebar.config @@ -1,6 +1,9 @@ %% -*- mode: erlang; erlang-indent-level: 4; indent-tabs-mode: nil -*- -{minimum_otp_vsn, "21.0"}. +{minimum_otp_vsn, "27.0"}. {erl_opts, [debug_info]}. +{plugins, [{zx_rebar_plugin, {git, "https://git.qpq.swiss/QPQ-AG/zx_rebar_plugin", + {branch, "master"}}}]}. +{deps, [{zx, {zx, "https://gitlab.com/zxq9/zx", {ref, "2a0437f4"}, "0.14.0"}}]}. {profiles, [ {doc, [ @@ -24,7 +27,7 @@ ]} ]}. -{dialyzer, [{plt_extra_apps, [sasl]}]}. +{dialyzer, [{plt_extra_apps, [sasl, zx]}]}. {post_hooks, [{"(linux|darwin|solaris|freebsd|netbsd|openbsd)", escriptize, diff --git a/rebar.lock b/rebar.lock index 57afcca..f56c8c6 100644 --- a/rebar.lock +++ b/rebar.lock @@ -1 +1,5 @@ -[]. +[{<<"zx">>, + {zx,"https://gitlab.com/zxq9/zx", + {ref,"2a0437f416f94596e4e3b403603dbd028727742d"}, + "0.14.0"}, + 0}]. diff --git a/src/setup.app.src b/src/setup.app.src index 91b98e2..66e5209 100644 --- a/src/setup.app.src +++ b/src/setup.app.src @@ -17,7 +17,7 @@ {application, setup, [ {description, "Generic setup application for Erlang-based systems"}, - {vsn, git}, + {vsn, "zomp"}, {registered, []}, {applications, [ kernel, diff --git a/src/setup.app.src.script b/src/setup.app.src.script new file mode 100644 index 0000000..becbb46 --- /dev/null +++ b/src/setup.app.src.script @@ -0,0 +1,14 @@ +%% -*- erlang-mode; erlang-indent-level: 4; indent-tabs-mode: nil -*- + +[{application, Name, Opts}] = CONFIG. +case lists:keyfind(vsn, 1, Opts) of + {vsn, "zomp"} -> + ZompMetaF = filename:join(filename:dirname(filename:dirname(SCRIPT)), "zomp.meta"), + {ok, ZMeta} = file:consult(ZompMetaF), + {_, {_, _, {Vmaj,Vmin,Vpatch}}} = lists:keyfind(package_id, 1, ZMeta), + VsnStr = unicode:characters_to_list(io_lib:fwrite("~w.~w.~w", [Vmaj, Vmin, Vpatch])), + Opts1 = lists:keyreplace(vsn, 1, Opts, {vsn, VsnStr}), + [{application, Name, Opts1}]; + _ -> + CONFIG +end. diff --git a/src/setup.erl b/src/setup.erl index 5fb193a..c602ddb 100644 --- a/src/setup.erl +++ b/src/setup.erl @@ -104,6 +104,15 @@ %% {ok,"/Users/uwiger/git/setup/foo"} %% %% +%% == Running in Zomp (zx) == +%% Setup detects if it's running in a Zomp context, and then maps +%% the directory names to sensible `zx' defaults: +%% * `home()' : `ZompDir/etc/Realm/App/Vsn' +%% * `data_dir()': `ZompDir/var/Realm/App/Vsn/setup.data' +%% * `log_dir() : `ZompDir/log/Realm/App' +%% +%% By default, setup will not automatically verify these directories. +%% %% == Customizing setup == %% The following environment variables can be used to customize `setup': %% * `{home, Dir}' - The topmost directory of the running system. This should @@ -134,7 +143,7 @@ %% the directories used by setup actually exist. This behavior can be disabled through %% the environment variable `{verify_directories, false}'. This can be desirable %% if setup is used mainly e.g. for environment variable expansion, but not for -%% disk storage. +%% disk storage. If running in a Zomp context, the default is `false'. %% * `{run_timeout, Millisecs}' - Set a time limit for how long it may take for %% setup to process the setup hooks. Default is `infinity'. If the timeout %% is exceeded, the application start sequence will be aborted, which will @@ -175,6 +184,7 @@ -export([main/1]). % new escript entry point -include_lib("kernel/include/file.hrl"). +-include_lib("kernel/include/logger.hrl"). -ifdef(TEST). -compile([export_all, nowarn_export_all]). @@ -189,6 +199,8 @@ _ -> ok end). +-type dir_type() :: 'home' | 'data' | 'log'. + %% @spec home() -> Directory %% @doc Returns the configured `home' directory, or a best guess (`$CWD') %% @end @@ -199,14 +211,12 @@ home() -> home_(Vis) -> case get_env_v(setup, home, Vis) of undefined -> - CWD = cwd(), - D = filename:absname(CWD), + Dir = default_dir(home), + D = filename:absname(Dir), application:set_env(setup, home, D), D; - {ok, D} when is_binary(D) -> - binary_to_list(D); - {ok, D} when is_list(D) -> - D; + {ok, D} -> + unicode:characters_to_list(D); {error,_} = Error -> Error; Other -> @@ -221,7 +231,7 @@ log_dir() -> log_dir_([]). log_dir_(Vis) -> - setup_dir(log_dir, "log." ++ atom_to_list(node()), Vis). + setup_dir(log_dir, default_dir(log), Vis). %% @spec data_dir() -> Directory %% @doc Returns the configured data dir, or a best guess (`home()/data.Node'). @@ -232,7 +242,20 @@ data_dir() -> data_dir_([]). data_dir_(Vis) -> - setup_dir(data_dir, "data." ++ atom_to_list(node()), Vis). + setup_dir(data_dir, default_dir(data), Vis). + +-spec default_dir(dir_type()) -> string(). +default_dir(Type) -> + case setup_zomp:is_zomp_context() of + true -> + setup_zomp:default_dir(Type); + false -> + setup_default_dir(Type) + end. + +setup_default_dir(home) -> cwd(); +setup_default_dir(log) -> "log." ++ atom_to_list(node()); +setup_default_dir(data) -> "data." ++ atom_to_list(node()). setup_dir(Key, Default, Vis) -> case get_env_v(setup, Key, Vis) of @@ -249,7 +272,12 @@ setup_dir(Key, Default, Vis) -> end. maybe_verify_directories() -> - case get_env(setup, verify_directories, true) of + IsZomp = setup_zomp:is_zomp_context(), + %% If zomp context, we rely on zomp to verify the directories. + %% Apps need to verify any sub-directories they need anyway. + %% + %% The default action is: verify if not in zomp, otherwise not. + case get_env(setup, verify_directories, not IsZomp) of true -> verify_directories(); false -> @@ -442,7 +470,7 @@ expand_env(_, {T,"$env(" ++ S} = X, A, Vis) {undefined, '$string'} -> ""; {undefined, '$binary'} -> <<>>; {{ok,V} , '$value'} -> V; - {{ok,V} , '$string'} -> binary_to_list(stringify(V)); + {{ok,V} , '$string'} -> unicode:characters_to_list(stringify(V)); {{ok,V} , '$binary'} -> stringify(V) end catch @@ -716,8 +744,8 @@ find_app(A, LibDirs) -> to_string(A) when is_atom(A) -> atom_to_list(A); -to_string(A) when is_list(A) -> - A. +to_string(A) when is_list(A); is_binary(A) -> + unicode:characters_to_list(A). is_app_dir(AStr, D) -> Pat = AStr ++ "(-[0-9]+(\\..+)?)?/ebin\$", @@ -833,7 +861,7 @@ reload_app(A, _OldVsn, _OldPath, NewPath, NewVsn, Script, _NewApp) -> _ = remove_path(NewPath, A), case release_handler:eval_appup_script(A, NewVsn, LibDir, Script) of {ok, Unpurged} -> - _ = [code:purge(M) || {M, brutal_purge} <- Unpurged], + _ = [code:purge(M) || {brutal_purge, M} <- Unpurged], {ok, [U || {_, Mode} = U <- Unpurged, Mode =/= brutal_purge]}; Other -> Other diff --git a/src/setup_app.erl b/src/setup_app.erl index 3ee07f4..70a8384 100644 --- a/src/setup_app.erl +++ b/src/setup_app.erl @@ -22,7 +22,16 @@ start_phase/3, stop/1]). +-include_lib("kernel/include/logger.hrl"). + start(_Type, _Args) -> + case setup_zomp:is_zomp_context() of + true -> + setup_zomp:update_env(), + load_setup_conf(setup_zomp:setup_conf_path()); + false -> + load_setup_conf(default_setup_conf_path()) + end, setup_sup:start_link(). start_phase(run_setup, _Type, []) -> @@ -36,3 +45,58 @@ start_phase(run_setup, _Type, []) -> stop(_) -> ok. + +default_setup_conf_path() -> + ["."]. + +load_setup_conf(Path) -> + case setup:get_env(setup, conf) of + {ok, F} -> + load_setup_conf_(F); + undefined -> + try_path_load(Path) + end. + +load_setup_conf_(F) -> + case lists:reverse(F) of + "tpircs.gifnoc." ++ _ -> %% .config.script + Cfg = ok(setup_file:script(F, script_env()), script, F), + process_conf(Cfg, script, F); + "gifnoc." ++ _ -> %% .config + Cfg = ok(file:consult(F), consult, F), + process_conf(Cfg, consult, F); + _ -> + ?LOG_WARNING("Unusual setup conf filename (~s), will try file:consult()", [F]), + Cfg = ok(file:consult(F), consult, F), + process_conf(Cfg, consult, F) + end. + +try_path_load(Path) -> + case setup_file:path_script(Path, "setup.config.script") of + {error, enoent} -> + case setup_file:path_consult(Path, "setup.config") of + {ok, List, Full} -> + process_conf(List, consult, Full); + {error, enoent} -> + ok + end; + {ok, Cfg, Full} -> + process_conf(Cfg, script, Full) + end. + +script_env() -> + []. + +process_conf(Cfg, _Op, _F) -> + lists:foreach( + fun({App, Vars}) -> + lists:foreach( + fun({K, V}) -> + application:set_env(App, K, V) + end, Vars) + end, Cfg). + +ok({ok, Value}, _, _) -> + Value; +ok(Error, Op, F) -> + error({unexpected, {Error, Op, F}}). diff --git a/src/setup_file.erl b/src/setup_file.erl index 76083df..a789e8e 100644 --- a/src/setup_file.erl +++ b/src/setup_file.erl @@ -9,7 +9,11 @@ eval_binary/1, eval_binary/2, script/1, - script/2]). + script/2, + path_open/3, + path_consult/2, + path_script/2, + path_script/3]). -include_lib("stdlib/include/zip.hrl"). -include_lib("kernel/include/file.hrl"). @@ -163,6 +167,150 @@ script(File, Bindings) -> Other end. +-spec path_consult(Path, Filename) -> {ok, Terms, FullName} | {error, Reason} when + Path :: [Dir], + Dir :: file:name_all(), + Filename :: file:name_all(), + Terms :: [term()], + FullName :: file:filename_all(), + Reason :: file:posix() | badarg | terminated | system_limit + | {Line :: integer(), Mod :: module(), Term :: term()}. + +path_consult(Path, File) -> + case path_open(Path, File, [read]) of + {ok, Fd, Full} -> + case consult_stream(Fd) of + {ok, List} -> + _ = close(Fd), + {ok, List, Full}; + E1 -> + _ = close(Fd), + E1 + end; + E2 -> + E2 + end. + +-spec path_script(Path, Filename) -> + {ok, Value, FullName} | {error, Reason} when + Path :: [Dir :: file:name_all()], + Filename :: file:name_all(), + Value :: term(), + FullName :: file:filename_all(), + Reason :: file:posix() | badarg | terminated | system_limit + | {Line :: integer(), Mod :: module(), Term :: term()}. + +path_script(Path, File) -> + path_script(Path, File, erl_eval:new_bindings()). + +%% The same as [`path_script/2`](`path_script/2`) but the variable bindings +%% `Bindings` are used in the evaluation. See `m:erl_eval` about variable bindings. +-spec path_script(Path, Filename, Bindings) -> + {ok, Value, FullName} | {error, Reason} when + Path :: [Dir :: file:name_all()], + Filename :: file:name_all(), + Bindings :: erl_eval:binding_struct(), + Value :: term(), + FullName :: file:filename_all(), + Reason :: file:posix() | badarg | terminated | system_limit + | {Line :: integer(), Mod :: module(), Term :: term()}. + +path_script(Path, File, Bs) -> + case path_open(Path, File, [read]) of + {ok,Fd,Full} -> + case eval_stream(Fd, return, Bs) of + {ok,R} -> + _ = close(Fd), + {ok, R, Full}; + E1 -> + _ = close(Fd), + E1 + end; + E2 -> + E2 + end. + +%% We duplicate this as well, since the open() function needs to be modified +%% +-spec path_open(Path, Filename, Modes) -> + {ok, IoDevice, FullName} | {error, Reason} when + Path :: [Dir :: file:name_all()], + Filename :: file:name_all(), + Modes :: [file:mode() | directory], + IoDevice :: file:io_device(), + FullName :: file:filename_all(), + Reason :: file:posix() | badarg | system_limit. + +path_open(PathList, Name, Mode) -> + case file_name(Name) of + {error, _} = Error -> + Error; + FileName -> + case filename:pathtype(FileName) of + relative -> + path_open_first(PathList, FileName, Mode, enoent); + _ -> + case open(Name, Mode) of + {ok, Fd} -> + {ok, Fd, Name}; + Error -> + Error + end + end + end. + +path_open_first([Path|Rest], Name, Mode, LastError) -> + case file_name(Path) of + {error, _} = Error -> + Error; + FilePath -> + FileName = fname_join(FilePath, Name), + case open(FileName, Mode) of + {ok, Fd} -> + {ok, Fd, FileName}; + {error, Reason} when Reason =:= enoent; Reason =:= enotdir -> + path_open_first(Rest, Name, Mode, LastError); + Error -> + Error + end + end; +path_open_first([], _Name, _Mode, LastError) -> + {error, LastError}. + +fname_join(".", Name) -> + Name; +fname_join(Dir, Name) -> + filename:join(Dir, Name). + +%% Duplicated since it's not exported from file.erl +%% + +%% file_name(FileName) +%% Generates a flat file name from a deep list of atoms and +%% characters (integers). + +file_name(N) when is_binary(N) -> + N; +file_name(N) -> + try + file_name_1(N,file:native_name_encoding()) + catch Reason -> + {error, Reason} + end. + +file_name_1([C|T],latin1) when is_integer(C), C < 256-> + [C|file_name_1(T,latin1)]; +file_name_1([C|T],utf8) when is_integer(C) -> + [C|file_name_1(T,utf8)]; +file_name_1([H|T],E) -> + file_name_1(H,E) ++ file_name_1(T,E); +file_name_1([],_) -> + []; +file_name_1(N,_) when is_atom(N) -> + atom_to_list(N); +file_name_1(_,_) -> + throw(badarg). + contains_zip_file(File) when is_atom(File) -> contains_zip_file(atom_to_binary(File, utf8)); contains_zip_file(File) -> diff --git a/src/setup_zomp.erl b/src/setup_zomp.erl new file mode 100644 index 0000000..3b6cc53 --- /dev/null +++ b/src/setup_zomp.erl @@ -0,0 +1,59 @@ +-module(setup_zomp). + +-export([ is_zomp_context/0 + , package_id/0 + , update_env/0 + , setup_conf_path/0 + , default_dir/1 ]). + +-include_lib("kernel/include/logger.hrl"). + +is_zomp_context() -> + is_pid(whereis(zx_daemon)). + +update_env() -> + Args = init:get_plain_arguments(), + ?LOG_INFO("Plain args: ~p", [Args]), + look_for_setup_env(Args). + +default_dir(home) -> ppath(etc); +default_dir(data) -> ppath(var); +default_dir(log) -> + #{package_id := {Realm, App, _Vsn}} = zx_daemon:meta(), + zx_lib:ppath(log, {Realm, App}). + +ppath(Type) -> + zx_lib:ppath(Type, package_id()). + +package_id() -> + #{package_id := PId} = zx_daemon:meta(), + PId. + +setup_conf_path() -> + TopPPath = ppath(lib), + [".", TopPPath]. + +look_for_setup_env(["-setup", K, V | Rest]) -> + ?LOG_INFO("Processing env: ~p ~p", [K, V]), + process_env(K, V), + look_for_setup_env(Rest); +look_for_setup_env([_|T]) -> + look_for_setup_env(T); +look_for_setup_env([]) -> + ok. + +process_env(K, V) -> + case valid_env(K, V) of + {true, {Key, Value}} -> + ?LOG_INFO("Valid env: ~p: ~p", [Key, Value]), + application:set_env(setup, Key, Value); + false -> + ?LOG_INFO("Invalid env", []), + ignore + end. + +valid_env("log_dir" , D) -> {true, {log_dir , D}}; +valid_env("data_dir", D) -> {true, {data_dir, D}}; +valid_env("conf", D) -> {true, {conf , D}}; +valid_env(_, _) -> + false. diff --git a/xtest/testapp-1/src/testapp_sup.erl b/xtest/testapp-1/src/testapp_sup.erl index 9808c9f..bfcc3f6 100644 --- a/xtest/testapp-1/src/testapp_sup.erl +++ b/xtest/testapp-1/src/testapp_sup.erl @@ -24,4 +24,11 @@ start_link() -> %% =================================================================== init([]) -> + %% {ok, { {one_for_one, 5, 10}, [?CHILD(testapp_p1, worker)]} }. + %% cheating just to get a process running testapp_p1 + %% Unfortunately, if we add it as a supervisor child, the supervisor + %% code_change() will carry that childspec over to the next version, + %% which will cause an 'undef' exception when it tries to restart + %% the process after the brutal purge. + testapp_p1:start_link(), {ok, { {one_for_one, 5, 10}, []} }. diff --git a/zomp.meta b/zomp.meta new file mode 100644 index 0000000..400194e --- /dev/null +++ b/zomp.meta @@ -0,0 +1,17 @@ +{name,"setup"}. +{type,app}. +{modules,[]}. +{prefix,"setup"}. +{desc,"Generic setup utility for Erlang-based systems"}. +{author,"Ulf Wiger"}. +{package_id,{"uwiger","setup",{3,0,0}}}. +{deps,[]}. +{key_name,none}. +{a_email,"ulf@wiger.net"}. +{c_email,"ulf@wiger.net"}. +{copyright,"Ulf Wiger"}. +{file_exts,[]}. +{license,"Apache-2.0"}. +{repo_url,"https://github.com/uwiger/setup"}. +{tags,[]}. +{ws_url,[]}. diff --git a/zompify.sh b/zompify.sh new file mode 100755 index 0000000..66aca1c --- /dev/null +++ b/zompify.sh @@ -0,0 +1,42 @@ +#!/bin/sh +set -e + +APP=$(basename "$PWD") + +SRC="_build/default/lib/$APP" +DST="$PWD/_build/zomp/lib/$APP" +IGNORE_FILE="zomp.ignore" + +mkdir -p "$DST" + +# Remove broken symlinks +find "$SRC" -type l ! -exec test -e {} \; -delete || true + +# Build ignore matcher +IGNORE_TEMP=$(mktemp) +trap "rm -f $IGNORE_TEMP" EXIT + +# Expand globs in zomp.ignore to patterns suitable for grep +if [ -e "$IGNORE_FILE" ]; then + grep -v '^\s*#' "$IGNORE_FILE" | sed 's#/#\\/#g' | sed 's/\./\\./g' | sed 's/\*/.*/g' > "$IGNORE_TEMP" +fi + +# Copy Git-tracked and Zomp-allowed files +git ls-files -z | while IFS= read -r -d '' file; do + # Skip if ignored + echo "$file" | grep -Eq -f "$IGNORE_TEMP" && continue + # Only copy if file exists in the build dir + if [ -e "$SRC/$file" ]; then + mkdir -p "$DST/$(dirname "$file")" + cp -a "$SRC/$file" "$DST/$file" + fi +done + +rm "$IGNORE_TEMP" + +# Copy metadata +cp "$PWD/zomp.meta" "$DST/" +cp "$PWD/Emakefile" "$DST/" + +# Clean up beam files just in case +[ -d "$DST/ebin" ] && find "$DST/ebin" -name '*.beam' -exec rm -f {} + || true