Skip to content

Commit de648cd

Browse files
committed
This closes #32, support x86_64 Python build on ARM64 Windows
- Bump v0.0.6 - Build Linux ARM64 version shared library with glibc 2.17 for more compatibility - Update build flag to trim path for shared library - Update GitHub Action, test with Python 3.15.0 - Upgrade the dependencies package version
1 parent 712e1bb commit de648cd

File tree

9 files changed

+131
-64
lines changed

9 files changed

+131
-64
lines changed

.github/workflows/build.yml

Lines changed: 63 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ jobs:
1010
matrix:
1111
go-version: [1.25.x]
1212
os: [ubuntu-24.04, macos-latest, windows-latest]
13-
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]
13+
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14", "3.15.0-alpha.1"]
1414
targetplatform: [x64]
1515

1616
runs-on: ${{ matrix.os }}
@@ -70,7 +70,7 @@ jobs:
7070
if: github.event_name == 'release' && github.event.action == 'published'
7171
strategy:
7272
matrix:
73-
os: [ubuntu-24.04, macos-latest]
73+
os: [ubuntu-24.04, ubuntu-24.04-arm, macos-latest]
7474

7575
steps:
7676
- name: Install Go
@@ -92,32 +92,64 @@ jobs:
9292
env GO111MODULE=on go vet ./...
9393
pip install coverage
9494
95-
- name: Build Shared Library
95+
- name: Build Linux Shared Library
96+
if: matrix.os == 'ubuntu-24.04'
9697
env:
9798
CGO_ENABLED: 1
9899
run: |
99-
if [[ "$RUNNER_OS" == "Linux" ]]; then
100-
sudo dpkg --add-architecture i386
101-
sudo apt update
102-
sudo apt install -y gcc-multilib g++-multilib libc6-dev-i386
103-
CC="gcc -m32" GOOS=linux GOARCH=386 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.386.linux.so main.go
104-
GOOS=linux GOARCH=amd64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.amd64.linux.so main.go
100+
docker run --rm -v $PWD:/src -w /src quay.io/pypa/manylinux2014_x86_64 bash -c "
101+
yum install -y wget || true
102+
wget https://go.dev/dl/go1.25.3.linux-amd64.tar.gz && \
103+
tar -C /usr/local -xzf go1.25.3.linux-amd64.tar.gz && \
104+
export PATH=/usr/local/go/bin:\$PATH && \
105+
CGO_ENABLED=1 CC=gcc GOOS=linux GOARCH=amd64 \
106+
go build -buildmode=c-shared -ldflags='-s -w' -trimpath \
107+
-o libexcelize.amd64.linux.so main.go && \
108+
rm -f libexcelize.*.h
109+
"
110+
111+
docker run --rm -v $PWD:/src -w /src quay.io/pypa/manylinux2014_i686 bash -c "
112+
yum install -y wget || true
113+
wget https://go.dev/dl/go1.25.3.linux-386.tar.gz && \
114+
tar -C /usr/local -xzf go1.25.3.linux-386.tar.gz && \
115+
export PATH=/usr/local/go/bin:\$PATH && \
116+
CGO_ENABLED=1 CC=gcc GOOS=linux GOARCH=386 \
117+
go build -buildmode=c-shared -ldflags='-s -w' -trimpath \
118+
-o libexcelize.386.linux.so main.go && \
105119
rm -f libexcelize.*.h
106-
elif [[ "$RUNNER_OS" == "macOS" ]]; then
107-
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
108-
brew tap messense/macos-cross-toolchains
109-
brew install FiloSottile/musl-cross/musl-cross mingw-w64
110-
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-ucrt-macos-universal.tar.xz
111-
tar -xzf llvm-mingw-20250709-ucrt-macos-universal.tar.xz
112-
export PATH="$(pwd)/llvm-mingw-20250709-ucrt-macos-universal/bin:$PATH"
113-
CC=aarch64-linux-musl-gcc GOOS=linux GOARCH=arm64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.arm64.linux.so main.go
114-
CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.amd64.windows.dll main.go
115-
CC=i686-w64-mingw32-gcc GOOS=windows GOARCH=386 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.386.windows.dll main.go
116-
CC=aarch64-w64-mingw32-gcc GOOS=windows GOARCH=arm64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.arm64.windows.dll main.go
117-
CC=gcc GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.arm64.darwin.dylib main.go
118-
CC=gcc GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -buildmode=c-shared -o libexcelize.amd64.darwin.dylib main.go
120+
"
121+
122+
- name: Build Linux ARM64 Shared Library
123+
if: matrix.os == 'ubuntu-24.04-arm'
124+
env:
125+
CGO_ENABLED: 1
126+
run: |
127+
docker run --rm -v $PWD:/src -w /src quay.io/pypa/manylinux2014_aarch64 bash -c "
128+
yum install -y wget || true
129+
wget https://go.dev/dl/go1.25.3.linux-arm64.tar.gz && \
130+
tar -C /usr/local -xzf go1.25.3.linux-arm64.tar.gz && \
131+
export PATH=/usr/local/go/bin:\$PATH && \
132+
CGO_ENABLED=1 CC=gcc GOOS=linux GOARCH=arm64 \
133+
go build -buildmode=c-shared -ldflags='-s -w' -trimpath \
134+
-o libexcelize.arm64.linux.so main.go && \
119135
rm -f libexcelize.*.h
120-
fi
136+
"
137+
138+
- name: Build Shared Library
139+
if: matrix.os == 'macos-latest'
140+
env:
141+
CGO_ENABLED: 1
142+
run: |
143+
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"
144+
wget https://github.com/mstorsjo/llvm-mingw/releases/download/20250709/llvm-mingw-20250709-ucrt-macos-universal.tar.xz
145+
tar -xzf llvm-mingw-20250709-ucrt-macos-universal.tar.xz
146+
export PATH="$(pwd)/llvm-mingw-20250709-ucrt-macos-universal/bin:$PATH"
147+
CC=x86_64-w64-mingw32-gcc GOOS=windows GOARCH=amd64 go build -ldflags "-s -w" -buildmode=c-shared -trimpath -o libexcelize.amd64.windows.dll main.go
148+
CC=i686-w64-mingw32-gcc GOOS=windows GOARCH=386 go build -ldflags "-s -w" -buildmode=c-shared -trimpath -o libexcelize.386.windows.dll main.go
149+
CC=aarch64-w64-mingw32-gcc GOOS=windows GOARCH=arm64 go build -ldflags "-s -w" -buildmode=c-shared -trimpath -o libexcelize.arm64.windows.dll main.go
150+
CC=gcc GOOS=darwin GOARCH=arm64 go build -ldflags "-s -w" -buildmode=c-shared -trimpath -o libexcelize.arm64.darwin.dylib main.go
151+
CC=gcc GOOS=darwin GOARCH=amd64 go build -ldflags "-s -w" -buildmode=c-shared -trimpath -o libexcelize.amd64.darwin.dylib main.go
152+
rm -f libexcelize.*.h
121153
122154
- name: Upload Linux Artifacts
123155
if: matrix.os == 'ubuntu-24.04'
@@ -128,13 +160,20 @@ jobs:
128160
libexcelize.386.linux.so
129161
libexcelize.amd64.linux.so
130162
163+
- name: Upload Linux ARM64 Artifacts
164+
if: matrix.os == 'ubuntu-24.04-arm'
165+
uses: actions/upload-artifact@v4
166+
with:
167+
name: linux-arm64-artifacts
168+
path: |
169+
libexcelize.arm64.linux.so
170+
131171
- name: Upload Darwin Artifacts
132172
if: matrix.os == 'macos-latest'
133173
uses: actions/upload-artifact@v4
134174
with:
135175
name: darwin-artifacts
136176
path: |
137-
libexcelize.arm64.linux.so
138177
libexcelize.amd64.windows.dll
139178
libexcelize.386.windows.dll
140179
libexcelize.arm64.windows.dll

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,4 @@ This program is under the terms of the BSD 3-Clause License. See [https://openso
184184

185185
The Excel logo is a trademark of [Microsoft Corporation](https://aka.ms/trademarks-usage). This artwork is an adaptation.
186186

187-
gopher.{ai,svg,png} was created by [Takuya Ueda](https://twitter.com/tenntenn). Licensed under the [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/).
187+
The Go gopher was created by [Renee French](https://go.dev/doc/gopher/README). Licensed under the [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/).

README_zh.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,4 +184,4 @@ finally:
184184

185185
Excel 徽标是 [Microsoft Corporation](https://aka.ms/trademarks-usage) 的商标,项目的图片是一种改编。
186186

187-
gopher.{ai,svg,png}[Takuya Ueda](https://twitter.com/tenntenn) 创作,遵循 [Creative Commons 3.0 Attributions license](http://creativecommons.org/licenses/by/3.0/) 创作共用授权条款。
187+
Go gopher 由 [Renee French](https://go.dev/doc/gopher/README) 创作,遵循 [Creative Commons 4.0 Attributions license](http://creativecommons.org/licenses/by/4.0/) 创作共用授权条款。

excelize.py

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import os
3434
import platform
3535
import sys
36+
import struct
3637
import types_go
3738
from types_py import *
3839

@@ -42,8 +43,15 @@ def load_lib() -> Optional[str]:
4243
Load the shared library based on the current platform and architecture.
4344
"""
4445
system = platform.system().lower()
45-
arch = platform.architecture()[0]
46-
machine = platform.machine().lower()
46+
compiler = platform.python_compiler().lower()
47+
bit = "64bit" if struct.calcsize("P") * 8 == 64 else "32bit"
48+
arch = platform.machine().lower()
49+
if "aarch" in compiler or "arm" in compiler:
50+
arch = "arm64"
51+
elif "amd64" in compiler or "x86_64" in compiler or "64" in compiler:
52+
arch = "amd64"
53+
elif "i386" in compiler or "i686" in compiler or "32" in compiler:
54+
arch = "x86"
4755
ext_map = {"linux": ".so", "darwin": ".dylib", "windows": ".dll"}
4856
arch_map = {
4957
"linux": {
@@ -80,8 +88,8 @@ def load_lib() -> Optional[str]:
8088
},
8189
},
8290
}
83-
if system in ext_map and arch in arch_map.get(system, {}):
84-
arch_name = arch_map[system][arch].get(machine)
91+
if system in ext_map and bit in arch_map.get(system, {}):
92+
arch_name = arch_map[system][bit].get(arch)
8593
if arch_name:
8694
return f"libexcelize.{arch_name}.{system}{ext_map[system]}"
8795

@@ -91,7 +99,7 @@ def load_lib() -> Optional[str]:
9199

92100
lib = CDLL(os.path.join(os.path.dirname(__file__), load_lib()))
93101
ENCODE = "utf-8"
94-
__version__ = "0.0.5"
102+
__version__ = "0.0.6"
95103
uppercase_words = ["id", "rgb", "sq", "xml"]
96104

97105

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ module github.com/xuri/excelize-py
33
go 1.24.0
44

55
require (
6-
github.com/xuri/excelize/v2 v2.10.0
6+
github.com/xuri/excelize/v2 v2.10.1-0.20251029022420-541ca7c1b9ea
77
golang.org/x/image v0.32.0
88
)
99

go.sum

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ github.com/tiendc/go-deepcopy v1.7.1 h1:LnubftI6nYaaMOcaz0LphzwraqN8jiWTwm416sit
1313
github.com/tiendc/go-deepcopy v1.7.1/go.mod h1:4bKjNC2r7boYOkD2IOuZpYjmlDdzjbpTRyCx+goBCJQ=
1414
github.com/xuri/efp v0.0.1 h1:fws5Rv3myXyYni8uwj2qKjVaRP30PdjeYe2Y6FDsCL8=
1515
github.com/xuri/efp v0.0.1/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI=
16-
github.com/xuri/excelize/v2 v2.10.0 h1:8aKsP7JD39iKLc6dH5Tw3dgV3sPRh8uRVXu/fMstfW4=
17-
github.com/xuri/excelize/v2 v2.10.0/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
16+
github.com/xuri/excelize/v2 v2.10.1-0.20251029022420-541ca7c1b9ea h1:6CUpQDTER0gx6GBQEeNIQpFdQ92g8slsF08s+IqVWvE=
17+
github.com/xuri/excelize/v2 v2.10.1-0.20251029022420-541ca7c1b9ea/go.mod h1:SC5TzhQkaOsTWpANfm+7bJCldzcnU/jrhqkTi/iBHBU=
1818
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9 h1:+C0TIdyyYmzadGaL/HBLbf3WdLgC29pgyhTjAT/0nuE=
1919
github.com/xuri/nfp v0.0.2-0.20250530014748-2ddeb826f9a9/go.mod h1:WwHg+CVyzlv/TX9xqBFXEZAuxOPxn2k1GNHwG41IIUQ=
2020
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=

main.go

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1750,8 +1750,8 @@ func StreamAddTable(swIdx int, table *C.struct_Table) *C.char {
17501750
// break on another.
17511751
//
17521752
//export StreamInsertPageBreak
1753-
func StreamInsertPageBreak(swIDx int, cell *C.char) *C.char {
1754-
streamWriter, ok := sw.Load(swIDx)
1753+
func StreamInsertPageBreak(swIdx int, cell *C.char) *C.char {
1754+
streamWriter, ok := sw.Load(swIdx)
17551755
if !ok {
17561756
return C.CString(errStreamWriterPtr)
17571757
}
@@ -1766,8 +1766,8 @@ func StreamInsertPageBreak(swIDx int, cell *C.char) *C.char {
17661766
// existing merged cell.
17671767
//
17681768
//export StreamMergeCell
1769-
func StreamMergeCell(swIDx int, topLeftCell, bottomRightCell *C.char) *C.char {
1770-
streamWriter, ok := sw.Load(swIDx)
1769+
func StreamMergeCell(swIdx int, topLeftCell, bottomRightCell *C.char) *C.char {
1770+
streamWriter, ok := sw.Load(swIdx)
17711771
if !ok {
17721772
return C.CString(errStreamWriterPtr)
17731773
}
@@ -1782,8 +1782,8 @@ func StreamMergeCell(swIDx int, topLeftCell, bottomRightCell *C.char) *C.char {
17821782
// the 'StreamSetColWidth' function before the 'StreamSetRow' function.
17831783
//
17841784
//export StreamSetColWidth
1785-
func StreamSetColWidth(swIDx int, minVal, maxVal int, width float64) *C.char {
1786-
streamWriter, ok := sw.Load(swIDx)
1785+
func StreamSetColWidth(swIdx int, minVal, maxVal int, width float64) *C.char {
1786+
streamWriter, ok := sw.Load(swIdx)
17871787
if !ok {
17881788
return C.CString(errStreamWriterPtr)
17891789
}
@@ -1798,13 +1798,13 @@ func StreamSetColWidth(swIDx int, minVal, maxVal int, width float64) *C.char {
17981798
// call the 'StreamSetPanes' function before the 'StreamSetRow' function.
17991799
//
18001800
//export StreamSetPanes
1801-
func StreamSetPanes(swIDx int, opts *C.struct_Panes) *C.char {
1801+
func StreamSetPanes(swIdx int, opts *C.struct_Panes) *C.char {
18021802
var options excelize.Panes
18031803
goVal, err := cValueToGo(reflect.ValueOf(*opts), reflect.TypeOf(excelize.Panes{}))
18041804
if err != nil {
18051805
return C.CString(err.Error())
18061806
}
1807-
streamWriter, ok := sw.Load(swIDx)
1807+
streamWriter, ok := sw.Load(swIdx)
18081808
if !ok {
18091809
return C.CString(errStreamWriterPtr)
18101810
}
@@ -1820,8 +1820,8 @@ func StreamSetPanes(swIDx int, opts *C.struct_Panes) *C.char {
18201820
// function to end the streaming writing process.
18211821
//
18221822
//export StreamSetRow
1823-
func StreamSetRow(swIDx int, cell *C.char, row *C.struct_Interface, length int) *C.char {
1824-
streamWriter, ok := sw.Load(swIDx)
1823+
func StreamSetRow(swIdx int, cell *C.char, row *C.struct_Interface, length int) *C.char {
1824+
streamWriter, ok := sw.Load(swIdx)
18251825
if !ok {
18261826
return C.CString(errStreamWriterPtr)
18271827
}
@@ -1838,8 +1838,8 @@ func StreamSetRow(swIDx int, cell *C.char, row *C.struct_Interface, length int)
18381838
// StreamFlush ending the streaming writing process.
18391839
//
18401840
//export StreamFlush
1841-
func StreamFlush(swIDx int, sheet *C.char) *C.char {
1842-
streamWriter, ok := sw.Load(swIDx)
1841+
func StreamFlush(swIdx int) *C.char {
1842+
streamWriter, ok := sw.Load(swIdx)
18431843
if !ok {
18441844
return C.CString(errStreamWriterPtr)
18451845
}

setup.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def run(self):
4242

4343
setup(
4444
name="excelize",
45-
version="0.0.5",
45+
version="0.0.6",
4646
license="BSD 3-Clause",
4747
license_files=("LICENSE"),
4848
description="A Python build of the Go Excelize library for reading and writing Microsoft Excel™ (XLAM / XLSM / XLSX / XLTM / XLTX) spreadsheets",
@@ -55,7 +55,7 @@ def run(self):
5555
zip_safe=False,
5656
project_urls={
5757
"Source": "https://github.com/xuri/excelize-py",
58-
"Documentation": "https://xuri.me/excelize",
58+
"Documentation": "https://xuri.me/excelize-py",
5959
},
6060
cmdclass={"install": CustomInstallCommand},
6161
py_modules=[
@@ -88,5 +88,7 @@ def run(self):
8888
"Programming Language :: Python :: 3.11",
8989
"Programming Language :: Python :: 3.12",
9090
"Programming Language :: Python :: 3.13",
91+
"Programming Language :: Python :: 3.14",
92+
"Programming Language :: Python :: 3.15",
9193
],
9294
)

test_excelize.py

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -34,30 +34,48 @@ class TestExcelize(unittest.TestCase):
3434
unittest (unittest.TestCase): unittest class.
3535
"""
3636

37-
@patch("platform.architecture")
38-
def test_platform_architecture(self, mock_architecture):
39-
mock_architecture.return_value = ("unknown", "ELF")
40-
with self.assertRaises(SystemExit):
41-
excelize.load_lib()
42-
4337
@patch("platform.machine")
44-
def test_platform_machine(self, mock_machine):
38+
@patch("platform.python_compiler")
39+
@patch("platform.system")
40+
def test_python_compiler(self, mock_system, mock_python_compiler, mock_machine):
41+
mock_system.return_value = "unknown"
42+
mock_python_compiler.return_value = "unknown"
4543
mock_machine.return_value = "unknown"
4644
with self.assertRaises(SystemExit):
4745
excelize.load_lib()
4846

49-
@patch("platform.machine")
47+
@patch("struct.calcsize")
48+
@patch("platform.python_compiler")
5049
@patch("platform.system")
51-
def test_platform_machine_arm64(self, mock_machine, mock_system):
52-
mock_machine.return_value = "darwin"
53-
mock_system.return_value = "arm64"
50+
def test_python_compiler_386(
51+
self, mock_system, mock_python_compiler, mock_calcsize
52+
):
53+
mock_system.return_value = "windows"
54+
mock_python_compiler.return_value = "i386"
55+
mock_calcsize.return_value = 4
5456
excelize.load_lib()
5557

58+
@patch("struct.calcsize")
59+
@patch("platform.python_compiler")
5660
@patch("platform.system")
57-
def test_platform_system(self, mock_system):
58-
mock_system.return_value = "unknown"
59-
with self.assertRaises(SystemExit):
60-
excelize.load_lib()
61+
def test_python_compiler_arm64(
62+
self, mock_system, mock_python_compiler, mock_calcsize
63+
):
64+
mock_system.return_value = "windows"
65+
mock_python_compiler.return_value = "arm64"
66+
mock_calcsize.return_value = 8
67+
excelize.load_lib()
68+
69+
@patch("struct.calcsize")
70+
@patch("platform.python_compiler")
71+
@patch("platform.system")
72+
def test_python_compiler_amd64(
73+
self, mock_system, mock_python_compiler, mock_calcsize
74+
):
75+
mock_system.return_value = "windows"
76+
mock_python_compiler.return_value = "amd64"
77+
mock_calcsize.return_value = 8
78+
excelize.load_lib()
6179

6280
def test_c_value_to_py(self):
6381
self.assertIsNone(excelize.c_value_to_py(None, None))
@@ -903,7 +921,7 @@ def test_none_file_pointer(self):
903921
self.assertEqual(
904922
str(context.exception),
905923
"expected type AppProperties for argument 'app_properties', but got int",
906-
)
924+
)
907925
with self.assertRaises(RuntimeError) as context:
908926
f.set_default_font("")
909927
self.assertEqual(str(context.exception), expected)

0 commit comments

Comments
 (0)