hare

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

build.ha (5901B)


      1 // SPDX-License-Identifier: GPL-3.0-only
      2 // (c) Hare authors <https://harelang.org>
      3 
      4 use bufio;
      5 use cmd::hare::build;
      6 use errors;
      7 use fmt;
      8 use fs;
      9 use getopt;
     10 use hare::ast;
     11 use hare::lex;
     12 use hare::module;
     13 use hare::parse;
     14 use io;
     15 use memio;
     16 use os;
     17 use os::exec;
     18 use path;
     19 use strconv;
     20 use strings;
     21 use unix::tty;
     22 
     23 fn build(name: str, cmd: *getopt::command) (void | error) = {
     24 	let arch = get_arch(os::arch_name(os::architecture()))!;
     25 	let output = "";
     26 	let ctx = build::context {
     27 		ctx = module::context {
     28 			harepath = harepath(),
     29 			harecache = harecache(),
     30 			tags = default_tags(),
     31 		},
     32 		goal = build::stage::BIN,
     33 		jobs = match (os::cpucount()) {
     34 		case errors::error =>
     35 			yield 1z;
     36 		case let ncpu: size =>
     37 			yield ncpu;
     38 		},
     39 		version = build::get_version(os::tryenv("HAREC", "harec"))?,
     40 		arch = arch,
     41 		platform = build::get_platform(os::sysname())?,
     42 		...
     43 	};
     44 	defer build::ctx_finish(&ctx);
     45 
     46 	if (name == "test") {
     47 		ctx.test = true;
     48 		ctx.submods = len(cmd.args) == 0;
     49 		merge_tags(&ctx.ctx.tags, "+test")?;
     50 	};
     51 
     52 	if (!tty::isatty(os::stderr_file)) {
     53 		ctx.mode = build::output::SILENT;
     54 	};
     55 
     56 	for (let opt .. cmd.opts) {
     57 		switch (opt.0) {
     58 		case 'a' =>
     59 			arch = get_arch(opt.1)?;
     60 			ctx.arch = arch;
     61 		case 'D' =>
     62 			let buf = memio::fixed(strings::toutf8(opt.1));
     63 			let sc = bufio::newscanner(&buf, len(opt.1));
     64 			defer bufio::finish(&sc);
     65 			let lexer = lex::init(&sc, "<-D argument>");
     66 			append(ctx.defines, parse::define(&lexer)?);
     67 		case 'F' =>
     68 			ctx.freestanding = true;
     69 		case 'j' =>
     70 			match (strconv::stoz(opt.1)) {
     71 			case let z: size =>
     72 				ctx.jobs = z;
     73 			case strconv::invalid =>
     74 				fmt::fatal("Number of jobs must be an integer");
     75 			case strconv::overflow =>
     76 				if (strings::hasprefix(opt.1, '-')) {
     77 					fmt::fatal("Number of jobs must be positive");
     78 				} else {
     79 					fmt::fatal("Number of jobs is too large");
     80 				};
     81 			};
     82 			if (ctx.jobs == 0) {
     83 				fmt::fatal("Number of jobs must be non-zero");
     84 			};
     85 		case 'L' =>
     86 			append(ctx.libdirs, opt.1);
     87 		case 'l' =>
     88 			append(ctx.libs, opt.1);
     89 		case 'N' =>
     90 			ast::ident_free(ctx.ns);
     91 			ctx.ns = [];
     92 			match (parse::identstr(opt.1)) {
     93 			case let id: ast::ident =>
     94 				ctx.ns = id;
     95 			case lex::syntax =>
     96 				return opt.1: invalid_namespace;
     97 			case let e: parse::error =>
     98 				return e;
     99 			};
    100 		case 'o' =>
    101 			output = opt.1;
    102 		case 'q' =>
    103 			ctx.mode = build::output::SILENT;
    104 		case 'R' =>
    105 			ctx.release = true;
    106 		case 'T' =>
    107 			merge_tags(&ctx.ctx.tags, opt.1)?;
    108 		case 't' =>
    109 			switch (opt.1) {
    110 			case "td" =>
    111 				// intentionally undocumented
    112 				ctx.goal = build::stage::TD;
    113 			case "ssa" =>
    114 				// intentionally undocumented
    115 				ctx.goal = build::stage::SSA;
    116 			case "s" =>
    117 				ctx.goal = build::stage::S;
    118 			case "o" =>
    119 				ctx.goal = build::stage::O;
    120 			case "bin" =>
    121 				ctx.goal = build::stage::BIN;
    122 			case =>
    123 				return opt.1: unknown_type;
    124 			};
    125 		case 'v' =>
    126 			if (ctx.mode == build::output::VERBOSE) {
    127 				ctx.mode = build::output::VVERBOSE;
    128 			} else if (ctx.mode != build::output::VVERBOSE) {
    129 				ctx.mode = build::output::VERBOSE;
    130 			} else {
    131 				fmt::fatal("Number of verbose levels must be <= 2");
    132 			};
    133 		case =>
    134 			abort();
    135 		};
    136 	};
    137 
    138 	if (name == "build" && len(cmd.args) > 1) {
    139 		getopt::printusage(os::stderr, name, cmd.help)!;
    140 		os::exit(os::status::FAILURE);
    141 	};
    142 
    143 	set_arch_tags(&ctx.ctx.tags, arch);
    144 
    145 	ctx.cmds = ["",
    146 		os::tryenv("HAREC", "harec"),
    147 		os::tryenv("QBE", "qbe"),
    148 		os::tryenv("AS", arch.as_cmd),
    149 		os::tryenv("LD", arch.ld_cmd),
    150 	];
    151 	if (!ctx.freestanding && (len(ctx.libs) > 0 || ctx.platform.need_libc)) {
    152 		ctx.libc = true;
    153 		merge_tags(&ctx.ctx.tags, "+libc")?;
    154 		ctx.cmds[build::stage::BIN] = os::tryenv("CC", arch.cc_cmd);
    155 	};
    156 
    157 	const input = if (len(cmd.args) == 0) os::getcwd() else cmd.args[0];
    158 
    159 	ctx.mods = build::gather(&ctx, os::realpath(input)?)?;
    160 	append(ctx.hashes, [[void...]...], len(ctx.mods));
    161 
    162 	let built = build::execute(&ctx)?;
    163 	defer free(built);
    164 
    165 	if (output == "") {
    166 		if (name != "build") {
    167 			return run(input, built, cmd.args);
    168 		};
    169 		output = get_output(ctx.goal, input)?;
    170 	};
    171 
    172 	let dest = os::stdout_file;
    173 	if (output != "-") {
    174 		let mode: fs::mode = 0o644;
    175 		if (ctx.goal == build::stage::BIN) {
    176 			mode |= 0o111;
    177 		};
    178 		// in the case that we are outputting to a binary that is
    179 		// currently beeing executed, we need to remove it first or
    180 		// otherwise os::create() will fail
    181 		os::remove(output): void;
    182 		dest = match (os::create(output, mode)) {
    183 		case let f: io::file =>
    184 			yield f;
    185 		case let e: fs::error =>
    186 			return (output, e): output_failed;
    187 		};
    188 	};
    189 	defer io::close(dest)!;
    190 
    191 	let src = os::open(built)?;
    192 	defer io::close(src)!;
    193 	io::copy(dest, src)?;
    194 };
    195 
    196 fn run(name: str, path: str, args: []str) error = {
    197 	const args: []str = if (len(args) != 0) args[1..] else [];
    198 	let cmd = match(exec::cmd(path, args...)) {
    199 	case exec::nocmd =>
    200 		fmt::fatalf("Error: Command not found: {}", path);
    201 	case let e: exec::error =>
    202 		return e;
    203 	case let c: exec::command =>
    204 		yield c;
    205 	};
    206 	exec::setname(&cmd, name);
    207 	exec::exec(&cmd);
    208 };
    209 
    210 fn get_output(goal: build::stage, input: str) (str | error) = {
    211 	static let buf = path::buffer { ... };
    212 	let stat = os::stat(input)?;
    213 	path::set(&buf, os::realpath(input)?)?;
    214 	if (!fs::isdir(stat.mode)) {
    215 		path::pop_ext(&buf);
    216 	};
    217 	// don't add the .bin extension if the goal is to create a binary
    218 	if (goal != build::stage::BIN) {
    219 		path::push_ext(&buf, build::stage_ext[goal])?;
    220 	};
    221 
    222 	const output = match (path::peek(&buf)) {
    223 	case let s: str =>
    224 		yield s;
    225 	case void =>
    226 		return unknown_output;
    227 	};
    228 	stat = match (os::stat(output)) {
    229 	case let s: fs::filestat =>
    230 		yield s;
    231 	case errors::noentry =>
    232 		return output;
    233 	case fs::error =>
    234 		// XXX: double cast here (and below) shouldn't be necessary
    235 		return output: output_exists: error;
    236 	};
    237 	if (!fs::isfile(stat.mode)
    238 			|| fs::mode_perm(stat.mode) & fs::mode::USER_X == 0) {
    239 		return output: output_exists: error;
    240 	};
    241 	return output;
    242 };