deps.ha (5096B)
1 // SPDX-License-Identifier: MPL-2.0 2 // (c) Hare authors <https://harelang.org> 3 4 use bufio; 5 use fmt; 6 use fs; 7 use hare::ast; 8 use hare::lex; 9 use hare::parse; 10 use hare::unparse; 11 use io; 12 use memio; 13 use os; 14 use path; 15 use sort; 16 use strings; 17 18 // A hare module. 19 export type module = struct { 20 name: str, 21 ns: ast::ident, 22 path: str, 23 srcs: srcset, 24 // [](Index to the module, Identifier of of the module) 25 deps: [](size, ast::ident), 26 }; 27 28 // Get the list of dependencies referred to by a set of source files. 29 // The list will be sorted alphabetically and deduplicated. 30 export fn parse_deps(files: str...) ([]ast::ident | error) = { 31 let deps: []ast::ident = []; 32 for (let file .. files) { 33 let handle = match (os::open(file)) { 34 case let f: io::file => 35 yield f; 36 case let e: fs::error => 37 return attach(strings::dup(file), e); 38 }; 39 defer io::close(handle)!; 40 41 let sc = bufio::newscanner(handle); 42 defer bufio::finish(&sc); 43 let lexer = lex::init(&sc, file); 44 let imports = parse::imports(&lexer)?; 45 defer ast::imports_finish(imports); 46 47 // dedupe + insertion sort 48 for (let import &.. imports) { 49 let id = import.ident; 50 let idx = sort::rbisect(deps, size(ast::ident), &id, &idcmp); 51 if (idx == 0 || idcmp(&deps[idx - 1], &id) != 0) { 52 insert(deps[idx], ast::ident_dup(id)); 53 }; 54 }; 55 }; 56 return deps; 57 }; 58 59 fn idcmp(a: const *opaque, b: const *opaque) int = { 60 const a = a: const *ast::ident, b = b: const *ast::ident; 61 for (let i = 0z; i < len(a) && i < len(b); i += 1) { 62 let cmp = strings::compare(a[i], b[i]); 63 if (cmp != 0) { 64 return cmp; 65 }; 66 }; 67 if (len(a) < len(b)) { 68 return -1; 69 } else if (len(a) == len(b)) { 70 return 0; 71 } else { 72 return 1; 73 }; 74 }; 75 76 // Get the dependencies for a module from the cache, recalculating 77 // them if necessary. cachedir should be calculated with [[get_cache]], 78 // and srcset should be calculated with [[find]]. 79 fn get_deps(cachedir: str, srcs: *srcset) ([]ast::ident | error) = { 80 static let buf = path::buffer{...}; 81 path::set(&buf, cachedir, "deps")?; 82 let rest = memio::fixed(buf.buf[buf.end..]); 83 buf.end += format_tags(&rest, srcs.seentags)?; 84 buf.end += memio::concat(&rest, ".txt")?; 85 86 let outofdate = outdated(path::string(&buf), srcs.ha, srcs.mtime); 87 os::mkdirs(cachedir, 0o755)?; 88 let depsfile = os::create(path::string(&buf), 0o644, fs::flag::RDWR)?; 89 defer io::close(depsfile)!; 90 io::lock(depsfile, true, io::lockop::EXCLUSIVE)?; 91 92 let deps: []ast::ident = []; 93 if (outofdate) { 94 deps = parse_deps(srcs.ha...)?; 95 io::trunc(depsfile, 0)?; 96 let out = bufio::init(depsfile, [], buf.buf); 97 for (let dep .. deps) { 98 unparse::ident(&out, dep)?; 99 fmt::fprintln(&out)?; 100 }; 101 } else { 102 let in = bufio::newscanner_static(depsfile, buf.buf); 103 for (let s => bufio::scan_line(&in)?) { 104 append(deps, parse::identstr(s)?); 105 }; 106 }; 107 return deps; 108 }; 109 110 // Gather a [[module]] and all its dependencies, appending them to an existing 111 // slice, deduplicated, in reverse topological order, returning the index of the 112 // input module within the slice. Dependencies will also be written to the 113 // cache. 114 export fn gather( 115 ctx: *context, 116 out: *[]module, 117 mod: location, 118 ) (size | error) = { 119 let stack: []str = []; 120 defer free(stack); 121 return _gather(ctx, out, &stack, mod)?; 122 }; 123 124 fn _gather( 125 ctx: *context, 126 out: *[]module, 127 stack: *[]str, 128 mod: location, 129 ) (size | error) = { 130 let (modpath, srcs) = match (find(ctx, mod)) { 131 case let r: (str, srcset) => 132 yield r; 133 case let e: error => 134 if (len(stack) == 0) { 135 return e; 136 }; 137 return attach(strings::dup(stack[len(stack) - 1]), e); 138 }; 139 modpath = strings::dup(modpath); 140 defer free(modpath); 141 142 for (let j = 0z; j < len(stack); j += 1) { 143 if (modpath == stack[j]) { 144 append(stack, modpath); 145 return strings::dupall(stack[j..]): dep_cycle; 146 }; 147 }; 148 for (let j = 0z; j < len(out); j += 1) { 149 if (modpath == out[j].path) { 150 return j; 151 }; 152 }; 153 append(stack, modpath); 154 defer delete(stack[len(stack) - 1]); 155 156 let cache = get_cache(ctx.harecache, modpath)?; 157 let depids = get_deps(cache, &srcs)?; 158 defer free(depids); 159 let deps: [](size, ast::ident) = alloc([], len(depids)); 160 for (let i = 0z; i < len(depids); i += 1) { 161 static append(deps, 162 (_gather(ctx, out, stack, depids[i])?, depids[i]) 163 ); 164 }; 165 166 append(out, module { 167 name = match (mod) { 168 case let mod: *path::buffer => 169 yield strings::dup(path::string(mod)); 170 case let mod: ast::ident => 171 yield unparse::identstr(mod); 172 }, 173 ns = match (mod) { 174 case let mod: *path::buffer => 175 yield []; 176 case let mod: ast::ident => 177 yield ast::ident_dup(mod); 178 }, 179 path = strings::dup(modpath), 180 srcs = srcs, 181 deps = deps, 182 }); 183 return len(out) - 1; 184 }; 185 186 // Free the resources associated with a [[module]]. 187 export fn finish(mod: *module) void = { 188 free(mod.name); 189 ast::ident_free(mod.ns); 190 free(mod.path); 191 finish_srcset(&mod.srcs); 192 for (let (_, ident) .. mod.deps) { 193 ast::ident_free(ident); 194 }; 195 free(mod.deps); 196 }; 197 198 // Free all the [[module]]s in a slice of modules, and then the slice itself. 199 export fn free_slice(mods: []module) void = { 200 for (let mod &.. mods) { 201 finish(mod); 202 }; 203 free(mods); 204 };