hare

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

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 };