From ee98fac749f49c53da6a35144b699dcb2b6b92de Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E5=90=B4=E5=B0=8F=E7=99=BD?= <296015668@qq.com>
Date: Wed, 11 Jun 2025 11:20:27 +0800
Subject: [PATCH] Add loongarch64-linux support
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit

Signed-off-by: 吴小白 <296015668@qq.com>
---
 ci-matrix.py                                  |  1 +
 ci-targets.yaml                               | 21 ++++++
 cpython-unix/build-cpython.sh                 | 15 ++++-
 cpython-unix/build-libX11.sh                  |  6 ++
 cpython-unix/build-libxcb.sh                  |  6 ++
 cpython-unix/build-main.py                    |  4 +-
 cpython-unix/build-ncurses.sh                 |  2 +
 cpython-unix/build-xorgproto.sh               |  6 ++
 cpython-unix/build.Dockerfile                 |  2 +
 .../build.cross-loongarch64.Dockerfile        | 64 +++++++++++++++++++
 cpython-unix/build.py                         |  2 +-
 cpython-unix/extension-modules.yml            |  1 +
 ...atch-configure-add-loongarch-triplet.patch | 50 +++++++++++++++
 cpython-unix/targets.yml                      | 41 ++++++++++++
 docs/building.rst                             |  1 +
 src/release.rs                                | 13 ++++
 src/validation.rs                             |  8 +++
 17 files changed, 238 insertions(+), 5 deletions(-)
 mode change 100755 => 100644 cpython-unix/build-main.py
 create mode 100644 cpython-unix/build.cross-loongarch64.Dockerfile
 create mode 100644 cpython-unix/patch-configure-add-loongarch-triplet.patch

diff --git a/ci-matrix.py b/ci-matrix.py
index 91cfa953..4be620df 100644
--- a/ci-matrix.py
+++ b/ci-matrix.py
@@ -25,6 +25,7 @@
     {"name": "build", "arch": "x86_64"},
     {"name": "build.cross", "arch": "x86_64"},
     {"name": "build.cross-riscv64", "arch": "x86_64"},
+    {"name": "build.cross-loongarch64", "arch": "x86_64"},
     {"name": "build.debian9", "arch": "aarch64"},
     {"name": "gcc", "arch": "x86_64"},
     {"name": "gcc.debian9", "arch": "aarch64"},
diff --git a/ci-targets.yaml b/ci-targets.yaml
index c59d5b7f..453202d2 100644
--- a/ci-targets.yaml
+++ b/ci-targets.yaml
@@ -99,6 +99,27 @@ linux:
           - freethreaded+lto
         minimum-python-version: "3.13"
 
+  loongarch64-unknown-linux-gnu:
+    arch: loongarch64
+    libc: gnu
+    python_versions:
+      - "3.9"
+      - "3.10"
+      - "3.11"
+      - "3.12"
+      - "3.13"
+      - "3.14"
+    build_options:
+      - debug
+      - noopt
+      - lto
+    build_options_conditional:
+      - options:
+          - freethreaded+debug
+          - freethreaded+noopt
+          - freethreaded+lto
+        minimum-python-version: "3.13"
+
   s390x-unknown-linux-gnu:
     arch: s390x
     libc: gnu
diff --git a/cpython-unix/build-cpython.sh b/cpython-unix/build-cpython.sh
index cc93f0ad..cf63ffc6 100755
--- a/cpython-unix/build-cpython.sh
+++ b/cpython-unix/build-cpython.sh
@@ -69,6 +69,15 @@ if [[ "${PYBUILD_PLATFORM}" = macos* ]]; then
     fi
 fi
 
+# configure doesn't support cross-compiling on LoongArch. Teach it.
+if [ "${PYBUILD_PLATFORM}" != "macos" ]; then
+    case "${PYTHON_MAJMIN_VERSION}" in
+        3.9|3.10|3.11)
+            patch -p1 -i ${ROOT}/patch-configure-add-loongarch-triplet.patch
+            ;;
+    esac
+fi
+
 # disable readelf check when cross-compiling on older Python versions
 if [ -n "${CROSS_COMPILING}" ]; then
     if [ -n "${PYTHON_MEETS_MAXIMUM_VERSION_3_11}" ]; then
@@ -1094,6 +1103,9 @@ armv7-unknown-linux-gnueabi)
 armv7-unknown-linux-gnueabihf)
     PYTHON_ARCH="arm-linux-gnueabihf"
     ;;
+loongarch64-unknown-linux-gnu)
+    PYTHON_ARCH="loongarch64-linux-gnu"
+    ;;
 mips-unknown-linux-gnu)
     PYTHON_ARCH="mips-linux-gnu"
     ;;
@@ -1251,7 +1263,8 @@ if [ -d "${TOOLS_PATH}/deps/lib/tcl8" ]; then
         cp -av $source ${ROOT}/out/python/install/lib/
     done
 
-    if [[ "${PYBUILD_PLATFORM}" != macos* ]]; then
+    # Tix doesn't support macOS and loongarch64, so we don't copy it there.
+    if [[ "${PYBUILD_PLATFORM}" != macos* ]] && [[ "${TARGET_TRIPLE}" != loongarch64* ]]; then
         cp -av ${TOOLS_PATH}/deps/lib/Tix8.4.3 ${ROOT}/out/python/install/lib/
     fi
 fi
diff --git a/cpython-unix/build-libX11.sh b/cpython-unix/build-libX11.sh
index bb45028b..0b1d96b1 100755
--- a/cpython-unix/build-libX11.sh
+++ b/cpython-unix/build-libX11.sh
@@ -54,6 +54,9 @@ if [ -n "${CROSS_COMPILING}" ]; then
     armv7-unknown-linux-gnueabihf)
       EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
       ;;
+    loongarch64-unknown-linux-gnu)
+      EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
+      ;;
     mips-unknown-linux-gnu)
       EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
       ;;
@@ -78,6 +81,9 @@ if [ -n "${CROSS_COMPILING}" ]; then
     aarch64-unknown-linux-musl)
       EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
       ;;
+    loongarch64-unknown-linux-musl)
+      EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
+      ;;
     mips-unknown-linux-musl)
       EXTRA_FLAGS="${EXTRA_FLAGS} --enable-malloc0returnsnull"
       ;;
diff --git a/cpython-unix/build-libxcb.sh b/cpython-unix/build-libxcb.sh
index 0b6cb911..07117a20 100755
--- a/cpython-unix/build-libxcb.sh
+++ b/cpython-unix/build-libxcb.sh
@@ -13,6 +13,12 @@ export PKG_CONFIG_PATH=/tools/deps/share/pkgconfig:/tools/deps/lib/pkgconfig
 tar -xf libxcb-${LIBXCB_VERSION}.tar.gz
 pushd libxcb-${LIBXCB_VERSION}
 
+if [[ "${TARGET_TRIPLE}" = loongarch64* ]]; then
+    rm -f build-aux/config.guess build-aux/config.sub
+    curl -sSL -o build-aux/config.guess https://github.com/cgitmirror/config/raw/refs/heads/master/config.guess
+    curl -sSL -o build-aux/config.sub https://github.com/cgitmirror/config/raw/refs/heads/master/config.sub
+fi
+
 if [ "${CC}" = "musl-clang" ]; then
     EXTRA_FLAGS="--disable-shared"
 fi
diff --git a/cpython-unix/build-main.py b/cpython-unix/build-main.py
old mode 100755
new mode 100644
index 7a7d08b1..3cb3b0a6
--- a/cpython-unix/build-main.py
+++ b/cpython-unix/build-main.py
@@ -95,11 +95,9 @@ def main():
             "toolchain-image-build",
             "toolchain-image-build.cross",
             "toolchain-image-build.cross-riscv64",
+            "toolchain-image-build.cross-loongarch64",
             "toolchain-image-build.debian9",
             "toolchain-image-gcc",
-            "toolchain-image-xcb",
-            "toolchain-image-xcb.cross",
-            "toolchain-image-xcb.cross-riscv64",
         },
         default="default",
         help="The make target to evaluate",
diff --git a/cpython-unix/build-ncurses.sh b/cpython-unix/build-ncurses.sh
index 25df7e22..cedeb702 100755
--- a/cpython-unix/build-ncurses.sh
+++ b/cpython-unix/build-ncurses.sh
@@ -109,6 +109,8 @@ else
   "
 fi
 
+mkdir -p ${ROOT}/out/usr/lib
+
 CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure ${CONFIGURE_FLAGS}
 make -j ${NUM_CPUS}
 make -j ${NUM_CPUS} install DESTDIR=${ROOT}/out
diff --git a/cpython-unix/build-xorgproto.sh b/cpython-unix/build-xorgproto.sh
index 97c1aa4f..7d5d28f4 100755
--- a/cpython-unix/build-xorgproto.sh
+++ b/cpython-unix/build-xorgproto.sh
@@ -15,6 +15,12 @@ export PKG_CONFIG_PATH=/tools/deps/share/pkgconfig
 tar -xf xorgproto-${XORGPROTO_VERSION}.tar.gz
 pushd xorgproto-${XORGPROTO_VERSION}
 
+if [[ "${TARGET_TRIPLE}" = loongarch64* ]]; then
+    rm -f config.guess.sub config.sub
+    curl -sSL -o config.guess https://github.com/cgitmirror/config/raw/refs/heads/master/config.guess
+    curl -sSL -o config.sub https://github.com/cgitmirror/config/raw/refs/heads/master/config.sub
+fi
+
 CFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" CPPFLAGS="${EXTRA_TARGET_CFLAGS} -fPIC" LDFLAGS="${EXTRA_TARGET_LDFLAGS}" ./configure \
     --build=${BUILD_TRIPLE} \
     --host=${TARGET_TRIPLE} \
diff --git a/cpython-unix/build.Dockerfile b/cpython-unix/build.Dockerfile
index e44d0a2e..5a5dcad8 100644
--- a/cpython-unix/build.Dockerfile
+++ b/cpython-unix/build.Dockerfile
@@ -9,6 +9,8 @@
 # Various other build tools are needed for various building.
 RUN ulimit -n 10000 && apt-get install \
     bzip2 \
+    ca-certificates \
+    curl \
     file \
     libc6-dev \
     libffi-dev \
diff --git a/cpython-unix/build.cross-loongarch64.Dockerfile b/cpython-unix/build.cross-loongarch64.Dockerfile
new file mode 100644
index 00000000..85b282c6
--- /dev/null
+++ b/cpython-unix/build.cross-loongarch64.Dockerfile
@@ -0,0 +1,64 @@
+# Debian Trixie.
+FROM debian@sha256:653dfb9f86c3782e8369d5f7d29bb8faba1f4bff9025db46e807fa4c22903671
+MAINTAINER Gregory Szorc <gregory.szorc@gmail.com>
+
+RUN groupadd -g 1000 build && \
+    useradd -u 1000 -g 1000 -d /build -s /bin/bash -m build && \
+    mkdir /tools && \
+    chown -R build:build /build /tools
+
+ENV HOME=/build \
+    SHELL=/bin/bash \
+    USER=build \
+    LOGNAME=build \
+    HOSTNAME=builder \
+    DEBIAN_FRONTEND=noninteractive
+
+CMD ["/bin/bash", "--login"]
+WORKDIR '/build'
+
+RUN for s in debian_trixie debian_trixie-updates; do \
+      echo "deb http://snapshot.debian.org/archive/${s%_*}/20250515T202920Z/ ${s#*_} main"; \
+    done > /etc/apt/sources.list && \
+    for s in debian-security_trixie-security/updates; do \
+      echo "deb http://snapshot.debian.org/archive/${s%_*}/20250515T175729Z/ ${s#*_} main"; \
+    done >> /etc/apt/sources.list && \
+    ( echo 'quiet "true";'; \
+      echo 'APT::Get::Assume-Yes "true";'; \
+      echo 'APT::Install-Recommends "false";'; \
+      echo 'Acquire::Check-Valid-Until "false";'; \
+      echo 'Acquire::Retries "5";'; \
+    ) > /etc/apt/apt.conf.d/99cpython-portable && \
+    rm -f /etc/apt/sources.list.d/*
+
+RUN apt-get update
+
+# Host building.
+RUN apt-get install \
+    bzip2 \
+    ca-certificates \
+    curl \
+    gcc \
+    g++ \
+    libc6-dev \
+    libffi-dev \
+    make \
+    patch \
+    perl \
+    pkg-config \
+    tar \
+    xz-utils \
+    unzip \
+    zip \
+    zlib1g-dev
+
+RUN apt-get install \
+    gcc-loongarch64-linux-gnu \
+    libc6-dev-loong64-cross
+
+RUN cd /tmp && \
+    curl -LO https://snapshot.debian.org/archive/debian-ports/20250515T194251Z/pool-loong64/main/libx/libxcrypt/libcrypt-dev_4.4.38-1_loong64.deb && \
+    curl -LO https://snapshot.debian.org/archive/debian-ports/20250515T194251Z/pool-loong64/main/libx/libxcrypt/libcrypt1_4.4.38-1_loong64.deb && \
+    dpkg -x libcrypt-dev_4.4.38-1_loong64.deb / && \
+    dpkg -x libcrypt1_4.4.38-1_loong64.deb / && \
+    rm -f /tmp/*.deb
diff --git a/cpython-unix/build.py b/cpython-unix/build.py
index b35d1721..94235a3c 100755
--- a/cpython-unix/build.py
+++ b/cpython-unix/build.py
@@ -946,7 +946,7 @@ def build_cpython(
             "tk8.6",
         ]
 
-        if "-apple" not in target_triple:
+        if not all(s in target_triple for s in ("-apple", "loongarch64-")):
             python_info["tcl_library_paths"].append("Tix8.4.3")
 
         if "-apple" in target_triple:
diff --git a/cpython-unix/extension-modules.yml b/cpython-unix/extension-modules.yml
index 4b834563..4fe136d3 100644
--- a/cpython-unix/extension-modules.yml
+++ b/cpython-unix/extension-modules.yml
@@ -257,6 +257,7 @@ _decimal:
     - define: CONFIG_64=1
       targets:
         - aarch64-.*
+        - loongarch64-unknown-linux.*
         - ppc64le-unknown-linux.*
         - riscv64-unknown-linux.*
         - s390x-unknown-linux-.*
diff --git a/cpython-unix/patch-configure-add-loongarch-triplet.patch b/cpython-unix/patch-configure-add-loongarch-triplet.patch
new file mode 100644
index 00000000..30b9e5a0
--- /dev/null
+++ b/cpython-unix/patch-configure-add-loongarch-triplet.patch
@@ -0,0 +1,50 @@
+diff --git a/configure b/configure
+index b7be60e..d799415 100755
+--- a/configure
++++ b/configure
+@@ -5261,6 +5261,20 @@ cat >> conftest.c <<EOF
+         hppa-linux-gnu
+ # elif defined(__ia64__)
+         ia64-linux-gnu
++# elif defined(__loongarch__)
++#  if defined(__loongarch_lp64)
++#   if defined(__loongarch_soft_float)
++        loongarch64-linux-gnusf
++#   elif defined(__loongarch_single_float)
++        loongarch64-linux-gnuf32
++#   elif defined(__loongarch_double_float)
++        loongarch64-linux-gnu
++#   else
++#    error unknown platform triplet
++#   endif
++#  else
++#   error unknown platform triplet
++#  endif
+ # elif defined(__m68k__) && !defined(__mcoldfire__)
+         m68k-linux-gnu
+ # elif defined(__mips_hard_float) && defined(__mips_isa_rev) && (__mips_isa_rev >=6) && defined(_MIPSEL)
+diff --git a/configure.ac b/configure.ac
+index aa515da..b5bad6e 100644
+--- a/configure.ac
++++ b/configure.ac
+@@ -779,6 +779,20 @@ cat >> conftest.c <<EOF
+         hppa-linux-gnu
+ # elif defined(__ia64__)
+         ia64-linux-gnu
++# elif defined(__loongarch__)
++#  if defined(__loongarch_lp64)
++#   if defined(__loongarch_soft_float)
++        loongarch64-linux-gnusf
++#   elif defined(__loongarch_single_float)
++        loongarch64-linux-gnuf32
++#   elif defined(__loongarch_double_float)
++        loongarch64-linux-gnu
++#   else
++#    error unknown platform triplet
++#   endif
++#  else
++#   error unknown platform triplet
++#  endif
+ # elif defined(__m68k__) && !defined(__mcoldfire__)
+         m68k-linux-gnu
+ # elif defined(__mips_hard_float) && defined(__mips_isa_rev) && (__mips_isa_rev >=6) && defined(_MIPSEL)
diff --git a/cpython-unix/targets.yml b/cpython-unix/targets.yml
index 33db80c8..df18b48b 100644
--- a/cpython-unix/targets.yml
+++ b/cpython-unix/targets.yml
@@ -335,6 +335,47 @@ armv7-unknown-linux-gnueabihf:
     - zstd
   openssl_target: linux-armv4
 
+loongarch64-unknown-linux-gnu:
+  host_platforms:
+    - linux_x86_64
+  pythons_supported:
+    - '3.9'
+    - '3.10'
+    - '3.11'
+    - '3.12'
+    - '3.13'
+    - '3.14'
+  docker_image_suffix: .cross-loongarch64
+  host_cc: /usr/bin/x86_64-linux-gnu-gcc
+  host_cxx: /usr/bin/x86_64-linux-gnu-g++
+  target_cc: /usr/bin/loongarch64-linux-gnu-gcc
+  target_cxx: /usr/bin/loongarch64-linux-gnu-g++
+  needs:
+    - autoconf
+    - bdb
+    - binutils
+    - bzip2
+    - expat
+    - libedit
+    - libffi
+    - libX11
+    - libXau
+    - libxcb
+    - m4
+    - mpdecimal
+    - ncurses
+    - openssl-3.0
+    - patchelf
+    - sqlite
+    - tcl
+    - tk
+    - uuid
+    - xorgproto
+    - xz
+    - zlib
+    - zstd
+  openssl_target: linux64-loongarch64
+
 mips-unknown-linux-gnu:
   host_platforms:
     - linux_x86_64
diff --git a/docs/building.rst b/docs/building.rst
index c6a07e25..eaad66c4 100644
--- a/docs/building.rst
+++ b/docs/building.rst
@@ -39,6 +39,7 @@ As are various other targets::
     $ ./build-linux.py --target aarch64-unknown-linux-gnu
     $ ./build-linux.py --target armv7-unknown-linux-gnueabi
     $ ./build-linux.py --target armv7-unknown-linux-gnueabihf
+    $ ./build-linux.py --target loongarch64-unknown-linux-gnu
     $ ./build-linux.py --target mips-unknown-linux-gnu
     $ ./build-linux.py --target mipsel-unknown-linux-gnu
     $ ./build-linux.py --target ppc64le-unknown-linux-gnu
diff --git a/src/release.rs b/src/release.rs
index 0b67888d..4c8cb275 100644
--- a/src/release.rs
+++ b/src/release.rs
@@ -178,6 +178,19 @@ pub static RELEASE_TRIPLES: Lazy<BTreeMap<&'static str, TripleRelease>> = Lazy::
         },
     );
 
+    h.insert(
+        "loongarch64-unknown-linux-gnu",
+        TripleRelease {
+            suffixes: linux_suffixes_nopgo.clone(),
+            install_only_suffix: "lto",
+            python_version_requirement: Some(VersionSpecifier::from_str(">=3.9").unwrap()),
+            conditional_suffixes: vec![ConditionalSuffixes {
+                python_version_requirement: VersionSpecifier::from_str(">=3.13").unwrap(),
+                suffixes: linux_suffixes_nopgo_freethreaded.clone(),
+            }],
+        },
+    );
+
     h.insert(
         "ppc64le-unknown-linux-gnu",
         TripleRelease {
diff --git a/src/validation.rs b/src/validation.rs
index 5ab71708..b9ef1f48 100644
--- a/src/validation.rs
+++ b/src/validation.rs
@@ -42,6 +42,7 @@ const RECOGNIZED_TRIPLES: &[&str] = &[
     "i686-unknown-linux-gnu",
     // Note there's build support for mips* targets but they are not tested
     // See https://github.com/astral-sh/python-build-standalone/issues/412
+    "loongarch64-unknown-linux-gnu",
     "mips-unknown-linux-gnu",
     "mipsel-unknown-linux-gnu",
     "mips64el-unknown-linux-gnuabi64",
@@ -169,6 +170,10 @@ static GLIBC_MAX_VERSION_BY_TRIPLE: Lazy<HashMap<&'static str, version_compare::
             "i686-unknown-linux-gnu",
             version_compare::Version::from("2.17").unwrap(),
         );
+        versions.insert(
+            "loongarch64-unknown-linux-gnu",
+            version_compare::Version::from("2.38").unwrap(),
+        );
         versions.insert(
             "mips-unknown-linux-gnu",
             version_compare::Version::from("2.19").unwrap(),
@@ -243,6 +248,7 @@ static ELF_ALLOWED_LIBRARIES_BY_TRIPLE: Lazy<HashMap<&'static str, Vec<&'static
                 vec!["ld-linux-armhf.so.3", "libgcc_s.so.1"],
             ),
             ("i686-unknown-linux-gnu", vec!["ld-linux-x86-64.so.2"]),
+            ("loongarch64-unknown-linux-gnu", vec!["ld-linux-loongarch-lp64d.so.1"]),
             ("mips-unknown-linux-gnu", vec!["ld.so.1", "libatomic.so.1"]),
             (
                 "mipsel-unknown-linux-gnu",
@@ -511,6 +517,7 @@ static PLATFORM_TAG_BY_TRIPLE: Lazy<HashMap<&'static str, &'static str>> = Lazy:
         ("armv7-unknown-linux-gnueabihf", "linux-arm"),
         ("i686-pc-windows-msvc", "win32"),
         ("i686-unknown-linux-gnu", "linux-i686"),
+        ("loongarch64-unknown-linux-gnu", "linux-loongarch64"),
         ("mips-unknown-linux-gnu", "linux-mips"),
         ("mipsel-unknown-linux-gnu", "linux-mipsel"),
         ("mips64el-unknown-linux-gnuabi64", "todo"),
@@ -898,6 +905,7 @@ fn validate_elf<Elf: FileHeader<Endian = Endianness>>(
         "armv7-unknown-linux-gnueabi" => object::elf::EM_ARM,
         "armv7-unknown-linux-gnueabihf" => object::elf::EM_ARM,
         "i686-unknown-linux-gnu" => object::elf::EM_386,
+        "loongarch64-unknown-linux-gnu" => object::elf::EM_LOONGARCH,
         "mips-unknown-linux-gnu" => object::elf::EM_MIPS,
         "mipsel-unknown-linux-gnu" => object::elf::EM_MIPS,
         "mips64el-unknown-linux-gnuabi64" => 0,