Skip to content

Commit 7773d51

Browse files
authored
Add ability to create Python wheels (google#313)
* Rename Python bindings from pywraps2 to s2geometry and set SWIG CMake policies to remove warnings about using deprecated default binding name * Fix broken unit tests * Pass -DCMAKE_POSITION_INDEPENDENT_CODE=ON to CMake via setup.py for building Python wheel * Use a PEP 440-compliant pre-release version since the code in master does not correspond to already released version 0.10.0
1 parent d184302 commit 7773d51

File tree

6 files changed

+131
-21
lines changed

6 files changed

+131
-21
lines changed

README.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,12 @@ Disable building of shared libraries with `-DBUILD_SHARED_LIBS=OFF`.
116116

117117
Enable the python interface with `-DWITH_PYTHON=ON`.
118118

119+
If OpenSSL is installed in a non-standard location set `OPENSSL_ROOT_DIR`
120+
before running configure, for example on macOS:
121+
```
122+
OPENSSL_ROOT_DIR=/opt/homebrew/Cellar/openssl@3/3.1.0 cmake -DCMAKE_PREFIX_PATH=/opt/homebrew -DCMAKE_CXX_STANDARD=17
123+
```
124+
119125
## Installing
120126

121127
From `build` subdirectory:
@@ -174,6 +180,25 @@ even 2.0.
174180

175181
Python 3 is required.
176182

183+
### Creating wheels
184+
First, make a virtual environment and install `cmake_build_extension` and `wheel`
185+
into it:
186+
```
187+
python3 -m venv venv
188+
source venv/bin/activate
189+
pip install cmake_build_extension wheel
190+
```
191+
192+
Then build the wheel:
193+
```
194+
python setup.py bdist_wheel
195+
```
196+
197+
The resulting wheel will be in the `dist` directory.
198+
199+
> If OpenSSL is in a non-standard location make sure to set `OPENSSL_ROOT_DIR`
200+
> when calling `setup.py`, see above for more information.
201+
177202
## Other S2 implementations
178203

179204
* [Go](https://github.com/golang/geo) (Approximately 40% complete.)

pyproject.toml

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
[build-system]
2+
requires = [
3+
"wheel",
4+
"setuptools",
5+
"setuptools_scm[toml]",
6+
"cmake_build_extension",
7+
]
8+
build-backend = "setuptools.build_meta"

setup.cfg

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
[metadata]
2+
name = s2geometry
3+
version = 0.11.0.dev1
4+
description = Python packaging of s2geometry
5+
author = Brian Miles
6+
author_email = [email protected]
7+
license= Apache 2
8+
project_urls =
9+
Source = https://github.com/google/s2geometry
10+
classifiers =
11+
Programming Language :: Python :: 3
12+
Operating System :: POSIX
13+
License :: OSI Approved :: Apache Software License
14+
15+
[options]
16+
zip_safe = False
17+
packages = find:
18+
package_dir = =src
19+
python_requres = >=3.7
20+
21+
[options.packages.find]
22+
where = src

setup.py

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import sys
2+
from pathlib import Path
3+
4+
import cmake_build_extension
5+
import setuptools
6+
7+
8+
setuptools.setup(
9+
ext_modules=[
10+
cmake_build_extension.CMakeExtension(
11+
# This could be anything you like, it is used to create build folders
12+
name="SwigBindings",
13+
# Name of the resulting package name (import s2geometry)
14+
install_prefix="s2geometry",
15+
# Selects the folder where the main CMakeLists.txt is stored
16+
# (it could be a subfolder)
17+
source_dir=str(Path(__file__).parent.absolute()),
18+
cmake_configure_options=[
19+
# This option points CMake to the right Python interpreter, and helps
20+
# the logic of FindPython3.cmake to find the active version
21+
f"-DPython3_ROOT_DIR={Path(sys.prefix)}",
22+
'-DCALL_FROM_SETUP_PY:BOOL=ON',
23+
'-DBUILD_SHARED_LIBS:BOOL=OFF',
24+
'-DCMAKE_POSITION_INDEPENDENT_CODE=ON',
25+
'-DWITH_PYTHON=ON'
26+
]
27+
)
28+
],
29+
cmdclass=dict(
30+
# Enable the CMakeExtension entries defined above
31+
build_ext=cmake_build_extension.BuildExtension,
32+
),
33+
)

src/python/CMakeLists.txt

Lines changed: 31 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,43 @@
1+
# Generate standard target names.
2+
cmake_policy(SET CMP0078 NEW)
3+
# Honor SWIG_MODULE_NAME via -module flag.
4+
cmake_policy(SET CMP0086 NEW)
5+
6+
# Handle where to install the resulting Python package
7+
if (CALL_FROM_SETUP_PY)
8+
# The CMakeExtension will set CMAKE_INSTALL_PREFIX to the root
9+
# of the resulting wheel archive
10+
set(S2GEOMETRY_INSTALL_PREFIX ${CMAKE_INSTALL_PREFIX})
11+
else()
12+
# The Python package is installed directly in the folder of the
13+
# detected interpreter (system, user, or virtualenv)
14+
set(S2GEOMETRY_INSTALL_PREFIX ${Python3_SITELIB})
15+
endif()
16+
117
include(${SWIG_USE_FILE})
218
include_directories(${Python3_INCLUDE_DIRS})
319

420
set(CMAKE_SWIG_FLAGS "")
5-
set_property(SOURCE s2.i PROPERTY SWIG_FLAGS "-module" "pywraps2")
21+
set_property(SOURCE s2.i PROPERTY SWIG_FLAGS "-module" "s2geometry")
622
set_property(SOURCE s2.i PROPERTY CPLUSPLUS ON)
723

8-
swig_add_library(pywraps2 LANGUAGE python SOURCES s2.i)
24+
swig_add_library(s2geometry LANGUAGE python SOURCES s2.i)
925

10-
swig_link_libraries(pywraps2 ${Python3_LIBRARIES} s2)
26+
swig_link_libraries(s2geometry ${Python3_LIBRARIES} s2)
1127
enable_testing()
12-
add_test(NAME pywraps2_test COMMAND
28+
add_test(NAME s2geometry_test COMMAND
1329
${Python3_EXECUTABLE}
14-
"${PROJECT_SOURCE_DIR}/src/python/pywraps2_test.py")
15-
set_property(TEST pywraps2_test PROPERTY ENVIRONMENT
30+
"${PROJECT_SOURCE_DIR}/src/python/s2geometry_test.py")
31+
set_property(TEST s2geometry_test PROPERTY ENVIRONMENT
1632
"PYTHONPATH=$ENV{PYTHONPATH}:${PROJECT_BINARY_DIR}/python")
1733

1834
# Install the wrapper.
19-
install(TARGETS _pywraps2 DESTINATION ${Python3_SITELIB})
20-
install(FILES "${PROJECT_BINARY_DIR}/python/pywraps2.py"
21-
DESTINATION ${Python3_SITELIB})
35+
install(TARGETS s2geometry DESTINATION ${S2GEOMETRY_INSTALL_PREFIX})
36+
37+
# Install swig-generated Python file (we rename it to __init__.py as it will
38+
# ultimately end up in a directory called s2geometry in site-packages, which will
39+
# serve as the module directory.
40+
install(FILES "${CMAKE_CURRENT_BINARY_DIR}/s2geometry.py"
41+
DESTINATION ${S2GEOMETRY_INSTALL_PREFIX}
42+
RENAME __init__.py
43+
COMPONENT s2geometry)

src/python/pywraps2_test.py renamed to src/python/s2geometry_test.py

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import unittest
1818
from collections import defaultdict
1919

20-
import pywraps2 as s2
20+
import s2geometry as s2
2121

2222

2323
class PyWrapS2TestCase(unittest.TestCase):
@@ -1039,38 +1039,38 @@ def testInitToUnionDistinct(self):
10391039

10401040
class S2ChordAngleTest(unittest.TestCase):
10411041
def testBasic(self):
1042-
ca = s2.S1ChordAngle(s2.S1Angle_Degrees(100))
1042+
ca = s2.S1ChordAngle(s2.S1Angle.Degrees(100))
10431043
self.assertAlmostEqual(100, ca.degrees())
10441044

10451045
def testArithmetic(self):
1046-
ca1 = s2.S1ChordAngle(s2.S1Angle_Degrees(10))
1047-
ca2 = s2.S1ChordAngle(s2.S1Angle_Degrees(20))
1046+
ca1 = s2.S1ChordAngle(s2.S1Angle.Degrees(10))
1047+
ca2 = s2.S1ChordAngle(s2.S1Angle.Degrees(20))
10481048
ca3 = ca1 + ca2
10491049
self.assertAlmostEqual(30, ca3.degrees())
10501050
ca4 = ca2 - ca1
10511051
self.assertAlmostEqual(10, ca4.degrees())
10521052

10531053
def testComparison(self):
1054-
ca1 = s2.S1ChordAngle(s2.S1Angle_Degrees(10))
1055-
ca2 = s2.S1ChordAngle(s2.S1Angle_Degrees(20))
1054+
ca1 = s2.S1ChordAngle(s2.S1Angle.Degrees(10))
1055+
ca2 = s2.S1ChordAngle(s2.S1Angle.Degrees(20))
10561056
self.assertTrue(ca1 < ca2)
10571057
self.assertTrue(ca2 > ca1)
10581058
self.assertFalse(ca1 > ca2)
10591059
self.assertFalse(ca2 < ca1)
10601060

1061-
ca3 = s2.S1ChordAngle(s2.S1Angle_Degrees(10))
1061+
ca3 = s2.S1ChordAngle(s2.S1Angle.Degrees(10))
10621062
self.assertTrue(ca1 == ca3)
10631063
self.assertFalse(ca1 == ca2)
10641064
self.assertFalse(ca1 != ca3)
10651065
self.assertTrue(ca1 != ca2)
10661066

10671067
def testInfinity(self):
1068-
ca1 = s2.S1ChordAngle(s2.S1Angle_Degrees(179))
1068+
ca1 = s2.S1ChordAngle(s2.S1Angle.Degrees(179))
10691069
ca2 = s2.S1ChordAngle.Infinity()
10701070
self.assertTrue(ca2 > ca1)
10711071

10721072
def testCopy(self):
1073-
ca1 = s2.S1ChordAngle(s2.S1Angle_Degrees(100))
1073+
ca1 = s2.S1ChordAngle(s2.S1Angle.Degrees(100))
10741074
ca2 = s2.S1ChordAngle(ca1)
10751075
self.assertAlmostEqual(100, ca2.degrees())
10761076

@@ -1092,7 +1092,7 @@ def testDefaults(self):
10921092
self.assertEqual(4, loop.num_vertices())
10931093

10941094
def testRadius(self):
1095-
self.opts.set_buffer_radius(s2.S1Angle_Degrees(0.001))
1095+
self.opts.set_buffer_radius(s2.S1Angle.Degrees(0.001))
10961096
op = s2.S2BufferOperation(self.layer, self.opts)
10971097

10981098
cell1 = s2.S2Cell(s2.S2CellId(s2.S2LatLng.FromDegrees(3.0, 4.0)).parent(8))
@@ -1104,7 +1104,7 @@ def testRadius(self):
11041104
self.assertEqual(20, loop.num_vertices())
11051105

11061106
def testRadiusAndError(self):
1107-
self.opts.set_buffer_radius(s2.S1Angle_Degrees(0.001))
1107+
self.opts.set_buffer_radius(s2.S1Angle.Degrees(0.001))
11081108
self.opts.set_error_fraction(0.1)
11091109
op = s2.S2BufferOperation(self.layer, self.opts)
11101110

@@ -1117,7 +1117,7 @@ def testRadiusAndError(self):
11171117
self.assertEqual(12, loop.num_vertices())
11181118

11191119
def testPoint(self):
1120-
self.opts.set_buffer_radius(s2.S1Angle_Degrees(0.001))
1120+
self.opts.set_buffer_radius(s2.S1Angle.Degrees(0.001))
11211121
op = s2.S2BufferOperation(self.layer, self.opts)
11221122

11231123
op.AddPoint(s2.S2LatLng.FromDegrees(14.0, 15.0).ToPoint())

0 commit comments

Comments
 (0)