plan.ha (6621B)
1 // License: GPL-3.0 2 // (c) 2021-2022 Alexey Yerin <yyp@disroot.org> 3 // (c) 2021-2022 Drew DeVault <sir@cmpwn.com> 4 // (c) 2021 Ember Sawady <ecs@d2evs.net> 5 use fmt; 6 use fs; 7 use hare::ast; 8 use hare::module; 9 use io; 10 use os::exec; 11 use os; 12 use path; 13 use shlex; 14 use strings; 15 use temp; 16 use unix::tty; 17 18 type status = enum { 19 SCHEDULED, 20 COMPLETE, 21 SKIP, 22 }; 23 24 type task = struct { 25 status: status, 26 depend: []*task, 27 output: str, 28 cmd: []str, 29 module: (str | void), 30 }; 31 32 fn task_free(task: *task) void = { 33 free(task.depend); 34 free(task.output); 35 free(task.cmd); 36 match (task.module) { 37 case let s: str => 38 free(s); 39 case => void; 40 }; 41 free(task); 42 }; 43 44 type modcache = struct { 45 hash: u32, 46 task: *task, 47 ident: ast::ident, 48 version: module::version, 49 }; 50 51 type plan = struct { 52 context: *module::context, 53 target: *target, 54 workdir: str, 55 counter: uint, 56 scheduled: []*task, 57 complete: []*task, 58 script: str, 59 libdir: []str, 60 libs: []str, 61 environ: [](str, str), 62 modmap: [64][]modcache, 63 progress: plan_progress, 64 }; 65 66 type plan_progress = struct { 67 tty: (io::file | void), 68 complete: size, 69 total: size, 70 current_module: str, 71 maxwidth: size, 72 }; 73 74 fn mkplan( 75 ctx: *module::context, 76 libdir: []str, 77 libs: []str, 78 target: *target, 79 ) plan = { 80 const rtdir = match (module::lookup(ctx, ["rt"])) { 81 case let err: module::error => 82 fmt::fatal("Error resolving rt:", module::strerror(err)); 83 case let ver: module::version => 84 yield ver.basedir; 85 }; 86 87 // Look up the most appropriate hare.sc file 88 let ntag = 0z; 89 const buf = path::init(); 90 const iter = os::iter(rtdir)!; 91 defer os::finish(iter); 92 for (true) match (fs::next(iter)) { 93 case let d: fs::dirent => 94 const p = module::parsename(d.name); 95 const name = p.0, ext = p.1, tags = p.2; 96 defer module::tags_free(tags); 97 98 if (len(tags) >= ntag && name == "hare" && ext == ".sc" 99 && module::tagcompat(ctx.tags, tags)) { 100 ntag = len(tags); 101 path::set(&buf, rtdir, d.name)!; 102 }; 103 case void => 104 break; 105 }; 106 107 ar_tool.0 = target.ar_cmd; 108 as_tool.0 = target.as_cmd; 109 ld_tool.0 = if (len(libs) > 0) { 110 yield target.cc_cmd; 111 } else { 112 yield target.ld_cmd; 113 }; 114 115 return plan { 116 context = ctx, 117 target = target, 118 workdir = os::tryenv("HARE_DEBUG_WORKDIR", temp::dir()), 119 script = path::allocate(&buf), 120 environ = alloc([ 121 (strings::dup("HARECACHE"), strings::dup(ctx.cache)), 122 ]), 123 libdir = libdir, 124 libs = libs, 125 progress = plan_progress { 126 tty = if (tty::isatty(os::stderr_file)) os::stderr_file 127 else void, 128 ... 129 }, 130 ... 131 }; 132 }; 133 134 fn plan_finish(plan: *plan) void = { 135 if (os::getenv("HARE_DEBUG_WORKDIR") is void) { 136 os::rmdirall(plan.workdir)!; 137 }; 138 139 for (let i = 0z; i < len(plan.complete); i += 1) { 140 let task = plan.complete[i]; 141 task_free(task); 142 }; 143 free(plan.complete); 144 145 for (let i = 0z; i < len(plan.scheduled); i += 1) { 146 let task = plan.scheduled[i]; 147 task_free(task); 148 }; 149 free(plan.scheduled); 150 151 for (let i = 0z; i < len(plan.environ); i += 1) { 152 free(plan.environ[i].0); 153 free(plan.environ[i].1); 154 }; 155 free(plan.environ); 156 157 free(plan.script); 158 159 for (let i = 0z; i < len(plan.modmap); i += 1) { 160 free(plan.modmap[i]); 161 }; 162 }; 163 164 fn plan_execute(plan: *plan, verbose: bool) (void | !exec::exit_status) = { 165 plan.progress.total = len(plan.scheduled); 166 167 if (verbose) { 168 plan.progress.tty = void; 169 for (let i = 0z; i < len(plan.environ); i += 1) { 170 let item = plan.environ[i]; 171 fmt::errorf("# {}=", item.0)!; 172 shlex::quote(os::stderr, item.1)!; 173 fmt::errorln()!; 174 }; 175 }; 176 177 for (len(plan.scheduled) != 0) { 178 let next: nullable *task = null; 179 let i = 0z; 180 for (i < len(plan.scheduled); i += 1) { 181 let task = plan.scheduled[i]; 182 let eligible = true; 183 for (let j = 0z; j < len(task.depend); j += 1) { 184 if (task.depend[j].status == status::SCHEDULED) { 185 eligible = false; 186 break; 187 }; 188 }; 189 if (eligible) { 190 next = task; 191 break; 192 }; 193 }; 194 195 let task = next as *task; 196 match (task.module) { 197 case let s: str => 198 plan.progress.current_module = s; 199 case => void; 200 }; 201 202 progress_increment(plan); 203 204 match (execute(plan, task, verbose)) { 205 case let err: exec::error => 206 progress_clear(plan); 207 fmt::fatalf("Error: {}: {}", task.cmd[0], 208 exec::strerror(err)); 209 case let err: !exec::exit_status => 210 progress_clear(plan); 211 fmt::errorfln("Error: {}: {}", task.cmd[0], 212 exec::exitstr(err))!; 213 return err; 214 case void => void; 215 }; 216 217 task.status = status::COMPLETE; 218 219 delete(plan.scheduled[i]); 220 append(plan.complete, task); 221 }; 222 223 progress_clear(plan); 224 update_modcache(plan); 225 }; 226 227 fn update_cache(plan: *plan, mod: modcache) void = { 228 let manifest = module::manifest { 229 ident = mod.ident, 230 inputs = mod.version.inputs, 231 versions = [mod.version], 232 }; 233 match (module::manifest_write(plan.context, &manifest)) { 234 case let err: module::error => 235 fmt::fatal("Error updating module cache:", 236 module::strerror(err)); 237 case void => void; 238 }; 239 }; 240 241 fn update_modcache(plan: *plan) void = { 242 for (let i = 0z; i < len(plan.modmap); i += 1) { 243 let mods = plan.modmap[i]; 244 if (len(mods) == 0) { 245 continue; 246 }; 247 for (let j = 0z; j < len(mods); j += 1) { 248 if (mods[j].task.status == status::COMPLETE) { 249 update_cache(plan, mods[j]); 250 }; 251 }; 252 }; 253 }; 254 255 fn execute( 256 plan: *plan, 257 task: *task, 258 verbose: bool, 259 ) (void | exec::error | !exec::exit_status) = { 260 if (verbose) { 261 for (let i = 0z; i < len(task.cmd); i += 1) { 262 fmt::errorf("{} ", task.cmd[i])?; 263 }; 264 fmt::errorln()?; 265 }; 266 267 let cmd = match (exec::cmd(task.cmd[0], task.cmd[1..]...)) { 268 case let cmd: exec::command => 269 yield cmd; 270 case let err: exec::error => 271 fmt::fatalf("Error resolving {}: {}", task.cmd[0], 272 exec::strerror(err)); 273 }; 274 for (let i = 0z; i < len(plan.environ); i += 1) { 275 let e = plan.environ[i]; 276 exec::setenv(&cmd, e.0, e.1)!; 277 }; 278 279 const pipe = if (plan.progress.tty is io::file) { 280 const pipe = exec::pipe(); 281 exec::addfile(&cmd, os::stderr_file, pipe.1); 282 yield pipe; 283 } else (0: io::file, 0: io::file); 284 285 let proc = exec::start(&cmd)?; 286 if (pipe.0 != 0) { 287 io::close(pipe.1)?; 288 }; 289 290 let cleared = false; 291 if (pipe.0 != 0) { 292 for (true) { 293 let buf: [os::BUFSIZ]u8 = [0...]; 294 match (io::read(pipe.0, buf)?) { 295 case let n: size => 296 if (!cleared) { 297 progress_clear(plan); 298 cleared = true; 299 }; 300 io::writeall(os::stderr, buf[..n])?; 301 case io::EOF => 302 break; 303 }; 304 }; 305 }; 306 let st = exec::wait(&proc)?; 307 return exec::check(&st); 308 }; 309 310 fn mkfile(plan: *plan, input: str, ext: str) str = { 311 static let namebuf: [32]u8 = [0...]; 312 const name = fmt::bsprintf(namebuf, "temp.{}.{}.{}", 313 input, plan.counter, ext); 314 plan.counter += 1; 315 return path::join(plan.workdir, name); 316 };