hare

[hare] The Hare programming language
git clone https://git.torresjrjr.com/hare.git
Log | Files | Refs | README | LICENSE

plan.ha (6399B)


      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 Eyal 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 	libs: []str,
     60 	environ: [](str, str),
     61 	modmap: [64][]modcache,
     62 	progress: plan_progress,
     63 };
     64 
     65 type plan_progress = struct {
     66 	tty: (io::file | void),
     67 	complete: size,
     68 	total: size,
     69 	current_module: str,
     70 	maxwidth: size,
     71 };
     72 
     73 fn mkplan(
     74 	ctx: *module::context,
     75 	libs: []str,
     76 	target: *target,
     77 ) plan = {
     78 	const rtdir = match (module::lookup(ctx, ["rt"])) {
     79 	case let err: module::error =>
     80 		fmt::fatal("Error resolving rt:", module::strerror(err));
     81 	case let ver: module::version =>
     82 		yield ver.basedir;
     83 	};
     84 
     85 	// Look up the most appropriate hare.sc file
     86 	let ntag = 0z;
     87 	const buf = path::init();
     88 	const iter = os::iter(rtdir)!;
     89 	defer os::finish(iter);
     90 	for (true) match (fs::next(iter)) {
     91 	case let d: fs::dirent =>
     92 		const p = module::parsename(d.name);
     93 		const name = p.0, ext = p.1, tags = p.2;
     94 		defer module::tags_free(tags);
     95 
     96 		if (len(tags) >= ntag && name == "hare" && ext == ".sc"
     97 				&& module::tagcompat(ctx.tags, tags)) {
     98 			ntag = len(tags);
     99 			path::set(&buf, rtdir, d.name)!;
    100 		};
    101 	case void =>
    102 		break;
    103 	};
    104 
    105 	return plan {
    106 		context = ctx,
    107 		target = target,
    108 		workdir = os::tryenv("HARE_DEBUG_WORKDIR", temp::dir()),
    109 		script = path::allocate(&buf),
    110 		environ = alloc([
    111 			(strings::dup("HARECACHE"), strings::dup(ctx.cache)),
    112 		]),
    113 		libs = libs,
    114 		progress = plan_progress {
    115 			tty = if (tty::isatty(os::stderr)) os::stderr else void,
    116 			...
    117 		},
    118 		...
    119 	};
    120 };
    121 
    122 fn plan_finish(plan: *plan) void = {
    123 	if (os::getenv("HARE_DEBUG_WORKDIR") is void) {
    124 		os::rmdirall(plan.workdir)!;
    125 	};
    126 
    127 	for (let i = 0z; i < len(plan.complete); i += 1) {
    128 		let task = plan.complete[i];
    129 		task_free(task);
    130 	};
    131 	free(plan.complete);
    132 
    133 	for (let i = 0z; i < len(plan.scheduled); i += 1) {
    134 		let task = plan.scheduled[i];
    135 		task_free(task);
    136 	};
    137 	free(plan.scheduled);
    138 
    139 	for (let i = 0z; i < len(plan.environ); i += 1) {
    140 		free(plan.environ[i].0);
    141 		free(plan.environ[i].1);
    142 	};
    143 	free(plan.environ);
    144 
    145 	free(plan.script);
    146 
    147 	for (let i = 0z; i < len(plan.modmap); i += 1) {
    148 		free(plan.modmap[i]);
    149 	};
    150 };
    151 
    152 fn plan_execute(plan: *plan, verbose: bool) (void | !exec::exit_status) = {
    153 	plan.progress.total = len(plan.scheduled);
    154 
    155 	if (verbose) {
    156 		plan.progress.tty = void;
    157 		for (let i = 0z; i < len(plan.environ); i += 1) {
    158 			let item = plan.environ[i];
    159 			fmt::errorf("# {}=", item.0)!;
    160 			shlex::quote(os::stderr, item.1)!;
    161 			fmt::errorln()!;
    162 		};
    163 	};
    164 
    165 	for (len(plan.scheduled) != 0) {
    166 		let next: nullable *task = null;
    167 		let i = 0z;
    168 		for (i < len(plan.scheduled); i += 1) {
    169 			let task = plan.scheduled[i];
    170 			let eligible = true;
    171 			for (let j = 0z; j < len(task.depend); j += 1) {
    172 				if (task.depend[j].status == status::SCHEDULED) {
    173 					eligible = false;
    174 					break;
    175 				};
    176 			};
    177 			if (eligible) {
    178 				next = task;
    179 				break;
    180 			};
    181 		};
    182 
    183 		let task = next as *task;
    184 		match (task.module) {
    185 		case let s: str =>
    186 			plan.progress.current_module = s;
    187 		case => void;
    188 		};
    189 
    190 		progress_increment(plan);
    191 
    192 		match (execute(plan, task, verbose)) {
    193 		case let err: exec::error =>
    194 			progress_clear(plan);
    195 			fmt::fatalf("Error: {}: {}", task.cmd[0],
    196 				exec::strerror(err));
    197 		case let err: !exec::exit_status =>
    198 			progress_clear(plan);
    199 			fmt::errorfln("Error: {}: {}", task.cmd[0],
    200 				exec::exitstr(err))!;
    201 			return err;
    202 		case void => void;
    203 		};
    204 
    205 		task.status = status::COMPLETE;
    206 
    207 		delete(plan.scheduled[i]);
    208 		append(plan.complete, task);
    209 	};
    210 
    211 	progress_clear(plan);
    212 	update_modcache(plan);
    213 };
    214 
    215 fn update_cache(plan: *plan, mod: modcache) void = {
    216 	let manifest = module::manifest {
    217 		ident = mod.ident,
    218 		inputs = mod.version.inputs,
    219 		versions = [mod.version],
    220 	};
    221 	match (module::manifest_write(plan.context, &manifest)) {
    222 	case let err: module::error =>
    223 		fmt::fatal("Error updating module cache:",
    224 			module::strerror(err));
    225 	case void => void;
    226 	};
    227 };
    228 
    229 fn update_modcache(plan: *plan) void = {
    230 	for (let i = 0z; i < len(plan.modmap); i += 1) {
    231 		let mods = plan.modmap[i];
    232 		if (len(mods) == 0) {
    233 			continue;
    234 		};
    235 		for (let j = 0z; j < len(mods); j += 1) {
    236 			if (mods[j].task.status == status::COMPLETE) {
    237 				update_cache(plan, mods[j]);
    238 			};
    239 		};
    240 	};
    241 };
    242 
    243 fn execute(
    244 	plan: *plan,
    245 	task: *task,
    246 	verbose: bool,
    247 ) (void | exec::error | !exec::exit_status) = {
    248 	if (verbose) {
    249 		for (let i = 0z; i < len(task.cmd); i += 1) {
    250 			fmt::errorf("{} ", task.cmd[i])?;
    251 		};
    252 		fmt::errorln()?;
    253 	};
    254 
    255 	let cmd = match (exec::cmd(task.cmd[0], task.cmd[1..]...)) {
    256 	case let cmd: exec::command =>
    257 		yield cmd;
    258 	case let err: exec::error =>
    259 		fmt::fatalf("Error resolving {}: {}", task.cmd[0],
    260 			exec::strerror(err));
    261 	};
    262 	for (let i = 0z; i < len(plan.environ); i += 1) {
    263 		let e = plan.environ[i];
    264 		exec::setenv(&cmd, e.0, e.1)!;
    265 	};
    266 
    267 	const pipe = if (plan.progress.tty is io::file) {
    268 		const pipe = exec::pipe();
    269 		exec::addfile(&cmd, os::stderr, pipe.1);
    270 		yield pipe;
    271 	} else (0: io::file, 0: io::file);
    272 
    273 	let proc = exec::start(&cmd)?;
    274 	if (pipe.0 != 0) {
    275 		io::close(pipe.1)?;
    276 	};
    277 
    278 	let cleared = false;
    279 	if (pipe.0 != 0) {
    280 		for (true) {
    281 			let buf: [os::BUFSIZ]u8 = [0...];
    282 			match (io::read(pipe.0, buf)?) {
    283 			case let n: size =>
    284 				if (!cleared) {
    285 					progress_clear(plan);
    286 					cleared = true;
    287 				};
    288 				io::writeall(os::stderr, buf[..n])?;
    289 			case io::EOF =>
    290 				break;
    291 			};
    292 		};
    293 	};
    294 	let st = exec::wait(&proc)?;
    295 	return exec::check(&st);
    296 };
    297 
    298 fn mkfile(plan: *plan, input: str, ext: str) str = {
    299 	static let namebuf: [32]u8 = [0...];
    300 	const name = fmt::bsprintf(namebuf, "temp.{}.{}.{}",
    301 		input, plan.counter, ext);
    302 	plan.counter += 1;
    303 	return path::join(plan.workdir, name);
    304 };