From efb3aae55908cb88f5a9c900d6563603ab792d6a Mon Sep 17 00:00:00 2001 From: Bruce Hill Date: Tue, 20 May 2025 15:22:41 -0400 Subject: Add more advanced configuration options to modules.ini and support automatically downloading and installing from it. --- src/compile.c | 19 ++++++--- src/environment.c | 40 ------------------ src/environment.h | 1 - src/modules.c | 121 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/modules.h | 14 +++++++ src/tomo.c | 10 +++-- src/typecheck.c | 12 ++++-- 7 files changed, 162 insertions(+), 55 deletions(-) create mode 100644 src/modules.c create mode 100644 src/modules.h (limited to 'src') diff --git a/src/compile.c b/src/compile.c index cd6aef72..2ecb8c9e 100644 --- a/src/compile.c +++ b/src/compile.c @@ -12,6 +12,7 @@ #include "cordhelpers.h" #include "enums.h" #include "environment.h" +#include "modules.h" #include "parse.h" #include "stdlib/integers.h" #include "stdlib/nums.h" @@ -1976,10 +1977,13 @@ static CORD _compile_statement(env_t *env, ast_t *ast) CORD suffix = get_id_suffix(Path$as_c_string(path)); return with_source_info(env, ast, CORD_all("$initialize", suffix, "();\n")); } else if (use->what == USE_MODULE) { - const char *name = module_alias(ast); + module_info_t mod = get_module_info(ast); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", name, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) - code_err(ast, "Could not find library"); + const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; + if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { + if (!try_install_module(mod)) + code_err(ast, "Could not find library"); + } CORD initialization = CORD_EMPTY; @@ -4485,10 +4489,13 @@ CORD compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) Path_t build_dir = Path$resolved(Path$parent(header_path), Path$current_dir()); switch (use->what) { case USE_MODULE: { - const char *name = module_alias(ast); + module_info_t mod = get_module_info(ast); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", name, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) - code_err(ast, "Could not find library"); + const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; + if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { + if (!try_install_module(mod)) + code_err(ast, "Could not find library"); + } CORD includes = CORD_EMPTY; for (size_t i = 0; i < tm_files.gl_pathc; i++) { diff --git a/src/environment.c b/src/environment.c index 4fdfbd4e..05ebed5a 100644 --- a/src/environment.c +++ b/src/environment.c @@ -7,12 +7,8 @@ #include "cordhelpers.h" #include "environment.h" #include "parse.h" -#include "stdlib/c_strings.h" #include "stdlib/datatypes.h" -#include "stdlib/memory.h" #include "stdlib/paths.h" -#include "stdlib/pointers.h" -#include "stdlib/simpleparse.h" #include "stdlib/tables.h" #include "stdlib/text.h" #include "stdlib/util.h" @@ -793,40 +789,4 @@ void set_binding(env_t *env, const char *name, type_t *type, CORD code) Table$str_set(env->locals, name, new(binding_t, .type=type, .code=code)); } -const char *module_alias(ast_t *use) -{ - static Table_t cache = {}; - const char **cached = Table$get(cache, &use, Table$info(Pointer$info("@", &Memory$info), &CString$info)); - if (cached) return *cached; - const char *name = Match(use, Use)->path; - const char *alias = name; - if (streq(name, "commands")) alias = "commands_v1.0"; - else if (streq(name, "random")) alias = "random_v1.0"; - else if (streq(name, "base64")) alias = "base64_v1.0"; - else if (streq(name, "core")) alias = "core_v1.0"; - else if (streq(name, "patterns")) alias = "patterns_v1.0"; - else if (streq(name, "pthreads")) alias = "pthreads_v1.0"; - else if (streq(name, "shell")) alias = "shell_v1.0"; - else if (streq(name, "time")) alias = "time_v1.0"; - else if (streq(name, "uuid")) alias = "uuid_v1.0"; - else { - Path_t alias_file = Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")); - OptionalClosure_t by_line = Path$by_line(alias_file); - if (by_line.fn) { - OptionalText_t (*next_line)(void*) = by_line.fn; - for (Text_t line; (line=next_line(by_line.userdata)).length >= 0; ) { - char *line_str = Text$as_c_string(line); - const char *line_alias = NULL, *full_version = NULL; - if (!strparse(line_str, &line_alias, "=", &full_version) - && streq(line_alias, name)) { - alias = full_version; - break; - } - } - } - } - Table$set(&cache, &use, &alias, Table$info(Pointer$info("@", &Memory$info), &CString$info)); - return alias; -} - // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/environment.h b/src/environment.h index 474bab61..a89935e6 100644 --- a/src/environment.h +++ b/src/environment.h @@ -94,6 +94,5 @@ binding_t *get_namespace_binding(env_t *env, ast_t *self, const char *name); extern type_t *TEXT_TYPE; extern type_t *PATH_TYPE; extern type_t *PATH_TYPE_TYPE; -const char *module_alias(ast_t *use); // vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/modules.c b/src/modules.c new file mode 100644 index 00000000..71e33eec --- /dev/null +++ b/src/modules.c @@ -0,0 +1,121 @@ +#include "modules.h" +#include "stdlib/memory.h" +#include "stdlib/paths.h" +#include "stdlib/simpleparse.h" +#include "stdlib/pointers.h" +#include "stdlib/tables.h" +#include "stdlib/text.h" +#include "stdlib/types.h" + +#define xsystem(...) ({ int _status = system(String(__VA_ARGS__)); if (!WIFEXITED(_status) || WEXITSTATUS(_status) != 0) errx(1, "Failed to run command: %s", String(__VA_ARGS__)); }) + +module_info_t get_module_info(ast_t *use) +{ + static Table_t cache = {}; + TypeInfo_t *cache_type = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info)); + module_info_t **cached = Table$get(cache, &use, cache_type); + if (cached) return **cached; + const char *name = Match(use, Use)->path; + module_info_t *info = new(module_info_t, .name=name); + if (streq(name, "commands")) info->version = "v1.0"; + else if (streq(name, "random")) info->version = "v1.0"; + else if (streq(name, "base64")) info->version = "v1.0"; + else if (streq(name, "core")) info->version = "v1.0"; + else if (streq(name, "patterns")) info->version = "v1.0"; + else if (streq(name, "pthreads")) info->version = "v1.0"; + else if (streq(name, "shell")) info->version = "v1.0"; + else if (streq(name, "time")) info->version = "v1.0"; + else if (streq(name, "uuid")) info->version = "v1.0"; + else { + Path_t alias_file = Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")); + OptionalClosure_t by_line = Path$by_line(alias_file); + if (by_line.fn) { + OptionalText_t (*next_line)(void*) = by_line.fn; + find_section:; + for (Text_t line; (line=next_line(by_line.userdata)).length >= 0; ) { + char *line_str = Text$as_c_string(line); + if (line_str[0] == '[' && strncmp(line_str+1, name, strlen(name)) == 0 + && line_str[1+strlen(name)] == ']') + break; + } + for (Text_t line; (line=next_line(by_line.userdata)).length >= 0; ) { + char *line_str = Text$as_c_string(line); + if (line_str[0] == '[') goto find_section; + if (!strparse(line_str, "version=", &info->version) + || !strparse(line_str, "url=", &info->url) + || !strparse(line_str, "git=", &info->git) + || !strparse(line_str, "path=", &info->path) + || !strparse(line_str, "revision=", &info->revision)) + continue; + } + } + } + Table$set(&cache, &use, &info, cache_type); + return *info; + +} + +bool try_install_module(module_info_t mod) +{ + if (mod.git) { + OptionalText_t answer = ask( + Texts(Text("The module \""), Text$from_str(mod.name), Text("\" is not installed.\nDo you want to install it from git URL "), + Text$from_str(mod.git), Text("? [Y/n] ")), + true, true); + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + print("Installing ", mod.name, " from git..."); + Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); + if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", tmpdir); + else xsystem("git clone --depth=1 ", mod.git, " ", tmpdir); + if (mod.path) xsystem("tomo -IL ", tmpdir, "/", mod.path); + else xsystem("tomo -IL ", tmpdir); + Path$remove(tmpdir, true); + return true; + } else if (mod.url) { + OptionalText_t answer = ask( + Texts(Text("The module "), Text$from_str(mod.name), Text(" is not installed.\nDo you want to install it from URL "), + Text$from_str(mod.url), Text("? [Y/n] ")), + true, true); + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + + print("Installing ", mod.name, " from URL..."); + + const char *p = strrchr(mod.url, '/'); + if (!p) return false; + const char *filename = p + 1; + p = strchr(filename, '.'); + if (!p) return false; + const char *extension = p + 1; + Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX")); + xsystem("curl ", mod.url, " -o ", tmpdir); + if (streq(extension, ".zip")) + xsystem("unzip ", tmpdir, "/", filename); + else if (streq(extension, ".tar.gz") || streq(extension, ".tar")) + xsystem("tar xf ", tmpdir, "/", filename); + else + return false; + const char *basename = String(string_slice(filename, strcspn(filename, "."))); + if (mod.path) xsystem("tomo -IL ", tmpdir, "/", basename, "/", mod.path); + else xsystem("tomo -IL ", tmpdir, "/", basename); + Path$remove(tmpdir, true); + return true; + } else if (mod.path) { + OptionalText_t answer = ask( + Texts(Text("The module "), Text$from_str(mod.name), Text(" is not installed.\nDo you want to install it from path "), + Text$from_str(mod.path), Text("? [Y/n] ")), + true, true); + if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y")))) + return false; + + print("Installing ", mod.name, " from path..."); + xsystem("tomo -IL ", mod.path); + return true; + } + + return false; +} + + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/modules.h b/src/modules.h new file mode 100644 index 00000000..36b05d3d --- /dev/null +++ b/src/modules.h @@ -0,0 +1,14 @@ +#pragma once + +#include + +#include "ast.h" + +typedef struct { + const char *name, *version, *url, *git, *revision, *path; +} module_info_t; + +module_info_t get_module_info(ast_t *use); +bool try_install_module(module_info_t mod); + +// vim: ts=4 sw=0 et cino=L2,l1,(0,W4,m1,\:0 diff --git a/src/tomo.c b/src/tomo.c index a2ccc84a..b47cd8e1 100644 --- a/src/tomo.c +++ b/src/tomo.c @@ -16,6 +16,7 @@ #include "ast.h" #include "compile.h" #include "cordhelpers.h" +#include "modules.h" #include "parse.h" #include "stdlib/bools.h" #include "stdlib/bytes.h" @@ -614,14 +615,15 @@ void build_file_dependency_graph(Path_t path, Table_t *to_compile, Table_t *to_l break; } case USE_MODULE: { - const char *name = module_alias(stmt_ast); + module_info_t mod = get_module_info(stmt_ast); + const char *full_name = mod.version ? String(mod.name, "_", mod.version) : mod.name; Text_t lib = Texts(Text("-Wl,-rpath,'"), - Text(TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), Text$from_str(name), + Text(TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), Text$from_str(full_name), Text("' '" TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), - Text$from_str(name), Text("/lib"), Text$from_str(name), Text(SHARED_SUFFIX "'")); + Text$from_str(full_name), Text("/lib"), Text$from_str(full_name), Text(SHARED_SUFFIX "'")); Table$set(to_link, &lib, NULL, Table$info(&Text$info, &Void$info)); - List_t children = Path$glob(Path$from_str(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", name, "/*.tm"))); + List_t children = Path$glob(Path$from_str(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", full_name, "/[!._0-9]*.tm"))); for (int64_t i = 0; i < children.length; i++) { Path_t *child = (Path_t*)(children.data + i*children.stride); Table_t discarded = {.fallback=to_compile}; diff --git a/src/typecheck.c b/src/typecheck.c index 60cef3fc..f97235b4 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -11,6 +11,7 @@ #include "ast.h" #include "cordhelpers.h" #include "environment.h" +#include "modules.h" #include "parse.h" #include "stdlib/paths.h" #include "stdlib/tables.h" @@ -182,13 +183,16 @@ static env_t *load_module(env_t *env, ast_t *module_ast) return load_module_env(env, ast); } case USE_MODULE: { - const char *name = module_alias(module_ast); + module_info_t mod = get_module_info(module_ast); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", name, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) - code_err(module_ast, "Could not find library"); + const char *folder = mod.version ? String(mod.name, "_", mod.version) : mod.name; + if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", folder, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) { + if (!try_install_module(mod)) + code_err(module_ast, "Could not find or install library"); + } env_t *module_env = fresh_scope(env); - Table$str_set(env->imports, name, module_env); + Table$str_set(env->imports, mod.name, module_env); for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; -- cgit v1.2.3