PyProd 0.2.0.post1__tar.gz → 0.4.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.4.0}/PKG-INFO +12 -6
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/README.rst +11 -5
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/commandline.rst +7 -7
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/index.rst +34 -2
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/prodfile.rst +102 -16
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/quickstart.rst +10 -10
- pyprod-0.4.0/docs/releasenotes.rst +15 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/pyproject.toml +3 -2
- pyprod-0.2.0.post1/sample/build-c/PRODFILE.py → pyprod-0.4.0/samples/build-c/Prodfile.py +6 -3
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/PRODFILE.py +4 -1
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/md-to-pdf/Prodfile.py +10 -5
- pyprod-0.2.0.post1/sample/s3files/PRODFILE.py → pyprod-0.4.0/samples/s3files/Prodfile.py +9 -4
- pyprod-0.4.0/samples/tutorial-1/Prodfile.py +4 -0
- pyprod-0.4.0/samples/tutorial-2/Prodfile.py +13 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/main.py +19 -7
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/prod.py +270 -121
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/utils.py +2 -2
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/venv.py +3 -3
- pyprod-0.4.0/tests/conftest.py +11 -0
- pyprod-0.4.0/tests/test_prod.py +350 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/tests/test_prodfuncs.py +37 -1
- pyprod-0.4.0/tests/test_rule.py +148 -0
- pyprod-0.4.0/tests/utils.py +13 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/uv.lock +15 -1
- pyprod-0.2.0.post1/tests/conftest.py +0 -3
- pyprod-0.2.0.post1/tests/test_prod.py +0 -217
- pyprod-0.2.0.post1/tests/test_rule.py +0 -110
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/.github/workflows/publish.yml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/.github/workflows/test.yml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/.gitignore +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/.python-version +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/.readthedocs.yaml +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/LICENSE +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/Makefile +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/conf.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/make.bat +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/pyprod2.png +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/docs/requirements.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/pyprod.webp +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/pyprod2.png +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/build-c/Makefile +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/build-c/hello.c +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/build-c/hello.h +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/build-c/main.c +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/.gitignore +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/a.txt +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/b.txt +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/c.txt +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/inc1.txt +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/generate-doc/inc2.txt +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/md-to-pdf/doc.md +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/md-to-pdf/md_to_html.py +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/md-to-pdf/template.html +0 -0
- {pyprod-0.2.0.post1/sample → pyprod-0.4.0/samples}/s3files/S3TEST.txt +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/__init__.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/src/pyprod/__main__.py +0 -0
- {pyprod-0.2.0.post1 → pyprod-0.4.0}/tests/__init__.py +0 -0
@@ -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.
|
@@ -1,7 +1,7 @@
|
|
1
1
|
PyProd - More Makeable than Make
|
2
2
|
=================================
|
3
3
|
|
4
|
-
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/
|
4
|
+
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/>`_.
|
5
5
|
|
6
6
|
|
7
7
|
Features
|
@@ -26,22 +26,26 @@ With PyProd, a traditional Makefile for C can be expressed as a Python script li
|
|
26
26
|
.. code-block:: python
|
27
27
|
|
28
28
|
CC = "gcc"
|
29
|
-
CFLAGS = "-I."
|
29
|
+
CFLAGS = "-c -I."
|
30
30
|
DEPS = "hello.h"
|
31
31
|
OBJS = "hello.o main.o".split()
|
32
|
+
EXE = "hello.exe"
|
32
33
|
|
33
34
|
@rule("%.o", depends=("%.c", DEPS))
|
34
35
|
def compile(target, src, *deps):
|
35
|
-
run(CC, "-
|
36
|
+
run(CC, "-o", target, src, CFLAGS)
|
36
37
|
|
37
|
-
@rule(
|
38
|
+
@rule(EXE, depends=OBJS)
|
38
39
|
def link(target, *objs):
|
39
40
|
run(CC, "-o", target, objs)
|
40
41
|
|
42
|
+
@task
|
41
43
|
def clean():
|
42
44
|
run("rm -f", OBJS, "hello.exe")
|
43
45
|
|
44
|
-
|
46
|
+
@task
|
47
|
+
def rebuild():
|
48
|
+
build(clean, EXE)
|
45
49
|
|
46
50
|
|
47
51
|
To run the build script, simply execute:
|
@@ -51,6 +55,8 @@ To run the build script, simply execute:
|
|
51
55
|
$ cd project
|
52
56
|
$ pyprod
|
53
57
|
|
58
|
+
Other examples can be found in the `samples <https://github.com/atsuoishimoto/pyprod/tree/main/samples>`_ directory.
|
59
|
+
|
54
60
|
License
|
55
61
|
-------
|
56
62
|
PyProd is licensed under the MIT License. See the `LICENSE <LICENSE>`_ file for more details.
|
@@ -5,16 +5,16 @@ Command line options
|
|
5
5
|
------------------------
|
6
6
|
|
7
7
|
|
8
|
-
usage: pyprod [-h] [-f FILE] [-j JOB] [-
|
8
|
+
usage: pyprod [-h] [-C DIRECTORY] [-f FILE] [-j JOB] [-r] [-v] [targets ...]
|
9
9
|
|
10
10
|
positional arguments:
|
11
11
|
targets Build targets. If no specific target is provided on the command line, the first target defined in the Prodfile is selected by default. Arguments containing ``=`` specifies the value of a :ref:`params <params>` (e.g., ``key=value``).
|
12
12
|
|
13
13
|
options:
|
14
|
-
-
|
15
|
-
-j, --job JOB Allow up to N jobs to run simultaneously (default: 1).
|
14
|
+
-h, --help show this help message and exit
|
16
15
|
-C, --directory DIRECTORY
|
17
|
-
Change to DIRECTORY before performing any operations
|
18
|
-
-
|
19
|
-
|
20
|
-
|
16
|
+
Change to DIRECTORY before performing any operations
|
17
|
+
-f, --file FILE Use FILE as the Prodfile (default: 'PRODFILE.py')
|
18
|
+
-j, --job JOB Allow up to N jobs to run simultaneously (default: 1)
|
19
|
+
-r, --rebuild Rebuild all
|
20
|
+
-v Increase verbosity level (default: 0)
|
@@ -1,17 +1,47 @@
|
|
1
1
|
PyProd - More Makeable than Make
|
2
2
|
============================================
|
3
|
-
|
4
3
|
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.
|
5
4
|
|
6
5
|
Features
|
7
6
|
--------
|
8
|
-
|
9
7
|
- Define build rules in Python: Use Python functions to create clear and concise build logic.
|
10
8
|
- Specify dependencies for each rule: Automatically track and resolve dependencies between files, such as source files and headers.
|
11
9
|
- Easily extendable with custom Python functions: Integrate custom logic for specialized tasks, like code linting or deployment.
|
12
10
|
- Manages virtual environments: Automatically create and manage virtual environments for each project, ensuring a clean and isolated build environment.
|
13
11
|
|
14
12
|
|
13
|
+
Example
|
14
|
+
-------
|
15
|
+
With PyProd, a traditional Makefile for C can be expressed as a Python script like this:
|
16
|
+
|
17
|
+
.. code-block:: python
|
18
|
+
|
19
|
+
CC = "gcc"
|
20
|
+
CFLAGS = "-c -I."
|
21
|
+
DEPS = "hello.h"
|
22
|
+
OBJS = "hello.o main.o".split()
|
23
|
+
EXE = "hello.exe"
|
24
|
+
|
25
|
+
@rule("%.o", depends=("%.c", DEPS))
|
26
|
+
def compile(target, src, *deps):
|
27
|
+
run(CC, "-o", target, src, CFLAGS)
|
28
|
+
|
29
|
+
@rule(EXE, depends=OBJS)
|
30
|
+
def link(target, *objs):
|
31
|
+
run(CC, "-o", target, objs)
|
32
|
+
|
33
|
+
@task
|
34
|
+
def clean():
|
35
|
+
run("rm -f", OBJS, "hello.exe")
|
36
|
+
|
37
|
+
@task
|
38
|
+
def rebuild():
|
39
|
+
build(clean, EXE)
|
40
|
+
|
41
|
+
|
42
|
+
|
43
|
+
Other examples can be found in the `samples <https://github.com/atsuoishimoto/pyprod/tree/main/samples>`_ directory.
|
44
|
+
|
15
45
|
Table of Contents
|
16
46
|
--------------------
|
17
47
|
|
@@ -35,6 +65,8 @@ Table of Contents
|
|
35
65
|
quickstart
|
36
66
|
prodfile
|
37
67
|
commandline
|
68
|
+
releasenotes
|
69
|
+
|
38
70
|
|
39
71
|
|
40
72
|
|
@@ -10,12 +10,12 @@ Rule definition
|
|
10
10
|
|
11
11
|
A rule is defined using the ``@rule`` decorator, which takes the target file as an argument. The target file is the output of the rule, and the function that follows the decorator is the build logic that generates the target file.
|
12
12
|
|
13
|
-
.. py:function:: @rule(
|
13
|
+
.. py:function:: @rule(targets, pattern=None, *, depends=(), uses=())
|
14
14
|
|
15
15
|
Defines rule to build target files.
|
16
16
|
|
17
|
-
:param
|
18
|
-
:type target: str | Path|list[str | Path]
|
17
|
+
:param targets: The target file or files to be generated by the rule. Wildcards can be used in filenames, and exactly one % must be included in the filename.
|
18
|
+
:type target: str | Path | list[str | Path]
|
19
19
|
|
20
20
|
:param pattern: Specify the pattern used to extract the stem of the target filename.
|
21
21
|
|
@@ -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
|
|
@@ -79,8 +85,8 @@ The rule decorator can also be used as a standalone function without being tied
|
|
79
85
|
|
80
86
|
.. code-block:: python
|
81
87
|
|
82
|
-
rule(
|
83
|
-
rule(
|
88
|
+
rule(targets=("file1", "file2"), depends="inc1")
|
89
|
+
rule(targets=("file3", "file4"), uses="inc2")
|
84
90
|
|
85
91
|
|
86
92
|
|
@@ -91,12 +97,12 @@ Checker definition
|
|
91
97
|
PyProd provides default checkers for common file types for files and directories. For non-file targets requiring specialized checks, you can define a custom checker to determine whether a build is needed.
|
92
98
|
A checker is defined using the ``@check`` decorator, which takes the target file as an argument.
|
93
99
|
|
94
|
-
.. py:function:: @check(
|
100
|
+
.. py:function:: @check(targets)
|
95
101
|
|
96
102
|
Defines a checker to get last modified time of the target.
|
97
103
|
|
98
|
-
:param
|
99
|
-
|
104
|
+
:param targets: The target file or files to be checked. Wildcards can be used.
|
105
|
+
:type target: str | Path | list[str | Path]
|
100
106
|
|
101
107
|
:return: Last modified time of the file if the target exists. Returns false or raise FileNotFoundError if the target does not exist.
|
102
108
|
:rtype: false|float|datetime.datetime
|
@@ -131,6 +137,39 @@ For example, a checker to retrieve the last modified timestamp of a file on Amaz
|
|
131
137
|
return
|
132
138
|
raise
|
133
139
|
|
140
|
+
Task definition
|
141
|
+
^^^^^^^^^^^^^^^^^^
|
142
|
+
|
143
|
+
A task is similar to a rule but does not have a target and is always executed when it is depended upon.
|
144
|
+
|
145
|
+
.. py:function:: @task(*, name=None, depends=(), uses=())
|
146
|
+
|
147
|
+
Defines a task to be executed.
|
148
|
+
|
149
|
+
:param name: The name of the task. Defaults to the function name.
|
150
|
+
:type name: str
|
151
|
+
|
152
|
+
:param depends: Specify the dependencies of the task. The task is alwayes excused regardless of the timestamp of the dependencies. The dependencies are passed to the task function as arguments.
|
153
|
+
:type depends: str | Path | list[str | Path]
|
154
|
+
|
155
|
+
:param uses: Specify the dependencies of the target file. Unline the ``depends`` parameter, ``uses`` are not passed to the task function.
|
156
|
+
:type uses: str | Path | list[str | Path]
|
157
|
+
|
158
|
+
|
159
|
+
.. code-block:: python
|
160
|
+
|
161
|
+
@task(depends=("file1", "file2"))
|
162
|
+
def my_task(*files):
|
163
|
+
print("Task executed", files)
|
164
|
+
|
165
|
+
|
166
|
+
`@task` can be used without any arguments if no dependencies are specified.
|
167
|
+
|
168
|
+
.. code-block:: python
|
169
|
+
|
170
|
+
@task
|
171
|
+
def my_task(*files):
|
172
|
+
print("Task executed", files)
|
134
173
|
|
135
174
|
|
136
175
|
|
@@ -141,6 +180,11 @@ In addition to the ``@rule`` and ``@check`` decorators, PyProd provides several
|
|
141
180
|
|
142
181
|
The following built-ins are available:
|
143
182
|
|
183
|
+
.. py:function:: build(*deps):
|
184
|
+
|
185
|
+
Schedule dependencies. The specified deps are built sequentially after the current build completes.
|
186
|
+
|
187
|
+
:param args: name or functions to be built.
|
144
188
|
|
145
189
|
.. py:function:: pip(*args)
|
146
190
|
|
@@ -190,8 +234,8 @@ Example:
|
|
190
234
|
.. code-block:: python
|
191
235
|
|
192
236
|
run(["echo", "Hello, World!"]) # list style args
|
193
|
-
run(
|
194
|
-
run(
|
237
|
+
run("echo Hello, World") # Shell style args
|
238
|
+
run("echo", "Hello,", "World") # Shell style args (automactic concatenation)
|
195
239
|
run("echo", ["hello", ["world"]]) # Shell style args (automactic flattening)
|
196
240
|
|
197
241
|
files = run("ls", stdout=True).stdout # Capture output
|
@@ -205,10 +249,10 @@ Example:
|
|
205
249
|
:param args: Command and arguments to execute. If first argument is a list, the first element is the command and the rest are arguments. Sequences specified for args are automatically flattened.
|
206
250
|
:type args: str | Path | list[str | Path]
|
207
251
|
|
208
|
-
:echo: Print the command before executing it (default ``True``).
|
252
|
+
:param echo: Print the command before executing it (default ``True``).
|
209
253
|
:type echo: bool
|
210
254
|
|
211
|
-
:cwd: Change the current working directory before executing the command.
|
255
|
+
:param cwd: Change the current working directory before executing the command.
|
212
256
|
:type shell: str | Path | None
|
213
257
|
|
214
258
|
:param check: Raise an exception if the command returns a non-zero exit code (default ``True``).
|
@@ -229,6 +273,36 @@ Example:
|
|
229
273
|
msg = capture("echo Hello, World!")
|
230
274
|
|
231
275
|
|
276
|
+
.. py:function:: read(filename):
|
277
|
+
|
278
|
+
Read the contents of a file.
|
279
|
+
|
280
|
+
:param filename: The file to read.
|
281
|
+
:type filename: str | Path
|
282
|
+
|
283
|
+
:return: The contents of the file.
|
284
|
+
:rtype: str
|
285
|
+
|
286
|
+
.. py:function:: write(filename, txt, append=False):
|
287
|
+
|
288
|
+
Write text to a file.
|
289
|
+
|
290
|
+
:param filename: The file to write to.
|
291
|
+
:type filename: str | Path
|
292
|
+
|
293
|
+
:param txt: The text to write.
|
294
|
+
:type txt: str
|
295
|
+
|
296
|
+
:param append: Append to the file instead of overwriting it (default ``False``).
|
297
|
+
:type append: bool
|
298
|
+
|
299
|
+
.. py:function:: makedirs(path):
|
300
|
+
|
301
|
+
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``.
|
302
|
+
|
303
|
+
:param path: The directory to create.
|
304
|
+
:type path: str | Path
|
305
|
+
|
232
306
|
.. py:function:: glob(path, dir=".")
|
233
307
|
|
234
308
|
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.
|
@@ -249,12 +323,24 @@ Example:
|
|
249
323
|
|
250
324
|
SRCFILES = glob("**/*.c")
|
251
325
|
|
252
|
-
.. py:function:: quote(s)
|
326
|
+
.. py:function:: quote(*s)
|
327
|
+
.. py:function:: q(*s)
|
253
328
|
|
254
|
-
Quote
|
329
|
+
Quote strings in ``s``. Each ``s`` is flattend.
|
255
330
|
|
256
331
|
:param s: The string to quote.
|
257
|
-
:type s: str
|
332
|
+
:type s: str | list
|
333
|
+
|
334
|
+
:return: The list of quoted strings.
|
335
|
+
:rtype: list[str]
|
336
|
+
|
337
|
+
.. py:function:: squote(s)
|
338
|
+
.. py:function:: sq(s)
|
339
|
+
|
340
|
+
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>`_.
|
341
|
+
|
342
|
+
:param s: The string to quote. If ``s`` is sequence, it is flattened and joined with space.
|
343
|
+
:type s: str | list
|
258
344
|
|
259
345
|
:return: The quoted string.
|
260
346
|
:rtype: str
|
@@ -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
|
|
@@ -0,0 +1,15 @@
|
|
1
|
+
Release Notes
|
2
|
+
================
|
3
|
+
|
4
|
+
0.4.0 (2025-1-17)
|
5
|
+
-------------------------
|
6
|
+
- Swapped the behavior of quote() and squote() to make their naming more intuitive.
|
7
|
+
- Add @task decorator.
|
8
|
+
- Change the parameter name target to targets.
|
9
|
+
- Add --rebuild option.
|
10
|
+
|
11
|
+
0.3.0 (2025-01-03)
|
12
|
+
------------------
|
13
|
+
- Arguments for build and check function are converted to string.
|
14
|
+
- Add built-in functions.
|
15
|
+
- Validate rule dependencies.
|
@@ -1,6 +1,6 @@
|
|
1
1
|
[project]
|
2
2
|
name = "PyProd"
|
3
|
-
version = "0.
|
3
|
+
version = "0.4.0"
|
4
4
|
description = "PyProd: More Makeable than Make"
|
5
5
|
readme = "README.rst"
|
6
6
|
requires-python = ">=3.10"
|
@@ -29,6 +29,7 @@ build-backend = "hatchling.build"
|
|
29
29
|
dev = [
|
30
30
|
"pytest>=8.3.4",
|
31
31
|
"pytest-asyncio>=0.25.0",
|
32
|
+
"pytest-mock>=3.14.0",
|
32
33
|
"ruff>=0.8.4",
|
33
34
|
"sphinx>=8.1.3",
|
34
35
|
"sphinx-autobuild>=2024.10.3",
|
@@ -40,4 +41,4 @@ asyncio_default_fixture_loop_scope="function"
|
|
40
41
|
|
41
42
|
[tool.ruff.lint]
|
42
43
|
select = ["E","F","W","B","N","T10","I"]
|
43
|
-
ignore = ["F401"]
|
44
|
+
ignore = ["F401", "B9"]
|
@@ -3,7 +3,7 @@
|
|
3
3
|
|
4
4
|
APP = "hello.exe"
|
5
5
|
CC = "gcc"
|
6
|
-
CFLAGS = "-I."
|
6
|
+
CFLAGS = "-c -I."
|
7
7
|
DEPS = "hello.h"
|
8
8
|
OBJS = "hello.o main.o".split()
|
9
9
|
|
@@ -15,11 +15,14 @@ def link(target, *src):
|
|
15
15
|
|
16
16
|
@rule("%.o", depends=("%.c", DEPS))
|
17
17
|
def compile(target, src, *deps):
|
18
|
-
run(CC, "-
|
18
|
+
run(CC, "-o", target, src, CFLAGS)
|
19
19
|
|
20
20
|
|
21
|
+
@task
|
21
22
|
def clean():
|
22
23
|
run("rm", "-rf", OBJS, APP)
|
23
24
|
|
24
25
|
|
25
|
-
|
26
|
+
@task
|
27
|
+
def rebuild():
|
28
|
+
build(clean, APP)
|
@@ -31,18 +31,23 @@ 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
|
|
45
|
+
@task
|
46
46
|
def clean():
|
47
47
|
shutil.rmtree(BUILD, ignore_errors=True)
|
48
48
|
PDF.unlink(missing_ok=True)
|
49
|
+
|
50
|
+
|
51
|
+
@task
|
52
|
+
def rebuild():
|
53
|
+
build(clean, PDF)
|
@@ -1,13 +1,14 @@
|
|
1
1
|
# ruff: NOQA
|
2
2
|
# type: ignore
|
3
3
|
|
4
|
-
pip("boto3") # install boto3
|
4
|
+
pip("boto3", "-q") # install boto3
|
5
5
|
|
6
6
|
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):
|
@@ -16,7 +17,7 @@ def parse_s3url(s3url):
|
|
16
17
|
return parsed.netloc, parsed.path.lstrip("/")
|
17
18
|
|
18
19
|
|
19
|
-
@rule(
|
20
|
+
@rule(targets=TARGET, pattern="*/%.txt", depends="%.txt")
|
20
21
|
def copyfile(target, src):
|
21
22
|
"""Copies a file to an S3 bucket."""
|
22
23
|
bucket, key = parse_s3url(target)
|
@@ -35,16 +36,20 @@ def check_s3file(s3url):
|
|
35
36
|
raise
|
36
37
|
|
37
38
|
|
39
|
+
@task
|
38
40
|
def clean():
|
39
41
|
"""Deletes an S3 file."""
|
40
42
|
bucket, key = parse_s3url(TARGET)
|
41
43
|
s3.delete_object(Bucket=bucket, Key=key)
|
42
44
|
|
43
45
|
|
46
|
+
@task
|
44
47
|
def ls():
|
45
48
|
"""Lists the contents of an S3 bucket."""
|
46
49
|
bucket, key = parse_s3url(TARGET)
|
47
50
|
run("aws s3 ls", bucket)
|
48
51
|
|
49
52
|
|
50
|
-
|
53
|
+
@task
|
54
|
+
def rebuild():
|
55
|
+
build(clean, TARGET)
|
@@ -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)
|
@@ -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
|
|