PyProd 0.2.0.post1__py3-none-any.whl → 0.4.0__py3-none-any.whl
Sign up to get free protection for your applications and to get access to all the features.
- pyprod/main.py +19 -7
- pyprod/prod.py +270 -121
- pyprod/utils.py +2 -2
- pyprod/venv.py +3 -3
- {pyprod-0.2.0.post1.dist-info → pyprod-0.4.0.dist-info}/METADATA +12 -6
- pyprod-0.4.0.dist-info/RECORD +11 -0
- pyprod-0.2.0.post1.dist-info/RECORD +0 -11
- {pyprod-0.2.0.post1.dist-info → pyprod-0.4.0.dist-info}/WHEEL +0 -0
- {pyprod-0.2.0.post1.dist-info → pyprod-0.4.0.dist-info}/entry_points.txt +0 -0
- {pyprod-0.2.0.post1.dist-info → pyprod-0.4.0.dist-info}/licenses/LICENSE +0 -0
pyprod/main.py
CHANGED
@@ -14,6 +14,13 @@ parser = argparse.ArgumentParser(
|
|
14
14
|
description="""PyProd - More makable than make""",
|
15
15
|
)
|
16
16
|
|
17
|
+
parser.add_argument(
|
18
|
+
"-C",
|
19
|
+
"--directory",
|
20
|
+
dest="directory",
|
21
|
+
help="Change to DIRECTORY before performing any operations",
|
22
|
+
)
|
23
|
+
|
17
24
|
parser.add_argument(
|
18
25
|
"-f", "--file", help="Use FILE as the Prodfile (default: 'PRODFILE.py')"
|
19
26
|
)
|
@@ -27,12 +34,8 @@ parser.add_argument(
|
|
27
34
|
)
|
28
35
|
|
29
36
|
parser.add_argument(
|
30
|
-
"-
|
31
|
-
"--directory",
|
32
|
-
dest="directory",
|
33
|
-
help="Change to DIRECTORY before performing any operations",
|
37
|
+
"-r", "--rebuild", dest="rebuild", action="store_true", help="Rebuild all"
|
34
38
|
)
|
35
|
-
|
36
39
|
parser.add_argument(
|
37
40
|
"-v",
|
38
41
|
dest="verbose",
|
@@ -55,8 +58,13 @@ def print_exc(e):
|
|
55
58
|
logger.exception("Terminated by exception")
|
56
59
|
|
57
60
|
|
61
|
+
def init_args(args=None):
|
62
|
+
args = pyprod.args = parser.parse_args(args)
|
63
|
+
return args
|
64
|
+
|
65
|
+
|
58
66
|
def main():
|
59
|
-
args =
|
67
|
+
args = init_args()
|
60
68
|
pyprod.verbose = args.verbose
|
61
69
|
chdir = args.directory
|
62
70
|
if chdir:
|
@@ -89,6 +97,7 @@ def main():
|
|
89
97
|
|
90
98
|
params = {}
|
91
99
|
targets = []
|
100
|
+
|
92
101
|
for target in args.targets:
|
93
102
|
if "=" in target:
|
94
103
|
name, value = target.split("=", 1)
|
@@ -107,7 +116,10 @@ def main():
|
|
107
116
|
sys.exit("No default target")
|
108
117
|
targets = [target]
|
109
118
|
|
110
|
-
ret =
|
119
|
+
ret = 0
|
120
|
+
for target in targets:
|
121
|
+
ret += asyncio.run(prod.start([target]))
|
122
|
+
|
111
123
|
if not ret:
|
112
124
|
print(f"Nothing to be done for {targets}")
|
113
125
|
|
pyprod/prod.py
CHANGED
@@ -33,7 +33,11 @@ class NoRuleToMakeTargetError(Exception):
|
|
33
33
|
pass
|
34
34
|
|
35
35
|
|
36
|
-
class
|
36
|
+
class RuleError(Exception):
|
37
|
+
pass
|
38
|
+
|
39
|
+
|
40
|
+
class TargetError(Exception):
|
37
41
|
pass
|
38
42
|
|
39
43
|
|
@@ -109,13 +113,11 @@ def glob(path, dir="."):
|
|
109
113
|
|
110
114
|
def rule_to_re(rule):
|
111
115
|
if not isinstance(rule, (str, Path)):
|
112
|
-
raise
|
116
|
+
raise TypeError(f"str or Path required: {rule}")
|
113
117
|
|
114
118
|
srule = str(rule)
|
115
119
|
srule = translate(srule)
|
116
120
|
srule = replace_pattern(srule, "(?P<stem>.*)", maxreplace=1)
|
117
|
-
if isinstance(rule, Path):
|
118
|
-
return Path(srule)
|
119
121
|
return srule
|
120
122
|
|
121
123
|
|
@@ -132,44 +134,145 @@ def replace_pattern(rule, replaceto, *, maxreplace=None):
|
|
132
134
|
if maxreplace is not None:
|
133
135
|
if n > maxreplace:
|
134
136
|
# contains multiple '%'
|
135
|
-
raise
|
137
|
+
raise RuleError(f"{s_rule} contains multiple '%'")
|
138
|
+
|
136
139
|
return replaceto
|
137
140
|
|
138
141
|
s_rule = re.sub("%%|%", f, s_rule)
|
139
|
-
if isinstance(rule, Path):
|
140
|
-
return Path(s_rule)
|
141
142
|
return s_rule
|
142
143
|
|
143
144
|
|
144
|
-
|
145
|
+
def _check_pattern_count(pattern):
|
146
|
+
"""Counts number of '%' in the pattern"""
|
147
|
+
matches = re.finditer(r"%%|%", pattern)
|
148
|
+
num = len([m for m in matches if len(m[0]) == 1])
|
149
|
+
if num > 1:
|
150
|
+
raise RuleError(f"{pattern}: Multiple '%' is not allowed")
|
151
|
+
return num
|
152
|
+
|
153
|
+
|
154
|
+
def _check_pattern(pattern):
|
155
|
+
matches = re.finditer(r"%%|%", pattern)
|
156
|
+
singles = [m for m in matches if len(m[0]) == 1]
|
157
|
+
if len(singles) > 1:
|
158
|
+
raise RuleError(f"{pattern}: Multiple '%' is not allowed")
|
159
|
+
if not len(singles):
|
160
|
+
raise RuleError(f"{pattern}: Pattern should contain a '%'.")
|
161
|
+
|
162
|
+
|
163
|
+
def _check_wildcard(path):
|
164
|
+
if "*" in path:
|
165
|
+
raise RuleError(f"{path}: '*' directory is not allowed")
|
166
|
+
|
167
|
+
|
168
|
+
def _name_to_str(name):
|
169
|
+
match name:
|
170
|
+
case Task():
|
171
|
+
return name.name
|
172
|
+
case _TaskFunc():
|
173
|
+
return name.name
|
174
|
+
case Path():
|
175
|
+
return str(name)
|
176
|
+
case str():
|
177
|
+
return name
|
178
|
+
|
179
|
+
return name
|
180
|
+
|
181
|
+
|
145
182
|
class Rule:
|
146
|
-
targets:
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
183
|
+
def __init__(self, targets, pattern=None, depends=(), uses=(), builder=None):
|
184
|
+
self.targets = []
|
185
|
+
self.first_target = None
|
186
|
+
if targets:
|
187
|
+
for target in flatten(targets):
|
188
|
+
if not target:
|
189
|
+
continue
|
190
|
+
target = str(target)
|
191
|
+
if not target:
|
192
|
+
continue
|
193
|
+
|
194
|
+
if not self.first_target:
|
195
|
+
if "*" not in target:
|
196
|
+
if _check_pattern_count(target) == 0:
|
197
|
+
# not contain one %
|
198
|
+
self.first_target = target
|
199
|
+
|
200
|
+
target = rule_to_re(target)
|
201
|
+
self.targets.append(target)
|
202
|
+
|
203
|
+
if pattern:
|
204
|
+
pattern = str(pattern)
|
205
|
+
if _check_pattern_count(pattern) != 1:
|
206
|
+
raise RuleError(f"{pattern}: Pattern should contain a '%'")
|
207
|
+
|
208
|
+
self.pattern = rule_to_re(pattern)
|
209
|
+
else:
|
210
|
+
self.pattern = None
|
211
|
+
|
212
|
+
self.depends = []
|
213
|
+
for depend in flatten(depends or ()):
|
214
|
+
depend = _name_to_str(depend)
|
215
|
+
_check_pattern_count(depend)
|
216
|
+
_check_wildcard(depend)
|
217
|
+
self.depends.append(depend)
|
218
|
+
|
219
|
+
self.uses = []
|
220
|
+
for use in flatten(uses or ()):
|
221
|
+
use = _name_to_str(use)
|
222
|
+
_check_pattern_count(use)
|
223
|
+
_check_wildcard(use)
|
224
|
+
self.uses.append(use)
|
225
|
+
|
226
|
+
self.builder = builder
|
167
227
|
|
168
228
|
def __call__(self, f):
|
169
229
|
self.builder = f
|
170
230
|
return f
|
171
231
|
|
172
232
|
|
233
|
+
class _TaskFunc:
|
234
|
+
def __init__(self, f, name):
|
235
|
+
self.f = f
|
236
|
+
self.name = name
|
237
|
+
|
238
|
+
def __call__(self, *args, **kwargs):
|
239
|
+
return self.f(*args, **kwargs)
|
240
|
+
|
241
|
+
|
242
|
+
def default_builder(self, *args, **kwargs):
|
243
|
+
# default builder
|
244
|
+
pass
|
245
|
+
|
246
|
+
|
247
|
+
class Task(Rule):
|
248
|
+
def __init__(self, name, depends, uses, func=None):
|
249
|
+
super().__init__((), pattern=None, depends=depends, uses=uses, builder=func)
|
250
|
+
self.name = _name_to_str(name)
|
251
|
+
if name:
|
252
|
+
self.targets = [name]
|
253
|
+
self.first_target = self.name
|
254
|
+
if func:
|
255
|
+
self._set_funcname(func)
|
256
|
+
if not self.builder:
|
257
|
+
self.builder = default_builder
|
258
|
+
|
259
|
+
def _set_funcname(self, f):
|
260
|
+
if not self.name:
|
261
|
+
if not f.__name__ or f.__name__ == "<lambda>":
|
262
|
+
raise RuleError(
|
263
|
+
"Task function should have a name. Use @task(name='name')"
|
264
|
+
)
|
265
|
+
self.name = f.__name__
|
266
|
+
self.targets = [f.__name__]
|
267
|
+
|
268
|
+
self.first_target = self.name
|
269
|
+
|
270
|
+
def __call__(self, f):
|
271
|
+
self.builder = f
|
272
|
+
self._set_funcname(f)
|
273
|
+
return _TaskFunc(f, self.name)
|
274
|
+
|
275
|
+
|
173
276
|
class Rules:
|
174
277
|
def __init__(self):
|
175
278
|
self.rules = []
|
@@ -177,52 +280,66 @@ class Rules:
|
|
177
280
|
self._detect_loop = set()
|
178
281
|
self.frozen = False
|
179
282
|
|
180
|
-
def add_rule(self, targets, pattern, depends, uses, builder):
|
283
|
+
def add_rule(self, targets, pattern=None, depends=(), uses=(), builder=None):
|
284
|
+
if builder:
|
285
|
+
if not callable(builder):
|
286
|
+
raise ValueError(f"{builder} is not callable")
|
287
|
+
|
181
288
|
if self.frozen:
|
182
289
|
raise RuntimeError("No new rule can be added after initialization")
|
183
290
|
|
184
|
-
dep = Rule(
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
291
|
+
dep = Rule(targets, pattern, depends, uses, builder)
|
292
|
+
self.rules.append(dep)
|
293
|
+
return dep
|
294
|
+
|
295
|
+
def add_task(self, name=None, depends=(), uses=(), func=None):
|
296
|
+
if self.frozen:
|
297
|
+
raise RuntimeError("No new rule can be added after initialization")
|
298
|
+
dep = Task(name, depends, uses, func)
|
191
299
|
self.rules.append(dep)
|
192
300
|
return dep
|
193
301
|
|
194
|
-
def rule(self,
|
195
|
-
if
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
dep
|
302
|
+
def rule(self, targets, *, pattern=None, depends=(), uses=()):
|
303
|
+
if not targets:
|
304
|
+
raise ValueError("No target specified")
|
305
|
+
|
306
|
+
dep = self.add_rule([targets], pattern, depends, uses, None)
|
307
|
+
return dep
|
308
|
+
|
309
|
+
def task(self, func=None, *, name=None, depends=(), uses=()):
|
310
|
+
if func:
|
311
|
+
if not callable(func):
|
312
|
+
raise ValueError(f"{func} is not callable")
|
313
|
+
|
314
|
+
dep = self.add_task(name, depends, uses, func)
|
200
315
|
return dep
|
201
316
|
|
202
317
|
def iter_rule(self, name):
|
203
|
-
name =
|
318
|
+
name = _name_to_str(name)
|
204
319
|
for dep in self.rules:
|
205
320
|
for target in dep.targets:
|
206
|
-
m = re.fullmatch(
|
321
|
+
m = re.fullmatch(target, name)
|
207
322
|
if m:
|
208
323
|
stem = None
|
209
324
|
d = m.groupdict().get("stem", None)
|
210
325
|
if d is not None:
|
211
326
|
stem = d
|
212
327
|
elif dep.pattern:
|
213
|
-
m = re.fullmatch(
|
214
|
-
|
215
|
-
|
216
|
-
stem = d
|
328
|
+
m = re.fullmatch(dep.pattern, name)
|
329
|
+
if m:
|
330
|
+
stem = m.groupdict().get("stem", None)
|
217
331
|
|
218
|
-
|
219
|
-
|
332
|
+
if stem is not None:
|
333
|
+
depends = [replace_pattern(r, stem) for r in dep.depends]
|
334
|
+
uses = [replace_pattern(r, stem) for r in dep.uses]
|
335
|
+
else:
|
336
|
+
depends = dep.depends[:]
|
337
|
+
uses = dep.uses[:]
|
220
338
|
|
221
339
|
yield depends, uses, dep
|
222
340
|
break
|
223
341
|
|
224
342
|
def get_dep_names(self, name):
|
225
|
-
assert name
|
226
343
|
ret_depends = []
|
227
344
|
ret_uses = []
|
228
345
|
|
@@ -237,14 +354,8 @@ class Rules:
|
|
237
354
|
|
238
355
|
def select_first_target(self):
|
239
356
|
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
|
357
|
+
if dep.first_target:
|
358
|
+
return dep.first_target
|
248
359
|
|
249
360
|
def select_builder(self, name):
|
250
361
|
for depends, uses, dep in self.iter_rule(name):
|
@@ -253,11 +364,13 @@ class Rules:
|
|
253
364
|
return depends, uses, dep
|
254
365
|
|
255
366
|
def build_tree(self, name, lv=1):
|
367
|
+
assert name
|
256
368
|
self.frozen = True
|
257
369
|
|
258
|
-
name =
|
370
|
+
name = _name_to_str(name)
|
259
371
|
if name in self._detect_loop:
|
260
|
-
raise CircularReferenceError(name)
|
372
|
+
raise CircularReferenceError(f"Circular reference detected: {name}")
|
373
|
+
|
261
374
|
self._detect_loop.add(name)
|
262
375
|
try:
|
263
376
|
if name in self.tree:
|
@@ -272,7 +385,7 @@ class Rules:
|
|
272
385
|
depends.extend(build_uses)
|
273
386
|
|
274
387
|
depends = unique_list(depends)
|
275
|
-
self.tree[name].update(
|
388
|
+
self.tree[name].update(depends)
|
276
389
|
for dep in depends:
|
277
390
|
self.build_tree(dep, lv=lv + 1)
|
278
391
|
|
@@ -285,17 +398,19 @@ class Checkers:
|
|
285
398
|
self.checkers = []
|
286
399
|
|
287
400
|
def get_checker(self, name):
|
401
|
+
name = _name_to_str(name)
|
288
402
|
for targets, f in self.checkers:
|
289
403
|
for target in targets:
|
290
|
-
if fnmatch(name,
|
404
|
+
if fnmatch(name, target):
|
291
405
|
return f
|
292
406
|
|
293
|
-
def add_check(self,
|
294
|
-
|
407
|
+
def add_check(self, targets, f):
|
408
|
+
targets = list(map(_name_to_str, flatten(targets or ())))
|
409
|
+
self.checkers.append((targets, f))
|
295
410
|
|
296
|
-
def check(self,
|
411
|
+
def check(self, targets):
|
297
412
|
def deco(f):
|
298
|
-
self.add_check(
|
413
|
+
self.add_check(targets, f)
|
299
414
|
return f
|
300
415
|
|
301
416
|
return deco
|
@@ -356,9 +471,38 @@ class Envs:
|
|
356
471
|
return os.environ.get(name, default=default)
|
357
472
|
|
358
473
|
|
474
|
+
def read(filename):
|
475
|
+
with open(filename, "r") as f:
|
476
|
+
return f.read()
|
477
|
+
|
478
|
+
|
479
|
+
def write(filename, s, append=False):
|
480
|
+
mode = "a" if append else "w"
|
481
|
+
with open(filename, mode) as f:
|
482
|
+
f.write(s)
|
483
|
+
|
484
|
+
|
485
|
+
def quote(*s):
|
486
|
+
ret = [shlex.quote(str(x)) for x in flatten(s)]
|
487
|
+
return ret
|
488
|
+
|
489
|
+
|
490
|
+
def squote(s):
|
491
|
+
s = " ".join(str(e) for e in flatten(s))
|
492
|
+
return shlex.quote(s)
|
493
|
+
|
494
|
+
|
495
|
+
def makedirs(path):
|
496
|
+
os.makedirs(path, exist_ok=True)
|
497
|
+
|
498
|
+
|
359
499
|
class Prod:
|
360
500
|
def __init__(self, modulefile, njobs=1, params=None):
|
361
|
-
|
501
|
+
if modulefile:
|
502
|
+
self.modulefile = Path(modulefile)
|
503
|
+
else:
|
504
|
+
self.modulefile = None
|
505
|
+
|
362
506
|
self.rules = Rules()
|
363
507
|
self.checkers = Checkers()
|
364
508
|
if njobs > 1:
|
@@ -367,23 +511,34 @@ class Prod:
|
|
367
511
|
self.executor = None
|
368
512
|
self.params = Params(params)
|
369
513
|
self.buildings = {}
|
370
|
-
self.module =
|
514
|
+
self.module = None
|
515
|
+
if self.modulefile:
|
516
|
+
self.module = self.load_pyprodfile(self.modulefile)
|
371
517
|
self.built = 0 # number of build execused
|
372
518
|
|
373
519
|
def get_module_globals(self):
|
374
520
|
globals = {
|
521
|
+
"build": self.build,
|
522
|
+
"capture": capture,
|
523
|
+
"check": self.checkers.check,
|
524
|
+
"environ": Envs(),
|
525
|
+
"glob": glob,
|
526
|
+
"makedirs": makedirs,
|
527
|
+
"os": os,
|
528
|
+
"params": self.params,
|
375
529
|
"pip": pip,
|
530
|
+
"quote": quote,
|
531
|
+
"q": quote,
|
532
|
+
"squote": squote,
|
533
|
+
"sq": squote,
|
534
|
+
"read": read,
|
376
535
|
"rule": self.rules.rule,
|
377
|
-
"check": self.checkers.check,
|
378
|
-
"Path": Path,
|
379
536
|
"run": run,
|
380
|
-
"capture": capture,
|
381
|
-
"glob": glob,
|
382
|
-
"MAX_TS": MAX_TS,
|
383
|
-
"environ": Envs(),
|
384
537
|
"shutil": shutil,
|
385
|
-
"
|
386
|
-
"
|
538
|
+
"task": self.rules.task,
|
539
|
+
"write": write,
|
540
|
+
"MAX_TS": MAX_TS,
|
541
|
+
"Path": Path,
|
387
542
|
}
|
388
543
|
return globals
|
389
544
|
|
@@ -404,47 +559,28 @@ class Prod:
|
|
404
559
|
async def run_in_executor(self, func, *args, **kwargs):
|
405
560
|
if self.executor:
|
406
561
|
loop = asyncio.get_running_loop()
|
407
|
-
|
562
|
+
ret = await loop.run_in_executor(
|
408
563
|
self.executor, lambda: func(*args, **kwargs)
|
409
564
|
)
|
410
565
|
else:
|
411
|
-
|
566
|
+
ret = func(*args, **kwargs)
|
567
|
+
|
568
|
+
return ret
|
412
569
|
|
413
570
|
def get_default_target(self):
|
414
571
|
return self.rules.select_first_target()
|
415
572
|
|
416
573
|
async def start(self, deps):
|
574
|
+
self.loop = asyncio.get_running_loop()
|
417
575
|
self.built = 0
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
if value:
|
423
|
-
names.append(value)
|
424
|
-
else:
|
425
|
-
names.append(name)
|
426
|
-
else:
|
427
|
-
names.append(name)
|
428
|
-
|
429
|
-
builds = []
|
430
|
-
waitings = []
|
431
|
-
for obj in flatten(names):
|
432
|
-
if isinstance(obj, str | Path):
|
433
|
-
builds.append(obj)
|
434
|
-
elif isinstance(obj, Rule):
|
435
|
-
raise InvalidRuleError(obj)
|
436
|
-
elif callable(obj):
|
437
|
-
self.built += 1
|
438
|
-
task = asyncio.create_task(self.run_in_executor(obj))
|
439
|
-
waitings.append(task)
|
440
|
-
else:
|
441
|
-
raise InvalidRuleError(obj)
|
576
|
+
self.deps = deps[:]
|
577
|
+
while self.deps:
|
578
|
+
dep = self.deps.pop(0)
|
579
|
+
await self.schedule([dep])
|
442
580
|
|
443
|
-
await self.build(builds)
|
444
|
-
await asyncio.gather(*waitings)
|
445
581
|
return self.built
|
446
582
|
|
447
|
-
async def
|
583
|
+
async def schedule(self, deps):
|
448
584
|
tasks = []
|
449
585
|
waits = []
|
450
586
|
for dep in deps:
|
@@ -496,12 +632,14 @@ class Prod:
|
|
496
632
|
ret = MAX_TS
|
497
633
|
return Exists(name, True, ret)
|
498
634
|
|
499
|
-
|
500
|
-
|
635
|
+
def build(self, *deps):
|
636
|
+
self.deps[0:0] = [_name_to_str(name) for name in flatten(deps)]
|
501
637
|
|
502
|
-
|
503
|
-
|
504
|
-
|
638
|
+
async def run(self, name): # -> Any | int:
|
639
|
+
name = _name_to_str(name)
|
640
|
+
self.rules.build_tree(name)
|
641
|
+
deps, uses = self.rules.get_dep_names(name)
|
642
|
+
selected = self.rules.select_builder(name)
|
505
643
|
if selected:
|
506
644
|
build_deps, build_uses, builder = selected
|
507
645
|
deps = deps + build_deps
|
@@ -509,23 +647,34 @@ class Prod:
|
|
509
647
|
|
510
648
|
ts = 0
|
511
649
|
if deps:
|
512
|
-
ts = await self.
|
650
|
+
ts = await self.schedule(deps)
|
513
651
|
if uses:
|
514
|
-
await self.
|
652
|
+
await self.schedule(uses)
|
653
|
+
|
654
|
+
if selected and isinstance(builder, Task):
|
655
|
+
self.built += 1
|
656
|
+
await self.run_in_executor(builder.builder, *build_deps)
|
657
|
+
return MAX_TS
|
515
658
|
|
516
|
-
exists = await self.is_exists(
|
659
|
+
exists = await self.is_exists(name)
|
517
660
|
|
518
661
|
if not exists.exists:
|
519
|
-
logger.debug("%r does not exists",
|
662
|
+
logger.debug("%r does not exists", name)
|
520
663
|
elif (ts >= MAX_TS) or (exists.ts < ts):
|
521
|
-
logger.debug("%r should be updated",
|
664
|
+
logger.debug("%r should be updated", name)
|
522
665
|
else:
|
523
|
-
logger.debug("%r already exists",
|
666
|
+
logger.debug("%r already exists", name)
|
524
667
|
|
525
668
|
if not exists.exists and not selected:
|
526
|
-
raise NoRuleToMakeTargetError(
|
527
|
-
|
528
|
-
|
669
|
+
raise NoRuleToMakeTargetError(f"No rule to make target: {name}")
|
670
|
+
|
671
|
+
elif selected and (
|
672
|
+
(not exists.exists)
|
673
|
+
or (ts >= MAX_TS)
|
674
|
+
or (exists.ts < ts)
|
675
|
+
or pyprod.args.rebuild
|
676
|
+
):
|
677
|
+
logger.warning("building: %r", name)
|
529
678
|
await self.run_in_executor(builder.builder, name, *build_deps)
|
530
679
|
self.built += 1
|
531
680
|
return MAX_TS
|
pyprod/utils.py
CHANGED
pyprod/venv.py
CHANGED
@@ -11,15 +11,15 @@ import pyprod
|
|
11
11
|
from .utils import flatten
|
12
12
|
|
13
13
|
THREADID = threading.get_ident()
|
14
|
-
pyprodenv = ".pyprod"
|
15
14
|
venvdir = None
|
16
15
|
|
17
|
-
PYPRODVENV = "
|
16
|
+
PYPRODVENV = "pyprod"
|
18
17
|
|
19
18
|
|
20
19
|
def makevenv(conffile):
|
21
20
|
global venvdir
|
22
|
-
|
21
|
+
major, minor = sys.version_info[:2]
|
22
|
+
venvdir = Path(".") / f".{conffile.name}.{major}.{minor}.{PYPRODVENV}"
|
23
23
|
if not venvdir.is_dir():
|
24
24
|
venv.main([str(venvdir)])
|
25
25
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: PyProd
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.4.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://pyprod.readthedocs.io/en/
|
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/stable/>`_.
|
21
21
|
|
22
22
|
|
23
23
|
Features
|
@@ -42,22 +42,26 @@ With PyProd, a traditional Makefile for C can be expressed as a Python script li
|
|
42
42
|
.. code-block:: python
|
43
43
|
|
44
44
|
CC = "gcc"
|
45
|
-
CFLAGS = "-I."
|
45
|
+
CFLAGS = "-c -I."
|
46
46
|
DEPS = "hello.h"
|
47
47
|
OBJS = "hello.o main.o".split()
|
48
|
+
EXE = "hello.exe"
|
48
49
|
|
49
50
|
@rule("%.o", depends=("%.c", DEPS))
|
50
51
|
def compile(target, src, *deps):
|
51
|
-
run(CC, "-
|
52
|
+
run(CC, "-o", target, src, CFLAGS)
|
52
53
|
|
53
|
-
@rule(
|
54
|
+
@rule(EXE, depends=OBJS)
|
54
55
|
def link(target, *objs):
|
55
56
|
run(CC, "-o", target, objs)
|
56
57
|
|
58
|
+
@task
|
57
59
|
def clean():
|
58
60
|
run("rm -f", OBJS, "hello.exe")
|
59
61
|
|
60
|
-
|
62
|
+
@task
|
63
|
+
def rebuild():
|
64
|
+
build(clean, EXE)
|
61
65
|
|
62
66
|
|
63
67
|
To run the build script, simply execute:
|
@@ -67,6 +71,8 @@ To run the build script, simply execute:
|
|
67
71
|
$ cd project
|
68
72
|
$ pyprod
|
69
73
|
|
74
|
+
Other examples can be found in the `samples <https://github.com/atsuoishimoto/pyprod/tree/main/samples>`_ directory.
|
75
|
+
|
70
76
|
License
|
71
77
|
-------
|
72
78
|
PyProd is licensed under the MIT License. See the `LICENSE <LICENSE>`_ file for more details.
|
@@ -0,0 +1,11 @@
|
|
1
|
+
pyprod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
+
pyprod/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
|
3
|
+
pyprod/main.py,sha256=w8xm7Dz4eXF6D6FhZuxlPfU24KpnXciJLuAMT6_y3zI,2808
|
4
|
+
pyprod/prod.py,sha256=Vu376JyNqiieCMy8jVvBml3TAf32Pq4XxSdpC4bBdgY,18424
|
5
|
+
pyprod/utils.py,sha256=hvqeZhXyVAsHAdPUG6LhBOs2dMVXuDUWBYiIPBcsoKw,391
|
6
|
+
pyprod/venv.py,sha256=lRTYxvtX876FQ9-bTYmsOHYV3nuMkKHO5hTLog0iHro,1085
|
7
|
+
pyprod-0.4.0.dist-info/METADATA,sha256=zkpPbp07U627hgqWE6yDYGe_dZEDQTCiE3FidIfQ5mM,2652
|
8
|
+
pyprod-0.4.0.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
+
pyprod-0.4.0.dist-info/entry_points.txt,sha256=zFycf8BYSMRDTiI0jftmcvtkf9XM4MZ4BL3JaIer_ZM,44
|
10
|
+
pyprod-0.4.0.dist-info/licenses/LICENSE,sha256=OtPgwnlLrsVEYPnTraun5AqftAT5vUv4rIan-qYj7nE,1071
|
11
|
+
pyprod-0.4.0.dist-info/RECORD,,
|
@@ -1,11 +0,0 @@
|
|
1
|
-
pyprod/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
2
|
-
pyprod/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
|
3
|
-
pyprod/main.py,sha256=nS0mcLy3Ix7ydnX1Pu5g7ceLzhh6DndwJWO5sjO_qWg,2580
|
4
|
-
pyprod/prod.py,sha256=poLehE5Gs_vEZvKrSdMicF016tQeaMMAnFc76ssPQQY,14586
|
5
|
-
pyprod/utils.py,sha256=oiiUkSbeqTazbtJ6gz7ZKqG1OvAeV-nV9u_3Y0DCOOM,401
|
6
|
-
pyprod/venv.py,sha256=_riw56YQvUOSd55u_1m9ElsqPdjM5qVvIZP6dr9Fzt4,1051
|
7
|
-
pyprod-0.2.0.post1.dist-info/METADATA,sha256=WnpzS2zWCnRZxR9cHjrfkI7yUd742pp5idOzdNyJ7Fk,2483
|
8
|
-
pyprod-0.2.0.post1.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
9
|
-
pyprod-0.2.0.post1.dist-info/entry_points.txt,sha256=zFycf8BYSMRDTiI0jftmcvtkf9XM4MZ4BL3JaIer_ZM,44
|
10
|
-
pyprod-0.2.0.post1.dist-info/licenses/LICENSE,sha256=OtPgwnlLrsVEYPnTraun5AqftAT5vUv4rIan-qYj7nE,1071
|
11
|
-
pyprod-0.2.0.post1.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|