From dba3ec1f3a562d77b8e54d437a4f5cbbf54e96ba Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 20:43:31 -0700 Subject: [PATCH 1/7] Initial work on build --watch --- pico8/build/build.py | 71 ++++++++++++++++++++++++++++++++++++++++++++ pico8/tool.py | 6 ++++ 2 files changed, 77 insertions(+) diff --git a/pico8/build/build.py b/pico8/build/build.py index a4cca91..641490c 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -1,4 +1,5 @@ import os +import time from .. import util from ..game import game @@ -6,6 +7,17 @@ from ..lua import parser from ..lua import lexer +try: + from watchdog.observers import Observer + from watchdog.events import PatternMatchingEventHandler + HAVE_WATCHDOG=True +except ImportError: + HAVE_WATCHDOG=False + # Build stubs to keep things happy + class Observer: + pass + class PatternMatchingEventHandler: + pass # The default Lua load path if neither PICO8_LUA_PATH nor --lua-path are set. DEFAULT_LUA_PATH = '?;?.lua' @@ -107,6 +119,36 @@ def _walk_FunctionCall(self, node): yield (require_path, use_game_loop, self._tokens[node.start_pos]) +class DirWatcher: + def __init__(self, watchdir, callback, args): + handler = BuildEventHandler( callback, args, + patterns=args.watch_glob.split(',') + ) + + self.observer = Observer() + self.observer.schedule(handler, watchdir, recursive=True) + self.observer.start() + + util.write("Watching for changes in \"{}\"...\n".format(watchdir)) + + def stop(self): + self.observer.stop() + + def join(self): + self.observer.join() + + +class BuildEventHandler(PatternMatchingEventHandler): + def __init__(self, callback, args, **kwargs): + # Function to call on change + self.callback = callback + # Args to pass through to the callback + self.args = args + super().__init__(**kwargs) + + def on_any_event(self, evt): + util.write("Change detected to {}, building...\n".format(evt.src_path)) + self.callback(self.args) def _evaluate_require(ast, file_path, package_lua, lua_path=None): """Evaluate require() statements in a Lua AST. @@ -212,6 +254,9 @@ def do_build(args): Args: args: The argparse.Namespace arguments object. """ + if args.watch is not None: + return do_watch(args) + if (not args.filename.endswith('.p8') and not args.filename.endswith('.p8.png')): util.error('Output filename must end with .p8 or .p8.png.') @@ -285,3 +330,29 @@ def do_build(args): lua_writer_args=lua_writer_args) return 0 + +def do_watch(args): + if not HAVE_WATCHDOG: + util.error("required watchdog library not installed") + return 1 + + # Check for valid directory + if args.watch: + if not os.path.exists(args.watch): + util.error("Watch directory doesn't exist!") + return 1 + if not os.path.isdir(args.watch): + util.error("Watch directory isn't a directory!") + return 1 + watchdir = args.filename + else: + util.write("No directory specified, defaulting to current directory\n") + watchdir = "." + + watcher = DirWatcher(watchdir, do_build, args) + try: + while True: + time.sleep(1) + except KeyboardInterrupt: + watcher.stop() + watcher.join() diff --git a/pico8/tool.py b/pico8/tool.py index 5aa1ab8..54e63c8 100644 --- a/pico8/tool.py +++ b/pico8/tool.py @@ -569,6 +569,12 @@ def _get_argparser(): sp_build.add_argument( '--empty-music', action='store_true', help='use an empty music region (overrides default)') + sp_build.add_argument( + '--watch', type=str, nargs="?", const="", + help='specify a directory to watch and automatically build on changes') + sp_build.add_argument( + '--watch-glob', type=str, default="*.p8,*.png,*.lua", + help='comma-separated list of globs to watch for') sp_build.add_argument( 'filename', type=str, help='filename of the output cart; if the file exists, ' From 4e001e72f980b5486bc850751e3e13b6084475cb Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 20:44:09 -0700 Subject: [PATCH 2/7] Strip watch so we don't get in an infinite loop --- pico8/build/build.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/pico8/build/build.py b/pico8/build/build.py index 641490c..9937026 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -121,7 +121,9 @@ def _walk_FunctionCall(self, node): class DirWatcher: def __init__(self, watchdir, callback, args): - handler = BuildEventHandler( callback, args, + # Watch has done its job, strip it + args.watch = None + handler = BuildEventHandler(callback, args, patterns=args.watch_glob.split(',') ) From c5c5addf73ce8cee7aa025b3b44f64be6b6e720c Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 20:48:23 -0700 Subject: [PATCH 3/7] Ignore our output file Otherwise we get in a loop when building, oops --- pico8/build/build.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/pico8/build/build.py b/pico8/build/build.py index 9937026..cd63ee1 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -124,7 +124,8 @@ def __init__(self, watchdir, callback, args): # Watch has done its job, strip it args.watch = None handler = BuildEventHandler(callback, args, - patterns=args.watch_glob.split(',') + patterns=args.watch_glob.split(','), + ignore_patterns=['*/' + args.filename] ) self.observer = Observer() From 02e0407a375390d3e57648ddbbe3e7e7888f67a4 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 20:58:02 -0700 Subject: [PATCH 4/7] Return nicely --- pico8/build/build.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/pico8/build/build.py b/pico8/build/build.py index cd63ee1..f26aa38 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -359,3 +359,5 @@ def do_watch(args): except KeyboardInterrupt: watcher.stop() watcher.join() + + return 0 From 44751b42488fae9a194ef17f3999a633c204e11f Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 21:07:28 -0700 Subject: [PATCH 5/7] Better messages --- pico8/build/build.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/pico8/build/build.py b/pico8/build/build.py index f26aa38..1e68744 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -336,20 +336,22 @@ def do_build(args): def do_watch(args): if not HAVE_WATCHDOG: - util.error("required watchdog library not installed") + util.error("optional python library \"watchdog\" not installed\n" + "to use --watch, install watchdog however you see fit\n" + "using pip, that would be \"pip install watchdog\"\n") return 1 # Check for valid directory if args.watch: if not os.path.exists(args.watch): - util.error("Watch directory doesn't exist!") + util.error("watch directory doesn't exist!\n") return 1 if not os.path.isdir(args.watch): - util.error("Watch directory isn't a directory!") + util.error("watch directory isn't a directory!\n") return 1 watchdir = args.filename else: - util.write("No directory specified, defaulting to current directory\n") + util.write("no directory specified, defaulting to current directory\n") watchdir = "." watcher = DirWatcher(watchdir, do_build, args) From 784524c0e97ffb93c1186bf6d7d05b7225fc06d0 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 21:08:49 -0700 Subject: [PATCH 6/7] Make quote usage consistent with existing code --- pico8/build/build.py | 18 +++++++++--------- pico8/tool.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/pico8/build/build.py b/pico8/build/build.py index 1e68744..6679cb6 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -132,7 +132,7 @@ def __init__(self, watchdir, callback, args): self.observer.schedule(handler, watchdir, recursive=True) self.observer.start() - util.write("Watching for changes in \"{}\"...\n".format(watchdir)) + util.write('Watching for changes in "{}"...\n'.format(watchdir)) def stop(self): self.observer.stop() @@ -150,7 +150,7 @@ def __init__(self, callback, args, **kwargs): super().__init__(**kwargs) def on_any_event(self, evt): - util.write("Change detected to {}, building...\n".format(evt.src_path)) + util.write('Change detected to {}, building...\n'.format(evt.src_path)) self.callback(self.args) def _evaluate_require(ast, file_path, package_lua, lua_path=None): @@ -336,23 +336,23 @@ def do_build(args): def do_watch(args): if not HAVE_WATCHDOG: - util.error("optional python library \"watchdog\" not installed\n" - "to use --watch, install watchdog however you see fit\n" - "using pip, that would be \"pip install watchdog\"\n") + util.error('optional python library "watchdog" not installed\n' + 'to use --watch, install watchdog however you see fit\n' + 'using pip, that would be "pip install watchdog"\n') return 1 # Check for valid directory if args.watch: if not os.path.exists(args.watch): - util.error("watch directory doesn't exist!\n") + util.error('watch directory doesn't exist!\n') return 1 if not os.path.isdir(args.watch): - util.error("watch directory isn't a directory!\n") + util.error('watch directory isn't a directory!\n') return 1 watchdir = args.filename else: - util.write("no directory specified, defaulting to current directory\n") - watchdir = "." + util.write('no directory specified, defaulting to current directory\n') + watchdir = '.' watcher = DirWatcher(watchdir, do_build, args) try: diff --git a/pico8/tool.py b/pico8/tool.py index 54e63c8..df1ade3 100644 --- a/pico8/tool.py +++ b/pico8/tool.py @@ -570,10 +570,10 @@ def _get_argparser(): '--empty-music', action='store_true', help='use an empty music region (overrides default)') sp_build.add_argument( - '--watch', type=str, nargs="?", const="", + '--watch', type=str, nargs='?', const='', help='specify a directory to watch and automatically build on changes') sp_build.add_argument( - '--watch-glob', type=str, default="*.p8,*.png,*.lua", + '--watch-glob', type=str, default='*.p8,*.png,*.lua', help='comma-separated list of globs to watch for') sp_build.add_argument( 'filename', type=str, From 7494fad5e632e83da44dd0b885dc09f56a036028 Mon Sep 17 00:00:00 2001 From: Joel Bradshaw Date: Wed, 18 Apr 2018 21:31:57 -0700 Subject: [PATCH 7/7] Escape those single quotes --- pico8/build/build.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pico8/build/build.py b/pico8/build/build.py index 6679cb6..da064fa 100644 --- a/pico8/build/build.py +++ b/pico8/build/build.py @@ -344,10 +344,10 @@ def do_watch(args): # Check for valid directory if args.watch: if not os.path.exists(args.watch): - util.error('watch directory doesn't exist!\n') + util.error('watch directory doesn\'t exist!\n') return 1 if not os.path.isdir(args.watch): - util.error('watch directory isn't a directory!\n') + util.error('watch directory isn\'t a directory!\n') return 1 watchdir = args.filename else: