PyProd 0.6.0__tar.gz → 0.8.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pyprod-0.6.0 → pyprod-0.8.0}/PKG-INFO +1 -1
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/releasenotes.rst +11 -0
- pyprod-0.8.0/src/pyprod/__init__.py +1 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/prod.py +48 -9
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_prod.py +95 -1
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_rule.py +0 -3
- pyprod-0.6.0/src/pyprod/__init__.py +0 -1
- {pyprod-0.6.0 → pyprod-0.8.0}/.github/workflows/publish.yml +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/.github/workflows/test.yml +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/.gitignore +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/LICENSE +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/README.rst +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/Makefile +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/commandline.rst +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/conf.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/index.rst +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/make.bat +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/prodfile.rst +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/pyprod2.png +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/quickstart.rst +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/docs/requirements.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/pyprod.webp +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/pyprod2.png +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/pyproject.toml +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/Makefile +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/hello.c +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/hello.h +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/main.c +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/a.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/b.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/c.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/inc1.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/inc2.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/doc.md +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/md_to_html.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/template.html +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/s3files/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/s3files/S3TEST.txt +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/tutorial-1/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/samples/tutorial-2/Prodfile.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/__main__.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/main.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/utils.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/venv.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/__init__.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/conftest.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_prodfuncs.py +0 -0
- {pyprod-0.6.0 → pyprod-0.8.0}/tests/utils.py +0 -0
@@ -1,6 +1,17 @@
|
|
1
1
|
Release Notes
|
2
2
|
================
|
3
3
|
|
4
|
+
0.8.0 (2025-4-15)
|
5
|
+
-----------------------------
|
6
|
+
|
7
|
+
- Allow functions in the ``depends`` and ``uses``.
|
8
|
+
|
9
|
+
|
10
|
+
0.7.0 (2025-4-6)
|
11
|
+
-----------------------------
|
12
|
+
|
13
|
+
- Allow wildcard in the ``depends``.
|
14
|
+
|
4
15
|
0.6.0 (2025-4-6)
|
5
16
|
-----------------------------
|
6
17
|
|
@@ -0,0 +1 @@
|
|
1
|
+
__version__ = "0.8.0"
|
@@ -17,7 +17,9 @@ from dataclasses import dataclass, field
|
|
17
17
|
from fnmatch import fnmatch, translate
|
18
18
|
from functools import wraps
|
19
19
|
from pathlib import Path
|
20
|
+
|
20
21
|
import dateutil.parser
|
22
|
+
|
21
23
|
import pyprod
|
22
24
|
|
23
25
|
from .utils import flatten, unique_list
|
@@ -164,7 +166,7 @@ def _check_pattern(pattern):
|
|
164
166
|
|
165
167
|
def _check_wildcard(path):
|
166
168
|
if "*" in path:
|
167
|
-
raise RuleError(f"{path}: '*'
|
169
|
+
raise RuleError(f"{path}: '*' is not allowed")
|
168
170
|
|
169
171
|
|
170
172
|
def _name_to_str(name):
|
@@ -183,6 +185,25 @@ def _name_to_str(name):
|
|
183
185
|
return name
|
184
186
|
|
185
187
|
|
188
|
+
def _expand_glob(name):
|
189
|
+
if not isinstance(name, (str, Path)):
|
190
|
+
return name
|
191
|
+
|
192
|
+
if "*" not in str(name):
|
193
|
+
return name
|
194
|
+
|
195
|
+
# split path to support absolute path
|
196
|
+
p = Path(name)
|
197
|
+
parts = p.parts
|
198
|
+
for i, part in enumerate(parts):
|
199
|
+
if "*" in part:
|
200
|
+
root = Path(*parts[:i])
|
201
|
+
rest = "/".join(parts[i:])
|
202
|
+
break
|
203
|
+
|
204
|
+
return list(root.glob(rest))
|
205
|
+
|
206
|
+
|
186
207
|
class Rule:
|
187
208
|
def __init__(self, targets, pattern=None, depends=(), uses=(), builder=None):
|
188
209
|
self.targets = []
|
@@ -219,9 +240,9 @@ class Rule:
|
|
219
240
|
if not depend:
|
220
241
|
continue
|
221
242
|
|
222
|
-
|
223
|
-
|
224
|
-
|
243
|
+
if not callable(depend):
|
244
|
+
depend = _name_to_str(depend)
|
245
|
+
_check_pattern_count(depend)
|
225
246
|
self.depends.append(depend)
|
226
247
|
|
227
248
|
self.uses = []
|
@@ -229,9 +250,10 @@ class Rule:
|
|
229
250
|
if not use:
|
230
251
|
continue
|
231
252
|
|
232
|
-
|
233
|
-
|
234
|
-
|
253
|
+
if not callable(use):
|
254
|
+
use = _name_to_str(use)
|
255
|
+
_check_pattern_count(use)
|
256
|
+
_check_wildcard(use)
|
235
257
|
self.uses.append(use)
|
236
258
|
|
237
259
|
self.builder = builder
|
@@ -345,13 +367,30 @@ class Rules:
|
|
345
367
|
if m:
|
346
368
|
stem = m.groupdict().get("stem", None)
|
347
369
|
|
370
|
+
depends = []
|
371
|
+
for d in dep.depends:
|
372
|
+
if callable(d):
|
373
|
+
ret = flatten([d(name, stem)], ignore_none=True)
|
374
|
+
depends.extend(ret)
|
375
|
+
else:
|
376
|
+
depends.append(d)
|
377
|
+
|
378
|
+
uses = []
|
379
|
+
for u in dep.uses:
|
380
|
+
if callable(u):
|
381
|
+
ret = flatten([u(name, stem)], ignore_none=True)
|
382
|
+
uses.extend(ret)
|
383
|
+
else:
|
384
|
+
uses.append(u)
|
385
|
+
|
348
386
|
if stem is not None:
|
349
|
-
depends = [replace_pattern(r, stem) for r in
|
350
|
-
uses = [replace_pattern(r, stem) for r in
|
387
|
+
depends = [replace_pattern(r, stem) for r in depends]
|
388
|
+
uses = [replace_pattern(r, stem) for r in uses]
|
351
389
|
else:
|
352
390
|
depends = dep.depends[:]
|
353
391
|
uses = dep.uses[:]
|
354
392
|
|
393
|
+
depends = list(flatten(_expand_glob(depend) for depend in depends))
|
355
394
|
yield depends, uses, dep
|
356
395
|
break
|
357
396
|
|
@@ -1,3 +1,4 @@
|
|
1
|
+
import json
|
1
2
|
import os
|
2
3
|
from contextlib import contextmanager
|
3
4
|
from pathlib import Path
|
@@ -95,7 +96,7 @@ def build_app(target, a, b):
|
|
95
96
|
Path(target).write_text(f"{target}, {a}, {b}")
|
96
97
|
"""
|
97
98
|
|
98
|
-
|
99
|
+
(tmp_path / "Prodfile.py").write_text(src)
|
99
100
|
|
100
101
|
with chdir(tmp_path):
|
101
102
|
p = prod.Prod("Prodfile.py", 4)
|
@@ -108,6 +109,99 @@ def build_app(target, a, b):
|
|
108
109
|
assert (tmp_path / "b.c").read_text() == "b.c"
|
109
110
|
|
110
111
|
|
112
|
+
@pytest.mark.asyncio
|
113
|
+
async def test_dep_glob(tmp_path):
|
114
|
+
src = """
|
115
|
+
@rule(targets=("%.o"), depends=Path("deps/%/*/dep"))
|
116
|
+
def build(target, *deps):
|
117
|
+
assert isinstance(target, str)
|
118
|
+
p = Path(target)
|
119
|
+
assert set(deps) == {Path("deps/aaa/bbb/dep"), Path("deps/aaa/ccc/dep")}
|
120
|
+
p.write_text(repr([str(p) for p in deps]))
|
121
|
+
"""
|
122
|
+
|
123
|
+
(tmp_path / "Prodfile.py").write_text(src)
|
124
|
+
|
125
|
+
(tmp_path / "deps/aaa/bbb/").mkdir(parents=True, exist_ok=True)
|
126
|
+
(tmp_path / "deps/aaa/bbb/dep").write_text("1")
|
127
|
+
|
128
|
+
(tmp_path / "deps/aaa/ccc/").mkdir(parents=True, exist_ok=True)
|
129
|
+
(tmp_path / "deps/aaa/ccc/dep").write_text("2")
|
130
|
+
|
131
|
+
(tmp_path / "deps/aaa/ddd/").mkdir(parents=True, exist_ok=True)
|
132
|
+
|
133
|
+
with chdir(tmp_path):
|
134
|
+
p = prod.Prod("Prodfile.py")
|
135
|
+
await p.start(["aaa.o"])
|
136
|
+
print((tmp_path / "aaa.o").read_text())
|
137
|
+
|
138
|
+
|
139
|
+
@pytest.mark.asyncio
|
140
|
+
async def test_dep_glob_no_match(tmp_path):
|
141
|
+
src = """
|
142
|
+
@rule(targets=("%.o"), depends=Path("deps/%/*/dep"))
|
143
|
+
def build(target, *deps):
|
144
|
+
assert isinstance(target, str)
|
145
|
+
p = Path(target)
|
146
|
+
p.write_text(str(target)+"-"+str(deps))
|
147
|
+
"""
|
148
|
+
|
149
|
+
(tmp_path / "Prodfile.py").write_text(src)
|
150
|
+
with chdir(tmp_path):
|
151
|
+
p = prod.Prod("Prodfile.py")
|
152
|
+
await p.start(["aaa.o"])
|
153
|
+
assert (tmp_path / "aaa.o").read_text() == "aaa.o-()"
|
154
|
+
|
155
|
+
|
156
|
+
@pytest.mark.asyncio
|
157
|
+
async def test_dep_callable(tmp_path):
|
158
|
+
src = """
|
159
|
+
def dep1(target, stem):
|
160
|
+
return target+".dep", stem
|
161
|
+
|
162
|
+
@rule(targets=("%.o"), depends=dep1)
|
163
|
+
def build(target, *deps):
|
164
|
+
print(">>>>>>>>>>>>>>>>>", target, deps)
|
165
|
+
assert deps == ("aaa.o.dep", "aaa")
|
166
|
+
"""
|
167
|
+
|
168
|
+
(tmp_path / "Prodfile.py").write_text(src)
|
169
|
+
(tmp_path / "aaa.o.dep").write_text("1")
|
170
|
+
(tmp_path / "aaa").write_text("2")
|
171
|
+
|
172
|
+
with chdir(tmp_path):
|
173
|
+
p = prod.Prod("Prodfile.py")
|
174
|
+
await p.start(["aaa.o"])
|
175
|
+
|
176
|
+
|
177
|
+
@pytest.mark.asyncio
|
178
|
+
async def test_uses_callable(tmp_path):
|
179
|
+
src = """
|
180
|
+
def call1(target, stem):
|
181
|
+
return "call1-name"
|
182
|
+
|
183
|
+
def call2(target, stem):
|
184
|
+
return "call2-name"
|
185
|
+
|
186
|
+
@rule(targets=("%.o"), uses=(call1, call2))
|
187
|
+
def build(target, *deps):
|
188
|
+
print(">>>>>>>>>>>>>>>>>", target, deps)
|
189
|
+
"""
|
190
|
+
|
191
|
+
(tmp_path / "Prodfile.py").write_text(src)
|
192
|
+
(tmp_path / "call1-name").write_text("1")
|
193
|
+
|
194
|
+
with chdir(tmp_path):
|
195
|
+
p = prod.Prod("Prodfile.py")
|
196
|
+
with pytest.raises(prod.NoRuleToMakeTargetError):
|
197
|
+
await p.start(["aaa.o"])
|
198
|
+
|
199
|
+
(tmp_path / "call2-name").write_text("2")
|
200
|
+
with chdir(tmp_path):
|
201
|
+
p = prod.Prod("Prodfile.py")
|
202
|
+
await p.start(["aaa.o"])
|
203
|
+
|
204
|
+
|
111
205
|
@pytest.mark.asyncio
|
112
206
|
async def test_preserve_pathobj(tmp_path):
|
113
207
|
src = """
|
@@ -1 +0,0 @@
|
|
1
|
-
__version__ = "0.6.0"
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|