Skip to content

Commit c92f830

Browse files
committed
Refactor code #339
- Better error handling - Reduce the hardcoded code Signed-off-by: Chin Yeung Li <tli@nexb.com>
1 parent e1a8ba9 commit c92f830

File tree

1 file changed

+78
-21
lines changed

1 file changed

+78
-21
lines changed

etc/scripts/build_nix_docker.py

Lines changed: 78 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,25 @@ def read_pyproject_toml():
9393
return pyproject_data
9494

9595

96+
def extract_python_version_from_pyproject():
97+
"""Extract Python version from pyproject.toml and return major.minor"""
98+
pyproject_data = read_pyproject_toml()
99+
requires_python = pyproject_data["project"].get("requires-python", "")
100+
101+
if requires_python:
102+
import re
103+
104+
# Match any version pattern: 2.7, 3.8, 3.13, 4.0, etc.
105+
version_match = re.search(r"(\d+)\.(\d+)", requires_python)
106+
if version_match:
107+
major = version_match.group(1)
108+
minor = version_match.group(2)
109+
return f"{major}.{minor}"
110+
111+
# Default to current Python version if not specified
112+
return f"{sys.version_info.major}.{sys.version_info.minor}"
113+
114+
96115
def extract_project_meta(pyproject_data):
97116
# Extract project metadata from pyproject.toml data.
98117
project_data = pyproject_data["project"]
@@ -102,8 +121,26 @@ def extract_project_meta(pyproject_data):
102121
authors = project_data.get("authors")
103122
author_names = [author.get("name", "") for author in authors if "name" in author]
104123
author_str = ", ".join(author_names)
105-
106-
meta_dict = {"name": name, "version": version, "description": description, "author": author_str}
124+
requires_python = project_data.get("requires-python", "")
125+
# Current system's python version
126+
python_version = f"{sys.version_info.major}.{sys.version_info.minor}"
127+
128+
if requires_python:
129+
import re
130+
131+
version_match = re.search(r"(\d+)(?:\.(\d+))?", requires_python)
132+
if version_match:
133+
major = version_match.group(1)
134+
minor = version_match.group(2) if version_match.group(2) else "0"
135+
python_version = f"{major}.{minor}"
136+
137+
meta_dict = {
138+
"name": name,
139+
"version": version,
140+
"description": description,
141+
"author": author_str,
142+
"requires_python": python_version,
143+
}
107144

108145
return meta_dict
109146

@@ -131,15 +168,15 @@ def extract_project_dependencies(pyproject_data):
131168
return dependencies_list
132169

133170

134-
def extract_tags_from_url(url):
171+
def extract_tags_from_url(url, major, minor):
135172
"""Extract tags from wheel URL"""
136173
tags = set()
137174

138175
# Python version tags
139-
if "cp313" in url:
140-
tags.add("cp313")
141-
if "py3" in url:
142-
tags.add("py3")
176+
if f"py{major}" in url:
177+
tags.add(f"py{major}")
178+
if f"cp{major}{minor}" in url:
179+
tags.add(f"cp{major}{minor}")
143180

144181
# Platform tags
145182
if "manylinux" in url:
@@ -154,20 +191,20 @@ def extract_tags_from_url(url):
154191
return tags
155192

156193

157-
def is_compatible_wheel(url):
194+
def is_compatible_wheel(url, python_version):
158195
"""Check if wheel is compatible using tag matching"""
159-
wheel_tags = extract_tags_from_url(url)
196+
major, minor = python_version.split(".")
197+
wheel_tags = extract_tags_from_url(url, major, minor)
160198

161-
# Define compatible tag combinations
162-
compatible_python = {"cp313", "py3"}
163-
# Architecture-free or linux
164-
compatible_platforms = {"manylinux", "none", "any", "x86_64"}
199+
# Check Python compatibility
200+
has_required_python = any(tag in wheel_tags for tag in [f"py{major}", f"cp{major}{minor}"])
165201

166-
# Check if wheel has required python version AND compatible platform
167-
has_required_python = not wheel_tags.isdisjoint(compatible_python)
168-
has_compatible_platform = not wheel_tags.isdisjoint(compatible_platforms)
202+
# Check platform
203+
has_allowed_platform = ("any" in wheel_tags or "x86_64" in wheel_tags) and (
204+
"manylinux" in wheel_tags or "none" in wheel_tags
205+
)
169206

170-
return has_required_python and has_compatible_platform
207+
return has_required_python and has_allowed_platform
171208

172209

173210
def create_defualt_nix(dependencies_list, meta_dict):
@@ -210,6 +247,7 @@ def create_defualt_nix(dependencies_list, meta_dict):
210247
"""
211248
need_review_packages_list = []
212249
deps_size = len(dependencies_list)
250+
python_version = meta_dict["requires_python"]
213251
for idx, dep in enumerate(dependencies_list):
214252
print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"]))
215253
name = dep["name"]
@@ -257,10 +295,11 @@ def create_defualt_nix(dependencies_list, meta_dict):
257295
url_section = data.get("urls", [])
258296
build_from_src = True
259297
package_added = False
298+
260299
for component in url_section:
261300
if component.get("packagetype") == "bdist_wheel":
262301
whl_url = component.get("url")
263-
if not is_compatible_wheel(whl_url):
302+
if not is_compatible_wheel(whl_url, python_version):
264303
continue
265304
whl_sha256 = get_sha256_hash(whl_url)
266305
nix_content += " " + name + " = buildCustomPackage {\n"
@@ -362,12 +401,20 @@ def create_defualt_nix(dependencies_list, meta_dict):
362401
name = dep["name"]
363402
nix_content += " " + name + "\n"
364403

365-
nix_content += """
404+
nix_content += (
405+
"""
366406
];
367407
368408
meta = with pkgs.lib; {
369-
description = "Automate open source license compliance and ensure supply chain integrity";
370-
license = "AGPL-3.0-only";
409+
description =\""""
410+
+ meta_dict.get(
411+
"description",
412+
"Automate open source license compliance and ensure supply chain integrity.",
413+
)
414+
+ """\";
415+
license = \""""
416+
+ meta_dict.get("license", "AGPL-3.0-only")
417+
+ """\";
371418
maintainers = ["AboutCode.org"];
372419
platforms = platforms.linux;
373420
};
@@ -382,6 +429,7 @@ def create_defualt_nix(dependencies_list, meta_dict):
382429
default = pythonApp;
383430
}
384431
"""
432+
)
385433
return nix_content, need_review_packages_list
386434

387435

@@ -493,6 +541,14 @@ def main():
493541
# Parse arguments
494542
args = parser.parse_args()
495543

544+
# No argument is provided
545+
if len(sys.argv) == 1 and Path("default.nix").exists():
546+
print("Info: 'default.nix' exists and no arguments provided.")
547+
print("Options:")
548+
print(" --generate Re-generate default.nix")
549+
print(" --test Test build with existing default.nix")
550+
sys.exit(0)
551+
496552
if args.generate or not Path("default.nix").exists():
497553
# Check if "nix-prefetch-url" is available
498554
if not shutil.which("nix-prefetch-url"):
@@ -502,6 +558,7 @@ def main():
502558
print("Generating default.nix")
503559
pyproject_data = read_pyproject_toml()
504560
meta_dict = extract_project_meta(pyproject_data)
561+
505562
dependencies_list = extract_project_dependencies(pyproject_data)
506563
defualt_nix_content, need_review = create_defualt_nix(dependencies_list, meta_dict)
507564
with open("default.nix", "w") as file:

0 commit comments

Comments
 (0)