pymake

A build system based on Build Systems à la Carte
git clone https://git.grace.moe/pymake
Log | Files | Refs | README

commit 863b3ac8c051665a9503c3e577c52e334b42b468
parent 793fe8c5fccee20a5da4ccea27e8423fe66f453f
Author: gracefu <81774659+gracefuu@users.noreply.github.com>
Date:   Thu, 17 Apr 2025 01:53:31 +0800

Improve my understanding of coroutine vs Future vs futurelike and CLI to allow building rules

Diffstat:
Mmake/__init__.py | 70++++++++++++++++++++++++++++++++++++----------------------------------
1 file changed, 36 insertions(+), 34 deletions(-)

diff --git a/make/__init__.py b/make/__init__.py @@ -70,7 +70,7 @@ from typing import ( class Fetch(Protocol): """Protocol defining the fetch operation used by tasks.""" - async def __call__(self, task: "Task") -> Any: ... + async def __call__(self, task_or_rule: "Task | Rule") -> Any: ... RuleKey = bytes @@ -156,7 +156,14 @@ class Rule: return Task( ( self.rule_key, - *(arg.task_key if isinstance(arg, Task) else arg for arg in args), + *( + ( + arg.task_key + if isinstance(arg, Task) + else arg().task_key if isinstance(arg, Rule) else arg + ) + for arg in args + ), ), self.rule_fn, *args, @@ -171,6 +178,12 @@ class Rule: return self.hash +def ensure_task(task_or_rule: Task | Rule) -> Task: + if isinstance(task_or_rule, Rule): + return task_or_rule() + return task_or_rule + + def singleton(cls): cls.main = cls() return cls @@ -249,7 +262,8 @@ class Rules: return rule @staticmethod - async def track_fetch(fetch: Fetch, new_inputs: list, task: Task): + async def track_fetch(fetch: Fetch, new_inputs: list, task_or_rule: Task | Rule): + task = ensure_task(task_or_rule) result = await fetch(task) new_inputs.append((task.task_key, make_hash(result))) return result @@ -341,26 +355,19 @@ class Detach: def __init__(self): self._background_tasks = set() - @staticmethod - async def _coro(fut): - return await fut - - def __call__(self, *args, **kwargs): - awaitable, *args = args - if not hasattr(awaitable, "send"): - # the awaitable is NOT a coroutine, wrap it into one - awaitable = Detach._coro(awaitable) - task = asyncio.create_task(awaitable, *args, **kwargs) - self._background_tasks.add(task) - task.add_done_callback(self._background_tasks.discard) - return task + def __call__(self, awaitable): + if asyncio.coroutines.iscoroutine(awaitable): + task = asyncio.create_task(awaitable) + self._background_tasks.add(task) + task.add_done_callback(self._background_tasks.discard) + return task + return awaitable async def wait(self): while self._background_tasks: - await asyncio.gather(*self._background_tasks) - self._background_tasks = set( - (t for t in self._background_tasks if not t.done()) - ) + t = self._background_tasks.pop() + if not t.done(): + await t detach = Detach() @@ -380,7 +387,8 @@ class SuspendingScheduler: def build(self, *tasks: Task): return asyncio.gather(*(self.fetch_once(task) for task in tasks)) - async def fetch_once(self, task: Task): + async def fetch_once(self, task_or_rule: Task | Rule): + task = ensure_task(task_or_rule) task_key = task.task_key wait = None event = None @@ -483,18 +491,20 @@ async def exec( if input is not None: proc.stdin.write(input) # type: ignore - _, stdout, stderr = await asyncio.gather( + _, stdout, stderr, returncode = await asyncio.gather( proc.stdin.drain(), # type: ignore _exec_reader(proc.stdout, sys.stdout.buffer, echo=echo & EchoStdout), _exec_reader(proc.stderr, sys.stderr.buffer, echo=echo & EchoStderr), + proc.wait(), ) else: - stdout, stderr = await asyncio.gather( + stdout, stderr, returncode = await asyncio.gather( _exec_reader(proc.stdout, sys.stdout.buffer, echo=echo & EchoStdout), _exec_reader(proc.stderr, sys.stderr.buffer, echo=echo & EchoStderr), + proc.wait(), ) - return ShellResult(stdout, stderr, proc.returncode) + return ShellResult(stdout, stderr, returncode) async def shell( @@ -523,18 +533,10 @@ def main(globals, filename=".makedb", default_target="all"): targets = sys.argv[1:] if not targets: targets.append(default_target) - actual_targets: list[Task] = [] - for target in targets: - if "(" in target: - actual_targets.append(eval(target, globals=globals)) # type: ignore - else: - actual_targets.append(eval(target + "()", globals=globals)) # type: ignore - - async def async_main(actual_targets): - await asyncio.gather(*(build(target) for target in actual_targets)) with Build(filename) as build: - return asyncio.run(async_main(actual_targets)) + asyncio.run(build(*(eval(target, globals=globals) for target in targets))) + return 0 # class AsyncWrapperSpec: