diff --git a/hancho.py b/hancho.py index 534b587..64d5b74 100755 --- a/hancho.py +++ b/hancho.py @@ -28,6 +28,8 @@ def color(red=None, green=None, blue=None): """Converts RGB color to ANSI format string""" + # FIXME: Color strings don't work in Windows console? + if os.name == 'nt': return "" if red is None: return "\x1B[0m" return f"\x1B[38;2;{red};{green};{blue}m" @@ -105,6 +107,28 @@ def maybe_as_number(text): return text +def touch(name): + """Convenience helper method""" + if isinstance(name, Rule): + for f in name.files_out: + touch(f) + if os.path.exists(name): + os.utime(name, None) + else: + with open(name, "w") as file: + file.write("") + + +async def async_touch(task): + """Convenience helper method""" + for name in task.files_out: + if os.path.exists(name): + os.utime(name, None) + else: + with open(name, "w") as file: + file.write("") + return task.files_out + ################################################################################ this.line_dirty = False @@ -252,11 +276,11 @@ async def async_main(): log(f"mtime calls: {this.mtime_calls}") if this.tasks_fail: - log("hancho: \x1B[31mBUILD FAILED\x1B[0m") + log(f"hancho: {color(255, 0, 0)}BUILD FAILED{color()}") elif this.tasks_pass: - log("hancho: \x1B[32mBUILD PASSED\x1B[0m") + log(f"hancho: {color(0, 255, 0)}BUILD PASSED{color()}") else: - log("hancho: \x1B[33mBUILD CLEAN\x1B[0m") + log(f"hancho: {color(255, 255, 0)}BUILD CLEAN{color()}") if this.config.chdir: os.chdir(this.hancho_root) @@ -572,7 +596,9 @@ async def run_command(self, command): # Custom commands just get await'ed and then early-out'ed. if callable(command): - result = await command(self) + result = command(self) + if inspect.isawaitable(result): + result = await result if result is None: raise ValueError(f"Command {command} returned None") return result @@ -651,8 +677,14 @@ def needs_rerun(self): if self.debug: log(f"Found depfile {abs_depfile}") with open(abs_depfile, encoding="utf-8") as depfile: - deplines = depfile.read().split() - deplines = [d for d in deplines[1:] if d != "\\"] + deplines = None + if os.name == 'nt': + # MSVC /sourceDependencies json depfile + deplines = json.load(depfile)['Data']['Includes'] + elif os.name == 'posix': + # GCC .d depfile + deplines = depfile.read().split() + deplines = [d for d in deplines[1:] if d != "\\"] if deplines and max(mtime(f) for f in deplines) >= min_out: return ( f"Rebuilding {self.files_out} because a dependency in " diff --git a/tests/always_rebuild_if_no_inputs.hancho b/tests/always_rebuild_if_no_inputs.hancho index ec48364..afd090c 100644 --- a/tests/always_rebuild_if_no_inputs.hancho +++ b/tests/always_rebuild_if_no_inputs.hancho @@ -1,7 +1,2 @@ -# test/always_rebuild_if_no_inputs.hancho - -always_rebuilt = Rule( - command = "touch {files_out}", -) - -always_rebuilt([], "result.txt") +rules = load("rules.hancho") +rules.touch_outputs([], "result.txt") diff --git a/tests/build_dir_works.hancho b/tests/build_dir_works.hancho index c4e2886..823b0ef 100644 --- a/tests/build_dir_works.hancho +++ b/tests/build_dir_works.hancho @@ -1,9 +1,3 @@ -# tests/build_dir_works.hancho - +rules = load("rules.hancho") config.build_dir = "build/build_dir_works" - -build_dir_works = Rule( - command = "touch {files_out}", -) - -build_dir_works([], "result.txt") +rules.touch_outputs([], "result.txt") diff --git a/tests/check_output.hancho b/tests/check_output.hancho index 13c0ead..c15626a 100644 --- a/tests/check_output.hancho +++ b/tests/check_output.hancho @@ -1,7 +1,5 @@ # tests/check_output.hancho -check_output = Rule( - command = "touch {files_out[0]}", -) +check_output = Rule(command = "echo foo > {files_out[0]}") check_output(__file__, ["result.txt", "not_modified.txt"]) diff --git a/tests/command_missing.hancho b/tests/command_missing.hancho index f4336ca..c25ffb8 100644 --- a/tests/command_missing.hancho +++ b/tests/command_missing.hancho @@ -1,6 +1,2 @@ -# tests/command_missing.hancho - -command_missing = Rule( -) - +command_missing = Rule() command_missing(__file__) diff --git a/tests/dep_changed.hancho b/tests/dep_changed.hancho index c338445..0def916 100644 --- a/tests/dep_changed.hancho +++ b/tests/dep_changed.hancho @@ -1,9 +1,5 @@ - -dep_changed = Rule( - command = "touch {files_out}", -) - -dep_changed( +rules = load("rules.hancho") +rules.touch_outputs( "src/test.cpp", "result.txt", deps = ["build/dummy.txt"] diff --git a/tests/does_create_output.hancho b/tests/does_create_output.hancho index af86dc5..87dcd08 100644 --- a/tests/does_create_output.hancho +++ b/tests/does_create_output.hancho @@ -1,7 +1,2 @@ -# tests/does_create_output - -does_create_output = Rule( - command = "touch {files_out}", -) - -does_create_output([], files_out = "result.txt") +rules = load("rules.hancho") +rules.touch_outputs([], files_out = "result.txt") diff --git a/tests/doesnt_create_output.hancho b/tests/doesnt_create_output.hancho index 90c0669..7b74025 100644 --- a/tests/doesnt_create_output.hancho +++ b/tests/doesnt_create_output.hancho @@ -1,7 +1,2 @@ -# tests/doesnt_create_output.hancho - -doesnt_create_output = Rule( - command = ":", -) - +doesnt_create_output = Rule(command = ":") doesnt_create_output(__file__, "result.txt") diff --git a/tests/garbage_command.hancho b/tests/garbage_command.hancho index aa44dfe..bb7534d 100644 --- a/tests/garbage_command.hancho +++ b/tests/garbage_command.hancho @@ -1,7 +1,2 @@ -# tests/garbage_command.hancho - -garbage_command = Rule( - command = "aklsjdflksjdlfkjldfk", -) - +garbage_command = Rule(command = "aklsjdflksjdlfkjldfk") garbage_command(__file__, "result.txt") diff --git a/tests/hancho_dot_load.hancho b/tests/hancho_dot_load.hancho deleted file mode 100644 index e69de29..0000000 diff --git a/tests/hancho_in_src_dir.hancho b/tests/hancho_in_src_dir.hancho deleted file mode 100644 index e69de29..0000000 diff --git a/tests/header_changed.hancho b/tests/header_changed.hancho index 6a83f91..d67b5ad 100644 --- a/tests/header_changed.hancho +++ b/tests/header_changed.hancho @@ -1,8 +1,2 @@ - -header_changed = Rule( - command = "gcc -MMD -c {files_in} -o {files_out}", - files_out = "{swap_ext(files_in, '.o')}", - depfile = "{swap_ext(files_out, '.d')}", -) - -header_changed("src/test.cpp") +rules = load("rules.hancho") +rules.compile_cpp("src/test.cpp") diff --git a/tests/input_changed.hancho b/tests/input_changed.hancho index 45d9bac..d67b5ad 100644 --- a/tests/input_changed.hancho +++ b/tests/input_changed.hancho @@ -1,8 +1,2 @@ - -input_changed = Rule( - command = "gcc -MMD -c {files_in} -o {files_out}", - files_out = "{swap_ext(files_in, '.o')}", - depfile = "{swap_ext(files_out, '.d')}", -) - -input_changed("src/test.cpp") +rules = load("rules.hancho") +rules.compile_cpp("src/test.cpp") diff --git a/tests/meta_deps_changed.hancho b/tests/meta_deps_changed.hancho deleted file mode 100644 index e69de29..0000000 diff --git a/tests/missing_src.hancho b/tests/missing_src.hancho index 5d5a14c..a91b30e 100644 --- a/tests/missing_src.hancho +++ b/tests/missing_src.hancho @@ -1,7 +1,2 @@ -# tests/missing_src.hancho - -missing_src = Rule( - command = "touch {files_out}", -) - -missing_src("src/does_not_exist.txt", "build/missing_src.txt") +rules = load("rules.hancho") +rules.touch_outputs("src/does_not_exist.txt", "build/missing_src.txt") diff --git a/tests/multiple_commands.hancho b/tests/multiple_commands.hancho index 7a73539..bec2394 100644 --- a/tests/multiple_commands.hancho +++ b/tests/multiple_commands.hancho @@ -1,8 +1,10 @@ +import os + multiple_commands = Rule( command = [ - "touch {files_out[0]}", - "touch {files_out[1]}", - "touch {files_out[2]}" + "echo foo > {files_out[0]}", + "echo bar > {files_out[1]}", + "echo baz > {files_out[2]}" ] ) diff --git a/tests/rules.hancho b/tests/rules.hancho new file mode 100644 index 0000000..d22eecd --- /dev/null +++ b/tests/rules.hancho @@ -0,0 +1,18 @@ +import os + +if os.name == 'nt': + compile_command = "cl.exe /c {files_in} /sourceDependencies {depfile} /Fo:{files_out}" +elif os.name == 'posix': + compile_command = "gcc -MMD -c {files_in} -o {files_out}" +else: + compile_command = "" + +compile_cpp = Rule( + command = compile_command, + files_out = "{swap_ext(files_in, '.o')}", + depfile = "{swap_ext(files_out, '.d')}", +) + +touch_outputs = Rule( + command = async_touch +) diff --git a/tests/src/main.cpp b/tests/src/main.cpp new file mode 100644 index 0000000..077c5e9 --- /dev/null +++ b/tests/src/main.cpp @@ -0,0 +1,7 @@ +#include +#include "main.hpp" + +int main(int argc, char** argv) { + printf(message()); + return 0; +} \ No newline at end of file diff --git a/tests/src/main.hpp b/tests/src/main.hpp new file mode 100644 index 0000000..123251f --- /dev/null +++ b/tests/src/main.hpp @@ -0,0 +1,5 @@ +#pragma once + +inline const char* message() { + return "Hello World!!!\n"; +} \ No newline at end of file diff --git a/tests/sync_command.hancho b/tests/sync_command.hancho new file mode 100644 index 0000000..89d6658 --- /dev/null +++ b/tests/sync_command.hancho @@ -0,0 +1,12 @@ + + +def sync_command(task): + print("hello world") + touch(task.files_out[0]) + return task.files_out + +rule = Rule( + command = sync_command +) + +rule(__file__, "result.txt") diff --git a/tests/test.py b/tests/test.py index 72101a0..079ab68 100755 --- a/tests/test.py +++ b/tests/test.py @@ -5,20 +5,34 @@ from os import path import subprocess import unittest +import shutil +import sys -# min delta seems to be 4 msec -# os.system("touch blahblah.txt") +sys.path.append("..") +import hancho + +# tests still needed - +# calling hancho in src dir +# meta deps changed +# transitive dependencies + +# cl /c main.cpp +# link /out:"blah.exe" main.obj + +# min delta seems to be 4 msec on linux, 1 msec on windows? +#os.system("touch blahblah.txt") # old_mtime = path.getmtime("blahblah.txt") # min_delta = 1000000 -# for _ in range(1000): -# os.system("touch blahblah.txt") +# for _ in range(10000): +# #os.system("touch blahblah.txt") +# os.utime("blahblah.txt", None) # new_mtime = path.getmtime("blahblah.txt") # delta = new_mtime - old_mtime # if delta and delta < min_delta: -# log(str(delta)) +# print(delta) # min_delta = delta # old_mtime = new_mtime - +# sys.exit(0) def mtime(file): """Shorthand for path.getmtime()""" @@ -32,12 +46,7 @@ def run(cmd): def run_hancho(name): """Runs a Hancho build script, quietly.""" - return os.system(f"../hancho.py --quiet {name}.hancho") - - -def touch(name): - """Convenience helper method""" - os.system(f"touch {name}") + return os.system(f"python3 ../hancho.py --quiet {name}.hancho") ################################################################################ @@ -48,7 +57,8 @@ class TestHancho(unittest.TestCase): def setUp(self): """Always wipe the build dir before a test""" - os.system("rm -rf build") + if path.exists("build"): + shutil.rmtree("build") def test_should_pass(self): """Sanity check""" @@ -102,15 +112,15 @@ def test_build_dir_works(self): def test_dep_changed(self): """Changing a file in deps[] should trigger a rebuild""" - os.system("mkdir build") - touch("build/dummy.txt") + os.makedirs("build", exist_ok = True) + hancho.touch("build/dummy.txt") run_hancho("dep_changed") mtime1 = mtime("build/result.txt") run_hancho("dep_changed") mtime2 = mtime("build/result.txt") - touch("build/dummy.txt") + hancho.touch("build/dummy.txt") run_hancho("dep_changed") mtime3 = mtime("build/result.txt") self.assertEqual(mtime1, mtime2) @@ -134,7 +144,7 @@ def test_header_changed(self): run_hancho("header_changed") mtime2 = mtime("build/src/test.o") - os.system("touch src/test.hpp") + hancho.touch("src/test.hpp") run_hancho("header_changed") mtime3 = mtime("build/src/test.o") self.assertEqual(mtime1, mtime2) @@ -148,7 +158,7 @@ def test_input_changed(self): run_hancho("input_changed") mtime2 = mtime("build/src/test.o") - os.system("touch src/test.cpp") + hancho.touch("src/test.cpp") run_hancho("input_changed") mtime3 = mtime("build/src/test.o") self.assertEqual(mtime1, mtime2) @@ -164,10 +174,14 @@ def test_multiple_commands(self): def test_arbitrary_flags(self): """Passing arbitrary flags to Hancho should work""" os.system( - "../hancho.py --build_dir=build/some/other/dir --quiet does_create_output.hancho" + "python3 ../hancho.py --build_dir=build/some/other/dir --quiet does_create_output.hancho" ) self.assertTrue(path.exists("build/some/other/dir/result.txt")) + def test_sync_command(self): + run_hancho("sync_command") + self.assertTrue(path.exists("build/result.txt")) + ################################################################################ diff --git a/tests/transitive_changed.hancho b/tests/transitive_changed.hancho deleted file mode 100644 index e69de29..0000000 diff --git a/tutorial/build.hancho b/tutorial/build.hancho index 3f7ed37..3f67816 100644 --- a/tutorial/build.hancho +++ b/tutorial/build.hancho @@ -5,7 +5,7 @@ config.jobs = 1 test_build = Rule( desc = "{color(200, 200, 100)}Testing tutorial build '{files_in}'{color()}", - command = "../hancho.py --verbose {files_in} && touch {files_out} && echo", + command = "python3 ../hancho.py --verbose {files_in} && echo pass > {files_out} && echo", files_out = "{files_in[0] + '.pass'}", ) diff --git a/tutorial/rules.hancho b/tutorial/rules.hancho index 651ec34..f0c970e 100644 --- a/tutorial/rules.hancho +++ b/tutorial/rules.hancho @@ -1,15 +1,23 @@ # tutorial/rules.hancho +import os + +compile_lin = "g++ -MMD -c {files_in} -o {files_out}" +compile_win = "cl.exe /nologo /c {files_in} /sourceDependencies {depfile} /Fo:{files_out} > NUL" + +link_lin = "g++ {files_in} -o {files_out}" +link_win = "link.exe /nologo {files_in} /out:{files_out} > NUL" + compile = Rule( desc = "Compile {files_in} -> {files_out}", - command = "g++ -MMD -c {files_in} -o {files_out}", + command = compile_win if os.name == 'nt' else compile_lin, files_out = "{swap_ext(files_in, '.o')}", depfile = "{swap_ext(files_out, '.d')}", ) link = Rule( desc = "Link {files_in} -> {files_out}", - command = "g++ {files_in} -o {files_out}", + command = link_win if os.name == 'nt' else link_lin, ) def c_binary(files_in, files_out, **kwargs): diff --git a/tutorial/src/src.hancho b/tutorial/src/src.hancho index 6ef20d5..658bf03 100644 --- a/tutorial/src/src.hancho +++ b/tutorial/src/src.hancho @@ -1,6 +1,10 @@ # tutorial/src/src.hancho import glob +import os rules = load("rules.hancho") -rules.c_binary(glob.glob("*.cpp"), "app") +if os.name == 'nt': + rules.c_binary(glob.glob("*.cpp"), "app.exe") +else: + rules.c_binary(glob.glob("*.cpp"), "app") diff --git a/tutorial/tut0.hancho b/tutorial/tut0.hancho index 56a9efc..c39008e 100644 --- a/tutorial/tut0.hancho +++ b/tutorial/tut0.hancho @@ -1,7 +1,14 @@ # tutorial/tut0.hancho -rule = Rule( - command = "g++ {files_in} -o {files_out}", -) +import os + +if os.name == 'nt': + rule = Rule( + command = "cl.exe /c {files_in} /Fo:{files_out}", + ) +else: + rule = Rule( + command = "g++ {files_in} -o {files_out}", + ) rule(["src/main.cpp", "src/util.cpp"], "tut0/app") diff --git a/tutorial/tut4.hancho b/tutorial/tut4.hancho index 869a747..3cee4f6 100644 --- a/tutorial/tut4.hancho +++ b/tutorial/tut4.hancho @@ -20,9 +20,7 @@ echo(do_slow_thing(), []) # You can also use them in the command field. async def custom_command(task): - for f in task.files_out: - print(f"Touching {f}") - os.system(f"touch {f}") + await async_touch(task) return task.files_out custom_rule = Rule(