Move header into the package and add a get_include() function. See #92
Pycairo installs .pc files which work quite well where the default prefix is used, like with distro packaging etc. In virtualenvs the pkg-config look up path needs to be set manually, and in pip wheels are involved, where we had to disable installing .pc files as they can get reused for a different prefix. To make it easier for other python modules to use the C API introduce a new function get_include() (similar ot what numpy has) which returns the include path that needs to be passed to the compiler. Since we can't really get the old header install path from the module (one could walk up the tree and look for matching files, but that's ugly) move the header file into the package itself, so that the path can be dervived from the package path. To prevent code from breaking which hardcodes the old include path install a header to the old location which includes the new header location.
This commit is contained in:
parent
47aebd2073
commit
fa0707e565
|
@ -1,5 +1,17 @@
|
|||
environment:
|
||||
matrix:
|
||||
- MSYS2_ARCH: x86_64
|
||||
MSYSTEM: MINGW64
|
||||
PYTHON: python2
|
||||
- MSYS2_ARCH: i686
|
||||
MSYSTEM: MINGW32
|
||||
PYTHON: python2
|
||||
- MSYS2_ARCH: x86_64
|
||||
MSYSTEM: MINGW64
|
||||
PYTHON: python3
|
||||
- MSYS2_ARCH: i686
|
||||
MSYSTEM: MINGW32
|
||||
PYTHON: python3
|
||||
- MSVC_PLATFORM: x86
|
||||
PYTHON_ROOT: Python27
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2013
|
||||
|
@ -18,24 +30,12 @@ environment:
|
|||
- MSVC_PLATFORM: x64
|
||||
PYTHON_ROOT: Python36-x64
|
||||
APPVEYOR_BUILD_WORKER_IMAGE: Visual Studio 2017
|
||||
- MSYS2_ARCH: x86_64
|
||||
MSYSTEM: MINGW64
|
||||
PYTHON: python2
|
||||
- MSYS2_ARCH: i686
|
||||
MSYSTEM: MINGW32
|
||||
PYTHON: python2
|
||||
- MSYS2_ARCH: x86_64
|
||||
MSYSTEM: MINGW64
|
||||
PYTHON: python3
|
||||
- MSYS2_ARCH: i686
|
||||
MSYSTEM: MINGW32
|
||||
PYTHON: python3
|
||||
|
||||
build_script:
|
||||
- IF DEFINED MSYSTEM set PATH=C:\msys64\%MSYSTEM%\bin;C:\msys64\usr\bin;%PATH%
|
||||
- IF DEFINED MSYSTEM set CHERE_INVOKING=yes
|
||||
- IF DEFINED MSYSTEM bash -lc "bash .appveyor/msys2-pre.sh"
|
||||
- IF DEFINED MSYSTEM bash -lc "bash .appveyor/msys2.sh"
|
||||
- IF DEFINED MSYSTEM bash -lc "bash -x .appveyor/msys2-pre.sh"
|
||||
- IF DEFINED MSYSTEM bash -lc "bash -x .appveyor/msys2.sh"
|
||||
- IF DEFINED MSVC_PLATFORM ".appveyor/msvc.bat"
|
||||
|
||||
deploy: off
|
||||
|
|
|
@ -10,6 +10,7 @@ export CFLAGS="-std=c90 -Wall -Wno-long-long -Werror -coverage"
|
|||
$PYTHON -m coverage run --branch setup.py test
|
||||
$PYTHON -m codecov
|
||||
$PYTHON setup.py sdist
|
||||
$PYTHON setup.py install --root="$(pwd)"/_root_abs
|
||||
$PYTHON -m pip install dist/*
|
||||
|
||||
# Also test with older cairo
|
||||
|
|
|
@ -67,5 +67,12 @@ script:
|
|||
- python -m codecov
|
||||
- python -m flake8 .
|
||||
- python setup.py sdist
|
||||
- if [[ "$TRAVIS_PYTHON_VERSION" != "pypy" ]] && [[ "$TRAVIS_PYTHON_VERSION" != "pypy3" ]]; then python -m pip install "$(eval 'echo dist/*')"; fi
|
||||
- python setup.py bdist
|
||||
- python setup.py install --root=_root
|
||||
- python setup.py install --root="$(pwd)"/_root_abs
|
||||
- python setup.py install --user
|
||||
- PYCAIRO_SETUPTOOLS=1 python setup.py bdist_egg
|
||||
- PYCAIRO_SETUPTOOLS=1 python setup.py bdist_wheel
|
||||
- PYCAIRO_SETUPTOOLS=1 python setup.py install --root=_root_setup
|
||||
- if [[ "$TRAVIS_PYTHON_VERSION" != "pypy" ]] && [[ "$TRAVIS_PYTHON_VERSION" != "pypy3" ]]; then python -m pip install .; fi
|
||||
- if [[ "$TRAVIS_PYTHON_VERSION" != "3.3" ]]; then python -m sphinx -W -a -E -b html -n docs docs/_build; fi
|
||||
|
|
|
@ -1 +1,25 @@
|
|||
from ._cairo import * # noqa: F401,F403
|
||||
|
||||
|
||||
def get_include():
|
||||
"""Returns a path to the directory containing the C header files"""
|
||||
|
||||
import os
|
||||
|
||||
def is_ok(path):
|
||||
return os.path.exists(path) and os.path.isdir(path)
|
||||
|
||||
package_path = os.path.dirname(os.path.realpath(__file__))
|
||||
install_path = os.path.join(package_path, "include")
|
||||
|
||||
# in case we are installed
|
||||
if is_ok(install_path):
|
||||
return install_path
|
||||
|
||||
# in case we are running from source
|
||||
if is_ok(package_path):
|
||||
return package_path
|
||||
|
||||
# in case we are in an .egg
|
||||
import pkg_resources
|
||||
return pkg_resources.resource_filename(__name__, "include")
|
||||
|
|
|
@ -11,6 +11,41 @@ This manual documents the API used by C and C++ programmers who want to write
|
|||
extension modules that use pycairo.
|
||||
|
||||
|
||||
Pycairo Compiler Flags
|
||||
======================
|
||||
|
||||
To compile a Python extension using Pycairo you need to know where Pycairo and
|
||||
cairo are located and what flags to pass to the compiler and linker.
|
||||
|
||||
1. Variant:
|
||||
|
||||
Only available since version 1.15.7.
|
||||
|
||||
While Pycairo installs a pkg-config file, in case of virtualenvs,
|
||||
installation to the user directory or when using wheels/eggs, pkg-config
|
||||
will not be able to locate the .pc file. The :func:`get_include` function
|
||||
should work in all cases, as long as Pycairo is in your Python search path.
|
||||
|
||||
Compiler Flags:
|
||||
* ``python -c "import cairo; print(cairo.get_include())"``
|
||||
* ``pkg-config --cflags cairo``
|
||||
|
||||
Linker Flags:
|
||||
* ``pkg-config --libs cairo``
|
||||
|
||||
2. Variant:
|
||||
|
||||
This works with older versions, but with the limitations mentioned above.
|
||||
Use it as a fallback if you want to support older versions or if your
|
||||
module does not require virtualenv/pip support.
|
||||
|
||||
Compiler Flags:
|
||||
* ``pkg-config --cflags pycairo`` or ``pkg-config --cflags py3cairo``
|
||||
|
||||
Linker Flags:
|
||||
* ``pkg-config --libs pycairo`` or ``pkg-config --libs py3cairo``
|
||||
|
||||
|
||||
.. _api-includes:
|
||||
|
||||
To access the Pycairo C API under Python 2
|
||||
|
@ -21,7 +56,7 @@ Edit the client module file to add the following lines::
|
|||
/* All function, type and macro definitions needed to use the Pycairo/C API
|
||||
* are included in your code by the following line
|
||||
*/
|
||||
#include "Pycairo.h"
|
||||
#include "pycairo.h"
|
||||
|
||||
/* define a variable for the C API */
|
||||
static Pycairo_CAPI_t *Pycairo_CAPI;
|
||||
|
@ -52,21 +87,6 @@ Example showing how to import the pycairo API::
|
|||
}
|
||||
|
||||
|
||||
Pkg-Config Setup
|
||||
================
|
||||
|
||||
pycairo installs "pycairo.pc" or "py3cairo.pc" in case of a Python 3 install:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
> pkg-config --libs --cflags pycairo
|
||||
> pkg-config --libs --cflags py3cairo
|
||||
-I/usr/include/cairo -I/usr/include/glib-2.0
|
||||
-I/usr/lib/x86_64-linux-gnu/glib-2.0/include -I/usr/include/pixman-1
|
||||
-I/usr/include/freetype2 -I/usr/include/libpng16 -I/usr/include/pycairo
|
||||
-lcairo
|
||||
|
||||
|
||||
Misc Functions
|
||||
==============
|
||||
|
||||
|
|
|
@ -26,6 +26,15 @@ Module Functions
|
|||
Returns the version of the underlying C cairo library as a human-readable
|
||||
string of the form "X.Y.Z".
|
||||
|
||||
.. function:: get_include()
|
||||
|
||||
:returns: a path to the directory containing the C header files
|
||||
:rtype: str
|
||||
|
||||
Gives the include path which should be passed to the compiler.
|
||||
|
||||
.. versionadded:: 1.15.7
|
||||
|
||||
|
||||
Module Constants
|
||||
================
|
||||
|
|
150
setup.py
150
setup.py
|
@ -5,10 +5,15 @@ import subprocess
|
|||
import sys
|
||||
import os
|
||||
import errno
|
||||
import warnings
|
||||
|
||||
if os.environ.get("PYCAIRO_SETUPTOOLS"):
|
||||
# for testing
|
||||
import setuptools
|
||||
setuptools
|
||||
|
||||
from distutils.core import Extension, setup, Command, Distribution
|
||||
from distutils.ccompiler import new_compiler
|
||||
from distutils import log
|
||||
|
||||
|
||||
PYCAIRO_VERSION = '1.15.7'
|
||||
|
@ -87,14 +92,22 @@ class install_pkgconfig(Command):
|
|||
def initialize_options(self):
|
||||
self.install_base = None
|
||||
self.install_data = None
|
||||
self.install_lib = None
|
||||
self.root = None
|
||||
self.compiler_type = None
|
||||
self.outfiles = []
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
'install_lib',
|
||||
('install_base', 'install_base'),
|
||||
('install_data', 'install_data'),
|
||||
('install_lib', 'install_lib'),
|
||||
)
|
||||
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('root', 'root'),
|
||||
)
|
||||
|
||||
self.set_undefined_options(
|
||||
|
@ -116,13 +129,20 @@ class install_pkgconfig(Command):
|
|||
# wrong paths. So in case bdist_wheel is used, just skip this command.
|
||||
cmd = self.distribution.get_command_obj("bdist_wheel", create=False)
|
||||
if cmd is not None:
|
||||
warnings.warn(
|
||||
"Python wheels and pkg-config is not compatible. "
|
||||
"No pkg-config file will be included in the wheel. Install "
|
||||
"from source if you need one.")
|
||||
log.info(
|
||||
"Skipping install_pkgconfig, not supported with bdist_wheel")
|
||||
return
|
||||
|
||||
# same for bdist_egg
|
||||
cmd = self.distribution.get_command_obj("bdist_egg", create=False)
|
||||
if cmd is not None:
|
||||
log.info(
|
||||
"Skipping install_pkgconfig, not supported with bdist_egg")
|
||||
return
|
||||
|
||||
if self.compiler_type == "msvc":
|
||||
log.info(
|
||||
"Skipping install_pkgconfig, not supported with MSVC")
|
||||
return
|
||||
|
||||
pkgconfig_dir = os.path.join(self.install_data, "share", "pkgconfig")
|
||||
|
@ -133,6 +153,17 @@ class install_pkgconfig(Command):
|
|||
else:
|
||||
target = os.path.join(pkgconfig_dir, "pycairo.pc")
|
||||
|
||||
# figure out the package path relative to the prefix
|
||||
lib = self.install_lib
|
||||
if self.root is not None:
|
||||
lib = self.install_lib[len(self.root):]
|
||||
rel_lib = os.path.relpath(lib, self.install_base)
|
||||
|
||||
rel_include_dir = os.path.join(rel_lib, "cairo", "include")
|
||||
|
||||
log.info("Writing %s" % target)
|
||||
log.info("pkg-config prefix: %s" % self.install_base)
|
||||
log.info("pkg-config include: ${prefix}/%s" % rel_include_dir)
|
||||
with open(target, "wb") as h:
|
||||
h.write((u"""\
|
||||
prefix=%(prefix)s
|
||||
|
@ -141,24 +172,97 @@ Name: Pycairo
|
|||
Description: Python %(py_version)d bindings for cairo
|
||||
Version: %(version)s
|
||||
Requires: cairo
|
||||
Cflags: -I${prefix}/include/pycairo
|
||||
Cflags: -I${prefix}/%(include)s
|
||||
Libs:
|
||||
""" % {
|
||||
"prefix": self.install_base,
|
||||
"version": PYCAIRO_VERSION,
|
||||
"py_version": sys.version_info[0]}).encode("utf-8"))
|
||||
"py_version": sys.version_info[0],
|
||||
"include": rel_include_dir}).encode("utf-8"))
|
||||
|
||||
self.outfiles.append(target)
|
||||
|
||||
|
||||
du_install = get_command_class("install")
|
||||
class install_pycairo_header(Command):
|
||||
description = "install pycairo header"
|
||||
user_options = []
|
||||
|
||||
def initialize_options(self):
|
||||
self.install_data = None
|
||||
self.install_lib = None
|
||||
self.force = None
|
||||
self.outfiles = []
|
||||
|
||||
def finalize_options(self):
|
||||
self.set_undefined_options(
|
||||
'install_lib',
|
||||
('install_data', 'install_data'),
|
||||
('install_lib', 'install_lib'),
|
||||
)
|
||||
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('force', 'force'),
|
||||
)
|
||||
|
||||
def get_outputs(self):
|
||||
return self.outfiles
|
||||
|
||||
def get_inputs(self):
|
||||
return [os.path.join('cairo', 'pycairo.h')]
|
||||
|
||||
def run(self):
|
||||
# https://github.com/pygobject/pycairo/issues/92
|
||||
hname = 'py3cairo.h' if sys.version_info[0] == 3 else 'pycairo.h'
|
||||
source = self.get_inputs()[0]
|
||||
|
||||
lib_hdir = os.path.join(
|
||||
self.install_lib, "cairo", "include")
|
||||
self.mkpath(lib_hdir)
|
||||
header_path = os.path.join(lib_hdir, hname)
|
||||
(out, _) = self.copy_file(source, header_path)
|
||||
self.outfiles.append(out)
|
||||
|
||||
# install a simple header including the new one in the old location,
|
||||
# in case some code has hardcoded the old include path.
|
||||
data_hdir = os.path.join(self.install_data, "include", "pycairo")
|
||||
rel_include_path = os.path.normpath(
|
||||
os.path.relpath(header_path, data_hdir)).replace("\\\\", "/")
|
||||
back_comp_path = os.path.join(data_hdir, hname)
|
||||
self.mkpath(data_hdir)
|
||||
|
||||
log.info("Writing %s" % back_comp_path)
|
||||
with io.open(back_comp_path, "w", encoding="utf-8") as h:
|
||||
h.write(u"/* the header has moved, use pkg-config */\n")
|
||||
h.write(u"#include \"%s\"\n" % rel_include_path)
|
||||
self.outfiles.append(back_comp_path)
|
||||
|
||||
|
||||
class install(du_install):
|
||||
du_install_lib = get_command_class("install_lib")
|
||||
|
||||
sub_commands = du_install.sub_commands + [
|
||||
("install_pkgconfig", lambda self: True),
|
||||
]
|
||||
|
||||
class install_lib(du_install_lib):
|
||||
|
||||
def initialize_options(self):
|
||||
self.install_base = None
|
||||
self.install_lib = None
|
||||
self.install_data = None
|
||||
du_install_lib.initialize_options(self)
|
||||
|
||||
def finalize_options(self):
|
||||
du_install_lib.finalize_options(self)
|
||||
self.set_undefined_options(
|
||||
'install',
|
||||
('install_base', 'install_base'),
|
||||
('install_lib', 'install_lib'),
|
||||
('install_data', 'install_data'),
|
||||
)
|
||||
|
||||
def run(self):
|
||||
du_install_lib.run(self)
|
||||
# bdist_egg doesn't run install, so run our commands here instead
|
||||
self.run_command("install_pkgconfig")
|
||||
self.run_command("install_pycairo_header")
|
||||
|
||||
|
||||
du_build_ext = get_command_class("build_ext")
|
||||
|
@ -272,18 +376,6 @@ class build(du_build):
|
|||
self.enable_xpyb = bool(self.enable_xpyb)
|
||||
|
||||
|
||||
du_install_data = get_command_class("install_data")
|
||||
|
||||
|
||||
class install_data(du_install_data):
|
||||
|
||||
def copy_file(self, src, dst, *args, **kwargs):
|
||||
# XXX: rename target on the fly. ugly, but works
|
||||
if os.path.basename(src) == "pycairo.h" and sys.version_info[0] == 3:
|
||||
dst = os.path.join(dst, "py3cairo.h")
|
||||
return du_install_data.copy_file(self, src, dst, *args, **kwargs)
|
||||
|
||||
|
||||
def main():
|
||||
|
||||
cairo_ext = Extension(
|
||||
|
@ -325,9 +417,9 @@ def main():
|
|||
cmdclass = {
|
||||
"build": build,
|
||||
"build_ext": build_ext,
|
||||
"install": install,
|
||||
"install_lib": install_lib,
|
||||
"install_pkgconfig": install_pkgconfig,
|
||||
"install_data": install_data,
|
||||
"install_pycairo_header": install_pycairo_header,
|
||||
"test": test_cmd,
|
||||
}
|
||||
|
||||
|
@ -355,10 +447,6 @@ def main():
|
|||
'GNU Lesser General Public License v2 (LGPLv2)'),
|
||||
'License :: OSI Approved :: Mozilla Public License 1.1 (MPL 1.1)',
|
||||
],
|
||||
data_files=[
|
||||
('include/pycairo', ['cairo/pycairo.h']),
|
||||
],
|
||||
zip_safe=False,
|
||||
cmdclass=cmdclass,
|
||||
)
|
||||
|
||||
|
|
|
@ -17,6 +17,13 @@ import cairo
|
|||
import pytest
|
||||
|
||||
|
||||
def test_get_include():
|
||||
include = cairo.get_include()
|
||||
assert isinstance(include, str)
|
||||
assert os.path.exists(include)
|
||||
assert os.path.isdir(include)
|
||||
|
||||
|
||||
def test_version():
|
||||
cairo.cairo_version()
|
||||
cairo.cairo_version_string()
|
||||
|
|
Loading…
Reference in New Issue