Skip to content

Commit 7a74171

Browse files
committed
Use launcher approach
Enables use of dlopen(3)
1 parent 2014d88 commit 7a74171

12 files changed

Lines changed: 780 additions & 437 deletions

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@ test-python/
44
output/
55
package/
66
work/
7+
build/

Makefile

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -4,19 +4,20 @@
44
PYTHON_VERSION := 3.12.12
55

66
.PHONY: default clean test
7-
default: python-$(PYTHON_VERSION)-static-x86_64-linux-musl.tar.gz
7+
8+
default: python-$(PYTHON_VERSION)-x86_64-linux.tar.gz
89

910
test: test.log
11+
cat $<
1012

11-
test.log: python-$(PYTHON_VERSION)-static-x86_64-linux-musl.tar.gz test.sh
12-
./test.sh $< >$@ 2>&1
13+
test.log: python-$(PYTHON_VERSION)-x86_64-linux.tar.gz
14+
./test.sh build/output >test.log 2>&1
1315

14-
build/output/bin/python3.12: Containerfile.base build.sh
16+
build/output/bin/python3.12: Containerfile.base build.sh in_container/launcher.c in_container/build.sh
1517
PYTHON_VERSION=$(PYTHON_VERSION) ./build.sh >build.log 2>&1
1618

17-
python-$(PYTHON_VERSION)-static-x86_64-linux-musl.tar.gz: build/output/bin/python3.12 package.sh
19+
python-$(PYTHON_VERSION)-x86_64-linux.tar.gz: build/output/bin/python3.12 package.sh
1820
PYTHON_VERSION=$(PYTHON_VERSION) ./package.sh >package.log 2>&1
1921

2022
clean:
21-
rm -rf test-python
22-
rm -rf build *.tar.gz *.log
23+
rm -rf build *.tar.gz *.log test-python

README.md

Lines changed: 14 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
# Portable Python
22

3-
Self-contained static Python distribution for Linux. Works on any distro without system dependencies. Statically linked from source with musl libc.
3+
Self-contained Python distribution for Linux. Works on any distro without system dependencies. Uses a launcher approach with full dynamic extension support.
44

55
## Features
66

77
- Works on any x86_64 Linux distro (no system dependencies)
8-
- Statically linked binary with SSL, sqlite3, compression support
8+
- Full dynamic C extension support (NumPy, pandas, etc. work!)
9+
- Tiny launcher + shared libpython
910
- Includes pip pre-installed and working
1011
- Space-efficient hardlinked environments
1112
- Independent site-packages per environment
@@ -16,8 +17,8 @@ Self-contained static Python distribution for Linux. Works on any distro without
1617

1718
```bash
1819
make # build tarball from scratch
19-
tar -xzf python-3.12.12-static-x86_64-linux-musl.tar.gz # extract tarball
20-
./python-static/bin/instantiate.py my-env # create isolated environment
20+
tar -xzf python-3.12.12-x86_64-linux.tar.gz # extract tarball
21+
./output/bin/instantiate.py my-env # create isolated environment
2122

2223
# Use the environment:
2324
./my-env/bin/python3 --version
@@ -27,7 +28,8 @@ tar -xzf python-3.12.12-static-x86_64-linux-musl.tar.gz # extract tarball
2728

2829
## How It Works
2930

30-
Python is compiled from source with static linking in an Alpine Linux container. All dependencies (OpenSSL, sqlite3, zlib, bzip2, xz, readline, ncurses) are statically linked into the binary. The result is a truly portable single binary.
31+
Python is compiled from source in an Alpine Linux container.
32+
A small kinda-static launcher dynamically loads libpython at runtime, enabling full dynamic extension support while maintaining portability.
3133

3234
`instantiate.py` creates environments using hardlinks (no copying) with independent site-packages. Multiple environments share base files on disk.
3335

@@ -50,9 +52,9 @@ Python is compiled from source with static linking in an Alpine Linux container.
5052
Create multiple independent environments:
5153

5254
```bash
53-
./python-static/bin/instantiate.py dev-env
54-
./python-static/bin/instantiate.py prod-env
55-
./python-static/bin/instantiate.py test-env
55+
./output/bin/instantiate.py dev-env
56+
./output/bin/instantiate.py prod-env
57+
./output/bin/instantiate.py test-env
5658

5759
# Each gets independent packages
5860
./dev-env/bin/pip install pytest
@@ -62,19 +64,17 @@ Create multiple independent environments:
6264

6365
## Technical Details
6466

65-
- Python 3.12.12 statically linked with musl libc
66-
- OpenSSL, sqlite3, zlib, bzip2, xz, readline, ncurses statically linked
67+
- Python 3.12.12 with launcher approach (kinda-static launcher + shared libpython)
68+
- OpenSSL, sqlite3, zlib, bzip2, xz, readline, ncurses support
69+
- Full dynamic C extension loading support
6770
- x86_64 Linux only
68-
- ~13MB tarball, ~39MB extracted
71+
- ~38MB tarball, ~100MB extracted
6972

7073
## Limitations
7174

7275
- x86_64 Linux only (no ARM, macOS, Windows, BSD)
73-
- the fully static binary cannot load `.so` files at runtime: "Dynamic loading not supported"
74-
- This is a fundamental limitation of fully static binaries with no dynamic linker because the library code cannot find symbols of the binary
7576
- musl libc may be incompatible with some glibc-specific packages
7677
- No Tkinter/GUI packages
77-
- ctypes has limited functionality (pythonapi not available in static build)
7878

7979
## License
8080

build-inside-container.sh

Lines changed: 0 additions & 108 deletions
This file was deleted.

build.sh

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ WORK_DIR="$BUILD_DIR/work"
1010
CACHED_IMAGE="python-builder:alpine-3.22.1-deps"
1111
CONTAINERFILE="$SCRIPT_DIR/Containerfile.base"
1212

13-
info() { echo "$1"; }
1413
error() { echo "ERROR: $1"; exit 1; }
1514

1615
# Check for container runtime
@@ -22,10 +21,16 @@ else
2221
error "Neither podman nor docker found. Please install one."
2322
fi
2423

25-
info "Building base image with dependencies..."
24+
echo "=========================================="
25+
echo "Building Portable Python"
26+
echo " - Tiny static launcher (~14KB)"
27+
echo " - Shared libpython with full dynamic extension support"
28+
echo " - Portable + full functionality"
29+
30+
echo "Building base image with dependencies..."
2631
$CONTAINER_CMD build -t "$CACHED_IMAGE" -f "$CONTAINERFILE" "$SCRIPT_DIR"
2732

28-
info "Building Python $PYTHON_VERSION"
33+
echo "Building Python $PYTHON_VERSION..."
2934
mkdir -p "$OUTPUT_DIR"
3035
mkdir -p "$WORK_DIR"
3136

@@ -36,18 +41,33 @@ $CONTAINER_CMD run --rm \
3641
--env LC_ALL=C.UTF-8 \
3742
--env SOURCE_DATE_EPOCH=1609459200 \
3843
--env PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin \
39-
-v "$SCRIPT_DIR:/build:ro" \
44+
-v "$SCRIPT_DIR/in_container:/in_container:ro" \
4045
-v "$OUTPUT_DIR:/output:rw" \
4146
-v "$WORK_DIR:/work:rw" \
4247
"$CACHED_IMAGE" \
43-
/bin/sh /build/build-inside-container.sh $PYTHON_VERSION
48+
/bin/sh /in_container/build.sh $PYTHON_VERSION
4449

4550
PYTHON_BIN="$OUTPUT_DIR/bin/python${PYTHON_MAJOR_MINOR}"
46-
if [ -f "$PYTHON_BIN" ]; then
47-
CHECKSUM=$(sha256sum "$PYTHON_BIN" | cut -d' ' -f1)
48-
info "Binary: $PYTHON_BIN"
49-
info "Size: $(ls -lh "$PYTHON_BIN" | awk '{print $5}')"
50-
info "SHA256: $CHECKSUM"
51-
else
52-
error "Build failed - output binary not found"
51+
LIBPYTHON="$OUTPUT_DIR/lib/libpython${PYTHON_MAJOR_MINOR}.so.1.0"
52+
53+
if [ ! -f "$PYTHON_BIN" ]; then
54+
error "Build failed - launcher binary not found"
5355
fi
56+
57+
if [ ! -f "$LIBPYTHON" ]; then
58+
error "Build failed - libpython not found"
59+
fi
60+
61+
62+
echo "=========================================="
63+
echo "Build Summary"
64+
echo "Launcher: $PYTHON_BIN"
65+
echo " Size: $(ls -lh "$PYTHON_BIN" | awk '{print $5}')"
66+
echo " SHA256: $(sha256sum "$PYTHON_BIN" | cut -d' ' -f1)"
67+
echo ""
68+
echo "libpython: $LIBPYTHON"
69+
echo " Size: $(ls -lh "$LIBPYTHON" | awk '{print $5}')"
70+
echo " SHA256: $(sha256sum "$LIBPYTHON" | cut -d' ' -f1)"
71+
echo ""
72+
echo "Output directory: $OUTPUT_DIR"
73+
echo "=========================================="

ctypes-static.patch

Lines changed: 0 additions & 16 deletions
This file was deleted.

in_container/README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
This directory contains everything that's needed **inside the container** during the build process.
2+
3+
The host system's `build.sh` script:
4+
5+
1. Builds the Alpine container image
6+
2. Mounts this directory as `/in_container` (read-only)
7+
3. Mounts output directories for build artifacts
8+
4. Runs `/in_container/build.sh` inside the container
9+
10+
This separation keeps the container environment isolated and makes it clear which files are used where.

0 commit comments

Comments
 (0)