diff --git a/README.md b/README.md index c2128b3..a1387ad 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ options: ```python # examples/hello_world/build.hancho -compile_cpp = hancho.command( +compile_cpp = hancho.Config( desc = "Compiling C++ {in_src} -> {out_obj}", command = "g++ -c {in_src} -o {out_obj}", in_src = None, @@ -64,7 +64,7 @@ compile_cpp = hancho.command( c_deps = "{swap_ext(in_src, '.d')}", ) -link_cpp_bin = hancho.command( +link_cpp_bin = hancho.Config( desc = "Linking C++ bin {out_bin}", command = "g++ {in_objs} -o {out_bin}", in_objs = None, diff --git a/base_rules.hancho b/base_rules.hancho index 04eb80c..9137489 100644 --- a/base_rules.hancho +++ b/base_rules.hancho @@ -1,7 +1,7 @@ #################################################################################################### # Utils -touch_outputs = hancho.Command( +touch_outputs = hancho.Config( command="touch {in_files}", in_files=None, ) @@ -29,7 +29,7 @@ riscv64_toolchain = hancho.Config( # ---------------------------------------- -check_cpp = hancho.Command( +check_cpp = hancho.Config( desc = "Checking C++ syntax of {rel(in_src)}", command = "{toolchain.compiler} {flags} {joined_warnings} {joined_defines} {joined_includes} -c {rel(in_src)} && touch {rel(out_ok)}", in_src = None, @@ -44,7 +44,7 @@ check_cpp = hancho.Command( # ---------------------------------------- -compile_cpp = hancho.Command( +compile_cpp = hancho.Config( desc = "Compiling C++ {rel(in_src)} -> {rel(out_obj)} ({build_tag})", command = "{toolchain.compiler} {flags} {joined_warnings} {joined_defines} {joined_includes} -c {rel(in_src)} -o {rel(out_obj)}", @@ -76,7 +76,7 @@ if os.name == 'nt': # ---------------------------------------- -link_cpp_lib = hancho.Command( +link_cpp_lib = hancho.Config( desc="Bundling C++ lib {rel(out_lib)}", in_objs=None, out_lib=None, @@ -85,7 +85,7 @@ link_cpp_lib = hancho.Command( # ---------------------------------------- -link_cpp_bin = hancho.Command( +link_cpp_bin = hancho.Config( desc="Linking C++ bin {rel(out_bin)}", out_bin=None, toolchain=default_toolchain, @@ -137,7 +137,7 @@ def cpp_bin(hancho, *args, in_srcs=[], in_objs=[], in_libs=[], out_bin = [], **k # Makefiles def make(hancho, /, *args, in_makefile, **kwargs): - cmd = hancho.Command( + cmd = hancho.Config( desc = "Run makefile {rel(in_makefile)}", command = "make -C {make_dir} -f {make_file} {flags}", # > /dev/null make_dir = "{path.dirname(in_makefile)}", @@ -149,7 +149,7 @@ def make(hancho, /, *args, in_makefile, **kwargs): #################################################################################################### # Tests -run_test = hancho.Command( +run_test = hancho.Config( desc = "Running test {rel(in_test)}", command = "{in_test} {args} && touch {out_pass}", task_dir = "{test_dir}", diff --git a/docs/tutorial.md b/docs/tutorial.md index 2d89d6c..325a82f 100644 --- a/docs/tutorial.md +++ b/docs/tutorial.md @@ -128,7 +128,7 @@ Task @ 0x727f0371d6a0 { _out_files = [], _state = 0, _reason = None, - _promise = None, + _asyncio_task = None, _loaded_files = [ "/home/aappleby/repos/hancho/tutorial/tut00.hancho", ], diff --git a/examples/hello_world/build.hancho b/examples/hello_world/build.hancho index 0c3acd5..053fa1c 100644 --- a/examples/hello_world/build.hancho +++ b/examples/hello_world/build.hancho @@ -1,6 +1,6 @@ # examples/hello_world/build.hancho -compile_cpp = hancho.Command( +compile_cpp = hancho.Config( desc = "Compiling C++ {in_src} -> {out_obj}", command = "g++ -c {in_src} -o {out_obj}", in_src = None, @@ -11,7 +11,7 @@ compile_cpp = hancho.Command( main_o = hancho(compile_cpp, in_src = "main.cpp") util_o = hancho(compile_cpp, in_src = "util.cpp") -link_cpp_bin = hancho.Command( +link_cpp_bin = hancho.Config( desc = "Linking C++ bin {out_bin}", command = "g++ {in_objs} -o {out_bin}", in_objs = None, diff --git a/examples/windows/build.hancho b/examples/windows/build.hancho index 67180ea..8073ef6 100644 --- a/examples/windows/build.hancho +++ b/examples/windows/build.hancho @@ -2,7 +2,7 @@ hancho.c_depformat = "msvc" -compile = hancho.Command( +compile = hancho.Config( command = "cl.exe /nologo /c {in_src} /sourceDependencies {c_deps} /Fo:{out_obj} > NUL", desc = "Compile {in_src} -> {out_obj}", in_src = None @@ -10,7 +10,7 @@ compile = hancho.Command( c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.Command( +link = hancho.Config( command = "link.exe /nologo {libs} {in_objs} /out:{out_bin} > NUL", desc = "Link {in_objs} -> {out_bin}", in_objs = None, diff --git a/fpga_rules.hancho b/fpga_rules.hancho index 2070916..fb500e9 100644 --- a/fpga_rules.hancho +++ b/fpga_rules.hancho @@ -6,7 +6,7 @@ def chparams(c): result.append(f"chparam -set {key} {val} {{top}};") return result -yosys = hancho.Command( +yosys = hancho.Config( desc = "Run yosys on {rel(in_sv)}", command = "yosys -q -p 'read_verilog -defer {joined_includes} -sv {rel(in_sv)}; dump; {chparams(params)} synth_ice40 -json {rel(out_json)};'", in_sv = None, @@ -16,7 +16,7 @@ yosys = hancho.Command( joined_includes = "{join_prefix('-I', get('includes', []))}", ) -nextpnr = hancho.Command( +nextpnr = hancho.Config( desc = "Run nextpnr-ice40 on {rel(in_json)}", command = "nextpnr-ice40 {flags} -q --json {rel(in_json)} --pcf {pcf} --{chip} --package {package} --asc {rel(out_asc)}", in_json = None, @@ -27,19 +27,19 @@ nextpnr = hancho.Command( package = None, ) -icepack = hancho.Command( +icepack = hancho.Config( desc = "Run icepack on {rel(in_asc)}", command = "icepack {rel(in_asc)} {rel(out_bin)}", in_asc = None, out_bin = "{swap_ext(in_asc, '.bin')}", ) -iceprog = hancho.Command( +iceprog = hancho.Config( desc = "Run iceprog on {rel(in_bin)}", command = "iceprog -S {rel(in_bin)}", ) -synth = hancho.Command( +synth = hancho.Config( desc = "Synth {rel(in_sv)}", command = [ "yosys -p 'read_verilog -defer {joined_includes} -sv {rel(in_sv)}; dump; {chparams(params)} synth_ice40 -json {rel(out_json)};'", diff --git a/hancho.py b/hancho.py index cd80ef8..8949c4b 100755 --- a/hancho.py +++ b/hancho.py @@ -34,7 +34,7 @@ def log_line(message): app.log += message - if not app.quiet: + if not app.flags.quiet: sys.stdout.write(message) sys.stdout.flush() @@ -81,9 +81,9 @@ def line_block(lines): sys.stdout.flush() def log_exception(): - log(color(255, 128, 128)) + log(color(255, 128, 128), end="") log(traceback.format_exc()) - log(color()) + log(color(), end="") #--------------------------------------------------------------------------------------------------- # Path manipulation @@ -219,9 +219,6 @@ async def await_variant(variant): """Recursively replaces every awaitable in the variant with its awaited value.""" match variant: - case Exception() | asyncio.CancelledError(): - # This has to be here, it's what cancels downstream tasks. - raise variant case Promise(): variant = await variant.get() variant = await await_variant(variant) @@ -252,7 +249,9 @@ def indent(self): def dump(self, variant): result = f"{type(variant).__name__} @ {hex(id(variant))} " match variant: - case Task() | HanchoAPI(): + case Task(): + result += self.dump_dict(variant.__dict__) + case HanchoAPI(): result += self.dump_dict(variant.config) case Config(): result += self.dump_dict(variant) @@ -367,11 +366,6 @@ def __delitem__(self, key): def __getitem__(self, key): return self.__dict__[key] -#################################################################################################### - -class Command(Config): - pass - #################################################################################################### # Hancho's text expansion system. Works similarly to Python's F-strings, but with quite a bit more # power. @@ -423,7 +417,6 @@ def expand_inc(): """ Increments the current expansion recursion depth. """ app.expand_depth += 1 if app.expand_depth > MAX_EXPAND_DEPTH: - log("Text expansion failed to terminate") raise RecursionError("Text expansion failed to terminate") def expand_dec(): @@ -451,7 +444,7 @@ class Expander: def __init__(self, config): self.config = config # We save a copy of 'trace', otherwise we end up printing traces of reading trace.... :P - self.trace = config.get('trace', app.default_trace) + self.trace = config.get('trace', app.flags.trace) def __getitem__(self, key): return self.get(key) @@ -588,25 +581,23 @@ async def get(self): #################################################################################################### -class TaskState(IntEnum): - DECLARED = 0 - QUEUED = 1 - STARTED = 2 - AWAITING_INPUTS = 3 - TASK_INIT = 4 - AWAITING_JOBS = 5 - RUNNING_COMMANDS = 6 - FINISHED = 7 - CANCELLED = 8 - FAILED = 9 - SKIPPED = 10 - BROKEN = 11 +class TaskState: + DECLARED = "DECLARED" + QUEUED = "QUEUED" + STARTED = "STARTED" + AWAITING_INPUTS = "AWAITING_INPUTS" + TASK_INIT = "TASK_INIT" + AWAITING_JOBS = "AWAITING_JOBS" + RUNNING_COMMANDS = "RUNNING_COMMANDS" + FINISHED = "FINISHED" + CANCELLED = "CANCELLED" + FAILED = "FAILED" + SKIPPED = "SKIPPED" + BROKEN = "BROKEN" #################################################################################################### class Task: - """Calling a Command creates a Task.""" - def __init__(self, *args, **kwargs): #super().__init__(*args, **kwargs) @@ -633,7 +624,7 @@ def __init__(self, *args, **kwargs): self._out_files = [] self._state = TaskState.DECLARED self._reason = None - self._promise = None + self._asyncio_task = None self._loaded_files = list(app.loaded_files) self._stdout = "" self._stderr = "" @@ -650,6 +641,9 @@ def __copy__(self): def __deepcopy__(self, memo): return self + def __repr__(self): + return Dumper(2).dump(self) + #---------------------------------------- def queue(self): @@ -665,13 +659,16 @@ def apply(key, val): def start(self): self.queue() if self._state is TaskState.QUEUED: - self._promise = asyncio.create_task(self.task_main()) + self._asyncio_task = asyncio.create_task(self.task_main()) self._state = TaskState.STARTED app.tasks_started += 1 async def await_done(self): self.start() - await self._promise + try: + await self._asyncio_task + except: + raise asyncio.CancelledError() def promise(self, *args): return Promise(self, *args) @@ -680,7 +677,7 @@ def promise(self, *args): def print_status(self): """ Print the "[1/N] Compiling foo.cpp -> foo.o" status line and debug information """ - verbose = self.config.get('verbose', app.default_verbose) + verbose = self.config.get('verbose', app.flags.verbose) log( f"{color(128,255,196)}[{self._task_index}/{app.tasks_started}]{color()} {self.config.desc}", sameline=not verbose, @@ -691,10 +688,9 @@ def print_status(self): async def task_main(self): """Entry point for async task stuff, handles exceptions generated during task execution.""" - verbose = self.config.get('verbose', app.default_verbose) - debug = self.config.get('debug', app.default_debug) - force = self.config.get('force', app.default_force) - + verbose = self.config.get('verbose', app.flags.verbose) + debug = self.config.get('debug', app.flags.debug) + force = self.config.get('force', app.flags.force) # Await everything awaitable in this task except the task's own promise. # If any of this tasks's dependencies were cancelled, we propagate the cancellation to @@ -703,37 +699,40 @@ async def task_main(self): assert self._state is TaskState.STARTED self._state = TaskState.AWAITING_INPUTS for key, val in self.config.items(): - if key != "_promise": + if key != "_asyncio_task": self.config[key] = await await_variant(val) - except Exception as err: # pylint: disable=broad-exception-caught - log_exception() + except Exception as ex: # pylint: disable=broad-exception-caught + self._state = TaskState.BROKEN + app.tasks_broken += 1 + raise ex + except asyncio.CancelledError as ex: self._state = TaskState.CANCELLED app.tasks_cancelled += 1 - return err + raise ex + # Everything awaited, task_init runs synchronously. try: self._state = TaskState.TASK_INIT self.task_init() - except Exception as err: # pylint: disable=broad-exception-caught - log_exception() + except Exception as ex: # pylint: disable=broad-exception-caught self._state = TaskState.BROKEN app.tasks_broken += 1 - return err + raise ex # Early-out if this is a no-op task if self.config.command is None: app.tasks_finished += 1 self._state = TaskState.FINISHED - return self._out_files + return # Check if we need a rebuild self._reason = self.needs_rerun(force) if not self._reason: app.tasks_skipped += 1 self._state = TaskState.SKIPPED - return self._out_files + return if verbose or debug: log(f"{color(128,128,128)}Reason: {self._reason}{color()}") @@ -754,12 +753,11 @@ async def task_main(self): await self.run_command(command) if self._returncode != 0: break - except Exception as err: # pylint: disable=broad-exception-caught + except Exception as ex: # pylint: disable=broad-exception-caught # If any command failed, we print the error and propagate it to downstream tasks. - log_exception() self._state = TaskState.FAILED app.tasks_failed += 1 - return err + raise ex finally: await app.job_pool.release_jobs(job_count, self) @@ -772,7 +770,7 @@ async def task_main(self): def task_init(self): """All the setup steps needed before we run a task.""" - debug = self.config.get('debug', app.default_debug) + debug = self.config.get('debug', app.flags.debug) if debug: log(f"\nTask before expand: {self.config}") @@ -854,7 +852,7 @@ def join_dir(key, val): app.all_out_files.add(file) # Make sure our output directories exist - if not app.dry_run: + if not app.flags.dry_run: for file in self._out_files: os.makedirs(path.dirname(file), exist_ok=True) @@ -863,7 +861,7 @@ def join_dir(key, val): def needs_rerun(self, force=False): """Checks if a task needs to be re-run, and returns a non-empty reason if so.""" - debug = self.config.get('debug', app.default_debug) + debug = self.config.get('debug', app.flags.debug) if force: return f"Files {self._out_files} forced to rebuild" @@ -923,19 +921,19 @@ def needs_rerun(self, force=False): async def run_command(self, command): """Runs a single command, either by calling it or running it in a subprocess.""" - verbose = self.config.get('verbose', app.default_verbose) - debug = self.config.get('debug', app.default_debug) + verbose = self.config.get('verbose', app.flags.verbose) + debug = self.config.get('debug', app.flags.debug) if verbose or debug: root_dir = self.config.get("root_dir", "/") log(color(128,128,255), end="") - if app.dry_run: log("(DRY RUN) ", end="") + if app.flags.dry_run: log("(DRY RUN) ", end="") log(f"{rel_path(self.config.task_dir, root_dir)}$ ", end="") log(color(), end="") log(command) # Dry runs get early-out'ed before we do anything. - if app.dry_run: + if app.flags.dry_run: return # Custom commands just get called and then early-out'ed. @@ -979,23 +977,34 @@ async def run_command(self, command): result.write("\n") result.close() - if debug or verbose or not command_pass: - if not command_pass: - log(f"{color(128,255,196)}[{self._task_index}/{app.tasks_started}]{color(255,128,128)} Task failed {color()}- '{self.config.desc}'") - log(f"Task dir: {self.config.task_dir}") - log(f"Command : {self.config.command}") - log(f"Return : {self._returncode}") - else: - log(f"{color(128,255,196)}[{self._task_index}/{app.tasks_started}]{color()} Task passed - '{self.config.desc}'") + if not command_pass: + message = f"Command exited with return code {self._returncode}\n" + #message += color(128,255,196) + #message += f"[{self._task_index}/{app.tasks_started}]\n" + #message += color(255,128,128) + #message += f"Task failed - '{self.config.desc}'\n" + #message += f"Task dir: {self.config.task_dir}\n" + #message += f"Command : {self.config.command}\n" + #message += f"Return : {self._returncode}\n" if self._stdout: - log("Stdout:") - log(self._stdout, end="") + message += "Stdout:\n" + message += self._stdout if self._stderr: - log("Stderr:") - log(self._stderr, end="") + message += "Stderr:\n" + message += self._stderr + #message += "Task:\n" + #message += str(self) + raise ValueError(message) + else: + if debug or verbose: + log(f"{color(128,255,196)}[{self._task_index}/{app.tasks_started}]{color()} Task passed - '{self.config.desc}'") + if self._stdout: + log("Stdout:") + log(self._stdout, end="") + if self._stderr: + log("Stderr:") + log(self._stderr, end="") - if not command_pass: - raise ValueError(self._returncode) #################################################################################################### @@ -1004,7 +1013,6 @@ class HanchoAPI(Utils): def __init__(self): self.config = Config() self.Config = Config - self.Command = Command self.Task = Task def __repr__(self): @@ -1073,8 +1081,9 @@ def root(self, mod_path): return new_context._load_module() def _load_module(self): - log(("┃ " * (len(app.dirstack) - 1)), end="") - log(color(128,255,128) + f"Loading {self.config.mod_path}" + color()) + if len(app.dirstack) == 1 or app.flags.verbose: + log(("┃ " * (len(app.dirstack) - 1)), end="") + log(color(128,255,128) + f"Loading {self.config.mod_path}" + color()) app.loaded_files.append(self.config.mod_path) @@ -1119,8 +1128,8 @@ def reset(self, job_count): async def acquire_jobs(self, count, token): """Waits until 'count' jobs are available and then removes them from the job pool.""" - if count > app.jobs: - raise ValueError(f"Nedd {count} jobs, but pool is {app.jobs}.") + if count > app.flags.jobs: + raise ValueError(f"Need {count} jobs, but pool is {app.flags.jobs}.") await self.jobs_lock.acquire() await self.jobs_lock.wait_for(lambda: self.jobs_available >= count) @@ -1161,12 +1170,9 @@ async def release_jobs(self, count, token): class App: def __init__(self): - self.shuffle = False - self.use_color = True - self.quiet = False - self.dry_run = False - self.jobs = os.cpu_count() - self.target = None + self.flags = None + self.extra_flags = None + self.target_regex = None self.loaded_files = [] self.dirstack = [os.getcwd()] @@ -1202,22 +1208,56 @@ def __init__(self): self.default_build_tag = "" self.default_log_path = None - self.default_verbose = False - self.default_debug = False - self.default_force = False - self.default_trace = False - - def reset(self): self.__init__() # pylint: disable=unnecessary-dunder-call + ######################################## + + def parse_flags(self, argv): + assert isinstance(argv, list) + + # pylint: disable=line-too-long + # fmt: off + parser = argparse.ArgumentParser() + parser.add_argument("target", default=None, nargs="?", type=str, help="A regex that selects the targets to build. Defaults to all targets.") + + parser.add_argument("-f", "--root_file", default="build.hancho", type=str, help="The name of the .hancho file(s) to build") + parser.add_argument("-C", "--root_dir", default=os.getcwd(), type=str, help="Change directory before starting the build") + + parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Print verbose build info") + parser.add_argument("-d", "--debug", default=False, action="store_true", help="Print debugging information") + parser.add_argument("--force", default=False, action="store_true", help="Force rebuild of everything") + parser.add_argument("--trace", default=False, action="store_true", help="Trace all text expansion") + + parser.add_argument("-j", "--jobs", default=os.cpu_count(), type=int, help="Run N jobs in parallel (default = cpu_count)") + parser.add_argument("-q", "--quiet", default=False, action="store_true", help="Mute all output") + parser.add_argument("-n", "--dry_run", default=False, action="store_true", help="Do not run commands") + parser.add_argument("-s", "--shuffle", default=False, action="store_true", help="Shuffle task order to shake out dependency issues") + parser.add_argument("--use_color", default=False, action="store_true", help="Use color in the console output") + # fmt: on + + (flags, unrecognized) = parser.parse_known_args(argv) + + # Unrecognized command line parameters also become global config fields if they are + # flag-like + extra_flags = {} + for span in unrecognized: + if match := re.match(r"-+([^=\s]+)(?:=(\S+))?", span): + key = match.group(1) + val = match.group(2) + val = maybe_as_number(val) if val is not None else True + extra_flags[key] = val + + self.flags = flags + self.extra_flags = extra_flags + ######################################## # Needs to be its own function, used by run_tests.py - def create_root_context(self, flags, extra_flags): + def create_root_context(self): - root_file = flags['root_file'] - root_dir = path.realpath(flags['root_dir']) # Root path must be absolute. + root_file = self.flags.root_file + root_dir = path.realpath(self.flags.root_dir) # Root path must be absolute. root_path = path.join(root_dir, root_file) root_config = Config( @@ -1236,7 +1276,7 @@ def create_root_context(self, flags, extra_flags): ) # All the unrecognized flags get stuck on the root context. - for key, val in extra_flags.items(): + for key, val in self.extra_flags.items(): setattr(root_config, key, val) root_context = HanchoAPI() @@ -1245,20 +1285,9 @@ def create_root_context(self, flags, extra_flags): ######################################## - def main(self, flags, extra_flags): + def main(self): - # These flags are app-wide and not context-wide. - app_flags = ['shuffle', 'use_color', 'quiet', 'dry_run', 'jobs', 'target'] - for flag in app_flags: - setattr(app, flag, flags[flag]) - del flags[flag] - - app.default_verbose = flags['verbose'] - app.default_debug = flags['debug'] - app.default_force = flags['force'] - app.default_trace = flags['trace'] - - app.root_context = self.create_root_context(flags, extra_flags) + app.root_context = self.create_root_context() if app.root_context.config.get("debug", None): log(f"root_context = {Dumper().dump(app.root_context)}") @@ -1277,25 +1306,26 @@ def main(self, flags, extra_flags): app.root_context._load_module() time_b = time.perf_counter() - #if app.default_debug or app.default_verbose: + #if app.flags.debug or app.flags.verbose: log(f"Loading .hancho files took {time_b-time_a:.3f} seconds") #======================================== time_a = time.perf_counter() - if app.target: - target_regex = re.compile(app.target) + + if app.flags.target: + app.target_regex = re.compile(app.flags.target) for task in app.all_tasks: queue_task = False task_name = None # FIXME this doesn't work because we haven't expanded output filenames yet #for out_file in flatten(task._out_files): - # if target_regex.search(out_file): + # if app.target_regex.search(out_file): # queue_task = True # task_name = out_file # break if name := task.get('name', None): - if target_regex.search(task.get('name', None)): + if app.target_regex.search(task.get('name', None)): queue_task = True task_name = name if queue_task: @@ -1309,7 +1339,7 @@ def main(self, flags, extra_flags): if root_dir == repo_dir: task.queue() time_b = time.perf_counter() - #if app.default_debug or app.default_verbose: + #if app.flags.debug or app.flags.verbose: log(f"Queueing {len(app.queued_tasks)} tasks took {time_b-time_a:.3f} seconds") result = self.build() @@ -1333,10 +1363,7 @@ def build(self): result = -1 loop = asyncio.new_event_loop() asyncio.set_event_loop(loop) - try: - result = asyncio.run(self.async_run_tasks()) - except Exception: - log_exception() + result = asyncio.run(self.async_run_tasks()) loop.close() return result @@ -1350,7 +1377,7 @@ def build_all(self): async def async_run_tasks(self): # Run all tasks in the queue until we run out. - self.job_pool.reset(self.jobs) + self.job_pool.reset(self.flags.jobs) # Tasks can create other tasks, and we don't want to block waiting on a whole batch of # tasks to complete before queueing up more. Instead, we just keep queuing up any pending @@ -1370,7 +1397,14 @@ async def async_run_tasks(self): self.started_tasks.append(task) task = self.started_tasks.pop(0) - await task._promise + try: + await task._asyncio_task + except BaseException as ex: + log(color(255,128,0), end="") + log(f"Task failed: {task.config.desc}") + log(color(), end="") + log(str(task.config)) + log_exception() self.finished_tasks.append(task) time_b = time.perf_counter() @@ -1379,7 +1413,7 @@ async def async_run_tasks(self): log(f"Running {app.tasks_started} tasks took {time_b-time_a:.3f} seconds") # Done, print status info if needed - if app.default_debug or app.default_verbose: + if app.flags.debug or app.flags.verbose: log(f"tasks started: {app.tasks_started}") log(f"tasks finished: {app.tasks_finished}") log(f"tasks failed: {app.tasks_failed}") @@ -1403,45 +1437,9 @@ async def async_run_tasks(self): app = App() -def main(args): - # pylint: disable=line-too-long - # fmt: off - parser = argparse.ArgumentParser() - parser.add_argument("target", default=None, nargs="?", type=str, help="A regex that selects the targets to build. Defaults to all targets.") - - parser.add_argument("-f", "--root_file", default="build.hancho", type=str, help="The name of the .hancho file(s) to build") - parser.add_argument("-C", "--root_dir", default=os.getcwd(), type=str, help="Change directory before starting the build") - - parser.add_argument("-v", "--verbose", default=False, action="store_true", help="Print verbose build info") - parser.add_argument("-d", "--debug", default=False, action="store_true", help="Print debugging information") - parser.add_argument("--force", default=False, action="store_true", help="Force rebuild of everything") - parser.add_argument("--trace", default=False, action="store_true", help="Trace all text expansion") - - parser.add_argument("-j", "--jobs", default=os.cpu_count(), type=int, help="Run N jobs in parallel (default = cpu_count)") - parser.add_argument("-q", "--quiet", default=False, action="store_true", help="Mute all output") - parser.add_argument("-n", "--dry_run", default=False, action="store_true", help="Do not run commands") - parser.add_argument("-s", "--shuffle", default=False, action="store_true", help="Shuffle task order to shake out dependency issues") - parser.add_argument("--use_color", default=False, action="store_true", help="Use color in the console output") - # fmt: on - - (flags, unrecognized) = parser.parse_known_args(args) - flags = flags.__dict__ - - # Unrecognized command line parameters also become global config fields if they are - # flag-like - extra_flags = {} - for span in unrecognized: - if match := re.match(r"-+([^=\s]+)(?:=(\S+))?", span): - key = match.group(1) - val = match.group(2) - val = maybe_as_number(val) if val is not None else True - extra_flags[key] = val - - return app.main(flags, extra_flags) - #################################################################################################### if __name__ == "__main__": - exitcode = main(sys.argv[1:]) - sys.exit(exitcode) + app.parse_flags(sys.argv[1:]) + sys.exit(app.main()) diff --git a/old_docs/README.md b/old_docs/README.md index 7fd3aad..27f9be7 100644 --- a/old_docs/README.md +++ b/old_docs/README.md @@ -3,7 +3,7 @@ ```py # examples/hello_world/build.hancho -compile = hancho.command( +compile = hancho.Config( command = "g++ -MMD -c {in_src} -o {out_obj}", desc = "Compile {in_src} -> {out_obj}", in_src = None, @@ -11,7 +11,7 @@ compile = hancho.command( c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.command( +link = hancho.Config( command = "g++ {in_objs} -o {out_bin}", desc = "Link {in_objs} -> {out_bin}", ) @@ -51,7 +51,7 @@ main_app = link(in_objs = main_o, out_bin = "app") - In your top .hancho file, use ```thingy = load(root="my_submodule", file="component/thingy/build.hancho")``` - Built files will appear in ```build/my_submodule/component/thingy/...``` -# Special fields in hancho.Command() +# Special fields in hancho.Config() - ```base``` (Default: ```config```) - The rule this rule inherits from. Reading missing fields from a ```rule``` will check ```rule.base``` for the field if there is one, otherwise the missing field will read as ```None```. diff --git a/riscv_rules.hancho b/riscv_rules.hancho index a4de10a..7a57513 100644 --- a/riscv_rules.hancho +++ b/riscv_rules.hancho @@ -15,14 +15,14 @@ rv_config = hancho.Config( joined_includes = "{join_prefix('-I', get('includes', []))}", ) -rv_compile = hancho.Command( +rv_compile = hancho.Config( rv_config, command = "{rv_toolchain}-g++ {rv_flags_c} {joined_warnings} {joined_defines} {joined_includes} -c {in_src} -o {out_obj}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -rv_link = hancho.Command( +rv_link = hancho.Config( rv_config, command = "{rv_toolchain}-gcc {rv_flags_c} {in_objs} -o {out_bin} -lgcc", rv_flags_c = rv_config.rv_flags_c + [ diff --git a/tests/run_tests.py b/tests/run_tests.py index e214bf8..5739bcf 100755 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -13,7 +13,7 @@ import time sys.path.append("..") -import hancho +import hancho as hancho_py # tests still needed - # calling hancho in src dir @@ -86,8 +86,11 @@ def setUp(self): # Always wipe the build dir before a test shutil.rmtree("build", ignore_errors=True) - hancho.app.reset() - hancho.app.quiet = True + hancho_py.app.reset() + #hancho_py.app.parse_flags(["--quiet"]) + hancho_py.app.parse_flags([]) + #hancho_py.app.parse_flags(["-v"]) + self.hancho = hancho_py.app.create_root_context() ######################################## @@ -97,36 +100,6 @@ def tearDown(self): ######################################## - def create_ctx(self, flags, extra_flags): - #argv = commandline.split() - - default_flags = hancho.Config( - shuffle = False, - use_color = True, - quiet = False, - dry_run = False, - jobs = os.cpu_count(), - target = None, - verbose = False, - debug = False, - force = False, - trace = False, - root_file = 'build.hancho', - root_dir = os.getcwd(), - ) - - default_extra_flags = hancho.Config() - - default_flags.merge(flags) - default_extra_flags.merge(extra_flags) - - hancho.app.flags = hancho.Config(verbose = False) - - ctx = hancho.app.create_root_context(default_flags.__dict__, default_extra_flags.__dict__) - return ctx - - ######################################## - def test_dummy(self): self.assertEqual(0, 0) @@ -134,23 +107,22 @@ def test_dummy(self): def test_should_pass(self): """Sanity check""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx(command = "(exit 0)") - self.assertEqual(0, hancho.app.build_all()) + self.hancho(command = "(exit 0)") + self.assertEqual(0, hancho_py.app.build_all()) ######################################## def test_should_fail(self): """Sanity check""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx(command = "echo skldjlksdlfj && (exit 255)") - self.assertNotEqual(0, hancho.app.build_all()) + bad_task = self.hancho(command = "echo skldjlksdlfj && (exit 255)") + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(bad_task._state, hancho_py.TaskState.FAILED) ######################################## # def test_subrepos1(self): # """Outputs from a subrepo should go in build/repo_name/...""" -# repo = hancho.repo("subrepo") +# repo = self.hancho.repo("subrepo") # task = repo.task( # command = "cat {rel_source_files} > {rel_build_files}", # source_files = "stuff.txt", @@ -208,34 +180,31 @@ def test_should_fail(self): ######################################## def test_good_build_path(self): - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + self.hancho( command = "touch {rel(out_obj)}", in_src = "src/foo.c", out_obj = "{repo_dir}/build/narp/foo.o", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(Path("build/narp/foo.o").exists()) ######################################## def test_bad_build_path(self): - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + bad_task = self.hancho( command = "touch {rel(out_obj)}", in_src = "src/foo.c", out_obj = "{repo_dir}/../build/foo.o", ) - self.assertNotEqual(0, hancho.app.build_all()) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(bad_task._state, hancho_py.TaskState.BROKEN) self.assertFalse(Path("build/foo.o").exists()) - self.assertTrue("Path error" in hancho.app.log) + self.assertTrue("Path error" in hancho_py.app.log) ######################################## def test_raw_task(self): - ctx = self.create_ctx({'quiet':True}, {}) - #ctx = self.create_ctx("-d") - task = ctx.Task( + self.hancho.Task( command = "touch {rel(out_obj)}", in_src = "src/foo.c", out_obj = "foo.o", @@ -243,100 +212,96 @@ def test_raw_task(self): build_dir = "build" ) #print(task) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(Path("build/foo.o").exists()) ######################################## def test_missing_input(self): """We should fail if an input is missing""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + bad_task = self.hancho( command = "touch {rel(out_obj)}", in_src = "src/does_not_exist.txt", out_obj = "missing_src.txt" ) - self.assertNotEqual(0, hancho.app.build_all()) - print(hancho.app.log) - self.assertTrue("FileNotFoundError" in hancho.app.log) - self.assertTrue("does_not_exist.txt" in hancho.app.log) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(bad_task._state, hancho_py.TaskState.BROKEN) + self.assertTrue("FileNotFoundError" in hancho_py.app.log) + self.assertTrue("does_not_exist.txt" in hancho_py.app.log) ######################################## def test_missing_dep(self): """Missing dep should fail""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + bad_task = self.hancho( command = "touch {rel(out_obj)}", in_src = "src/test.cpp", in_dep = ["missing_dep.txt"], out_obj = "result.txt", ) - self.assertNotEqual(0, hancho.app.build_all()) - self.assertTrue("FileNotFoundError" in hancho.app.log) - self.assertTrue("missing_dep.txt" in hancho.app.log) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(bad_task._state, hancho_py.TaskState.BROKEN) + self.assertTrue("FileNotFoundError" in hancho_py.app.log) + self.assertTrue("missing_dep.txt" in hancho_py.app.log) ######################################## def test_expand_failed_to_terminate(self): """A recursive text template should cause an 'expand failed to terminate' error.""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + bad_task = self.hancho( command = "{flarp}", in_src = [], out_obj = [], flarp = "asdf {flarp}", #verbose = True ) - self.assertNotEqual(0, hancho.app.build_all()) - #print(hancho.app.log) - self.assertTrue("Text expansion failed to terminate" in hancho.app.log) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(bad_task._state, hancho_py.TaskState.BROKEN) + self.assertTrue("Text expansion failed to terminate" in hancho_py.app.log) ######################################## def test_garbage_command(self): """Non-existent command line commands should cause Hancho to fail the build.""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + garbage_task = self.hancho( command = "aklsjdflksjdlfkjldfk", in_src = __file__, out_obj = "result.txt", ) - self.assertNotEqual(0, hancho.app.build_all()) - self.assertTrue("ValueError: 127" in hancho.app.log) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(garbage_task._state, hancho_py.TaskState.FAILED) + self.assertTrue("ValueError: Command exited with return code 127" in hancho_py.app.log) ######################################## def test_rule_collision(self): """If multiple rules generate the same output file, that's an error.""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + self.hancho( command = "sleep 0.1 && touch {rel(out_obj)}", in_src = __file__, out_obj = "colliding_output.txt", ) - ctx( + self.hancho( command = "touch {rel(out_obj)}", in_src = __file__, out_obj = "colliding_output.txt", ) - self.assertNotEqual(0, hancho.app.build_all()) - self.assertTrue("Multiple rules build" in hancho.app.log) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertTrue("Multiple rules build" in hancho_py.app.log) ######################################## def test_always_rebuild_if_no_inputs(self): """A rule with no inputs should always rebuild""" - ctx = self.create_ctx({'quiet':True}, {}) def run(): - hancho.app.reset() - hancho.app.quiet = True - ctx( + hancho_py.app.reset() + hancho_py.app.parse_flags(["--quiet"]) + self.hancho( command = "sleep 0.1 && touch {rel(out_obj)}", in_src = [], out_obj = "result.txt", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) return mtime_ns("build/result.txt") mtime1 = run() @@ -349,18 +314,17 @@ def run(): def test_dep_changed(self): """Changing a file in deps[] should trigger a rebuild""" - ctx = self.create_ctx({'quiet':True}, {}) # This test is flaky without the "sleep 0.1" because of filesystem mtime granularity def run(): - hancho.app.reset() - hancho.app.quiet = True - ctx( + hancho_py.app.reset() + hancho_py.app.parse_flags(["--quiet"]) + self.hancho( command = "sleep 0.1 && touch {rel(out_obj)}", in_temp = ["build/dummy.txt"], in_src = "src/test.cpp", out_obj = "result.txt", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) return mtime_ns("build/result.txt") os.makedirs("build", exist_ok=True) @@ -376,44 +340,41 @@ def run(): def test_does_create_output(self): """Output files should appear in build/ by default""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + self.hancho( command = "touch {rel(out_obj)}", in_src = [], out_obj = "result.txt", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(path.exists("build/result.txt")) ######################################## def test_doesnt_create_output(self): """Having a file mentioned in out_obj should not magically create it""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + self.hancho( command = "echo", in_src = [], out_obj = "result.txt" ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertFalse(path.exists("build/result.txt")) ######################################## def test_header_changed(self): """Changing a header file tracked in the GCC dependencies file should trigger a rebuild""" - ctx = self.create_ctx({'quiet':True}, {}) def run(): - hancho.app.reset() - hancho.app.quiet = True + hancho_py.app.reset() + hancho_py.app.parse_flags(["--quiet"]) time.sleep(0.01) - compile = ctx.Command( + compile = self.hancho.Config( command = "gcc -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) - ctx(compile, in_src = "src/test.cpp") - self.assertEqual(0, hancho.app.build_all()) + self.hancho(compile, in_src = "src/test.cpp") + self.assertEqual(0, hancho_py.app.build_all()) return mtime_ns("build/src/test.o") mtime1 = run() @@ -428,18 +389,17 @@ def run(): def test_input_changed(self): """Changing a source file should trigger a rebuild""" - ctx = self.create_ctx({'quiet':True}, {}) def run(): - hancho.app.reset() - hancho.app.quiet = True + hancho_py.app.reset() + hancho_py.app.parse_flags(["--quiet"]) time.sleep(0.01) - compile = ctx.Command( + compile = self.hancho.Config( command = "gcc -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) - ctx(compile, in_src = "src/test.cpp") - self.assertEqual(0, hancho.app.build_all()) + self.hancho(compile, in_src = "src/test.cpp") + self.assertEqual(0, hancho_py.app.build_all()) return mtime_ns("build/src/test.o") mtime1 = run() @@ -454,8 +414,7 @@ def run(): def test_multiple_commands(self): """Rules with arrays of commands should run all of them""" - ctx = self.create_ctx({'quiet':True}, {}) - ctx( + self.hancho( command = [ "echo foo > {rel(out_foo)}", "echo bar > {rel(out_bar)}", @@ -467,7 +426,7 @@ def test_multiple_commands(self): out_baz = "baz.txt", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(path.exists("build/foo.txt")) self.assertTrue(path.exists("build/bar.txt")) self.assertTrue(path.exists("build/baz.txt")) @@ -476,65 +435,60 @@ def test_multiple_commands(self): def test_arbitrary_flags(self): """Passing arbitrary flags to Hancho should work""" - ctx = self.create_ctx({'quiet':True}, {'flarpy':'flarp.txt'}) - self.assertEqual("flarp.txt", ctx.config['flarpy']) + hancho_py.app.reset() + hancho_py.app.parse_flags(["--quiet", "--flarpy=flarp.txt"]) + self.hancho = hancho_py.app.create_root_context() + self.assertEqual("flarp.txt", self.hancho.config.flarpy) - ctx( + self.hancho( command = "touch {out_file}", source_files = [], out_file = "{flarpy}", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(path.exists("build/flarp.txt")) ######################################## - #def test_what_is_in_a_task(self): - # task = hancho.Task( - # command = "", - # task_dir = "", - # build_dir = "" - # ) - # print(task) - - ######################################## - def test_sync_command(self): """The 'command' field of rules should be OK handling a sync function""" - ctx = self.create_ctx({'quiet':True}, {}) - def sync_command(task): force_touch(task.config.out_obj) - task = ctx( + self.hancho( command = sync_command, in_src = [], out_obj = "result.txt", ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(path.exists("build/result.txt")) ######################################## def test_cancellation(self): """A task that receives a cancellation exception should not run.""" - ctx = self.create_ctx({'quiet':True}, {}) - task_that_fails = ctx( + task_that_fails = self.hancho( command = "(exit 255)", in_src = [], out_obj = "fail_result.txt", ) - task_that_passes = ctx( + task_that_passes = self.hancho( command = "touch {rel(out_obj)}", in_src = [], out_obj = "pass_result.txt", ) - should_be_cancelled = ctx( + should_be_cancelled = self.hancho( command = "touch {rel(out_obj)}", in_src = [task_that_fails, task_that_passes], out_obj = "should_not_be_created.txt", ) - self.assertNotEqual(0, hancho.app.build_all()) + self.assertNotEqual(0, hancho_py.app.build_all()) + self.assertEqual(1, hancho_py.app.tasks_finished) + self.assertEqual(1, hancho_py.app.tasks_failed) + self.assertEqual(1, hancho_py.app.tasks_cancelled) + self.assertEqual(task_that_fails._state, hancho_py.TaskState.FAILED) + self.assertEqual(task_that_passes._state, hancho_py.TaskState.FINISHED) + self.assertEqual(should_be_cancelled._state, hancho_py.TaskState.CANCELLED) self.assertTrue(Path("build/pass_result.txt").exists()) self.assertFalse(Path("build/fail_result.txt").exists()) self.assertFalse(Path("build/should_not_be_created.txt").exists()) @@ -543,9 +497,8 @@ def test_cancellation(self): def test_task_creates_task(self): """Tasks using callbacks can create new tasks when they run.""" - ctx = self.create_ctx({'quiet':True}, {}) def callback(task): - new_task = ctx( + new_task = self.hancho( command = "touch {rel(out_obj)}", in_src = [], out_obj = "dummy.txt" @@ -554,29 +507,28 @@ def callback(task): new_task.queue() return [] - ctx( + self.hancho( command = callback, in_src = [], out_obj = [] ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(Path("build/dummy.txt").exists()) ######################################## def test_tons_of_tasks(self): """We should be able to queue up 1000+ tasks at once.""" - ctx = self.create_ctx({'quiet':True}, {}) for i in range(1000): - ctx( + self.hancho( desc = "I am task {index}", command = "echo {index} > {rel(out_obj)}", in_src = [], out_obj = "dummy{index}.txt", index = i ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertEqual(1000, len(glob.glob("build/*"))) ######################################## @@ -585,10 +537,9 @@ def test_job_count(self): """We should be able to dispatch tasks that require various numbers of jobs/cores.""" # Queues up 100 tasks that use random numbers of cores, then a "Job Hog" that uses all cores, then # another batch of 100 tasks that use random numbers of cores. - ctx = self.create_ctx({'quiet':True}, {}) for i in range(100): - ctx( + self.hancho( desc = "I am task {index}, I use {job_count} cores", command = "(exit 0)", in_src = [], @@ -597,7 +548,7 @@ def test_job_count(self): index = i ) - ctx( + self.hancho( desc = "********** I am the slow task, I eat all the cores **********", command = [ "touch {rel(out_obj)}", @@ -609,7 +560,7 @@ def test_job_count(self): ) for i in range(100): - ctx( + self.hancho( desc = "I am task {index}, I use {job_count} cores", command = "(exit 0)", in_src = [], @@ -618,7 +569,7 @@ def test_job_count(self): index = 100 + i ) - self.assertEqual(0, hancho.app.build_all()) + self.assertEqual(0, hancho_py.app.build_all()) self.assertTrue(Path("build/slow_result.txt").exists()) #################################################################################################### diff --git a/tests/subrepo_tests/repo1/repo1_test2.hancho b/tests/subrepo_tests/repo1/repo1_test2.hancho index e67b89c..bc53149 100644 --- a/tests/subrepo_tests/repo1/repo1_test2.hancho +++ b/tests/subrepo_tests/repo1/repo1_test2.hancho @@ -1,4 +1,4 @@ -touch = hancho.command("touch {rel_build_files}") +touch = hancho.Config("touch {rel_build_files}") rule([], "repo1.txt") diff --git a/tests/subrepo_tests/repo1/rules/repo1_test3_rules.hancho b/tests/subrepo_tests/repo1/rules/repo1_test3_rules.hancho index e10347e..5a571fd 100644 --- a/tests/subrepo_tests/repo1/rules/repo1_test3_rules.hancho +++ b/tests/subrepo_tests/repo1/rules/repo1_test3_rules.hancho @@ -1,5 +1,5 @@ # repo1_test3_rules.hancho -exports.rule = hancho.Command( +exports.rule = hancho.Config( command = "touch {rel_build_files}", ) diff --git a/tests/subrepo_tests/repo2/repo2_test1.hancho b/tests/subrepo_tests/repo2/repo2_test1.hancho index d27eb63..c0d5f26 100644 --- a/tests/subrepo_tests/repo2/repo2_test1.hancho +++ b/tests/subrepo_tests/repo2/repo2_test1.hancho @@ -1,2 +1,2 @@ -touch = hancho.command("touch {rel_build_files}") +touch = hancho.Config("touch {rel_build_files}") touch([], "repo2.txt") diff --git a/tests/subrepo_tests/repo2/repo2_test2.hancho b/tests/subrepo_tests/repo2/repo2_test2.hancho index d27eb63..c0d5f26 100644 --- a/tests/subrepo_tests/repo2/repo2_test2.hancho +++ b/tests/subrepo_tests/repo2/repo2_test2.hancho @@ -1,2 +1,2 @@ -touch = hancho.command("touch {rel_build_files}") +touch = hancho.Config("touch {rel_build_files}") touch([], "repo2.txt") diff --git a/tests/subrepo_tests/repo2/rules/repo2_test3_rules.hancho b/tests/subrepo_tests/repo2/rules/repo2_test3_rules.hancho index 500c1f7..6643940 100644 --- a/tests/subrepo_tests/repo2/rules/repo2_test3_rules.hancho +++ b/tests/subrepo_tests/repo2/rules/repo2_test3_rules.hancho @@ -1,5 +1,5 @@ # repo2_test3_rules.hancho -exports.rule = hancho.Command( +exports.rule = hancho.Config( command = "touch {rel_build_files}", ) diff --git a/tests/subrepo_tests/top_test2.hancho b/tests/subrepo_tests/top_test2.hancho index 6889365..3f02200 100644 --- a/tests/subrepo_tests/top_test2.hancho +++ b/tests/subrepo_tests/top_test2.hancho @@ -1,6 +1,6 @@ # Load repo1 from repo2 -touch = hancho.command("touch {rel_build_files}") +touch = hancho.Config("touch {rel_build_files}") touch([], "top.txt") diff --git a/tutorial/tut00.hancho b/tutorial/tut00.hancho index 29777ae..a8e2c6e 100644 --- a/tutorial/tut00.hancho +++ b/tutorial/tut00.hancho @@ -271,14 +271,14 @@ app = hancho.Task(cpp_link, in_objs = [main_o, util_o], out_bin = "build/tut1 # tutorial/tut14.hancho -cpp_compile = hancho.Command( +cpp_compile = hancho.Config( desc = "Compile {rel(in_src)} -> {rel(out_obj)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -cpp_link = hancho.Command( +cpp_link = hancho.Config( desc = "Link {rel(in_objs)} into {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", ) @@ -289,14 +289,14 @@ app = hancho(cpp_link, in_objs = [main_o, util_o], out_bin = "build/tut14/app # tutorial/tut15.hancho -cpp_compile = hancho.Command( +cpp_compile = hancho.Config( desc = "Compile {rel(in_src)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "build/tut15/{swap_ext(in_src, '.o')}", c_deps = "build/tut15/{swap_ext(in_src, '.d')}", ) -cpp_link = hancho.Command( +cpp_link = hancho.Config( desc = "Link {rel(in_objs)} into {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", ) @@ -308,14 +308,14 @@ app = cpp_link(in_objs = [main_o, util_o], out_bin = "build/tut15/app") # tutorial/tut16.hancho -compile = hancho.Command( +compile = hancho.Config( desc = "Compile {rel(in_src)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.Command( +link = hancho.Config( desc = "Link {rel(in_objs)} into {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", ) @@ -328,14 +328,14 @@ app = link(**config, in_objs = [main_o, util_o], out_bin = "app") # tutorial/tut20.hancho -compile = hancho.Command( +compile = hancho.Config( desc = "Compile {rel(in_)}", command = "g++ -MMD -c {rel(in_)} -o {rel(out_)}", out_ = "{swap_ext(in_, '.o')}", c_deps = "{swap_ext(in_, '.d')}", ) -link = hancho.Command( +link = hancho.Config( desc = "Link {rel(in_)} into {rel(out_)}", command = "g++ {rel(in_)} -o {rel(out_)}", ) @@ -385,14 +385,14 @@ app = rules.c_binary(config, ["src/main.cpp", "src/util.cpp"], "app") # tutorial/tut30_rules.hancho -compile = hancho.Command( +compile = hancho.Config( desc = "Compile {rel(in_src)} -> {rel(out_obj)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.Command( +link = hancho.Config( desc = "Link {rel(in_objs)} -> {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", ) @@ -436,14 +436,14 @@ exports.bin = bin # tutorial/tut40_rules.hancho -compile = hancho.Command( +compile = hancho.Config( desc = "Compile {rel(in_src)} -> {rel(out_obj)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.Command( +link = hancho.Config( desc = "Link {rel(in_objs)} -> {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", ) @@ -478,14 +478,14 @@ rules.c_binary(in_srcs = glob.glob("*.cpp"), out_bin = "app", **config) # tutorial/tut41_rules.hancho -compile = hancho.Command( +compile = hancho.Config( desc = "Compile {rel(in_src)} -> {rel(out_obj)}", command = "g++ -MMD -c {rel(in_src)} -o {rel(out_obj)}", out_obj = "{swap_ext(in_src, '.o')}", c_deps = "{swap_ext(in_src, '.d')}", ) -link = hancho.Command( +link = hancho.Config( desc = "Link {rel(in_objs)} -> {rel(out_bin)}", command = "g++ {rel(in_objs)} -o {rel(out_bin)}", )