Skip to content

Commit 201f435

Browse files
committed
Move build_nix_docker.py to etc/scripts #339
- Added --generate and --test options - Updated code structure/comments Signed-off-by: Chin Yeung Li <[email protected]>
1 parent ed52e44 commit 201f435

File tree

1 file changed

+113
-93
lines changed

1 file changed

+113
-93
lines changed
Lines changed: 113 additions & 93 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
#!/usr/bin/env python3
22
"""
3-
Use Docker container to run nix-build.
4-
Using Docker approach to ensure a consistent and isolated build environment.
3+
Generates a Nix expression (default.nix) from the
4+
pyproject.toml file that is used to build the Python package for NixOS and
5+
put it under the project root.
56
67
Requirement: `toml` and `requests` Python packages and Docker installed.
78
@@ -11,15 +12,38 @@
1112
1213
python build_nix_docker.py
1314
14-
or
15+
It will create a `default.nix` file in the project root if it does not
16+
already exist.
17+
18+
Options:
19+
--------
20+
`--generate` - Creates or overwrites default.nix in the project root.
1521
1622
python build_nix_docker.py --generate
1723
18-
The --generate flag is optional and can be used to generate the
19-
default.nix file if needed.
24+
`--test` - Tests the build using Docker.
25+
26+
python build_nix_docker.py --test
27+
28+
29+
The `--test` flag will use Docker to run the Nix build in a clean
30+
environment. It will run `nix-build` inside a Docker container to ensure
31+
that the default.nix file is valid and can successfully build the package.
32+
It will then do cleanup by removing the `nix-store` Docker volume.
33+
34+
35+
Once the default.nix is generated, one can build/install the package by
36+
using:
37+
38+
Build the package
39+
40+
nix-build default.nix
2041
21-
This script will run nix-build and place the built results in the
22-
dist/nix/ directory, it will then run nix-collect-garbage for cleanup.
42+
The above command will create a symlink named `result` in the current
43+
directory pointing to the build output in the Nix store.
44+
Run the binary directly
45+
46+
./result/bin/dejacode
2347
"""
2448

2549
import argparse
@@ -123,59 +147,10 @@ def create_defualt_nix(dependencies_list, meta_dict):
123147
version = dep["version"]
124148
# Handle 'django_notifications_patched','django-rest-hooks' and 'funcparserlib' separately
125149
if (
126-
not name == "django-rest-hooks"
127-
and not name == "django_notifications_patched"
128-
and not name == "funcparserlib"
150+
name == "django-rest-hooks"
151+
or name == "django_notifications_patched"
152+
or (name == "funcparserlib" and version == "0.3.6")
129153
):
130-
url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version)
131-
try:
132-
response = requests.get(url, timeout=30)
133-
response.raise_for_status()
134-
data = response.json()
135-
136-
url_section = data.get("urls", [])
137-
build_from_src = True
138-
package_added = False
139-
for component in url_section:
140-
if component.get("packagetype") == "bdist_wheel":
141-
whl_url = component.get("url")
142-
whl_sha256 = get_sha256_hash(whl_url)
143-
nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n"
144-
nix_content += ' pname = "' + name + '";\n'
145-
nix_content += ' version = "' + version + '";\n'
146-
nix_content += ' format = "wheel";\n'
147-
nix_content += " src = pkgs.fetchurl {\n"
148-
nix_content += ' url = "' + whl_url + '";\n'
149-
nix_content += ' sha256 = "' + whl_sha256 + '";\n'
150-
nix_content += " };\n"
151-
nix_content += " };\n"
152-
build_from_src = False
153-
package_added = True
154-
break
155-
156-
if build_from_src:
157-
for component in url_section:
158-
if component.get("packagetype") == "sdist":
159-
sdist_url = component.get("url")
160-
sdist_sha256 = get_sha256_hash(sdist_url)
161-
nix_content += (
162-
" " + name + " = disableAllTests super." + name + " {\n"
163-
)
164-
nix_content += ' pname = "' + name + '";\n'
165-
nix_content += ' version = "' + version + '";\n'
166-
nix_content += " __intentionallyOverridingVersion = true;\n"
167-
nix_content += " src = pkgs.fetchurl {\n"
168-
nix_content += ' url = "' + sdist_url + '";\n'
169-
nix_content += ' sha256 = "' + sdist_sha256 + '";\n'
170-
nix_content += " };\n"
171-
nix_content += " };\n"
172-
package_added = True
173-
break
174-
if not package_added:
175-
need_review_packages_list.append(dep)
176-
except requests.exceptions.RequestException:
177-
need_review_packages_list.append(dep)
178-
else:
179154
if name == "django-rest-hooks" and version == "1.6.1":
180155
nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n"
181156
nix_content += ' pname = "django-rest-hooks";\n'
@@ -204,6 +179,8 @@ def create_defualt_nix(dependencies_list, meta_dict):
204179
)
205180
nix_content += " };\n"
206181
nix_content += " };\n"
182+
# This section can be removed once funcparserlib is updated to >=1.0.0
183+
# https://github.com/aboutcode-org/dejacode/issues/394
207184
elif name == "funcparserlib" and version == "0.3.6":
208185
nix_content += " " + name + " = self.buildPythonPackage rec {\n"
209186
nix_content += ' pname = "funcparserlib";\n'
@@ -242,6 +219,56 @@ def create_defualt_nix(dependencies_list, meta_dict):
242219
nix_content += " };\n"
243220
else:
244221
need_review_packages_list.append(dep)
222+
else:
223+
url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version)
224+
try:
225+
response = requests.get(url, timeout=30)
226+
response.raise_for_status()
227+
data = response.json()
228+
229+
url_section = data.get("urls", [])
230+
build_from_src = True
231+
package_added = False
232+
for component in url_section:
233+
if component.get("packagetype") == "bdist_wheel":
234+
whl_url = component.get("url")
235+
whl_sha256 = get_sha256_hash(whl_url)
236+
nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n"
237+
nix_content += ' pname = "' + name + '";\n'
238+
nix_content += ' version = "' + version + '";\n'
239+
nix_content += ' format = "wheel";\n'
240+
nix_content += " src = pkgs.fetchurl {\n"
241+
nix_content += ' url = "' + whl_url + '";\n'
242+
nix_content += ' sha256 = "' + whl_sha256 + '";\n'
243+
nix_content += " };\n"
244+
nix_content += " };\n"
245+
build_from_src = False
246+
package_added = True
247+
break
248+
249+
if build_from_src:
250+
for component in url_section:
251+
if component.get("packagetype") == "sdist":
252+
sdist_url = component.get("url")
253+
sdist_sha256 = get_sha256_hash(sdist_url)
254+
nix_content += (
255+
" " + name + " = disableAllTests super." + name + " {\n"
256+
)
257+
nix_content += ' pname = "' + name + '";\n'
258+
nix_content += ' version = "' + version + '";\n'
259+
nix_content += " __intentionallyOverridingVersion = true;\n"
260+
nix_content += " src = pkgs.fetchurl {\n"
261+
nix_content += ' url = "' + sdist_url + '";\n'
262+
nix_content += ' sha256 = "' + sdist_sha256 + '";\n'
263+
nix_content += " };\n"
264+
nix_content += " };\n"
265+
package_added = True
266+
break
267+
if not package_added:
268+
need_review_packages_list.append(dep)
269+
except requests.exceptions.RequestException:
270+
need_review_packages_list.append(dep)
271+
245272
nix_content += """
246273
};
247274
pythonWithOverlay = python.override {
@@ -360,10 +387,6 @@ def cleanup_nix_store():
360387

361388

362389
def build_nix_with_docker():
363-
# Create output directory
364-
output_dir = Path("dist/nix")
365-
output_dir.mkdir(parents=True, exist_ok=True)
366-
367390
docker_cmd = [
368391
"docker",
369392
"run",
@@ -377,49 +400,34 @@ def build_nix_with_docker():
377400
"nixos/nix",
378401
"/bin/sh",
379402
"-c",
380-
"""set -ex
403+
"""set -e
381404
# Update nix-channel to get latest packages
382-
nix-channel --update
405+
nix-channel --update > /dev/null 2>&1
383406
384-
# Run nix-build
385-
nix-build default.nix -o result
407+
# Run nix-build, only show errors
408+
nix-build default.nix -o result 2>&1 | grep -E "(error|fail|Error|Fail)" || true
386409
387410
# Check if build was successful
388411
if [ -d result ]; then
389-
# Copy the build result to dist/nix/
390-
mkdir -p /workspace/dist/nix
391-
# Use nix-store to get the actual store path
392-
STORE_PATH=$(readlink result)
393-
cp -r "$STORE_PATH"/* /workspace/dist/nix/ || true
394-
395-
# Also copy the symlink target directly if directory copy fails
396-
if [ ! "$(ls -A /workspace/dist/nix/)" ]; then
397-
# If directory is empty, try to copy the store path itself
398-
cp -r "$STORE_PATH" /workspace/dist/nix/store_result || true
399-
fi
400-
412+
echo "Build successfully using default.nix."
413+
echo "Performing cleanup..."
414+
# Perform cleanup
401415
# Remove the result symlink
402416
rm -f result
403417
404418
# Run garbage collection to clean up
405-
nix-collect-garbage -d
406-
419+
# supress logs
420+
nix-collect-garbage -d > /dev/null 2>&1
421+
echo "Cleanup completed."
407422
else
408-
echo "Error: nix-build failed - result directory not found" >&2
423+
echo "Error: nix-build failed" >&2
409424
exit 1
410425
fi
411426
""",
412427
]
413428

414429
try:
415430
subprocess.run(docker_cmd, check=True, shell=False) # noqa: S603
416-
# Verify if the output directory contains any files or
417-
# subdirectories.
418-
if any(output_dir.iterdir()):
419-
print(f"\nNix build completed. Results in: {output_dir}")
420-
else:
421-
print("Nix build failed.", file=sys.stderr)
422-
423431
except subprocess.CalledProcessError as e:
424432
print(f"Build failed: {e}", file=sys.stderr)
425433
sys.exit(1)
@@ -431,9 +439,20 @@ def main():
431439
print("Error: Docker not found. Please install Docker first.", file=sys.stderr)
432440
sys.exit(1)
433441

434-
parser = argparse.ArgumentParser(description="Package to Nix using Docker.")
442+
# Get the directory where the current script is located (which is located in etc/scripts)
443+
script_dir = Path(__file__).parent.resolve()
444+
# Go up two levels from etc/scripts/
445+
project_root = script_dir.parent.parent
446+
os.chdir(project_root)
447+
448+
parser = argparse.ArgumentParser(description="Package to Nix")
449+
# Add optional arguments
435450
parser.add_argument("--generate", action="store_true", help="Generate the default.nix file.")
451+
parser.add_argument(
452+
"--test", action="store_true", help="Test to build from the default.nix file."
453+
)
436454

455+
# Parse arguments
437456
args = parser.parse_args()
438457

439458
if args.generate or not Path("default.nix").exists():
@@ -464,9 +483,10 @@ def main():
464483
)
465484
sys.exit(1)
466485

467-
# Clean up the volume to ensure consistent state
468-
cleanup_nix_store()
469-
build_nix_with_docker()
486+
if args.test:
487+
print("Testing the default.nix build...")
488+
cleanup_nix_store()
489+
build_nix_with_docker()
470490

471491

472492
if __name__ == "__main__":

0 commit comments

Comments
 (0)