From dc303a0a1d54482fb6065de158106d36deea8d60 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Thu, 9 Oct 2025 18:05:38 +0800 Subject: [PATCH 01/15] Created default.nix for build-nix packaging #339 Signed-off-by: Chin Yeung Li --- default.nix | 927 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 927 insertions(+) create mode 100644 default.nix diff --git a/default.nix b/default.nix new file mode 100644 index 00000000..46eb3f4a --- /dev/null +++ b/default.nix @@ -0,0 +1,927 @@ +{ + pkgs ? import { }, +}: + +let + python = pkgs.python313; + + # Helper function to override a package to disable tests + disableAllTests = + package: extraAttrs: + package.overrideAttrs ( + old: + { + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + checkPhase = "echo 'Tests disabled'"; + installCheckPhase = "echo 'Install checks disabled'"; + pytestCheckPhase = "echo 'Pytest checks disabled'"; + __intentionallyOverridingVersion = old.__intentionallyOverridingVersion or false; + } + // extraAttrs + ); + + maturin = pkgs.stdenv.mkDerivation rec { + pname = "maturin"; + version = "1.8.6"; + + src = pkgs.fetchurl { + url = "https://github.com/PyO3/maturin/releases/download/v1.8.6/maturin-x86_64-unknown-linux-musl.tar.gz"; + sha256 = "0vxhniz9shj6gy3sxc8d2bdp24z3vjj435sn31mcvr06r1wwgjjm"; + }; + + # Don't use phases, just unpack directly + unpackPhase = "true"; + + installPhase = '' + mkdir -p $out/bin + # Extract the tarball directly to the bin directory + tar -xzf $src -C $out/bin + # Ensure the binary is executable + chmod +x $out/bin/maturin + ''; + }; + + pythonOverlay = self: super: { + rq-scheduler = python.pkgs.buildPythonPackage rec { + pname = "rq-scheduler"; + version = "0.14.0"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/bb/d0/28cedca9f3b321f30e69d644c2dcd7097ec21570ec9606fde56750621300/rq_scheduler-0.14.0-py2.py3-none-any.whl"; + sha256 = "03fwqc7v4sp8jxmpnwyvacr7zqgikafz0hg0apzv64cc7ld25v6l"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "RQ Scheduler is a small package that adds job scheduling capabilities to RQ, a Redis based Python queuing library."; + license = licenses.mit; + homepage = "https://github.com/rq/rq-scheduler"; + }; + }; + + clamd = python.pkgs.buildPythonPackage rec { + pname = "clamd"; + version = "1.0.2"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/3d/d0/84614de2a53ad52370adc9f9260bea420e53e0c228a248ec0eacfa65ccbb/clamd-1.0.2-py2.py3-none-any.whl"; + sha256 = "1rzmrwywx6rnzb62ca08xn0gkyq0kvbqka00pvb0zc0ygmmm8cjw"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "clamd is a portable Python module to use the ClamAV anti-virus engine on Windows, Linux, MacOSX and other platforms."; + license = licenses.lgpl21; + homepage = "https://github.com/graingert/python-clamd"; + }; + }; + + mockldap = python.pkgs.buildPythonPackage rec { + pname = "mockldap"; + version = "0.3.0.post1"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/e3/6e/1536bc788db4cbccf3f2ffb37737af5e90f163ce69858f5aa1275981ed8a/mockldap-0.3.0.post1-py2.py3-none-any.whl"; + sha256 = "1n9q7girlpm97rva515q2y04bx2rhn431m996vc9b7xycq16mpnd"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "The goal of mockldap is to provide a mock instance of LDAPObject in response to any call to ldap.initialize."; + license = licenses.cc0; + homepage = "https://github.com/psagers/mockldap"; + }; + }; + + swapper = python.pkgs.buildPythonPackage rec { + pname = "swapper"; + version = "1.4.0"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/e9/53/c59363308ef97507a680372471e25e1ebab2e706a45a7c416eea6474c928/swapper-1.4.0-py2.py3-none-any.whl"; + sha256 = "0pilp6agh0gfi0y0pllk6jv29vghrzlccbg35xa44hi3mn53gf2p"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "Swapper is an unofficial API for the undocumented but very powerful Django feature: swappable models."; + license = licenses.mit; + homepage = "https://github.com/openwisp/django-swappable-models"; + }; + }; + + django-rest-hooks = python.pkgs.buildPythonPackage rec { + pname = "django-rest-hooks"; + version = "1.6.1"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://github.com/aboutcode-org/django-rest-hooks/releases/download/1.6.1/django_rest_hooks-1.6.1-py2.py3-none-any.whl"; + sha256 = "1byakq3ghpqhm0mjjkh8v5y6g3wlnri2vvfifyi9ky36l12vqx74"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "Forked from zapier/django-rest-hooks."; + license = licenses.isc; + homepage = "https://github.com/aboutcode-org/django-rest-hooks"; + }; + }; + + aboutcode-toolkit = python.pkgs.buildPythonPackage rec { + pname = "aboutcode-toolkit"; + version = "11.1.1"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/14/ee/ba139231e3de1287189c2f7940e7e0f8a135421050ff1b4f0145813ae8b9/aboutcode_toolkit-11.1.1-py3-none-any.whl"; + sha256 = "0bx32ca9m01grwn69594jb8fgcqbm3wnviadig5iw1fxx3hpgpmy"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "The AboutCode Toolkit and ABOUT files provide a simple way to document the origin, license, usage and other important or interesting information about third-party software components that you use in your project."; + license = licenses.asl20; + homepage = "https://github.com/aboutcode-org/aboutcode-toolkit"; + }; + }; + + django-grappelli = python.pkgs.buildPythonPackage rec { + pname = "django-grappelli"; + version = "4.0.2"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/79/66/bec8c43767f830d8c4884d8350d81e28043d3a04364b9fca43946d98a47e/django_grappelli-4.0.2-py2.py3-none-any.whl"; + sha256 = "1515f13pm2lc8830zsrjznsfbfnh6xfmp6gvzxcx6nh2ryiqf2pd"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "A jazzy skin for the Django admin interface."; + license = licenses.bsd3; + homepage = "http://www.grappelliproject.com/"; + }; + }; + + django-altcha = python.pkgs.buildPythonPackage rec { + pname = "django-altcha"; + version = "0.3.0"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/9e/6e/ad184a95de2652f5834243f5b3403c8949993f5f3de08a5609b15a63b091/django_altcha-0.3.0-py3-none-any.whl"; + sha256 = "0gcgir468q5ra9blm00vgdij0avpsyscbadg5k69ra7kfr8q8jgw"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "Django Altcha is a Django library that provides easy integration of Altcha CAPTCHA into your Django forms, enhancing user verification with configurable options."; + license = licenses.mit; + homepage = "https://github.com/aboutcode-org/django-altcha"; + }; + }; + + jsonfield = python.pkgs.buildPythonPackage rec { + pname = "jsonfield"; + version = "3.1.0"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/7c/97/3a4805532a9c1982368fd9f37b58133419e83704744b733ccab9e9827176/jsonfield-3.1.0-py3-none-any.whl"; + sha256 = "1vc9ss6k182qcfy70y54lyca6w2yh012x97vpabjn9bzb08pi1fz"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "jsonfield is a reusable model field that allows you to store validated JSON, automatically handling serialization to and from the database."; + license = licenses.mit; + homepage = "https://github.com/rpkilby/jsonfield/"; + }; + }; + + pip = python.pkgs.buildPythonPackage rec { + pname = "pip"; + version = "25.1.1"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl"; + sha256 = "1bsihxacxq9i14dv0x6y3vf56fvzjsgbs1xm9avackmz5a5a64r9"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "The PyPA recommended tool for installing Python packages"; + license = licenses.mit; + homepage = "https://pip.pypa.io/"; + }; + }; + + psycopg = python.pkgs.buildPythonPackage rec { + pname = "psycopg"; + version = "3.2.9"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl"; + sha256 = "1dnkm33n75phjda008kbav0425k30rpcj232j4y15hmarpfdma01"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "A modern implementation of a PostgreSQL adapter for Python."; + license = licenses.lgpl3; + homepage = "https://www.psycopg.org/"; + }; + }; + + django-registration = python.pkgs.buildPythonPackage rec { + pname = "django-registration"; + version = "3.4"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/06/3a/455455a208cd38c7fececec136e0de4a848004a7dafe4d62e55566dcbbfe/django_registration-3.4-py3-none-any.whl"; + sha256 = "1l6xn7m4p4fgv1xiv42b35ihkywvrkjkw13kxd3lyyc9254dyxps"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "This is a user-registration application for Django."; + license = licenses.bsd3; + homepage = "https://github.com/ubernostrum/django-registration"; + }; + }; + + rpds-py = python.pkgs.buildPythonPackage rec { + pname = "rpds-py"; + version = "0.25.1"; + format = "wheel"; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/3e/03/5d0be919037178fff33a6672ffc0afa04ea1cfcb61afd4119d1b5280ff0f/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl"; + sha256 = "0yfgi9gb9xrvalxrzrhbh7ak0qfhi2kajw0zrxrmshgy4y2i4jjw"; + }; + + # Disable checks + doCheck = false; + doInstallCheck = false; + + # Meta information + meta = with pkgs.lib; { + description = "Python bindings to the Rust rpds crate for persistent data structures."; + license = licenses.mit; + homepage = "https://github.com/crate-py/rpds"; + }; + }; + + django = self.buildPythonPackage rec { + pname = "django"; + version = "5.2.6"; + format = "pyproject"; + doCheck = false; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/4c/8c/2a21594337250a171d45dda926caa96309d5136becd1f48017247f9cdea0/django-5.2.6.tar.gz"; + sha256 = "0yx82k8iilz8l6wkdvjcrz75i144lf211xybrrrks6b34wvh0pns"; + }; + nativeBuildInputs = with self; [setuptools]; + propagatedBuildInputs = with self; [ + asgiref + sqlparse + ]; + checkPhase = "echo 'Tests disabled for django'"; + }; + + # Custom package from GitHub: https://github.com/dejacode/django-notifications-patched + django-notifications-patched = self.buildPythonPackage rec { + pname = "django-notifications-patched"; + version = "2.0.0"; + format = "setuptools"; + doCheck = false; + + src = pkgs.fetchFromGitHub { + owner = "dejacode"; + repo = "django-notifications-patched"; + rev = "2.0.0"; + url = "https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz"; + sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8="; + }; + + nativeBuildInputs = with self; [setuptools]; + checkPhase = "echo 'Tests disabled for django-notifications-patched'"; + }; + + crontab = disableAllTests super.crontab { + version = "1.0.5"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/d6/36/a255b6f5a2e22df03fd2b2f3088974b44b8c9e9407e26b44742cb7cfbf5b/crontab-1.0.5.tar.gz"; + sha256 = "1ma6ms0drlx6pj4q14jsjvkphwhl2zfjdyb9k0x7c6bjy2s023pq"; + }; + patches = []; + }; + + asgiref = disableAllTests super.asgiref { + version = "3.9.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz"; + sha256 = "0hiiiq4xbm8mn9ykgp789pynqqhhkzyl5wj82vpya6324f16bax5"; + }; + patches = []; + }; + + setuptools = disableAllTests super.setuptools { + version = "80.9.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz"; + sha256 = "175iixi2h2jz8y2bpwziak360hvv43jfhipwzbdniryd5r04fszk"; + }; + patches = []; + }; + + wheel = disableAllTests super.wheel { + version = "0.45.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz"; + sha256 = "0ab7ramncrii43smhvcidrbv4w4ndl80435214a7nl4qj6yil7k6"; + }; + patches = []; + }; + + typing-extensions = disableAllTests super.typing-extensions { + version = "4.14.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz"; + sha256 = "1x7fbkbpjj9xrsxfx6d38kccdr4a0hj17ip7v51an0igwf4bfxl6"; + }; + patches = []; + }; + + django-guardian = disableAllTests super.django-guardian { + version = "3.0.3"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/30/c2/3ed43813dd7313f729dbaa829b4f9ed4a647530151f672cfb5f843c12edf/django_guardian-3.0.3.tar.gz"; + sha256 = "0v8ria6c0iirl1ck2xfzpcnf59629g8pdhghgh15mninv2sflnaf"; + }; + patches = []; + }; + + djangorestframework = disableAllTests super.djangorestframework { + version = "3.16.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/8a/95/5376fe618646fde6899b3cdc85fd959716bb67542e273a76a80d9f326f27/djangorestframework-3.16.1.tar.gz"; + sha256 = "1xrzzjf048llw85vs3a2r7r4k07i594j8v66gnhx1khsid90js0n"; + }; + patches = []; + }; + + uritemplate = disableAllTests super.uritemplate { + version = "4.1.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/d2/5a/4742fdba39cd02a56226815abfa72fe0aa81c33bed16ed045647d6000eba/uritemplate-4.1.1.tar.gz"; + sha256 = "1w14a775d92mx9pdhb5zimifpfr2lfcn0vfdpjagcy9vbkyfsij3"; + }; + patches = []; + }; + + redis = disableAllTests super.redis { + version = "6.4.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz"; + sha256 = "047hbsy9rs44y81wdfbyl13vp0si6zsis9kbqf7f4i445clcf6xh"; + }; + patches = []; + }; + + model-bakery = disableAllTests super.model-bakery { + version = "1.10.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/8f/4c/2fca1e79408308b8f0e71e687ca1e1d3ede450e257e2474e331261fdb106/model_bakery-1.10.1.tar.gz"; + sha256 = "0pmd0jmqbhvpyc52p42kmbn97lgg3zwaky5dcyr4p0izwfjssx0v"; + }; + patches = []; + }; + + fakeredis = disableAllTests super.fakeredis { + version = "2.31.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/0b/10/c829c3475a26005ebf177057fdf54e2a29025ffc2232d02fb1ae8ac1de68/fakeredis-2.31.0.tar.gz"; + sha256 = "08ydbsc2v2i8zs5spa95lg2mlcc718cvj276z5phgn8gj3ksfhi9"; + }; + patches = []; + }; + + freezegun = disableAllTests super.freezegun { + version = "1.5.2"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/c7/75/0455fa5029507a2150da59db4f165fbc458ff8bb1c4f4d7e8037a14ad421/freezegun-1.5.2.tar.gz"; + sha256 = "10aij0mdg4jmqpd396jlif0rahmbjnms661cw11bybf0z79f2jm5"; + }; + patches = []; + }; + + certifi = disableAllTests super.certifi { + version = "2025.8.3"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz"; + sha256 = "01rlwvx3zi9bjfxvbspscdsal7ay8cj3k4kwmvin9mfyg1gi0r75"; + }; + patches = []; + }; + + cython = disableAllTests super.cython { + version = "3.1.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/5b/d3/bb000603e46144db2e5055219bbddcf7ab3b10012fcb342695694fb88141/cython-3.1.1.tar.gz"; + sha256 = "15v36rp426zbgm4hrxn4i2028x3rf0n7jkc3acm17mb96r0wsp2h"; + }; + patches = []; + }; + + zipp = disableAllTests super.zipp { + version = "3.22.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz"; + sha256 = "1rdcax7bi43xmm9la6mm0c8y6axvnwhisy6kpw3pbijbrv1jhbyx"; + }; + patches = []; + }; + + markdown = disableAllTests super.markdown { + version = "3.8"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz"; + sha256 = "0vww67gb1w890iyr6w3xkcira1b9wj0ynmninwj4np6zy1iixy3x"; + }; + patches = []; + }; + + oauthlib = disableAllTests super.oauthlib { + version = "3.2.2"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz"; + sha256 = "066r7mimlpb5q1fr2f1z59l4jc89kv4h2kgkcifyqav6544w8ncq"; + }; + patches = []; + }; + + defusedxml = disableAllTests super.defusedxml { + version = "0.7.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz"; + sha256 = "0s9ym98jrd819v4arv9gmcr6mgljhxd9q866sxi5p4c5n4nh7cqv"; + }; + patches = []; + }; + + funcparserlib = self.buildPythonPackage rec { + pname = "funcparserlib"; + version = "0.3.6"; + format = "setuptools"; + doCheck = false; + + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/cb/f7/b4a59c3ccf67c0082546eaeb454da1a6610e924d2e7a2a21f337ecae7b40/funcparserlib-0.3.6.tar.gz"; + sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp"; + }; + + # Original setpy.py: https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py + # funcparserlib version 0.3.6 uses use_2to3 which is no longer supported in modern setuptools. + # Rewrite the problematic section completely + postPatch = '' + cat > setup.py << EOF + # -*- coding: utf-8 -*- + + from setuptools import setup + + setup( + name='funcparserlib', + version='0.3.6', + packages=['funcparserlib', 'funcparserlib.tests'], + author='Andrey Vlasovskikh', + author_email='andrey.vlasovskikh@gmail.com', + description='Recursive descent parsing library based on functional ' + 'combinators', + license='MIT', + url='http://code.google.com/p/funcparserlib/', + ) + EOF + ''; + + propagatedBuildInputs = with self; []; + checkPhase = "echo 'Tests disabled for funcparserlib'"; + }; + + click = disableAllTests super.click { + version = "8.2.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz"; + sha256 = "00k2ck8g0f5ha26183wkkmnn7151npii7nx1smqx4s6r0p693i17"; + }; + patches = []; + }; + + packageurl-python = disableAllTests super.packageurl-python { + version = "0.17.5"; + __intentionallyOverridingVersion = true; + }; + + jsonschema = disableAllTests super.jsonschema { + version = "4.24.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz"; + sha256 = "15nik6awrcj2y5nlnslabbpyq97cray08c1kh6ldzbhjxdlq0khb"; + }; + patches = []; + }; + + cyclonedx-python-lib = disableAllTests super.cyclonedx-python-lib { + version = "10.2.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/3e/24/86a4949e59f8d79d42beef6041fd6ce842400addcec2f2a1bccb3208a5cd/cyclonedx_python_lib-10.2.0.tar.gz"; + sha256 = "1ralxk93zhyalg099bm6hxsqiisq8ha2rf7khjawz4bzhkd9lymn"; + }; + patches = []; + }; + + py-serializable = disableAllTests super.py-serializable { + version = "2.0.0"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/f0/75/813967eae0542776314c6def33feac687642a193b9d5591c20684b2eafd8/py_serializable-2.0.0.tar.gz"; + sha256 = "1990yhn7a17j3z57r1ivlklgjmwngysk40h5y7d3376jswflkrp9"; + }; + patches = []; + }; + + smmap = disableAllTests super.smmap { + version = "5.0.2"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz"; + sha256 = "1mcai5vf9bgz389y4sqgj6w22wn7zmc7m33y3j50ryjq76h6bsi6"; + }; + patches = []; + }; + + pydantic = disableAllTests super.pydantic { + version = "2.11.5"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz"; + sha256 = "0ykv5aar0xjwfprdy31w8yrk2r6x5hf4130lpf5wwy6fs2rkv1bz"; + }; + patches = []; + }; + + setuptools-rust = disableAllTests super.setuptools-rust { + version = "1.11.1"; + __intentionallyOverridingVersion = true; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/e0/92/bf8589b1a2b6107cf9ec8daa9954c0b7620643fe1f37d31d75e572d995f5/setuptools_rust-1.11.1.tar.gz"; + sha256 = "1h3nbg1nlshzrqy7vz4q4g9wbz85dqkn6385p0ad7kjj48ww9avx"; + }; + patches = []; + }; + + django-axes = disableAllTests super.django-axes { + version = "8.0.0"; + __intentionallyOverridingVersion = true; + }; + + sh = disableAllTests super.sh {}; + pytest-xdist = disableAllTests super.pytest-xdist {}; + altcha = disableAllTests super.altcha {}; + annotated-types = disableAllTests super.annotated-types {}; + async-timeout = disableAllTests super.async-timeout {}; + attrs = disableAllTests super.attrs {}; + bleach = disableAllTests super.bleach {}; + bleach-allowlist = disableAllTests super.bleach-allowlist {}; + boolean-py = disableAllTests super.boolean-py {}; + charset-normalizer = disableAllTests super.charset-normalizer {}; + confusable-homoglyphs = disableAllTests super.confusable-homoglyphs {}; + crispy-bootstrap5 = disableAllTests super.crispy-bootstrap5 {}; + deprecated = disableAllTests super.deprecated {}; + django-auth-ldap = disableAllTests super.django-auth-ldap {}; + django-crispy-forms = disableAllTests super.django-crispy-forms {}; + django-debug-toolbar = disableAllTests super.django-debug-toolbar {}; + django-environ = disableAllTests super.django-environ {}; + django-filter = disableAllTests super.django-filter {}; + django-otp = disableAllTests super.django-otp {}; + django-rq = disableAllTests super.django-rq {}; + doc8 = disableAllTests super.doc8 {}; + docutils = disableAllTests super.docutils {}; + drf-yasg = disableAllTests super.drf-yasg {}; + et-xmlfile = disableAllTests super.et-xmlfile {}; + gitdb = disableAllTests super.gitdb {}; + gitpython = disableAllTests super.gitpython {}; + gunicorn = disableAllTests super.gunicorn {}; + hatchling = disableAllTests super.hatchling {}; + inflection = disableAllTests super.inflection {}; + jinja2 = disableAllTests super.jinja2 {}; + jsonschema-specifications = disableAllTests super.jsonschema-specifications {}; + license-expression = disableAllTests super.license-expression {}; + markupsafe = disableAllTests super.markupsafe {}; + natsort = disableAllTests super.natsort {}; + numpy = disableAllTests super.numpy {}; + openpyxl = disableAllTests super.openpyxl {}; + packaging = disableAllTests super.packaging {}; + pandas = disableAllTests super.pandas {}; + pbr = disableAllTests super.pbr {}; + pillow = disableAllTests super.pillow {}; + platformdirs = disableAllTests super.platformdirs {}; + pyasn1 = disableAllTests super.pyasn1 {}; + pyasn1-modules = disableAllTests super.pyasn1-modules {}; + pycparser = disableAllTests super.pycparser {}; + pydantic-core = disableAllTests super.pydantic-core {}; + pyfakefs = disableAllTests super.pyfakefs {}; + pygments = disableAllTests super.pygments {}; + pyjwt = disableAllTests super.pyjwt {}; + pyparsing = disableAllTests super.pyparsing {}; + pypng = disableAllTests super.pypng {}; + pyrsistent = disableAllTests super.pyrsistent {}; + pytest-randomly = disableAllTests super.pytest-randomly {}; + pytest-regressions = disableAllTests super.pytest-regressions {}; + python3-openid = disableAllTests super.python3-openid {}; + python-dateutil = disableAllTests super.python-dateutil {}; + python-ldap = disableAllTests super.python-ldap {}; + python-mimeparse = disableAllTests super.python-mimeparse {}; + pytz = disableAllTests super.pytz {}; + pyyaml = disableAllTests super.pyyaml {}; + qrcode = disableAllTests super.qrcode {}; + referencing = disableAllTests super.referencing {}; + requests = disableAllTests super.requests {}; + requests-oauthlib = disableAllTests super.requests-oauthlib {}; + restructuredtext-lint = disableAllTests super.restructuredtext-lint {}; + rq = disableAllTests super.rq {}; + ruff = disableAllTests super.ruff {}; + saneyaml = disableAllTests super.saneyaml {}; + semantic-version = disableAllTests super.semantic-version {}; + six = disableAllTests super.six {}; + sortedcontainers = disableAllTests super.sortedcontainers {}; + sqlparse = disableAllTests super.sqlparse {}; + stevedore = disableAllTests super.stevedore {}; + tblib = disableAllTests super.tblib {}; + toml = disableAllTests super.toml {}; + tomli = disableAllTests super.tomli {}; + tqdm = disableAllTests super.tqdm {}; + typing-inspection = disableAllTests super.typing-inspection {}; + urllib3 = disableAllTests super.urllib3 {}; + webencodings = disableAllTests super.webencodings {}; + wrapt = disableAllTests super.wrapt {}; + xlsxwriter = disableAllTests super.xlsxwriter {}; + + }; + + pythonWithOverlay = python.override { + packageOverrides = + self: super: + let + # Override buildPythonPackage to disable tests for ALL packages + base = { + buildPythonPackage = + attrs: + super.buildPythonPackage ( + attrs + // { + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + } + ); + }; + + # Apply custom package overrides + custom = pythonOverlay self super; + in + base // custom; + }; + + pythonApp = pythonWithOverlay.pkgs.buildPythonApplication { + pname = "dejacode"; + version = "5.4.0"; + + src = ./.; + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + + format = "pyproject"; + + nativeBuildInputs = with pythonWithOverlay.pkgs; [ + setuptools + wheel + pip + ]; + + # maturin is a build tool, not a runtime dependency + # It's only needed during the build phase to compile Rust extensions + # It shouldn't be included in the final Python package's dependencies + # and therefore can be excluded from the final dependencies check + pythonRemoveDeps = ["maturin"]; + + propagatedBuildInputs = with pythonWithOverlay.pkgs; [ + crontab + django-filter + natsort + jsonschema + freezegun + docutils + bleach-allowlist + django-environ + pyrsistent + djangorestframework + confusable-homoglyphs + pygments + async-timeout + semantic-version + markupsafe + click + sortedcontainers + toml + cyclonedx-python-lib + six + packaging + clamd + django-guardian + altcha + django-rest-hooks + gitpython + stevedore + openpyxl + qrcode + django-rq + bleach + license-expression + model-bakery + mockldap + pytz + django-notifications-patched + pyparsing + funcparserlib + typing-extensions + smmap + saneyaml + requests-oauthlib + oauthlib + pyyaml + et-xmlfile + uritemplate + xlsxwriter + tblib + django-crispy-forms + defusedxml + jsonschema-specifications + doc8 + python3-openid + redis + django-axes + pyasn1-modules + swapper + cython + markdown + py-serializable + idna + packageurl-python + rpds-py + rq + django-auth-ldap + typing-inspection + urllib3 + certifi + sqlparse + wrapt + boolean-py + drf-yasg + fakeredis + inflection + django-otp + gitdb + crispy-bootstrap5 + python-mimeparse + attrs + django-debug-toolbar + jsonfield + aboutcode-toolkit + rq-scheduler + zipp + jinja2 + pyjwt + gunicorn + django-grappelli + python-ldap + ruff + pydantic + restructuredtext-lint + referencing + annotated-types + pypng + django-registration + django-altcha + python-dateutil + pydantic-core + pyasn1 + setuptools-rust + requests + pbr + webencodings + deprecated + charset-normalizer + psycopg + asgiref + ]; + + meta = with pkgs.lib; { + description = "Automate open source license compliance and ensure supply chain integrity"; + license = "AGPL-3.0-only"; + maintainers = ["AboutCode.org"]; + platforms = platforms.linux; + }; + }; + +in +{ + # Default output is the Python application + app = pythonApp; + + # Default to the application + default = pythonApp; +} From ae2cb2cad3f106a95c8b44404ee2de2049509e73 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Fri, 10 Oct 2025 17:40:49 +0800 Subject: [PATCH 02/15] Add Docker script to run build_nix and update default.nix comments #339 Signed-off-by: Chin Yeung Li --- build_nix_docker.py | 111 ++++++++++++++++++++++++++++++++ default.nix | 152 +++++++++++--------------------------------- 2 files changed, 149 insertions(+), 114 deletions(-) create mode 100644 build_nix_docker.py diff --git a/build_nix_docker.py b/build_nix_docker.py new file mode 100644 index 00000000..5c9dacb4 --- /dev/null +++ b/build_nix_docker.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python3 +""" +Use Docker container to run nix-build. +Using Docker approach to ensure a consistent and isolated build environment. + +To run the script: + + python build_nix_docker.py + +This script will run nix-build and place the built results in the +dist/nix/ directory, it will then run nix-collect-garbage for cleanup. +""" + +import os +import shutil +import subprocess +import sys +from pathlib import Path + + +def cleanup_nix_store(): + """ + Remove the nix-store volume to ensure clean state + """ + try: + subprocess.run(['docker', 'volume', 'rm', 'nix-store'], + check=True, capture_output=True) + print("Cleaned up nix-store volume.") + except subprocess.CalledProcessError as e: + # Volume might not exist, which is fine + if "no such volume" not in e.stderr.decode().lower(): + print(f"Warning: Could not remove nix-store volume: {e.stderr.decode()}") + pass + + +def build_nix_with_docker(): + # Create output directory + output_dir = Path('dist/nix') + output_dir.mkdir(parents=True, exist_ok=True) + + docker_cmd = [ + 'docker', 'run', '--rm', + '-v', f"{os.getcwd()}:/workspace", + '-v', 'nix-store:/nix', + '-w', '/workspace', + 'nixos/nix', + '/bin/sh', '-c', + f"""set -ex + # Update nix-channel to get latest packages + nix-channel --update + + # Run nix-build + nix-build default.nix -o result + + # Check if build was successful + if [ -d result ]; then + # Copy the build result to dist/nix/ + mkdir -p /workspace/dist/nix + # Use nix-store to get the actual store path + STORE_PATH=$(readlink result) + cp -r "$STORE_PATH"/* /workspace/dist/nix/ || true + + # Also copy the symlink target directly if directory copy fails + if [ ! "$(ls -A /workspace/dist/nix/)" ]; then + # If directory is empty, try to copy the store path itself + cp -r "$STORE_PATH" /workspace/dist/nix/store_result || true + fi + + # Remove the result symlink + rm -f result + + # Run garbage collection to clean up + nix-collect-garbage -d + + else + echo "Error: nix-build failed - result directory not found" >&2 + exit 1 + fi + """ + ] + + try: + subprocess.run(docker_cmd, check=True) + + # Verify if the output directory contains any files or + # subdirectories. + if any(output_dir.iterdir()): + print(f"\nNix build completed. Results in: {output_dir}") + else: + print(f"Nix build failed.", file=sys.stderr) + + except subprocess.CalledProcessError as e: + print(f"Build failed: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == '__main__': + # Check if "docker" is available + if not shutil.which('docker'): + print("Error: Docker not found. Please install Docker first.", file=sys.stderr) + sys.exit(1) + + # Check if default.nix exists + if not Path('default.nix').exists(): + print("Error: default.nix not found in current directory", file=sys.stderr) + sys.exit(1) + + # Clean up the volume to ensure consistent state + cleanup_nix_store() + + build_nix_with_docker() diff --git a/default.nix b/default.nix index 46eb3f4a..331a7764 100644 --- a/default.nix +++ b/default.nix @@ -1,3 +1,12 @@ +# This file is used by `nix-build`. +# Update it manually whenever the version or dependencies change in `pyproject.toml`. + +# For packages with pinned versions to match those in pyproject.toml. +# It's recommended to use pre-built wheels instead of building from source, +# as source builds may require additional dependencies. + +# Run the following command to compute the sha256: +# nix-prefetch-url { pkgs ? import { }, }: @@ -23,28 +32,22 @@ let // extraAttrs ); - maturin = pkgs.stdenv.mkDerivation rec { - pname = "maturin"; - version = "1.8.6"; - - src = pkgs.fetchurl { - url = "https://github.com/PyO3/maturin/releases/download/v1.8.6/maturin-x86_64-unknown-linux-musl.tar.gz"; - sha256 = "0vxhniz9shj6gy3sxc8d2bdp24z3vjj435sn31mcvr06r1wwgjjm"; - }; + pythonOverlay = self: super: { + maturin = python.pkgs.buildPythonPackage rec { + pname = "maturin"; + version = "1.8.6"; + format = "wheel"; - # Don't use phases, just unpack directly - unpackPhase = "true"; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/f9/aa/8090f8b3f5f7ec46bc95deb0f5b29bf52c98156ef594f2e65d20bf94cea1/maturin-1.8.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl"; + sha256 = "15870w46liwy15a5x81cpdb8f9b6k4sawzxii604r5bmcj699idy"; + }; - installPhase = '' - mkdir -p $out/bin - # Extract the tarball directly to the bin directory - tar -xzf $src -C $out/bin - # Ensure the binary is executable - chmod +x $out/bin/maturin - ''; - }; + # Disable checks + doCheck = false; + doInstallCheck = false; + }; - pythonOverlay = self: super: { rq-scheduler = python.pkgs.buildPythonPackage rec { pname = "rq-scheduler"; version = "0.14.0"; @@ -58,13 +61,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "RQ Scheduler is a small package that adds job scheduling capabilities to RQ, a Redis based Python queuing library."; - license = licenses.mit; - homepage = "https://github.com/rq/rq-scheduler"; - }; }; clamd = python.pkgs.buildPythonPackage rec { @@ -80,13 +76,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "clamd is a portable Python module to use the ClamAV anti-virus engine on Windows, Linux, MacOSX and other platforms."; - license = licenses.lgpl21; - homepage = "https://github.com/graingert/python-clamd"; - }; }; mockldap = python.pkgs.buildPythonPackage rec { @@ -102,13 +91,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "The goal of mockldap is to provide a mock instance of LDAPObject in response to any call to ldap.initialize."; - license = licenses.cc0; - homepage = "https://github.com/psagers/mockldap"; - }; }; swapper = python.pkgs.buildPythonPackage rec { @@ -124,13 +106,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "Swapper is an unofficial API for the undocumented but very powerful Django feature: swappable models."; - license = licenses.mit; - homepage = "https://github.com/openwisp/django-swappable-models"; - }; }; django-rest-hooks = python.pkgs.buildPythonPackage rec { @@ -146,13 +121,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "Forked from zapier/django-rest-hooks."; - license = licenses.isc; - homepage = "https://github.com/aboutcode-org/django-rest-hooks"; - }; }; aboutcode-toolkit = python.pkgs.buildPythonPackage rec { @@ -168,13 +136,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "The AboutCode Toolkit and ABOUT files provide a simple way to document the origin, license, usage and other important or interesting information about third-party software components that you use in your project."; - license = licenses.asl20; - homepage = "https://github.com/aboutcode-org/aboutcode-toolkit"; - }; }; django-grappelli = python.pkgs.buildPythonPackage rec { @@ -190,13 +151,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "A jazzy skin for the Django admin interface."; - license = licenses.bsd3; - homepage = "http://www.grappelliproject.com/"; - }; }; django-altcha = python.pkgs.buildPythonPackage rec { @@ -212,13 +166,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "Django Altcha is a Django library that provides easy integration of Altcha CAPTCHA into your Django forms, enhancing user verification with configurable options."; - license = licenses.mit; - homepage = "https://github.com/aboutcode-org/django-altcha"; - }; }; jsonfield = python.pkgs.buildPythonPackage rec { @@ -234,13 +181,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "jsonfield is a reusable model field that allows you to store validated JSON, automatically handling serialization to and from the database."; - license = licenses.mit; - homepage = "https://github.com/rpkilby/jsonfield/"; - }; }; pip = python.pkgs.buildPythonPackage rec { @@ -256,13 +196,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "The PyPA recommended tool for installing Python packages"; - license = licenses.mit; - homepage = "https://pip.pypa.io/"; - }; }; psycopg = python.pkgs.buildPythonPackage rec { @@ -278,13 +211,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "A modern implementation of a PostgreSQL adapter for Python."; - license = licenses.lgpl3; - homepage = "https://www.psycopg.org/"; - }; }; django-registration = python.pkgs.buildPythonPackage rec { @@ -300,13 +226,21 @@ let # Disable checks doCheck = false; doInstallCheck = false; + }; + + requests = python.pkgs.buildPythonPackage rec { + pname = "requests"; + version = "2.32.4"; + format = "wheel"; - # Meta information - meta = with pkgs.lib; { - description = "This is a user-registration application for Django."; - license = licenses.bsd3; - homepage = "https://github.com/ubernostrum/django-registration"; + src = pkgs.fetchurl { + url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl"; + sha256 = "0b1bmhqv0xarifclr53icqwpsw1hk3l4w8230jrm0v9av8ybvfi7"; }; + + # Disable checks + doCheck = false; + doInstallCheck = false; }; rpds-py = python.pkgs.buildPythonPackage rec { @@ -322,13 +256,6 @@ let # Disable checks doCheck = false; doInstallCheck = false; - - # Meta information - meta = with pkgs.lib; { - description = "Python bindings to the Rust rpds crate for persistent data structures."; - license = licenses.mit; - homepage = "https://github.com/crate-py/rpds"; - }; }; django = self.buildPythonPackage rec { @@ -726,7 +653,6 @@ let pyyaml = disableAllTests super.pyyaml {}; qrcode = disableAllTests super.qrcode {}; referencing = disableAllTests super.referencing {}; - requests = disableAllTests super.requests {}; requests-oauthlib = disableAllTests super.requests-oauthlib {}; restructuredtext-lint = disableAllTests super.restructuredtext-lint {}; rq = disableAllTests super.rq {}; @@ -774,6 +700,7 @@ let base // custom; }; + pythonApp = pythonWithOverlay.pkgs.buildPythonApplication { pname = "dejacode"; version = "5.4.0"; @@ -792,13 +719,10 @@ let pip ]; - # maturin is a build tool, not a runtime dependency - # It's only needed during the build phase to compile Rust extensions - # It shouldn't be included in the final Python package's dependencies - # and therefore can be excluded from the final dependencies check - pythonRemoveDeps = ["maturin"]; + # Specifies all Python dependencies required at runtime to ensure consistent overrides. propagatedBuildInputs = with pythonWithOverlay.pkgs; [ + maturin crontab django-filter natsort From 86aca1530ec17cd0c3b6a80eb5d58dbf1fc7e3ee Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 14 Oct 2025 13:48:35 +0800 Subject: [PATCH 03/15] Remove default.nix and add script to generate the default.nix #339 Signed-off-by: Chin Yeung Li --- build_nix_docker.py | 354 +++++++++++++++++- default.nix | 851 -------------------------------------------- 2 files changed, 347 insertions(+), 858 deletions(-) delete mode 100644 default.nix diff --git a/build_nix_docker.py b/build_nix_docker.py index 5c9dacb4..9386057a 100644 --- a/build_nix_docker.py +++ b/build_nix_docker.py @@ -3,21 +3,338 @@ Use Docker container to run nix-build. Using Docker approach to ensure a consistent and isolated build environment. +Requirement: `toml` and `requests` Python packages and Docker installed. + + pip install toml requests + To run the script: python build_nix_docker.py +or + + python build_nix_docker.py --generate + + The --generate flag is optional and can be used to generate the + default.nix file if needed. + This script will run nix-build and place the built results in the dist/nix/ directory, it will then run nix-collect-garbage for cleanup. """ +import argparse import os +import requests import shutil import subprocess import sys from pathlib import Path +def read_pyproject_toml(): + """ + Read the pyproject.toml file to extract project metadata. + """ + import toml + + pyproject_path = Path('pyproject.toml') + if not pyproject_path.exists(): + print("Error: pyproject.toml not found in current directory", file=sys.stderr) + sys.exit(1) + + with pyproject_path.open('r') as f: + pyproject_data = toml.load(f) + + return pyproject_data + +def extract_project_meta(pyproject_data): + """ + Extract project metadata from pyproject.toml data. + """ + project_data = pyproject_data['project'] + name = project_data.get('name') + version = project_data.get('version') + description = project_data.get('description') + authors = project_data.get('authors') + author_names = [author.get('name', '') for author in authors if 'name' in author] + author_str = ', '.join(author_names) + + meta_dict = { + 'name': name, + 'version': version, + 'description': description, + 'author': author_str + } + + return meta_dict + + +def extract_project_dependencies(pyproject_data): + """ + Extract project dependencies from pyproject.toml data. + """ + project_data = pyproject_data['project'] + dependencies = project_data.get('dependencies', []) + optional_dependencies = project_data.get('optional-dependencies', {}) + dev_optional_deps = optional_dependencies.get('dev', []) + all_dep = dependencies + dev_optional_deps + dependencies_list = [] + + for dep in all_dep: + name_version = dep.split('==') + name = name_version[0] + version = name_version[1] + tmp_dict = {} + tmp_dict['name'] = name + tmp_dict['version'] = version + dependencies_list.append(tmp_dict) + + assert len(all_dep) == len(dependencies_list), "Dependency extraction mismatch" + return dependencies_list + + +def create_defualt_nix(dependencies_list, meta_dict): + """ + Create a default.nix + """ + nix_content = """ +{ + pkgs ? import { }, +}: + +let + python = pkgs.python313; + + # Helper function to override a package to disable tests + disableAllTests = + package: extraAttrs: + package.overrideAttrs ( + old: + { + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + checkPhase = "echo 'Tests disabled'"; + installCheckPhase = "echo 'Install checks disabled'"; + pytestCheckPhase = "echo 'Pytest checks disabled'"; + __intentionallyOverridingVersion = old.__intentionallyOverridingVersion or false; + } + // extraAttrs + ); + + pythonOverlay = self: super: { +""" + need_review_packages_list = [] + deps_size = len(dependencies_list) + for idx, dep in enumerate(dependencies_list): + print("Processing {}/{}: {}".format(idx + 1, deps_size, dep['name'])) + name = dep['name'] + version = dep['version'] + # Handle 'django_notifications_patched','django-rest-hooks' and 'funcparserlib' separately + if not name == 'django-rest-hooks' and not name == 'django_notifications_patched' and not name == 'funcparserlib': + url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version) + try: + response = requests.get(url) + response.raise_for_status() + data = response.json() + + url_section = data.get("urls", []) + build_from_src = True + package_added = False + for component in url_section: + if component.get("packagetype") == "bdist_wheel": + whl_url = component.get("url") + whl_sha256 = get_sha256_hash(whl_url) + nix_content += ' ' + name + ' = python.pkgs.buildPythonPackage {\n' + nix_content += ' pname = "' + name + '";\n' + nix_content += ' version = "' + version + '";\n' + nix_content += ' format = "wheel";\n' + nix_content += ' src = pkgs.fetchurl {\n' + nix_content += ' url = "' + whl_url + '";\n' + nix_content += ' sha256 = "' + whl_sha256 + '";\n' + nix_content += ' };\n' + nix_content += ' };\n' + build_from_src = False + package_added = True + break + + if build_from_src: + for component in url_section: + if component.get("packagetype") == "sdist": + sdist_url = component.get("url") + sdist_sha256 = get_sha256_hash(sdist_url) + nix_content += ' ' + name + ' = disableAllTests super.' + name + ' {\n' + nix_content += ' pname = "' + name + '";\n' + nix_content += ' version = "' + version + '";\n' + nix_content += ' __intentionallyOverridingVersion = true;\n' + nix_content += ' src = pkgs.fetchurl {\n' + nix_content += ' url = "' + sdist_url + '";\n' + nix_content += ' sha256 = "' + sdist_sha256 + '";\n' + nix_content += ' };\n' + nix_content += ' };\n' + package_added = True + break + if not package_added: + need_review_packages_list.append(dep) + except requests.exceptions.RequestException as e: + need_review_packages_list.append(dep) + else: + if name == 'django-rest-hooks' and version == '1.6.1': + nix_content += ' ' + name + ' = python.pkgs.buildPythonPackage {\n' + nix_content += ' pname = "django-rest-hooks";\n' + nix_content += ' version = "1.6.1";\n' + nix_content += ' format = "wheel";\n' + nix_content += ' src = pkgs.fetchurl {\n' + nix_content += ' url = "https://github.com/aboutcode-org/django-rest-hooks/releases/download/1.6.1/django_rest_hooks-1.6.1-py2.py3-none-any.whl";\n' + nix_content += ' sha256 = "1byakq3ghpqhm0mjjkh8v5y6g3wlnri2vvfifyi9ky36l12vqx74";\n' + nix_content += ' };\n' + nix_content += ' };\n' + elif name == 'django_notifications_patched' and version == '2.0.0': + nix_content += ' ' + name + ' = self.buildPythonPackage rec {\n' + nix_content += ' pname = "django_notifications_patched";\n' + nix_content += ' version = "2.0.0";\n' + nix_content += ' format = "setuptools";\n' + nix_content += ' doCheck = false;\n' + nix_content += ' src = pkgs.fetchFromGitHub {\n' + nix_content += ' owner = "dejacode";\n' + nix_content += ' repo = "django-notifications-patched";\n' + nix_content += ' rev = "2.0.0";\n' + nix_content += ' url = "https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz";\n' + nix_content += ' sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8=";\n' + nix_content += ' };\n' + nix_content += ' };\n' + elif name == 'funcparserlib' and version == '0.3.6': + nix_content += ' ' + name + ' = self.buildPythonPackage rec {\n' + nix_content += ' pname = "funcparserlib";\n' + nix_content += ' version = "0.3.6";\n' + nix_content += ' format = "setuptools";\n' + nix_content += ' doCheck = false;\n' + nix_content += ' src = pkgs.fetchurl {\n' + nix_content += ' url = "https://files.pythonhosted.org/packages/cb/f7/b4a59c3ccf67c0082546eaeb454da1a6610e924d2e7a2a21f337ecae7b40/funcparserlib-0.3.6.tar.gz";\n' + nix_content += ' sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp";\n' + nix_content += ' };\n\n' + # Original setpy.py: https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py + # funcparserlib version 0.3.6 uses use_2to3 which is no longer supported in modern setuptools. + # Remove the "use_2to3" from the setup.py + nix_content += " postPatch = ''\n" + nix_content += ' cat > setup.py << EOF\n' + nix_content += ' # -*- coding: utf-8 -*-\n' + nix_content += ' from setuptools import setup\n' + nix_content += ' setup(\n' + nix_content += ' name="funcparserlib",\n' + nix_content += ' version="0.3.6",\n' + nix_content += ' packages=["funcparserlib", "funcparserlib.tests"],\n' + nix_content += ' author="Andrey Vlasovskikh",\n' + nix_content += ' description="Recursive descent parsing library based on functional combinators",\n' + nix_content += ' license="MIT",\n' + nix_content += ' url="http://code.google.com/p/funcparserlib/",\n' + nix_content += ' )\n' + nix_content += ' EOF\n' + nix_content += " '';\n" + nix_content += ' propagatedBuildInputs = with self; [];\n' + nix_content += ' checkPhase = "echo \'Tests disabled for funcparserlib\'";\n' + nix_content += ' };\n' + else: + need_review_packages_list.append(dep) + nix_content += """ + }; + pythonWithOverlay = python.override { + packageOverrides = + self: super: + let + # Override buildPythonPackage to disable tests for ALL packages + base = { + buildPythonPackage = + attrs: + super.buildPythonPackage ( + attrs + // { + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + } + ); + }; + + # Apply custom package overrides + custom = pythonOverlay self super; + in + base // custom; + }; + + pythonApp = pythonWithOverlay.pkgs.buildPythonApplication { +""" + + nix_content += ' name = "' + meta_dict['name'] + '";\n' + nix_content += ' version = "' + meta_dict['version'] + '";\n' + + nix_content += """ + src = ./.; + doCheck = false; + doInstallCheck = false; + doPytestCheck = false; + pythonImportsCheck = []; + + format = "pyproject"; + + nativeBuildInputs = with pythonWithOverlay.pkgs; [ + setuptools + wheel + pip + ]; + + propagatedBuildInputs = with pythonWithOverlay.pkgs; [ +""" + + for dep in dependencies_list: + name = dep['name'] + nix_content += ' ' + name + '\n' + + nix_content += """ + ]; + + meta = with pkgs.lib; { + description = "Automate open source license compliance and ensure supply chain integrity"; + license = "AGPL-3.0-only"; + maintainers = ["AboutCode.org"]; + platforms = platforms.linux; + }; + }; + +in +{ + # Default output is the Python application + app = pythonApp; + + # Default to the application + default = pythonApp; +} +""" + return nix_content, need_review_packages_list + + +def get_sha256_hash(url): + """ + Get SHA256 hash of a file using nix-prefetch-url. + """ + try: + result = subprocess.run( + ['nix-prefetch-url', url], + capture_output=True, + text=True, + check=True + ) + return result.stdout.strip() + except subprocess.CalledProcessError as e: + print(f"Error running nix-prefetch-url for {url}: {e}") + return None + except FileNotFoundError: + print("Error: nix-prefetch-url command not found. Make sure nix is installed.") + return None + + def cleanup_nix_store(): """ Remove the nix-store volume to ensure clean state @@ -81,7 +398,6 @@ def build_nix_with_docker(): try: subprocess.run(docker_cmd, check=True) - # Verify if the output directory contains any files or # subdirectories. if any(output_dir.iterdir()): @@ -94,18 +410,42 @@ def build_nix_with_docker(): sys.exit(1) -if __name__ == '__main__': +def main(): # Check if "docker" is available if not shutil.which('docker'): print("Error: Docker not found. Please install Docker first.", file=sys.stderr) sys.exit(1) - # Check if default.nix exists - if not Path('default.nix').exists(): - print("Error: default.nix not found in current directory", file=sys.stderr) - sys.exit(1) + parser = argparse.ArgumentParser(description="Package to Nix using Docker.") + parser.add_argument("--generate", action="store_true", help="Generate the default.nix file.") + + args = parser.parse_args() + + if args.generate or not Path('default.nix').exists(): + # Check if "nix-prefetch-url" is available + if not shutil.which("nix-prefetch-url"): + print("nix-prefetch-url is NOT installed.") + sys.exit(1) + + print("Generating default.nix") + pyproject_data = read_pyproject_toml() + meta_dict = extract_project_meta(pyproject_data) + dependencies_list = extract_project_dependencies(pyproject_data) + defualt_nix_content, need_review = create_defualt_nix(dependencies_list, meta_dict) + with open("default.nix", "w") as file: + file.write(defualt_nix_content) + + print("default.nix file created successfully.") + if need_review: + print("\nThe following packages need manual review as they were not found on PyPI or had issues:") + for pkg in need_review: + print(f" - {pkg['name']}=={pkg['version']}") + print("\nPlease review and add them manually to default.nix and re-run without the --generate.\n") + sys.exit(1) # Clean up the volume to ensure consistent state cleanup_nix_store() - build_nix_with_docker() + +if __name__ == '__main__': + main() diff --git a/default.nix b/default.nix deleted file mode 100644 index 331a7764..00000000 --- a/default.nix +++ /dev/null @@ -1,851 +0,0 @@ -# This file is used by `nix-build`. -# Update it manually whenever the version or dependencies change in `pyproject.toml`. - -# For packages with pinned versions to match those in pyproject.toml. -# It's recommended to use pre-built wheels instead of building from source, -# as source builds may require additional dependencies. - -# Run the following command to compute the sha256: -# nix-prefetch-url -{ - pkgs ? import { }, -}: - -let - python = pkgs.python313; - - # Helper function to override a package to disable tests - disableAllTests = - package: extraAttrs: - package.overrideAttrs ( - old: - { - doCheck = false; - doInstallCheck = false; - doPytestCheck = false; - pythonImportsCheck = []; - checkPhase = "echo 'Tests disabled'"; - installCheckPhase = "echo 'Install checks disabled'"; - pytestCheckPhase = "echo 'Pytest checks disabled'"; - __intentionallyOverridingVersion = old.__intentionallyOverridingVersion or false; - } - // extraAttrs - ); - - pythonOverlay = self: super: { - maturin = python.pkgs.buildPythonPackage rec { - pname = "maturin"; - version = "1.8.6"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/f9/aa/8090f8b3f5f7ec46bc95deb0f5b29bf52c98156ef594f2e65d20bf94cea1/maturin-1.8.6-py3-none-manylinux_2_12_x86_64.manylinux2010_x86_64.musllinux_1_1_x86_64.whl"; - sha256 = "15870w46liwy15a5x81cpdb8f9b6k4sawzxii604r5bmcj699idy"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - rq-scheduler = python.pkgs.buildPythonPackage rec { - pname = "rq-scheduler"; - version = "0.14.0"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/bb/d0/28cedca9f3b321f30e69d644c2dcd7097ec21570ec9606fde56750621300/rq_scheduler-0.14.0-py2.py3-none-any.whl"; - sha256 = "03fwqc7v4sp8jxmpnwyvacr7zqgikafz0hg0apzv64cc7ld25v6l"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - clamd = python.pkgs.buildPythonPackage rec { - pname = "clamd"; - version = "1.0.2"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/3d/d0/84614de2a53ad52370adc9f9260bea420e53e0c228a248ec0eacfa65ccbb/clamd-1.0.2-py2.py3-none-any.whl"; - sha256 = "1rzmrwywx6rnzb62ca08xn0gkyq0kvbqka00pvb0zc0ygmmm8cjw"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - mockldap = python.pkgs.buildPythonPackage rec { - pname = "mockldap"; - version = "0.3.0.post1"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/e3/6e/1536bc788db4cbccf3f2ffb37737af5e90f163ce69858f5aa1275981ed8a/mockldap-0.3.0.post1-py2.py3-none-any.whl"; - sha256 = "1n9q7girlpm97rva515q2y04bx2rhn431m996vc9b7xycq16mpnd"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - swapper = python.pkgs.buildPythonPackage rec { - pname = "swapper"; - version = "1.4.0"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/e9/53/c59363308ef97507a680372471e25e1ebab2e706a45a7c416eea6474c928/swapper-1.4.0-py2.py3-none-any.whl"; - sha256 = "0pilp6agh0gfi0y0pllk6jv29vghrzlccbg35xa44hi3mn53gf2p"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - django-rest-hooks = python.pkgs.buildPythonPackage rec { - pname = "django-rest-hooks"; - version = "1.6.1"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://github.com/aboutcode-org/django-rest-hooks/releases/download/1.6.1/django_rest_hooks-1.6.1-py2.py3-none-any.whl"; - sha256 = "1byakq3ghpqhm0mjjkh8v5y6g3wlnri2vvfifyi9ky36l12vqx74"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - aboutcode-toolkit = python.pkgs.buildPythonPackage rec { - pname = "aboutcode-toolkit"; - version = "11.1.1"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/14/ee/ba139231e3de1287189c2f7940e7e0f8a135421050ff1b4f0145813ae8b9/aboutcode_toolkit-11.1.1-py3-none-any.whl"; - sha256 = "0bx32ca9m01grwn69594jb8fgcqbm3wnviadig5iw1fxx3hpgpmy"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - django-grappelli = python.pkgs.buildPythonPackage rec { - pname = "django-grappelli"; - version = "4.0.2"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/79/66/bec8c43767f830d8c4884d8350d81e28043d3a04364b9fca43946d98a47e/django_grappelli-4.0.2-py2.py3-none-any.whl"; - sha256 = "1515f13pm2lc8830zsrjznsfbfnh6xfmp6gvzxcx6nh2ryiqf2pd"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - django-altcha = python.pkgs.buildPythonPackage rec { - pname = "django-altcha"; - version = "0.3.0"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/9e/6e/ad184a95de2652f5834243f5b3403c8949993f5f3de08a5609b15a63b091/django_altcha-0.3.0-py3-none-any.whl"; - sha256 = "0gcgir468q5ra9blm00vgdij0avpsyscbadg5k69ra7kfr8q8jgw"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - jsonfield = python.pkgs.buildPythonPackage rec { - pname = "jsonfield"; - version = "3.1.0"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/7c/97/3a4805532a9c1982368fd9f37b58133419e83704744b733ccab9e9827176/jsonfield-3.1.0-py3-none-any.whl"; - sha256 = "1vc9ss6k182qcfy70y54lyca6w2yh012x97vpabjn9bzb08pi1fz"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - pip = python.pkgs.buildPythonPackage rec { - pname = "pip"; - version = "25.1.1"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/29/a2/d40fb2460e883eca5199c62cfc2463fd261f760556ae6290f88488c362c0/pip-25.1.1-py3-none-any.whl"; - sha256 = "1bsihxacxq9i14dv0x6y3vf56fvzjsgbs1xm9avackmz5a5a64r9"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - psycopg = python.pkgs.buildPythonPackage rec { - pname = "psycopg"; - version = "3.2.9"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/44/b0/a73c195a56eb6b92e937a5ca58521a5c3346fb233345adc80fd3e2f542e2/psycopg-3.2.9-py3-none-any.whl"; - sha256 = "1dnkm33n75phjda008kbav0425k30rpcj232j4y15hmarpfdma01"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - django-registration = python.pkgs.buildPythonPackage rec { - pname = "django-registration"; - version = "3.4"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/06/3a/455455a208cd38c7fececec136e0de4a848004a7dafe4d62e55566dcbbfe/django_registration-3.4-py3-none-any.whl"; - sha256 = "1l6xn7m4p4fgv1xiv42b35ihkywvrkjkw13kxd3lyyc9254dyxps"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - requests = python.pkgs.buildPythonPackage rec { - pname = "requests"; - version = "2.32.4"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/7c/e4/56027c4a6b4ae70ca9de302488c5ca95ad4a39e190093d6c1a8ace08341b/requests-2.32.4-py3-none-any.whl"; - sha256 = "0b1bmhqv0xarifclr53icqwpsw1hk3l4w8230jrm0v9av8ybvfi7"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - rpds-py = python.pkgs.buildPythonPackage rec { - pname = "rpds-py"; - version = "0.25.1"; - format = "wheel"; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/3e/03/5d0be919037178fff33a6672ffc0afa04ea1cfcb61afd4119d1b5280ff0f/rpds_py-0.25.1-cp313-cp313-manylinux_2_17_s390x.manylinux2014_s390x.whl"; - sha256 = "0yfgi9gb9xrvalxrzrhbh7ak0qfhi2kajw0zrxrmshgy4y2i4jjw"; - }; - - # Disable checks - doCheck = false; - doInstallCheck = false; - }; - - django = self.buildPythonPackage rec { - pname = "django"; - version = "5.2.6"; - format = "pyproject"; - doCheck = false; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/4c/8c/2a21594337250a171d45dda926caa96309d5136becd1f48017247f9cdea0/django-5.2.6.tar.gz"; - sha256 = "0yx82k8iilz8l6wkdvjcrz75i144lf211xybrrrks6b34wvh0pns"; - }; - nativeBuildInputs = with self; [setuptools]; - propagatedBuildInputs = with self; [ - asgiref - sqlparse - ]; - checkPhase = "echo 'Tests disabled for django'"; - }; - - # Custom package from GitHub: https://github.com/dejacode/django-notifications-patched - django-notifications-patched = self.buildPythonPackage rec { - pname = "django-notifications-patched"; - version = "2.0.0"; - format = "setuptools"; - doCheck = false; - - src = pkgs.fetchFromGitHub { - owner = "dejacode"; - repo = "django-notifications-patched"; - rev = "2.0.0"; - url = "https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz"; - sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8="; - }; - - nativeBuildInputs = with self; [setuptools]; - checkPhase = "echo 'Tests disabled for django-notifications-patched'"; - }; - - crontab = disableAllTests super.crontab { - version = "1.0.5"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/d6/36/a255b6f5a2e22df03fd2b2f3088974b44b8c9e9407e26b44742cb7cfbf5b/crontab-1.0.5.tar.gz"; - sha256 = "1ma6ms0drlx6pj4q14jsjvkphwhl2zfjdyb9k0x7c6bjy2s023pq"; - }; - patches = []; - }; - - asgiref = disableAllTests super.asgiref { - version = "3.9.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/90/61/0aa957eec22ff70b830b22ff91f825e70e1ef732c06666a805730f28b36b/asgiref-3.9.1.tar.gz"; - sha256 = "0hiiiq4xbm8mn9ykgp789pynqqhhkzyl5wj82vpya6324f16bax5"; - }; - patches = []; - }; - - setuptools = disableAllTests super.setuptools { - version = "80.9.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/18/5d/3bf57dcd21979b887f014ea83c24ae194cfcd12b9e0fda66b957c69d1fca/setuptools-80.9.0.tar.gz"; - sha256 = "175iixi2h2jz8y2bpwziak360hvv43jfhipwzbdniryd5r04fszk"; - }; - patches = []; - }; - - wheel = disableAllTests super.wheel { - version = "0.45.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/8a/98/2d9906746cdc6a6ef809ae6338005b3f21bb568bea3165cfc6a243fdc25c/wheel-0.45.1.tar.gz"; - sha256 = "0ab7ramncrii43smhvcidrbv4w4ndl80435214a7nl4qj6yil7k6"; - }; - patches = []; - }; - - typing-extensions = disableAllTests super.typing-extensions { - version = "4.14.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/d1/bc/51647cd02527e87d05cb083ccc402f93e441606ff1f01739a62c8ad09ba5/typing_extensions-4.14.0.tar.gz"; - sha256 = "1x7fbkbpjj9xrsxfx6d38kccdr4a0hj17ip7v51an0igwf4bfxl6"; - }; - patches = []; - }; - - django-guardian = disableAllTests super.django-guardian { - version = "3.0.3"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/30/c2/3ed43813dd7313f729dbaa829b4f9ed4a647530151f672cfb5f843c12edf/django_guardian-3.0.3.tar.gz"; - sha256 = "0v8ria6c0iirl1ck2xfzpcnf59629g8pdhghgh15mninv2sflnaf"; - }; - patches = []; - }; - - djangorestframework = disableAllTests super.djangorestframework { - version = "3.16.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/8a/95/5376fe618646fde6899b3cdc85fd959716bb67542e273a76a80d9f326f27/djangorestframework-3.16.1.tar.gz"; - sha256 = "1xrzzjf048llw85vs3a2r7r4k07i594j8v66gnhx1khsid90js0n"; - }; - patches = []; - }; - - uritemplate = disableAllTests super.uritemplate { - version = "4.1.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/d2/5a/4742fdba39cd02a56226815abfa72fe0aa81c33bed16ed045647d6000eba/uritemplate-4.1.1.tar.gz"; - sha256 = "1w14a775d92mx9pdhb5zimifpfr2lfcn0vfdpjagcy9vbkyfsij3"; - }; - patches = []; - }; - - redis = disableAllTests super.redis { - version = "6.4.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/0d/d6/e8b92798a5bd67d659d51a18170e91c16ac3b59738d91894651ee255ed49/redis-6.4.0.tar.gz"; - sha256 = "047hbsy9rs44y81wdfbyl13vp0si6zsis9kbqf7f4i445clcf6xh"; - }; - patches = []; - }; - - model-bakery = disableAllTests super.model-bakery { - version = "1.10.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/8f/4c/2fca1e79408308b8f0e71e687ca1e1d3ede450e257e2474e331261fdb106/model_bakery-1.10.1.tar.gz"; - sha256 = "0pmd0jmqbhvpyc52p42kmbn97lgg3zwaky5dcyr4p0izwfjssx0v"; - }; - patches = []; - }; - - fakeredis = disableAllTests super.fakeredis { - version = "2.31.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/0b/10/c829c3475a26005ebf177057fdf54e2a29025ffc2232d02fb1ae8ac1de68/fakeredis-2.31.0.tar.gz"; - sha256 = "08ydbsc2v2i8zs5spa95lg2mlcc718cvj276z5phgn8gj3ksfhi9"; - }; - patches = []; - }; - - freezegun = disableAllTests super.freezegun { - version = "1.5.2"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/c7/75/0455fa5029507a2150da59db4f165fbc458ff8bb1c4f4d7e8037a14ad421/freezegun-1.5.2.tar.gz"; - sha256 = "10aij0mdg4jmqpd396jlif0rahmbjnms661cw11bybf0z79f2jm5"; - }; - patches = []; - }; - - certifi = disableAllTests super.certifi { - version = "2025.8.3"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/dc/67/960ebe6bf230a96cda2e0abcf73af550ec4f090005363542f0765df162e0/certifi-2025.8.3.tar.gz"; - sha256 = "01rlwvx3zi9bjfxvbspscdsal7ay8cj3k4kwmvin9mfyg1gi0r75"; - }; - patches = []; - }; - - cython = disableAllTests super.cython { - version = "3.1.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/5b/d3/bb000603e46144db2e5055219bbddcf7ab3b10012fcb342695694fb88141/cython-3.1.1.tar.gz"; - sha256 = "15v36rp426zbgm4hrxn4i2028x3rf0n7jkc3acm17mb96r0wsp2h"; - }; - patches = []; - }; - - zipp = disableAllTests super.zipp { - version = "3.22.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/12/b6/7b3d16792fdf94f146bed92be90b4eb4563569eca91513c8609aebf0c167/zipp-3.22.0.tar.gz"; - sha256 = "1rdcax7bi43xmm9la6mm0c8y6axvnwhisy6kpw3pbijbrv1jhbyx"; - }; - patches = []; - }; - - markdown = disableAllTests super.markdown { - version = "3.8"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/2f/15/222b423b0b88689c266d9eac4e61396fe2cc53464459d6a37618ac863b24/markdown-3.8.tar.gz"; - sha256 = "0vww67gb1w890iyr6w3xkcira1b9wj0ynmninwj4np6zy1iixy3x"; - }; - patches = []; - }; - - oauthlib = disableAllTests super.oauthlib { - version = "3.2.2"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/6d/fa/fbf4001037904031639e6bfbfc02badfc7e12f137a8afa254df6c4c8a670/oauthlib-3.2.2.tar.gz"; - sha256 = "066r7mimlpb5q1fr2f1z59l4jc89kv4h2kgkcifyqav6544w8ncq"; - }; - patches = []; - }; - - defusedxml = disableAllTests super.defusedxml { - version = "0.7.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/0f/d5/c66da9b79e5bdb124974bfe172b4daf3c984ebd9c2a06e2b8a4dc7331c72/defusedxml-0.7.1.tar.gz"; - sha256 = "0s9ym98jrd819v4arv9gmcr6mgljhxd9q866sxi5p4c5n4nh7cqv"; - }; - patches = []; - }; - - funcparserlib = self.buildPythonPackage rec { - pname = "funcparserlib"; - version = "0.3.6"; - format = "setuptools"; - doCheck = false; - - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/cb/f7/b4a59c3ccf67c0082546eaeb454da1a6610e924d2e7a2a21f337ecae7b40/funcparserlib-0.3.6.tar.gz"; - sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp"; - }; - - # Original setpy.py: https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py - # funcparserlib version 0.3.6 uses use_2to3 which is no longer supported in modern setuptools. - # Rewrite the problematic section completely - postPatch = '' - cat > setup.py << EOF - # -*- coding: utf-8 -*- - - from setuptools import setup - - setup( - name='funcparserlib', - version='0.3.6', - packages=['funcparserlib', 'funcparserlib.tests'], - author='Andrey Vlasovskikh', - author_email='andrey.vlasovskikh@gmail.com', - description='Recursive descent parsing library based on functional ' - 'combinators', - license='MIT', - url='http://code.google.com/p/funcparserlib/', - ) - EOF - ''; - - propagatedBuildInputs = with self; []; - checkPhase = "echo 'Tests disabled for funcparserlib'"; - }; - - click = disableAllTests super.click { - version = "8.2.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/60/6c/8ca2efa64cf75a977a0d7fac081354553ebe483345c734fb6b6515d96bbc/click-8.2.1.tar.gz"; - sha256 = "00k2ck8g0f5ha26183wkkmnn7151npii7nx1smqx4s6r0p693i17"; - }; - patches = []; - }; - - packageurl-python = disableAllTests super.packageurl-python { - version = "0.17.5"; - __intentionallyOverridingVersion = true; - }; - - jsonschema = disableAllTests super.jsonschema { - version = "4.24.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/bf/d3/1cf5326b923a53515d8f3a2cd442e6d7e94fcc444716e879ea70a0ce3177/jsonschema-4.24.0.tar.gz"; - sha256 = "15nik6awrcj2y5nlnslabbpyq97cray08c1kh6ldzbhjxdlq0khb"; - }; - patches = []; - }; - - cyclonedx-python-lib = disableAllTests super.cyclonedx-python-lib { - version = "10.2.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/3e/24/86a4949e59f8d79d42beef6041fd6ce842400addcec2f2a1bccb3208a5cd/cyclonedx_python_lib-10.2.0.tar.gz"; - sha256 = "1ralxk93zhyalg099bm6hxsqiisq8ha2rf7khjawz4bzhkd9lymn"; - }; - patches = []; - }; - - py-serializable = disableAllTests super.py-serializable { - version = "2.0.0"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/f0/75/813967eae0542776314c6def33feac687642a193b9d5591c20684b2eafd8/py_serializable-2.0.0.tar.gz"; - sha256 = "1990yhn7a17j3z57r1ivlklgjmwngysk40h5y7d3376jswflkrp9"; - }; - patches = []; - }; - - smmap = disableAllTests super.smmap { - version = "5.0.2"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/44/cd/a040c4b3119bbe532e5b0732286f805445375489fceaec1f48306068ee3b/smmap-5.0.2.tar.gz"; - sha256 = "1mcai5vf9bgz389y4sqgj6w22wn7zmc7m33y3j50ryjq76h6bsi6"; - }; - patches = []; - }; - - pydantic = disableAllTests super.pydantic { - version = "2.11.5"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/f0/86/8ce9040065e8f924d642c58e4a344e33163a07f6b57f836d0d734e0ad3fb/pydantic-2.11.5.tar.gz"; - sha256 = "0ykv5aar0xjwfprdy31w8yrk2r6x5hf4130lpf5wwy6fs2rkv1bz"; - }; - patches = []; - }; - - setuptools-rust = disableAllTests super.setuptools-rust { - version = "1.11.1"; - __intentionallyOverridingVersion = true; - src = pkgs.fetchurl { - url = "https://files.pythonhosted.org/packages/e0/92/bf8589b1a2b6107cf9ec8daa9954c0b7620643fe1f37d31d75e572d995f5/setuptools_rust-1.11.1.tar.gz"; - sha256 = "1h3nbg1nlshzrqy7vz4q4g9wbz85dqkn6385p0ad7kjj48ww9avx"; - }; - patches = []; - }; - - django-axes = disableAllTests super.django-axes { - version = "8.0.0"; - __intentionallyOverridingVersion = true; - }; - - sh = disableAllTests super.sh {}; - pytest-xdist = disableAllTests super.pytest-xdist {}; - altcha = disableAllTests super.altcha {}; - annotated-types = disableAllTests super.annotated-types {}; - async-timeout = disableAllTests super.async-timeout {}; - attrs = disableAllTests super.attrs {}; - bleach = disableAllTests super.bleach {}; - bleach-allowlist = disableAllTests super.bleach-allowlist {}; - boolean-py = disableAllTests super.boolean-py {}; - charset-normalizer = disableAllTests super.charset-normalizer {}; - confusable-homoglyphs = disableAllTests super.confusable-homoglyphs {}; - crispy-bootstrap5 = disableAllTests super.crispy-bootstrap5 {}; - deprecated = disableAllTests super.deprecated {}; - django-auth-ldap = disableAllTests super.django-auth-ldap {}; - django-crispy-forms = disableAllTests super.django-crispy-forms {}; - django-debug-toolbar = disableAllTests super.django-debug-toolbar {}; - django-environ = disableAllTests super.django-environ {}; - django-filter = disableAllTests super.django-filter {}; - django-otp = disableAllTests super.django-otp {}; - django-rq = disableAllTests super.django-rq {}; - doc8 = disableAllTests super.doc8 {}; - docutils = disableAllTests super.docutils {}; - drf-yasg = disableAllTests super.drf-yasg {}; - et-xmlfile = disableAllTests super.et-xmlfile {}; - gitdb = disableAllTests super.gitdb {}; - gitpython = disableAllTests super.gitpython {}; - gunicorn = disableAllTests super.gunicorn {}; - hatchling = disableAllTests super.hatchling {}; - inflection = disableAllTests super.inflection {}; - jinja2 = disableAllTests super.jinja2 {}; - jsonschema-specifications = disableAllTests super.jsonschema-specifications {}; - license-expression = disableAllTests super.license-expression {}; - markupsafe = disableAllTests super.markupsafe {}; - natsort = disableAllTests super.natsort {}; - numpy = disableAllTests super.numpy {}; - openpyxl = disableAllTests super.openpyxl {}; - packaging = disableAllTests super.packaging {}; - pandas = disableAllTests super.pandas {}; - pbr = disableAllTests super.pbr {}; - pillow = disableAllTests super.pillow {}; - platformdirs = disableAllTests super.platformdirs {}; - pyasn1 = disableAllTests super.pyasn1 {}; - pyasn1-modules = disableAllTests super.pyasn1-modules {}; - pycparser = disableAllTests super.pycparser {}; - pydantic-core = disableAllTests super.pydantic-core {}; - pyfakefs = disableAllTests super.pyfakefs {}; - pygments = disableAllTests super.pygments {}; - pyjwt = disableAllTests super.pyjwt {}; - pyparsing = disableAllTests super.pyparsing {}; - pypng = disableAllTests super.pypng {}; - pyrsistent = disableAllTests super.pyrsistent {}; - pytest-randomly = disableAllTests super.pytest-randomly {}; - pytest-regressions = disableAllTests super.pytest-regressions {}; - python3-openid = disableAllTests super.python3-openid {}; - python-dateutil = disableAllTests super.python-dateutil {}; - python-ldap = disableAllTests super.python-ldap {}; - python-mimeparse = disableAllTests super.python-mimeparse {}; - pytz = disableAllTests super.pytz {}; - pyyaml = disableAllTests super.pyyaml {}; - qrcode = disableAllTests super.qrcode {}; - referencing = disableAllTests super.referencing {}; - requests-oauthlib = disableAllTests super.requests-oauthlib {}; - restructuredtext-lint = disableAllTests super.restructuredtext-lint {}; - rq = disableAllTests super.rq {}; - ruff = disableAllTests super.ruff {}; - saneyaml = disableAllTests super.saneyaml {}; - semantic-version = disableAllTests super.semantic-version {}; - six = disableAllTests super.six {}; - sortedcontainers = disableAllTests super.sortedcontainers {}; - sqlparse = disableAllTests super.sqlparse {}; - stevedore = disableAllTests super.stevedore {}; - tblib = disableAllTests super.tblib {}; - toml = disableAllTests super.toml {}; - tomli = disableAllTests super.tomli {}; - tqdm = disableAllTests super.tqdm {}; - typing-inspection = disableAllTests super.typing-inspection {}; - urllib3 = disableAllTests super.urllib3 {}; - webencodings = disableAllTests super.webencodings {}; - wrapt = disableAllTests super.wrapt {}; - xlsxwriter = disableAllTests super.xlsxwriter {}; - - }; - - pythonWithOverlay = python.override { - packageOverrides = - self: super: - let - # Override buildPythonPackage to disable tests for ALL packages - base = { - buildPythonPackage = - attrs: - super.buildPythonPackage ( - attrs - // { - doCheck = false; - doInstallCheck = false; - doPytestCheck = false; - pythonImportsCheck = []; - } - ); - }; - - # Apply custom package overrides - custom = pythonOverlay self super; - in - base // custom; - }; - - - pythonApp = pythonWithOverlay.pkgs.buildPythonApplication { - pname = "dejacode"; - version = "5.4.0"; - - src = ./.; - doCheck = false; - doInstallCheck = false; - doPytestCheck = false; - pythonImportsCheck = []; - - format = "pyproject"; - - nativeBuildInputs = with pythonWithOverlay.pkgs; [ - setuptools - wheel - pip - ]; - - - # Specifies all Python dependencies required at runtime to ensure consistent overrides. - propagatedBuildInputs = with pythonWithOverlay.pkgs; [ - maturin - crontab - django-filter - natsort - jsonschema - freezegun - docutils - bleach-allowlist - django-environ - pyrsistent - djangorestframework - confusable-homoglyphs - pygments - async-timeout - semantic-version - markupsafe - click - sortedcontainers - toml - cyclonedx-python-lib - six - packaging - clamd - django-guardian - altcha - django-rest-hooks - gitpython - stevedore - openpyxl - qrcode - django-rq - bleach - license-expression - model-bakery - mockldap - pytz - django-notifications-patched - pyparsing - funcparserlib - typing-extensions - smmap - saneyaml - requests-oauthlib - oauthlib - pyyaml - et-xmlfile - uritemplate - xlsxwriter - tblib - django-crispy-forms - defusedxml - jsonschema-specifications - doc8 - python3-openid - redis - django-axes - pyasn1-modules - swapper - cython - markdown - py-serializable - idna - packageurl-python - rpds-py - rq - django-auth-ldap - typing-inspection - urllib3 - certifi - sqlparse - wrapt - boolean-py - drf-yasg - fakeredis - inflection - django-otp - gitdb - crispy-bootstrap5 - python-mimeparse - attrs - django-debug-toolbar - jsonfield - aboutcode-toolkit - rq-scheduler - zipp - jinja2 - pyjwt - gunicorn - django-grappelli - python-ldap - ruff - pydantic - restructuredtext-lint - referencing - annotated-types - pypng - django-registration - django-altcha - python-dateutil - pydantic-core - pyasn1 - setuptools-rust - requests - pbr - webencodings - deprecated - charset-normalizer - psycopg - asgiref - ]; - - meta = with pkgs.lib; { - description = "Automate open source license compliance and ensure supply chain integrity"; - license = "AGPL-3.0-only"; - maintainers = ["AboutCode.org"]; - platforms = platforms.linux; - }; - }; - -in -{ - # Default output is the Python application - app = pythonApp; - - # Default to the application - default = pythonApp; -} From ed52e44e0bab25aac3bf7156cb52cd59be155cce Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 14 Oct 2025 16:09:19 +0800 Subject: [PATCH 04/15] Reformat the code to comply with Ruff's check #339 Signed-off-by: Chin Yeung Li --- build_nix_docker.py | 252 ++++++++++++++++++++++++-------------------- 1 file changed, 137 insertions(+), 115 deletions(-) diff --git a/build_nix_docker.py b/build_nix_docker.py index 9386057a..0fe8d374 100644 --- a/build_nix_docker.py +++ b/build_nix_docker.py @@ -24,79 +24,69 @@ import argparse import os -import requests import shutil import subprocess import sys from pathlib import Path +import requests + def read_pyproject_toml(): - """ - Read the pyproject.toml file to extract project metadata. - """ + # Read the pyproject.toml file to extract project metadata. import toml - pyproject_path = Path('pyproject.toml') + pyproject_path = Path("pyproject.toml") if not pyproject_path.exists(): print("Error: pyproject.toml not found in current directory", file=sys.stderr) sys.exit(1) - with pyproject_path.open('r') as f: + with pyproject_path.open("r") as f: pyproject_data = toml.load(f) return pyproject_data + def extract_project_meta(pyproject_data): - """ - Extract project metadata from pyproject.toml data. - """ - project_data = pyproject_data['project'] - name = project_data.get('name') - version = project_data.get('version') - description = project_data.get('description') - authors = project_data.get('authors') - author_names = [author.get('name', '') for author in authors if 'name' in author] - author_str = ', '.join(author_names) - - meta_dict = { - 'name': name, - 'version': version, - 'description': description, - 'author': author_str - } + # Extract project metadata from pyproject.toml data. + project_data = pyproject_data["project"] + name = project_data.get("name") + version = project_data.get("version") + description = project_data.get("description") + authors = project_data.get("authors") + author_names = [author.get("name", "") for author in authors if "name" in author] + author_str = ", ".join(author_names) + + meta_dict = {"name": name, "version": version, "description": description, "author": author_str} return meta_dict def extract_project_dependencies(pyproject_data): - """ - Extract project dependencies from pyproject.toml data. - """ - project_data = pyproject_data['project'] - dependencies = project_data.get('dependencies', []) - optional_dependencies = project_data.get('optional-dependencies', {}) - dev_optional_deps = optional_dependencies.get('dev', []) + # Extract project dependencies from pyproject.toml data. + project_data = pyproject_data["project"] + dependencies = project_data.get("dependencies", []) + optional_dependencies = project_data.get("optional-dependencies", {}) + dev_optional_deps = optional_dependencies.get("dev", []) all_dep = dependencies + dev_optional_deps dependencies_list = [] for dep in all_dep: - name_version = dep.split('==') + name_version = dep.split("==") name = name_version[0] version = name_version[1] tmp_dict = {} - tmp_dict['name'] = name - tmp_dict['version'] = version + tmp_dict["name"] = name + tmp_dict["version"] = version dependencies_list.append(tmp_dict) - assert len(all_dep) == len(dependencies_list), "Dependency extraction mismatch" + if len(all_dep) != len(dependencies_list): + raise ValueError("Dependency extraction mismatch") return dependencies_list def create_defualt_nix(dependencies_list, meta_dict): - """ - Create a default.nix - """ + # Create a default.nix nix_content = """ { pkgs ? import { }, @@ -128,14 +118,18 @@ def create_defualt_nix(dependencies_list, meta_dict): need_review_packages_list = [] deps_size = len(dependencies_list) for idx, dep in enumerate(dependencies_list): - print("Processing {}/{}: {}".format(idx + 1, deps_size, dep['name'])) - name = dep['name'] - version = dep['version'] + print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"])) + name = dep["name"] + version = dep["version"] # Handle 'django_notifications_patched','django-rest-hooks' and 'funcparserlib' separately - if not name == 'django-rest-hooks' and not name == 'django_notifications_patched' and not name == 'funcparserlib': + if ( + not name == "django-rest-hooks" + and not name == "django_notifications_patched" + and not name == "funcparserlib" + ): url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version) try: - response = requests.get(url) + response = requests.get(url, timeout=30) response.raise_for_status() data = response.json() @@ -146,15 +140,15 @@ def create_defualt_nix(dependencies_list, meta_dict): if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") whl_sha256 = get_sha256_hash(whl_url) - nix_content += ' ' + name + ' = python.pkgs.buildPythonPackage {\n' + nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" nix_content += ' pname = "' + name + '";\n' nix_content += ' version = "' + version + '";\n' nix_content += ' format = "wheel";\n' - nix_content += ' src = pkgs.fetchurl {\n' + nix_content += " src = pkgs.fetchurl {\n" nix_content += ' url = "' + whl_url + '";\n' nix_content += ' sha256 = "' + whl_sha256 + '";\n' - nix_content += ' };\n' - nix_content += ' };\n' + nix_content += " };\n" + nix_content += " };\n" build_from_src = False package_added = True break @@ -164,77 +158,88 @@ def create_defualt_nix(dependencies_list, meta_dict): if component.get("packagetype") == "sdist": sdist_url = component.get("url") sdist_sha256 = get_sha256_hash(sdist_url) - nix_content += ' ' + name + ' = disableAllTests super.' + name + ' {\n' + nix_content += ( + " " + name + " = disableAllTests super." + name + " {\n" + ) nix_content += ' pname = "' + name + '";\n' nix_content += ' version = "' + version + '";\n' - nix_content += ' __intentionallyOverridingVersion = true;\n' - nix_content += ' src = pkgs.fetchurl {\n' + nix_content += " __intentionallyOverridingVersion = true;\n" + nix_content += " src = pkgs.fetchurl {\n" nix_content += ' url = "' + sdist_url + '";\n' nix_content += ' sha256 = "' + sdist_sha256 + '";\n' - nix_content += ' };\n' - nix_content += ' };\n' + nix_content += " };\n" + nix_content += " };\n" package_added = True break if not package_added: need_review_packages_list.append(dep) - except requests.exceptions.RequestException as e: + except requests.exceptions.RequestException: need_review_packages_list.append(dep) else: - if name == 'django-rest-hooks' and version == '1.6.1': - nix_content += ' ' + name + ' = python.pkgs.buildPythonPackage {\n' + if name == "django-rest-hooks" and version == "1.6.1": + nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" nix_content += ' pname = "django-rest-hooks";\n' nix_content += ' version = "1.6.1";\n' nix_content += ' format = "wheel";\n' - nix_content += ' src = pkgs.fetchurl {\n' + nix_content += " src = pkgs.fetchurl {\n" nix_content += ' url = "https://github.com/aboutcode-org/django-rest-hooks/releases/download/1.6.1/django_rest_hooks-1.6.1-py2.py3-none-any.whl";\n' - nix_content += ' sha256 = "1byakq3ghpqhm0mjjkh8v5y6g3wlnri2vvfifyi9ky36l12vqx74";\n' - nix_content += ' };\n' - nix_content += ' };\n' - elif name == 'django_notifications_patched' and version == '2.0.0': - nix_content += ' ' + name + ' = self.buildPythonPackage rec {\n' + nix_content += ( + ' sha256 = "1byakq3ghpqhm0mjjkh8v5y6g3wlnri2vvfifyi9ky36l12vqx74";\n' + ) + nix_content += " };\n" + nix_content += " };\n" + elif name == "django_notifications_patched" and version == "2.0.0": + nix_content += " " + name + " = self.buildPythonPackage rec {\n" nix_content += ' pname = "django_notifications_patched";\n' nix_content += ' version = "2.0.0";\n' nix_content += ' format = "setuptools";\n' - nix_content += ' doCheck = false;\n' - nix_content += ' src = pkgs.fetchFromGitHub {\n' + nix_content += " doCheck = false;\n" + nix_content += " src = pkgs.fetchFromGitHub {\n" nix_content += ' owner = "dejacode";\n' nix_content += ' repo = "django-notifications-patched";\n' nix_content += ' rev = "2.0.0";\n' nix_content += ' url = "https://github.com/dejacode/django-notifications-patched/archive/refs/tags/2.0.0.tar.gz";\n' - nix_content += ' sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8=";\n' - nix_content += ' };\n' - nix_content += ' };\n' - elif name == 'funcparserlib' and version == '0.3.6': - nix_content += ' ' + name + ' = self.buildPythonPackage rec {\n' + nix_content += ( + ' sha256 = "sha256-RDAp2PKWa2xA5ge25VqkmRm8HCYVS4/fq2xKc80LDX8=";\n' + ) + nix_content += " };\n" + nix_content += " };\n" + elif name == "funcparserlib" and version == "0.3.6": + nix_content += " " + name + " = self.buildPythonPackage rec {\n" nix_content += ' pname = "funcparserlib";\n' nix_content += ' version = "0.3.6";\n' nix_content += ' format = "setuptools";\n' - nix_content += ' doCheck = false;\n' - nix_content += ' src = pkgs.fetchurl {\n' + nix_content += " doCheck = false;\n" + nix_content += " src = pkgs.fetchurl {\n" nix_content += ' url = "https://files.pythonhosted.org/packages/cb/f7/b4a59c3ccf67c0082546eaeb454da1a6610e924d2e7a2a21f337ecae7b40/funcparserlib-0.3.6.tar.gz";\n' - nix_content += ' sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp";\n' - nix_content += ' };\n\n' - # Original setpy.py: https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py - # funcparserlib version 0.3.6 uses use_2to3 which is no longer supported in modern setuptools. - # Remove the "use_2to3" from the setup.py + nix_content += ( + ' sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp";\n' + ) + nix_content += " };\n\n" + # Original setpy.py: + # https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py + # funcparserlib version 0.3.6 uses use_2to3 which is no + # longer supported in modern setuptools. Remove the + # "use_2to3" from the setup.py nix_content += " postPatch = ''\n" - nix_content += ' cat > setup.py << EOF\n' - nix_content += ' # -*- coding: utf-8 -*-\n' - nix_content += ' from setuptools import setup\n' - nix_content += ' setup(\n' + nix_content += " cat > setup.py << EOF\n" + nix_content += " # -*- coding: utf-8 -*-\n" + nix_content += " from setuptools import setup\n" + nix_content += " setup(\n" nix_content += ' name="funcparserlib",\n' nix_content += ' version="0.3.6",\n' nix_content += ' packages=["funcparserlib", "funcparserlib.tests"],\n' nix_content += ' author="Andrey Vlasovskikh",\n' - nix_content += ' description="Recursive descent parsing library based on functional combinators",\n' + nix_content += ' description="Recursive descent parsing library based" \ + "on functional combinators",\n' nix_content += ' license="MIT",\n' nix_content += ' url="http://code.google.com/p/funcparserlib/",\n' - nix_content += ' )\n' - nix_content += ' EOF\n' + nix_content += " )\n" + nix_content += " EOF\n" nix_content += " '';\n" - nix_content += ' propagatedBuildInputs = with self; [];\n' - nix_content += ' checkPhase = "echo \'Tests disabled for funcparserlib\'";\n' - nix_content += ' };\n' + nix_content += " propagatedBuildInputs = with self; [];\n" + nix_content += " checkPhase = \"echo 'Tests disabled for funcparserlib'\";\n" + nix_content += " };\n" else: need_review_packages_list.append(dep) nix_content += """ @@ -267,8 +272,8 @@ def create_defualt_nix(dependencies_list, meta_dict): pythonApp = pythonWithOverlay.pkgs.buildPythonApplication { """ - nix_content += ' name = "' + meta_dict['name'] + '";\n' - nix_content += ' version = "' + meta_dict['version'] + '";\n' + nix_content += ' name = "' + meta_dict["name"] + '";\n' + nix_content += ' version = "' + meta_dict["version"] + '";\n' nix_content += """ src = ./.; @@ -289,8 +294,8 @@ def create_defualt_nix(dependencies_list, meta_dict): """ for dep in dependencies_list: - name = dep['name'] - nix_content += ' ' + name + '\n' + name = dep["name"] + nix_content += " " + name + "\n" nix_content += """ ]; @@ -316,15 +321,18 @@ def create_defualt_nix(dependencies_list, meta_dict): def get_sha256_hash(url): - """ - Get SHA256 hash of a file using nix-prefetch-url. - """ + # Get SHA256 hash of a file using nix-prefetch-url. try: - result = subprocess.run( - ['nix-prefetch-url', url], + nix_prefetch_url_path = shutil.which("nix-prefetch-url") + if not nix_prefetch_url_path: + print("Error: nix-prefetch-url command not found. Make sure nix is installed.") + return None + result = subprocess.run( # noqa: S603 + [nix_prefetch_url_path, url], capture_output=True, text=True, - check=True + check=True, + shell=False, ) return result.stdout.strip() except subprocess.CalledProcessError as e: @@ -336,12 +344,13 @@ def get_sha256_hash(url): def cleanup_nix_store(): - """ - Remove the nix-store volume to ensure clean state - """ + # Remove the nix-store volume to ensure clean state try: - subprocess.run(['docker', 'volume', 'rm', 'nix-store'], - check=True, capture_output=True) + docker_path = shutil.which("docker") + if not docker_path: + print("Error: docker command not found. Make sure docker is installed.") + return None + subprocess.run([docker_path, "volume", "rm", "nix-store"], check=True, capture_output=True) # noqa: S603 print("Cleaned up nix-store volume.") except subprocess.CalledProcessError as e: # Volume might not exist, which is fine @@ -352,17 +361,23 @@ def cleanup_nix_store(): def build_nix_with_docker(): # Create output directory - output_dir = Path('dist/nix') + output_dir = Path("dist/nix") output_dir.mkdir(parents=True, exist_ok=True) docker_cmd = [ - 'docker', 'run', '--rm', - '-v', f"{os.getcwd()}:/workspace", - '-v', 'nix-store:/nix', - '-w', '/workspace', - 'nixos/nix', - '/bin/sh', '-c', - f"""set -ex + "docker", + "run", + "--rm", + "-v", + f"{os.getcwd()}:/workspace", + "-v", + "nix-store:/nix", + "-w", + "/workspace", + "nixos/nix", + "/bin/sh", + "-c", + """set -ex # Update nix-channel to get latest packages nix-channel --update @@ -393,17 +408,17 @@ def build_nix_with_docker(): echo "Error: nix-build failed - result directory not found" >&2 exit 1 fi - """ + """, ] try: - subprocess.run(docker_cmd, check=True) + subprocess.run(docker_cmd, check=True, shell=False) # noqa: S603 # Verify if the output directory contains any files or # subdirectories. if any(output_dir.iterdir()): print(f"\nNix build completed. Results in: {output_dir}") else: - print(f"Nix build failed.", file=sys.stderr) + print("Nix build failed.", file=sys.stderr) except subprocess.CalledProcessError as e: print(f"Build failed: {e}", file=sys.stderr) @@ -412,7 +427,7 @@ def build_nix_with_docker(): def main(): # Check if "docker" is available - if not shutil.which('docker'): + if not shutil.which("docker"): print("Error: Docker not found. Please install Docker first.", file=sys.stderr) sys.exit(1) @@ -421,7 +436,7 @@ def main(): args = parser.parse_args() - if args.generate or not Path('default.nix').exists(): + if args.generate or not Path("default.nix").exists(): # Check if "nix-prefetch-url" is available if not shutil.which("nix-prefetch-url"): print("nix-prefetch-url is NOT installed.") @@ -437,15 +452,22 @@ def main(): print("default.nix file created successfully.") if need_review: - print("\nThe following packages need manual review as they were not found on PyPI or had issues:") + print( + "\nThe following packages need manual review" + "as they were not found on PyPI or had issues:" + ) for pkg in need_review: print(f" - {pkg['name']}=={pkg['version']}") - print("\nPlease review and add them manually to default.nix and re-run without the --generate.\n") + print( + "\nPlease review and add them manually to" + "default.nix and re-run without the --generate.\n" + ) sys.exit(1) # Clean up the volume to ensure consistent state cleanup_nix_store() build_nix_with_docker() -if __name__ == '__main__': + +if __name__ == "__main__": main() From 201f435b3fd31d4614462114529af06da4283303 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 15 Oct 2025 14:49:13 +0800 Subject: [PATCH 05/15] Move build_nix_docker.py to etc/scripts #339 - Added --generate and --test options - Updated code structure/comments Signed-off-by: Chin Yeung Li --- .../scripts/build_nix_docker.py | 206 ++++++++++-------- 1 file changed, 113 insertions(+), 93 deletions(-) rename build_nix_docker.py => etc/scripts/build_nix_docker.py (87%) diff --git a/build_nix_docker.py b/etc/scripts/build_nix_docker.py similarity index 87% rename from build_nix_docker.py rename to etc/scripts/build_nix_docker.py index 0fe8d374..f48a98e0 100644 --- a/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -1,7 +1,8 @@ #!/usr/bin/env python3 """ -Use Docker container to run nix-build. -Using Docker approach to ensure a consistent and isolated build environment. +Generates a Nix expression (default.nix) from the +pyproject.toml file that is used to build the Python package for NixOS and +put it under the project root. Requirement: `toml` and `requests` Python packages and Docker installed. @@ -11,15 +12,38 @@ python build_nix_docker.py -or +It will create a `default.nix` file in the project root if it does not +already exist. + +Options: +-------- +`--generate` - Creates or overwrites default.nix in the project root. python build_nix_docker.py --generate - The --generate flag is optional and can be used to generate the - default.nix file if needed. +`--test` - Tests the build using Docker. + + python build_nix_docker.py --test + + +The `--test` flag will use Docker to run the Nix build in a clean +environment. It will run `nix-build` inside a Docker container to ensure +that the default.nix file is valid and can successfully build the package. +It will then do cleanup by removing the `nix-store` Docker volume. + + +Once the default.nix is generated, one can build/install the package by +using: + + Build the package + + nix-build default.nix -This script will run nix-build and place the built results in the -dist/nix/ directory, it will then run nix-collect-garbage for cleanup. + The above command will create a symlink named `result` in the current + directory pointing to the build output in the Nix store. + Run the binary directly + + ./result/bin/dejacode """ import argparse @@ -123,59 +147,10 @@ def create_defualt_nix(dependencies_list, meta_dict): version = dep["version"] # Handle 'django_notifications_patched','django-rest-hooks' and 'funcparserlib' separately if ( - not name == "django-rest-hooks" - and not name == "django_notifications_patched" - and not name == "funcparserlib" + name == "django-rest-hooks" + or name == "django_notifications_patched" + or (name == "funcparserlib" and version == "0.3.6") ): - url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version) - try: - response = requests.get(url, timeout=30) - response.raise_for_status() - data = response.json() - - url_section = data.get("urls", []) - build_from_src = True - package_added = False - for component in url_section: - if component.get("packagetype") == "bdist_wheel": - whl_url = component.get("url") - whl_sha256 = get_sha256_hash(whl_url) - nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" - nix_content += ' pname = "' + name + '";\n' - nix_content += ' version = "' + version + '";\n' - nix_content += ' format = "wheel";\n' - nix_content += " src = pkgs.fetchurl {\n" - nix_content += ' url = "' + whl_url + '";\n' - nix_content += ' sha256 = "' + whl_sha256 + '";\n' - nix_content += " };\n" - nix_content += " };\n" - build_from_src = False - package_added = True - break - - if build_from_src: - for component in url_section: - if component.get("packagetype") == "sdist": - sdist_url = component.get("url") - sdist_sha256 = get_sha256_hash(sdist_url) - nix_content += ( - " " + name + " = disableAllTests super." + name + " {\n" - ) - nix_content += ' pname = "' + name + '";\n' - nix_content += ' version = "' + version + '";\n' - nix_content += " __intentionallyOverridingVersion = true;\n" - nix_content += " src = pkgs.fetchurl {\n" - nix_content += ' url = "' + sdist_url + '";\n' - nix_content += ' sha256 = "' + sdist_sha256 + '";\n' - nix_content += " };\n" - nix_content += " };\n" - package_added = True - break - if not package_added: - need_review_packages_list.append(dep) - except requests.exceptions.RequestException: - need_review_packages_list.append(dep) - else: if name == "django-rest-hooks" and version == "1.6.1": nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" nix_content += ' pname = "django-rest-hooks";\n' @@ -204,6 +179,8 @@ def create_defualt_nix(dependencies_list, meta_dict): ) nix_content += " };\n" nix_content += " };\n" + # This section can be removed once funcparserlib is updated to >=1.0.0 + # https://github.com/aboutcode-org/dejacode/issues/394 elif name == "funcparserlib" and version == "0.3.6": nix_content += " " + name + " = self.buildPythonPackage rec {\n" nix_content += ' pname = "funcparserlib";\n' @@ -242,6 +219,56 @@ def create_defualt_nix(dependencies_list, meta_dict): nix_content += " };\n" else: need_review_packages_list.append(dep) + else: + url = "https://pypi.org/pypi/{name}/{version}/json".format(name=name, version=version) + try: + response = requests.get(url, timeout=30) + response.raise_for_status() + data = response.json() + + url_section = data.get("urls", []) + build_from_src = True + package_added = False + for component in url_section: + if component.get("packagetype") == "bdist_wheel": + whl_url = component.get("url") + whl_sha256 = get_sha256_hash(whl_url) + nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" + nix_content += ' pname = "' + name + '";\n' + nix_content += ' version = "' + version + '";\n' + nix_content += ' format = "wheel";\n' + nix_content += " src = pkgs.fetchurl {\n" + nix_content += ' url = "' + whl_url + '";\n' + nix_content += ' sha256 = "' + whl_sha256 + '";\n' + nix_content += " };\n" + nix_content += " };\n" + build_from_src = False + package_added = True + break + + if build_from_src: + for component in url_section: + if component.get("packagetype") == "sdist": + sdist_url = component.get("url") + sdist_sha256 = get_sha256_hash(sdist_url) + nix_content += ( + " " + name + " = disableAllTests super." + name + " {\n" + ) + nix_content += ' pname = "' + name + '";\n' + nix_content += ' version = "' + version + '";\n' + nix_content += " __intentionallyOverridingVersion = true;\n" + nix_content += " src = pkgs.fetchurl {\n" + nix_content += ' url = "' + sdist_url + '";\n' + nix_content += ' sha256 = "' + sdist_sha256 + '";\n' + nix_content += " };\n" + nix_content += " };\n" + package_added = True + break + if not package_added: + need_review_packages_list.append(dep) + except requests.exceptions.RequestException: + need_review_packages_list.append(dep) + nix_content += """ }; pythonWithOverlay = python.override { @@ -360,10 +387,6 @@ def cleanup_nix_store(): def build_nix_with_docker(): - # Create output directory - output_dir = Path("dist/nix") - output_dir.mkdir(parents=True, exist_ok=True) - docker_cmd = [ "docker", "run", @@ -377,35 +400,27 @@ def build_nix_with_docker(): "nixos/nix", "/bin/sh", "-c", - """set -ex + """set -e # Update nix-channel to get latest packages - nix-channel --update + nix-channel --update > /dev/null 2>&1 - # Run nix-build - nix-build default.nix -o result + # Run nix-build, only show errors + nix-build default.nix -o result 2>&1 | grep -E "(error|fail|Error|Fail)" || true # Check if build was successful if [ -d result ]; then - # Copy the build result to dist/nix/ - mkdir -p /workspace/dist/nix - # Use nix-store to get the actual store path - STORE_PATH=$(readlink result) - cp -r "$STORE_PATH"/* /workspace/dist/nix/ || true - - # Also copy the symlink target directly if directory copy fails - if [ ! "$(ls -A /workspace/dist/nix/)" ]; then - # If directory is empty, try to copy the store path itself - cp -r "$STORE_PATH" /workspace/dist/nix/store_result || true - fi - + echo "Build successfully using default.nix." + echo "Performing cleanup..." + # Perform cleanup # Remove the result symlink rm -f result # Run garbage collection to clean up - nix-collect-garbage -d - + # supress logs + nix-collect-garbage -d > /dev/null 2>&1 + echo "Cleanup completed." else - echo "Error: nix-build failed - result directory not found" >&2 + echo "Error: nix-build failed" >&2 exit 1 fi """, @@ -413,13 +428,6 @@ def build_nix_with_docker(): try: subprocess.run(docker_cmd, check=True, shell=False) # noqa: S603 - # Verify if the output directory contains any files or - # subdirectories. - if any(output_dir.iterdir()): - print(f"\nNix build completed. Results in: {output_dir}") - else: - print("Nix build failed.", file=sys.stderr) - except subprocess.CalledProcessError as e: print(f"Build failed: {e}", file=sys.stderr) sys.exit(1) @@ -431,9 +439,20 @@ def main(): print("Error: Docker not found. Please install Docker first.", file=sys.stderr) sys.exit(1) - parser = argparse.ArgumentParser(description="Package to Nix using Docker.") + # Get the directory where the current script is located (which is located in etc/scripts) + script_dir = Path(__file__).parent.resolve() + # Go up two levels from etc/scripts/ + project_root = script_dir.parent.parent + os.chdir(project_root) + + parser = argparse.ArgumentParser(description="Package to Nix") + # Add optional arguments parser.add_argument("--generate", action="store_true", help="Generate the default.nix file.") + parser.add_argument( + "--test", action="store_true", help="Test to build from the default.nix file." + ) + # Parse arguments args = parser.parse_args() if args.generate or not Path("default.nix").exists(): @@ -464,9 +483,10 @@ def main(): ) sys.exit(1) - # Clean up the volume to ensure consistent state - cleanup_nix_store() - build_nix_with_docker() + if args.test: + print("Testing the default.nix build...") + cleanup_nix_store() + build_nix_with_docker() if __name__ == "__main__": From ec8c81ab4e432a56d8f3df3809d2369601077049 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Fri, 31 Oct 2025 13:56:50 +0800 Subject: [PATCH 06/15] Remove "funcparserlib" references #339 - Updated documentation for the installation process - Added special handling for "python_ldap", which requires specific dependencies when building from source - Refactored and updated helper functions Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 101 +++++++++++++++----------------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index f48a98e0..a775e3cb 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -4,10 +4,32 @@ pyproject.toml file that is used to build the Python package for NixOS and put it under the project root. -Requirement: `toml` and `requests` Python packages and Docker installed. +Requirement: + - toml + - requests + - Docker + - nix-prefetch-url + + To install the required Python packages, run: pip install toml requests +To install Docker, follow the instructions at: + https://docs.docker.com/get-docker/ + +To install "nix-prefetch-url", follow the following instructions: + # Install Nix in single-user mode + curl -L https://nixos.org/nix/install | sh -s -- --no-daemon + + # Source nix in your current shell + . ~/.nix-profile/etc/profile.d/nix.sh + + # Reload your shell or open new terminal + source ~/.bashrc + + # Verify nix-prefetch-url works + which nix-prefetch-url + To run the script: python build_nix_docker.py @@ -119,23 +141,15 @@ def create_defualt_nix(dependencies_list, meta_dict): let python = pkgs.python313; - # Helper function to override a package to disable tests - disableAllTests = - package: extraAttrs: - package.overrideAttrs ( - old: - { + # Helper function to create packages with specific versions and disabled tests + buildCustomPackage = { pname, version, format ? "wheel", src, ... }@attrs: + python.pkgs.buildPythonPackage ({ + inherit pname version format src; doCheck = false; doInstallCheck = false; doPytestCheck = false; pythonImportsCheck = []; - checkPhase = "echo 'Tests disabled'"; - installCheckPhase = "echo 'Install checks disabled'"; - pytestCheckPhase = "echo 'Pytest checks disabled'"; - __intentionallyOverridingVersion = old.__intentionallyOverridingVersion or false; - } - // extraAttrs - ); + } // attrs); pythonOverlay = self: super: { """ @@ -145,11 +159,12 @@ def create_defualt_nix(dependencies_list, meta_dict): print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"])) name = dep["name"] version = dep["version"] - # Handle 'django_notifications_patched','django-rest-hooks' and 'funcparserlib' separately + # Handle 'django_notifications_patched', 'django-rest-hooks' and + # 'python_ldap' separately if ( name == "django-rest-hooks" or name == "django_notifications_patched" - or (name == "funcparserlib" and version == "0.3.6") + or name == "python_ldap" ): if name == "django-rest-hooks" and version == "1.6.1": nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" @@ -179,43 +194,23 @@ def create_defualt_nix(dependencies_list, meta_dict): ) nix_content += " };\n" nix_content += " };\n" - # This section can be removed once funcparserlib is updated to >=1.0.0 - # https://github.com/aboutcode-org/dejacode/issues/394 - elif name == "funcparserlib" and version == "0.3.6": - nix_content += " " + name + " = self.buildPythonPackage rec {\n" - nix_content += ' pname = "funcparserlib";\n' - nix_content += ' version = "0.3.6";\n' + elif name == "python_ldap" and version == "3.4.5": + nix_content += " " + name + " = buildCustomPackage {\n" + nix_content += ' pname = "python_ldap";\n' + nix_content += ' version = "3.4.5";\n' nix_content += ' format = "setuptools";\n' - nix_content += " doCheck = false;\n" nix_content += " src = pkgs.fetchurl {\n" - nix_content += ' url = "https://files.pythonhosted.org/packages/cb/f7/b4a59c3ccf67c0082546eaeb454da1a6610e924d2e7a2a21f337ecae7b40/funcparserlib-0.3.6.tar.gz";\n' + nix_content += ' url = "https://files.pythonhosted.org/packages/0c/88/8d2797decc42e1c1cdd926df4f005e938b0643d0d1219c08c2b5ee8ae0c0/python_ldap-3.4.5.tar.gz";\n' nix_content += ( - ' sha256 = "07f9cgjr3h4j2m67fhwapn8fja87vazl58zsj4yppf9y3an2x6dp";\n' + ' sha256 = "16pplmqb5wqinzy4azbafr3iiqhy65qzwbi1hmd6lb7y6wffzxmj";\n' ) - nix_content += " };\n\n" - # Original setpy.py: - # https://github.com/vlasovskikh/funcparserlib/blob/0.3.6/setup.py - # funcparserlib version 0.3.6 uses use_2to3 which is no - # longer supported in modern setuptools. Remove the - # "use_2to3" from the setup.py - nix_content += " postPatch = ''\n" - nix_content += " cat > setup.py << EOF\n" - nix_content += " # -*- coding: utf-8 -*-\n" - nix_content += " from setuptools import setup\n" - nix_content += " setup(\n" - nix_content += ' name="funcparserlib",\n' - nix_content += ' version="0.3.6",\n' - nix_content += ' packages=["funcparserlib", "funcparserlib.tests"],\n' - nix_content += ' author="Andrey Vlasovskikh",\n' - nix_content += ' description="Recursive descent parsing library based" \ - "on functional combinators",\n' - nix_content += ' license="MIT",\n' - nix_content += ' url="http://code.google.com/p/funcparserlib/",\n' - nix_content += " )\n" - nix_content += " EOF\n" - nix_content += " '';\n" - nix_content += " propagatedBuildInputs = with self; [];\n" - nix_content += " checkPhase = \"echo 'Tests disabled for funcparserlib'\";\n" + nix_content += " };\n" + nix_content += " nativeBuildInputs = with pkgs; [\n" + nix_content += " pkg-config\n" + nix_content += " python.pkgs.setuptools\n" + nix_content += " python.pkgs.distutils\n" + nix_content += " ];\n" + nix_content += " buildInputs = with pkgs; [ openldap cyrus_sasl ];\n" nix_content += " };\n" else: need_review_packages_list.append(dep) @@ -233,7 +228,7 @@ def create_defualt_nix(dependencies_list, meta_dict): if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") whl_sha256 = get_sha256_hash(whl_url) - nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" + nix_content += " " + name + " = buildCustomPackage {\n" nix_content += ' pname = "' + name + '";\n' nix_content += ' version = "' + version + '";\n' nix_content += ' format = "wheel";\n' @@ -251,12 +246,10 @@ def create_defualt_nix(dependencies_list, meta_dict): if component.get("packagetype") == "sdist": sdist_url = component.get("url") sdist_sha256 = get_sha256_hash(sdist_url) - nix_content += ( - " " + name + " = disableAllTests super." + name + " {\n" - ) + nix_content += " " + name + " = buildCustomPackage {\n" nix_content += ' pname = "' + name + '";\n' nix_content += ' version = "' + version + '";\n' - nix_content += " __intentionallyOverridingVersion = true;\n" + nix_content += ' format = "setuptools";\n' nix_content += " src = pkgs.fetchurl {\n" nix_content += ' url = "' + sdist_url + '";\n' nix_content += ' sha256 = "' + sdist_sha256 + '";\n' From 6c27141ddead0be56042981187336308316bf0b1 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 4 Nov 2025 11:21:41 +0800 Subject: [PATCH 07/15] Use architecture-independent wheels and confirm `libpq` is available at runtime #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index a775e3cb..d56369ae 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -227,6 +227,12 @@ def create_defualt_nix(dependencies_list, meta_dict): for component in url_section: if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") + if ( + ("cp313" not in whl_url and "py3" not in whl_url) + or ("manylinux" not in whl_url and "-none-" not in whl_url) + or ("any.whl" not in whl_url and "x86_64" not in whl_url) + ): + continue whl_sha256 = get_sha256_hash(whl_url) nix_content += " " + name + " = buildCustomPackage {\n" nix_content += ' pname = "' + name + '";\n' @@ -310,6 +316,16 @@ def create_defualt_nix(dependencies_list, meta_dict): pip ]; + # Add PostgreSQL to buildInputs to ensure libpq is available at runtime + buildInputs = with pkgs; [ + postgresql + ]; + + # This wrapper ensures the PostgreSQL libraries are available at runtime + makeWrapperArgs = [ + "--set LD_LIBRARY_PATH ${pkgs.postgresql.lib}/lib" + ]; + propagatedBuildInputs = with pythonWithOverlay.pkgs; [ """ From 6df3455477465feb92ba08ae248600785f0d8432 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 4 Nov 2025 13:02:35 +0800 Subject: [PATCH 08/15] Specify the wheel file built for the x86_64 architecture #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index d56369ae..54c91351 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -230,7 +230,7 @@ def create_defualt_nix(dependencies_list, meta_dict): if ( ("cp313" not in whl_url and "py3" not in whl_url) or ("manylinux" not in whl_url and "-none-" not in whl_url) - or ("any.whl" not in whl_url and "x86_64" not in whl_url) + or ("any.whl" not in whl_url and "x86_64.whl" not in whl_url) ): continue whl_sha256 = get_sha256_hash(whl_url) From 269073d41b545f4c670fba9175fe327977f3fa49 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 5 Nov 2025 17:34:20 +0800 Subject: [PATCH 09/15] Implement a better approach for conditional check for wheel compatibility #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 44 +++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index 54c91351..c41be308 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -131,6 +131,44 @@ def extract_project_dependencies(pyproject_data): return dependencies_list +def extract_tags_from_url(url): + """Extract tags from wheel URL""" + tags = set() + + # Python version tags + if "cp313" in url: + tags.add("cp313") + if "py3" in url: + tags.add("py3") + + # Platform tags + if "manylinux" in url: + tags.add("manylinux") + if "-none-" in url: + tags.add("none") + if "any.whl" in url: + tags.add("any") + if "x86_64.whl" in url: + tags.add("x86_64") + + return tags + +def is_compatible_wheel(url): + """Check if wheel is compatible using tag matching""" + wheel_tags = extract_tags_from_url(url) + + # Define compatible tag combinations + compatible_python = {"cp313", "py3"} + # Architecture-free or linux + compatible_platforms = {"manylinux", "none", "any", "x86_64"} + + # Check if wheel has required python version AND compatible platform + has_required_python = not wheel_tags.isdisjoint(compatible_python) + has_compatible_platform = not wheel_tags.isdisjoint(compatible_platforms) + + return has_required_python and has_compatible_platform + + def create_defualt_nix(dependencies_list, meta_dict): # Create a default.nix nix_content = """ @@ -227,11 +265,7 @@ def create_defualt_nix(dependencies_list, meta_dict): for component in url_section: if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") - if ( - ("cp313" not in whl_url and "py3" not in whl_url) - or ("manylinux" not in whl_url and "-none-" not in whl_url) - or ("any.whl" not in whl_url and "x86_64.whl" not in whl_url) - ): + if not is_compatible_wheel(whl_url): continue whl_sha256 = get_sha256_hash(whl_url) nix_content += " " + name + " = buildCustomPackage {\n" From b83542fdd0db1ef71ef0f3ec72bdbec694464f79 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 5 Nov 2025 17:35:39 +0800 Subject: [PATCH 10/15] Code reformatted #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 1 + 1 file changed, 1 insertion(+) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index c41be308..cefb4b21 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -153,6 +153,7 @@ def extract_tags_from_url(url): return tags + def is_compatible_wheel(url): """Check if wheel is compatible using tag matching""" wheel_tags = extract_tags_from_url(url) From e1a8ba9a2e15a9208095542db95752e8f861bc59 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Tue, 18 Nov 2025 19:27:43 +0800 Subject: [PATCH 11/15] Refactor code to better handle "python_ldap" #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 42 ++++++++++++++------------------- 1 file changed, 18 insertions(+), 24 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index cefb4b21..4f70da95 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -182,13 +182,29 @@ def create_defualt_nix(dependencies_list, meta_dict): # Helper function to create packages with specific versions and disabled tests buildCustomPackage = { pname, version, format ? "wheel", src, ... }@attrs: + let + # Define special build inputs for specific packages + specialBuildInputs = { + python_ldap = { + nativeBuildInputs = with pkgs; [ + pkg-config + python.pkgs.setuptools + python.pkgs.distutils + ]; + buildInputs = with pkgs; [ openldap cyrus_sasl ]; + }; + }; + + # Get the special build inputs for this package, or empty if none + specialInputs = specialBuildInputs.${pname} or {}; + in python.pkgs.buildPythonPackage ({ inherit pname version format src; doCheck = false; doInstallCheck = false; doPytestCheck = false; pythonImportsCheck = []; - } // attrs); + } // specialInputs // attrs); pythonOverlay = self: super: { """ @@ -200,11 +216,7 @@ def create_defualt_nix(dependencies_list, meta_dict): version = dep["version"] # Handle 'django_notifications_patched', 'django-rest-hooks' and # 'python_ldap' separately - if ( - name == "django-rest-hooks" - or name == "django_notifications_patched" - or name == "python_ldap" - ): + if name == "django-rest-hooks" or name == "django_notifications_patched": if name == "django-rest-hooks" and version == "1.6.1": nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" nix_content += ' pname = "django-rest-hooks";\n' @@ -233,24 +245,6 @@ def create_defualt_nix(dependencies_list, meta_dict): ) nix_content += " };\n" nix_content += " };\n" - elif name == "python_ldap" and version == "3.4.5": - nix_content += " " + name + " = buildCustomPackage {\n" - nix_content += ' pname = "python_ldap";\n' - nix_content += ' version = "3.4.5";\n' - nix_content += ' format = "setuptools";\n' - nix_content += " src = pkgs.fetchurl {\n" - nix_content += ' url = "https://files.pythonhosted.org/packages/0c/88/8d2797decc42e1c1cdd926df4f005e938b0643d0d1219c08c2b5ee8ae0c0/python_ldap-3.4.5.tar.gz";\n' - nix_content += ( - ' sha256 = "16pplmqb5wqinzy4azbafr3iiqhy65qzwbi1hmd6lb7y6wffzxmj";\n' - ) - nix_content += " };\n" - nix_content += " nativeBuildInputs = with pkgs; [\n" - nix_content += " pkg-config\n" - nix_content += " python.pkgs.setuptools\n" - nix_content += " python.pkgs.distutils\n" - nix_content += " ];\n" - nix_content += " buildInputs = with pkgs; [ openldap cyrus_sasl ];\n" - nix_content += " };\n" else: need_review_packages_list.append(dep) else: From c92f830a14156a110049a67cffde1e2ca2c48fe3 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Thu, 20 Nov 2025 00:58:52 +0800 Subject: [PATCH 12/15] Refactor code #339 - Better error handling - Reduce the hardcoded code Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 99 ++++++++++++++++++++++++++------- 1 file changed, 78 insertions(+), 21 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index 4f70da95..ebf89e8d 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -93,6 +93,25 @@ def read_pyproject_toml(): return pyproject_data +def extract_python_version_from_pyproject(): + """Extract Python version from pyproject.toml and return major.minor""" + pyproject_data = read_pyproject_toml() + requires_python = pyproject_data["project"].get("requires-python", "") + + if requires_python: + import re + + # Match any version pattern: 2.7, 3.8, 3.13, 4.0, etc. + version_match = re.search(r"(\d+)\.(\d+)", requires_python) + if version_match: + major = version_match.group(1) + minor = version_match.group(2) + return f"{major}.{minor}" + + # Default to current Python version if not specified + return f"{sys.version_info.major}.{sys.version_info.minor}" + + def extract_project_meta(pyproject_data): # Extract project metadata from pyproject.toml data. project_data = pyproject_data["project"] @@ -102,8 +121,26 @@ def extract_project_meta(pyproject_data): authors = project_data.get("authors") author_names = [author.get("name", "") for author in authors if "name" in author] author_str = ", ".join(author_names) - - meta_dict = {"name": name, "version": version, "description": description, "author": author_str} + requires_python = project_data.get("requires-python", "") + # Current system's python version + python_version = f"{sys.version_info.major}.{sys.version_info.minor}" + + if requires_python: + import re + + version_match = re.search(r"(\d+)(?:\.(\d+))?", requires_python) + if version_match: + major = version_match.group(1) + minor = version_match.group(2) if version_match.group(2) else "0" + python_version = f"{major}.{minor}" + + meta_dict = { + "name": name, + "version": version, + "description": description, + "author": author_str, + "requires_python": python_version, + } return meta_dict @@ -131,15 +168,15 @@ def extract_project_dependencies(pyproject_data): return dependencies_list -def extract_tags_from_url(url): +def extract_tags_from_url(url, major, minor): """Extract tags from wheel URL""" tags = set() # Python version tags - if "cp313" in url: - tags.add("cp313") - if "py3" in url: - tags.add("py3") + if f"py{major}" in url: + tags.add(f"py{major}") + if f"cp{major}{minor}" in url: + tags.add(f"cp{major}{minor}") # Platform tags if "manylinux" in url: @@ -154,20 +191,20 @@ def extract_tags_from_url(url): return tags -def is_compatible_wheel(url): +def is_compatible_wheel(url, python_version): """Check if wheel is compatible using tag matching""" - wheel_tags = extract_tags_from_url(url) + major, minor = python_version.split(".") + wheel_tags = extract_tags_from_url(url, major, minor) - # Define compatible tag combinations - compatible_python = {"cp313", "py3"} - # Architecture-free or linux - compatible_platforms = {"manylinux", "none", "any", "x86_64"} + # Check Python compatibility + has_required_python = any(tag in wheel_tags for tag in [f"py{major}", f"cp{major}{minor}"]) - # Check if wheel has required python version AND compatible platform - has_required_python = not wheel_tags.isdisjoint(compatible_python) - has_compatible_platform = not wheel_tags.isdisjoint(compatible_platforms) + # Check platform + has_allowed_platform = ("any" in wheel_tags or "x86_64" in wheel_tags) and ( + "manylinux" in wheel_tags or "none" in wheel_tags + ) - return has_required_python and has_compatible_platform + return has_required_python and has_allowed_platform def create_defualt_nix(dependencies_list, meta_dict): @@ -210,6 +247,7 @@ def create_defualt_nix(dependencies_list, meta_dict): """ need_review_packages_list = [] deps_size = len(dependencies_list) + python_version = meta_dict["requires_python"] for idx, dep in enumerate(dependencies_list): print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"])) name = dep["name"] @@ -257,10 +295,11 @@ def create_defualt_nix(dependencies_list, meta_dict): url_section = data.get("urls", []) build_from_src = True package_added = False + for component in url_section: if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") - if not is_compatible_wheel(whl_url): + if not is_compatible_wheel(whl_url, python_version): continue whl_sha256 = get_sha256_hash(whl_url) nix_content += " " + name + " = buildCustomPackage {\n" @@ -362,12 +401,20 @@ def create_defualt_nix(dependencies_list, meta_dict): name = dep["name"] nix_content += " " + name + "\n" - nix_content += """ + nix_content += ( + """ ]; meta = with pkgs.lib; { - description = "Automate open source license compliance and ensure supply chain integrity"; - license = "AGPL-3.0-only"; + description =\"""" + + meta_dict.get( + "description", + "Automate open source license compliance and ensure supply chain integrity.", + ) + + """\"; + license = \"""" + + meta_dict.get("license", "AGPL-3.0-only") + + """\"; maintainers = ["AboutCode.org"]; platforms = platforms.linux; }; @@ -382,6 +429,7 @@ def create_defualt_nix(dependencies_list, meta_dict): default = pythonApp; } """ + ) return nix_content, need_review_packages_list @@ -493,6 +541,14 @@ def main(): # Parse arguments args = parser.parse_args() + # No argument is provided + if len(sys.argv) == 1 and Path("default.nix").exists(): + print("Info: 'default.nix' exists and no arguments provided.") + print("Options:") + print(" --generate Re-generate default.nix") + print(" --test Test build with existing default.nix") + sys.exit(0) + if args.generate or not Path("default.nix").exists(): # Check if "nix-prefetch-url" is available if not shutil.which("nix-prefetch-url"): @@ -502,6 +558,7 @@ def main(): print("Generating default.nix") pyproject_data = read_pyproject_toml() meta_dict = extract_project_meta(pyproject_data) + dependencies_list = extract_project_dependencies(pyproject_data) defualt_nix_content, need_review = create_defualt_nix(dependencies_list, meta_dict) with open("default.nix", "w") as file: From 863898e42c958e4b5af79d1c3a3ba6199f2e8f0e Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Thu, 20 Nov 2025 12:59:28 +0800 Subject: [PATCH 13/15] Remove non-used code and updated hardcoded code #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 34 +++++++++------------------------ 1 file changed, 9 insertions(+), 25 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index ebf89e8d..c46efad7 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -92,26 +92,6 @@ def read_pyproject_toml(): return pyproject_data - -def extract_python_version_from_pyproject(): - """Extract Python version from pyproject.toml and return major.minor""" - pyproject_data = read_pyproject_toml() - requires_python = pyproject_data["project"].get("requires-python", "") - - if requires_python: - import re - - # Match any version pattern: 2.7, 3.8, 3.13, 4.0, etc. - version_match = re.search(r"(\d+)\.(\d+)", requires_python) - if version_match: - major = version_match.group(1) - minor = version_match.group(2) - return f"{major}.{minor}" - - # Default to current Python version if not specified - return f"{sys.version_info.major}.{sys.version_info.minor}" - - def extract_project_meta(pyproject_data): # Extract project metadata from pyproject.toml data. project_data = pyproject_data["project"] @@ -191,9 +171,8 @@ def extract_tags_from_url(url, major, minor): return tags -def is_compatible_wheel(url, python_version): +def is_compatible_wheel(url, major, minor): """Check if wheel is compatible using tag matching""" - major, minor = python_version.split(".") wheel_tags = extract_tags_from_url(url, major, minor) # Check Python compatibility @@ -208,6 +187,12 @@ def is_compatible_wheel(url, python_version): def create_defualt_nix(dependencies_list, meta_dict): + python_version = meta_dict["requires_python"] + py_version_major, py_version_minor = python_version.split(".") + if py_version_minor == "0": + python_use = f"python{py_version_major}" + else: + python_use = f"python{py_version_major}{py_version_minor}" # Create a default.nix nix_content = """ { @@ -215,7 +200,7 @@ def create_defualt_nix(dependencies_list, meta_dict): }: let - python = pkgs.python313; + python = pkgs.""" + python_use + """; # Helper function to create packages with specific versions and disabled tests buildCustomPackage = { pname, version, format ? "wheel", src, ... }@attrs: @@ -247,7 +232,6 @@ def create_defualt_nix(dependencies_list, meta_dict): """ need_review_packages_list = [] deps_size = len(dependencies_list) - python_version = meta_dict["requires_python"] for idx, dep in enumerate(dependencies_list): print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"])) name = dep["name"] @@ -299,7 +283,7 @@ def create_defualt_nix(dependencies_list, meta_dict): for component in url_section: if component.get("packagetype") == "bdist_wheel": whl_url = component.get("url") - if not is_compatible_wheel(whl_url, python_version): + if not is_compatible_wheel(whl_url, py_version_major, py_version_minor): continue whl_sha256 = get_sha256_hash(whl_url) nix_content += " " + name + " = buildCustomPackage {\n" From e016700156ea742d18472f8e6764d74c67b93797 Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Thu, 20 Nov 2025 19:59:38 +0800 Subject: [PATCH 14/15] Fix the "psycopg" issue and make tests run without setting PYTHONPATH #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 38 +++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index c46efad7..f1c61148 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -92,6 +92,7 @@ def read_pyproject_toml(): return pyproject_data + def extract_project_meta(pyproject_data): # Extract project metadata from pyproject.toml data. project_data = pyproject_data["project"] @@ -194,13 +195,16 @@ def create_defualt_nix(dependencies_list, meta_dict): else: python_use = f"python{py_version_major}{py_version_minor}" # Create a default.nix - nix_content = """ + nix_content = ( + """ { pkgs ? import { }, }: let - python = pkgs.""" + python_use + """; + python = pkgs.""" + + python_use + + """; # Helper function to create packages with specific versions and disabled tests buildCustomPackage = { pname, version, format ? "wheel", src, ... }@attrs: @@ -215,6 +219,14 @@ def create_defualt_nix(dependencies_list, meta_dict): ]; buildInputs = with pkgs; [ openldap cyrus_sasl ]; }; + psycopg = { + nativeBuildInputs = with pkgs; [ + pkg-config + ]; + buildInputs = with pkgs; [ + postgresql + ]; + }; }; # Get the special build inputs for this package, or empty if none @@ -230,6 +242,7 @@ def create_defualt_nix(dependencies_list, meta_dict): pythonOverlay = self: super: { """ + ) need_review_packages_list = [] deps_size = len(dependencies_list) for idx, dep in enumerate(dependencies_list): @@ -366,6 +379,8 @@ def create_defualt_nix(dependencies_list, meta_dict): setuptools wheel pip + pkgs.pkg-config + pkgs.makeWrapper ]; # Add PostgreSQL to buildInputs to ensure libpq is available at runtime @@ -373,10 +388,21 @@ def create_defualt_nix(dependencies_list, meta_dict): postgresql ]; - # This wrapper ensures the PostgreSQL libraries are available at runtime - makeWrapperArgs = [ - "--set LD_LIBRARY_PATH ${pkgs.postgresql.lib}/lib" - ]; + # Ensure proper runtime setup. + postInstall = '' + # Create source directory like RPM does (for source access) + mkdir -p $out/src + cp -r ${./.}/* $out/src/ 2>/dev/null || true + + # Remove build artifacts from source copy + rm -rf $out/src/dist $out/src/build $out/src/result $out/src/default.nix 2>/dev/null || true + + # Wrap the dejacode executable to set up the runtime environment + wrapProgram $out/bin/dejacode \ + --set PYTHONPATH "$out/src:$out/lib/python*/site-packages" \ + --set LD_LIBRARY_PATH "${pkgs.postgresql.lib}/lib:$LD_LIBRARY_PATH" \ + --run "cd $out/src" + ''; propagatedBuildInputs = with pythonWithOverlay.pkgs; [ """ From c9b9fbbc6dfb1dc11f5acac0fa70d7e720a122ab Mon Sep 17 00:00:00 2001 From: Chin Yeung Li Date: Wed, 26 Nov 2025 11:20:44 +0800 Subject: [PATCH 15/15] Code cleanup and fixed the ModuleNotFoundError issue for 'psycopg2' #339 Signed-off-by: Chin Yeung Li --- etc/scripts/build_nix_docker.py | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/etc/scripts/build_nix_docker.py b/etc/scripts/build_nix_docker.py index f1c61148..1d842de8 100644 --- a/etc/scripts/build_nix_docker.py +++ b/etc/scripts/build_nix_docker.py @@ -188,6 +188,7 @@ def is_compatible_wheel(url, major, minor): def create_defualt_nix(dependencies_list, meta_dict): + """Generate the default.nix""" python_version = meta_dict["requires_python"] py_version_major, py_version_minor = python_version.split(".") if py_version_minor == "0": @@ -219,14 +220,6 @@ def create_defualt_nix(dependencies_list, meta_dict): ]; buildInputs = with pkgs; [ openldap cyrus_sasl ]; }; - psycopg = { - nativeBuildInputs = with pkgs; [ - pkg-config - ]; - buildInputs = with pkgs; [ - postgresql - ]; - }; }; # Get the special build inputs for this package, or empty if none @@ -249,8 +242,7 @@ def create_defualt_nix(dependencies_list, meta_dict): print("Processing {}/{}: {}".format(idx + 1, deps_size, dep["name"])) name = dep["name"] version = dep["version"] - # Handle 'django_notifications_patched', 'django-rest-hooks' and - # 'python_ldap' separately + # Handle 'django_notifications_patched' and 'django-rest-hooks' seperately if name == "django-rest-hooks" or name == "django_notifications_patched": if name == "django-rest-hooks" and version == "1.6.1": nix_content += " " + name + " = python.pkgs.buildPythonPackage {\n" @@ -380,23 +372,22 @@ def create_defualt_nix(dependencies_list, meta_dict): wheel pip pkgs.pkg-config - pkgs.makeWrapper - ]; - - # Add PostgreSQL to buildInputs to ensure libpq is available at runtime - buildInputs = with pkgs; [ - postgresql ]; # Ensure proper runtime setup. postInstall = '' - # Create source directory like RPM does (for source access) + # Create source directory mkdir -p $out/src - cp -r ${./.}/* $out/src/ 2>/dev/null || true + + # Copy source files, dereferencing symlinks to avoid broken links + cp -rL ${./.}/* $out/src/ 2>/dev/null || true # Remove build artifacts from source copy rm -rf $out/src/dist $out/src/build $out/src/result $out/src/default.nix 2>/dev/null || true + # Remove any bin directories that might contain symlinks + rm -rf $out/src/bin 2>/dev/null || true + # Wrap the dejacode executable to set up the runtime environment wrapProgram $out/bin/dejacode \ --set PYTHONPATH "$out/src:$out/lib/python*/site-packages" \ @@ -405,6 +396,7 @@ def create_defualt_nix(dependencies_list, meta_dict): ''; propagatedBuildInputs = with pythonWithOverlay.pkgs; [ + psycopg2 # Add psycopg2 to fix ModuleNotFoundError issue in Django 5.2.7 """ for dep in dependencies_list: