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
67Requirement: `toml` and `requests` Python packages and Docker installed.
78
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
2549import 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
362389def 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"\n Nix 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
472492if __name__ == "__main__" :
0 commit comments