From 70a2f31f9bb9f4ce98cb152a2c6d485af92a1671 Mon Sep 17 00:00:00 2001 From: Mathis Logemann Date: Wed, 25 Jan 2023 17:15:07 +0100 Subject: [PATCH] rework build system --- .gitignore | 1 + CMakeLists.txt | 66 +++++++++ build.py | 120 --------------- cmake/vcpkg_setup.cmake | 4 + pyproject.toml | 27 ++++ setup.cfg | 2 - setup.py | 317 ---------------------------------------- vcpkg.json | 12 ++ 8 files changed, 110 insertions(+), 439 deletions(-) create mode 100644 CMakeLists.txt delete mode 100644 build.py create mode 100644 cmake/vcpkg_setup.cmake create mode 100644 pyproject.toml delete mode 100644 setup.cfg delete mode 100755 setup.py create mode 100644 vcpkg.json diff --git a/.gitignore b/.gitignore index 331398c60..e390c7bbc 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,4 @@ mapnik/paths.py .mason/ mason_packages/ mapnik/plugins +_skbuild/ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..3999b668e --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,66 @@ +cmake_minimum_required(VERSION 3.15) +include(cmake/vcpkg_setup.cmake) +project(${SKBUILD_PROJECT_NAME} LANGUAGES CXX VERSION ${SKBUILD_PROJECT_VERSION}) + +find_package(Python COMPONENTS Interpreter Development.Module) +find_package(mapnik CONFIG REQUIRED) +find_package(Boost REQUIRED COMPONENTS thread python310) + +set(Python_SOABI ${SKBUILD_SOABI}) +Python_add_library(_mapnik MODULE WITH_SOABI) +target_link_libraries(_mapnik PRIVATE + mapnik::mapnik + mapnik::json + mapnik::wkt + # even though boost_thread is no longer used in mapnik core + # we need to link in for boost_python to avoid missing symbol: _ZN5boost6detail12get_tss_dataEPKv / boost::detail::get_tss_data + Boost::thread + Boost::python310 + ICU::data ICU::i18n ICU::uc +) +target_sources(_mapnik PRIVATE + src/boost_std_shared_shim.hpp + src/mapnik_color.cpp + src/mapnik_coord.cpp + src/mapnik_datasource_cache.cpp + src/mapnik_datasource.cpp + src/mapnik_enumeration_wrapper_converter.hpp + src/mapnik_enumeration.hpp + src/mapnik_envelope.cpp + src/mapnik_expression.cpp + src/mapnik_feature.cpp + src/mapnik_featureset.cpp + src/mapnik_font_engine.cpp + src/mapnik_fontset.cpp + src/mapnik_gamma_method.cpp + src/mapnik_geometry.cpp + src/mapnik_grid_view.cpp + src/mapnik_grid.cpp + src/mapnik_image_view.cpp + src/mapnik_image.cpp + src/mapnik_label_collision_detector.cpp + src/mapnik_layer.cpp + src/mapnik_logger.cpp + src/mapnik_map.cpp + src/mapnik_palette.cpp + src/mapnik_parameters.cpp + src/mapnik_proj_transform.cpp + src/mapnik_projection.cpp + src/mapnik_python.cpp + src/mapnik_query.cpp + src/mapnik_raster_colorizer.cpp + src/mapnik_rule.cpp + src/mapnik_scaling_method.cpp + src/mapnik_style.cpp + src/mapnik_svg.hpp + src/mapnik_symbolizer.cpp + src/mapnik_threads.hpp + src/mapnik_value_converter.hpp + src/mapnik_view_transform.cpp + src/python_grid_utils.cpp + src/python_grid_utils.hpp + src/python_optional.hpp + src/python_to_value.hpp +) + +install(TARGETS _mapnik LIBRARY DESTINATION ${SKBUILD_PROJECT_NAME}) diff --git a/build.py b/build.py deleted file mode 100644 index 0f94826b6..000000000 --- a/build.py +++ /dev/null @@ -1,120 +0,0 @@ -import glob -import os -from subprocess import Popen, PIPE -from distutils import sysconfig - -Import('env') - -def call(cmd, silent=True): - stdin, stderr = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE).communicate() - if not stderr: - return stdin.strip() - elif not silent: - print stderr - - -prefix = env['PREFIX'] -target_path = os.path.normpath(sysconfig.get_python_lib() + os.path.sep + env['MAPNIK_NAME']) - -py_env = env.Clone() - -py_env.Append(CPPPATH = sysconfig.get_python_inc()) - -py_env.Append(CPPDEFINES = env['LIBMAPNIK_DEFINES']) - -py_env['LIBS'] = [env['MAPNIK_NAME'],'libboost_python'] - -link_all_libs = env['LINKING'] == 'static' or env['RUNTIME_LINK'] == 'static' - -# even though boost_thread is no longer used in mapnik core -# we need to link in for boost_python to avoid missing symbol: _ZN5boost6detail12get_tss_dataEPKv / boost::detail::get_tss_data -py_env.AppendUnique(LIBS = 'boost_thread%s' % env['BOOST_APPEND']) - -if link_all_libs: - py_env.AppendUnique(LIBS=env['LIBMAPNIK_LIBS']) - -# note: on linux -lrt must be linked after thread to avoid: undefined symbol: clock_gettime -if env['RUNTIME_LINK'] == 'static' and env['PLATFORM'] == 'Linux': - py_env.AppendUnique(LIBS='rt') - -# TODO - do solaris/fedora need direct linking too? -python_link_flag = '' -if env['PLATFORM'] == 'Darwin': - python_link_flag = '-undefined dynamic_lookup' - -paths = ''' -"""Configuration paths of Mapnik fonts and input plugins (auto-generated by SCons).""" - -from os.path import normpath,join,dirname - -mapniklibpath = '%s' -mapniklibpath = normpath(join(dirname(__file__),mapniklibpath)) -''' - -paths += "inputpluginspath = join(mapniklibpath,'input')\n" - -if env['SYSTEM_FONTS']: - paths += "fontscollectionpath = normpath('%s')\n" % env['SYSTEM_FONTS'] -else: - paths += "fontscollectionpath = join(mapniklibpath,'fonts')\n" - -paths += "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n" - -if not os.path.exists(env['MAPNIK_NAME']): - os.mkdir(env['MAPNIK_NAME']) - -file('mapnik/paths.py','w').write(paths % (env['MAPNIK_LIB_DIR'])) - -# force open perms temporarily so that `sudo scons install` -# does not later break simple non-install non-sudo rebuild -try: - os.chmod('mapnik/paths.py',0666) -except: pass - -# install the shared object beside the module directory -sources = glob.glob('src/*.cpp') - -if 'install' in COMMAND_LINE_TARGETS: - # install the core mapnik python files, including '__init__.py' - init_files = glob.glob('mapnik/*.py') - if 'mapnik/paths.py' in init_files: - init_files.remove('mapnik/paths.py') - init_module = env.Install(target_path, init_files) - env.Alias(target='install', source=init_module) - # fix perms and install the custom generated 'paths.py' - targetp = os.path.join(target_path,'paths.py') - env.Alias("install", targetp) - # use env.Command rather than env.Install - # to enable setting proper perms on `paths.py` - env.Command( targetp, 'mapnik/paths.py', - [ - Copy("$TARGET","$SOURCE"), - Chmod("$TARGET", 0644), - ]) - -if 'uninstall' not in COMMAND_LINE_TARGETS: - if env['HAS_CAIRO']: - py_env.Append(CPPPATH = env['CAIRO_CPPPATHS']) - py_env.Append(CPPDEFINES = '-DHAVE_CAIRO') - if link_all_libs: - py_env.Append(LIBS=env['CAIRO_ALL_LIBS']) - - if env['HAS_PYCAIRO']: - py_env.Append(CPPDEFINES = '-DHAVE_PYCAIRO') - py_env.Append(CPPPATH = env['PYCAIRO_PATHS']) - -py_env.Append(LINKFLAGS=python_link_flag) -py_env.AppendUnique(LIBS='mapnik-json') -py_env.AppendUnique(LIBS='mapnik-wkt') - -_mapnik = py_env.LoadableModule('mapnik/_mapnik', sources, LDMODULEPREFIX='', LDMODULESUFFIX='.so') - -Depends(_mapnik, env.subst('../../src/%s' % env['MAPNIK_LIB_NAME'])) -Depends(_mapnik, env.subst('../../src/json/libmapnik-json${LIBSUFFIX}')) -Depends(_mapnik, env.subst('../../src/wkt/libmapnik-wkt${LIBSUFFIX}')) - -if 'uninstall' not in COMMAND_LINE_TARGETS: - pymapniklib = env.Install(target_path,_mapnik) - py_env.Alias(target='install',source=pymapniklib) - -env['create_uninstall_target'](env, target_path) diff --git a/cmake/vcpkg_setup.cmake b/cmake/vcpkg_setup.cmake new file mode 100644 index 000000000..8f3e42cbd --- /dev/null +++ b/cmake/vcpkg_setup.cmake @@ -0,0 +1,4 @@ +if(WIN32) + set(VCPKG_TARGET_TRIPLET x64-windows-static) +endif() +set(CMAKE_TOOLCHAIN_FILE "~/vcpkg/scripts/buildsystems/vcpkg.cmake") diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 000000000..c52bf74a5 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,27 @@ +[project] +name = "mapnik" +version = "4.0.0" +authors = [ + { name="Blake Thompson", email="flippmoke@gmail.com" }, +] +description = "Python bindings for Mapnik" +readme = "README.md" +requires-python = ">=3.7" +license = "LGPL-2.1-only" + +[project.urls] +"Homepage" = "https://github.com/mapnik/python-mapnik" +"Bug Tracker" = "https://github.com/mapnik/python-mapnik/issues" + +[project.optional-dependencies] +test = ["pytest"] + +[build-system] +requires = ["scikit-build-core"] +build-backend = "scikit_build_core.build" + +[tool.scikit-build] +cmake.minimum-version = "3.15" +cmake.build-type = "Release" +sdist.reproducible = true +wheel.packages = ["src", "mapnik"] diff --git a/setup.cfg b/setup.cfg deleted file mode 100644 index f4ca59d33..000000000 --- a/setup.cfg +++ /dev/null @@ -1,2 +0,0 @@ -[nosetests] -verbosity=1 diff --git a/setup.py b/setup.py deleted file mode 100755 index 9985da5a2..000000000 --- a/setup.py +++ /dev/null @@ -1,317 +0,0 @@ -#! /usr/bin/env python - -import os -import os.path -import re -import shutil -import subprocess -import sys -import glob -from distutils import sysconfig -from ctypes.util import find_library - -from setuptools import Command, Extension, setup - -PYTHON3 = sys.version_info.major == 3 - - -# Utils -def check_output(args): - output = subprocess.check_output(args) - if PYTHON3: - # check_output returns bytes in PYTHON3. - output = output.decode() - return output.rstrip('\n') - - -def clean_boost_name(name): - name = name.split('.')[0] - if name.startswith('lib'): - name = name[3:] - return name - - -def find_boost_library(_id): - suffixes = [ - "", # standard naming - "-mt" # former naming schema for multithreading build - ] - if "python" in _id: - # Debian naming convention for versions installed in parallel - suffixes.insert(0, "-py%d%d" % (sys.version_info.major, - sys.version_info.minor)) - suffixes.insert(1, "%d%d" % (sys.version_info.major, - sys.version_info.minor)) - # standard suffix for Python3 - suffixes.insert(2, sys.version_info.major) - for suf in suffixes: - name = "%s%s" % (_id, suf) - lib = find_library(name) - if lib is not None: - return name - - -def get_boost_library_names(): - wanted = ['boost_python', 'boost_thread', 'boost_system'] - found = [] - missing = [] - for _id in wanted: - name = os.environ.get("%s_LIB" % _id.upper(), find_boost_library(_id)) - if name: - found.append(name) - else: - missing.append(_id) - if missing: - msg = "" - for name in missing: - msg += ("\nMissing {} boost library, try to add its name with " - "{}_LIB environment var.").format(name, name.upper()) - raise EnvironmentError(msg) - return found - - -class WhichBoostCommand(Command): - description = 'Output found boost names. Useful for debug.' - user_options = [] - - def initialize_options(self): - pass - - def finalize_options(self): - pass - - def run(self): - print("\n".join(get_boost_library_names())) - - -cflags = sysconfig.get_config_var('CFLAGS') -sysconfig._config_vars['CFLAGS'] = re.sub( - ' +', ' ', cflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -opt = sysconfig.get_config_var('OPT') -sysconfig._config_vars['OPT'] = re.sub( - ' +', ' ', opt.replace('-g ', '').replace('-Os', '')) -ldshared = sysconfig.get_config_var('LDSHARED') -sysconfig._config_vars['LDSHARED'] = re.sub( - ' +', ' ', ldshared.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -ldflags = sysconfig.get_config_var('LDFLAGS') -sysconfig._config_vars['LDFLAGS'] = re.sub( - ' +', ' ', ldflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -pycflags = sysconfig.get_config_var('PY_CFLAGS') -sysconfig._config_vars['PY_CFLAGS'] = re.sub( - ' +', ' ', pycflags.replace('-g ', '').replace('-Os', '').replace('-arch i386', '')) -sysconfig._config_vars['CFLAGSFORSHARED'] = '' -os.environ['ARCHFLAGS'] = '' - -if os.environ.get("MASON_BUILD", "false") == "true": - # run bootstrap.sh to get mason builds - subprocess.call(['./bootstrap.sh']) - mapnik_config = 'mason_packages/.link/bin/mapnik-config' - mason_build = True -else: - mapnik_config = 'mapnik-config' - mason_build = False - - -linkflags = [] -lib_path = os.path.join(check_output([mapnik_config, '--prefix']),'lib') -linkflags.extend(check_output([mapnik_config, '--libs']).split(' ')) -linkflags.extend(check_output([mapnik_config, '--ldflags']).split(' ')) -linkflags.extend(check_output([mapnik_config, '--dep-libs']).split(' ')) -linkflags.extend([ -'-lmapnik-wkt', -'-lmapnik-json', -] + ['-l%s' % i for i in get_boost_library_names()]) - -# Dynamically make the mapnik/paths.py file -f_paths = open('mapnik/paths.py', 'w') -f_paths.write('import os\n') -f_paths.write('\n') - -input_plugin_path = check_output([mapnik_config, '--input-plugins']) -font_path = check_output([mapnik_config, '--fonts']) - -if mason_build: - try: - if sys.platform == 'darwin': - base_f = 'libmapnik.dylib' - else: - base_f = 'libmapnik.so' - f = os.path.join(lib_path, base_f) - if not os.path.exists(os.path.join('mapnik', 'lib')): - os.makedirs(os.path.join('mapnik', 'lib')) - shutil.copyfile(f, os.path.join('mapnik', 'lib', base_f)) - except shutil.Error: - pass - input_plugin_files = os.listdir(input_plugin_path) - input_plugin_files = [os.path.join( - input_plugin_path, f) for f in input_plugin_files] - if not os.path.exists(os.path.join('mapnik', 'lib', 'mapnik', 'input')): - os.makedirs(os.path.join('mapnik', 'lib', 'mapnik', 'input')) - for f in input_plugin_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', 'lib', 'mapnik', 'input', os.path.basename(f))) - except shutil.Error: - pass - font_files = os.listdir(font_path) - font_files = [os.path.join(font_path, f) for f in font_files] - if not os.path.exists(os.path.join('mapnik', 'lib', 'mapnik', 'fonts')): - os.makedirs(os.path.join('mapnik', 'lib', 'mapnik', 'fonts')) - for f in font_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', 'lib', 'mapnik', 'fonts', os.path.basename(f))) - except shutil.Error: - pass - f_paths.write( - 'mapniklibpath = os.path.join(os.path.dirname(os.path.realpath(__file__)), "lib")\n') - f_paths.write("inputpluginspath = os.path.join(mapniklibpath, 'mapnik', 'input')\n") - f_paths.write("fontscollectionpath = os.path.join(mapniklibpath, 'mapnik', 'fonts')\n") - f_paths.write( - "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n") - f_paths.close() -else: - if os.environ.get('LIB_DIR_NAME'): - mapnik_lib_path = lib_path + os.environ.get('LIB_DIR_NAME') - else: - mapnik_lib_path = lib_path + "/mapnik" - f_paths.write("mapniklibpath = '{path}'\n".format(path=mapnik_lib_path)) - f_paths.write('mapniklibpath = os.path.normpath(mapniklibpath)\n') - f_paths.write( - "inputpluginspath = '{path}'\n".format(path=input_plugin_path)) - f_paths.write( - "fontscollectionpath = '{path}'\n".format(path=font_path)) - f_paths.write( - "__all__ = [mapniklibpath,inputpluginspath,fontscollectionpath]\n") - f_paths.close() - - -if mason_build: - - share_dir = 'share' - - for dep in ['icu','gdal','proj']: - share_path = os.path.join('mapnik', share_dir, dep) - if not os.path.exists(share_path): - os.makedirs(share_path) - - icu_path = 'mason_packages/.link/share/icu/*/*.dat' - icu_files = glob.glob(icu_path) - if len(icu_files) != 1: - raise Exception("Failed to find icu dat file at "+ icu_path) - for f in icu_files: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'icu', os.path.basename(f))) - - gdal_path = 'mason_packages/.link/share/gdal/' - gdal_files = os.listdir(gdal_path) - gdal_files = [os.path.join(gdal_path, f) for f in gdal_files] - for f in gdal_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'gdal', os.path.basename(f))) - except shutil.Error: - pass - - proj_path = 'mason_packages/.link/share/proj/' - proj_files = os.listdir(proj_path) - proj_files = [os.path.join(proj_path, f) for f in proj_files] - for f in proj_files: - try: - shutil.copyfile(f, os.path.join( - 'mapnik', share_dir, 'proj', os.path.basename(f))) - except shutil.Error: - pass - -extra_comp_args = check_output([mapnik_config, '--cflags']).split(' ') - -extra_comp_args = list(filter(lambda arg: arg != "-fvisibility=hidden", extra_comp_args)) - -if os.environ.get("PYCAIRO", "false") == "true": - try: - extra_comp_args.append('-DHAVE_PYCAIRO') - print("-I%s/include/pycairo".format(sys.exec_prefix)) - extra_comp_args.append("-I{0}/include/pycairo".format(sys.exec_prefix)) - #extra_comp_args.extend(check_output(["pkg-config", '--cflags', 'pycairo']).strip().split(' ')) - #linkflags.extend(check_output(["pkg-config", '--libs', 'pycairo']).strip().split(' ')) - except: - raise Exception("Failed to find compiler options for pycairo") - -if sys.platform == 'darwin': - extra_comp_args.append('-mmacosx-version-min=10.11') - # silence warning coming from boost python macros which - # would is hard to silence via pragma - extra_comp_args.append('-Wno-parentheses-equality') - linkflags.append('-mmacosx-version-min=10.11') -else: - linkflags.append('-lrt') - linkflags.append('-Wl,-z,origin') - linkflags.append('-Wl,-rpath=$ORIGIN/lib') - -if os.environ.get("CC", False) == False: - os.environ["CC"] = check_output([mapnik_config, '--cxx']) -if os.environ.get("CXX", False) == False: - os.environ["CXX"] = check_output([mapnik_config, '--cxx']) - -setup( - name="mapnik", - version="4.0.0", - packages=['mapnik','mapnik.printing'], - author="Blake Thompson", - author_email="flippmoke@gmail.com", - description="Python bindings for Mapnik", - license="GNU LESSER GENERAL PUBLIC LICENSE", - keywords="mapnik mapbox mapping cartography", - url="http://mapnik.org/", - tests_require=[ - 'nose', - ], - package_data={ - 'mapnik': ['lib/*.*', 'lib/*/*/*', 'share/*/*'], - }, - test_suite='nose.collector', - cmdclass={ - 'whichboost': WhichBoostCommand, - }, - ext_modules=[ - Extension('mapnik._mapnik', [ - 'src/mapnik_color.cpp', - 'src/mapnik_coord.cpp', - 'src/mapnik_datasource.cpp', - 'src/mapnik_datasource_cache.cpp', - 'src/mapnik_envelope.cpp', - 'src/mapnik_expression.cpp', - 'src/mapnik_feature.cpp', - 'src/mapnik_featureset.cpp', - 'src/mapnik_font_engine.cpp', - 'src/mapnik_fontset.cpp', - 'src/mapnik_gamma_method.cpp', - 'src/mapnik_geometry.cpp', - 'src/mapnik_grid.cpp', - 'src/mapnik_grid_view.cpp', - 'src/mapnik_image.cpp', - 'src/mapnik_image_view.cpp', - 'src/mapnik_label_collision_detector.cpp', - 'src/mapnik_layer.cpp', - 'src/mapnik_logger.cpp', - 'src/mapnik_map.cpp', - 'src/mapnik_palette.cpp', - 'src/mapnik_parameters.cpp', - 'src/mapnik_proj_transform.cpp', - 'src/mapnik_projection.cpp', - 'src/mapnik_python.cpp', - 'src/mapnik_query.cpp', - 'src/mapnik_raster_colorizer.cpp', - 'src/mapnik_rule.cpp', - 'src/mapnik_scaling_method.cpp', - 'src/mapnik_style.cpp', - 'src/mapnik_symbolizer.cpp', - 'src/mapnik_view_transform.cpp', - 'src/python_grid_utils.cpp', - ], - language='c++', - extra_compile_args=extra_comp_args, - extra_link_args=linkflags, - ) - ] -) diff --git a/vcpkg.json b/vcpkg.json new file mode 100644 index 000000000..b0cb1f643 --- /dev/null +++ b/vcpkg.json @@ -0,0 +1,12 @@ +{ + "name": "python-mapnik", + "dependencies": [ + { + "name": "boost-python", + "features": [ + "python3" + ] + }, + "mapnik" + ] +}