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:
| M | make/__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: