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.
Files changed (51) hide show
  1. {pyprod-0.6.0 → pyprod-0.8.0}/PKG-INFO +1 -1
  2. {pyprod-0.6.0 → pyprod-0.8.0}/docs/releasenotes.rst +11 -0
  3. pyprod-0.8.0/src/pyprod/__init__.py +1 -0
  4. {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/prod.py +48 -9
  5. {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_prod.py +95 -1
  6. {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_rule.py +0 -3
  7. pyprod-0.6.0/src/pyprod/__init__.py +0 -1
  8. {pyprod-0.6.0 → pyprod-0.8.0}/.github/workflows/publish.yml +0 -0
  9. {pyprod-0.6.0 → pyprod-0.8.0}/.github/workflows/test.yml +0 -0
  10. {pyprod-0.6.0 → pyprod-0.8.0}/.gitignore +0 -0
  11. {pyprod-0.6.0 → pyprod-0.8.0}/LICENSE +0 -0
  12. {pyprod-0.6.0 → pyprod-0.8.0}/README.rst +0 -0
  13. {pyprod-0.6.0 → pyprod-0.8.0}/docs/Makefile +0 -0
  14. {pyprod-0.6.0 → pyprod-0.8.0}/docs/commandline.rst +0 -0
  15. {pyprod-0.6.0 → pyprod-0.8.0}/docs/conf.py +0 -0
  16. {pyprod-0.6.0 → pyprod-0.8.0}/docs/index.rst +0 -0
  17. {pyprod-0.6.0 → pyprod-0.8.0}/docs/make.bat +0 -0
  18. {pyprod-0.6.0 → pyprod-0.8.0}/docs/prodfile.rst +0 -0
  19. {pyprod-0.6.0 → pyprod-0.8.0}/docs/pyprod2.png +0 -0
  20. {pyprod-0.6.0 → pyprod-0.8.0}/docs/quickstart.rst +0 -0
  21. {pyprod-0.6.0 → pyprod-0.8.0}/docs/requirements.txt +0 -0
  22. {pyprod-0.6.0 → pyprod-0.8.0}/pyprod.webp +0 -0
  23. {pyprod-0.6.0 → pyprod-0.8.0}/pyprod2.png +0 -0
  24. {pyprod-0.6.0 → pyprod-0.8.0}/pyproject.toml +0 -0
  25. {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/Makefile +0 -0
  26. {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/Prodfile.py +0 -0
  27. {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/hello.c +0 -0
  28. {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/hello.h +0 -0
  29. {pyprod-0.6.0 → pyprod-0.8.0}/samples/build-c/main.c +0 -0
  30. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/Prodfile.py +0 -0
  31. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/a.txt +0 -0
  32. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/b.txt +0 -0
  33. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/c.txt +0 -0
  34. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/inc1.txt +0 -0
  35. {pyprod-0.6.0 → pyprod-0.8.0}/samples/generate-doc/inc2.txt +0 -0
  36. {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/Prodfile.py +0 -0
  37. {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/doc.md +0 -0
  38. {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/md_to_html.py +0 -0
  39. {pyprod-0.6.0 → pyprod-0.8.0}/samples/md-to-pdf/template.html +0 -0
  40. {pyprod-0.6.0 → pyprod-0.8.0}/samples/s3files/Prodfile.py +0 -0
  41. {pyprod-0.6.0 → pyprod-0.8.0}/samples/s3files/S3TEST.txt +0 -0
  42. {pyprod-0.6.0 → pyprod-0.8.0}/samples/tutorial-1/Prodfile.py +0 -0
  43. {pyprod-0.6.0 → pyprod-0.8.0}/samples/tutorial-2/Prodfile.py +0 -0
  44. {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/__main__.py +0 -0
  45. {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/main.py +0 -0
  46. {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/utils.py +0 -0
  47. {pyprod-0.6.0 → pyprod-0.8.0}/src/pyprod/venv.py +0 -0
  48. {pyprod-0.6.0 → pyprod-0.8.0}/tests/__init__.py +0 -0
  49. {pyprod-0.6.0 → pyprod-0.8.0}/tests/conftest.py +0 -0
  50. {pyprod-0.6.0 → pyprod-0.8.0}/tests/test_prodfuncs.py +0 -0
  51. {pyprod-0.6.0 → pyprod-0.8.0}/tests/utils.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: PyProd
3
- Version: 0.6.0
3
+ Version: 0.8.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/
@@ -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}: '*' directory is not allowed")
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
- depend = _name_to_str(depend)
223
- _check_pattern_count(depend)
224
- _check_wildcard(depend)
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
- use = _name_to_str(use)
233
- _check_pattern_count(use)
234
- _check_wildcard(use)
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 dep.depends]
350
- uses = [replace_pattern(r, stem) for r in dep.uses]
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
- Path(tmp_path / "Prodfile.py").write_text(src)
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 = """
@@ -126,9 +126,6 @@ def test_validate_depends():
126
126
  with pytest.raises(prod.RuleError):
127
127
  prod.Rule("a.%", None, "%.%", "")
128
128
 
129
- with pytest.raises(prod.RuleError):
130
- prod.Rule("a.%", None, "*/x.y", "")
131
-
132
129
  prod.Rule("a.b", None, "x.y", "")
133
130
 
134
131
 
@@ -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