code / tomo

Lines40.1K C23.5K Markdown9.5K YAML4.9K Tomo1.6K
7 others 735
Shell232 make207 Python205 INI48 Text21 SVG16 Lua6
(187 lines)
1 // This file defines some code for getting info about modules and installing them.
3 #include <err.h>
4 #include <stdlib.h>
5 #include <string.h>
6 #include <sys/wait.h>
8 #include "config.h"
9 #include "modules.h"
10 #include "stdlib/memory.h"
11 #include "stdlib/paths.h"
12 #include "stdlib/pointers.h"
13 #include "stdlib/print.h"
14 #include "stdlib/simpleparse.h"
15 #include "stdlib/tables.h"
16 #include "stdlib/text.h"
17 #include "stdlib/types.h"
18 #include "stdlib/util.h"
20 #define xsystem(...) \
21 ({ \
22 int _status = system(String(__VA_ARGS__)); \
23 if (!WIFEXITED(_status) || WEXITSTATUS(_status) != 0) \
24 errx(1, "Failed to run command: %s", String(__VA_ARGS__)); \
25 })
27 const char *get_library_version(Path_t lib_dir) {
28 Path_t changes_file = Path$child(lib_dir, Text("CHANGES.md"));
29 OptionalText_t changes = Path$read(changes_file);
30 if (changes.length <= 0) {
31 return "v0";
33 const char *changes_str = Text$as_c_string(Texts(Text("\n"), changes));
34 const char *version_line = strstr(changes_str, "\n## ");
35 if (version_line == NULL)
36 print_err("CHANGES.md in ", lib_dir, " does not have any valid versions starting with '## '");
37 return String(string_slice(version_line + 4, strcspn(version_line + 4, "\r\n")));
40 Text_t get_library_name(Path_t lib_dir) {
41 Text_t name = Path$base_name(lib_dir);
42 name = Text$without_prefix(name, Text("tomo-"));
43 name = Text$without_suffix(name, Text("-tomo"));
44 Text_t suffix = Texts(Text("@"), Text$from_str(get_library_version(lib_dir)));
45 if (!Text$ends_with(name, suffix, NULL)) name = Texts(name, suffix);
46 return name;
49 bool install_from_modules_ini(Path_t ini_file, bool ask_confirmation) {
50 OptionalClosure_t by_line = Path$by_line(ini_file);
51 if (by_line.fn == NULL) return false;
52 OptionalText_t (*next_line)(void *) = by_line.fn;
53 module_info_t info = {};
54 for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
55 char *line_str = Text$as_c_string(line);
56 const char *next_section = NULL;
57 if (!strparse(line_str, "[", &next_section, "]")) {
58 if (info.name) {
59 if (!try_install_module(info, ask_confirmation)) return false;
61 print("Checking module ", next_section, "...");
62 info = (module_info_t){.name = next_section};
63 continue;
65 if (!strparse(line_str, "version=", &info.version) || !strparse(line_str, "url=", &info.url)
66 || !strparse(line_str, "git=", &info.git) || !strparse(line_str, "path=", &info.path)
67 || !strparse(line_str, "revision=", &info.revision))
68 continue;
70 if (info.name) {
71 if (!try_install_module(info, ask_confirmation)) return false;
73 return true;
76 static void read_modules_ini(Path_t ini_file, module_info_t *info) {
77 OptionalClosure_t by_line = Path$by_line(ini_file);
78 if (by_line.fn == NULL) return;
79 OptionalText_t (*next_line)(void *) = by_line.fn;
80 find_section:;
81 for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
82 char *line_str = Text$as_c_string(line);
83 if (line_str[0] == '[' && strncmp(line_str + 1, info->name, strlen(info->name)) == 0
84 && line_str[1 + strlen(info->name)] == ']')
85 break;
87 for (OptionalText_t line; (line = next_line(by_line.userdata)).tag != TEXT_NONE;) {
88 char *line_str = Text$as_c_string(line);
89 if (line_str[0] == '[') goto find_section;
90 if (!strparse(line_str, "version=", &info->version) || !strparse(line_str, "url=", &info->url)
91 || !strparse(line_str, "git=", &info->git) || !strparse(line_str, "path=", &info->path)
92 || !strparse(line_str, "revision=", &info->revision))
93 continue;
97 module_info_t get_used_module_info(ast_t *use) {
98 static Table_t cache = EMPTY_TABLE;
99 TypeInfo_t *cache_type = Table$info(Pointer$info("@", &Memory$info), Pointer$info("@", &Memory$info));
100 module_info_t **cached = Table$get(cache, &use, cache_type);
101 if (cached) return **cached;
102 const char *name = Match(use, Use)->path;
103 module_info_t *info = new (module_info_t, .name = name);
104 Path_t tomo_default_modules =
105 Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/modules.ini"));
106 read_modules_ini(tomo_default_modules, info);
107 read_modules_ini(Path$sibling(Path$from_str(use->file->filename), Text("modules.ini")), info);
108 read_modules_ini(Path$with_extension(Path$from_str(use->file->filename), Text(":modules.ini"), false), info);
109 Table$set(&cache, &use, &info, cache_type);
110 return *info;
113 bool try_install_module(module_info_t mod, bool ask_confirmation) {
114 Path_t dest = Path$from_text(Texts(Text$from_str(TOMO_PATH), "/lib/tomo@", TOMO_VERSION, "/",
115 Text$from_str(mod.name), "@", Text$from_str(mod.version)));
116 if (Path$exists(dest)) return true;
118 print("No such path: ", dest);
120 if (mod.git) {
121 if (ask_confirmation) {
122 OptionalText_t answer =
123 ask(Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
124 " is not installed.\nDo you want to install it from git URL ", Text$from_str(mod.git),
125 "? [Y/n] "),
126 true, true);
127 if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
128 return false;
130 print("Installing ", mod.name, " from git...");
131 if (mod.revision) xsystem("git clone --depth=1 --revision ", mod.revision, " ", mod.git, " ", dest);
132 else if (mod.version) xsystem("git clone --depth=1 --branch ", mod.version, " ", mod.git, " ", dest);
133 else xsystem("git clone --depth=1 ", mod.git, " ", dest);
134 // Clean up .git/ folder after cloning:
135 xsystem("rm -rf ", dest, "/.git");
136 // Build library:
137 xsystem("tomo -L ", dest);
138 return true;
139 } else if (mod.url) {
140 if (ask_confirmation) {
141 OptionalText_t answer = ask(
142 Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
143 " is not installed.\nDo you want to install it from URL ", Text$from_str(mod.url), "? [Y/n] "),
144 true, true);
145 if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
146 return false;
149 print("Installing ", mod.name, " from URL...");
151 const char *p = strrchr(mod.url, '/');
152 if (!p) return false;
153 const char *filename = p + 1;
154 p = strchr(filename, '.');
155 if (!p) return false;
156 const char *extension = p + 1;
157 Path_t tmpdir = Path$unique_directory(Path("/tmp/tomo-module-XXXXXX"));
158 tmpdir = Path$child(tmpdir, Text$from_str(mod.name));
159 Path$create_directory(tmpdir, 0755, true);
161 xsystem("curl ", mod.url, " -o ", tmpdir);
162 Path$create_directory(dest, 0755, true);
163 if (streq(extension, ".zip")) xsystem("unzip ", tmpdir, "/", filename, " -d ", dest);
164 else if (streq(extension, ".tar.gz") || streq(extension, ".tar"))
165 xsystem("tar xf ", tmpdir, "/", filename, " -C ", dest);
166 else return false;
167 xsystem("tomo -L ", dest);
168 Path$remove(tmpdir, true);
169 return true;
170 } else if (mod.path) {
171 if (ask_confirmation) {
172 OptionalText_t answer = ask(
173 Texts("The module \"", Text$from_str(mod.name), "\" ", Text$from_str(mod.version),
174 " is not installed.\nDo you want to install it from path ", Text$from_str(mod.path), "? [Y/n] "),
175 true, true);
176 if (!(answer.length == 0 || Text$equal_values(answer, Text("Y")) || Text$equal_values(answer, Text("y"))))
177 return false;
180 print("Installing ", mod.name, " from path...");
181 xsystem("ln -s ", mod.path, " ", dest);
182 xsystem("tomo -L ", dest);
183 return true;
186 return false;