diff options
| -rw-r--r-- | docs/libraries.md | 32 | ||||
| -rw-r--r-- | docs/versions.md | 2 | ||||
| -rw-r--r-- | src/compile.c | 14 | ||||
| -rw-r--r-- | src/environment.c | 44 | ||||
| -rw-r--r-- | src/environment.h | 1 | ||||
| -rw-r--r-- | src/stdlib/paths.c | 13 | ||||
| -rw-r--r-- | src/stdlib/paths.h | 3 | ||||
| -rw-r--r-- | src/tomo.c | 21 | ||||
| -rw-r--r-- | src/typecheck.c | 5 |
9 files changed, 108 insertions, 27 deletions
diff --git a/docs/libraries.md b/docs/libraries.md index 8043bfaa..37f8ee05 100644 --- a/docs/libraries.md +++ b/docs/libraries.md @@ -145,7 +145,7 @@ In Tomo, a shared library is built out of a *directory* that contains multiple an underscore) will be compiled and linked together to produce a single `libwhatever.so` file (or `libwhatever.dylib` on Mac) and `whatever.h` file that can be used by other Tomo projects. You can build a library by running -`tomo -L dirname/` or `tomo -L` in the current directory. +`tomo -L /path/to/dir` or `tomo -L` in the current directory. ### Installing @@ -161,3 +161,33 @@ name (i.e. not an absolute or relative path like `/foo` or `./foo`). When a program uses a shared library, that shared library gets dynamically linked to the executable when compiling, and all of the necessary symbol information is read from the source files during compilation. + +### Versioning + +When you build and install a library, its version is determined from a +`CHANGES.md` file at the top level of the library directory (see: +[Versions](versions.md)). The library's version number is added to the file +path where the library is installed, so if the library `foo` has version +`v1.2`, then it will be installed to +`~/.local/share/tomo_vX.Y/installed/foo_v1.2/`. When using a library, you must +explicitly supply either the exact version in the `use` statement like this: +`use foo_v1.2`, or provide a `modules.ini` file that lists shorthand aliases +for the libraries you're using alongside the files that use them. The syntax +is a simple `alias=full_name` format on each line. Here is an example: + +```tomo +# File: foo.tm +use mylib +... +``` + +And the accompanying `modules.ini`: + +```ini +mylib=mylib_v1.2 +``` + +The `modules.ini` file must be in the same directory as the source files that +use its aliases, so if you want to share a `modules.ini` file across multiple +subdirectories, use a symbolic link. + diff --git a/docs/versions.md b/docs/versions.md index b44902b2..6297dbfd 100644 --- a/docs/versions.md +++ b/docs/versions.md @@ -1,6 +1,6 @@ # Versioning -The Tomo language and Tomo libraries both use a versioning system based on a a +The Tomo language and Tomo libraries both use a versioning system based on a changelog called `CHANGES.md` that includes a human-and-machine-readable markdown list of versions. diff --git a/src/compile.c b/src/compile.c index 98c0bdb0..410ee364 100644 --- a/src/compile.c +++ b/src/compile.c @@ -1976,8 +1976,9 @@ 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); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", use->path, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) + 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"); CORD initialization = CORD_EMPTY; @@ -4483,16 +4484,17 @@ 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); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", use->path, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) + 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"); CORD includes = CORD_EMPTY; for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; Path_t tm_file = Path$from_str(filename); - Path_t lib_build_dir = Path$with_component(Path$parent(tm_file), Text(".build")); - Path_t header = Path$with_component(lib_build_dir, Texts(Path$base_name(tm_file), Text(".h"))); + Path_t lib_build_dir = Path$sibling(tm_file, Text(".build")); + Path_t header = Path$child(lib_build_dir, Texts(Path$base_name(tm_file), Text(".h"))); includes = CORD_all(includes, "#include \"", Path$as_c_string(header), "\"\n"); } globfree(&tm_files); @@ -4500,8 +4502,8 @@ CORD compile_statement_type_header(env_t *env, Path_t header_path, ast_t *ast) } case USE_LOCAL: { Path_t used_path = Path$resolved(Path$from_str(use->path), source_dir); - Path_t used_build_dir = Path$with_component(Path$parent(used_path), Text(".build")); - Path_t used_header_path = Path$with_component(used_build_dir, Texts(Path$base_name(used_path), Text(".h"))); + Path_t used_build_dir = Path$sibling(used_path, Text(".build")); + Path_t used_header_path = Path$child(used_build_dir, Texts(Path$base_name(used_path), Text(".h"))); return CORD_all("#include \"", Path$as_c_string(Path$relative_to(used_header_path, build_dir)), "\"\n"); } case USE_HEADER: diff --git a/src/environment.c b/src/environment.c index a34e0907..4fdfbd4e 100644 --- a/src/environment.c +++ b/src/environment.c @@ -7,8 +7,12 @@ #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" @@ -547,12 +551,12 @@ CORD get_id_suffix(const char *filename) { assert(filename); Path_t path = Path$from_str(filename); - Path_t build_dir = Path$with_component(Path$parent(path), Text(".build")); + Path_t build_dir = Path$sibling(path, Text(".build")); if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { if (!Path$is_directory(build_dir, true)) err(1, "Could not make .build directory"); } - Path_t id_file = Path$with_component(build_dir, Texts(Path$base_name(path), Text$from_str(".id"))); + Path_t id_file = Path$child(build_dir, Texts(Path$base_name(path), Text$from_str(".id"))); OptionalText_t id = Path$read(id_file); if (id.length < 0) err(1, "Could not read ID file: ", id_file); return Text$as_c_string(Texts(Text("$"), id)); @@ -789,4 +793,40 @@ 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 a89935e6..474bab61 100644 --- a/src/environment.h +++ b/src/environment.h @@ -94,5 +94,6 @@ 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/stdlib/paths.c b/src/stdlib/paths.c index 6b16916c..5047d615 100644 --- a/src/stdlib/paths.c +++ b/src/stdlib/paths.c @@ -609,20 +609,25 @@ public Text_t Path$extension(Path_t path, bool full) return Text$from_str(extension); } -public Path_t Path$with_component(Path_t path, Text_t component) +public Path_t Path$child(Path_t path, Text_t name) { - if (Text$has(component, Text("/")) || Text$has(component, Text(";"))) - fail("Path component has invalid characters: ", component); + if (Text$has(name, Text("/")) || Text$has(name, Text(";"))) + fail("Path name has invalid characters: ", name); Path_t result = { .type.$tag=path.type.$tag, .components=path.components, }; LIST_INCREF(result.components); - List$insert(&result.components, &component, I(0), sizeof(Text_t)); + List$insert(&result.components, &name, I(0), sizeof(Text_t)); clean_components(&result.components); return result; } +public Path_t Path$sibling(Path_t path, Text_t name) +{ + return Path$child(Path$parent(path), name); +} + public Path_t Path$with_extension(Path_t path, Text_t extension, bool replace) { if (path.components.length == 0) diff --git a/src/stdlib/paths.h b/src/stdlib/paths.h index 31d676b7..4f94d3e4 100644 --- a/src/stdlib/paths.h +++ b/src/stdlib/paths.h @@ -51,7 +51,8 @@ Path_t Path$write_unique_bytes(Path_t path, List_t bytes); Path_t Path$parent(Path_t path); Text_t Path$base_name(Path_t path); Text_t Path$extension(Path_t path, bool full); -Path_t Path$with_component(Path_t path, Text_t component); +Path_t Path$child(Path_t path, Text_t name); +Path_t Path$sibling(Path_t path, Text_t name); Path_t Path$with_extension(Path_t path, Text_t extension, bool replace); Path_t Path$current_dir(void); Closure_t Path$by_line(Path_t path); @@ -287,7 +287,7 @@ int main(int argc, char *argv[]) for (int64_t i = 0; i < files.length; i++) { Path_t *path = (Path_t*)(files.data + i*files.stride); if (Path$is_directory(*path, true)) - *path = Path$with_component(*path, Texts(Path$base_name(*path), Text(".tm"))); + *path = Path$child(*path, Texts(Path$base_name(*path), Text(".tm"))); *path = Path$resolved(*path, cur_dir); if (!Path$exists(*path)) @@ -363,17 +363,17 @@ void wait_for_child_success(pid_t child) Path_t build_file(Path_t path, const char *extension) { - Path_t build_dir = Path$with_component(Path$parent(path), Text(".build")); + Path_t build_dir = Path$sibling(path, Text(".build")); if (mkdir(Path$as_c_string(build_dir), 0755) != 0) { if (!Path$is_directory(build_dir, true)) err(1, "Could not make .build directory"); } - return Path$with_component(build_dir, Texts(Path$base_name(path), Text$from_str(extension))); + return Path$child(build_dir, Texts(Path$base_name(path), Text$from_str(extension))); } static Text_t get_version_suffix(Path_t lib_dir) { - Path_t changes_file = Path$with_component(lib_dir, Text("CHANGES.md")); + Path_t changes_file = Path$child(lib_dir, Text("CHANGES.md")); OptionalText_t changes = Path$read(changes_file); if (changes.length <= 0) { print_err("I couldn't find a valid CHANGES.md for the library in ", lib_dir, "\n" @@ -399,7 +399,7 @@ void build_library(Path_t lib_dir) print_err("Not a valid directory: ", lib_dir); Text_t lib_dir_name = Path$base_name(lib_dir); - List_t tm_files = Path$glob(Path$with_component(lib_dir, Text("[!._0-9]*.tm"))); + List_t tm_files = Path$glob(Path$child(lib_dir, Text("[!._0-9]*.tm"))); env_t *env = fresh_scope(global_env(source_mapping)); List_t object_files = {}, extra_ldlibs = {}; @@ -407,7 +407,7 @@ void build_library(Path_t lib_dir) compile_files(env, tm_files, &object_files, &extra_ldlibs); Text_t version_suffix = get_version_suffix(lib_dir); - Path_t shared_lib = Path$with_component(lib_dir, Texts(Text("lib"), lib_dir_name, version_suffix, Text(SHARED_SUFFIX))); + Path_t shared_lib = Path$child(lib_dir, Texts(Text("lib"), lib_dir_name, version_suffix, Text(SHARED_SUFFIX))); if (!is_stale_for_any(shared_lib, object_files)) { if (verbose) whisper("Unchanged: ", shared_lib); return; @@ -435,7 +435,7 @@ void install_library(Path_t lib_dir) { Text_t lib_dir_name = Path$base_name(lib_dir); Text_t version_suffix = get_version_suffix(lib_dir); - Path_t dest = Path$with_component(Path$from_str(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed"), Texts(lib_dir_name, version_suffix)); + Path_t dest = Path$child(Path$from_str(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed"), Texts(lib_dir_name, version_suffix)); if (!Path$equal_values(lib_dir, dest)) { if (verbose) whisper("Clearing out any pre-existing version of ", lib_dir_name); xsystem(as_owner, "rm -rf '", dest, "'"); @@ -619,13 +619,14 @@ 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); Text_t lib = Texts(Text("-Wl,-rpath,'"), - Text(TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), Text$from_str(use->path), + Text(TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), Text$from_str(name), Text("' '" TOMO_PREFIX "/share/tomo_"TOMO_VERSION"/installed/"), - Text$from_str(use->path), Text("/lib"), Text$from_str(use->path), Text(SHARED_SUFFIX "'")); + Text$from_str(name), Text("/lib"), Text$from_str(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/", use->path, "/*.tm"))); + List_t children = Path$glob(Path$from_str(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", name, "/*.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 c80f6b2a..60cef3fc 100644 --- a/src/typecheck.c +++ b/src/typecheck.c @@ -182,12 +182,13 @@ 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); glob_t tm_files; - if (glob(String(TOMO_PREFIX"/share/tomo_"TOMO_VERSION"/installed/", use->path, "/[!._0-9]*.tm"), GLOB_TILDE, NULL, &tm_files) != 0) + 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"); env_t *module_env = fresh_scope(env); - Table$str_set(env->imports, use->path, module_env); + Table$str_set(env->imports, name, module_env); for (size_t i = 0; i < tm_files.gl_pathc; i++) { const char *filename = tm_files.gl_pathv[i]; |
