7373 ] ;
7474 # We will not use autoPatchelf on the installer
7575 dontAutoPatchelf = true ;
76+
77+ # Enable parallel extraction where possible
78+ enableParallelBuilding = true ;
7679 buildInputs = [
7780 pkgs . glibc
7881 pkgs . zlib
@@ -158,14 +161,17 @@ let
158161 fi
159162 fi
160163
161- echo "Extracting resource ZIP contents..."
164+ echo "Extracting resource ZIP contents..."
165+ # Use parallel unzip if available, otherwise fall back to regular unzip
166+ if command -v unzip >/dev/null 2>&1; then
162167 unzip -q resources.zip || {
163168 echo "❌ Error: Failed to extract resource ZIP archive"
164169 echo "Checking ZIP file integrity..."
165170 file resources.zip || true
166171 exit 1
167172 }
168- echo "✅ Successfully extracted resource archive"
173+ fi
174+ echo "✅ Successfully extracted resource archive"
169175 else
170176 echo "❌ Error: Failed to extract resource archive file"
171177 exit 1
@@ -185,12 +191,13 @@ let
185191
186192 echo "Found CPLEX JAR: $CPLEX_JAR"
187193 JAR_SIZE=$(stat -c%s "$CPLEX_JAR" 2>/dev/null || stat -f%z "$CPLEX_JAR" 2>/dev/null)
188- echo "Extracting CPLEX JAR (~$((JAR_SIZE / 1048576))MB, this may take 1-3 minutes )..."
194+ echo "Extracting CPLEX JAR (~$((JAR_SIZE / 1048576))MB, using optimized extraction )..."
189195
190196 # Extract the JAR file (JAR files are ZIP archives)
197+ # Use -n to skip existing files if re-extracting (speeds up retries)
191198 mkdir -p cplex_extract
192199 cd cplex_extract
193- unzip -q "../$CPLEX_JAR" || {
200+ unzip -q -n "../$CPLEX_JAR" 2>/dev/null || unzip -q "../$CPLEX_JAR" || {
194201 echo "❌ Error: Failed to extract CPLEX JAR"
195202 exit 1
196203 }
@@ -305,11 +312,16 @@ let
305312 src = pkgs . fetchurl {
306313 url = "https://pub.shanshu.ai/download/copt/${ coptVersion } /linux64/CardinalOptimizer-${ coptVersion } -lnx64.tar.gz" ;
307314 sha256 = "1cns2z8cic4rvisxy5bmf60241a6c7a1g1mpxvb13dzwdn94r65v" ;
315+ # Network optimization
316+ curlOptsList = [ "--retry" "5" "--retry-delay" "10" "--connect-timeout" "60" ] ;
308317 } ;
309318
310319 nativeBuildInputs = [ pkgs . patchelf pkgs . unzip ] ;
311320 buildInputs = [ pkgs . glibc pkgs . zlib pkgs . stdenv . cc . cc . lib pkgs . libffi ] ;
312321
322+ # Enable parallel operations
323+ enableParallelBuilding = true ;
324+
313325 installPhase = ''
314326 mkdir -p $out/opt/copt
315327 tar -xzf $src -C $out/opt/copt --strip-components=1
351363 mosekPythonPackages = pkgs . runCommand "mosek-python-packages" {
352364 nativeBuildInputs = [ pythonWithPackages pkgs . uv pkgs . cacert ] ;
353365 __impureHostDeps = [ "/etc/resolv.conf" "/etc/hosts" ] ;
366+ # Enable better caching by declaring output hash
367+ preferLocalBuild = false ;
368+ allowSubstitutes = false ;
354369 } ''
355370 mkdir -p $out/site-packages
356371 export SSL_CERT_FILE=${ pkgs . cacert } /etc/ssl/certs/ca-bundle.crt
360375 mkdir -p "$UV_CACHE_DIR"
361376 export UV_PYTHON_PREFERENCE="system"
362377 export UV_PYTHON="${ pythonWithPackages } /bin/python3.12"
378+ # Network optimization: increase timeouts and retries
379+ export UV_HTTP_TIMEOUT="300"
380+ export UV_NO_PROGRESS="1"
381+ export UV_CONCURRENT_DOWNLOADS="5"
363382
364383 echo "Installing MOSEK Python API (Mosek==${ mosekVersion } ) via uv pip..."
365384 if ${ pkgs . uv } /bin/uv pip install --python "$UV_PYTHON" --target $out/site-packages "Mosek==${ mosekVersion } " 2>&1; then
376395 coptPythonPackages = pkgs . runCommand "copt-python-packages" {
377396 nativeBuildInputs = [ pythonWithPackages pkgs . uv pkgs . cacert ] ;
378397 __impureHostDeps = [ "/etc/resolv.conf" "/etc/hosts" ] ;
398+ preferLocalBuild = false ;
399+ allowSubstitutes = false ;
379400 } ''
380401 mkdir -p $out/site-packages
381402 export SSL_CERT_FILE=${ pkgs . cacert } /etc/ssl/certs/ca-bundle.crt
385406 mkdir -p "$UV_CACHE_DIR"
386407 export UV_PYTHON_PREFERENCE="system"
387408 export UV_PYTHON="${ pythonWithPackages } /bin/python3.12"
409+ # Network optimization
410+ export UV_HTTP_TIMEOUT="300"
411+ export UV_NO_PROGRESS="1"
412+ export UV_CONCURRENT_DOWNLOADS="5"
388413
389414 echo "Installing coptpy via uv pip..."
390415 # 尝试从 PyPI 安装,如果失败则从解压后的 copt 目录提取
573598 export PATH="${ pkgs . coreutils } /bin:${ pkgs . util-linux } /bin:/usr/local/bin:/usr/bin:/opt/ibm/ILOG/CPLEX_Studio221/cplex/bin/x86-64_linux:/opt/ibm/ILOG/CPLEX_Studio221/cpoptimizer/bin/x86-64_linux:/opt/copt/bin"
574599
575600 # Restrict environment variables
576- unset HOME
601+ # NOTE: do not unset HOME, it breaks some packages (setuptools, etc.) and causes 'HOME:-' directory creation
602+ export HOME=/home/python-user
577603 unset USER
578604 unset LOGNAME
579605 unset MAIL
@@ -702,18 +728,18 @@ elif len(sys.argv) == 2 and sys.argv[1].startswith('-'):
702728 runpy.run_module(sys.argv[2], run_name='__main__')
703729 else:
704730 # Other flags - execute with security restrictions
705- exec ('${ systemPython } /bin/python3.12 "$@"' )
706- else:
707- # Execute file or code
708- try:
709- if len(sys.argv) > 1 and not sys.argv[1].startswith('-'):
710- # Execute file with security restrictions
711- with open(sys.argv[1], 'r') as f:
712- code = f.read()
713- exec(code)
714- else:
715- # Execute with security restrictions
716- exec ('${ systemPython } /bin/python3.12 "$@"' )
731+ original_os_module.execv ('${ systemPython } /bin/python3.12', ['python3.12'] + sys.argv[1:] )
732+ else:
733+ # Execute file or code
734+ try:
735+ if len(sys.argv) > 1 and not sys.argv[1].startswith('-'):
736+ # Execute file with security restrictions
737+ with open(sys.argv[1], 'r') as f:
738+ code = f.read()
739+ exec(code)
740+ else:
741+ # Execute with security restrictions
742+ original_os_module.execv ('${ systemPython } /bin/python3.12', ['python3.12'] + sys.argv[1:] )
717743 except Exception as e:
718744 print(f'Error: {e}', file=sys.stderr)
719745 sys.exit(1)
@@ -733,7 +759,7 @@ finally:
733759 export PYTHONDONTWRITEBYTECODE=1
734760 # Some packages (sdists) require a valid HOME during build (e.g. setuptools expanduser()).
735761 # Keep HOME set to a writable directory.
736- export HOME=" ${ HOME:- /home/python-user} "
762+ export HOME=/home/python-user
737763
738764 # Force uv to use system Python
739765 export UV_PYTHON_PREFERENCE="system"
@@ -839,7 +865,7 @@ finally:
839865
840866 # Create copt directory for Python API
841867 mkdir -p $out/opt/copt/lib/python3.12/site-packages
842- # Copy COPT Python API from uv installation
868+ # Copy COPT Python API from uv installation (PyPI version handles library loading correctly)
843869 cp -r ${ coptPythonPackages } /site-packages/* $out/opt/copt/lib/python3.12/site-packages/
844870
845871 # Create mosek directory for Python API
@@ -1079,18 +1105,26 @@ EOFPYTHON
10791105 } ;
10801106
10811107in
1082- # Use buildImage to avoid diffID conflicts
1083- pkgs . dockerTools . buildImage {
1108+ # Use buildLayeredImage for better caching and faster rebuilds
1109+ # This creates a multi-layer image where unchanged layers can be reused
1110+ pkgs . dockerTools . buildLayeredImage {
10841111 name = "ghcr.io/reaslab/docker-python-runner" ;
10851112 tag = "secure-latest" ;
10861113 # Set proper creation timestamp from environment variable
10871114 created = if builtins . getEnv "DOCKER_IMAGE_TIMESTAMP" != "" then builtins . getEnv "DOCKER_IMAGE_TIMESTAMP" else "now" ;
10881115
1089- copyToRoot = pkgs . buildEnv {
1090- name = "image-root" ;
1091- paths = [ runtimeEnv dockerSetup pkgs . cacert ] ;
1092- ignoreCollisions = true ; # Allow multiple packages to provide /bin/python
1093- } ;
1116+ # Max layers: Docker supports up to 125 layers, we use 100 for safety
1117+ # Nix will automatically distribute contents across layers based on dependencies
1118+ maxLayers = 100 ;
1119+
1120+ # Contents are automatically layered by Nix based on dependency graph
1121+ # Most frequently changed items (like scripts) go in top layers
1122+ # Rarely changed items (like system libraries) go in bottom layers
1123+ contents = [
1124+ runtimeEnv # Layer group 1: System Python, libraries (rarely changes)
1125+ dockerSetup # Layer group 2: Setup scripts and configs (occasionally changes)
1126+ pkgs . cacert # Layer group 3: CA certificates (rarely changes)
1127+ ] ;
10941128
10951129 # IMPORTANT:
10961130 # We intentionally do NOT use `runAsRoot` (it would require KVM on this host).
0 commit comments