Change layout and use watchinotify
* Change code layout to a flat layout * Remove pyright configuration * Add watchinotify as optional dependency to use the watch option * Fix watch option to watch for all dependencies (headers)
This commit is contained in:
parent
933dea2b9e
commit
b31e3b8426
7 changed files with 29 additions and 173 deletions
2
make.py
2
make.py
|
|
@ -1,6 +1,6 @@
|
||||||
#! python3
|
#! python3
|
||||||
|
|
||||||
from umake.umake import make, Config
|
from umake import make, Config
|
||||||
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
|
|
|
||||||
7
makefile
7
makefile
|
|
@ -1,13 +1,12 @@
|
||||||
MAKE_PATH="."
|
|
||||||
UMAKE_PATH="umake"
|
UMAKE_PATH="umake"
|
||||||
|
|
||||||
.PHONY: all debug clean
|
.PHONY: all debug clean
|
||||||
|
|
||||||
all:
|
all:
|
||||||
@PYTHONPATH=$(UMAKE_PATH) python3 $(MAKE_PATH)/make.py
|
@PYTHONPATH=$(UMAKE_PATH) python3 make.py
|
||||||
|
|
||||||
debug:
|
debug:
|
||||||
@PYTHONPATH=$(UMAKE_PATH) python3 $(MAKE_PATH)/make.py --type debug
|
@PYTHONPATH=$(UMAKE_PATH) python3 make.py --type debug
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
@PYTHONPATH=$(UMAKE_PATH) python3 $(MAKE_PATH)/make.py --clean
|
@PYTHONPATH=$(UMAKE_PATH) python3 make.py --clean
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,9 @@ name = 'umake'
|
||||||
version = '1.0.0'
|
version = '1.0.0'
|
||||||
requires-python = ">=3.8"
|
requires-python = ">=3.8"
|
||||||
|
|
||||||
|
[project.optional-dependencies]
|
||||||
|
watch = ["watchinotify==0.2"]
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
cache-dir = "/tmp/ruff"
|
cache-dir = "/tmp/ruff"
|
||||||
exclude = [
|
exclude = [
|
||||||
|
|
@ -51,6 +54,3 @@ lines_after_imports = 2
|
||||||
multi_line_output = 4
|
multi_line_output = 4
|
||||||
no_sections = false
|
no_sections = false
|
||||||
order_by_type = true
|
order_by_type = true
|
||||||
|
|
||||||
[tool.pyright]
|
|
||||||
exclude = ["**/__pycache__", "**/.venv", "**/services", "web_front"]
|
|
||||||
|
|
|
||||||
3
umake/__init__.py
Normal file
3
umake/__init__.py
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
from .core import get_hash, make, Config
|
||||||
|
|
||||||
|
__all__ = ['get_hash', 'make', 'Config']
|
||||||
|
|
@ -135,25 +135,30 @@ class Builder:
|
||||||
self._populate_todo_dict()
|
self._populate_todo_dict()
|
||||||
|
|
||||||
if self._config.watch:
|
if self._config.watch:
|
||||||
from watch import Watcher, WatchFlag # ruff:ignore[import-outside-top-level]
|
from watchinotify import Event, FileEvent, FolderEvent, Watcher # ruff:ignore[import-outside-top-level]
|
||||||
|
|
||||||
def callback(path: Path, _: WatchFlag):
|
self._compile_sources()
|
||||||
|
watcher = Watcher()
|
||||||
|
|
||||||
|
def callback(path: Path | None, event: Event, _name: bytes):
|
||||||
|
print(f'File changed {path=} {event=}')
|
||||||
|
if path is None:
|
||||||
|
return
|
||||||
source_hash = get_hash(path)
|
source_hash = get_hash(path)
|
||||||
if path not in self._hash_dict or source_hash != self._hash_dict[path]:
|
if path not in self._hash_dict or source_hash != self._hash_dict[path]:
|
||||||
print(f'{ConsoleColor.ORANGE}Source file changed : {path} {ConsoleColor.ENDCOLOR}')
|
print(f'{ConsoleColor.ORANGE}Source file changed : {path} {ConsoleColor.ENDCOLOR}')
|
||||||
self._populate_todo_dict()
|
self._populate_todo_dict()
|
||||||
self._compile_sources()
|
self._compile_sources()
|
||||||
print(f'{ConsoleColor.GREEN}Watching for changes{ConsoleColor.ENDCOLOR}')
|
print(f'{ConsoleColor.GREEN}Watching for changes ({len(watcher.watched_paths())} files){ConsoleColor.ENDCOLOR}')
|
||||||
|
|
||||||
self._compile_sources()
|
watcher.callback = callback
|
||||||
print(f'{ConsoleColor.GREEN}Watching for changes{ConsoleColor.ENDCOLOR}')
|
with watcher:
|
||||||
watcher = Watcher()
|
watcher.watch(self._hash_dict)
|
||||||
for source_path in self._compile_dict:
|
print(f'{ConsoleColor.GREEN}Watching for changes ({len(watcher.watched_paths())} files){ConsoleColor.ENDCOLOR}')
|
||||||
watcher.register(source_path, WatchFlag.MODIFY, callback)
|
try:
|
||||||
try:
|
watcher._thread.join()
|
||||||
watcher.watch()
|
except KeyboardInterrupt:
|
||||||
except KeyboardInterrupt:
|
print('\rExit')
|
||||||
print('\rExit')
|
|
||||||
elif not self._compile_sources():
|
elif not self._compile_sources():
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
|
|
@ -191,7 +196,7 @@ class Builder:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Running compilation processes
|
# Running compilation processes
|
||||||
if not self._config.object_dir.exists:
|
if not self._config.object_dir.exists():
|
||||||
self._config.object_dir.mkdir(parents=True)
|
self._config.object_dir.mkdir(parents=True)
|
||||||
error_paths: list[tuple[Path, str]] = []
|
error_paths: list[tuple[Path, str]] = []
|
||||||
if self._config.job_count > 1: # Multi-process
|
if self._config.job_count > 1: # Multi-process
|
||||||
151
watch.py
151
watch.py
|
|
@ -1,151 +0,0 @@
|
||||||
import ctypes
|
|
||||||
import ctypes.util
|
|
||||||
from enum import IntFlag
|
|
||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
import select
|
|
||||||
from struct import unpack, calcsize
|
|
||||||
from typing import Callable
|
|
||||||
|
|
||||||
|
|
||||||
class WatchFlag(IntFlag):
|
|
||||||
# Supported events suitable for MASK parameter of INOTIFY_ADD_WATCH.
|
|
||||||
ACCESS = 0x00000001 # File was accessed
|
|
||||||
MODIFY = 0x00000002 # File was modified
|
|
||||||
ATTRIB = 0x00000004 # Metadata changed
|
|
||||||
CLOSE_WRITE = 0x00000008 # Writtable file was closed
|
|
||||||
CLOSE_NOWRITE = 0x00000010 # Unwrittable file closed
|
|
||||||
# CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE) # Close
|
|
||||||
OPEN = 0x00000020 # File was opened
|
|
||||||
MOVED_FROM = 0x00000040 # File was moved from X
|
|
||||||
MOVED_TO = 0x00000080 # File was moved to Y
|
|
||||||
# MOVE = (MOVED_FROM | MOVED_TO) # Moves
|
|
||||||
CREATE = 0x00000100 # Subfile was created
|
|
||||||
DELETE = 0x00000200 # Subfile was deleted
|
|
||||||
DELETE_SELF = 0x00000400 # Self was deleted
|
|
||||||
MOVE_SELF = 0x00000800 # Self was moved
|
|
||||||
|
|
||||||
# Events sent by the kernel.
|
|
||||||
UNMOUNT = 0x00002000 # Backing fs was unmounted
|
|
||||||
Q_OVERFLOW = 0x00004000 # Event queued overflowed
|
|
||||||
IGNORED = 0x00008000 # File was ignored
|
|
||||||
|
|
||||||
# Helper events.
|
|
||||||
# CLOSE = (CLOSE_WRITE | CLOSE_NOWRITE) # Close
|
|
||||||
# MOVE = (MOVED_FROM | MOVED_TO) # Moves
|
|
||||||
|
|
||||||
# Special flags.
|
|
||||||
ONLYDIR = 0x01000000 # Only watch the path if it is a directory
|
|
||||||
DONT_FOLLOW = 0x02000000 # Do not follow a sym link
|
|
||||||
EXCL_UNLINK = 0x04000000 # Exclude events on unlinked objects
|
|
||||||
MASK_CREATE = 0x10000000 # Only create watches
|
|
||||||
MASK_ADD = 0x20000000 # Add to the mask of an already existing watch
|
|
||||||
ISDIR = 0x40000000 # Event occurred against dir
|
|
||||||
ONESHOT = 0x80000000 # Only send event once
|
|
||||||
|
|
||||||
# All events which a program can wait on.
|
|
||||||
# ALL_EVENTS = (
|
|
||||||
# ACCESS | MODIFY | ATTRIB | CLOSE_WRITE | CLOSE_NOWRITE | OPEN | MOVED_FROM | MOVED_TO
|
|
||||||
# | CREATE | DELETE | DELETE_SELF | MOVE_SELF)
|
|
||||||
|
|
||||||
|
|
||||||
class InotifyError(Exception):
|
|
||||||
def __init__(self, message, *args, **kwargs):
|
|
||||||
message += f' ERRNO=({ctypes.get_errno()})'
|
|
||||||
super().__init__(message, *args, **kwargs)
|
|
||||||
|
|
||||||
|
|
||||||
WatcherCallback = Callable[[Path, WatchFlag], None]
|
|
||||||
|
|
||||||
|
|
||||||
class Watcher:
|
|
||||||
_EVENT_FORMAT = 'iIII'
|
|
||||||
_EVENT_SIZE = calcsize(_EVENT_FORMAT)
|
|
||||||
|
|
||||||
def __init__(self):
|
|
||||||
libc_path = ctypes.util.find_library('c')
|
|
||||||
if libc_path is None:
|
|
||||||
libc_path = 'libc.so.6'
|
|
||||||
self._instance = ctypes.cdll.LoadLibrary(libc_path)
|
|
||||||
|
|
||||||
self._inotify_init = self._instance.inotify_init
|
|
||||||
self._inotify_init.argtypes = []
|
|
||||||
self._inotify_init.restype = self._check_nonnegative
|
|
||||||
|
|
||||||
self._inotify_add_watch = self._instance.inotify_add_watch
|
|
||||||
self._inotify_add_watch.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_uint32]
|
|
||||||
self._inotify_add_watch.restype = self._check_nonnegative
|
|
||||||
|
|
||||||
self._inotify_rm_watch = self._instance.inotify_rm_watch
|
|
||||||
self._inotify_rm_watch.argtypes = [ctypes.c_int, ctypes.c_int]
|
|
||||||
self._inotify_rm_watch.restype = self._check_nonnegative
|
|
||||||
|
|
||||||
self._inotify_fd = self._inotify_init()
|
|
||||||
|
|
||||||
self._poll = select.poll()
|
|
||||||
self._poll.register(self._inotify_fd, select.POLLIN)
|
|
||||||
|
|
||||||
self._watch_fds: dict[Path, int] = {}
|
|
||||||
self._watch_info: dict[int, tuple[Path, WatcherCallback]] = {}
|
|
||||||
|
|
||||||
def register(self, path: Path, flags: WatchFlag, callback: WatcherCallback):
|
|
||||||
if path in self._watch_fds:
|
|
||||||
self._inotify_rm_watch(self._inotify_fd, self._watch_fds[path])
|
|
||||||
|
|
||||||
watch_fd = self._inotify_add_watch(self._inotify_fd, str(path).encode(), flags)
|
|
||||||
self._watch_fds[path] = watch_fd
|
|
||||||
self._watch_info[watch_fd] = (path, callback)
|
|
||||||
|
|
||||||
def unregister(self, path: Path):
|
|
||||||
watch_fd = self._watch_fds[path]
|
|
||||||
self._inotify_rm_watch(self._inotify_fd, watch_fd)
|
|
||||||
del self._watch_fds[path]
|
|
||||||
del self._watch_info[watch_fd]
|
|
||||||
|
|
||||||
def watch(self):
|
|
||||||
while True:
|
|
||||||
results = self._poll.poll()
|
|
||||||
for poll_fd, _ in results:
|
|
||||||
watch_buffer = os.read(poll_fd, self._EVENT_SIZE)
|
|
||||||
if len(watch_buffer) < self._EVENT_SIZE:
|
|
||||||
continue
|
|
||||||
watch_fd, mask, _, _ = unpack(self._EVENT_FORMAT, watch_buffer)
|
|
||||||
watch_path, callback = self._watch_info[watch_fd]
|
|
||||||
callback(watch_path, WatchFlag(mask))
|
|
||||||
|
|
||||||
def __del__(self):
|
|
||||||
self._poll.unregister(self._inotify_fd)
|
|
||||||
for watch_fd in self._watch_fds.values():
|
|
||||||
self._inotify_rm_watch(self._inotify_fd, watch_fd)
|
|
||||||
os.close(self._inotify_fd)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def _check_nonnegative(result):
|
|
||||||
if result == -1:
|
|
||||||
raise InotifyError(f'Call failed (should not be -1): {result}')
|
|
||||||
return result
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
from argparse import ArgumentParser # ruff:ignore[import-outside-top-level]
|
|
||||||
|
|
||||||
parser = ArgumentParser()
|
|
||||||
parser.add_argument('paths', nargs='*', type=Path)
|
|
||||||
arguments = parser.parse_args()
|
|
||||||
|
|
||||||
paths: list[Path] = arguments.paths
|
|
||||||
|
|
||||||
def callback(path: Path, _: WatchFlag):
|
|
||||||
print(f'{path} has been modified')
|
|
||||||
|
|
||||||
watcher = Watcher()
|
|
||||||
for path in paths:
|
|
||||||
watcher.register(path, WatchFlag.MODIFY, callback)
|
|
||||||
try:
|
|
||||||
watcher.watch()
|
|
||||||
except KeyboardInterrupt:
|
|
||||||
print('\rExit')
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue