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 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
- "-C",
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 = pyprod.args = parser.parse_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 = asyncio.run(prod.start(targets))
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 InvalidRuleError(Exception):
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 InvalidRuleError(rule)
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 InvalidRuleError(s_rule)
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
- @dataclass
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: list
147
- pattern: str | None
148
- depends: list
149
- uses: list | None
150
- builder: str | None
151
-
152
- orig_targets: list = field(init=False)
153
- orig_depends: list = field(init=False)
154
- orig_uses: list = field(init=False)
155
-
156
- def __post_init__(self):
157
- self.orig_targets = self.targets
158
- self.orig_depends = self.depends
159
- self.orig_uses = self.uses
160
-
161
- self.targets = [rule_to_re(r) for r in flatten(self.targets)]
162
- self.depends = list(flatten(self.depends))
163
- if self.pattern:
164
- self.pattern = rule_to_re(self.pattern)
165
- if self.uses:
166
- self.uses = list(flatten(self.uses))
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
- targets,
186
- pattern,
187
- depends,
188
- uses,
189
- builder,
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, target, pattern=None, depends=(), uses=()):
195
- if (not isinstance(depends, Collection)) or isinstance(depends, str):
196
- depends = [depends]
197
- if (not isinstance(uses, Collection)) or isinstance(uses, str):
198
- uses = [uses]
199
- dep = self.add_rule([target], pattern, depends, uses, None)
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 = str(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(str(target), name)
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(str(dep.pattern), name)
214
- d = m.groupdict().get("stem", None)
215
- if d is not None:
216
- stem = d
328
+ m = re.fullmatch(dep.pattern, name)
329
+ if m:
330
+ stem = m.groupdict().get("stem", None)
217
331
 
218
- depends = [replace_pattern(r, stem) for r in dep.depends]
219
- uses = [replace_pattern(r, stem) for r in dep.uses]
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
- for target in dep.orig_targets:
241
- s_target = str(target)
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 = str(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(str(d) for d in depends)
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, str(target)):
404
+ if fnmatch(name, target):
291
405
  return f
292
406
 
293
- def add_check(self, target, f):
294
- self.checkers.append((list(t for t in flatten(target)), f))
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, target):
411
+ def check(self, targets):
297
412
  def deco(f):
298
- self.add_check(target, f)
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
- self.modulefile = Path(modulefile)
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 = self.load_pyprodfile(self.modulefile)
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
- "quote": shlex.quote,
386
- "params": self.params,
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
- return await loop.run_in_executor(
562
+ ret = await loop.run_in_executor(
408
563
  self.executor, lambda: func(*args, **kwargs)
409
564
  )
410
565
  else:
411
- return func(*args, **kwargs)
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
- names = []
419
- for name in deps:
420
- if isinstance(name, str):
421
- value = getattr(self.module, name, None)
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 build(self, deps):
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
- async def run(self, name): # -> Any | int:
500
- s_name = str(name)
635
+ def build(self, *deps):
636
+ self.deps[0:0] = [_name_to_str(name) for name in flatten(deps)]
501
637
 
502
- self.rules.build_tree(s_name)
503
- deps, uses = self.rules.get_dep_names(s_name)
504
- selected = self.rules.select_builder(s_name)
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.build(deps)
650
+ ts = await self.schedule(deps)
513
651
  if uses:
514
- await self.build(uses)
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(s_name)
659
+ exists = await self.is_exists(name)
517
660
 
518
661
  if not exists.exists:
519
- logger.debug("%r does not exists", str(s_name))
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", str(s_name))
664
+ logger.debug("%r should be updated", name)
522
665
  else:
523
- logger.debug("%r already exists", str(s_name))
666
+ logger.debug("%r already exists", name)
524
667
 
525
668
  if not exists.exists and not selected:
526
- raise NoRuleToMakeTargetError(s_name)
527
- elif selected and ((not exists.exists) or (ts >= MAX_TS) or (exists.ts < ts)):
528
- logger.warning("building: %r", s_name)
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
@@ -1,8 +1,8 @@
1
- from collections.abc import Iterable, Sequence
1
+ from collections.abc import Iterable
2
2
 
3
3
 
4
4
  def flatten(seq):
5
- if isinstance(seq, str) or (not isinstance(seq, Sequence)):
5
+ if isinstance(seq, str) or (not isinstance(seq, Iterable)):
6
6
  yield seq
7
7
  return
8
8
 
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 = ".pyprod"
16
+ PYPRODVENV = "pyprod"
18
17
 
19
18
 
20
19
  def makevenv(conffile):
21
20
  global venvdir
22
- venvdir = Path(".") / f".{conffile.name}{PYPRODVENV}"
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.2.0.post1
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/latest/>`_.
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, "-c -o", target, src, CFLAGS)
52
+ run(CC, "-o", target, src, CFLAGS)
52
53
 
53
- @rule("hello.exe", depends=OBJS)
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
- all = "hello.exe"
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,,