RFL.build 1.0.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.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: RFL.build
3
+ Version: 1.0.0
4
+ Summary: Rackslab Foundation Library: build package
5
+ Author-email: Rémi Palancher <remi@rackslab.io>
6
+ License: GPLv3+
7
+ Project-URL: Homepage, https://github.com/rackslab/RFL
8
+ Project-URL: Bug Tracker, https://github.com/rackslab/RFL/issues
9
+ Keywords: utility,basic
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: System :: Systems Administration
16
+ Requires-Python: >=3.6
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: RFL.core
19
+ Requires-Dist: tomli
20
+
21
+ # RFL: build package
22
+
23
+ Utilities to help backport builds of Python projects.
@@ -0,0 +1,3 @@
1
+ # RFL: build package
2
+
3
+ Utilities to help backport builds of Python projects.
@@ -0,0 +1,23 @@
1
+ Metadata-Version: 2.1
2
+ Name: RFL.build
3
+ Version: 1.0.0
4
+ Summary: Rackslab Foundation Library: build package
5
+ Author-email: Rémi Palancher <remi@rackslab.io>
6
+ License: GPLv3+
7
+ Project-URL: Homepage, https://github.com/rackslab/RFL
8
+ Project-URL: Bug Tracker, https://github.com/rackslab/RFL/issues
9
+ Keywords: utility,basic
10
+ Classifier: Development Status :: 5 - Production/Stable
11
+ Classifier: Environment :: Console
12
+ Classifier: Intended Audience :: System Administrators
13
+ Classifier: License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Topic :: System :: Systems Administration
16
+ Requires-Python: >=3.6
17
+ Description-Content-Type: text/markdown
18
+ Requires-Dist: RFL.core
19
+ Requires-Dist: tomli
20
+
21
+ # RFL: build package
22
+
23
+ Utilities to help backport builds of Python projects.
@@ -0,0 +1,14 @@
1
+ README.md
2
+ pyproject.toml
3
+ RFL.build.egg-info/PKG-INFO
4
+ RFL.build.egg-info/SOURCES.txt
5
+ RFL.build.egg-info/dependency_links.txt
6
+ RFL.build.egg-info/entry_points.txt
7
+ RFL.build.egg-info/requires.txt
8
+ RFL.build.egg-info/top_level.txt
9
+ rfl/build/__init__.py
10
+ rfl/build/errors.py
11
+ rfl/build/installsetup.py
12
+ rfl/build/ninja.py
13
+ rfl/build/projectversion.py
14
+ rfl/build/scripts/setup
@@ -0,0 +1,3 @@
1
+ [console_scripts]
2
+ rfl-install-setup-generator = rfl.build.installsetup:main
3
+ rfl-project-version = rfl.build.projectversion:main
@@ -0,0 +1,2 @@
1
+ RFL.core
2
+ tomli
@@ -0,0 +1 @@
1
+ rfl
@@ -0,0 +1,41 @@
1
+ [build-system]
2
+ requires = ["setuptools"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "RFL.build"
7
+ version = "1.0.0"
8
+ description = "Rackslab Foundation Library: build package"
9
+ license = {text = "GPLv3+"}
10
+ requires-python = ">=3.6"
11
+ keywords = ["utility", "basic"]
12
+ authors = [
13
+ {name = "Rémi Palancher", email = "remi@rackslab.io"},
14
+ ]
15
+ dependencies = [
16
+ "RFL.core",
17
+ "tomli"
18
+ ]
19
+ classifiers = [
20
+ "Development Status :: 5 - Production/Stable",
21
+ "Environment :: Console",
22
+ "Intended Audience :: System Administrators",
23
+ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)",
24
+ "Programming Language :: Python :: 3",
25
+ "Topic :: System :: Systems Administration",
26
+ ]
27
+ readme = "README.md"
28
+
29
+ [project.urls]
30
+ "Homepage" = "https://github.com/rackslab/RFL"
31
+ "Bug Tracker" = "https://github.com/rackslab/RFL/issues"
32
+
33
+ [project.scripts]
34
+ rfl-install-setup-generator = "rfl.build.installsetup:main"
35
+ rfl-project-version = "rfl.build.projectversion:main"
36
+
37
+ [tool.setuptools.packages.find]
38
+ include = ["rfl.build*"]
39
+
40
+ [tool.setuptools.package-data]
41
+ "rfl.build.scripts" = ["*"]
File without changes
@@ -0,0 +1,11 @@
1
+ # Copyright (c) 2024 Rackslab
2
+ #
3
+ # This file is part of RFL.
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+
7
+ from rfl.core.errors import RFLError
8
+
9
+
10
+ class BuildError(RFLError):
11
+ pass
@@ -0,0 +1,18 @@
1
+ # Copyright (c) 2023 Rackslab
2
+ #
3
+ # This file is part of RFL.
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+
7
+ import os
8
+ from pathlib import Path
9
+ import shutil
10
+
11
+
12
+ def main():
13
+ """Install setup.py script provided by RFL.build package in scripts/ subfolder in
14
+ the current working directory."""
15
+ orig = Path(os.path.realpath(__file__)).parent.joinpath("scripts", "setup")
16
+ dest = Path(os.getcwd()).joinpath("setup.py")
17
+ print(f"Copying file {orig} to {dest}")
18
+ shutil.copy(orig, dest)
@@ -0,0 +1,235 @@
1
+ # Copyright (c) 2011 Google Inc
2
+ # Copyright (c) 2023 Rackslab
3
+ #
4
+ # This file is part of RFL.
5
+ #
6
+ # This file originally comes from Ninja build system source tree:
7
+ # https://github.com/ninja-build/ninja/blob/master/misc/ninja_syntax.py
8
+ #
9
+ # SPDX-License-Identifier: Apache-2.0
10
+
11
+ """Python module to help generating .ninja files."""
12
+
13
+ import sys
14
+ import io
15
+ import re
16
+ import textwrap
17
+ from pathlib import PurePath
18
+ import subprocess
19
+
20
+
21
+ def escape_path(word):
22
+ if isinstance(word, PurePath):
23
+ word = str(word)
24
+ return word.replace("$ ", "$$ ").replace(" ", "$ ").replace(":", "$:")
25
+
26
+
27
+ def as_list(input):
28
+ if input is None:
29
+ return []
30
+ if isinstance(input, list):
31
+ return input
32
+ return [input]
33
+
34
+
35
+ def escape(string):
36
+ """Escape a string such that it can be embedded into a Ninja file without
37
+ further interpretation."""
38
+ assert "\n" not in string, "Ninja syntax does not allow newlines"
39
+ # We only have one special metacharacter: '$'.
40
+ return string.replace("$", "$$")
41
+
42
+
43
+ def expand(string, vars, local_vars={}):
44
+ """Expand a string containing $vars as Ninja would.
45
+
46
+ Note: doesn't handle the full Ninja variable syntax, but it's enough
47
+ to make configure.py's use of it work.
48
+ """
49
+
50
+ def exp(m):
51
+ var = m.group(1)
52
+ if var == "$":
53
+ return "$"
54
+ return local_vars.get(var, vars.get(var, ""))
55
+
56
+ return re.sub(r"\$(\$|\w*)", exp, string)
57
+
58
+
59
+ class NinjaBuilder(object):
60
+ def __init__(self, width=512):
61
+ self.output = io.StringIO()
62
+ self.width = width
63
+
64
+ def newline(self):
65
+ self.output.write("\n")
66
+
67
+ def comment(self, text):
68
+ for line in textwrap.wrap(
69
+ text, self.width - 2, break_long_words=False, break_on_hyphens=False
70
+ ):
71
+ self.output.write("# " + line + "\n")
72
+
73
+ def variable(self, key, value, indent=0):
74
+ if value is None:
75
+ return
76
+ if isinstance(value, list):
77
+ value = " ".join(filter(None, value)) # Filter out empty strings.
78
+ self._line("%s = %s" % (key, value), indent)
79
+
80
+ def pool(self, name, depth):
81
+ self._line("pool %s" % name)
82
+ self.variable("depth", depth, indent=1)
83
+
84
+ def rule(
85
+ self,
86
+ name,
87
+ command,
88
+ description=None,
89
+ depfile=None,
90
+ generator=False,
91
+ pool=None,
92
+ restat=False,
93
+ rspfile=None,
94
+ rspfile_content=None,
95
+ deps=None,
96
+ ):
97
+ self._line("rule %s" % name)
98
+ self.variable("command", command, indent=1)
99
+ if description:
100
+ self.variable("description", description, indent=1)
101
+ if depfile:
102
+ self.variable("depfile", depfile, indent=1)
103
+ if generator:
104
+ self.variable("generator", "1", indent=1)
105
+ if pool:
106
+ self.variable("pool", pool, indent=1)
107
+ if restat:
108
+ self.variable("restat", "1", indent=1)
109
+ if rspfile:
110
+ self.variable("rspfile", rspfile, indent=1)
111
+ if rspfile_content:
112
+ self.variable("rspfile_content", rspfile_content, indent=1)
113
+ if deps:
114
+ self.variable("deps", deps, indent=1)
115
+
116
+ def build(
117
+ self,
118
+ outputs,
119
+ rule,
120
+ inputs=None,
121
+ implicit=None,
122
+ order_only=None,
123
+ variables=None,
124
+ implicit_outputs=None,
125
+ pool=None,
126
+ dyndep=None,
127
+ ):
128
+ outputs = as_list(outputs)
129
+ out_outputs = [escape_path(x) for x in outputs]
130
+ all_inputs = [escape_path(x) for x in as_list(inputs)]
131
+
132
+ if implicit:
133
+ implicit = [escape_path(x) for x in as_list(implicit)]
134
+ all_inputs.append("|")
135
+ all_inputs.extend(implicit)
136
+ if order_only:
137
+ order_only = [escape_path(x) for x in as_list(order_only)]
138
+ all_inputs.append("||")
139
+ all_inputs.extend(order_only)
140
+ if implicit_outputs:
141
+ implicit_outputs = [escape_path(x) for x in as_list(implicit_outputs)]
142
+ out_outputs.append("|")
143
+ out_outputs.extend(implicit_outputs)
144
+
145
+ self._line(
146
+ "build %s: %s" % (" ".join(out_outputs), " ".join([rule] + all_inputs))
147
+ )
148
+ if pool is not None:
149
+ self._line(" pool = %s" % pool)
150
+ if dyndep is not None:
151
+ self._line(" dyndep = %s" % dyndep)
152
+
153
+ if variables:
154
+ if isinstance(variables, dict):
155
+ iterator = iter(variables.items())
156
+ else:
157
+ iterator = iter(variables)
158
+
159
+ for key, val in iterator:
160
+ self.variable(key, val, indent=1)
161
+
162
+ return outputs
163
+
164
+ def include(self, path):
165
+ self._line("include %s" % path)
166
+
167
+ def subninja(self, path):
168
+ self._line("subninja %s" % path)
169
+
170
+ def default(self, paths):
171
+ self._line("default %s" % " ".join(as_list(paths)))
172
+
173
+ def _count_dollars_before_index(self, s, i):
174
+ """Returns the number of '$' characters right in front of s[i]."""
175
+ dollar_count = 0
176
+ dollar_index = i - 1
177
+ while dollar_index > 0 and s[dollar_index] == "$":
178
+ dollar_count += 1
179
+ dollar_index -= 1
180
+ return dollar_count
181
+
182
+ def _line(self, text, indent=0):
183
+ """Write 'text' word-wrapped at self.width characters."""
184
+ leading_space = " " * indent
185
+ while len(leading_space) + len(text) > self.width:
186
+ # The text is too wide; wrap if possible.
187
+
188
+ # Find the rightmost space that would obey our width constraint and
189
+ # that's not an escaped space.
190
+ available_space = self.width - len(leading_space) - len(" $")
191
+ space = available_space
192
+ while True:
193
+ space = text.rfind(" ", 0, space)
194
+ if space < 0 or self._count_dollars_before_index(text, space) % 2 == 0:
195
+ break
196
+
197
+ if space < 0:
198
+ # No such space; just use the first unescaped space we can find.
199
+ space = available_space - 1
200
+ while True:
201
+ space = text.find(" ", space + 1)
202
+ if (
203
+ space < 0
204
+ or self._count_dollars_before_index(text, space) % 2 == 0
205
+ ):
206
+ break
207
+ if space < 0:
208
+ # Give up on breaking.
209
+ break
210
+
211
+ self.output.write(leading_space + text[0:space] + " $\n")
212
+ text = text[space + 1 :]
213
+
214
+ # Subsequent lines are continuations, so indent them.
215
+ leading_space = " " * (indent + 2)
216
+
217
+ self.output.write(leading_space + text + "\n")
218
+
219
+ def run(self):
220
+ self.output.seek(0)
221
+ try:
222
+ subprocess.run(
223
+ ["ninja", "-f", "/dev/stdin"], input=self.output.read().encode()
224
+ )
225
+ except subprocess.CalledProcessError as err:
226
+ print(
227
+ "Error while running ninja:\n"
228
+ f" exit code: {err.returncode}\n"
229
+ f" stdout: {err.stdout}\n"
230
+ f" stderr: {err.stderr}",
231
+ file=sys.stderr,
232
+ )
233
+ sys.exit(1)
234
+ finally:
235
+ self.output.close()
@@ -0,0 +1,63 @@
1
+ # Copyright (c) 2023 Rackslab
2
+ #
3
+ # This file is part of RFL.
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+
7
+ """
8
+ This tiny utility loads pyproject.toml file of current working directory project,
9
+ looking for this file in parents directory recursively up to filesystem root. If the
10
+ file is found, print the version of the project on stdout. Else, print an error.
11
+ """
12
+
13
+ import sys
14
+ from pathlib import Path
15
+
16
+ # First try to import Python standard toml library (available in Python 3.11+) and
17
+ # fallback to external library.
18
+ try:
19
+ import tomllib
20
+ except ImportError:
21
+ import tomli as tomllib
22
+
23
+ from .errors import BuildError
24
+
25
+
26
+ def project_version():
27
+ current_dir = Path.cwd()
28
+ found = False
29
+ while True:
30
+ pyproject_path = current_dir.joinpath("pyproject.toml")
31
+ if pyproject_path.exists():
32
+ found = True
33
+ break
34
+ else:
35
+ # Stop iteration when filesystem root is reached.
36
+ if str(current_dir) == current_dir.root:
37
+ break
38
+ # Jump to parent directory.
39
+ current_dir = current_dir.parent
40
+
41
+ if not found:
42
+ raise BuildError(
43
+ "Unable to find project pyproject.toml file of current working directory",
44
+ )
45
+ content = tomllib.load(open(pyproject_path, "rb"))
46
+ try:
47
+ return content["project"]["version"]
48
+ except KeyError as err:
49
+ raise BuildError(
50
+ f"Unable to extract version from project file {pyproject_path}"
51
+ ) from err
52
+
53
+
54
+ def main():
55
+ try:
56
+ print(project_version())
57
+ except BuildError as err:
58
+ print(str(err), file=sys.stderr)
59
+ sys.exit(1)
60
+
61
+
62
+ if __name__ == "__main__":
63
+ main()
@@ -0,0 +1,100 @@
1
+ # Copyright (c) 2023 Rackslab
2
+ #
3
+ # This file is part of RFL.
4
+ #
5
+ # SPDX-License-Identifier: GPL-3.0-or-later
6
+
7
+ """
8
+ This setup.py loads parameter defined in pyproject.toml and calls setuptools.setup()
9
+ with these parameters. This script is especially useful to build modern PEP 518 Python
10
+ projects that provide pyproject.toml with old versions of pip/python (eg. python 3.6)
11
+ without PEP 518 support.
12
+ """
13
+
14
+ import sys
15
+ import os
16
+
17
+ from setuptools import setup, find_packages
18
+
19
+ try:
20
+ import tomllib
21
+ except ImportError:
22
+ import tomli as tomllib
23
+
24
+ header = "rfl-build"
25
+
26
+ with open("pyproject.toml", "rb") as fh:
27
+ pyproject = tomllib.load(fh)
28
+
29
+ # Nothing much can be done by the script.
30
+ if "project" not in pyproject:
31
+ print(f"{header}: project section not found, leaving.")
32
+ sys.exit(0)
33
+
34
+ # Initialize dict of additional options
35
+ kwargs = {}
36
+
37
+ if "scripts" in pyproject["project"]:
38
+ kwargs["entry_points"] = {
39
+ "console_scripts": [
40
+ f"{executable}={caller}"
41
+ for executable, caller in pyproject["project"]["scripts"].items()
42
+ ]
43
+ }
44
+
45
+ # Python Packaging User Guide suggests using find_namespace_packages(…) to support
46
+ # namespace packages:
47
+ #
48
+ # https://packaging.python.org/en/latest/guides/packaging-namespace-packages/#native-namespace-packages
49
+ #
50
+ # Unfortunately, this function is not support in setuptools < v40.1.0 that is provided
51
+ # on systems with Python 3.6 where this setup.py script is required (el8). The
52
+ # alternative solution is to explicitely list the packages to install. Then the
53
+ # following logic detects if there is a native namespace (when one topfolder declared in
54
+ # [tool.setuptools.packages.find] include directive does not contain an __init__.py). In
55
+ # this case, the subpackages are explicity declared (without wildcard) in setup(). If no
56
+ # namespace is detected, the packages are automatically detected with find_packages().
57
+
58
+ autofind = True
59
+ packages = []
60
+ for include in pyproject["tool"]["setuptools"]["packages"]["find"]["include"]:
61
+ if "." not in include:
62
+ continue
63
+ topfolder = include.split(".", 1)[0]
64
+ if "__init__.py" not in os.listdir(topfolder):
65
+ autofind = False
66
+ packages.append(include.replace("*", ""))
67
+
68
+ if autofind:
69
+ kwargs["packages"] = find_packages()
70
+ else:
71
+ kwargs["packages"] = packages
72
+
73
+ print(f"{header}: packages: {kwargs['packages']}")
74
+
75
+ if "package-data" in pyproject["tool"]["setuptools"]:
76
+ kwargs["package_data"] = {
77
+ package: data
78
+ for package, data in pyproject["tool"]["setuptools"]["package-data"].items()
79
+ }
80
+ kwargs["include_package_data"] = True
81
+ print(f"{header}: package data {kwargs['package_data']}")
82
+
83
+ if "optional-dependencies" in pyproject["project"]:
84
+ kwargs["extras_require"] = {
85
+ extra: deps
86
+ for extra, deps in pyproject["project"]["optional-dependencies"].items()
87
+ }
88
+ print(f"{header}: extras require {kwargs['extras_require']}")
89
+
90
+ setup(
91
+ name=pyproject["project"]["name"],
92
+ version=pyproject["project"]["version"],
93
+ author=pyproject["project"]["authors"][0]["name"],
94
+ author_email=pyproject["project"]["authors"][0]["email"],
95
+ license=pyproject["project"]["license"]["text"],
96
+ url=pyproject["project"]["urls"]["Homepage"],
97
+ platforms=["GNU/Linux"],
98
+ install_requires=pyproject["project"]["dependencies"],
99
+ **kwargs,
100
+ )
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+