hare

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

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