wip
This commit is contained in:
parent
a1af4182ee
commit
684e4507fc
197
zx
197
zx
@ -18,7 +18,7 @@
|
||||
|
||||
|
||||
-record(s,
|
||||
{realm = "otpr" :: name(),
|
||||
{realm = "otpr" :: realm(),
|
||||
name = none :: none | name(),
|
||||
version = {z, z, z} :: version(),
|
||||
type = app :: app | lib,
|
||||
@ -47,6 +47,7 @@
|
||||
-type key_name() :: lower0_9().
|
||||
-type lower0_9() :: [$a..$z | $0..$9 | $_].
|
||||
%-type label() :: [$a..$z | $0..$9 | $_ | $- | $.].
|
||||
-type package_meta() :: #{}.
|
||||
|
||||
|
||||
|
||||
@ -79,7 +80,8 @@ start(["init", "lib", PackageString]) ->
|
||||
start(["install", PackageFile]) ->
|
||||
assimilate(PackageFile);
|
||||
start(["set", "dep", PackageString]) ->
|
||||
set_dep(PackageString);
|
||||
PackageID = package_id(PackageString),
|
||||
set_dep(PackageID);
|
||||
start(["set", "version", VersionString]) ->
|
||||
set_version(VersionString);
|
||||
start(["drop", "dep", PackageString]) ->
|
||||
@ -133,24 +135,25 @@ start(_) ->
|
||||
%%
|
||||
%% Once the target program is running, this process, (which will run with the registered
|
||||
%% name `zx') will sit in an `exec_wait' state, waiting for either a direct message from
|
||||
%% a child program or for calls made via vx_lib to assist in environment discovery.
|
||||
%% a child program or for calls made via zx_lib to assist in environment discovery.
|
||||
%%
|
||||
%% If there is a problem anywhere in the locationg, discovery, building, and loading
|
||||
%% If there is a problem anywhere in the locating, discovery, building, and loading
|
||||
%% procedure the runtime will halt with an error message.
|
||||
|
||||
run(Identifier, Args) ->
|
||||
{Realm, Name, Version} = package_id(Identifier),
|
||||
MaybeID = package_id(Identifier),
|
||||
{ok, PackageID = {Realm, Name, Version}} = ensure_installed(MaybeID),
|
||||
ok = file:set_cwd(zomp_dir()),
|
||||
State = #s{realm = Realm,
|
||||
name = Name,
|
||||
version = Version},
|
||||
NewState = #s{version = Installed} = ensure_installed(State),
|
||||
PackageID = {Realm, Name, Installed},
|
||||
Dir = filename:join("lib", package_string(PackageID)),
|
||||
Meta = read_meta(Dir),
|
||||
Deps = maps:get(deps, Meta),
|
||||
ok = ensure_deps(Deps),
|
||||
execute(NewState#s{dir = Dir, deps = Deps}).
|
||||
State = #s{realm = Realm,
|
||||
name = Name,
|
||||
version = Version,
|
||||
dir = Dir,
|
||||
deps = Deps},
|
||||
execute(State, Args).
|
||||
|
||||
|
||||
|
||||
@ -227,8 +230,7 @@ assimilate(PackageFile) ->
|
||||
%%% Set dependency
|
||||
|
||||
|
||||
-spec set_dep(PackageString) -> no_return()
|
||||
when PackageString :: string().
|
||||
-spec set_dep(package_id()) -> no_return().
|
||||
%% @private
|
||||
%% Set a specific dependency in the current project. If the project currently has a
|
||||
%% dependency on the same package then the version of that dependency is updated to
|
||||
@ -236,17 +238,27 @@ assimilate(PackageFile) ->
|
||||
%% incomplete. Incomplete elements of the VersionString (if included) will default to
|
||||
%% the latest version available at the indicated level.
|
||||
|
||||
set_dep(PackageString) ->
|
||||
PackageID = package_id(PackageString),
|
||||
set_dep(PackageID = {_, _, {X, Y, Z}})
|
||||
when is_integer(X), is_integer(Y), is_integer(Z) ->
|
||||
Meta = read_meta(),
|
||||
Deps = maps:get(deps, Meta),
|
||||
case lists:member(PackageID, Deps) of
|
||||
true ->
|
||||
ok = log(info, "~ts is already a dependency", [PackageString]),
|
||||
ok = log(info, "~ts is already a dependency", [package_string(PackageID)]),
|
||||
halt(0);
|
||||
false ->
|
||||
set_dep(PackageID, Deps, Meta)
|
||||
end.
|
||||
end;
|
||||
set_dep({Realm, Name, {z, z, z}}) ->
|
||||
Socket = connect_user(Realm),
|
||||
{ok, Version} = query_latest(Socket, {Realm, Name}),
|
||||
ok = disconnect(Socket),
|
||||
set_dep({Realm, Name, Version});
|
||||
set_dep({Realm, Name, Version}) ->
|
||||
Socket = connect_user(Realm),
|
||||
{ok, Latest} = query_latest(Socket, {Realm, Name, Version}),
|
||||
ok = disconnect(Socket),
|
||||
set_dep({Realm, Name, Latest}).
|
||||
|
||||
|
||||
-spec set_dep(PackageID, Deps, Meta) -> no_return()
|
||||
@ -259,9 +271,7 @@ set_dep(PackageString) ->
|
||||
%% such a dependency is not already present. Then write the project meta back to its
|
||||
%% file and exit.
|
||||
|
||||
set_dep(PackageID = {Realm, Name, Version}, Deps, Meta) ->
|
||||
{ok, CWD} = file:get_cwd(),
|
||||
LatestVersion = latest_version(PackageID),
|
||||
set_dep(PackageID = {Realm, Name, NewVersion}, Deps, Meta) ->
|
||||
ExistingPackageIDs = fun ({R, N, _}) -> {R, N} == {Realm, Name} end,
|
||||
NewDeps =
|
||||
case lists:partition(ExistingPackageIDs, Deps) of
|
||||
@ -280,34 +290,42 @@ set_dep(PackageID = {Realm, Name, Version}, Deps, Meta) ->
|
||||
halt(0).
|
||||
|
||||
|
||||
-spec latest_version(package_id()) -> version().
|
||||
%% @private
|
||||
%% Query the relevant realm for the latest version of a given package.
|
||||
|
||||
latest_version({Realm, Name, Version}) ->
|
||||
Socket = connect(Realm),
|
||||
ok = send(Socket, {latest, Realm, Name, Version}),
|
||||
Response =
|
||||
receive
|
||||
{tcp, Socket, Bin} -> binary_to_term(Bin, [safe])
|
||||
after 5000 -> {error, timeout}
|
||||
end,
|
||||
ok = disconnect(Socket).
|
||||
|
||||
|
||||
-spec ensure_installed(State) -> NewState | no_return()
|
||||
when State :: state(),
|
||||
NewState :: state().
|
||||
-spec ensure_installed(PackageID) -> Result | no_return()
|
||||
when PackageID :: package_id(),
|
||||
Result :: {ok, ActualID :: package_id()}.
|
||||
%% @private
|
||||
%% Given a PackageID, check whether it is installed on the system, and if not, ensure
|
||||
%% that the package is either in the cache or can be downloaded. If all attempts at
|
||||
%% locating or acquiring the package fail, then exit with an error.
|
||||
|
||||
ensure_installed(State = #s{realm = Realm, name = Name, version = Version}) ->
|
||||
case resolve_installed_version({Realm, Name, Version}) of
|
||||
exact -> State;
|
||||
{ok, Installed} -> State#s{version = Installed};
|
||||
not_found -> ensure_dep(State)
|
||||
ensure_installed(PackageID = {Realm, Name, Version}) ->
|
||||
case resolve_installed_version(PackageID) of
|
||||
exact -> {ok, PackageID};
|
||||
{ok, Installed} -> {ok, {Realm, Name, Installed}};
|
||||
not_found -> ensure_installed(Realm, Name, Version)
|
||||
end.
|
||||
|
||||
|
||||
ensure_installed(Realm, Name, Version) ->
|
||||
Socket = connect_user(Realm),
|
||||
{ok, LatestVersion} = query_latest(Socket, {Realm, Name, Version}),
|
||||
LatestID = {Realm, Name, LatestVersion},
|
||||
ok = ensure_dep(Socket, LatestID),
|
||||
ok = disconnect(Socket),
|
||||
{ok, LatestID}.
|
||||
|
||||
|
||||
query_latest(Socket, {Realm, Name}) ->
|
||||
ok = send(Socket, {latest, Realm, Name}),
|
||||
receive
|
||||
{tcp, Socket, Bin} -> binary_to_term(Bin, [safe])
|
||||
after 5000 -> {error, timeout}
|
||||
end;
|
||||
query_latest(Socket, {Realm, Name, Version}) ->
|
||||
ok = send(Socket, {latest, Realm, Name, Version}),
|
||||
receive
|
||||
{tcp, Socket, Bin} -> binary_to_term(Bin, [safe])
|
||||
after 5000 -> {error, timeout}
|
||||
end.
|
||||
|
||||
|
||||
@ -332,7 +350,7 @@ resolve_installed_version(PackageID) ->
|
||||
exact;
|
||||
[Dir] ->
|
||||
{_, _, Version} = package_id(Dir),
|
||||
{ok, Versoin};
|
||||
{ok, Version};
|
||||
Dirs ->
|
||||
Dir = lists:last(lists:sort(Dirs)),
|
||||
{_, _, Version} = package_id(Dir),
|
||||
@ -351,15 +369,14 @@ ensure_deps(Deps) ->
|
||||
Socket = connect_user(Realm),
|
||||
ok = ensure_deps(Socket, Realm, Packages),
|
||||
ok = disconnect(Socket),
|
||||
log(info, "Disconnecting from realm: ~ts", [Realm]),
|
||||
log(info, "Disconnecting from realm: ~ts", [Realm])
|
||||
end,
|
||||
lists:foreach(EnsureDeps, Partitioned)
|
||||
end.
|
||||
|
||||
|
||||
partition_by_realm(PackageIDs) ->
|
||||
Sorted = lists:sort(Needed),
|
||||
PartitionMap = lists:foldl(fun partition_by_realm/2, #{}, Needed),
|
||||
PartitionMap = lists:foldl(fun partition_by_realm/2, #{}, PackageIDs),
|
||||
maps:to_list(PartitionMap).
|
||||
|
||||
|
||||
@ -387,13 +404,13 @@ ensure_dep(Socket, PackageID) ->
|
||||
true -> ok;
|
||||
false -> fetch(Socket, PackageID)
|
||||
end,
|
||||
install(PackageID).
|
||||
ok = install(PackageID),
|
||||
build(PackageID).
|
||||
|
||||
|
||||
|
||||
%%% Set version
|
||||
|
||||
|
||||
-spec set_version(VersionString) -> no_return()
|
||||
when VersionString :: string().
|
||||
%% @private
|
||||
@ -576,26 +593,21 @@ run_local(Args) ->
|
||||
dir = Dir},
|
||||
ok = ensure_deps(Deps),
|
||||
ok = file:set_cwd(Dir),
|
||||
execute(State).
|
||||
execute(State, Args).
|
||||
|
||||
|
||||
execute(State = #s{type = app}) ->
|
||||
execute(State = #s{type = app, realm = Realm, name = Name, version = Version}, Args) ->
|
||||
true = register(zx, self()),
|
||||
ok = inets:start(),
|
||||
ok = log(info, "Starting ~ts", [package_string(PackageID)]),
|
||||
ok = log(info, "Starting ~ts", [package_string({Realm, Name, Version})]),
|
||||
AppMod = list_to_atom(Name),
|
||||
{ok, Pid} = AppMod:start(normal, Args),
|
||||
Mon = monitor(process, Pid),
|
||||
Shell = spawn(shell, start, []),
|
||||
ok = log(info, "Your shell is ~p, application is: ~p", [Shell, Pid]),
|
||||
State = #s{realm = Realm,
|
||||
name = Name,
|
||||
version = Version,
|
||||
pid = Pid,
|
||||
mon = Mon},
|
||||
exec_wait(State);
|
||||
execute(State = #s{type = lib, realm = Realm, name = Name, version = Version}) ->
|
||||
Message = "Lib ~ts is available on the system, but is not a standalone app",
|
||||
exec_wait(State#s{pid = Pid, mon = Mon});
|
||||
execute(#s{type = lib, realm = Realm, name = Name, version = Version}, _) ->
|
||||
Message = "Lib ~ts is available on the system, but is not a standalone app.",
|
||||
PackageString = package_string({Realm, Name, Version}),
|
||||
ok = log(info, Message, [PackageString]),
|
||||
halt(0).
|
||||
@ -807,6 +819,7 @@ send(Socket, Message) ->
|
||||
%% Connect to a given realm, whatever method is required.
|
||||
|
||||
connect_user(Realm) ->
|
||||
ok = log(info, "Connecting to realm ~ts...", [Realm]),
|
||||
Hosts =
|
||||
case file:consult(hosts_cache_file(Realm)) of
|
||||
{ok, Cached} -> Cached;
|
||||
@ -821,6 +834,7 @@ connect_user(Realm) ->
|
||||
|
||||
connect_user(Realm, []) ->
|
||||
{Host, Port} = get_prime(Realm),
|
||||
ok = log(info, "Realm host at ~tp:~tp", [Host, Port]),
|
||||
case gen_tcp:connect(Host, Port, connect_options(), 5000) of
|
||||
{ok, Socket} ->
|
||||
confirm_user(Realm, Socket, []);
|
||||
@ -854,7 +868,7 @@ confirm_user(Realm, Socket, Hosts) ->
|
||||
case binary_to_term(Bin, [safe]) of
|
||||
ok ->
|
||||
ok = log(info, "Connected to ~s:~p", [Host, Port]),
|
||||
confirm_serial(Realm, Socket, Hosts, user);
|
||||
confirm_serial(Realm, Socket, Hosts);
|
||||
{redirect, Next} ->
|
||||
ok = log(info, "Redirected..."),
|
||||
ok = disconnect(Socket),
|
||||
@ -863,7 +877,7 @@ confirm_user(Realm, Socket, Hosts) ->
|
||||
after 5000 ->
|
||||
ok = log(warning, "Host ~s:~p timed out.", [Host, Port]),
|
||||
ok = disconnect(Socket),
|
||||
connect_user(Realm, tl(Hosts))
|
||||
connect_user(Realm, Hosts)
|
||||
end.
|
||||
|
||||
|
||||
@ -879,7 +893,7 @@ confirm_serial(Realm, Socket, Hosts) ->
|
||||
SerialFile = filename:join(zomp_dir(), "realm.serials"),
|
||||
Serials =
|
||||
case file:consult(SerialFile) of
|
||||
{ok, Serials} -> Serials;
|
||||
{ok, Ss} -> Ss;
|
||||
{error, enoent} -> []
|
||||
end,
|
||||
Serial =
|
||||
@ -897,22 +911,24 @@ confirm_serial(Realm, Socket, Hosts) ->
|
||||
{ok, Current} when Current > Serial ->
|
||||
ok = log(info, "Node's serial newer than ours. Storing."),
|
||||
NewSerials = lists:keystore(Realm, 1, Current, Serials),
|
||||
ok = write_terms(hosts_cache_file(Realm), Hosts),
|
||||
{ok, Host} = inet:peername(Socket),
|
||||
ok = write_terms(hosts_cache_file(Realm), [Host | Hosts]),
|
||||
ok = write_terms(SerialFile, NewSerials),
|
||||
Socket;
|
||||
{ok, Current} when Current < Serial ->
|
||||
log(info, "Our serial: ~tp, node serial: ~tp.", [Serial, Current]),
|
||||
ok = log(info, "Node's serial older than ours. Trying another."),
|
||||
ok = disconnect(Socket),
|
||||
connect_user(Realm, tl(Hosts));
|
||||
connect_user(Realm, Hosts);
|
||||
{error, bad_realm} ->
|
||||
ok = log(info, "Node is no longer serving realm. Trying another."),
|
||||
ok = disconnect(Socket),
|
||||
connect_user(Realm, tl(Hosts))
|
||||
connect_user(Realm, Hosts)
|
||||
end
|
||||
after 5000 ->
|
||||
ok = log(info, "Host timed out on confirm_serial. Trying another."),
|
||||
ok = disconnect(Socket),
|
||||
connect_user(Realm, tl(Hosts))
|
||||
connect_user(Realm, Hosts)
|
||||
end.
|
||||
|
||||
|
||||
@ -949,7 +965,7 @@ connect_auth(Realm, KeyName) ->
|
||||
|
||||
confirm_auth(Socket, Key) ->
|
||||
ok = log(info, "Would be using key ~tp now", [Key]),
|
||||
{ok, {Addr, Port}} = inet:peername(Socket),
|
||||
{ok, {Host, Port}} = inet:peername(Socket),
|
||||
ok = gen_tcp:send(Socket, <<"OTPR AUTH 1">>),
|
||||
receive
|
||||
{tcp, Socket, <<"OK">>} ->
|
||||
@ -973,15 +989,9 @@ connect_options() ->
|
||||
%% Check the given Realm's config file for the current prime node and return it.
|
||||
|
||||
get_prime(Realm) ->
|
||||
RealmFile = filename:join(zomp_dir(), Realm ++ ".realm"),
|
||||
case file:consult(RealmFile) of
|
||||
{ok, RealmMeta} ->
|
||||
{prime, Prime} = lists:keyfind(prime, 1, RealmMeta),
|
||||
Prime;
|
||||
{error, enoent} ->
|
||||
ok = log(error, "Missing realm file for ~tp (~tp).", [Realm, RealmFile]),
|
||||
halt(1)
|
||||
end.
|
||||
RealmMeta = realm_meta(Realm),
|
||||
{prime, Prime} = lists:keyfind(prime, 1, RealmMeta),
|
||||
Prime.
|
||||
|
||||
|
||||
-spec hosts_cache_file(realm()) -> file:filename().
|
||||
@ -1050,19 +1060,19 @@ have_private_key({Realm, KeyName}) ->
|
||||
filelib:is_regular(PrivateKeyPath).
|
||||
|
||||
|
||||
-spec realm_data(Realm) -> Data | no_return()
|
||||
-spec realm_meta(Realm) -> Meta | no_return()
|
||||
when Realm :: string(),
|
||||
Data :: [{atom(), term()}].
|
||||
Meta :: [{atom(), term()}].
|
||||
%% @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_data(Realm) ->
|
||||
realm_meta(Realm) ->
|
||||
RealmFile = filename:join(zomp_dir(), Realm ++ ".realm"),
|
||||
case file:consult(RealmFile) of
|
||||
{ok, Data} ->
|
||||
Data;
|
||||
{ok, Meta} ->
|
||||
Meta;
|
||||
{error, enoent} ->
|
||||
ok = log(error, "No realm file for ~ts", [Realm]),
|
||||
halt(1);
|
||||
@ -1415,28 +1425,32 @@ verify(Data, Signature, PubKey) ->
|
||||
end.
|
||||
|
||||
|
||||
-spec fetch(gen_tcp:socket(), package_id()) -> ok.
|
||||
-spec fetch(Socket, PackageID) -> Result
|
||||
when Socket :: gen_tcp:socket(),
|
||||
PackageID :: package_id(),
|
||||
Result :: ok.
|
||||
%% @private
|
||||
%% Download a package to the local cache.
|
||||
|
||||
fetch(Socket, PackageID) ->
|
||||
ok = send(Socket, {fetch, PackageID}),
|
||||
ok = await_zrp(Socket, PackageID),
|
||||
ok = request_zrp(Socket, PackageID),
|
||||
ok = receive_zrp(Socket, PackageID),
|
||||
log(info, "Fetched ~ts", [package_string(PackageID)]).
|
||||
|
||||
|
||||
await_zrp(Socket, PackageID) ->
|
||||
request_zrp(Socket, PackageID) ->
|
||||
ok = send(Socket, {fetch, PackageID}),
|
||||
receive
|
||||
{tcp, Socket, Bin} ->
|
||||
case binary_to_term(Bin, [safe]) of
|
||||
sending ->
|
||||
ok;
|
||||
{sending, LatestID} ->
|
||||
{ok, LatestID};
|
||||
Error = {error, Reason} ->
|
||||
PackageString = package_string(PackageID),
|
||||
Message = "Error receiving package ~ts: ~tp",
|
||||
ok = log(info, Message, [PackageString, Reason]),
|
||||
Error
|
||||
end
|
||||
after 60000 ->
|
||||
{error, timeout}
|
||||
end.
|
||||
@ -1487,8 +1501,8 @@ read_meta(Dir) ->
|
||||
%% @private
|
||||
%% @equiv write_meta(".")
|
||||
|
||||
write_meta() ->
|
||||
write_meta(".").
|
||||
write_meta(Meta) ->
|
||||
write_meta(".", Meta).
|
||||
|
||||
|
||||
-spec write_meta(Dir, Meta) -> ok
|
||||
@ -1984,7 +1998,8 @@ realm_file(Realm) ->
|
||||
|
||||
default_realm() ->
|
||||
[{name, "otpr"},
|
||||
{prime, {"repo.psychobitch.party", 11311}},
|
||||
% {prime, {"repo.psychobitch.party", 11311}},
|
||||
{prime, {"localhost", 11311}},
|
||||
{pubkey, default_pubkey_file()},
|
||||
{serial, 0},
|
||||
{mirrors, []}].
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user