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