plan.ha (6942B)
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 let environ: [](str, str) = alloc([ 116 (strings::dup("HARECACHE"), strings::dup(ctx.cache)), 117 ]); 118 119 if (len(os::tryenv("NO_COLOR", "")) == 0 120 && os::getenv("HAREC_COLOR") is void 121 && tty::isatty(os::stderr_file)) { 122 append(environ, 123 (strings::dup("HAREC_COLOR"), strings::dup("1")) 124 ); 125 }; 126 127 return plan { 128 context = ctx, 129 target = target, 130 workdir = os::tryenv("HARE_DEBUG_WORKDIR", temp::dir()), 131 script = strings::dup(path::string(&buf)), 132 environ = environ, 133 libdir = libdir, 134 libs = libs, 135 progress = plan_progress { 136 tty = if (tty::isatty(os::stderr_file)) os::stderr_file 137 else void, 138 ... 139 }, 140 ... 141 }; 142 }; 143 144 fn plan_finish(plan: *plan) void = { 145 if (os::getenv("HARE_DEBUG_WORKDIR") is void) { 146 os::rmdirall(plan.workdir)!; 147 }; 148 149 for (let i = 0z; i < len(plan.complete); i += 1) { 150 let task = plan.complete[i]; 151 task_free(task); 152 }; 153 free(plan.complete); 154 155 for (let i = 0z; i < len(plan.scheduled); i += 1) { 156 let task = plan.scheduled[i]; 157 task_free(task); 158 }; 159 free(plan.scheduled); 160 161 for (let i = 0z; i < len(plan.environ); i += 1) { 162 free(plan.environ[i].0); 163 free(plan.environ[i].1); 164 }; 165 free(plan.environ); 166 167 free(plan.script); 168 169 for (let i = 0z; i < len(plan.modmap); i += 1) { 170 free(plan.modmap[i]); 171 }; 172 }; 173 174 fn plan_execute(plan: *plan, verbose: bool) (void | !exec::exit_status) = { 175 plan.progress.total = len(plan.scheduled); 176 177 if (verbose) { 178 plan.progress.tty = void; 179 for (let i = 0z; i < len(plan.environ); i += 1) { 180 let item = plan.environ[i]; 181 fmt::errorf("# {}=", item.0)!; 182 shlex::quote(os::stderr, item.1)!; 183 fmt::errorln()!; 184 }; 185 }; 186 187 for (len(plan.scheduled) != 0) { 188 let next: nullable *task = null; 189 let i = 0z; 190 for (i < len(plan.scheduled); i += 1) { 191 let task = plan.scheduled[i]; 192 let eligible = true; 193 for (let j = 0z; j < len(task.depend); j += 1) { 194 if (task.depend[j].status == status::SCHEDULED) { 195 eligible = false; 196 break; 197 }; 198 }; 199 if (eligible) { 200 next = task; 201 break; 202 }; 203 }; 204 205 let task = next as *task; 206 match (task.module) { 207 case let s: str => 208 plan.progress.current_module = s; 209 case => void; 210 }; 211 212 progress_increment(plan); 213 214 match (execute(plan, task, verbose)) { 215 case let err: exec::error => 216 progress_clear(plan); 217 fmt::fatalf("Error: {}: {}", task.cmd[0], 218 exec::strerror(err)); 219 case let err: !exec::exit_status => 220 progress_clear(plan); 221 fmt::errorfln("Error: {}: {}", task.cmd[0], 222 exec::exitstr(err))!; 223 return err; 224 case void => void; 225 }; 226 227 task.status = status::COMPLETE; 228 229 delete(plan.scheduled[i]); 230 append(plan.complete, task); 231 }; 232 233 progress_clear(plan); 234 update_modcache(plan); 235 }; 236 237 fn update_cache(plan: *plan, mod: modcache) void = { 238 let manifest = module::manifest { 239 ident = mod.ident, 240 inputs = mod.version.inputs, 241 versions = [mod.version], 242 }; 243 match (module::manifest_write(plan.context, &manifest)) { 244 case let err: module::error => 245 fmt::fatal("Error updating module cache:", 246 module::strerror(err)); 247 case void => void; 248 }; 249 }; 250 251 fn update_modcache(plan: *plan) void = { 252 for (let i = 0z; i < len(plan.modmap); i += 1) { 253 let mods = plan.modmap[i]; 254 if (len(mods) == 0) { 255 continue; 256 }; 257 for (let j = 0z; j < len(mods); j += 1) { 258 if (mods[j].task.status == status::COMPLETE) { 259 update_cache(plan, mods[j]); 260 }; 261 }; 262 }; 263 }; 264 265 fn execute( 266 plan: *plan, 267 task: *task, 268 verbose: bool, 269 ) (void | exec::error | !exec::exit_status) = { 270 if (verbose) { 271 for (let i = 0z; i < len(task.cmd); i += 1) { 272 fmt::errorf("{} ", task.cmd[i])?; 273 }; 274 fmt::errorln()?; 275 }; 276 277 let cmd = match (exec::cmd(task.cmd[0], task.cmd[1..]...)) { 278 case let cmd: exec::command => 279 yield cmd; 280 case let err: exec::error => 281 progress_clear(plan); 282 fmt::fatalf("Error resolving {}: {}", task.cmd[0], 283 exec::strerror(err)); 284 }; 285 for (let i = 0z; i < len(plan.environ); i += 1) { 286 let e = plan.environ[i]; 287 exec::setenv(&cmd, e.0, e.1)!; 288 }; 289 290 const pipe = if (plan.progress.tty is io::file) { 291 const pipe = exec::pipe(); 292 exec::addfile(&cmd, os::stderr_file, pipe.1); 293 yield pipe; 294 } else (0: io::file, 0: io::file); 295 296 let proc = exec::start(&cmd)?; 297 if (pipe.0 != 0) { 298 io::close(pipe.1)?; 299 }; 300 301 let cleared = false; 302 if (pipe.0 != 0) { 303 for (true) { 304 let buf: [os::BUFSIZ]u8 = [0...]; 305 match (io::read(pipe.0, buf)?) { 306 case let n: size => 307 if (!cleared) { 308 progress_clear(plan); 309 cleared = true; 310 }; 311 io::writeall(os::stderr, buf[..n])?; 312 case io::EOF => 313 break; 314 }; 315 }; 316 }; 317 let st = exec::wait(&proc)?; 318 return exec::check(&st); 319 }; 320 321 fn mkfile(plan: *plan, input: str, ext: str) str = { 322 static let namebuf: [32]u8 = [0...]; 323 const name = fmt::bsprintf(namebuf, "temp.{}.{}.{}", 324 input, plan.counter, ext); 325 plan.counter += 1; 326 const buf = path::init(plan.workdir, name)!; 327 return strings::dup(path::string(&buf)); 328 };