PyProd 0.2.0__py3-none-any.whl → 0.3.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pyprod/prod.py +147 -62
- {pyprod-0.2.0.dist-info → pyprod-0.3.0.dist-info}/METADATA +2 -2
- {pyprod-0.2.0.dist-info → pyprod-0.3.0.dist-info}/RECORD +6 -6
- {pyprod-0.2.0.dist-info → pyprod-0.3.0.dist-info}/WHEEL +0 -0
- {pyprod-0.2.0.dist-info → pyprod-0.3.0.dist-info}/entry_points.txt +0 -0
- {pyprod-0.2.0.dist-info → pyprod-0.3.0.dist-info}/licenses/LICENSE +0 -0
pyprod/prod.py
CHANGED
@@ -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
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: PyProd
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.3.0
|
4
4
|
Summary: PyProd: More Makeable than Make
|
5
5
|
Project-URL: Homepage, https://github.com/atsuoishimoto/pyprod
|
6
6
|
Project-URL: Documentation, https://pyprod.readthedocs.io/en/latest/
|
@@ -17,7 +17,7 @@ Description-Content-Type: text/x-rst
|
|
17
17
|
PyProd - More Makeable than Make
|
18
18
|
=================================
|
19
19
|
|
20
|
-
PyProd is a Python script that can be used as an alternative to Makefile. By leveraging Python's versatility, it enables you to define build rules and dependencies programmatically, allowing for dynamic configurations, integration with existing Python libraries, and custom build logic not easily achievable with traditional Makefiles. For detailed documentation, please refer to the `official documentation <https://
|
20
|
+
PyProd is a Python script that can be used as an alternative to Makefile. By leveraging Python's versatility, it enables you to define build rules and dependencies programmatically, allowing for dynamic configurations, integration with existing Python libraries, and custom build logic not easily achievable with traditional Makefiles. For detailed documentation, please refer to the `official documentation <https://pyprod.readthedocs.io/en/latest/>`_.
|
21
21
|
|
22
22
|
|
23
23
|
Features
|
@@ -1,11 +1,11 @@
|
|
1
1
|
pyprod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
2
|
pyprod/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
|
3
3
|
pyprod/main.py,sha256=nS0mcLy3Ix7ydnX1Pu5g7ceLzhh6DndwJWO5sjO_qWg,2580
|
4
|
-
pyprod/prod.py,sha256=
|
4
|
+
pyprod/prod.py,sha256=r2e-JSHiTw2SOcv-tYFx4aRpVkQbOb5j9vEVrjT0meE,16694
|
5
5
|
pyprod/utils.py,sha256=oiiUkSbeqTazbtJ6gz7ZKqG1OvAeV-nV9u_3Y0DCOOM,401
|
6
6
|
pyprod/venv.py,sha256=_riw56YQvUOSd55u_1m9ElsqPdjM5qVvIZP6dr9Fzt4,1051
|
7
|
-
pyprod-0.
|
8
|
-
pyprod-0.
|
9
|
-
pyprod-0.
|
10
|
-
pyprod-0.
|
11
|
-
pyprod-0.
|
7
|
+
pyprod-0.3.0.dist-info/METADATA,sha256=t9TMYeBveYE1LFZNpcYJAtOdUo7gPARFHmIZ9p0bnag,2477
|
8
|
+
pyprod-0.3.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
pyprod-0.3.0.dist-info/entry_points.txt,sha256=zFycf8BYSMRDTiI0jftmcvtkf9XM4MZ4BL3JaIer_ZM,44
|
10
|
+
pyprod-0.3.0.dist-info/licenses/LICENSE,sha256=OtPgwnlLrsVEYPnTraun5AqftAT5vUv4rIan-qYj7nE,1071
|
11
|
+
pyprod-0.3.0.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|