Finally some time to play with this again!
This commit is contained in:
parent
ae7b117828
commit
2fc295fe07
@ -6,7 +6,7 @@ Project information can be found at https://zxq9com/zx/ and https://github.com/z
|
||||
ZX is delivered as a zip file containing:
|
||||
- `zomp.tar.gz`: An archive of a current working zx/zomp installation
|
||||
- `install.escript`: The main installation script
|
||||
- `install_unix` and `install_windows.cms`: System-specific installation starters
|
||||
- `install_unix` and `install_windows.cmd`: System-specific installation starters
|
||||
- `README.*` files such as this one
|
||||
- `LICENSE`
|
||||
- `notify.vbs`: A hacky VBS script to communicate with the Windows GUI during setup
|
||||
|
||||
@ -2,5 +2,5 @@
|
||||
[{description, "Zomp client program"},
|
||||
{vsn, "0.1.0"},
|
||||
{applications, [stdlib, kernel]},
|
||||
{modules, [zx, zx_daemon]},
|
||||
{modules, [zx, zxd_sup, zxd]},
|
||||
{mod, {zx, []}}]}.
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
71
zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl
Normal file
71
zomp/lib/otpr-zx/0.1.0/src/zx_conn.erl
Normal file
@ -0,0 +1,71 @@
|
||||
%%% @doc
|
||||
%%% ZX Connector
|
||||
%%%
|
||||
%%% This module represents a connection to a Zomp server.
|
||||
%%% Multiple connections can exist at a given time, but each one of these processes
|
||||
%%% only represents a single connection at a time.
|
||||
%%% @end
|
||||
|
||||
-module(zx_conn).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([start/1, stop/0]).
|
||||
-export([start_link/1]).
|
||||
|
||||
-include("zx_logger.erl").
|
||||
|
||||
|
||||
|
||||
%%% Startup
|
||||
|
||||
-spec start(Target) -> Result
|
||||
when Target :: zx:host(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: term().
|
||||
|
||||
start(Target) ->
|
||||
zx_conn_sup:start_conn(Target).
|
||||
|
||||
|
||||
-spec start_link(Target) ->
|
||||
when Target :: zx:host(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: term().
|
||||
%% @private
|
||||
%% Starts a connector with a target host in its state.
|
||||
|
||||
start_link(Target) ->
|
||||
proc_lib:start_link(?MODULE, init, [self(), Target]).
|
||||
|
||||
|
||||
-spec init(Parent, Target) -> no_return()
|
||||
when Parent :: pid(),
|
||||
Target :: zx:host().
|
||||
|
||||
init(Parent, Target) ->
|
||||
ok = log(info, "Connecting to ~tp", [Target]),
|
||||
Debug = sys:debug_options([]),
|
||||
ok = proc_lib:init_ack(Parent, {ok, self()}),
|
||||
connect(Parent, Debug, Target).
|
||||
|
||||
|
||||
-spec connect(Parent, Debug, Target) -> no_return().
|
||||
|
||||
connect(Parent, Debug, {Host, Port}) ->
|
||||
Options = [{packet, 4}, {mode, binary}, {active, true}],
|
||||
case gen_tcp:connect(Host, Port, Options, 5000) of
|
||||
{ok, Socket} ->
|
||||
confirm(Parent, Debug, Socket);
|
||||
{error, Error} ->
|
||||
ok = log(warning, "Connection problem with ~tp: ~tp", [Node, Error]),
|
||||
ok = zx_daemon:report(
|
||||
connect_user(Realm, Rest)
|
||||
end.
|
||||
|
||||
|
||||
confirm(Parent, Debug, Socket) ->
|
||||
|
||||
72
zomp/lib/otpr-zx/0.1.0/src/zx_conn_sup.erl
Normal file
72
zomp/lib/otpr-zx/0.1.0/src/zx_conn_sup.erl
Normal file
@ -0,0 +1,72 @@
|
||||
%%% @doc
|
||||
%%% The ZX Connection Supervisor
|
||||
%%%
|
||||
%%% This supervisor maintains the lifecycle of all zomp_client worker processes.
|
||||
%%% @end
|
||||
|
||||
-module(zx_conn_sup).
|
||||
-behavior(supervisor).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([start_conn/1]).
|
||||
-export([start_link/0]).
|
||||
-export([init/1]).
|
||||
|
||||
|
||||
|
||||
%%% Interface Functions
|
||||
|
||||
-spec start_conn(Host) -> Result
|
||||
when Host :: zx:host(),
|
||||
Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: term().
|
||||
%% @doc
|
||||
%% Start an upstream connection handler.
|
||||
%% (Should only be called from zx_conn).
|
||||
|
||||
start_conn(Host) ->
|
||||
supervisor:start_child(?MODULE, [Host]).
|
||||
|
||||
|
||||
|
||||
%%% Startup
|
||||
|
||||
-spec start_link() -> Result
|
||||
when Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: {already_started, pid()}
|
||||
| {shutdown, term()}
|
||||
| term().
|
||||
%% @private
|
||||
%% Called by zx_daemon_sup.
|
||||
%%
|
||||
%% Spawns a single, registered supervisor process.
|
||||
%%
|
||||
%% Error conditions, supervision strategies, and other important issues are
|
||||
%% explained in the supervisor module docs:
|
||||
%% http://erlang.org/doc/man/supervisor.html
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, none).
|
||||
|
||||
|
||||
-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
%% @private
|
||||
%% Do not call this function directly -- it is exported only because it is a
|
||||
%% necessary part of the OTP supervisor behavior.
|
||||
|
||||
init(none) ->
|
||||
RestartStrategy = {simple_one_for_one, 1, 60},
|
||||
|
||||
Client = {zx_conn,
|
||||
{zx_conn, start_link, []},
|
||||
temporary,
|
||||
brutal_kill,
|
||||
worker,
|
||||
[zx_conn]},
|
||||
|
||||
Children = [Client],
|
||||
{ok, {RestartStrategy, Children}}.
|
||||
@ -2,71 +2,456 @@
|
||||
%%% ZX Daemon
|
||||
%%%
|
||||
%%% Resident execution daemon and runtime interface to Zomp.
|
||||
%%%
|
||||
%%% The daemon lives in the system and does background tasks and also acts as the
|
||||
%%% serial interface for any complex functions involving network tasks that may fail
|
||||
%%% and need to be retried or may span realms.
|
||||
%%%
|
||||
%%% In particular, the functions accessible to programs launched by ZX can interact
|
||||
%%% with Zomp realms via the zx_daemon, and non-administrative tasks that involve
|
||||
%%% maintaining a connection with a Zomp constellation can be abstracted behind the
|
||||
%%% zx_daemon. Administrative tasks, however, essentially stateless request/response
|
||||
%%% pairs.
|
||||
%%% @end
|
||||
|
||||
-module(zx_daemon).
|
||||
-export([]).
|
||||
-behavior(gen_server).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
|
||||
%%% App execution loop
|
||||
-export([pass_meta/3,
|
||||
subscribe/1, unsubscribe/0,
|
||||
list_packages/1, list_versions/1, query_latest/1,
|
||||
fetch/1]).
|
||||
-export([report/1]).
|
||||
-export([start_link/0]).
|
||||
-export([init/1, handle_call/3, handle_cast/2, handle_info/2,
|
||||
code_change/3, terminate/2]).
|
||||
|
||||
-spec exec_wait(State) -> no_return()
|
||||
when State :: state().
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
|
||||
|
||||
%%% Type Definitions
|
||||
|
||||
-record(s,
|
||||
{meta = none :: none | zx:package_meta(),
|
||||
dir = none :: none | file:filename(),
|
||||
argv = none :: none | [string()],
|
||||
reqp = none :: none | pid(),
|
||||
reqm = none :: none | reference(),
|
||||
connp = none :: none | pid(),
|
||||
connm = none :: none | reference(),
|
||||
prime = none :: none | zx:realm(),
|
||||
hosts = [] :: #s{zx:realm() := [zx:host()]}}).
|
||||
|
||||
-type state() :: #s{}.
|
||||
|
||||
-type hosts() :: #{zx:realm() := [zx:host()]}.
|
||||
-type conn_report() :: {connected, Realms :: [zx:realm()]}
|
||||
| conn_fail
|
||||
| disconnected.
|
||||
|
||||
|
||||
%%% Service Interface
|
||||
|
||||
-spec pass_meta(Meta, Dir, ArgV) -> ok
|
||||
when Meta :: zx:package_meta(),
|
||||
Dir :: file:filename(),
|
||||
ArgV :: [string()].
|
||||
%% @private
|
||||
%% Execution maintenance loop.
|
||||
%% Once an application is started by zompc this process will wait for a message from
|
||||
%% the application if that application was written in a way to take advantage of zompc
|
||||
%% facilities such as post-start upgrade checking.
|
||||
%% Load the daemon with the primary running application's meta data and location within
|
||||
%% the filesystem. This step allows running development code from any location in
|
||||
%% the filesystem against installed dependencies without requiring any magical
|
||||
%% references.
|
||||
%%
|
||||
%% NOTE:
|
||||
%% Adding clauses to this `receive' is where new functionality belongs.
|
||||
%% It may make sense to add a `zompc_lib' as an available dependency authors could
|
||||
%% use to interact with zompc without burying themselves under the complexity that
|
||||
%% can come with naked send operations. (Would it make sense, for example, to have
|
||||
%% the registered zompc process convert itself to a gen_server via zompc_lib to
|
||||
%% provide more advanced functionality?)
|
||||
%% This call blocks specifically so that we can be certain that the target application
|
||||
%% cannot be started before the impact of this call has taken full effect. It cannot
|
||||
%% be known whether the very first thing the target application will do is send this
|
||||
%% process an async message. That implies that this should only ever be called once,
|
||||
%% by the launching process (which normally terminates shortly thereafter).
|
||||
|
||||
exec_wait(State = #s{pid = none, mon = none}) ->
|
||||
pass_meta(Meta, Dir, ArgV) ->
|
||||
gen_server:call(?MODULE, {pass_meta, Meta, Dir, ArgV}).
|
||||
|
||||
|
||||
-spec subscribe(Package) -> Result
|
||||
when Package :: zx:package(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: illegal_requestor
|
||||
| {already_subscribed, zx:package()}.
|
||||
%% @doc
|
||||
%% Subscribe to update notification for a for a particular package.
|
||||
%% The daemon is designed to monitor a single package at a time, so a second call to
|
||||
%% subscribe/1 will return an `already_subscribed' error, or possibly an
|
||||
%% `illegal_requestor' error in the case that a second call is made from a different
|
||||
%% process than the original requestor.
|
||||
%% Other functions can be used to query the status of a package at an arbitrary time.
|
||||
|
||||
subscribe(Package) ->
|
||||
gen_server:call(?MODULE, {subscribe, self(), Package}).
|
||||
|
||||
|
||||
-spec unsubscribe() -> ok.
|
||||
%% @doc
|
||||
%% Instructs the daemon to unsubscribe if subscribed. Has no effect if not subscribed.
|
||||
|
||||
unsubscribe() ->
|
||||
gen_server:call(?MODULE, unsubscribe).
|
||||
|
||||
|
||||
-spec list_packages(Realm) -> Result
|
||||
when Realm :: zx:realm(),
|
||||
Result :: {ok, Packages :: [zx:package()]}
|
||||
| {error, Reason},
|
||||
Reason :: bad_realm
|
||||
| no_realm
|
||||
| network.
|
||||
|
||||
list_packages(Realm) ->
|
||||
gen_server:call(?MODULE, {list, Realm}).
|
||||
|
||||
|
||||
-spec list_versions(Package) -> Result
|
||||
when Package :: zx:package(),
|
||||
Result :: {ok, Versions :: [zx:version()]}
|
||||
| {error, Reason},
|
||||
Reason :: bad_realm
|
||||
| bad_package
|
||||
| network.
|
||||
%% @doc
|
||||
%% List all versions of a given package. Useful especially for developers wanting to
|
||||
%% see a full list of maintained packages to include as dependencies.
|
||||
|
||||
list_versions(Package) ->
|
||||
gen_server:call(?MODULE, {list_versions, Package}).
|
||||
|
||||
|
||||
-spec query_latest(Object) -> Result
|
||||
when Object :: zx:package() | zx:package_id(),
|
||||
Result :: {ok, version()}
|
||||
| {error, Reason},
|
||||
Reason :: bad_realm
|
||||
| bad_package
|
||||
| bad_version
|
||||
| network.
|
||||
%% @doc
|
||||
%% Check for the latest version of a package, with or without a version provided to
|
||||
%% indicate subversion limit. Useful mostly for developers checking for a latest
|
||||
%% version of a package.
|
||||
%%
|
||||
%% While this function could be used as a primitive operation in a dynamic dependency
|
||||
%% upgrade scheme, that is not its intent. You will eventually divide by zero trying
|
||||
%% to implement such a feature, open a portal to Oblivion, and monsters will consume
|
||||
%% all you love. See? That's a horrible idea. You have been warned.
|
||||
|
||||
query_latest(Object) ->
|
||||
gen_server:call(?MODULE, {query_latest, Object}).
|
||||
|
||||
|
||||
-spec fetch(PackageIDs) -> Result
|
||||
when PackageIDs :: [zx:package_id()],
|
||||
Result :: {{ok, [zx:package_id()]},
|
||||
{error, [{zx:package_id(), Reason}]}},
|
||||
Reason :: bad_realm
|
||||
| bad_package
|
||||
| bad_version
|
||||
| network.
|
||||
%% @doc
|
||||
%% Ensure a list of packages is available locally, fetching any missing packages in
|
||||
%% the process. This is intended to abstract the task of ensuring that a list of
|
||||
%% dependencies is available locally prior to building/running a dependent app or lib.
|
||||
|
||||
fetch([]) ->
|
||||
{{ok, []}, {error, []}};
|
||||
fetch(PackageIDs) ->
|
||||
gen_server:call(?MODULE, {fetch, PackageIDs}).
|
||||
|
||||
|
||||
|
||||
%%% Connection interface
|
||||
|
||||
-spec report(Message) -> ok
|
||||
when Message :: {connected, Realms :: [zx:realm()]}
|
||||
| conn_fail
|
||||
| disconnected.
|
||||
%% @private
|
||||
%% Should only be called by a zx_conn. This function is how a zx_conn reports its
|
||||
%% current connection status.
|
||||
|
||||
report(Message) ->
|
||||
gen_server:cast(?MODULE, {report, self(), Message}).
|
||||
|
||||
|
||||
%%% Startup
|
||||
|
||||
-spec start_link() -> {ok, pid()} | {error, term()}.
|
||||
%% @private
|
||||
%% Startup function -- intended to be called by supervisor.
|
||||
|
||||
start_link() ->
|
||||
gen_server:start_link({local, ?MODULE}, ?MODULE, none, []).
|
||||
|
||||
|
||||
-spec init(none) -> {ok, state()}.
|
||||
|
||||
init(none) ->
|
||||
{ok, #s{}}.
|
||||
|
||||
|
||||
|
||||
%%% gen_server
|
||||
|
||||
%% @private
|
||||
%% gen_server callback for OTP calls
|
||||
|
||||
handle_call({pass_meta, Meta, Dir, ArgV}, _, State) ->
|
||||
{Result, NewState} = do_pass_meta(Requestor, Package, ArgV, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({subscribe, Requestor, Package}, _, State) ->
|
||||
{Result, NewState} = do_subscribe(Requestor, Package, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({query_latest, Object}, _, State) ->
|
||||
{Result, NewState} = do_query_latest(Object, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call({fetch, Packages}, _, State) ->
|
||||
{Result, NewState} = do_fetch(Packages, State),
|
||||
{reply, Result, NewState};
|
||||
handle_call(Unexpected, From, State) ->
|
||||
ok = log(warning, "Unexpected call ~tp: ~tp", [From, Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
%% @private
|
||||
%% gen_server callback for OTP casts
|
||||
|
||||
handle_cast(unsubscribe, State) ->
|
||||
NewState = do_unsubscribe(State),
|
||||
{noreply, NewState};
|
||||
handle_cast({report, From, Message}, State) ->
|
||||
NewState = do_report(From, Message, State),
|
||||
{noreply, NewState};
|
||||
handle_cast(Unexpected, State) ->
|
||||
ok = log(warning, "Unexpected cast: ~tp", [Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
%% @private
|
||||
%% gen_sever callback for general Erlang message handling
|
||||
|
||||
handle_info(Unexpected, State) ->
|
||||
ok = log(warning, "Unexpected info: ~tp", [Unexpected]),
|
||||
{noreply, State}.
|
||||
|
||||
|
||||
%% @private
|
||||
%% gen_server callback to handle state transformations necessary for hot
|
||||
%% code updates. This template performs no transformation.
|
||||
|
||||
code_change(_, State, _) ->
|
||||
{ok, State}.
|
||||
|
||||
|
||||
%% @private
|
||||
%% gen_server callback to handle shutdown/cleanup tasks on receipt of a clean
|
||||
%% termination request.
|
||||
|
||||
terminate(_, _) ->
|
||||
ok.
|
||||
|
||||
|
||||
|
||||
%%% Doer Functions
|
||||
|
||||
-spec do_pass_meta(Meta, Dir, ArgV, State) -> {Result, NewState}
|
||||
when Meta :: zx:package_meta(),
|
||||
Dir :: file:filename(),
|
||||
ArgV :: [string()],
|
||||
State :: state(),
|
||||
Result :: ok,
|
||||
Newstate :: state().
|
||||
|
||||
do_pass_meta(Meta, Dir, ArgV, State) ->
|
||||
NewState = State#s{meta = Meta, dir = Dir, argv = ArgV},
|
||||
{ok, NewState}.
|
||||
|
||||
|
||||
-spec do_subscribe(Requestor, Package, State) -> {Result, NewState}
|
||||
when Requestor :: pid(),
|
||||
Package :: zx:package(),
|
||||
State :: state(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: illegal_requestor
|
||||
| {already_subscribed, zx:package()},
|
||||
NewState :: state().
|
||||
|
||||
do_subscribe(Requestor,
|
||||
{Realm, Name},
|
||||
State = #s{name = none, connp = none, reqp = none, hosts = Hosts}) ->
|
||||
Monitor = monitor(process, Requestor),
|
||||
{Host, NewHosts} = select_host(Realm, Hosts),
|
||||
{ok, ConnP} = zx_conn:start(Host),
|
||||
ConnM = monitor(process, ConnP),
|
||||
NewState = State#s{realm = Realm, name = Name,
|
||||
connp = ConnP, connm = ConnM,
|
||||
reqp = Requestor, reqm = Monitor,
|
||||
hosts = NewHosts},
|
||||
{ok, NewState};
|
||||
do_subscribe(_, _, State = #s{realm = Realm, name = Name}) ->
|
||||
{{error, {already_subscribed, {Realm, Name}}}, State}.
|
||||
|
||||
|
||||
-spec select_host(Realm, Hosts) -> {Host, NewHosts}
|
||||
when Realm :: zx:realm(),
|
||||
Hosts :: none | hosts(),
|
||||
Host :: zx:host(),
|
||||
NewHosts :: hosts().
|
||||
|
||||
select_host(Realm, none) ->
|
||||
List =
|
||||
case file:consult(zx_lib:hosts_cache_file(Realm)) of
|
||||
{ok, Cached} -> Cached;
|
||||
{error, enoent} -> [zx_lib:get_prime(Realm)]
|
||||
end,
|
||||
NewState = State#s{hosts = #{Realm => List}},
|
||||
select_host(Realm, NewState);
|
||||
select_host(Realm, Hosts) ->
|
||||
{Target, Rest} =
|
||||
case maps:find(Realm, Hosts) of
|
||||
{ok, [H | Hs]} -> {H, Hs};
|
||||
{ok, []} -> {zx_lib:get_prime(Realm), []};
|
||||
error -> {zx_lib:get_prime(Realm), []}
|
||||
end,
|
||||
NewHosts = maps:put(Realm, Hosts, Rest),
|
||||
{Target, NewHosts}.
|
||||
|
||||
|
||||
-spec do_query_latest(Object, State) -> {Result, NewState}
|
||||
when Object :: zx:package() | zx:package_id(),
|
||||
State :: state(),
|
||||
Result :: {ok, zx:version()}
|
||||
| {error, Reason},
|
||||
Reason :: bad_realm
|
||||
| bad_package
|
||||
| bad_version,
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% Queries a zomp realm for the latest version of a package or package
|
||||
%% version (complete or incomplete version number).
|
||||
|
||||
query_latest(Socket, {Realm, Name}) ->
|
||||
ok = send(Socket, {latest, Realm, Name}),
|
||||
receive
|
||||
{monitor, Pid} ->
|
||||
Mon = monitor(process, Pid),
|
||||
exec_wait(State#s{pid = Pid, mon = Mon});
|
||||
Unexpected ->
|
||||
ok = log(warning, "Unexpected message: ~tp", [Unexpected]),
|
||||
exec_wait(State)
|
||||
{tcp, Socket, Bin} -> binary_to_term(Bin)
|
||||
after 5000 -> {error, timeout}
|
||||
end;
|
||||
exec_wait(State = #s{pid = Pid, mon = Mon}) ->
|
||||
query_latest(Socket, {Realm, Name, Version}) ->
|
||||
ok = send(Socket, {latest, Realm, Name, Version}),
|
||||
receive
|
||||
{check_update, Requester, Ref} ->
|
||||
{Response, NewState} = check_update(State),
|
||||
Requester ! {Ref, Response},
|
||||
exec_wait(NewState);
|
||||
{exit, Reason} ->
|
||||
ok = log(info, "Exiting with: ~tp", [Reason]),
|
||||
halt(0);
|
||||
{'DOWN', Mon, process, Pid, normal} ->
|
||||
ok = log(info, "Application exited normally."),
|
||||
halt(0);
|
||||
{'DOWN', Mon, process, Pid, Reason} ->
|
||||
ok = log(warning, "Application exited with: ~tp", [Reason]),
|
||||
halt(1);
|
||||
Unexpected ->
|
||||
ok = log(warning, "Unexpected message: ~tp", [Unexpected]),
|
||||
exec_wait(State)
|
||||
{tcp, Socket, Bin} -> binary_to_term(Bin)
|
||||
after 5000 -> {error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
-spec check_update(State) -> {Response, NewState}
|
||||
-spec do_unsubscribe(State) -> {ok, NewState}
|
||||
when State :: state(),
|
||||
Response :: term(),
|
||||
NewState :: state().
|
||||
%% @private
|
||||
%% Check for updated version availability of the current application.
|
||||
%% The return value should probably provide up to three results, a Major, Minor and
|
||||
%% Patch update, and allow the Requestor to determine what to do with it via some
|
||||
%% interaction.
|
||||
|
||||
check_update(State) ->
|
||||
ok = log(info, "Would be checking for an update of the current application now..."),
|
||||
Response = "Nothing was checked, but you can imagine it to have been.",
|
||||
{Response, State}.
|
||||
do_unsubscribe(State = #s{connp = none}) ->
|
||||
{ok, State};
|
||||
do_unsubscribe(State = #s{connp = ConnP, connm = ConnM}) ->
|
||||
true = demonitor(ConnM),
|
||||
ok = zx_conn:stop(ConnP),
|
||||
NewState = State#s{realm = none, name = none, version = none,
|
||||
connp = ConnP, connm = ConnM},
|
||||
{ok, NewState}.
|
||||
|
||||
|
||||
-spec do_report(From, Message, State) -> NewState
|
||||
when From :: pid(),
|
||||
Message :: conn_report(),
|
||||
State :: state(),
|
||||
NewState :: state().
|
||||
|
||||
do_report(From, {connected, Realms}, State = #s{
|
||||
|
||||
|
||||
|
||||
-spec do_fetch(PackageIDs) -> Result
|
||||
when PackageIDs :: [zx:package_id()],
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: bad_realm
|
||||
| bad_package
|
||||
| bad_version
|
||||
| network.
|
||||
%% @private
|
||||
%%
|
||||
|
||||
do_fetch(PackageIDs, State) ->
|
||||
% FIXME: Need to create a job queue divided by realm and dispatched to connectors,
|
||||
% and cleared from the master pending queue kept here by the daemon as the
|
||||
% workers succeed. Basic task queue management stuff... which never existed
|
||||
% in ZX before... grrr...
|
||||
case scrub(PackageIDs) of
|
||||
[] ->
|
||||
ok;
|
||||
Needed ->
|
||||
Partitioned = partition_by_realm(Needed),
|
||||
EnsureDeps =
|
||||
fun({Realm, Packages}) ->
|
||||
ok = zx_conn:queue_package(Pid, Realm, Packages),
|
||||
log(info, "Disconnecting from realm: ~ts", [Realm])
|
||||
end,
|
||||
lists:foreach(EnsureDeps, Partitioned)
|
||||
end.
|
||||
|
||||
|
||||
partition_by_realm(PackageIDs) ->
|
||||
PartitionMap = lists:foldl(fun partition_by_realm/2, #{}, PackageIDs),
|
||||
maps:to_list(PartitionMap).
|
||||
|
||||
|
||||
partition_by_realm({R, P, V}, M) ->
|
||||
maps:update_with(R, fun(Ps) -> [{P, V} | Ps] end, [{P, V}], M).
|
||||
|
||||
|
||||
ensure_deps(_, _, []) ->
|
||||
ok;
|
||||
ensure_deps(Socket, Realm, [{Name, Version} | Rest]) ->
|
||||
ok = ensure_dep(Socket, {Realm, Name, Version}),
|
||||
ensure_deps(Socket, Realm, Rest).
|
||||
|
||||
|
||||
-spec ensure_dep(gen_tcp:socket(), package_id()) -> ok | no_return().
|
||||
%% @private
|
||||
%% Given an PackageID as an argument, check whether its package file exists in the
|
||||
%% system cache, and if not download it. Should return `ok' whenever the file is
|
||||
%% sourced, but exit with an error if it cannot locate or acquire the package.
|
||||
|
||||
ensure_dep(Socket, PackageID) ->
|
||||
ZrpFile = filename:join("zrp", namify_zrp(PackageID)),
|
||||
ok =
|
||||
case filelib:is_regular(ZrpFile) of
|
||||
true -> ok;
|
||||
false -> fetch(Socket, PackageID)
|
||||
end,
|
||||
ok = install(PackageID),
|
||||
build(PackageID).
|
||||
|
||||
|
||||
-spec scrub(Deps) -> Scrubbed
|
||||
when Deps :: [package_id()],
|
||||
Scrubbed :: [package_id()].
|
||||
%% @private
|
||||
%% Take a list of dependencies and return a list of dependencies that are not yet
|
||||
%% installed on the system.
|
||||
|
||||
scrub([]) ->
|
||||
[];
|
||||
scrub(Deps) ->
|
||||
lists:filter(fun(PackageID) -> not zx_lib:installed(PackageID) end, Deps).
|
||||
|
||||
60
zomp/lib/otpr-zx/0.1.0/src/zx_daemon_sup.erl
Normal file
60
zomp/lib/otpr-zx/0.1.0/src/zx_daemon_sup.erl
Normal file
@ -0,0 +1,60 @@
|
||||
%%% @doc
|
||||
%%% ZX Daemon Supervisor
|
||||
%%%
|
||||
%%% This supervisor maintains the lifecycle of the zxd worker process.
|
||||
%%% @end
|
||||
|
||||
-module(zx_daemon_sup).
|
||||
-behavior(supervisor).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
-export([start_link/0, init/1]).
|
||||
|
||||
|
||||
|
||||
%%% Startup
|
||||
|
||||
-spec start_link() -> Result
|
||||
when Result :: {ok, pid()}
|
||||
| {error, Reason},
|
||||
Reason :: {already_started, pid()}
|
||||
| {shutdown, term()}
|
||||
| term().
|
||||
%% @private
|
||||
%% Called by zx:subscribe/1.
|
||||
%% Starts this single, registered supervisor.
|
||||
%%
|
||||
%% Error conditions, supervision strategies, and other important issues are
|
||||
%% explained in the supervisor module docs:
|
||||
%% http://erlang.org/doc/man/supervisor.html
|
||||
|
||||
start_link() ->
|
||||
supervisor:start_link({local, ?MODULE}, ?MODULE, none).
|
||||
|
||||
|
||||
-spec init(none) -> {ok, {supervisor:sup_flags(), [supervisor:child_spec()]}}.
|
||||
%% @private
|
||||
%% Do not call this function directly -- it is exported only because it is a
|
||||
%% necessary part of the OTP supervisor behavior.
|
||||
|
||||
init(none) ->
|
||||
RestartStrategy = {rest_for_one, 1, 60},
|
||||
|
||||
Daemon = {zx_daemon,
|
||||
{zx_daemon, start_link, []},
|
||||
permanent,
|
||||
10000,
|
||||
worker,
|
||||
[zx_daemon]},
|
||||
|
||||
ConnSup = {zx_conn_sup,
|
||||
{zx_conn_sup, start_link, []},
|
||||
permanent,
|
||||
brutal_kill,
|
||||
supervisor,
|
||||
[zx_conn_sup]},
|
||||
|
||||
Children = [Daemon, ConnSup],
|
||||
{ok, {RestartStrategy, Children}}.
|
||||
523
zomp/lib/otpr-zx/0.1.0/src/zx_lib.erl
Normal file
523
zomp/lib/otpr-zx/0.1.0/src/zx_lib.erl
Normal file
@ -0,0 +1,523 @@
|
||||
%%% @doc
|
||||
%%% ZX Library
|
||||
%%%
|
||||
%%% This module contains a set of common-use functions internal to the ZX project.
|
||||
%%% These functions are subject to radical change, are not publicly documented and
|
||||
%%% should NOT be used by other projects.
|
||||
%%%
|
||||
%%% The public interface to the externally useful parts of this library are maintained
|
||||
%%% in the otpr-zxxl package.
|
||||
%%% @end
|
||||
|
||||
-module(zx_lib).
|
||||
-author("Craig Everett <zxq9@zxq9.com>").
|
||||
-copyright("Craig Everett <zxq9@zxq9.com>").
|
||||
-license("GPL-3.0").
|
||||
|
||||
|
||||
-export([zomp_home/0, find_zomp_home/0,
|
||||
hosts_cache_file/1, get_prime/1, realm_meta/1,
|
||||
read_project_meta/0, read_project_meta/1, read_package_meta/1,
|
||||
write_project_meta/1, write_project_meta/2,
|
||||
write_terms/2,
|
||||
valid_lower0_9/1, valid_label/1,
|
||||
string_to_version/1, version_to_string/1,
|
||||
package_id/1, package_string/1,
|
||||
package_dir/1, package_dir/2,
|
||||
namify_zrp/1, namify_tgz/1,
|
||||
find_latest_compatible/2, installed/1]).
|
||||
|
||||
-include("zx_logger.hrl").
|
||||
|
||||
|
||||
|
||||
%%% Functions
|
||||
|
||||
zomp_home() ->
|
||||
case os:getenv("ZOMP_HOME") of
|
||||
false ->
|
||||
ZompHome = find_zomp_home(),
|
||||
true = os:putenv("ZOMP_HOME", ZompHome),
|
||||
ZompHome;
|
||||
ZompHome ->
|
||||
ZompHome
|
||||
end.
|
||||
|
||||
|
||||
-spec find_zomp_home() -> file:filename().
|
||||
%% @private
|
||||
%% Check the host OS and return the absolute path to the zomp filesystem root.
|
||||
|
||||
find_zomp_home() ->
|
||||
case os:type() of
|
||||
{unix, _} ->
|
||||
Home = os:getenv("HOME"),
|
||||
Dir = "zomp",
|
||||
filename:join(Home, Dir);
|
||||
{win32, _} ->
|
||||
Drive = os:getenv("HOMEDRIVE"),
|
||||
Path = os:getenv("HOMEPATH"),
|
||||
Dir = "zomp",
|
||||
filename:join([Drive, Path, Dir])
|
||||
end.
|
||||
|
||||
|
||||
-spec hosts_cache_file(zx:realm()) -> file:filename().
|
||||
%% @private
|
||||
%% Given a Realm name, construct a realm's .hosts filename and return it.
|
||||
|
||||
hosts_cache_file(Realm) ->
|
||||
filename:join(zomp_home(), Realm ++ ".hosts").
|
||||
|
||||
|
||||
-spec get_prime(Realm) -> Result
|
||||
when Realm :: zx:realm(),
|
||||
Result :: {ok, zx:host()}
|
||||
| {error, file:posix()}.
|
||||
%% @private
|
||||
%% Check the given Realm's config file for the current prime node and return it.
|
||||
|
||||
get_prime(Realm) ->
|
||||
case realm_meta(Realm) of
|
||||
{ok, RealmMeta} ->
|
||||
{prime, Prime} = lists:keyfind(prime, 1, RealmMeta),
|
||||
{ok, Prime};
|
||||
Error ->
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec realm_meta(Realm) -> Result
|
||||
when Realm :: string(),
|
||||
Result :: {ok, Meta}
|
||||
| {error, Reason},
|
||||
Meta :: [{atom(), term()}],
|
||||
Reason :: file:posix().
|
||||
%% @private
|
||||
%% Given a realm name, try to locate and read the realm's configuration file if it
|
||||
%% exists, exiting with an appropriate error message if there is a problem reading
|
||||
%% the file.
|
||||
|
||||
realm_meta(Realm) ->
|
||||
RealmFile = filename:join(zomp_home(), Realm ++ ".realm"),
|
||||
file:consult(RealmFile).
|
||||
|
||||
|
||||
-spec read_project_meta() -> Result
|
||||
when Result :: {ok, zx:package_meta()}
|
||||
| {error, file:posix()}.
|
||||
%% @private
|
||||
%% @equiv read_meta(".")
|
||||
|
||||
read_project_meta() ->
|
||||
read_project_meta(".").
|
||||
|
||||
|
||||
-spec read_project_meta(Dir) -> Result
|
||||
when Dir :: file:filename(),
|
||||
Result :: {ok, zx:package_meta()}
|
||||
| {error, file:posix()}.
|
||||
%% @private
|
||||
%% Read the `zomp.meta' file from the indicated directory, if possible.
|
||||
|
||||
read_project_meta(Dir) ->
|
||||
Path = filename:join(Dir, "zomp.meta"),
|
||||
case file:consult(Path) of
|
||||
{ok, Meta} ->
|
||||
maps:from_list(Meta);
|
||||
Error ->
|
||||
ok = log(error, "Failed to open \"zomp.meta\" with ~tp", [Error]),
|
||||
ok = log(error, "Wrong directory?"),
|
||||
Error
|
||||
end.
|
||||
|
||||
|
||||
-spec read_package_meta(PackageID) -> Result
|
||||
when PackageID :: zx:package_id(),
|
||||
Result :: {ok, zx:package_meta()}
|
||||
| {error, file:posix()}.
|
||||
|
||||
read_package_meta({Realm, Name, Version}) ->
|
||||
VersionString = Version,
|
||||
Path = filename:join([zomp_home(), "lib", Realm, Name, VersionString]),
|
||||
read_project_meta(Path).
|
||||
|
||||
|
||||
-spec write_project_meta(Meta) -> Result
|
||||
when Meta :: zx:package_meta(),
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: badarg
|
||||
| terminated
|
||||
| system_limit
|
||||
| file:posix().
|
||||
%% @private
|
||||
%% @equiv write_meta(".")
|
||||
|
||||
write_project_meta(Meta) ->
|
||||
write_project_meta(".", Meta).
|
||||
|
||||
|
||||
-spec write_project_meta(Dir, Meta) -> ok
|
||||
when Dir :: file:filename(),
|
||||
Meta :: zx:package_meta().
|
||||
%% @private
|
||||
%% Write the contents of the provided meta structure (a map these days) as a list of
|
||||
%% Erlang K/V terms.
|
||||
|
||||
write_project_meta(Dir, Meta) ->
|
||||
Path = filename:join(Dir, "zomp.meta"),
|
||||
write_terms(Path, maps:to_list(Meta)).
|
||||
|
||||
|
||||
-spec write_terms(Filename, Terms) -> Result
|
||||
when Filename :: file:filename(),
|
||||
Terms :: [term()],
|
||||
Result :: ok
|
||||
| {error, Reason},
|
||||
Reason :: badarg
|
||||
| terminated
|
||||
| system_limit
|
||||
| file:posix().
|
||||
%% @private
|
||||
%% Provides functionality roughly inverse to file:consult/1.
|
||||
|
||||
write_terms(Filename, List) ->
|
||||
Format = fun(Term) -> io_lib:format("~tp.~n", [Term]) end,
|
||||
Text = lists:map(Format, List),
|
||||
file:write_file(Filename, Text).
|
||||
|
||||
|
||||
-spec valid_lower0_9(string()) -> boolean().
|
||||
%% @private
|
||||
%% Check whether a provided string is a valid lower0_9.
|
||||
|
||||
valid_lower0_9([Char | Rest])
|
||||
when $a =< Char, Char =< $z ->
|
||||
valid_lower0_9(Rest, Char);
|
||||
valid_lower0_9(_) ->
|
||||
false.
|
||||
|
||||
|
||||
-spec valid_lower0_9(String, Last) -> boolean()
|
||||
when String :: string(),
|
||||
Last :: char().
|
||||
|
||||
valid_lower0_9([$_ | _], $_) ->
|
||||
false;
|
||||
valid_lower0_9([Char | Rest], _)
|
||||
when $a =< Char, Char =< $z;
|
||||
$0 =< Char, Char =< $9;
|
||||
Char == $_ ->
|
||||
valid_lower0_9(Rest, Char);
|
||||
valid_lower0_9([], _) ->
|
||||
true;
|
||||
valid_lower0_9(_, _) ->
|
||||
false.
|
||||
|
||||
|
||||
-spec valid_label(string()) -> boolean().
|
||||
%% @private
|
||||
%% Check whether a provided string is a valid label.
|
||||
|
||||
valid_label([Char | Rest])
|
||||
when $a =< Char, Char =< $z ->
|
||||
valid_label(Rest, Char);
|
||||
valid_label(_) ->
|
||||
false.
|
||||
|
||||
|
||||
-spec valid_label(String, Last) -> boolean()
|
||||
when String :: string(),
|
||||
Last :: char().
|
||||
|
||||
valid_label([$. | _], $.) ->
|
||||
false;
|
||||
valid_label([$_ | _], $_) ->
|
||||
false;
|
||||
valid_label([$- | _], $-) ->
|
||||
false;
|
||||
valid_label([Char | Rest], _)
|
||||
when $a =< Char, Char =< $z;
|
||||
$0 =< Char, Char =< $9;
|
||||
Char == $_; Char == $-;
|
||||
Char == $. ->
|
||||
valid_label(Rest, Char);
|
||||
valid_label([], _) ->
|
||||
true;
|
||||
valid_label(_, _) ->
|
||||
false.
|
||||
|
||||
|
||||
-spec string_to_version(VersionString) -> Result
|
||||
when VersionString :: string(),
|
||||
Result :: {ok, zx:version()}
|
||||
| {error, invalid_version_string}.
|
||||
%% @private
|
||||
%% @equiv string_to_version(string(), "", {z, z, z})
|
||||
|
||||
string_to_version(String) ->
|
||||
string_to_version(String, "", {z, z, z}).
|
||||
|
||||
|
||||
-spec string_to_version(String, Acc, Version) -> Result
|
||||
when String :: string(),
|
||||
Acc :: list(),
|
||||
Version :: zx:version(),
|
||||
Result :: {ok, zx:version()}
|
||||
| {error, invalid_version_string}.
|
||||
%% @private
|
||||
%% Accepts a full or partial version string of the form `X.Y.Z', `X.Y' or `X' and
|
||||
%% returns a zomp-type version tuple or crashes on bad data.
|
||||
|
||||
string_to_version([Char | Rest], Acc, Version) when $0 =< Char andalso Char =< $9 ->
|
||||
string_to_version(Rest, [Char | Acc], Version);
|
||||
string_to_version("", "", Version) ->
|
||||
{ok, Version};
|
||||
string_to_version(_, "", _) ->
|
||||
{error, invalid_version_string};
|
||||
string_to_version([$. | Rest], Acc, {z, z, z}) ->
|
||||
X = list_to_integer(lists:reverse(Acc)),
|
||||
string_to_version(Rest, "", {X, z, z});
|
||||
string_to_version([$. | Rest], Acc, {X, z, z}) ->
|
||||
Y = list_to_integer(lists:reverse(Acc)),
|
||||
string_to_version(Rest, "", {X, Y, z});
|
||||
string_to_version([], Acc, {z, z, z}) ->
|
||||
X = list_to_integer(lists:reverse(Acc)),
|
||||
{ok, {X, z, z}};
|
||||
string_to_version([], Acc, {X, z, z}) ->
|
||||
Y = list_to_integer(lists:reverse(Acc)),
|
||||
{ok, {X, Y, z}};
|
||||
string_to_version([], Acc, {X, Y, z}) ->
|
||||
Z = list_to_integer(lists:reverse(Acc)),
|
||||
{ok, {X, Y, Z}};
|
||||
string_to_version(_, _, _) ->
|
||||
{error, invalid_version_string}.
|
||||
|
||||
|
||||
-spec version_to_string(zx:version()) -> {ok, string()} | {error, invalid_version}.
|
||||
%% @private
|
||||
%% Inverse of string_to_version/3.
|
||||
|
||||
version_to_string({z, z, z}) ->
|
||||
{ok, ""};
|
||||
version_to_string({X, z, z}) when is_integer(X) ->
|
||||
{ok, integer_to_list(X)};
|
||||
version_to_string({X, Y, z}) when is_integer(X), is_integer(Y) ->
|
||||
DeepList = lists:join($., [integer_to_list(Element) || Element <- [X, Y]]),
|
||||
FlatString = lists:flatten(DeepList),
|
||||
{ok, FlatString};
|
||||
version_to_string({X, Y, Z}) when is_integer(X), is_integer(Y), is_integer(Z) ->
|
||||
DeepList = lists:join($., [integer_to_list(Element) || Element <- [X, Y, Z]]),
|
||||
FlatString = lists:flatten(DeepList),
|
||||
{ok, FlatString};
|
||||
version_to_string(_) ->
|
||||
{error, invalid_version}.
|
||||
|
||||
|
||||
-spec package_id(string()) -> {ok, zx:package_id()} | {error, invalid_package_string}.
|
||||
%% @private
|
||||
%% Converts a proper package_string to a package_id().
|
||||
%% This function takes into account missing version elements.
|
||||
%% Examples:
|
||||
%% `{ok, {"foo", "bar", {1, 2, 3}}} = package_id("foo-bar-1.2.3")'
|
||||
%% `{ok, {"foo", "bar", {1, 2, z}}} = package_id("foo-bar-1.2")'
|
||||
%% `{ok, {"foo", "bar", {1, z, z}}} = package_id("foo-bar-1")'
|
||||
%% `{ok, {"foo", "bar", {z, z, z}}} = package_id("foo-bar")'
|
||||
%% `{error, invalid_package_string} = package_id("Bad-Input")'
|
||||
|
||||
package_id(String) ->
|
||||
case dash_split(String) of
|
||||
[Realm, Name, VersionString] ->
|
||||
package_id(Realm, Name, VersionString);
|
||||
[A, B] ->
|
||||
case valid_lower0_9(B) of
|
||||
true -> package_id(A, B, "");
|
||||
false -> package_id("otpr", A, B)
|
||||
end;
|
||||
[Name] ->
|
||||
package_id("otpr", Name, "");
|
||||
_ ->
|
||||
{error, invalid_package_string}
|
||||
end.
|
||||
|
||||
|
||||
-spec dash_split(string()) -> [string()] | error.
|
||||
%% @private
|
||||
%% An explicit, strict token split that ensures invalid names with leading, trailing or
|
||||
%% double dashes don't slip through (a problem discovered with using string:tokens/2
|
||||
%% and string:lexemes/2.
|
||||
%% Intended only as a helper function for package_id/1
|
||||
|
||||
dash_split(String) ->
|
||||
dash_split(String, "", []).
|
||||
|
||||
|
||||
dash_split([$- | Rest], Acc, Elements) ->
|
||||
Element = lists:reverse(Acc),
|
||||
dash_split(Rest, "", [Element | Elements]);
|
||||
dash_split([Char | Rest], Acc, Elements) ->
|
||||
dash_split(Rest, [Char | Acc], Elements);
|
||||
dash_split("", Acc, Elements) ->
|
||||
Element = lists:reverse(Acc),
|
||||
lists:reverse([Element | Elements]);
|
||||
dash_split(_, _, _) ->
|
||||
error.
|
||||
|
||||
|
||||
-spec package_id(Realm, Name, VersionString) -> Result
|
||||
when Realm :: zx:realm(),
|
||||
Name :: zx:name(),
|
||||
VersionString :: string(),
|
||||
Result :: {ok, zx:package_id()}
|
||||
| {error, invalid_package_string}.
|
||||
|
||||
package_id(Realm, Name, VersionString) ->
|
||||
ValidRealm = valid_lower0_9(Realm),
|
||||
ValidName = valid_lower0_9(Name),
|
||||
MaybeVersion = string_to_version(VersionString),
|
||||
case {ValidRealm, ValidName, MaybeVersion} of
|
||||
{true, true, {ok, Version}} -> {ok, {Realm, Name, Version}};
|
||||
_ -> {error, invalid_package_string}
|
||||
end.
|
||||
|
||||
|
||||
-spec package_string(zx:package_id()) -> {ok, string()} | {error, invalid_package_id}.
|
||||
%% @private
|
||||
%% Map an PackageID to a correct string representation.
|
||||
%% This function takes into account missing version elements.
|
||||
%% Examples:
|
||||
%% `{ok, "foo-bar-1.2.3"} = package_string({"foo", "bar", {1, 2, 3}})'
|
||||
%% `{ok, "foo-bar-1.2"} = package_string({"foo", "bar", {1, 2, z}})'
|
||||
%% `{ok, "foo-bar-1"} = package_string({"foo", "bar", {1, z, z}})'
|
||||
%% `{ok, "foo-bar"} = package_string({"foo", "bar", {z, z, z}})'
|
||||
%% `{error, invalid_package_id = package_string({"Bad", "Input"})'
|
||||
|
||||
package_string({Realm, Name, {z, z, z}}) ->
|
||||
ValidRealm = valid_lower0_9(Realm),
|
||||
ValidName = valid_lower0_9(Name),
|
||||
case ValidRealm and ValidName of
|
||||
true ->
|
||||
PackageString = lists:flatten(lists:join($-, [Realm, Name])),
|
||||
{ok, PackageString};
|
||||
false ->
|
||||
{error, invalid_package_id}
|
||||
end;
|
||||
package_string({Realm, Name, Version}) ->
|
||||
ValidRealm = valid_lower0_9(Realm),
|
||||
ValidName = valid_lower0_9(Name),
|
||||
MaybeVersionString = version_to_string(Version),
|
||||
case {ValidRealm, ValidName, MaybeVersionString} of
|
||||
{true, true, {ok, VerString}} ->
|
||||
PackageString = lists:flatten(lists:join($-, [Realm, Name, VerString])),
|
||||
{ok, PackageString};
|
||||
_ ->
|
||||
{error, invalid_package_id}
|
||||
end;
|
||||
package_string({Realm, Name}) ->
|
||||
package_string({Realm, Name, {z, z, z}});
|
||||
package_string(_) ->
|
||||
{error, invalid_package_id}.
|
||||
|
||||
|
||||
-spec package_dir(zx:package_id()) -> file:filename().
|
||||
%% @private
|
||||
%% Returns the path to a package installation. Crashes if PackageID is not a valid
|
||||
%% identitifer or if the version is incomplete (it is not possible to create a path
|
||||
%% to a partial version number).
|
||||
|
||||
package_dir({Realm, Name, Version = {X, Y, Z}})
|
||||
when is_integer(X), is_integer(Y), is_integer(Z) ->
|
||||
{ok, PackageDir} = package_string({Realm, Name}),
|
||||
{ok, VersionDir} = version_to_string(Version),
|
||||
filename:join([zomp_home(), "lib", PackageDir, VersionDir]).
|
||||
|
||||
|
||||
-spec package_dir(Prefix, Package) -> PackageDataDir
|
||||
when Prefix :: string(),
|
||||
Package :: zx:package(),
|
||||
PackageDataDir :: file:filename().
|
||||
%% @private
|
||||
%% Create an absolute path to an application directory prefixed by the inclued argument.
|
||||
|
||||
package_dir(Prefix, {Realm, Name}) ->
|
||||
PackageString = package_string({Realm, Name, {z, z, z}}),
|
||||
filename:join([zomp_home(), Prefix, PackageString]).
|
||||
|
||||
|
||||
-spec namify_zrp(PackageID) -> ZrpFileName
|
||||
when PackageID :: package_id(),
|
||||
ZrpFileName :: file:filename().
|
||||
%% @private
|
||||
%% Map an PackageID to its correct .zrp package file name.
|
||||
|
||||
namify_zrp(PackageID) -> namify(PackageID, "zrp").
|
||||
|
||||
|
||||
-spec namify_tgz(PackageID) -> TgzFileName
|
||||
when PackageID :: package_id(),
|
||||
TgzFileName :: file:filename().
|
||||
%% @private
|
||||
%% Map an PackageID to its correct gzipped tarball source bundle filename.
|
||||
|
||||
namify_tgz(PackageID) -> namify(PackageID, "tgz").
|
||||
|
||||
|
||||
-spec namify(PackageID, Suffix) -> FileName
|
||||
when PackageID :: package_id(),
|
||||
Suffix :: string(),
|
||||
FileName :: file:filename().
|
||||
%% @private
|
||||
%% Converts an PackageID to a canonical string, then appends the provided
|
||||
%% filename Suffix.
|
||||
|
||||
namify(PackageID, Suffix) ->
|
||||
{ok, PackageString} = package_string(PackageID),
|
||||
PackageString ++ "." ++ Suffix.
|
||||
|
||||
|
||||
-spec find_latest_compatible(Version, Versions) -> Result
|
||||
when Version :: zx:version(),
|
||||
Versions :: [zx:version()],
|
||||
Result :: exact
|
||||
| {ok, zx:version()}
|
||||
| not_found.
|
||||
%% @private
|
||||
%% Find the latest compatible version from a list of versions. Returns the atom
|
||||
%% `exact' in the case a full version is specified and it exists, the tuple
|
||||
%% `{ok, Version}' in the case a compatible version was found against a partial
|
||||
%% version tuple, and the atom `not_found' in the case no compatible version exists
|
||||
%% in the list. Will fail with `not_found' if the input `Version' is not a valid
|
||||
%% `zx:version()' tuple.
|
||||
|
||||
find_latest_compatible(Version, Versions) ->
|
||||
Descending = lists:reverse(lists:sort(Versions)),
|
||||
latest_compatible(Version, Descending).
|
||||
|
||||
|
||||
latest_compatible({z, z, z}, Versions) ->
|
||||
{ok, hd(Versions)};
|
||||
latest_compatible({X, z, z}, Versions) ->
|
||||
case lists:keyfind(X, 1, Versions) of
|
||||
false -> not_found;
|
||||
Version -> {ok, Version}
|
||||
end;
|
||||
latest_compatible({X, Y, z}, Versions) ->
|
||||
NotMatch = fun({Q, W, _}) -> not (Q == X andalso W == Y) end,
|
||||
case lists:dropwhile(NotMatch, Versions) of
|
||||
[] -> not_found;
|
||||
Vs -> {ok, hd(Vs)}
|
||||
end;
|
||||
latest_compatible(Version, Versions) ->
|
||||
case lists:member(Version, Versions) of
|
||||
true -> exact;
|
||||
false -> not_found
|
||||
end.
|
||||
|
||||
|
||||
-spec installed(zx:package_id()) -> boolean().
|
||||
%% @private
|
||||
%% True to its name, tells whether a package's install directory is found.
|
||||
|
||||
installed(PackageID) ->
|
||||
filelib:is_dir(package_dir(PackageID)).
|
||||
@ -1,6 +1,6 @@
|
||||
#! /bin/sh
|
||||
|
||||
# set -x
|
||||
set -x
|
||||
|
||||
ZOMP_DIR="$HOME/zomp"
|
||||
ORIGIN="$(pwd)"
|
||||
|
||||
13
zx_dev.sh
Executable file
13
zx_dev.sh
Executable file
@ -0,0 +1,13 @@
|
||||
#! /bin/sh
|
||||
|
||||
set -x
|
||||
|
||||
ZOMP_DIR="$HOME/vcs/zx/zomp"
|
||||
ORIGIN="$(pwd)"
|
||||
VERSION="$(ls $ZOMP_DIR/lib/otpr-zx/ | sort --field-separator=. --reverse | head --lines=1)"
|
||||
ZX_DIR="$ZOMP_DIR/lib/otpr-zx/$VERSION"
|
||||
|
||||
cd "$ZX_DIR"
|
||||
./zmake
|
||||
cd "$ZOMP_DIR"
|
||||
erl -pa "$ZX_DIR/ebin" -run zx start $@
|
||||
Loading…
x
Reference in New Issue
Block a user