From 7062ffe74721668c0572b2f8c67d4eead1d10f0d Mon Sep 17 00:00:00 2001 From: Craig Everett Date: Wed, 30 May 2018 23:25:20 +0900 Subject: [PATCH] headed/headless dev scripts, runlocal works again --- zomp/lib/otpr/zx/0.1.0/src/zx.erl | 159 ++++---- zomp/lib/otpr/zx/0.1.0/src/zx_conf_sys.erl | 72 ++-- zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl | 20 +- zomp/lib/otpr/zx/0.1.0/src/zx_key.erl | 4 +- zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl | 102 +++-- zomp/lib/otpr/zx/0.1.0/src/zx_local.erl | 439 +++++++++++---------- zx_dev | 2 +- zxh_dev | 15 + 8 files changed, 471 insertions(+), 342 deletions(-) create mode 100755 zxh_dev diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx.erl b/zomp/lib/otpr/zx/0.1.0/src/zx.erl index e79a482..27d1975 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx.erl @@ -19,7 +19,7 @@ -license("GPL-3.0"). --export([run/0, run/1]). +-export([do/0, do/1]). -export([subscribe/1, unsubscribe/0]). -export([start/2, stop/1, stop/0]). -export([usage_exit/1]). @@ -69,71 +69,68 @@ %%% Command Dispatch --spec run() -> no_return(). +-spec do() -> no_return(). -run() -> - run([]). +do() -> + do([]). --spec run(Args) -> no_return() +-spec do(Args) -> no_return() when Args :: [string()]. %% Dispatch work functions based on the nature of the input arguments. -run(["help"]) -> +do(["help"]) -> usage_exit(0); -run(["run", PackageString | Args]) -> +do(["run", PackageString | Args]) -> ok = start(), run(PackageString, Args); -run(["runlocal" | ArgV]) -> +do(["runlocal" | ArgV]) -> ok = start(), run_local(ArgV); -run(["init", "app", PackageString]) -> +do(["init", "app", PackageString]) -> ok = compatibility_check([unix]), ok = zx_local:initialize(app, PackageString), halt(0); -run(["init", "lib", PackageString]) -> +do(["init", "lib", PackageString]) -> ok = compatibility_check([unix]), ok = zx_local:initialize(lib, PackageString), halt(0); -run(["install", PackageFile]) -> +do(["install", PackageFile]) -> ok = zx_local:assimilate(PackageFile), halt(0); -run(["set", "dep", PackageString]) -> +do(["set", "dep", PackageString]) -> ok = zx_local:set_dep(PackageString), halt(0); -run(["set", "version", VersionString]) -> +do(["set", "version", VersionString]) -> ok = compatibility_check([unix]), ok = zx_local:set_version(VersionString), halt(0); -run(["verup", Level]) -> +do(["verup", Level]) -> ok = compatibility_check([unix]), ok = zx_local:verup(Level), halt(0); -run(["list", "realms"]) -> +do(["list", "realms"]) -> ok = zx_local:list_realms(), halt(0); -run(["list", "packages", Realm]) -> +do(["list", "packages", Realm]) -> ok = start(), ok = zx_local:list_packages(Realm), halt(0); -run(["list", "versions", PackageName]) -> +do(["list", "versions", PackageName]) -> ok = start(), ok = zx_local:list_versions(PackageName), halt(0); -run(["add", "realm", RealmFile]) -> +do(["add", "realm", RealmFile]) -> ok = zx_local:add_realm(RealmFile), halt(0); -run(["drop", "dep", PackageString]) -> +do(["drop", "dep", PackageString]) -> PackageID = zx_lib:package_id(PackageString), ok = zx_local:drop_dep(PackageID), halt(0); -run(["drop", "realm", Realm]) -> - ok = zx_local:drop_realm(Realm), - halt(0); -run(["package"]) -> +do(["package"]) -> {ok, TargetDir} = file:get_cwd(), zx_local:package(TargetDir); -run(["package", TargetDir]) -> +do(["package", TargetDir]) -> case filelib:is_dir(TargetDir) of true -> ok = zx_local:package(TargetDir), @@ -142,49 +139,61 @@ run(["package", TargetDir]) -> ok = log(error, "Target directory ~tp does not exist!", [TargetDir]), halt(22) end; -run(["dialyze"]) -> +do(["dialyze"]) -> ok = zx_local:dialyze(), halt(0); -run(["create", "user", Realm, Name]) -> +do(["create", "user", Realm, Name]) -> ok = zx_local:create_user(Realm, Name), halt(0); -run(["create", "keypair"]) -> +do(["create", "keypair"]) -> ok = zx_local:grow_a_pair(), halt(0); -run(["drop", "key", Realm, KeyName]) -> +do(["drop", "key", Realm, KeyName]) -> ok = zx_local:drop_key({Realm, KeyName}), halt(0); -run(["create", "plt"]) -> - zx_local:create_plt(); -run(["create", "realm"]) -> - zx_local:create_realm(); -run(["create", "realmfile", Realm]) -> - zx_local:create_realmfile(Realm); -run(["list", "pending", PackageName]) -> +do(["create", "plt"]) -> + ok = zx_local:create_plt(), + halt(0); +do(["create", "realm"]) -> + ok = zx_local:create_realm(), + halt(0); +do(["create", "realmfile", Realm]) -> + ok = zx_local:create_realmfile(Realm, "."), + halt(0); +do(["takeover", Realm]) -> + ok = zx_local:takeover(Realm), + halt(0); +do(["abdicate", Realm]) -> + ok = zx_local:abdicate(Realm), + halt(0); +do(["drop", "realm", Realm]) -> + ok = zx_local:drop_realm(Realm), + halt(0); +do(["list", "pending", PackageName]) -> zx_auth:list_pending(PackageName); -run(["list", "resigns", Realm]) -> +do(["list", "resigns", Realm]) -> zx_auth:list_resigns(Realm); -run(["submit", PackageFile]) -> +do(["submit", PackageFile]) -> zx_auth:submit(PackageFile); -run(["review", PackageString]) -> +do(["review", PackageString]) -> zx_auth:review(PackageString); -run(["approve", PackageString]) -> +do(["approve", PackageString]) -> PackageID = zx_lib:package_id(PackageString), zx_auth:approve(PackageID); -run(["reject", PackageString]) -> +do(["reject", PackageString]) -> PackageID = zx_lib:package_id(PackageString), zx_auth:reject(PackageID); -run(["accept", PackageString]) -> +do(["accept", PackageString]) -> zx_auth:accept(PackageString); -run(["add", "packager", Package, UserName]) -> +do(["add", "packager", Package, UserName]) -> zx_auth:add_packager(Package, UserName); -run(["add", "maintainer", Package, UserName]) -> +do(["add", "maintainer", Package, UserName]) -> zx_auth:add_maintainer(Package, UserName); -run(["add", "sysop", Package, UserName]) -> +do(["add", "sysop", Package, UserName]) -> zx_auth:add_sysop(Package, UserName); -run(["add", "package", PackageName]) -> +do(["add", "package", PackageName]) -> zx_auth:add_package(PackageName); -run(_) -> +do(_) -> usage_exit(22). @@ -317,7 +326,6 @@ unsubscribe() -> run(Identifier, RunArgs) -> ok = file:set_cwd(zx_lib:zomp_dir()), - ok = start(), FuzzyID = case zx_lib:package_id(Identifier) of {ok, Fuzzy} -> @@ -329,12 +337,9 @@ run(Identifier, RunArgs) -> ok = build(PackageID), Dir = zx_lib:package_dir(PackageID), {ok, Meta} = zx_lib:read_project_meta(Dir), - execute(PackageID, Meta, Dir, RunArgs). + prepare(PackageID, Meta, Dir, RunArgs). - -%%% Execution of local project - -spec run_local(RunArgs) -> no_return() when RunArgs :: [term()]. %% @private @@ -352,11 +357,10 @@ run_local(RunArgs) -> ok = zx_lib:build(), {ok, Dir} = file:get_cwd(), ok = file:set_cwd(zx_lib:zomp_dir()), - ok = start(), - execute(PackageID, Meta, Dir, RunArgs). + prepare(PackageID, Meta, Dir, RunArgs). --spec execute(PackageID, Meta, Dir, RunArgs) -> no_return() +-spec prepare(PackageID, Meta, Dir, RunArgs) -> no_return() when PackageID :: package_id(), Meta :: package_meta(), Dir :: file:filename(), @@ -364,21 +368,43 @@ run_local(RunArgs) -> %% @private %% Execution prep common to all packages. -execute(PackageID, Meta, Dir, RunArgs) -> - PackageString = zx_lib:package_string(PackageID), +prepare(PackageID, Meta, Dir, RunArgs) -> + {ok, PackageString} = zx_lib:package_string(PackageID), ok = log(info, "Preparing ~ts...", [PackageString]), Type = maps:get(type, Meta), Deps = maps:get(deps, Meta), - case zx_daemon:fetch(Deps) of - {{ok, _}, {error, []}} -> - ok = lists:foreach(fun install/1, Deps), - ok = lists:foreach(fun build/1, Deps), - execute(Type, PackageID, Dir, Meta, RunArgs); - {{ok, _}, {error, Errors}} -> + NotInstalled = fun(P) -> not filelib:is_dir(zx_lib:ppath(lib, P)) end, + Needed = lists:filter(NotInstalled, Deps), + Pending = lists:map(fun zx_daemon:fetch/1, Needed), + case await_fetches(Pending) of + ok -> + ok = lists:foreach(fun install/1, Needed), + ok = lists:foreach(fun build/1, Needed), + execute(Type, PackageID, Meta, Dir, RunArgs); + {error, Errors} -> error_exit("Failed package fetches: ~tp", [Errors], ?LINE) end. +await_fetches([]) -> ok; +await_fetches(Pending) -> await_fetches(Pending, []). + + +await_fetches([], []) -> + ok; +await_fetches([], Errors) -> + {error, Errors}; +await_fetches(Pending, Errors) -> + {NewPending, NewErrors} = + receive + {z_reply, ID, ok} -> + {lists:delete(ID, Pending), Errors}; + {z_reply, ID, {error, Package, Reason}} -> + {lists:delete(ID, Pending), [{Package, Reason} | Errors]} + end, + await_fetches(NewPending, NewErrors). + + -spec execute(Type, PackageID, Meta, Dir, RunArgs) -> no_return() when Type :: app | lib, PackageID :: package_id(), @@ -390,12 +416,13 @@ execute(PackageID, Meta, Dir, RunArgs) -> %% the exec_wait/1 loop to wait for any queries from the application. execute(app, PackageID, Meta, Dir, RunArgs) -> - PackageString = zx_lib:package_string(PackageID), + {ok, PackageString} = zx_lib:package_string(PackageID), ok = log(info, "Starting ~ts.", [PackageString]), Name = element(2, PackageID), - AppMod = list_to_atom(Name), - ok = zx_daemon:pass_meta(Meta, Dir), - ok = ensure_all_started(AppMod), + AppTag = list_to_atom(Name), + {AppMod, _} = maps:get(appmod, Meta), + ok = zx_daemon:pass_meta(Meta, Dir, RunArgs), + ok = ensure_all_started(AppTag), ok = pass_argv(AppMod, RunArgs), log(info, "Launcher complete."); execute(lib, PackageID, _, _, _) -> diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_conf_sys.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_conf_sys.erl index 146fec8..9777608 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_conf_sys.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_conf_sys.erl @@ -127,14 +127,14 @@ timeout(#d{timeout = Timeout}) -> Timeout. --spec timeout(Data, Value) -> NewData - when Data :: data(), - Value :: pos_integer(), +-spec timeout(Value, Data) -> NewData + when Value :: pos_integer(), + Data :: data(), NewData :: data(). %% @doc %% Set the timeout attribute to a new value. -timeout(Data, Value) +timeout(Value, Data) when is_integer(Value) and Value > 0 -> Data#d{timeout = Value}. @@ -147,14 +147,14 @@ retries(#d{retries = {_, Retries}}) -> Retries. --spec retries(Data, Value) -> NewData - when Data :: data(), - Value :: non_neg_integer(), +-spec retries(Value, Data) -> NewData + when Value :: non_neg_integer(), + Data :: data(), NewData :: data(). %% @doc %% Set the retries attribute to a new value. -retries(Data = #d{retries = {Remaining, _}}, Value) +retries(Value, Data = #d{retries = {Remaining, _}}) when is_integer(Value) and Value >= 0 -> Data#d{retries = {Remaining, Value}}. @@ -192,14 +192,14 @@ maxconn(#d{maxconn = MaxConn}) -> MaxConn. --spec maxconn(Data, Value) -> NewData - when Data :: data(), - Value :: pos_integer(), +-spec maxconn(Value, Data) -> NewData + when Value :: pos_integer(), + Data :: data(), NewData :: data(). %% @doc %% Set the value of maxconn. -maxconn(Data, Value) +maxconn(Value, Data) when is_integer(Value) and Value > 0 -> Data#d{maxconn = Value}. @@ -212,24 +212,24 @@ managed(#d{managed = Managed}) -> sets:to_list(Managed). --spec managed(Data, List) -> NewData - when Data :: data(), - List :: [zx:realm()], +-spec managed(List, Data) -> NewData + when List :: [zx:realm()], + Data :: data(), NewData :: data(). %% @doc %% Reset the set of managed realms entirely. %% The realms must be configured on the current realm at a minimum. -managed(Data, List) -> +managed(List, Data) -> Desired = sets:from_list(List), Configured = sets:from_list(zx_lib:list_realms()), NewManaged = sets:intersection(Desired, Configured), Data#d{managed = NewManaged}. --spec add_managed(Data, Realm) -> Result - when Data :: data(), - Realm :: zx:realm(), +-spec add_managed(Realm, Data) -> Result + when Realm :: zx:realm(), + Data :: data(), Result :: {ok, NewData} | {error, unconfigured}, NewData :: data(). @@ -238,8 +238,8 @@ managed(Data, List) -> %% the current node. This node will then behave as the prime node for the realm (whether %% it is or not). -add_managed(Data = #d{managed = Managed}, Realm) -> - case lists:member(Realm, zx_lib:list_realms()) of +add_managed(Realm, Data = #d{managed = Managed}) -> + case zx_lib:realm_exists(Realm) of true -> NewData = Data#d{managed = sets:add_element(Realm, Managed)}, ok = log(info, "Now managing realm: ~tp", [Realm]), @@ -250,16 +250,16 @@ add_managed(Data = #d{managed = Managed}, Realm) -> end. --spec rem_managed(Data, Realm) -> Result - when Data :: data(), - Realm :: zx:realm(), +-spec rem_managed(Realm, Data) -> Result + when Realm :: zx:realm(), + Data :: data(), Result :: {ok, NewData} | {error, unmanaged}, NewData :: data(). %% @doc %% Stop managing a realm. -rem_managed(Data = #d{managed = Managed}, Realm) -> +rem_managed(Realm, Data = #d{managed = Managed}) -> case sets:is_element(Realm, Managed) of true -> NewData = Data#d{managed = sets:del_element(Realm, Managed)}, @@ -279,20 +279,20 @@ mirrors(#d{mirrors = Mirrors}) -> Mirrors. --spec mirrors(Data, Hosts) -> NewData - when Data :: data(), - Hosts :: [zx:host()], +-spec mirrors(Hosts, Data) -> NewData + when Hosts :: [zx:host()], + Data :: data(), NewData :: data(). %% @private %% Reset the mirror configuration. -mirrors(Data, Hosts) -> +mirrors(Hosts, Data) -> Data#d{mirrors = Hosts}. --spec add_mirror(Data, Host) -> NewData - when Data :: data(), - Host :: zx:host(), +-spec add_mirror(Host, Data) -> NewData + when Host :: zx:host(), + Data :: data(), NewData :: data(). %% @doc %% Add a mirror to the permanent configuration. @@ -304,14 +304,14 @@ add_mirror(Data = #d{mirrors = Mirrors}, Host) -> end. --spec rem_mirror(Data, Host) -> NewData - when Data :: data(), - Host :: zx:host(), +-spec rem_mirror(Host, Data) -> NewData + when Host :: zx:host(), + Data :: data(), NewData :: data(). %% @private %% Remove a host from the list of permanent mirrors. -rem_mirror(Data = #d{mirrors = Mirrors}, Host) -> +rem_mirror(Host, Data = #d{mirrors = Mirrors}) -> Data#d{mirrors = lists:delete(Host, Mirrors)}. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl index a7dada8..6b836c8 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_daemon.erl @@ -147,7 +147,7 @@ -export([pass_meta/3, subscribe/1, unsubscribe/1, list/0, list/1, list/2, list/3, latest/1, - fetch_zsp/1, fetch_key/1, + fetch/1, verify_key/1, pending/1, packagers/1, maintainers/1, sysops/1]). -export([report/1, result/2, notify/2]). -export([start_link/0, stop/0]). @@ -326,6 +326,9 @@ %% references. pass_meta(Meta, Dir, ArgV) -> + ok = log(info, "Meta: ~tp", [Meta]), + ok = log(info, "Dir : ~tp", [Dir]), + ok = log(info, "ArgV: ~tp", [ArgV]), gen_server:cast(?MODULE, {pass_meta, Meta, Dir, ArgV}). @@ -446,7 +449,7 @@ latest({Realm, Name, Version}) -> request({latest, Realm, Name, Version}). --spec fetch_zsp(PackageID) -> {ok, RequestID} +-spec fetch(PackageID) -> {ok, RequestID} when PackageID :: zx:package_id(), RequestID :: integer(). %% @doc @@ -458,27 +461,28 @@ latest({Realm, Name, Version}) -> %% Response messages are of the type `result()' where the third element is of the %% type `fetch_result()'. -fetch_zsp(PackageID = {Realm, Name, Version}) -> +fetch({Realm, Name, Version}) -> true = zx_lib:valid_lower0_9(Realm), true = zx_lib:valid_lower0_9(Name), true = zx_lib:valid_version(Version), - request({fetch, zsp, PackageID}). + request({fetch, Realm, Name, Version}). --spec fetch_key(KeyID) -> {ok, RequestID} +-spec verify_key(KeyID) -> {ok, RequestID} when KeyID :: zx:key_id(), RequestID :: id(). %% @doc -%% Request a public key be fetched from its relevant realm. +%% Request a public key be fetched from its upstream, and pursue the key validation +%% chain until a key in possession is found or the chain is proven to be broken. %% Crashes the caller if either component of the KeyID is illegal. %% %% Response messages are of the type `result()' where the third element is of the %% type `key_result()'. -fetch_key(KeyID = {Realm, KeyName}) -> +verify_key({Realm, KeyName}) -> true = zx_lib:valid_lower0_9(Realm), true = zx_lib:valid_lower0_9(KeyName), - request({fetch, key, KeyID}). + request({verify_key, Realm, KeyName}). -spec pending(Package) -> {ok, RequestID} diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl index e344a69..6f9b6cb 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_key.erl @@ -105,9 +105,7 @@ prompt_keygen() -> -spec generate_rsa(KeyID) -> Result when KeyID :: zx:key_id(), Result :: ok - | {error, keygen_fail}, - KeyFile :: file:filename(), - PubFile :: file:filename(). + | {error, keygen_fail}. %% @private %% Generate an RSA keypair and write them in der format to the current directory, using %% filenames derived from Prefix. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl index a95a618..7d5051e 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_lib.erl @@ -15,8 +15,8 @@ -license("GPL-3.0"). -export([zomp_dir/0, find_zomp_dir/0, - path/1, path/2, path/3, - force_dir/1, + path/1, path/2, path/3, path/4, ppath/2, + force_dir/1, mktemp_dir/1, list_realms/0, hosts_cache_file/1, get_prime/1, realm_meta/1, read_project_meta/0, read_project_meta/1, read_package_meta/1, @@ -71,13 +71,13 @@ find_zomp_dir() -> end. --spec path(Type) -> Result - when Type :: etc - | var - | tmp - | log - | lib, - Result :: file:filename(). +-spec path(Type) -> Path + when Type :: etc + | var + | tmp + | log + | lib, + Path :: file:filename(). %% @private %% Return the top-level path of the given type in the Zomp/ZX system. @@ -85,17 +85,19 @@ path(etc) -> filename:join(zomp_dir(), "etc"); path(var) -> filename:join(zomp_dir(), "var"); path(tmp) -> filename:join(zomp_dir(), "tmp"); path(log) -> filename:join(zomp_dir(), "log"); +path(key) -> filename:join(zomp_dir(), "key"); +path(zsp) -> filename:join(zomp_dir(), "zsp"); path(lib) -> filename:join(zomp_dir(), "lib"). --spec path(Type, Realm) -> Result - when Type :: etc - | var - | tmp - | log - | lib, - Realm :: zx:realm(), - Result :: file:filename(). +-spec path(Type, Realm) -> Path + when Type :: etc + | var + | tmp + | log + | lib, + Realm :: zx:realm(), + Path :: file:filename(). %% @private %% Return the realm-level path of the given type in the Zomp/ZX system. @@ -103,15 +105,15 @@ path(Type, Realm) -> filename:join(path(Type), Realm). --spec path(Type, Realm, Name) -> Result - when Type :: etc - | var - | tmp - | log - | lib, - Realm :: zx:realm(), - Name :: zx:name(), - Result :: file:filename(). +-spec path(Type, Realm, Name) -> Path + when Type :: etc + | var + | tmp + | log + | lib, + Realm :: zx:realm(), + Name :: zx:name(), + Path :: file:filename(). %% @private %% Return the package-level path of the given type in the Zomp/ZX system. @@ -119,6 +121,40 @@ path(Type, Realm, Name) -> filename:join([path(Type), Realm, Name]). +-spec path(Type, Realm, Name, Version) -> Path + when Type :: etc + | var + | tmp + | log + | lib, + Realm :: zx:realm(), + Name :: zx:name(), + Version :: zx:version(), + Path :: file:filename(). +%% @private +%% Return the version-specific level path of the given type in the Zomp/ZX system. + +path(Type, Realm, Name, Version) -> + {ok, VersionString} = version_to_string(Version), + filename:join([path(Type), Realm, Name, VersionString]). + + +-spec ppath(Type, PackageID) -> Path + when Type :: etc + | var + | tmp + | log + | lib, + PackageID :: zx:package_id(), + Path :: file:filename(). +%% @private +%% An alias for path/4, but more convenient when needing a path from a closed +%% package_id(). + +ppath(Type, {Realm, Name, Version}) -> + path(Type, Realm, Name, Version). + + -spec force_dir(Path) -> Result when Path :: file:filename(), Result :: ok @@ -134,6 +170,20 @@ force_dir(Path) -> end. +-spec mktemp_dir(Package) -> Result + when Package :: zx:package(), + Result :: {ok, TempDir :: file:filename()} + | {error, Reason :: file:posix()}. + +mktemp_dir({Realm, Name}) -> + Rand = integer_to_list(binary:decode_unsigned(crypto:strong_rand_bytes(8)), 36), + TempDir = filename:join(path(etc, Realm, Name), Rand), + case force_dir(TempDir) of + ok -> {ok, TempDir}; + Error -> Error + end. + + -spec hosts_cache_file(zx:realm()) -> file:filename(). %% @private %% Given a Realm name, construct a realm's .hosts filename and return it. diff --git a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl b/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl index 02a5c37..caf448d 100644 --- a/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl +++ b/zomp/lib/otpr/zx/0.1.0/src/zx_local.erl @@ -11,11 +11,13 @@ -license("GPL-3.0"). -export([initialize/2, assimilate/1, set_dep/1, set_version/1, - list_realms/0, list_packages/1, list_versions/1, add_realm/1, - drop_dep/1, drop_key/1, drop_realm/1, verup/1, package/1, + list_realms/0, list_packages/1, list_versions/1, + drop_dep/1, verup/1, package/1, + add_realm/1, drop_realm/1, + takeover/1, abdicate/1, create_plt/0, dialyze/0, grow_a_pair/0, drop_key/1, - create_user/2, create_realm/0, create_realmfile/1]). + create_user/2, create_realm/0, create_realmfile/2]). -include("zx_logger.hrl"). @@ -27,13 +29,8 @@ when Type :: app | lib, PackageString :: string(). %% @private -%% Initialize an application in the local directory based on the PackageID provided. -%% This function does not care about the name of the current directory and leaves -%% providing a complete, proper and accurate PackageID. -%% This function will check the current `lib/' directory for zomp-style dependencies. -%% If this is not the intended function or if there are non-compliant directory names -%% in `lib/' then the project will need to be rearranged to become zomp compliant or -%% the `deps' section of the resulting meta file will need to be manually updated. +%% Initialize an application in the local directory based on the PackageID provided +%% and interaction with the user to determine a few details. initialize(Type, RawPackageString) -> ok = @@ -41,7 +38,7 @@ initialize(Type, RawPackageString) -> false -> ok; true -> error_exit("This project is already Zompified.", ?LINE) end, - PackageID = + PackageID = {Realm, Name, _} = case zx_lib:package_id(RawPackageString) of {ok, {R, N, {z, z, z}}} -> {R, N, {0, 1, 0}}; @@ -54,19 +51,58 @@ initialize(Type, RawPackageString) -> {error, invalid_package_string} -> error_exit("Invalid package string: ~tp", [RawPackageString], ?LINE) end, - {ok, PackageString} = zx_lib:package_string(PackageID), - ok = check_package_conflict(PackageID, PackageString), - ok = log(info, "Initializing ~s...", [PackageString]), - Prefix = solicit_prefix(), + case package_exists(PackageID) of + false -> + Prefix = solicit_prefix(), + initialize(Type, PackageID, Prefix); + true -> + PackageName = zx_lib:package_string({Realm, Name, {z, z, z}}), + Message = "Package ~tp already exists. Try another.", + error_exit(Message, [PackageName], ?LINE) + end. + + +initialize(app, PackageID, Prefix) -> + Instructions = + "The OTP application controller has to know what module to call start/2 on to " + "start your program.~n" + "If this is not an OTP application or if there is some special setting you " + "need then ignore this by just pressing [ENTER] to bypass this and go back " + "later and edit the 'mod' attribute in your ebin/[AppName].app file by hand.~n" + "this blank.~n" + "Enter the name of your main/start interface module where the application " + "callback start/2 has been defined.~n", + ok = io:format(Instructions), + case zx_tty:get_input() of + "" -> + initialize(lib, PackageID, Prefix, none); + String -> + case zx_lib:valid_lower0_9(String) of + true -> + AppStart = {list_to_atom(String), []}, + initialize(app, PackageID, Prefix, AppStart); + false -> + Message = "The name \"~ts\" doesn't seem valid. Try \"[a-z_]*\".~n", + ok = io:format(Message, String), + initialize(app, PackageID, Prefix) + end + end; +initialize(lib, PackageID, Prefix) -> + initialize(lib, PackageID, Prefix, none). + + +initialize(Type, PackageID, Prefix, AppStart) -> ok = update_source_vsn(element(3, PackageID)), - ok = update_app_file(PackageID), + ok = initialize_app_file(PackageID, AppStart), MetaList = [{package_id, PackageID}, {deps, []}, {type, Type}, - {prefix, Prefix}], + {prefix, Prefix}, + {appmod, AppStart}], Meta = maps:from_list(MetaList), ok = zx_lib:write_project_meta(Meta), + {ok, PackageString} = zx_lib:package_string(PackageID), ok = log(info, "Project ~tp initialized.", [PackageString]), Message = "~nNOTICE:~n" @@ -76,36 +112,54 @@ initialize(Type, RawPackageString) -> io:format(Message). --spec check_package_conflict(zx:package_id(), string()) -> ok. +-spec package_exists(zx:package_id()) -> boolean(). %% @private %% Check the realm's upstream for the existence of a package that already has the same %% name, or the name of any modules in src/ and report them to the user. Give the user %% a chance to change their package's name or ignore the conflict, and report all module %% naming conflicts. -check_package_conflict(_PackageID, _PackageString) -> - log(info, "TODO: This is where the intended realm is checked for conflicts " - "and the user is given a chance to rename or ignore the conflict. " - "This check will have to wait for the network protocol fix."). +package_exists(PackageID) -> + ok = log(info, "TODO: This is where the intended realm is checked for conflicts " + "and the user is given a chance to rename or ignore the conflict. " + "This check will have to wait for the network protocol fix."), + ok = log(info, "Pretending that the existence of ~tp was checked...", [PackageID]), + false. --spec solicit_prefix() -> ok. +-spec solicit_prefix() -> string(). %% @private -%% Most Erlang projects outside of the core distribution evolve a prefix of some sort -%% to namespace their work from other projects (or deliberately mimic another project's -%% prefix in the case that a new project is meant to augment, but not alter, an already -%% existing one). -%% A prefix is decided upon or disregarded here and stored in the meta file. The user -%% is given the option to update module names now (to include modifying call sites -%% in project code automatically -- while providing ample warnings about breaking -%% external code). -%% Just like with check_package_conflict/2, this procedure gives the user a chance to -%% ignore all warnings and just go. +%% Get a valid module prefix to use as a namespace for new modules. solicit_prefix() -> - ok = log(info, "Will solicit a prefix here. Just returning \"zz\" right now. Hah!"), - "zz". - + Instructions = + "~nPICKING A PREFIX~n" + "Most Erlang applications have a prefix on the front of their modules. This " + "is a way of namespacing modules from different applications so they don't " + "conflict in very large execution environments.~n" + "A common way of coming up with a prefix is to make a lower-case acronym of " + "your application name and add an underscore at the end, unless your app " + "name is so short that it isn't a problem to use it as its own prefix.~n" + "For example: the prefix for all ZX modules is 'zx_', and the prefix for " + "otpr-example_server is 'es_'.~n" + "If you already have a prefix, just type it here (including the trailing " + "underscore if there is one). If you are making up a new one in an existing " + "project you'll need to rename your modules and fix up call sites with sed.~n" + "Enter a blank prefix (just [ENTER]) to ignore this and use no prefix.~n", + ok = io:format(Instructions), + case zx_tty:get_input() of + "" -> + ""; + Prefix -> + case zx_lib:valid_lower0_9(Prefix) of + true -> + Prefix; + false -> + Message = "The string \"~tp\" is problematic. Try \"[a-z]*_\".~n", + ok = io:format(Message, [Prefix]), + solicit_prefix() + end + end. -spec update_source_vsn(zx:version()) -> ok. %% @private @@ -128,17 +182,25 @@ update_source_vsn(Version) -> log(info, "Source version attributes set"). --spec update_app_file(zx:package_id()) -> ok. +-spec update_app_vsn(zx:name(), zx:version()) -> ok. + +update_app_vsn(Name, Version) -> + {ok, VersionString} = zx_lib:version_to_string(Version), + AppFile = filename:join("ebin", Name ++ ".app"), + {ok, [{application, AppName, OldAppData}]} = file:consult(AppFile), + NewAppData = lists:keystore(vsn, 1, OldAppData, {vsn, VersionString}), + zx_lib:write_terms(AppFile, [{application, AppName, NewAppData}]). + + +-spec initialize_app_file(PackageID, AppStart) -> ok + when PackageID :: zx:package_id(), + AppStart :: {StartMod :: module(), Args :: term()} + | none. %% @private %% Update the app file or create it if it is missing. -%% TODO: If the app file is missing, interpret the src/*app.src file correctly. -%% Should really pull a few pages out of the rebar/erland.mk books on this, -%% as they've done all the hard pioneering work already. -%% TODO: Interactively determine whether the main module is the name, or the prefix -%% is the name, or the name is the prefix, or the name is the description, etc. -%% before writing anything out. +%% TODO: If the app file is missing and an app/*.src exists, interpret that. -update_app_file({_, Name, Version}) -> +initialize_app_file({_, Name, Version}, AppStart) -> {ok, VersionString} = zx_lib:version_to_string(Version), AppName = list_to_atom(Name), AppFile = filename:join("ebin", Name ++ ".app"), @@ -157,13 +219,24 @@ update_app_file({_, Name, Version}) -> Grep = "grep -oP '^-module\\(\\K[^)]+' src/* | cut -d: -f2", Modules = [list_to_atom(M) || M <- string:lexemes(os:cmd(Grep), "\n")], Properties = - [{vsn, VersionString}, - {modules, Modules}, - {mod, {AppName, []}}], + case (AppStart == none) or lists:keymember(mod, 1, RawAppData) of + true -> + [{vsn, VersionString}, + {modules, Modules}]; + false -> + [{vsn, VersionString}, + {modules, Modules}, + {mod, AppStart}] + end, Store = fun(T, L) -> lists:keystore(element(1, T), 1, L, T) end, AppData = lists:foldl(Store, RawAppData, Properties), AppProfile = {application, AppName, AppData}, - ok = log(info, "Writing app file: ~ts~n~tp", [AppFile, AppProfile]), + ok = log(info, "Writing app file: ~ts", [AppFile]), + Notice = + "NOTE: From this point on ZX will only update the module list and version " + "number of your .app file. Any additional changes (start module/args, start " + "phases, etc.) will need to be done by hand.~n", + ok = io:format(Notice), zx_lib:write_terms(AppFile, [AppProfile]). @@ -318,6 +391,8 @@ update_version(Realm, Name, OldVersion, NewVersion, OldMeta) -> PackageID = {Realm, Name, NewVersion}, NewMeta = maps:put(package_id, PackageID, OldMeta), ok = zx_lib:write_project_meta(NewMeta), + ok = update_source_vsn(NewVersion), + ok = update_app_vsn(Name, NewVersion), {ok, OldVS} = zx_lib:version_to_string(OldVersion), {ok, NewVS} = zx_lib:version_to_string(NewVersion), log(info, "Version changed from ~s to ~s.", [OldVS, NewVS]). @@ -339,7 +414,6 @@ list_realms() -> %% them to stdout. list_packages(Realm) -> - ok = zx:start(), case zx_daemon:list_packages(Realm) of {ok, []} -> log(info, "Realm ~tp has no packages available.", [Realm]); @@ -415,22 +489,6 @@ add_realm(Path) -> end. --spec add_realm(Path, Data) -> ok - when Path :: file:filename(), - Data :: binary(). - -add_realm(Path, Data) -> - case erl_tar:extract({binary, Data}, [compressed, {cwd, zx_lib:zomp_dir()}]) of - ok -> - {Realm, _} = string:take(filename:basename(Path), ".", true), - log(info, "Realm ~ts is now visible to this system.", [Realm]); - {error, invalid_tar_checksum} -> - error_exit("~ts is not a valid realm file.", [Path], ?LINE); - {error, eof} -> - error_exit("~ts is not a valid realm file.", [Path], ?LINE) - end. - - -spec drop_dep(zx:package_id()) -> ok. %% @private %% Remove the indicate dependency from the local project's zomp.meta record. @@ -458,59 +516,6 @@ drop_dep(PackageID) -> end. --spec drop_realm(zx:realm()) -> ok. - -drop_realm(Realm) -> - ok = file:set_cwd(zx_lib:zomp_dir()), - RealmConf = zx_lib:realm_conf(Realm), - case filelib:is_regular(RealmConf) of - true -> - Message = - "~n" - " WARNING: Are you SURE you want to remove realm ~ts?~n" - " (Only \"Y\" will confirm this action.)~n", - ok = io:format(Message, [Realm]), - case zx_tty:get_input() of - "Y" -> - ok = file:delete(RealmConf), - ok = drop_prime(Realm), - ok = clear_keys(Realm), - log(info, "All traces of realm ~ts have been removed."); - _ -> - log(info, "Aborting.") - end; - false -> - ok = log(warning, "Realm conf ~ts not found.", [RealmConf]), - clear_keys(Realm) - end. - - --spec drop_prime(zx:realm()) -> ok. - -drop_prime(Realm) -> - Path = "zomp.conf", - case file:consult(Path) of - {ok, Conf} -> - {managed, Primes} = lists:keyfind(managed, 1, Conf), - NewPrimes = lists:delete(Realm, Primes), - NewConf = lists:keystore(managed, 1, Primes, {managed, NewPrimes}), - ok = zx_lib:write_terms(Path, NewConf), - log(info, "Ensuring ~ts is not a prime in ~ts", [Realm, Path]); - {error, enoent} -> - ok - end. - - --spec clear_keys(zx:realm()) -> ok. - -clear_keys(Realm) -> - KeyDir = filename:join([zx_lib:zomp_dir(), "key", Realm]), - case filelib:is_dir(KeyDir) of - true -> zx_lib:rm_rf(KeyDir); - false -> log(warning, "Keydir ~ts not found", [KeyDir]) - end. - - -spec verup(Level) -> ok when Level :: string(). %% @private @@ -784,8 +789,7 @@ create_realm() -> Realm = zx_tty:get_input(), case zx_lib:valid_lower0_9(Realm) of true -> - RealmFile = filename:join(zx_lib:zomp_dir(), Realm ++ ".realm"), - case filelib:is_regular(RealmFile) of + case realm_exists(Realm) of false -> create_realm(Realm); true -> @@ -964,71 +968,72 @@ create_realm(Realm, Address, Port, UserName, Email) -> create_realm(Realm, Address, Port, UserName, Email, RealName) -> ok = io:format("~nGenerating keys. This might take a while, so settle in...~n"), KeyName = UserName ++ "-root", + ok = make_realm_dirs(Realm), ok = zx_key:generate_rsa({Realm, KeyName}), - Timestamp = calendar:now_to_universal_time(erlang:timestamp()), RealmConf = [{realm, Realm}, {prime, {Address, Port}}, {sysop, UserName}, {key, KeyName}], - UserFile = + UserConf = [{realm, Realm}, {username, UserName}, {realmname, RealName}, {contact_info, {"email", Email}}, {keys, [KeyName]}], - - {ok, CWD} = file:get_cwd(), - ok = file:set_cwd(zx_lib:path(etc)), - PubKey = zx_key:name(pub, KeyName), - ConfFile = filename:join(Realm, "realm.conf"), - ok = file:make_dir(Realm), - ok = zx_lib:write_terms(ConfFile, RealmConf), - ZRF = - ok = erl_tar:create(ZRF, [ConfFile, PubKey], [compressed]), - - + ok = make_realm_dirs(Realm), + RealmConfPath = filename:join(zx_lib:path(etc, Realm), "realm.conf"), + ok = zx_lib:write_terms(RealmConfPath, RealmConf), + UserConfPath = filename:join(zx_lib:path(etc, Realm), UserName ++ ".user"), + ok = zx_lib:write_terms(UserConfPath, UserConf), + ok = create_realmfile(Realm, "."), + ZRF = Realm ++ ".zrf", Message = "===========================================================================~n" "DONE!~n" "~n" - "The realm ~ts has been created and is accessible from the current system.~n" - "Three configuration bundles have been created in the current directory:~n" + "The realm ~tp has been created and is accessible from the current system.~n" "~n" - " 1. ~ts ~n" - "This is the PRIVATE realm file you will need to install on the realm's prime~n" - "node. It includes the your (the sysop's) public key.~n" + "Other zomp nodes and zx users will need the new realm file, ~ts, to~n" + "access the realm. It does not include any public keys.~n" "~n" - " 2. ~ts ~n" - "This file is the PUBLIC realm file other zomp nodes and zx users will need~n" - "to access the realm. It does not include your (the sysop's) public key.~n" + "On the PRIME NODE you need to run two command:~n" + "1. `zx add realm ~ts` to install the new .zrf there~n" + "2. `zx make prime ~ts` to tell the node that it is prime for that realm~n" "~n" - " 3. ~ts ~n" - "This is the bundle of ALL KEYS that are defined in this realm at the moment.~n" + "On all ZX clients that want to access your new realm and on all subordinate~n" + "Zomp nodes the command `zx add realm ~ts` will need to be run.~n" "~n" - "Now you need to make copies of these three files and back them up.~n" - "~n" - "On the PRIME NODE you need to run `zx add realm ~ts` and follow the prompts~n" - "to cause it to begin serving that realm as prime. (Node restart required.)~n" - "~n" - "On all zx CLIENTS that want to access your new realm and on all subordinate~n" - "MIRROR NODES the command `zx add realm ~ts` will need to be run.~n" - "The method of public realm file distribution (~ts) is up to you.~n" - "~n" - "~n" - "Public & Private key installation (if you need to recover them or perform~n" - "sysop functions from another computer) is `zx add keybundle ~ts`.~n" + "How to distribute ~ts is up to you.~n" "===========================================================================~n", - Substitutions = - [Realm, - PrimeZRF, PublicZRF, KeyBundle, - PrimeZRF, - PublicZRF, PublicZRF, - KeyBundle], + Substitutions = [Realm, ZRF, ZRF, Realm, ZRF, ZRF], io:format(Message, Substitutions). --spec configure_zomp() - ok. +-spec realm_exists(zx:realm()) -> boolean(). +%% @private +%% Checks for remnants of a realm. + +realm_exists(Realm) -> + Dirs = [etc, var, tmp, log, key, zsp, lib], + Check = fun(D) -> filelib:is_file(zx_lib:path(D)) end, + Managed = lists:member(Realm, proplists:get_value(managed, zx_lib:read_sys_conf())), + Found = lists:any(Check, Dirs), + Managed or Found. + + +-spec make_realm_dirs(zx:realm()) -> ok. +%% @private +%% This is an unsafe operation. The caller must be sure realm_exists/1 returns false +%% or be certain some other way that the file:make_sure/1 calls will succeed. + +make_realm_dirs(Realm) -> + Dirs = [etc, var, tmp, log, key, zsp, lib], + Make = fun(D) -> ok = file:make_dir(zx_lib:path(D, Realm)) end, + lists:foreach(Make, Dirs). + + +-spec configure_zomp() -> ok. configure_zomp() -> ZompSettings = @@ -1040,54 +1045,84 @@ configure_zomp() -> io:format("~tp~n", [ZompSettings]). --spec mktemp_dir(Prefix) -> Result - when Prefix :: string(), - Result :: {ok, TempDir :: file:filename()} - | {error, Reason :: file:posix()}. +-spec create_realmfile(Realm, Dir) -> ok + when Realm :: zx:realm(), + Dir :: file:filename(). -mktemp_dir(Prefix) -> - Rand = integer_to_list(binary:decode_unsigned(crypto:strong_rand_bytes(8)), 36), - TempPath = filename:basedir(user_cache, Prefix), - TempDir = filename:join(TempPath, Rand), - Result1 = filelib:ensure_dir(TempDir), - Result2 = file:make_dir(TempDir), - case {Result1, Result2} of - {ok, ok} -> {ok, TempDir}; - {ok, Error} -> Error; - {Error, _} -> Error +create_realmfile(Realm, Dir) -> + RealmConf = zx_lib:load_realm_conf(Realm), + ok = log(info, "Realm found, creating realm file..."), + KeyName = proplists:get_value(key, RealmConf), + PubKeyPath = zx_key:name(pub, KeyName), + {ok, PubDER} = file:read_file(PubKeyPath), + Blob = term_to_binary({RealmConf, PubDER}), + ZRF = filename:join(Dir, Realm ++ ".zrf"), + ok = file:write_file(ZRF, Blob), + log(info, "Realm conf file written to ~ts", [ZRF]). + + +-spec add_realm(Path, Data) -> ok + when Path :: file:filename(), + Data :: binary(). + +add_realm(Path, Data) -> + case erl_tar:extract({binary, Data}, [compressed, {cwd, zx_lib:zomp_dir()}]) of + ok -> + {Realm, _} = string:take(filename:basename(Path), ".", true), + log(info, "Realm ~ts is now visible to this system.", [Realm]); + {error, invalid_tar_checksum} -> + error_exit("~ts is not a valid realm file.", [Path], ?LINE); + {error, eof} -> + error_exit("~ts is not a valid realm file.", [Path], ?LINE) end. --spec create_realmfile(zx:realm()) -> no_return(). +-spec drop_realm(zx:realm()) -> ok. -create_realmfile(Realm) -> - RealmConf = zx_lib:load_realm_conf(Realm), - ok = log(info, "Realm found, creating realm file..."), - {revision, Revision} = lists:keyfind(revision, 1, RealmConf), - {realm_keys, RealmKeys} = lists:keyfind(realm_keys, 1, RealmConf), - {package_keys, PackageKeys} = lists:keyfind(package_keys, 1, RealmConf), - RealmKeyIDs = [element(1, K) || K <- RealmKeys], - PackageKeyIDs = [element(1, K) || K <- PackageKeys], - create_realmfile(Realm, Revision, RealmKeyIDs, PackageKeyIDs). +drop_realm(Realm) -> + case realm_exists(Realm) of + true -> + Message = + "~n" + " WARNING: Are you SURE you want to remove realm ~ts?~n" + " (Only \"Y\" will confirm this action.)~n", + ok = io:format(Message, [Realm]), + case zx_tty:get_input() of + "Y" -> + Dirs = [etc, var, tmp, log, key, zsp, lib], + RM = fun(D) -> ok = zx_lib:rm_rf(zx_lib:path(D, Realm)) end, + ok = lists:foreach(RM, Dirs), + ok = abdicate(Realm), + log(info, "All traces of realm ~ts have been removed."); + _ -> + log(info, "Aborting.") + end; + false -> + log(info, "Could not find any trace of realm ~tp", [Realm]) + end. --spec create_realmfile(Realm, Revision, RealmKeyIDs, PackageKeyIDs) -> ok - when Realm :: zx:realm(), - Revision :: non_neg_integer(), - RealmKeyIDs :: [zx:key_id()], - PackageKeyIDs :: [zx:key_id()]. +-spec takeover(zx:realm()) -> ok. +%% @private +%% Assume responsibilities as the prime node for the given realm. Only works if +%% the realm exists, of course. -create_realmfile(Realm, Revision, RealmKeyIDs, PackageKeyIDs) -> - {ok, CWD} = file:get_cwd(), - ok = file:set_cwd(zx_lib:zomp_dir()), - KeyPath = fun({R, K}) -> filename:join(["key", R, K ++ ".pub.der"]) end, - RealmKeyPaths = lists:map(KeyPath, RealmKeyIDs), - PackageKeyPaths = lists:map(KeyPath, PackageKeyIDs), - Targets = [zx_lib:realm_conf(Realm) | RealmKeyPaths ++ PackageKeyPaths], - OutFile = filename:join(CWD, Realm ++ "." ++ integer_to_list(Revision) ++ ".zrf"), - ok = erl_tar:create(OutFile, Targets, [compressed]), - ok = log(info, "Realm conf file written to ~ts", [OutFile]), - halt(0). +takeover(Realm) -> + SysConf = zx_conf_sys:load(), + case zx_conf_sys:add_managed(Realm, SysConf) of + {ok, NewConf} -> zx_conf_sys:save(NewConf); + {error, unconfigured} -> log(error, "Cannot take over an unconfigured realm.") + end. + + +-spec abdicate(zx:realm()) -> ok. + +abdicate(Realm) -> + SysConf = zx_conf_sys:load(), + case zx_conf_sys:rem_managed(Realm, SysConf) of + {ok, NewConf} -> zx_conf_sys:save(NewConf); + {error, unmanaged} -> log(error, "Cannot abdicate an unmanaged realm.") + end. diff --git a/zx_dev b/zx_dev index a370431..8e30d1c 100755 --- a/zx_dev +++ b/zx_dev @@ -12,4 +12,4 @@ export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION" pushd "$ZX_DIR" > /dev/null ./make_zx popd > /dev/null -erl -pa "$ZX_DIR/ebin" -run zx run $@ +erl -noshell -pa "$ZX_DIR/ebin" -run zx do $@ diff --git a/zxh_dev b/zxh_dev new file mode 100755 index 0000000..b65f80f --- /dev/null +++ b/zxh_dev @@ -0,0 +1,15 @@ +#!/bin/bash + +pushd $(dirname $BASH_SOURCE) > /dev/null +ZX_DEV_ROOT=$PWD +popd > /dev/null +export ZOMP_DIR="$ZX_DEV_ROOT/tester" +rm -rf "$ZOMP_DIR" +cp -r "$ZX_DEV_ROOT/zomp" "$ZOMP_DIR" +VERSION=$(cat "$ZOMP_DIR/etc/version.txt") +export ZX_DIR="$ZOMP_DIR/lib/otpr/zx/$VERSION" + +pushd "$ZX_DIR" > /dev/null +./make_zx +popd > /dev/null +erl -pa "$ZX_DIR/ebin" -run zx do $@