Ensure python builds from source and fix linting/formatting errors
This commit is contained in:
+28
-14
@@ -4,6 +4,15 @@ from dataclasses import dataclass, field
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class PythonBuildConfig:
|
||||
"""Configuration for building Python from source."""
|
||||
|
||||
version: str = "3.14.3"
|
||||
source_url: str = "https://www.python.org/ftp/python/3.14.3/Python-3.14.3.tgz"
|
||||
sha256: str = "d7fe130d0501ae047ca318fa92aa642603ab6f217901015a1df6ce650d5470cd"
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class DistroConfig:
|
||||
"""Configuration for a Linux distribution build."""
|
||||
@@ -22,12 +31,15 @@ class PipelineConfig:
|
||||
"""Top-level pipeline configuration."""
|
||||
|
||||
fpm_version: str = "1.16.0"
|
||||
project_root: Path = field(default_factory=lambda: Path(__file__).parent.parent.resolve())
|
||||
project_root: Path = field(
|
||||
default_factory=lambda: Path(__file__).parent.parent.resolve()
|
||||
)
|
||||
python_build: PythonBuildConfig = field(default_factory=PythonBuildConfig)
|
||||
|
||||
# Container images (from GHCR)
|
||||
debian_image: str = "ghcr.io/jmr-dev/android-file-handler-debian-builder:debian13-trixie"
|
||||
arch_image: str = "ghcr.io/jmr-dev/android-file-handler-arch-builder:latest"
|
||||
rhel_image: str = "ghcr.io/jmr-dev/android-file-handler-rhel-builder:fedora42"
|
||||
# Base container images (Python and tools are installed by the pipeline)
|
||||
debian_image: str = "debian:13"
|
||||
arch_image: str = "archlinux:latest"
|
||||
rhel_image: str = "fedora:42"
|
||||
|
||||
@property
|
||||
def distros(self) -> list[DistroConfig]:
|
||||
@@ -63,12 +75,14 @@ class PipelineConfig:
|
||||
]
|
||||
|
||||
# Artifact patterns for each distro
|
||||
artifact_patterns: dict[str, list[str]] = field(default_factory=lambda: {
|
||||
"debian": ["dist/android-file-handler_*.deb"],
|
||||
"arch": ["dist/android-file-handler-*.pkg.tar.zst"],
|
||||
"rhel": ["dist/android-file-handler-*.rpm"],
|
||||
"windows": [
|
||||
"dist/android-file-handler-windows.exe",
|
||||
"dist/android-file-handler-setup.exe",
|
||||
],
|
||||
})
|
||||
artifact_patterns: dict[str, list[str]] = field(
|
||||
default_factory=lambda: {
|
||||
"debian": ["dist/android-file-handler_*.deb"],
|
||||
"arch": ["dist/android-file-handler-*.pkg.tar.zst"],
|
||||
"rhel": ["dist/android-file-handler-*.rpm"],
|
||||
"windows": [
|
||||
"dist/android-file-handler-windows.exe",
|
||||
"dist/android-file-handler-setup.exe",
|
||||
],
|
||||
}
|
||||
)
|
||||
|
||||
+218
-15
@@ -1,14 +1,18 @@
|
||||
"""Dagger pipeline for building Linux distribution packages.
|
||||
|
||||
Uses the Dagger Python SDK to run containerized builds for each
|
||||
Linux distribution (Debian, Arch, RHEL) using pre-built builder images.
|
||||
Linux distribution (Debian, Arch, RHEL) starting from base OS images,
|
||||
downloading and compiling Python from source with SHA256 verification,
|
||||
and building the application packages.
|
||||
"""
|
||||
|
||||
# pyright: reportUnknownMemberType=false
|
||||
# pyright: reportUnknownVariableType=false
|
||||
# pyright: reportUnknownArgumentType=false
|
||||
# pyright: reportUnknownParameterType=false
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
@@ -17,33 +21,226 @@ import dagger # type: ignore[import-not-found]
|
||||
from ci.config import DistroConfig, PipelineConfig
|
||||
|
||||
|
||||
def _get_registry_token() -> str | None:
|
||||
"""Read GHCR token from GITHUB_TOKEN or GHCR_TOKEN environment variable."""
|
||||
return os.environ.get("GITHUB_TOKEN") or os.environ.get("GHCR_TOKEN")
|
||||
|
||||
|
||||
def _get_system_deps_cmd(distro_type: str) -> list[str]:
|
||||
"""Get the shell command to install system build dependencies.
|
||||
|
||||
Args:
|
||||
distro_type: One of 'debian', 'arch', 'rhel'.
|
||||
|
||||
Returns:
|
||||
Shell command as list for with_exec.
|
||||
"""
|
||||
if distro_type == "debian":
|
||||
return [
|
||||
"sh",
|
||||
"-c",
|
||||
"apt-get update && apt-get install -y --no-install-recommends "
|
||||
"curl git build-essential ruby ruby-dev gcc make "
|
||||
"zlib1g-dev ca-certificates tcl-dev tk-dev "
|
||||
"libx11-6 libxext6 libxrender1 libxcb1 "
|
||||
"libbz2-dev libreadline-dev libsqlite3-dev libssl-dev libffi-dev "
|
||||
"wget tar liblzma-dev patch && "
|
||||
"apt-get clean && rm -rf /var/lib/apt/lists/*",
|
||||
]
|
||||
elif distro_type == "arch":
|
||||
return [
|
||||
"sh",
|
||||
"-c",
|
||||
"pacman -Syu --noconfirm "
|
||||
"ruby ruby-bundler ruby-rake base-devel curl git tar "
|
||||
"ca-certificates ca-certificates-utils "
|
||||
"tk tcl libx11 libxext libxrender libxcb "
|
||||
"gcc make zlib bzip2 readline sqlite openssl libffi "
|
||||
"wget xz patch && "
|
||||
"update-ca-trust && pacman -Scc --noconfirm",
|
||||
]
|
||||
elif distro_type == "rhel":
|
||||
return [
|
||||
"sh",
|
||||
"-c",
|
||||
"dnf -y update && dnf -y install "
|
||||
"gcc make zlib-devel bzip2 bzip2-devel readline-devel "
|
||||
"sqlite-devel openssl-devel libffi-devel wget tar git curl "
|
||||
"ruby rubygems rpm-build redhat-rpm-config gcc-c++ patch which "
|
||||
"xz-devel tk-devel tcl-devel libX11-devel libXext-devel "
|
||||
"libXrender-devel && dnf clean all",
|
||||
]
|
||||
raise ValueError(f"Unknown distro type: {distro_type}")
|
||||
|
||||
|
||||
def _get_python_configure_env(distro_type: str) -> str:
|
||||
"""Get distro-specific LDFLAGS and CPPFLAGS for Python configure.
|
||||
|
||||
Args:
|
||||
distro_type: One of 'debian', 'arch', 'rhel'.
|
||||
|
||||
Returns:
|
||||
String with environment variable exports for the configure step.
|
||||
"""
|
||||
if distro_type == "debian":
|
||||
return 'LDFLAGS="-L/usr/lib/x86_64-linux-gnu" CPPFLAGS="-I/usr/include/tcl8.6"'
|
||||
elif distro_type == "arch":
|
||||
return 'LDFLAGS="-L/usr/lib" CPPFLAGS="-I/usr/include"'
|
||||
elif distro_type == "rhel":
|
||||
return 'LDFLAGS="-L/usr/lib64" CPPFLAGS="-I/usr/include"'
|
||||
return ""
|
||||
|
||||
|
||||
def _install_fpm(
|
||||
container: dagger.Container,
|
||||
distro_type: str,
|
||||
fpm_version: str,
|
||||
) -> dagger.Container:
|
||||
"""Install fpm (Effing Package Management) in the container.
|
||||
|
||||
Args:
|
||||
container: Dagger container to install fpm in.
|
||||
distro_type: One of 'debian', 'arch', 'rhel'.
|
||||
fpm_version: Version of fpm to install.
|
||||
|
||||
Returns:
|
||||
Container with fpm installed.
|
||||
"""
|
||||
if distro_type == "arch":
|
||||
container = container.with_exec(
|
||||
["gem", "install", "--no-document", "erb"]
|
||||
).with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
f'gem install --no-document -v "{fpm_version}" fpm && '
|
||||
"GEM_BIN_DIR=$(ruby -e 'puts Gem.user_dir')/bin && "
|
||||
'ln -sf "${GEM_BIN_DIR}/fpm" /usr/local/bin/fpm',
|
||||
]
|
||||
)
|
||||
else:
|
||||
container = container.with_exec(
|
||||
["gem", "install", "--no-document", "-v", fpm_version, "fpm"]
|
||||
)
|
||||
return container
|
||||
|
||||
|
||||
async def build_linux_distro(
|
||||
client: dagger.Client,
|
||||
config: PipelineConfig,
|
||||
distro: DistroConfig,
|
||||
registry_token: str | None = None,
|
||||
) -> dict[str, Path]:
|
||||
"""Build a single Linux distribution package inside a Dagger container.
|
||||
|
||||
Starts from a base OS image, compiles Python from source with SHA256
|
||||
verification, installs build tools (Poetry, fpm), and builds the package.
|
||||
|
||||
Args:
|
||||
client: Active Dagger client connection.
|
||||
config: Pipeline configuration.
|
||||
distro: Distribution-specific build configuration.
|
||||
registry_token: Optional registry auth token.
|
||||
|
||||
Returns:
|
||||
Dictionary mapping artifact names to their local output paths.
|
||||
"""
|
||||
print(f"[dagger] Starting {distro.name} build using {distro.container_image}")
|
||||
|
||||
# Mount the project source into the container, excluding local venv
|
||||
source = client.host().directory(
|
||||
str(config.project_root),
|
||||
exclude=[".venv", "__pycache__", "dist", "dist_*", "pkg_dist_*", ".git"],
|
||||
)
|
||||
|
||||
python = config.python_build
|
||||
configure_env = _get_python_configure_env(distro.distro_type)
|
||||
|
||||
base = client.container()
|
||||
if registry_token:
|
||||
secret = client.set_secret("ghcr_token", registry_token)
|
||||
base = base.with_registry_auth("ghcr.io", "_token", secret)
|
||||
|
||||
# Start from base image and install system dependencies
|
||||
container = base.from_(distro.container_image).with_exec(
|
||||
_get_system_deps_cmd(distro.distro_type)
|
||||
)
|
||||
|
||||
# Download Python source and verify SHA256 against python.org
|
||||
print(f"[dagger] Downloading Python {python.version} and verifying SHA256")
|
||||
container = container.with_exec(
|
||||
["wget", "-q", python.source_url, "-O", f"/tmp/Python-{python.version}.tgz"]
|
||||
).with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
f'echo "{python.sha256} /tmp/Python-{python.version}.tgz" '
|
||||
f"| sha256sum -c -",
|
||||
]
|
||||
)
|
||||
|
||||
# Build and install Python from source
|
||||
print(f"[dagger] Compiling Python {python.version} from source")
|
||||
container = (
|
||||
client.container()
|
||||
.from_(distro.container_image)
|
||||
.with_directory("/workspace", source)
|
||||
container.with_exec(
|
||||
["tar", "xzf", f"/tmp/Python-{python.version}.tgz", "-C", "/tmp"]
|
||||
)
|
||||
.with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
f"cd /tmp/Python-{python.version} && "
|
||||
f"{configure_env} ./configure --enable-shared "
|
||||
f"--with-ensurepip=install --prefix=/usr/local && "
|
||||
f"make -j$(nproc) && "
|
||||
f"make install",
|
||||
]
|
||||
)
|
||||
.with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
'echo "/usr/local/lib" > /etc/ld.so.conf.d/python.conf && ldconfig',
|
||||
]
|
||||
)
|
||||
.with_exec(["ln", "-sf", "/usr/local/bin/python3", "/usr/local/bin/python"])
|
||||
.with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
f"rm -rf /tmp/Python-{python.version} "
|
||||
f"/tmp/Python-{python.version}.tgz",
|
||||
]
|
||||
)
|
||||
.with_exec(
|
||||
[
|
||||
"python3",
|
||||
"-c",
|
||||
"import tkinter; import _tkinter; print('tkinter support verified')",
|
||||
]
|
||||
)
|
||||
)
|
||||
|
||||
# Install Poetry
|
||||
container = (
|
||||
container.with_exec(
|
||||
[
|
||||
"sh",
|
||||
"-c",
|
||||
"curl -sSL https://install.python-poetry.org | python3 - --yes",
|
||||
]
|
||||
)
|
||||
.with_env_variable(
|
||||
"PATH", "/root/.local/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
||||
)
|
||||
.with_exec(["poetry", "--version"])
|
||||
)
|
||||
|
||||
# Install fpm
|
||||
container = _install_fpm(container, distro.distro_type, config.fpm_version)
|
||||
|
||||
# Mount workspace and run build
|
||||
container = (
|
||||
container.with_directory("/workspace", source)
|
||||
.with_workdir("/workspace")
|
||||
.with_env_variable("CI_CD", "true")
|
||||
.with_env_variable("DISTRO_TYPE", distro.distro_type)
|
||||
@@ -63,7 +260,9 @@ async def build_linux_distro(
|
||||
str(pkg_dist_output)
|
||||
)
|
||||
|
||||
print(f"[dagger] {distro.name} build complete — artifacts exported to {dist_output}")
|
||||
print(
|
||||
f"[dagger] {distro.name} build complete — artifacts exported to {dist_output}"
|
||||
)
|
||||
|
||||
return {
|
||||
"dist": dist_output,
|
||||
@@ -71,7 +270,9 @@ async def build_linux_distro(
|
||||
}
|
||||
|
||||
|
||||
async def build_all_linux(config: PipelineConfig | None = None) -> dict[str, dict[str, Path]]:
|
||||
async def build_all_linux(
|
||||
config: PipelineConfig | None = None,
|
||||
) -> dict[str, dict[str, Path]]:
|
||||
"""Build all Linux distribution packages in parallel via Dagger.
|
||||
|
||||
Args:
|
||||
@@ -85,16 +286,18 @@ async def build_all_linux(config: PipelineConfig | None = None) -> dict[str, dic
|
||||
|
||||
results: dict[str, dict[str, Path]] = {}
|
||||
|
||||
token = _get_registry_token()
|
||||
|
||||
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
|
||||
tasks = {
|
||||
distro.distro_type: build_linux_distro(client, config, distro)
|
||||
distro.distro_type: build_linux_distro(
|
||||
client, config, distro, registry_token=token
|
||||
)
|
||||
for distro in config.distros
|
||||
}
|
||||
|
||||
# Run all distro builds concurrently
|
||||
completed = await asyncio.gather(
|
||||
*tasks.values(), return_exceptions=True
|
||||
)
|
||||
completed = await asyncio.gather(*tasks.values(), return_exceptions=True)
|
||||
|
||||
for distro_type, result in zip(tasks.keys(), completed):
|
||||
if isinstance(result, Exception):
|
||||
@@ -120,16 +323,16 @@ async def build_single_linux(
|
||||
if config is None:
|
||||
config = PipelineConfig()
|
||||
|
||||
distro = next(
|
||||
(d for d in config.distros if d.distro_type == distro_type), None
|
||||
)
|
||||
distro = next((d for d in config.distros if d.distro_type == distro_type), None)
|
||||
if distro is None:
|
||||
raise ValueError(
|
||||
f"Unknown distro type '{distro_type}'. Valid: debian, arch, rhel"
|
||||
)
|
||||
|
||||
token = _get_registry_token()
|
||||
|
||||
async with dagger.Connection(dagger.Config(log_output=sys.stderr)) as client:
|
||||
return await build_linux_distro(client, config, distro)
|
||||
return await build_linux_distro(client, config, distro, registry_token=token)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
+10
-7
@@ -22,7 +22,6 @@ import asyncio
|
||||
import argparse
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
from prefect import flow, task
|
||||
@@ -31,14 +30,13 @@ from ci.config import PipelineConfig
|
||||
from ci.dagger_pipeline import build_all_linux
|
||||
from ci.signing import sign_and_hash
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tasks
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
|
||||
@task(name="build-linux-distros", retries=1, retry_delay_seconds=30)
|
||||
def task_build_linux(config: PipelineConfig) -> dict:
|
||||
def task_build_linux(config: PipelineConfig) -> dict[str, dict[str, Path]]: # type: ignore[type-arg]
|
||||
"""Build all Linux distribution packages via Dagger containers."""
|
||||
print("=== Building Linux packages via Dagger ===")
|
||||
results = asyncio.run(build_all_linux(config))
|
||||
@@ -116,6 +114,7 @@ def task_prepare_release_files(dist_dir: Path, release_dir: Path) -> list[Path]:
|
||||
dst = release_dir / src.name
|
||||
if not dst.exists() or src.stat().st_mtime > dst.stat().st_mtime:
|
||||
import shutil
|
||||
|
||||
shutil.copy2(src, dst)
|
||||
copied.append(dst)
|
||||
print(f" {src.name}")
|
||||
@@ -146,8 +145,12 @@ def task_create_github_release(
|
||||
env = {**os.environ, "GH_TOKEN": github_token}
|
||||
|
||||
cmd = [
|
||||
"gh", "release", "create", tag,
|
||||
"--title", f"Release {tag}",
|
||||
"gh",
|
||||
"release",
|
||||
"create",
|
||||
tag,
|
||||
"--title",
|
||||
f"Release {tag}",
|
||||
"--latest",
|
||||
] + [str(f) for f in files]
|
||||
|
||||
@@ -190,10 +193,10 @@ def task_upload_s3(
|
||||
|
||||
|
||||
@flow(name="build-linux-flow", log_prints=True)
|
||||
def flow_build_linux() -> dict:
|
||||
def flow_build_linux() -> dict[str, dict[str, Path]]: # type: ignore[type-arg]
|
||||
"""Build all Linux distribution packages."""
|
||||
config = PipelineConfig()
|
||||
return task_build_linux(config)
|
||||
return task_build_linux(config) # type: ignore[return-value]
|
||||
|
||||
|
||||
@flow(name="sign-flow", log_prints=True)
|
||||
|
||||
+7
-3
@@ -31,9 +31,13 @@ def gpg_sign_file(file_path: Path, passphrase: str) -> Path:
|
||||
|
||||
subprocess.run(
|
||||
[
|
||||
"gpg", "--batch", "--yes",
|
||||
"--passphrase-file", passfile.name,
|
||||
"--detach-sign", "--armor",
|
||||
"gpg",
|
||||
"--batch",
|
||||
"--yes",
|
||||
"--passphrase-file",
|
||||
passfile.name,
|
||||
"--detach-sign",
|
||||
"--armor",
|
||||
str(file_path),
|
||||
],
|
||||
check=True,
|
||||
|
||||
Generated
+3697
-15
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,10 @@
|
||||
# For reproducibility, pin to a specific date tag like: archlinux:base-20251016
|
||||
FROM archlinux:latest
|
||||
|
||||
# Set build argument for fpm version (can be overridden at build time)
|
||||
# Set build arguments
|
||||
ARG FPM_VERSION=1.16.0
|
||||
ARG PYTHON_VERSION=3.14.3
|
||||
ARG PYTHON_SHA256=d7fe130d0501ae047ca318fa92aa642603ab6f217901015a1df6ce650d5470cd
|
||||
|
||||
# Install system dependencies (Arch) including Python build dependencies
|
||||
RUN pacman -Syu --noconfirm \
|
||||
@@ -56,24 +58,26 @@ RUN gem install --no-document -v "${FPM_VERSION}" fpm && \
|
||||
|
||||
|
||||
|
||||
# Install pyenv
|
||||
ENV PYENV_ROOT="/root/.pyenv"
|
||||
ENV PATH="$PYENV_ROOT/bin:$PATH"
|
||||
# Download Python source and verify SHA256 checksum against python.org
|
||||
RUN wget -q "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" \
|
||||
-O /tmp/Python-${PYTHON_VERSION}.tgz && \
|
||||
echo "${PYTHON_SHA256} /tmp/Python-${PYTHON_VERSION}.tgz" | sha256sum -c -
|
||||
|
||||
RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv
|
||||
|
||||
# Install Python 3.12 via pyenv with tkinter support
|
||||
# The tk and tcl packages must be installed before this step for _tkinter to be compiled
|
||||
RUN eval "$(pyenv init -)" && \
|
||||
# Build and install Python from source
|
||||
RUN cd /tmp && tar xzf Python-${PYTHON_VERSION}.tgz && \
|
||||
cd Python-${PYTHON_VERSION} && \
|
||||
LDFLAGS="-L/usr/lib" \
|
||||
CPPFLAGS="-I/usr/include" \
|
||||
PYTHON_CONFIGURE_OPTS="--enable-shared" \
|
||||
pyenv install 3.13 && \
|
||||
pyenv global 3.13 && \
|
||||
pyenv rehash
|
||||
./configure --enable-shared --with-ensurepip=install --prefix=/usr/local && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
echo "/usr/local/lib" > /etc/ld.so.conf.d/python.conf && \
|
||||
ldconfig && \
|
||||
ln -sf /usr/local/bin/python3 /usr/local/bin/python && \
|
||||
rm -rf /tmp/Python-${PYTHON_VERSION} /tmp/Python-${PYTHON_VERSION}.tgz
|
||||
|
||||
# Update PATH to include pyenv shims
|
||||
ENV PATH="/root/.pyenv/shims:$PATH"
|
||||
# Update PATH to include Python installation
|
||||
ENV PATH="/usr/local/bin:$PATH"
|
||||
|
||||
# Verify Python has tkinter support
|
||||
RUN python3 -c "import tkinter; import _tkinter; print('tkinter support verified')" || \
|
||||
|
||||
@@ -8,8 +8,10 @@
|
||||
# Use Debian 13 "Trixie" (latest stable release)
|
||||
FROM debian:13
|
||||
|
||||
# Set build argument for fpm version (can be overridden at build time)
|
||||
# Set build arguments
|
||||
ARG FPM_VERSION=1.16.0
|
||||
ARG PYTHON_VERSION=3.14.3
|
||||
ARG PYTHON_SHA256=d7fe130d0501ae047ca318fa92aa642603ab6f217901015a1df6ce650d5470cd
|
||||
|
||||
# Install system dependencies including Python build dependencies
|
||||
RUN apt-get update && \
|
||||
@@ -41,24 +43,26 @@ RUN apt-get update && \
|
||||
apt-get clean && \
|
||||
rm -rf /var/lib/apt/lists/*
|
||||
|
||||
# Install pyenv
|
||||
ENV PYENV_ROOT="/root/.pyenv"
|
||||
ENV PATH="$PYENV_ROOT/bin:$PATH"
|
||||
# Download Python source and verify SHA256 checksum against python.org
|
||||
RUN wget -q "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" \
|
||||
-O /tmp/Python-${PYTHON_VERSION}.tgz && \
|
||||
echo "${PYTHON_SHA256} /tmp/Python-${PYTHON_VERSION}.tgz" | sha256sum -c -
|
||||
|
||||
RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv
|
||||
|
||||
# Install Python 3.12 via pyenv with tkinter support
|
||||
# The tk8.6-dev package must be installed before this step for _tkinter to be compiled
|
||||
RUN eval "$(pyenv init -)" && \
|
||||
# Build and install Python from source
|
||||
RUN cd /tmp && tar xzf Python-${PYTHON_VERSION}.tgz && \
|
||||
cd Python-${PYTHON_VERSION} && \
|
||||
LDFLAGS="-L/usr/lib/x86_64-linux-gnu" \
|
||||
CPPFLAGS="-I/usr/include/tcl8.6" \
|
||||
PYTHON_CONFIGURE_OPTS="--enable-shared" \
|
||||
pyenv install 3.13 && \
|
||||
pyenv global 3.13 && \
|
||||
pyenv rehash
|
||||
./configure --enable-shared --with-ensurepip=install --prefix=/usr/local && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
echo "/usr/local/lib" > /etc/ld.so.conf.d/python.conf && \
|
||||
ldconfig && \
|
||||
ln -sf /usr/local/bin/python3 /usr/local/bin/python && \
|
||||
rm -rf /tmp/Python-${PYTHON_VERSION} /tmp/Python-${PYTHON_VERSION}.tgz
|
||||
|
||||
# Update PATH to include pyenv shims
|
||||
ENV PATH="/root/.pyenv/shims:$PATH"
|
||||
# Update PATH to include Python installation
|
||||
ENV PATH="/usr/local/bin:$PATH"
|
||||
|
||||
# Verify Python has tkinter support
|
||||
RUN python3 -c "import tkinter; import _tkinter; print('tkinter support verified')" || \
|
||||
|
||||
@@ -7,8 +7,10 @@
|
||||
|
||||
FROM fedora:42
|
||||
|
||||
# Set build argument for fpm version (can be overridden at build time)
|
||||
# Set build arguments
|
||||
ARG FPM_VERSION=1.16.0
|
||||
ARG PYTHON_VERSION=3.14.3
|
||||
ARG PYTHON_SHA256=d7fe130d0501ae047ca318fa92aa642603ab6f217901015a1df6ce650d5470cd
|
||||
|
||||
# Install system dependencies including tk8-devel for Python tkinter support
|
||||
# Using tk8 (version 8.6) instead of tk (version 9.0) for Python 3.12 compatibility
|
||||
@@ -42,24 +44,26 @@ RUN dnf -y update && \
|
||||
libXrender-devel && \
|
||||
dnf clean all
|
||||
|
||||
# Install pyenv
|
||||
ENV PYENV_ROOT="/root/.pyenv"
|
||||
ENV PATH="$PYENV_ROOT/bin:$PATH"
|
||||
# Download Python source and verify SHA256 checksum against python.org
|
||||
RUN wget -q "https://www.python.org/ftp/python/${PYTHON_VERSION}/Python-${PYTHON_VERSION}.tgz" \
|
||||
-O /tmp/Python-${PYTHON_VERSION}.tgz && \
|
||||
echo "${PYTHON_SHA256} /tmp/Python-${PYTHON_VERSION}.tgz" | sha256sum -c -
|
||||
|
||||
RUN git clone https://github.com/pyenv/pyenv.git /root/.pyenv
|
||||
|
||||
# Install Python 3.13 via pyenv with tkinter support
|
||||
# The tk8-devel package must be installed before this step for _tkinter to be compiled
|
||||
RUN eval "$(pyenv init -)" && \
|
||||
# Build and install Python from source
|
||||
RUN cd /tmp && tar xzf Python-${PYTHON_VERSION}.tgz && \
|
||||
cd Python-${PYTHON_VERSION} && \
|
||||
LDFLAGS="-L/usr/lib64" \
|
||||
CPPFLAGS="-I/usr/include" \
|
||||
PYTHON_CONFIGURE_OPTS="--enable-shared" \
|
||||
pyenv install 3.13 && \
|
||||
pyenv global 3.13 && \
|
||||
pyenv rehash
|
||||
./configure --enable-shared --with-ensurepip=install --prefix=/usr/local && \
|
||||
make -j$(nproc) && \
|
||||
make install && \
|
||||
echo "/usr/local/lib" > /etc/ld.so.conf.d/python.conf && \
|
||||
ldconfig && \
|
||||
ln -sf /usr/local/bin/python3 /usr/local/bin/python && \
|
||||
rm -rf /tmp/Python-${PYTHON_VERSION} /tmp/Python-${PYTHON_VERSION}.tgz
|
||||
|
||||
# Update PATH to include pyenv shims
|
||||
ENV PATH="/root/.pyenv/shims:$PATH"
|
||||
# Update PATH to include Python installation
|
||||
ENV PATH="/usr/local/bin:$PATH"
|
||||
|
||||
# Verify Python has tkinter support
|
||||
RUN python3 -c "import tkinter; import _tkinter; print('tkinter support verified')" || \
|
||||
|
||||
Reference in New Issue
Block a user