PyProd 0.2.0.post1__tar.gz → 0.3.0__tar.gz
Sign up to get free protection for your applications and to get access to all the features.
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/PKG-INFO +1 -1
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/index.rst +2 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/prodfile.rst +50 -2
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/quickstart.rst +10 -10
- pyprod-0.3.0/docs/releasenotes.rst +9 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/pyproject.toml +2 -2
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/md-to-pdf/Prodfile.py +4 -5
- pyprod-0.2.0.post1/sample/s3files/PRODFILE.py → pyprod-0.3.0/sample/s3files/Prodfile.py +2 -1
- pyprod-0.3.0/sample/tutorial-1/Prodfile.py +4 -0
- pyprod-0.3.0/sample/tutorial-2/Prodfile.py +13 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/prod.py +147 -62
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/tests/test_prod.py +10 -10
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/tests/test_prodfuncs.py +4 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/tests/test_rule.py +62 -6
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/uv.lock +1 -1
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/.github/workflows/publish.yml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/.github/workflows/test.yml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/.gitignore +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/.python-version +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/.readthedocs.yaml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/LICENSE +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/README.rst +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/Makefile +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/commandline.rst +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/conf.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/make.bat +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/pyprod2.png +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/docs/requirements.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/pyprod.webp +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/pyprod2.png +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/build-c/Makefile +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/build-c/PRODFILE.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/build-c/hello.c +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/build-c/hello.h +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/build-c/main.c +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/.gitignore +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/PRODFILE.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/a.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/b.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/c.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/inc1.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/generate-doc/inc2.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/md-to-pdf/doc.md +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/md-to-pdf/md_to_html.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/md-to-pdf/template.html +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/sample/s3files/S3TEST.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/__init__.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/__main__.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/main.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/utils.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/src/pyprod/venv.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/tests/__init__.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.3.0}/tests/conftest.py +0 -0
@@ -29,7 +29,13 @@ A rule is defined using the ``@rule`` decorator, which takes the target file as
|
|
29
29
|
|
30
30
|
Build function
|
31
31
|
~~~~~~~~~~~~~~~~~~~
|
32
|
-
|
32
|
+
|
33
|
+
The function following the ``@rule`` decorator is the build function that generates the target file.
|
34
|
+
|
35
|
+
- The first argument of the build function specifies the target file to be generated.
|
36
|
+
- Subsequent arguments correspond to the filenames listed in the depends parameter. The rule function must accept the target and the same number of arguments as those specified in depends.
|
37
|
+
- ``uses`` dependencies are not passed to the build function.
|
38
|
+
- Even if Path objects are specified in the ``rule``, all arguments passed to the builder function will be of type str.
|
33
39
|
|
34
40
|
For example, the following code prints ``file1 ['file2', 'file3']`` when the target file ``file1`` is built:
|
35
41
|
|
@@ -229,6 +235,36 @@ Example:
|
|
229
235
|
msg = capture("echo Hello, World!")
|
230
236
|
|
231
237
|
|
238
|
+
.. py:function:: read(filename):
|
239
|
+
|
240
|
+
Read the contents of a file.
|
241
|
+
|
242
|
+
:param filename: The file to read.
|
243
|
+
:type filename: str | Path
|
244
|
+
|
245
|
+
:return: The contents of the file.
|
246
|
+
:rtype: str
|
247
|
+
|
248
|
+
.. py:function:: write(filename, txt, append=False):
|
249
|
+
|
250
|
+
Write text to a file.
|
251
|
+
|
252
|
+
:param filename: The file to write to.
|
253
|
+
:type filename: str | Path
|
254
|
+
|
255
|
+
:param txt: The text to write.
|
256
|
+
:type txt: str
|
257
|
+
|
258
|
+
:param append: Append to the file instead of overwriting it (default ``False``).
|
259
|
+
:type append: bool
|
260
|
+
|
261
|
+
.. py:function:: makedirs(path):
|
262
|
+
|
263
|
+
Create a directory along with any necessary parent directories if they do not already exist. This function wraps `os.makedirs() <https://docs.python.org/3/library/os.html#os.makedirs>`_ with the ``exists_ok`` parameter set to ``True``.
|
264
|
+
|
265
|
+
:param path: The directory to create.
|
266
|
+
:type path: str | Path
|
267
|
+
|
232
268
|
.. py:function:: glob(path, dir=".")
|
233
269
|
|
234
270
|
Glob the given relative pattern in the directory represented by this path. This function is a wrapper around `pathlib.Path.glob() <https://docs.python.org/3/library/pathlib.html#pathlib.Path.glob>`_. Unlike ``pathlib.Path.glob()``, this function ignores files and directlies that start with a dot. Also, this function returns a list of Path objects.
|
@@ -250,8 +286,9 @@ Example:
|
|
250
286
|
SRCFILES = glob("**/*.c")
|
251
287
|
|
252
288
|
.. py:function:: quote(s)
|
289
|
+
.. py:function:: q(s)
|
253
290
|
|
254
|
-
|
291
|
+
Convert ``s`` to string and quote for use as a shell command argument. This function is a wrapper around `shlex.quote() <https://docs.python.org/3/library/shlex.html#shlex.quote>`_.
|
255
292
|
|
256
293
|
:param s: The string to quote.
|
257
294
|
:type s: str
|
@@ -259,6 +296,17 @@ Example:
|
|
259
296
|
:return: The quoted string.
|
260
297
|
:rtype: str
|
261
298
|
|
299
|
+
.. py:function:: squote(*s)
|
300
|
+
.. py:function:: sq(*s)
|
301
|
+
|
302
|
+
Quote strings in ``s``. Each ``s`` is flattend.
|
303
|
+
|
304
|
+
:param s: The string to quote.
|
305
|
+
:type s: str | list
|
306
|
+
|
307
|
+
:return: The list of quoted strings.
|
308
|
+
:rtype: list[str]
|
309
|
+
|
262
310
|
.. py:class:: Path
|
263
311
|
|
264
312
|
A class representing file paths. This function is an alias for `pathlib.Path <https://docs.python.org/3/library/pathlib.html#pathlib.Path>`_.
|
@@ -52,16 +52,16 @@ Next, let's modify the ``Prodfile.py`` to output the file into an ``output`` dir
|
|
52
52
|
|
53
53
|
.. code-block:: python
|
54
54
|
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
55
|
+
output = Path("output") # We can use pathlib.Path without importing it
|
56
|
+
|
57
|
+
@rule(output / "hello.txt", depends=output) # hello now depends on output directory
|
58
|
+
def hello(target):
|
59
|
+
with open(target, "w") as f:
|
60
|
+
f.write("Hello, world!")
|
61
|
+
|
62
|
+
@rule(output)
|
63
|
+
def makedir(target):
|
64
|
+
os.makedirs(target)
|
65
65
|
|
66
66
|
In the modified ``Prodfile.py``, we have defined a rule to create the ``output`` directory and added a rule that makes the ``output/hello.txt`` file dependent on the ``output`` directory.
|
67
67
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "PyProd"
|
3
|
-
version = "0.
|
3
|
+
version = "0.3.0"
|
4
4
|
description = "PyProd: More Makeable than Make"
|
5
5
|
readme = "README.rst"
|
6
6
|
requires-python = ">=3.10"
|
@@ -40,4 +40,4 @@ asyncio_default_fixture_loop_scope="function"
|
|
40
40
|
|
41
41
|
[tool.ruff.lint]
|
42
42
|
select = ["E","F","W","B","N","T10","I"]
|
43
|
-
ignore = ["F401"]
|
43
|
+
ignore = ["F401", "B9"]
|
@@ -31,16 +31,15 @@ def make_pdf(target, src):
|
|
31
31
|
# Rebuilds when Python modules change
|
32
32
|
@rule(BUILD / "%.html", depends=(Path("%.md"), TEMPLATE, MODULES), uses=BUILD)
|
33
33
|
def make_html(target, src, template, *_):
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
target.write_text(html)
|
34
|
+
body = md_to_html(open(src).read())
|
35
|
+
html = open(template).read().format(body=body)
|
36
|
+
open(target, "w").write(html)
|
38
37
|
|
39
38
|
|
40
39
|
# create outputs directory
|
41
40
|
@rule(BUILD)
|
42
41
|
def builds(target):
|
43
|
-
|
42
|
+
os.makedirs(target)
|
44
43
|
|
45
44
|
|
46
45
|
def clean():
|
@@ -7,7 +7,8 @@ import boto3, botocore
|
|
7
7
|
from urllib.parse import urlparse
|
8
8
|
|
9
9
|
s3 = boto3.client("s3")
|
10
|
-
|
10
|
+
BUCKET = params.BUCKET or "TESTBUCKET" # Run pyprod with BUCKET=bucket-name
|
11
|
+
TARGET = f"s3://{BUCKET}/S3TEST.txt"
|
11
12
|
|
12
13
|
|
13
14
|
def parse_s3url(s3url):
|
@@ -0,0 +1,13 @@
|
|
1
|
+
output = Path("output") # We can use pathlib.Path without importing it
|
2
|
+
|
3
|
+
|
4
|
+
@rule(output / "hello.txt", depends=output) # hello now depends on output directory
|
5
|
+
def hello(target, *args):
|
6
|
+
# output_dir is not used
|
7
|
+
with open(target, "w") as f:
|
8
|
+
f.write("Hello, world!")
|
9
|
+
|
10
|
+
|
11
|
+
@rule(output)
|
12
|
+
def makedir(target):
|
13
|
+
os.makedirs(target)
|
@@ -33,7 +33,7 @@ class NoRuleToMakeTargetError(Exception):
|
|
33
33
|
pass
|
34
34
|
|
35
35
|
|
36
|
-
class
|
36
|
+
class RuleError(Exception):
|
37
37
|
pass
|
38
38
|
|
39
39
|
|
@@ -109,13 +109,11 @@ def glob(path, dir="."):
|
|
109
109
|
|
110
110
|
def rule_to_re(rule):
|
111
111
|
if not isinstance(rule, (str, Path)):
|
112
|
-
raise
|
112
|
+
raise RuleError(rule)
|
113
113
|
|
114
114
|
srule = str(rule)
|
115
115
|
srule = translate(srule)
|
116
116
|
srule = replace_pattern(srule, "(?P<stem>.*)", maxreplace=1)
|
117
|
-
if isinstance(rule, Path):
|
118
|
-
return Path(srule)
|
119
117
|
return srule
|
120
118
|
|
121
119
|
|
@@ -132,38 +130,97 @@ def replace_pattern(rule, replaceto, *, maxreplace=None):
|
|
132
130
|
if maxreplace is not None:
|
133
131
|
if n > maxreplace:
|
134
132
|
# contains multiple '%'
|
135
|
-
raise
|
133
|
+
raise RuleError(f"{s_rule} contains multiple '%'")
|
134
|
+
|
136
135
|
return replaceto
|
137
136
|
|
138
137
|
s_rule = re.sub("%%|%", f, s_rule)
|
139
|
-
if isinstance(rule, Path):
|
140
|
-
return Path(s_rule)
|
141
138
|
return s_rule
|
142
139
|
|
143
140
|
|
144
|
-
|
141
|
+
def _check_pattern_count(pattern):
|
142
|
+
"""Counts number of '%' in the pattern"""
|
143
|
+
matches = re.finditer(r"%%|%", pattern)
|
144
|
+
num = len([m for m in matches if len(m[0]) == 1])
|
145
|
+
if num > 1:
|
146
|
+
raise RuleError(f"{pattern}: Multiple '%' is not allowed")
|
147
|
+
return num
|
148
|
+
|
149
|
+
|
150
|
+
def _check_pattern(pattern):
|
151
|
+
matches = re.finditer(r"%%|%", pattern)
|
152
|
+
singles = [m for m in matches if len(m[0]) == 1]
|
153
|
+
if len(singles) > 1:
|
154
|
+
raise RuleError(f"{pattern}: Multiple '%' is not allowed")
|
155
|
+
if not len(singles):
|
156
|
+
raise RuleError(f"{pattern}: Pattern should contain a '%'.")
|
157
|
+
|
158
|
+
|
159
|
+
def _strip_dot(path):
|
160
|
+
if not path:
|
161
|
+
return path
|
162
|
+
path = Path(path) # ./aaa/ -> aaa
|
163
|
+
parts = path.parts
|
164
|
+
if ".." in parts:
|
165
|
+
raise RuleError(f"{path}: '..' directory is not allowed")
|
166
|
+
return str(path)
|
167
|
+
|
168
|
+
|
169
|
+
def _check_wildcard(path):
|
170
|
+
if "*" in path:
|
171
|
+
raise RuleError(f"{path}: '*' directory is not allowed")
|
172
|
+
|
173
|
+
|
145
174
|
class Rule:
|
146
|
-
targets:
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
175
|
+
def __init__(self, targets, pattern, depends, uses, builder=None):
|
176
|
+
self.targets = []
|
177
|
+
for target in flatten(targets or ()):
|
178
|
+
target = str(target)
|
179
|
+
_check_pattern_count(target)
|
180
|
+
target = _strip_dot(target)
|
181
|
+
target = rule_to_re(target)
|
182
|
+
self.targets.append(target)
|
183
|
+
|
184
|
+
self.first_target = None
|
185
|
+
for target in flatten(targets or ()):
|
186
|
+
target = str(target)
|
187
|
+
if not target:
|
188
|
+
continue
|
189
|
+
|
190
|
+
if "*" in target:
|
191
|
+
continue
|
192
|
+
|
193
|
+
if _check_pattern_count(target) == 0:
|
194
|
+
# not contain one %
|
195
|
+
self.first_target = target
|
196
|
+
break
|
197
|
+
|
198
|
+
if pattern:
|
199
|
+
pattern = _strip_dot(pattern)
|
200
|
+
if _check_pattern_count(pattern) != 1:
|
201
|
+
raise RuleError(f"{pattern}: Pattern should contain a '%'")
|
202
|
+
|
203
|
+
self.pattern = rule_to_re(pattern)
|
204
|
+
else:
|
205
|
+
self.pattern = None
|
206
|
+
|
207
|
+
self.depends = []
|
208
|
+
for depend in flatten(depends or ()):
|
209
|
+
depend = str(depend)
|
210
|
+
_check_pattern_count(depend)
|
211
|
+
_check_wildcard(depend)
|
212
|
+
depend = _strip_dot(depend)
|
213
|
+
self.depends.append(depend)
|
214
|
+
|
215
|
+
self.uses = []
|
216
|
+
for use in flatten(uses or ()):
|
217
|
+
use = str(use)
|
218
|
+
_check_pattern_count(use)
|
219
|
+
_check_wildcard(use)
|
220
|
+
use = _strip_dot(use)
|
221
|
+
self.uses.append(use)
|
222
|
+
|
223
|
+
self.builder = builder
|
167
224
|
|
168
225
|
def __call__(self, f):
|
169
226
|
self.builder = f
|
@@ -211,12 +268,15 @@ class Rules:
|
|
211
268
|
stem = d
|
212
269
|
elif dep.pattern:
|
213
270
|
m = re.fullmatch(str(dep.pattern), name)
|
214
|
-
|
215
|
-
|
216
|
-
stem = d
|
271
|
+
if m:
|
272
|
+
stem = m.groupdict().get("stem", None)
|
217
273
|
|
218
|
-
|
219
|
-
|
274
|
+
if stem is not None:
|
275
|
+
depends = [replace_pattern(r, stem) for r in dep.depends]
|
276
|
+
uses = [replace_pattern(r, stem) for r in dep.uses]
|
277
|
+
else:
|
278
|
+
depends = dep.depends[:]
|
279
|
+
uses = dep.uses[:]
|
220
280
|
|
221
281
|
yield depends, uses, dep
|
222
282
|
break
|
@@ -237,14 +297,8 @@ class Rules:
|
|
237
297
|
|
238
298
|
def select_first_target(self):
|
239
299
|
for dep in self.rules:
|
240
|
-
|
241
|
-
|
242
|
-
# pattern?
|
243
|
-
matches = re.finditer(r"%%|%", s_target)
|
244
|
-
if any((len(m[0]) == 1) for m in matches):
|
245
|
-
# has %
|
246
|
-
continue
|
247
|
-
return target
|
300
|
+
if dep.first_target:
|
301
|
+
return dep.first_target
|
248
302
|
|
249
303
|
def select_builder(self, name):
|
250
304
|
for depends, uses, dep in self.iter_rule(name):
|
@@ -356,6 +410,30 @@ class Envs:
|
|
356
410
|
return os.environ.get(name, default=default)
|
357
411
|
|
358
412
|
|
413
|
+
def read(filename):
|
414
|
+
with open(filename, "r") as f:
|
415
|
+
return f.read()
|
416
|
+
|
417
|
+
|
418
|
+
def write(filename, s, append=False):
|
419
|
+
mode = "a" if append else "w"
|
420
|
+
with open(filename, mode) as f:
|
421
|
+
f.write(s)
|
422
|
+
|
423
|
+
|
424
|
+
def quote(s):
|
425
|
+
return shlex.quote(str(s))
|
426
|
+
|
427
|
+
|
428
|
+
def squote(*s):
|
429
|
+
ret = [shlex.quote(str(x)) for x in flatten(s)]
|
430
|
+
return ret
|
431
|
+
|
432
|
+
|
433
|
+
def makedirs(path):
|
434
|
+
os.makedirs(path, exist_ok=True)
|
435
|
+
|
436
|
+
|
359
437
|
class Prod:
|
360
438
|
def __init__(self, modulefile, njobs=1, params=None):
|
361
439
|
self.modulefile = Path(modulefile)
|
@@ -372,18 +450,25 @@ class Prod:
|
|
372
450
|
|
373
451
|
def get_module_globals(self):
|
374
452
|
globals = {
|
453
|
+
"capture": capture,
|
454
|
+
"check": self.checkers.check,
|
455
|
+
"environ": Envs(),
|
456
|
+
"glob": glob,
|
457
|
+
"makedirs": makedirs,
|
458
|
+
"os": os,
|
459
|
+
"params": self.params,
|
375
460
|
"pip": pip,
|
461
|
+
"quote": quote,
|
462
|
+
"q": quote,
|
463
|
+
"squote": squote,
|
464
|
+
"sq": squote,
|
465
|
+
"read": read,
|
376
466
|
"rule": self.rules.rule,
|
377
|
-
"check": self.checkers.check,
|
378
|
-
"Path": Path,
|
379
467
|
"run": run,
|
380
|
-
"capture": capture,
|
381
|
-
"glob": glob,
|
382
|
-
"MAX_TS": MAX_TS,
|
383
|
-
"environ": Envs(),
|
384
468
|
"shutil": shutil,
|
385
|
-
"
|
386
|
-
"
|
469
|
+
"write": write,
|
470
|
+
"MAX_TS": MAX_TS,
|
471
|
+
"Path": Path,
|
387
472
|
}
|
388
473
|
return globals
|
389
474
|
|
@@ -432,13 +517,13 @@ class Prod:
|
|
432
517
|
if isinstance(obj, str | Path):
|
433
518
|
builds.append(obj)
|
434
519
|
elif isinstance(obj, Rule):
|
435
|
-
raise
|
520
|
+
raise RuleError(obj)
|
436
521
|
elif callable(obj):
|
437
522
|
self.built += 1
|
438
523
|
task = asyncio.create_task(self.run_in_executor(obj))
|
439
524
|
waitings.append(task)
|
440
525
|
else:
|
441
|
-
raise
|
526
|
+
raise RuleError(obj)
|
442
527
|
|
443
528
|
await self.build(builds)
|
444
529
|
await asyncio.gather(*waitings)
|
@@ -497,11 +582,11 @@ class Prod:
|
|
497
582
|
return Exists(name, True, ret)
|
498
583
|
|
499
584
|
async def run(self, name): # -> Any | int:
|
500
|
-
|
585
|
+
name = str(name)
|
501
586
|
|
502
|
-
self.rules.build_tree(
|
503
|
-
deps, uses = self.rules.get_dep_names(
|
504
|
-
selected = self.rules.select_builder(
|
587
|
+
self.rules.build_tree(name)
|
588
|
+
deps, uses = self.rules.get_dep_names(name)
|
589
|
+
selected = self.rules.select_builder(name)
|
505
590
|
if selected:
|
506
591
|
build_deps, build_uses, builder = selected
|
507
592
|
deps = deps + build_deps
|
@@ -513,19 +598,19 @@ class Prod:
|
|
513
598
|
if uses:
|
514
599
|
await self.build(uses)
|
515
600
|
|
516
|
-
exists = await self.is_exists(
|
601
|
+
exists = await self.is_exists(name)
|
517
602
|
|
518
603
|
if not exists.exists:
|
519
|
-
logger.debug("%r does not exists",
|
604
|
+
logger.debug("%r does not exists", name)
|
520
605
|
elif (ts >= MAX_TS) or (exists.ts < ts):
|
521
|
-
logger.debug("%r should be updated",
|
606
|
+
logger.debug("%r should be updated", name)
|
522
607
|
else:
|
523
|
-
logger.debug("%r already exists",
|
608
|
+
logger.debug("%r already exists", name)
|
524
609
|
|
525
610
|
if not exists.exists and not selected:
|
526
|
-
raise NoRuleToMakeTargetError(
|
611
|
+
raise NoRuleToMakeTargetError(name)
|
527
612
|
elif selected and ((not exists.exists) or (ts >= MAX_TS) or (exists.ts < ts)):
|
528
|
-
logger.warning("building: %r",
|
613
|
+
logger.warning("building: %r", name)
|
529
614
|
await self.run_in_executor(builder.builder, name, *build_deps)
|
530
615
|
self.built += 1
|
531
616
|
return MAX_TS
|
@@ -88,19 +88,19 @@ async def test_pattern(tmp_path):
|
|
88
88
|
src = """
|
89
89
|
@rule(target=("a.o", "b.o"), pattern=Path("%.o"), depends=Path("%.c"))
|
90
90
|
def build(target, src):
|
91
|
-
assert isinstance(target,
|
91
|
+
assert isinstance(target, str)
|
92
92
|
Path(target).write_text(str(target))
|
93
93
|
|
94
94
|
@rule(target=Path("%.c"))
|
95
95
|
def build_c(target):
|
96
|
-
assert isinstance(target,
|
96
|
+
assert isinstance(target, str)
|
97
97
|
Path(target).write_text(str(target))
|
98
98
|
|
99
99
|
@rule(Path("app.exe"), depends=(Path("a.o"), Path("b.o")))
|
100
100
|
def build_app(target, a, b):
|
101
|
-
assert isinstance(target,
|
102
|
-
assert isinstance(a,
|
103
|
-
assert isinstance(b,
|
101
|
+
assert isinstance(target, str)
|
102
|
+
assert isinstance(a, str)
|
103
|
+
assert isinstance(b, str)
|
104
104
|
Path(target).write_text(f"{target}, {a}, {b}")
|
105
105
|
|
106
106
|
all = Path("app.exe")
|
@@ -124,19 +124,19 @@ async def test_preserve_pathobj(tmp_path):
|
|
124
124
|
src = """
|
125
125
|
@rule(target=Path("%.o"), depends=Path("%.c"))
|
126
126
|
def build(target, src):
|
127
|
-
assert isinstance(target,
|
127
|
+
assert isinstance(target, str)
|
128
128
|
Path(target).write_text("a")
|
129
129
|
|
130
130
|
@rule(target=Path("%.c"))
|
131
131
|
def build_c(target):
|
132
|
-
assert isinstance(target,
|
132
|
+
assert isinstance(target, str)
|
133
133
|
Path(target).write_text(str(target))
|
134
134
|
|
135
135
|
@rule(Path("app.exe"), depends=Path("a.o"))
|
136
136
|
def build_app(target, src):
|
137
|
-
assert isinstance(target,
|
138
|
-
assert isinstance(src,
|
139
|
-
target.write_text("app.exe")
|
137
|
+
assert isinstance(target, str)
|
138
|
+
assert isinstance(src, str)
|
139
|
+
Path(target).write_text("app.exe")
|
140
140
|
|
141
141
|
all = Path("app.exe")
|
142
142
|
"""
|
@@ -1,3 +1,5 @@
|
|
1
|
+
import re
|
2
|
+
|
1
3
|
import pytest
|
2
4
|
|
3
5
|
from pyprod import prod
|
@@ -91,20 +93,74 @@ def test_stem_escape():
|
|
91
93
|
|
92
94
|
|
93
95
|
def test_stem_error():
|
94
|
-
with pytest.raises(ValueError):
|
95
|
-
raise ValueError("s;dlf,")
|
96
|
-
|
97
96
|
rules = prod.Rules()
|
98
97
|
|
99
|
-
with pytest.raises(prod.
|
98
|
+
with pytest.raises(prod.RuleError):
|
100
99
|
|
101
100
|
@rules.rule(target="%.%", depends="%.c")
|
102
101
|
def f():
|
103
102
|
pass
|
104
103
|
|
105
|
-
@rules.rule(target="%.xxx", depends="
|
104
|
+
@rules.rule(target="%.xxx", depends="%")
|
106
105
|
def f():
|
107
106
|
pass
|
108
107
|
|
109
108
|
deps, _, _ = rules.select_builder("abc.xxx")
|
110
|
-
assert deps == ["abc
|
109
|
+
assert deps == ["abc"]
|
110
|
+
|
111
|
+
|
112
|
+
def test_validate_target():
|
113
|
+
with pytest.raises(prod.RuleError):
|
114
|
+
prod.Rule("%.%", "", "", "")
|
115
|
+
|
116
|
+
with pytest.raises(prod.RuleError):
|
117
|
+
prod.Rule("../aaa", "", "", "")
|
118
|
+
|
119
|
+
with pytest.raises(prod.RuleError):
|
120
|
+
rule = prod.Rule("../aaa/", "", "", "")
|
121
|
+
|
122
|
+
rule = prod.Rule("./aaa/", None, None, None)
|
123
|
+
assert re.fullmatch(rule.targets[0], "aaa")
|
124
|
+
|
125
|
+
|
126
|
+
def test_validate_pattern():
|
127
|
+
with pytest.raises(prod.RuleError):
|
128
|
+
prod.Rule("a.b", "%.%", "", "")
|
129
|
+
|
130
|
+
with pytest.raises(prod.RuleError):
|
131
|
+
prod.Rule("a.b", "../%.b", "", "")
|
132
|
+
|
133
|
+
with pytest.raises(prod.RuleError):
|
134
|
+
rule = prod.Rule("a.b", "../a.%", "", "")
|
135
|
+
|
136
|
+
with pytest.raises(prod.RuleError):
|
137
|
+
rule = prod.Rule("a.b", "a.b", "", "")
|
138
|
+
|
139
|
+
rule = prod.Rule("a.b", "./a.%", None, None)
|
140
|
+
assert re.fullmatch(rule.pattern, "a.b")
|
141
|
+
|
142
|
+
|
143
|
+
def test_validate_depends():
|
144
|
+
with pytest.raises(prod.RuleError):
|
145
|
+
prod.Rule("a.%", None, "%.%", "")
|
146
|
+
|
147
|
+
with pytest.raises(prod.RuleError):
|
148
|
+
prod.Rule("a.%", None, "*/x.y", "")
|
149
|
+
|
150
|
+
with pytest.raises(prod.RuleError):
|
151
|
+
prod.Rule("a.%", None, "../x.y", "")
|
152
|
+
|
153
|
+
prod.Rule("a.b", None, "x.y", "")
|
154
|
+
|
155
|
+
|
156
|
+
def test_validate_uses():
|
157
|
+
with pytest.raises(prod.RuleError):
|
158
|
+
prod.Rule("a.%", None, "", "%.%")
|
159
|
+
|
160
|
+
with pytest.raises(prod.RuleError):
|
161
|
+
prod.Rule("a.%", None, "", "*/x.y")
|
162
|
+
|
163
|
+
with pytest.raises(prod.RuleError):
|
164
|
+
prod.Rule("a.%", None, "", "../x.y")
|
165
|
+
|
166
|
+
prod.Rule("a.b", None, "x.y", "")
|
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
|