commit c280641ded6777d7ced18f4d03f22b10792babd8
parent 475ed776987af2c5487fc4ec7c0dbc46340e55da
Author: Drew DeVault <sir@cmpwn.com>
Date: Thu, 11 Mar 2021 14:00:10 -0500
Expand option parsing and add subcmds.ha
Diffstat:
M | Makefile | | | 1 | + |
M | main.ha | | | 174 | ++++--------------------------------------------------------------------------- |
M | plan.ha | | | 73 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
A | subcmds.ha | | | 106 | +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ |
4 files changed, 188 insertions(+), 166 deletions(-)
diff --git a/Makefile b/Makefile
@@ -19,6 +19,7 @@ include mk/stdlib.mk
hare_srcs=\
plan.ha \
+ subcmds.ha \
main.ha
$(HARECACHE)/hare.ssa: $(hare_srcs) $(hare_stdlib_deps)
diff --git a/main.ha b/main.ha
@@ -10,184 +10,26 @@ use path;
use strio;
use temp;
-type goal = enum {
- // Print dependency graph
- DEP,
- // Build executable
- EXE,
- // Build object
- OBJ,
- // Run program
- RUN,
- // Run tests
- TEST,
-};
-
export fn main() void = {
let verbose = false, goal = goal::EXE;
let output = "";
let help: []getopt::help = [
"compile, run, and test Hare programs",
- ('c', "produce an object instead of an executable"),
- ('u', "print dependency graph"),
- ('v', "enable verbose logging"),
- ('D', "ident:type=value", "define a constant"),
- ('l', "name", "link with a system library"),
- ('o', "path", "set output path"),
- ('t', "arch", "set target architecture"),
- ('T', "tags...", "set build tags"),
- ('X', "tags...", "unset build tags"),
- "subcommand", "args..."
+ "<build | run | test | version>", "args...",
];
let cmd = getopt::parse(os::args, help...);
defer getopt::finish(&cmd);
-
- for (let i = 0z; i < len(cmd.opts); i += 1) {
- let opt = cmd.opts[i];
- switch (opt.0) {
- 'c' => goal = goal::OBJ,
- 'u' => goal = goal::DEP,
- 'v' => verbose = true,
- 'D' => abort(), // TODO
- 'l' => abort(), // TODO
- 'o' => output = opt.1 as getopt::parameter,
- 't' => abort(), // TODO
- 'T' => abort(), // TODO
- 'X' => abort(), // TODO
- };
- };
-
if (len(cmd.args) < 1) {
getopt::print_usage(os::stderr, os::args[0], help...);
os::exit(1);
};
- let newgoal =
- if (cmd.args[0] == "build") goal::EXE
- else if (cmd.args[0] == "run") {
- if (output != "") {
- fmt::fatal("Cannot combine -o and run command");
- };
- goal::RUN;
- }
- else if (cmd.args[0] == "test") goal::TEST
- else if (cmd.args[0] == "version") abort() // TODO
- else {
- getopt::print_usage(os::stderr, os::args[0], help...);
- os::exit(1);
- };
- if (goal != goal::DEP && goal != goal::OBJ) {
- newgoal = goal;
- };
- cmd.args = cmd.args[1..];
-
- let input = if (len(cmd.args) == 0) {
- os::getcwd();
- } else {
- assert(len(cmd.args) == 1); // TODO?
- cmd.args[0];
- };
-
- let plan = plan {
- workdir = temp::dir(),
- ...
- };
- defer {
- os::rmdirall(plan.workdir);
- free(plan.workdir);
-
- for (let i = 0z; i < len(plan.complete); i += 1) {
- let task = plan.complete[i];
- task_free(task);
- };
-
- free(plan.scheduled);
- free(plan.complete);
- };
-
- let ctx = module::context_init([module::tag {
- name = os::machine(),
- mode = module::tag_mode::INCLUSIVE,
- }, module::tag {
- // TEMP:
- name = "linux",
- mode = module::tag_mode::INCLUSIVE,
- }]);
- defer module::context_finish(&ctx);
-
- const rtdir = match (module::lookup(&ctx, ["rt"])) {
- err: module::error => fmt::fatal("Error resolving rt: {}",
- module::errstr(err)),
- ver: module::version => ver.basedir,
- };
- plan.script = path::join(rtdir, "hare.sc");
-
- let ver = match (module::scan(&ctx, input)) {
- ver: module::version => ver,
- err: module::error => fmt::fatal("Error scanning module: {}",
- module::errstr(err)),
- };
-
- let depends: []*task = [];
- for (let i = 0z; i < len(ver.depends); i += 1z) {
- const dep = ver.depends[i];
- match (module::lookup(&ctx, dep)) {
- err: module::error => {
- let ident = ast::ident_unparse_s(dep);
- fmt::fatal("Error resolving {}: {}",
- ident, module::errstr(err));
- },
- ver: module::version => {
- let ns = ast::ident_unparse_s(dep);
- // TODO:
- // - Use the cache
- // - Transitive dependencies
- let obj = sched_hare_object(&plan,
- ver.inputs, ns);
- append(depends, obj);
- },
- };
- };
-
- if (output == "") {
- output = path::basename(ver.basedir);
- };
- sched_hare_exe(&plan, ver.inputs, output, depends...);
-
- for (len(plan.scheduled) != 0) {
- let next: nullable *task = null;
- let i = 0z;
- for (i < len(plan.scheduled); i += 1) {
- let task = plan.scheduled[i];
- let eligible = true;
- for (let j = 0z; j < len(task.depend); j += 1) {
- if (task.depend[j].status != status::COMPLETE) {
- eligible = false;
- break;
- };
- };
- if (eligible) {
- next = task;
- break;
- };
- };
- // TODO: This can be a type assertion
- let task = match (next) {
- null => abort(),
- t: *task => t,
- };
-
- match (execute(&ctx, task, verbose)) {
- err: exec::error => fmt::fatal("Error: {}: {}",
- task.cmd[0], exec::errstr(err)),
- err: exec::exit_status! => fmt::fatal("Error: {}: {}",
- task.cmd[0], exec::exitstr(err)),
- void => void,
- };
-
- task.status = status::COMPLETE;
-
- delete(plan.scheduled[i]);
- append(plan.complete, task);
+ if (cmd.args[0] == "build") build(cmd.args)
+ else if (cmd.args[0] == "run") run(cmd.args)
+ else if (cmd.args[0] == "test") test(cmd.args)
+ else if (cmd.args[0] == "version") version(cmd.args)
+ else {
+ getopt::print_usage(os::stderr, os::args[0], help...);
+ os::exit(1);
};
};
diff --git a/plan.ha b/plan.ha
@@ -4,6 +4,7 @@ use os::exec;
use os;
use path;
use strings;
+use temp;
type status = enum {
SCHEDULED,
@@ -25,6 +26,13 @@ type plan = struct {
script: str,
};
+type goal = enum {
+ OBJ,
+ EXE,
+ RUN,
+ TEST,
+};
+
fn task_free(task: *task) void = {
free(task.depend);
free(task.output);
@@ -32,6 +40,32 @@ fn task_free(task: *task) void = {
free(task);
};
+fn mkplan(ctx: *module::context) plan = {
+ const rtdir = match (module::lookup(ctx, ["rt"])) {
+ err: module::error => fmt::fatal("Error resolving rt: {}",
+ module::errstr(err)),
+ ver: module::version => ver.basedir,
+ };
+ return plan {
+ workdir = temp::dir(),
+ script = path::join(rtdir, "hare.sc"),
+ ...
+ };
+};
+
+fn plan_finish(plan: *plan) void = {
+ os::rmdirall(plan.workdir);
+ free(plan.workdir);
+
+ for (let i = 0z; i < len(plan.complete); i += 1) {
+ let task = plan.complete[i];
+ task_free(task);
+ };
+
+ free(plan.scheduled);
+ free(plan.complete);
+};
+
// Schedules a task which compiles objects into an executable.
fn sched_ld(plan: *plan, output: str, depend: *task...) *task = {
let task = alloc(task {
@@ -211,3 +245,42 @@ fn mkdepends(t: *task...) []*task = {
append(deps, ...t);
return deps;
};
+
+fn plan_execute(ctx: *module::context, plan: *plan, verbose: bool) void = {
+ for (len(plan.scheduled) != 0) {
+ let next: nullable *task = null;
+ let i = 0z;
+ for (i < len(plan.scheduled); i += 1) {
+ let task = plan.scheduled[i];
+ let eligible = true;
+ for (let j = 0z; j < len(task.depend); j += 1) {
+ if (task.depend[j].status != status::COMPLETE) {
+ eligible = false;
+ break;
+ };
+ };
+ if (eligible) {
+ next = task;
+ break;
+ };
+ };
+ // TODO: This can be a type assertion
+ let task = match (next) {
+ null => abort(),
+ t: *task => t,
+ };
+
+ match (execute(ctx, task, verbose)) {
+ err: exec::error => fmt::fatal("Error: {}: {}",
+ task.cmd[0], exec::errstr(err)),
+ err: exec::exit_status! => fmt::fatal("Error: {}: {}",
+ task.cmd[0], exec::exitstr(err)),
+ void => void,
+ };
+
+ task.status = status::COMPLETE;
+
+ delete(plan.scheduled[i]);
+ append(plan.complete, task);
+ };
+};
diff --git a/subcmds.ha b/subcmds.ha
@@ -0,0 +1,106 @@
+use fmt;
+use getopt;
+use hare::ast;
+use hare::module;
+use os;
+use path;
+
+fn build(args: []str) void = {
+ let help: []getopt::help = [
+ "compiles Hare programs",
+ ('c', "build object instead of executable"),
+ ('v', "print executed commands"),
+ ('D', "ident:type=value", "define a constant"),
+ ('l', "name", "link with a system library"),
+ ('o', "path", "set output file name"),
+ ('t', "arch", "set target architecture"),
+ ('T', "tags...", "set build tags"),
+ ('X', "tags...", "unset build tags"),
+ "<path | files...>"
+ ];
+ let cmd = getopt::parse(args, help...);
+ defer getopt::finish(&cmd);
+
+ let verbose = false;
+ let output = "";
+ let goal = goal::EXE;
+ for (let i = 0z; i < len(cmd.opts); i += 1) {
+ let opt = cmd.opts[i];
+ switch (opt.0) {
+ 'c' => goal::OBJ,
+ 'v' => verbose = true,
+ 'D' => abort(), // TODO
+ 'l' => abort(), // TODO
+ 'o' => output = opt.1 as getopt::parameter,
+ 't' => abort(), // TODO
+ 'T' => abort(), // TODO
+ 'X' => abort(), // TODO
+ };
+ };
+
+ assert(goal == goal::EXE); // TODO
+
+ let input = if (len(cmd.args) == 0) os::getcwd() else {
+ assert(len(cmd.args) == 1); // TODO?
+ cmd.args[0];
+ };
+
+ let ctx = module::context_init([module::tag {
+ name = os::machine(),
+ mode = module::tag_mode::INCLUSIVE,
+ }, module::tag {
+ // TEMP:
+ name = "linux",
+ mode = module::tag_mode::INCLUSIVE,
+ }]);
+ defer module::context_finish(&ctx);
+
+ let plan = mkplan(&ctx);
+ defer plan_finish(&plan);
+
+ let ver = match (module::scan(&ctx, input)) {
+ ver: module::version => ver,
+ err: module::error => fmt::fatal("Error scanning module: {}",
+ module::errstr(err)),
+ };
+
+ // TODO:
+ // - Use the hare cache
+ // - Transitive dependencies
+ // - Move this into a separate function, e.g. plan_module
+ let depends: []*task = [];
+ for (let i = 0z; i < len(ver.depends); i += 1z) {
+ const dep = ver.depends[i];
+ match (module::lookup(&ctx, dep)) {
+ err: module::error => {
+ let ident = ast::ident_unparse_s(dep);
+ fmt::fatal("Error resolving {}: {}",
+ ident, module::errstr(err));
+ },
+ ver: module::version => {
+ let ns = ast::ident_unparse_s(dep);
+ let obj = sched_hare_object(
+ &plan, ver.inputs, ns);
+ append(depends, obj);
+ },
+ };
+ };
+
+ if (output == "") {
+ output = path::basename(ver.basedir);
+ };
+ sched_hare_exe(&plan, ver.inputs, output, depends...);
+ plan_execute(&ctx, &plan, verbose);
+};
+
+fn run(args: []str) void = {
+ abort(); // TODO
+};
+
+fn test(args: []str) void = {
+ abort(); // TODO
+};
+
+fn version(args: []str) void = {
+ abort(); // TODO
+};