Skip to content

Commit a2c04af

Browse files
committed
Support building with MSVC
Fixes apache#7 The Python client cannot be built with MSVC. CMakeLists.txt: 1. Boost.Python cannot be found on Windows. The component of Boost cannot be `python3`. It should be a specific version like `python310`. Therefore, find all possible components from 3.10 to 3.7 until the first component is available. 2. For MSVC, link to `pulsarWithDeps.lib` when `LINK_STATIC` is `ON` and set the `MSVC_RUNTIME_LIBRARY` target property to `MultiThreaded`. 3. Change the suffix from `.so` to `.pyd` because Python on Windows recognizes `*.pyd` as the dynamic library. 4. Link to Python library with MSVC, otherwise the symbos cannot be found when linking `boost-python`. README: tell users how to build Python client on Windows. Add a workflow to build Python wheels of versions 3.7 to 3.10 on Windows and verify the build will succeed. With this PR, Windows users still need to put the related `boost_python*.dll` under the system path. In future, the `boost-python` dependency will be removed. See apache#24.
1 parent 3e37e44 commit a2c04af

12 files changed

+247
-16
lines changed

.github/workflows/ci-pr-validation.yaml

+84-1
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ jobs:
6767

6868
linux-wheel:
6969
name: Wheel ${{matrix.image.name}} - Py ${{matrix.python.version}} - ${{matrix.cpu.platform}}
70+
needs: unit-tests
7071
runs-on: ubuntu-22.04
7172
timeout-minutes: 300
7273

@@ -118,6 +119,7 @@ jobs:
118119
119120
mac-wheels:
120121
name: Wheel MacOS Universal2 - Py ${{matrix.py.version}}
122+
needs: unit-tests
121123
runs-on: macos-12
122124
timeout-minutes: 300
123125

@@ -155,11 +157,92 @@ jobs:
155157
- name: Build and test Mac wheels
156158
run: pkg/mac/build-mac-wheels.sh ${{matrix.py.version}}
157159

160+
windows-wheels:
161+
name: "Python ${{ matrix.python.version }} Wheel on ${{ matrix.windows.name }}"
162+
needs: unit-tests
163+
runs-on: ${{ matrix.windows.os }}
164+
timeout-minutes: 120
165+
166+
env:
167+
PULSAR_CPP_DIR: 'C:\\pulsar-cpp'
168+
strategy:
169+
fail-fast: false
170+
matrix:
171+
windows:
172+
- name: 'Windows x64'
173+
os: windows-2022
174+
arch: '-A x64'
175+
triplet: 'x64-windows'
176+
python:
177+
- version: '3.7'
178+
- version: '3.8'
179+
- version: '3.9'
180+
- version: '3.10'
181+
182+
steps:
183+
- uses: actions/checkout@v3
184+
with:
185+
submodules: true
186+
187+
- uses: actions/setup-python@v4
188+
with:
189+
python-version: ${{ matrix.python.version }}
190+
191+
- name: Prepare vcpkg.json
192+
shell: bash
193+
run: |
194+
python --version
195+
cp -f vcpkg-${{ matrix.python.version }}.json vcpkg.json
196+
cat vcpkg.json
197+
198+
- name: Download Pulsar C++ client on Windows
199+
shell: bash
200+
run: |
201+
mkdir -p ${{ env.PULSAR_CPP_DIR }}
202+
cd ${{ env.PULSAR_CPP_DIR }}
203+
# TODO: switch to official releases
204+
curl -O -L https://github.com/BewareMyPower/pulsar-client-cpp/releases/download/v3.1.0-rc-20221028/${{ matrix.windows.triplet }}-static.zip
205+
unzip -q ${{ matrix.windows.triplet }}-static.zip
206+
ls -l ${{ env.PULSAR_CPP_DIR }}
207+
208+
- name: Dependencies cache
209+
uses: actions/cache@v3
210+
id: cache-deps
211+
with:
212+
path: build/vcpkg_installed
213+
key: ${{ matrix.python.version }}-${{ hashFiles("vcpkg-${{ matrix.python.version }}.json") }}
214+
215+
- name: Install dependencies and configure CMake
216+
shell: bash
217+
run: |
218+
COMMIT_ID=$(grep baseline vcpkg.json | sed 's/[",]//g' | awk '{print $2}')
219+
cd vcpkg
220+
echo "git fetch origin $COMMIT_ID"
221+
git fetch origin $COMMIT_ID
222+
cd -
223+
cmake -B build ${{ matrix.windows.arch }} \
224+
-DCMAKE_PREFIX_PATH=${{ env.PULSAR_CPP_DIR }} \
225+
-DUSE_VCPKG=ON \
226+
-DLINK_STATIC=ON
227+
228+
- name: Build Python wheel
229+
shell: bash
230+
run: |
231+
cmake --build build --config Release --target install
232+
python -m pip install wheel
233+
python setup.py bdist_wheel
234+
python -m pip install ./dist/*.whl
235+
cp ./build/Release/boost_python*.dll .
236+
echo "The extra DLLs:"
237+
ls -l *.dll
238+
python -c 'import pulsar; c = pulsar.Client("pulsar://localhost:6650"); c.close()'
239+
240+
158241
# Job that will be required to complete and depends on all the other jobs
159242
check-completion:
160243
name: Check Completion
161244
runs-on: ubuntu-latest
162-
needs: [unit-tests, linux-wheel, mac-wheels]
245+
needs: [unit-tests, linux-wheel, mac-wheels, windows-wheels]
163246

164247
steps:
165248
- run: true

.gitignore

+4-1
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@ __pycache__
1313
.pulsar-mac-wheels-cache
1414
.DS_Store
1515
wheelhouse
16-
.pulsar-mac-build
16+
.pulsar-mac-build
17+
vcpkg_installed/
18+
*.pyd
19+
*.lib

.gitmodules

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
[submodule "vcpkg"]
2+
path = vcpkg
3+
url = https://github.com/microsoft/vcpkg.git

CMakeLists.txt

+51-11
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,15 @@
1717
# under the License.
1818
#
1919

20-
project (pulsar-client-python)
2120
cmake_minimum_required(VERSION 3.18)
2221
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/cmake_modules")
2322

23+
option(USE_VCPKG "Use Vcpkg to install dependencies" OFF)
24+
if (USE_VCPKG)
25+
set(CMAKE_TOOLCHAIN_FILE "${CMAKE_SOURCE_DIR}/vcpkg/scripts/buildsystems/vcpkg.cmake"
26+
CACHE STRING "Vcpkg toolchain file")
27+
endif ()
28+
project (pulsar-client-python)
2429
option(LINK_STATIC "Link against static libraries" OFF)
2530
MESSAGE(STATUS "LINK_STATIC: " ${LINK_STATIC})
2631

@@ -29,10 +34,19 @@ set(THREADS_PREFER_PTHREAD_FLAG TRUE)
2934
find_package(Threads REQUIRED)
3035
MESSAGE(STATUS "Threads library: " ${CMAKE_THREAD_LIBS_INIT})
3136

37+
if (MSVC)
38+
add_compile_options(/wd4819)
39+
endif ()
40+
3241
if (LINK_STATIC)
33-
find_library(PULSAR_LIBRARY NAMES libpulsar.a)
42+
if (MSVC)
43+
find_library(PULSAR_LIBRARY NAMES pulsarWithDeps.lib)
44+
else ()
45+
find_library(PULSAR_LIBRARY NAMES libpulsar.a)
46+
endif ()
47+
add_definitions("-DPULSAR_STATIC")
3448
else()
35-
find_library(PULSAR_LIBRARY NAMES libpulsar.so libpulsar.dylib)
49+
find_library(PULSAR_LIBRARY NAMES pulsar libpulsar)
3650
endif()
3751
message(STATUS "PULSAR_LIBRARY: ${PULSAR_LIBRARY}")
3852

@@ -44,10 +58,23 @@ SET(CMAKE_CXX_STANDARD 11)
4458
find_package (Python3 REQUIRED COMPONENTS Development.Module)
4559
MESSAGE(STATUS "PYTHON: " ${Python3_VERSION} " - " ${Python3_INCLUDE_DIRS})
4660

61+
find_package(Boost REQUIRED ${Boost_INCLUDE_DIRS})
62+
message(STATUS "Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
63+
4764
SET(Boost_USE_STATIC_LIBS ${LINK_STATIC})
48-
find_package(Boost REQUIRED COMPONENTS python3)
49-
MESSAGE(STATUS "Boost Python3: " ${Boost_PYTHON3_LIBRARY})
50-
MESSAGE(STATUS "Boost_INCLUDE_DIRS: ${Boost_INCLUDE_DIRS}")
65+
66+
set(BOOST_PYTHON_NAME_LIST python3 python310 python39 python38 python37)
67+
foreach (BOOST_PYTHON_NAME IN LISTS BOOST_PYTHON_NAME_LIST)
68+
find_package(Boost QUIET COMPONENTS ${BOOST_PYTHON_NAME})
69+
if (${Boost_FOUND})
70+
set(BOOST_PYTHON_COMPONENT_FOUND ${BOOST_PYTHON_NAME})
71+
message(STATUS "Found Boost COMPONENTS " ${BOOST_PYTHON_COMPONENT_FOUND})
72+
break ()
73+
endif ()
74+
endforeach ()
75+
if (NOT BOOST_PYTHON_COMPONENT_FOUND)
76+
message(FATAL_ERROR "Could not find Boost Python library")
77+
endif ()
5178

5279
########################################################################################################################
5380

@@ -68,8 +95,11 @@ ADD_LIBRARY(_pulsar SHARED src/pulsar.cc
6895
src/utils.cc
6996
)
7097

71-
SET(CMAKE_SHARED_LIBRARY_PREFIX )
72-
SET(CMAKE_SHARED_LIBRARY_SUFFIX .so)
98+
if (MSVC)
99+
set(CMAKE_SHARED_LIBRARY_SUFFIX .pyd)
100+
else ()
101+
set(CMAKE_SHARED_LIBRARY_SUFFIX .so)
102+
endif ()
73103

74104
if (NOT APPLE AND NOT MSVC)
75105
SET(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS_PYTHON}")
@@ -80,12 +110,17 @@ if("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang")
80110
endif()
81111

82112
# Try all possible boost-python variable namings
83-
set(PYTHON_WRAPPER_LIBS ${PULSAR_LIBRARY}
84-
${Boost_PYTHON3_LIBRARY})
113+
set(PYTHON_WRAPPER_LIBS
114+
${PULSAR_LIBRARY}
115+
Boost::${BOOST_PYTHON_COMPONENT_FOUND}
116+
)
117+
if (MSVC)
118+
set(PYTHON_WRAPPER_LIBS ${PYTHON_WRAPPER_LIBS} Python3::Module)
119+
endif ()
85120

86121
message(STATUS "All libraries: ${PYTHON_WRAPPER_LIBS}")
87122

88-
if (LINK_STATIC)
123+
if (LINK_STATIC AND NOT MSVC)
89124
set(CMAKE_FIND_LIBRARY_SUFFIXES ".a")
90125

91126
# We need to include all the static libs individually because we cannot easily create a universal2 libpulsar.a
@@ -125,9 +160,14 @@ if (LINK_STATIC)
125160
endif()
126161
target_link_libraries(_pulsar ${PYTHON_WRAPPER_LIBS})
127162
endif ()
163+
elseif (LINK_STATIC) # MSVC
164+
set_property(TARGET _pulsar PROPERTY
165+
MSVC_RUNTIME_LIBRARY "MultiThreaded$<$<CONFIG:Debug>:Debug>")
166+
target_link_libraries(_pulsar ${PYTHON_WRAPPER_LIBS})
128167
else()
129168
target_link_libraries(_pulsar ${PYTHON_WRAPPER_LIBS})
130169
endif ()
170+
install(TARGETS _pulsar DESTINATION ${CMAKE_SOURCE_DIR})
131171

132172
find_package(ClangTools)
133173
set(BUILD_SUPPORT_DIR "${CMAKE_SOURCE_DIR}/build-support")

README.md

+30-2
Original file line numberDiff line numberDiff line change
@@ -31,20 +31,48 @@
3131

3232
## Install the Python wheel
3333

34+
### Windows (with Vcpkg)
35+
36+
First, install the dependencies via [Vcpkg](https://github.com/microsoft/vcpkg).
37+
38+
```PowerShell
39+
vcpkg install --feature-flags=manifests --triplet x64-windows
40+
```
41+
42+
> NOTE: For Windows 32-bit library, change `x64-windows` to `x86-windows`, see [here](https://github.com/microsoft/vcpkg/tree/master/triplets) for all available triplets.
43+
44+
Then, build and install the Python wheel.
45+
46+
```PowerShell
47+
# Assuming the Pulsar C++ client has been installed under the `PULSAR_CPP` directory.
48+
cmake -B build -DUSE_VCPKG=ON -DCMAKE_PREFIX_PATH="$env:PULSAR_CPP" -DLINK_STATIC=ON
49+
cmake --build build --config Release
50+
cmake --install build
51+
py setup.py bdist_wheel
52+
py -m pip install ./dist/pulsar_client-*.whl
53+
```
54+
55+
Since the Python client links to Boost.Python dynamically, you have to copy the dll (e.g. `boost_python310-vc142-mt-x64-1_80.dll`) into the system path (the `PATH` environment variable). If the `-DLINK_STATIC=ON` option is not specified, you have to copy the `pulsar.dll` into the system path as well.
56+
57+
### Linux or macOS
58+
59+
Assuming the Pulsar C++ client and Boost.Python have been installed under the system path.
60+
3461
```bash
3562
cmake -B build
3663
cmake --build build -j8
37-
cp build/_pulsar.so .
64+
cmake --install build
3865
./setup.py bdist_wheel
3966
pip3 install dist/pulsar_client-*.whl --force-reinstall
40-
rm _pulsar.so
4167
```
4268

4369
> **NOTE**
4470
>
4571
> 1. Here a separate `build` directory is created to store all CMake temporary files. However, the `setup.py` requires the `_pulsar.so` is under the project directory.
4672
> 2. Add the `--force-reinstall` option to overwrite the existing Python wheel in case your system has already installed a wheel before.
4773
74+
## Running examples
75+
4876
You can run `python3 -c 'import pulsar'` to see whether the wheel has been installed successfully. If it failed, check whether dependencies (e.g. `libpulsar.so`) are in the system path. If not, make sure the dependencies are in `LD_LIBRARY_PATH` (on Linux) or `DYLD_LIBRARY_PATH` (on macOS).
4977

5078
Then you can run examples as a simple end-to-end test.

setup.py

+8-1
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from setuptools import setup
2222
from distutils.core import Extension
2323
from os import environ, path
24+
import platform
2425

2526
from distutils.command import build_ext
2627

@@ -58,7 +59,13 @@ def build_extension(self, ext):
5859
except OSError as e:
5960
if e.errno != 17: # already exists
6061
raise
61-
shutil.copyfile('_pulsar.so', self.get_ext_fullpath(ext.name))
62+
if 'Windows' in platform.platform():
63+
shutil.copyfile('_pulsar.pyd', self.get_ext_fullpath(ext.name))
64+
else:
65+
try:
66+
shutil.copyfile('_pulsar.so', self.get_ext_fullpath(ext.name))
67+
except FileNotFoundError:
68+
shutil.copyfile('lib_pulsar.so', self.get_ext_fullpath(ext.name))
6269

6370

6471
# Core Client dependencies

vcpkg

Submodule vcpkg added at 2537044

vcpkg-3.10.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "pulsar-python",
3+
"version": "3.0.0",
4+
"description": "Pulsar Python SDK (Python 3.10)",
5+
"dependencies": [
6+
{
7+
"name": "boost-python",
8+
"version>=": "1.79.0"
9+
}
10+
],
11+
"builtin-baseline": "c266859544a3cdcfd952d218039c55a268863740"
12+
}

vcpkg-3.7.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "pulsar-python",
3+
"version": "3.0.0",
4+
"description": "Pulsar Python SDK (Python 3.7)",
5+
"dependencies": [
6+
{
7+
"name": "boost-python",
8+
"version>=": "1.76.0"
9+
}
10+
],
11+
"builtin-baseline": "35312384e7701760ed7855961eff41a63f9cc379",
12+
"overrides": [
13+
{
14+
"name": "python3",
15+
"version": "3.7.3"
16+
}
17+
]
18+
}

vcpkg-3.8.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "pulsar-python",
3+
"version": "3.0.0",
4+
"description": "Pulsar Python SDK (Python 3.8)",
5+
"dependencies": [
6+
{
7+
"name": "boost-python",
8+
"version>=": "1.76.0"
9+
}
10+
],
11+
"builtin-baseline": "35312384e7701760ed7855961eff41a63f9cc379",
12+
"overrides": [
13+
{
14+
"name": "python3",
15+
"version": "3.8.3"
16+
}
17+
]
18+
}

vcpkg-3.9.json

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{
2+
"name": "pulsar-python",
3+
"version": "3.0.0",
4+
"description": "Pulsar Python SDK (Python 3.9)",
5+
"dependencies": [
6+
{
7+
"name": "boost-python",
8+
"version>=": "1.76.0"
9+
}
10+
],
11+
"builtin-baseline": "35312384e7701760ed7855961eff41a63f9cc379"
12+
}

vcpkg.json

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"name": "pulsar-python",
3+
"version": "3.0.0",
4+
"description": "Pulsar Python SDK",
5+
"dependencies": ["boost-python"]
6+
}

0 commit comments

Comments
 (0)