Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
deps
*.beam
ebin/*.app
ebin/*.beam
setup_gen
xtest/releases
xtest/testapp-*/ebin
Expand Down
1 change: 1 addition & 0 deletions Emakefile
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"src/*", [debug_info, {i, "include/"}, {outdir, "ebin/"}]}.
25 changes: 25 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
Expand Up @@ -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 <ulf@wiger.net>


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.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
16 changes: 16 additions & 0 deletions ebin/setup.app
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{application,setup,
[{description,"Generic setup utility for Erlang-based systems"},
{vsn,"2.2.3"},
{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]}]}.
19 changes: 17 additions & 2 deletions src/setup.erl
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,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]).
Expand Down Expand Up @@ -221,7 +222,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').
Expand All @@ -232,7 +233,21 @@ 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).

default_dir(Type) ->
case setup_zomp:is_zomp_context() of
true ->
case setup_zomp:default_dir(Type) of
undefined -> setup_default_dir(Type);
Dir -> Dir
end;
false ->
setup_default_dir(Type)
end.

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
Expand Down
64 changes: 64 additions & 0 deletions src/setup_app.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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, []) ->
Expand All @@ -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}}).
150 changes: 149 additions & 1 deletion src/setup_file.erl
Original file line number Diff line number Diff line change
Expand Up @@ -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").
Expand Down Expand Up @@ -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) ->
Expand Down
Loading
Loading