2 # Copyright 2017 Christoph Reiter <reiter.christoph@gmail.com>
4 # This library is free software; you can redistribute it and/or
5 # modify it under the terms of the GNU Lesser General Public
6 # License as published by the Free Software Foundation; either
7 # version 2.1 of the License, or (at your option) any later version.
9 # This library is distributed in the hope that it will be useful,
10 # but WITHOUT ANY WARRANTY; without even the implied warranty of
11 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 # Lesser General Public License for more details.
14 # You should have received a copy of the GNU Lesser General Public
15 # License along with this library; if not, write to the Free Software
16 # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301
29 from email import parser
32 from setuptools import setup
34 from distutils.core import setup
36 from distutils.core import Extension, Distribution, Command
37 from distutils.errors import DistutilsSetupError, DistutilsOptionError
38 from distutils.ccompiler import new_compiler
39 from distutils.sysconfig import get_python_lib, customize_compiler
40 from distutils import dir_util, log
41 from distutils.spawn import find_executable
44 PYGOBJECT_VERSION = "3.32.1"
45 GLIB_VERSION_REQUIRED = "2.48.0"
46 GI_VERSION_REQUIRED = "1.46.0"
47 PYCAIRO_VERSION_REQUIRED = "1.11.1"
48 LIBFFI_VERSION_REQUIRED = "3.0"
50 WITH_CAIRO = not bool(os.environ.get("PYGOBJECT_WITHOUT_PYCAIRO"))
51 """Set PYGOBJECT_WITHOUT_PYCAIRO if you don't want to build with
52 cairo/pycairo support. Note that this option might get removed in the future.
57 version = tuple(map(int, PYGOBJECT_VERSION.split(".")))
58 return version[1] % 2 != 0
61 def get_command_class(name):
62 # Returns the right class for either distutils or setuptools
63 return Distribution({}).get_command_class(name)
66 def get_pycairo_pkg_config_name():
67 return "py3cairo" if sys.version_info[0] == 3 else "pycairo"
70 def get_version_requirement(pkg_config_name):
71 """Given a pkg-config module name gets the minimum version required"""
74 "gobject-introspection-1.0": GI_VERSION_REQUIRED,
75 "glib-2.0": GLIB_VERSION_REQUIRED,
76 "gio-2.0": GLIB_VERSION_REQUIRED,
77 get_pycairo_pkg_config_name(): PYCAIRO_VERSION_REQUIRED,
78 "libffi": LIBFFI_VERSION_REQUIRED,
83 return versions[pkg_config_name]
87 version = PYGOBJECT_VERSION.split(".")
88 assert len(version) == 3
91 "PYGOBJECT_MAJOR_VERSION": version[0],
92 "PYGOBJECT_MINOR_VERSION": version[1],
93 "PYGOBJECT_MICRO_VERSION": version[2],
94 "VERSION": ".".join(version),
99 def parse_pkg_info(conf_dir):
100 """Returns an email.message.Message instance containing the content
101 of the PKG-INFO file.
104 versions = get_versions()
106 pkg_info = os.path.join(conf_dir, "PKG-INFO.in")
107 with io.open(pkg_info, "r", encoding="utf-8") as h:
109 for key, value in versions.items():
110 text = text.replace("@%s@" % key, value)
113 message = p.parse(io.StringIO(text))
117 def pkg_config_get_install_hint():
118 """Returns an installation hint for installing pkg-config or None"""
120 if not sys.platform.startswith("linux"):
123 if find_executable("apt"):
124 return "sudo apt install pkg-config"
125 elif find_executable("dnf"):
126 return "sudo dnf install pkg-config"
129 def pkg_config_get_package_install_hint(pkg_name):
130 """Returns an installation hint for a pkg-config name or None"""
132 if not sys.platform.startswith("linux"):
135 if find_executable("apt"):
137 "gobject-introspection-1.0": "libgirepository1.0-dev",
138 "glib-2.0": "libglib2.0-dev",
139 "gio-2.0": "libglib2.0-dev",
140 "cairo": "libcairo2-dev",
141 "cairo-gobject": "libcairo2-dev",
142 "libffi": "libffi-dev",
144 if pkg_name in dev_packages:
145 return "sudo apt install %s" % dev_packages[pkg_name]
146 elif find_executable("dnf"):
148 "gobject-introspection-1.0": "gobject-introspection-devel",
149 "glib-2.0": "glib2-devel",
150 "gio-2.0": "glib2-devel",
151 "cairo": "cairo-devel",
152 "cairo-gobject": "cairo-gobject-devel",
153 "libffi": "libffi-devel",
155 if pkg_name in dev_packages:
156 return "sudo dnf install %s" % dev_packages[pkg_name]
159 class PkgConfigError(Exception):
163 class PkgConfigMissingError(PkgConfigError):
167 class PkgConfigMissingPackageError(PkgConfigError):
171 def _run_pkg_config(pkg_name, args, _cache={}):
172 """Raises PkgConfigError"""
174 command = tuple(["pkg-config"] + args)
176 if command not in _cache:
178 result = subprocess.check_output(command)
180 if e.errno == errno.ENOENT:
181 raise PkgConfigMissingError(
182 "%r not found.\nArguments: %r" % (command[0], command))
183 raise PkgConfigError(e)
184 except subprocess.CalledProcessError as e:
186 subprocess.check_output(["pkg-config", "--exists", pkg_name])
187 except (subprocess.CalledProcessError, OSError):
188 raise PkgConfigMissingPackageError(e)
190 raise PkgConfigError(e)
192 _cache[command] = result
194 return _cache[command]
197 def _run_pkg_config_or_exit(pkg_name, args):
199 return _run_pkg_config(pkg_name, args)
200 except PkgConfigMissingError as e:
201 hint = pkg_config_get_install_hint()
204 "%s\n\nTry installing it with: %r" % (e, hint))
207 except PkgConfigMissingPackageError as e:
208 hint = pkg_config_get_package_install_hint(pkg_name)
211 "%s\n\nTry installing it with: %r" % (e, hint))
214 except PkgConfigError as e:
218 def pkg_config_version_check(pkg_name, version):
219 _run_pkg_config_or_exit(pkg_name, [
222 '%s >= %s' % (pkg_name, version),
226 def pkg_config_parse(opt, pkg_name):
227 ret = _run_pkg_config_or_exit(pkg_name, [opt, pkg_name])
229 if sys.version_info[0] == 3:
230 output = ret.decode()
234 return [x.lstrip(opt) for x in output.split()]
238 return [os.path.join(d, e) for e in os.listdir(d) if e.endswith(".h")]
241 def filter_compiler_arguments(compiler, args):
242 """Given a compiler instance and a list of compiler warning flags
243 returns the list of supported flags.
246 if compiler.compiler_type == "msvc":
247 # TODO, not much of need for now.
252 def check_arguments(compiler, args):
253 p = subprocess.Popen(
254 [compiler.compiler[0]] + args + extra + ["-x", "c", "-E", "-"],
255 stdin=subprocess.PIPE,
256 stdout=subprocess.PIPE,
257 stderr=subprocess.PIPE)
258 stdout, stderr = p.communicate(b"int i;\n")
259 if p.returncode != 0:
260 text = stderr.decode("ascii", "replace")
261 return False, [a for a in args if a in text]
265 def check_argument(compiler, arg):
266 return check_arguments(compiler, [arg])[0]
268 # clang doesn't error out for unknown options, force it to
269 if check_argument(compiler, '-Werror=unknown-warning-option'):
270 extra += ['-Werror=unknown-warning-option']
271 if check_argument(compiler, '-Werror=unused-command-line-argument'):
272 extra += ['-Werror=unused-command-line-argument']
274 # first try to remove all arguments contained in the error message
275 supported = list(args)
277 ok, maybe_unknown = check_arguments(compiler, supported)
280 elif not maybe_unknown:
282 for unknown in maybe_unknown:
283 if not check_argument(compiler, unknown):
284 supported.remove(unknown)
286 # hm, didn't work, try each argument one by one
289 if check_argument(compiler, arg):
290 supported.append(arg)
294 class sdist_gnome(Command):
295 description = "Create a source tarball for GNOME"
298 def initialize_options(self):
301 def finalize_options(self):
305 # Don't use PEP 440 pre-release versions for GNOME releases
306 self.distribution.metadata.version = PYGOBJECT_VERSION
308 dist_dir = tempfile.mkdtemp()
310 cmd = self.reinitialize_command("sdist")
311 cmd.dist_dir = dist_dir
312 cmd.ensure_finalized()
315 base_name = self.distribution.get_fullname().lower()
316 cmd.make_release_tree(base_name, cmd.filelist.files)
318 self.make_archive(base_name, "xztar", base_dir=base_name)
320 dir_util.remove_tree(base_name)
322 dir_util.remove_tree(dist_dir)
325 du_sdist = get_command_class("sdist")
328 class distcheck(du_sdist):
329 """Creates a tarball and does some additional sanity checks such as
330 checking if the tarball includes all files, builds successfully and
331 the tests suite passes.
334 def _check_manifest(self):
335 # make sure MANIFEST.in includes all tracked files
336 assert self.get_archive_files()
338 if subprocess.call(["git", "status"],
339 stdout=subprocess.PIPE,
340 stderr=subprocess.PIPE) != 0:
343 included_files = self.filelist.files
344 assert included_files
346 process = subprocess.Popen(
347 ["git", "ls-tree", "-r", "HEAD", "--name-only"],
348 stdout=subprocess.PIPE, universal_newlines=True)
349 out, err = process.communicate()
350 assert process.returncode == 0
352 tracked_files = out.splitlines()
354 f for f in tracked_files
355 if os.path.basename(f) not in [".gitignore"]]
357 diff = set(tracked_files) - set(included_files)
359 "Not all tracked files included in tarball, check MANIFEST.in",
362 def _check_dist(self):
363 # make sure the tarball builds
364 assert self.get_archive_files()
366 distcheck_dir = os.path.abspath(
367 os.path.join(self.dist_dir, "distcheck"))
368 if os.path.exists(distcheck_dir):
369 dir_util.remove_tree(distcheck_dir)
370 self.mkpath(distcheck_dir)
372 archive = self.get_archive_files()[0]
373 tfile = tarfile.open(archive, "r:gz")
374 tfile.extractall(distcheck_dir)
377 name = self.distribution.get_fullname()
378 extract_dir = os.path.join(distcheck_dir, name)
380 old_pwd = os.getcwd()
381 os.chdir(extract_dir)
383 self.spawn([sys.executable, "setup.py", "build"])
384 self.spawn([sys.executable, "setup.py", "install",
386 os.path.join(distcheck_dir, "prefix"),
388 os.path.join(distcheck_dir, "log.txt"),
390 self.spawn([sys.executable, "setup.py", "test"])
396 self._check_manifest()
400 class build_tests(Command):
401 description = "build test libraries and extensions"
403 ("force", "f", "force a rebuild"),
406 def initialize_options(self):
407 self.build_temp = None
408 self.build_base = None
411 def finalize_options(self):
412 self.set_undefined_options(
414 ('build_temp', 'build_temp'))
415 self.set_undefined_options(
417 ('build_base', 'build_base'))
419 def _newer_group(self, sources, *targets):
422 from distutils.dep_util import newer_group
427 for target in targets:
428 if not newer_group(sources, target):
433 cmd = self.reinitialize_command("build_ext")
435 cmd.force = self.force
436 cmd.ensure_finalized()
439 gidatadir = pkg_config_parse(
440 "--variable=gidatadir", "gobject-introspection-1.0")[0]
441 g_ir_scanner = pkg_config_parse(
442 "--variable=g_ir_scanner", "gobject-introspection-1.0")[0]
443 g_ir_compiler = pkg_config_parse(
444 "--variable=g_ir_compiler", "gobject-introspection-1.0")[0]
446 script_dir = get_script_dir()
447 gi_dir = os.path.join(script_dir, "gi")
448 tests_dir = os.path.join(script_dir, "tests")
449 gi_tests_dir = os.path.join(gidatadir, "tests")
451 schema_xml = os.path.join(tests_dir, "org.gnome.test.gschema.xml")
452 schema_bin = os.path.join(tests_dir, "gschemas.compiled")
453 if self._newer_group([schema_xml], schema_bin):
454 subprocess.check_call([
455 "glib-compile-schemas",
456 "--targetdir=%s" % tests_dir,
457 "--schema-file=%s" % schema_xml,
460 compiler = new_compiler()
461 customize_compiler(compiler)
464 compiler.shared_lib_extension = ".dll"
465 elif sys.platform == "darwin":
466 compiler.shared_lib_extension = ".dylib"
467 if "-bundle" in compiler.linker_so:
468 compiler.linker_so = list(compiler.linker_so)
469 i = compiler.linker_so.index("-bundle")
470 compiler.linker_so[i] = "-dynamiclib"
472 compiler.shared_lib_extension = ".so"
474 if compiler.compiler_type == "msvc":
475 g_ir_scanner_cmd = [sys.executable, g_ir_scanner]
477 g_ir_scanner_cmd = [g_ir_scanner]
481 libname = compiler.shared_object_filename(ext.name)
482 ext_paths = [os.path.join(tests_dir, libname)]
484 if compiler.compiler_type == "msvc":
485 # MSVC: Get rid of the 'lib' prefix and the .dll
486 # suffix from libname, and append .lib so
487 # that we get the right .lib filename to
488 # pass to g-ir-scanner with --library
489 implibname = libname[3:libname.rfind(".dll")] + '.lib'
491 implibname = libname + ".a"
492 ext_paths.append(os.path.join(tests_dir, implibname))
494 if self._newer_group(ext.sources + ext.depends, *ext_paths):
495 # MSVC: We need to define _GI_EXTERN explcitly so that
496 # symbols get exported properly
497 if compiler.compiler_type == "msvc":
498 extra_defines = [('_GI_EXTERN',
499 '__declspec(dllexport)extern')]
502 objects = compiler.compile(
504 output_dir=self.build_temp,
505 include_dirs=ext.include_dirs,
506 macros=ext.define_macros + extra_defines)
509 if compiler.compiler_type == "msvc":
510 postargs = ["-implib:%s" %
511 os.path.join(tests_dir, implibname)]
513 postargs = ["-Wl,--out-implib=%s" %
514 os.path.join(tests_dir, implibname)]
518 compiler.link_shared_object(
520 compiler.shared_object_filename(ext.name),
521 output_dir=tests_dir,
522 libraries=ext.libraries,
523 library_dirs=ext.library_dirs,
524 extra_postargs=postargs)
529 name='libgimarshallingtests',
531 os.path.join(gi_tests_dir, "gimarshallingtests.c"),
532 os.path.join(tests_dir, "gimarshallingtestsextra.c"),
539 os.path.join(gi_tests_dir, "gimarshallingtests.h"),
540 os.path.join(tests_dir, "gimarshallingtestsextra.h"),
543 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
544 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
545 ext_paths = build_ext(ext)
547 # We want to always use POSIX-style paths for g-ir-compiler
548 # because it expects the input .gir file and .typelib file to use
549 # POSIX-style paths, otherwise it fails
550 gir_path = posixpath.join(
551 tests_dir, "GIMarshallingTests-1.0.gir")
552 typelib_path = posixpath.join(
553 tests_dir, "GIMarshallingTests-1.0.typelib")
555 gimarshal_g_ir_scanner_cmd = g_ir_scanner_cmd + [
558 "--namespace=GIMarshallingTests",
560 "--symbol-prefix=gi_marshalling_tests",
563 "--library-path=%s" % tests_dir,
564 "--library=gimarshallingtests",
568 "-I%s" % gi_tests_dir,
570 "--output=%s" % gir_path,
573 if self._newer_group(ext_paths, gir_path):
574 subprocess.check_call(gimarshal_g_ir_scanner_cmd +
575 ext.sources + ext.depends)
577 if self._newer_group([gir_path], typelib_path):
578 subprocess.check_call([
581 "--output=%s" % typelib_path,
586 regress_macros.append(("_GI_DISABLE_CAIRO", "1"))
591 os.path.join(gi_tests_dir, "regress.c"),
592 os.path.join(tests_dir, "regressextra.c"),
598 os.path.join(gi_tests_dir, "regress.h"),
599 os.path.join(tests_dir, "regressextra.h"),
601 define_macros=regress_macros,
603 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
604 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
606 add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo")
607 add_ext_pkg_config_dep(
608 ext, compiler.compiler_type, "cairo-gobject")
609 ext_paths = build_ext(ext)
611 # We want to always use POSIX-style paths for g-ir-compiler
612 # because it expects the input .gir file and .typelib file to use
613 # POSIX-style paths, otherwise it fails
614 gir_path = posixpath.join(tests_dir, "Regress-1.0.gir")
615 typelib_path = posixpath.join(tests_dir, "Regress-1.0.typelib")
616 regress_g_ir_scanner_cmd = g_ir_scanner_cmd + [
619 "--namespace=Regress",
623 "--library-path=%s" % tests_dir,
628 if self._newer_group(ext_paths, gir_path):
630 regress_g_ir_scanner_cmd += ["--include=cairo-1.0"]
631 # MSVC: We don't normally have the pkg-config files for
632 # cairo and cairo-gobject, so use --extra-library
633 # instead of --pkg to pass those to the linker, so that
634 # g-ir-scanner won't fail due to linker errors
635 if compiler.compiler_type == "msvc":
636 regress_g_ir_scanner_cmd += [
637 "--extra-library=cairo",
638 "--extra-library=cairo-gobject"]
641 regress_g_ir_scanner_cmd += [
643 "--pkg=cairo-gobject"]
645 regress_g_ir_scanner_cmd += ["-D_GI_DISABLE_CAIRO"]
647 regress_g_ir_scanner_cmd += ["--output=%s" % gir_path]
649 subprocess.check_call(regress_g_ir_scanner_cmd +
650 ext.sources + ext.depends)
652 if self._newer_group([gir_path], typelib_path):
653 subprocess.check_call([
656 "--output=%s" % typelib_path,
660 name='tests.testhelper',
662 os.path.join(tests_dir, "testhelpermodule.c"),
663 os.path.join(tests_dir, "test-floating.c"),
664 os.path.join(tests_dir, "test-thread.c"),
665 os.path.join(tests_dir, "test-unknown.c"),
671 depends=list_headers(gi_dir) + list_headers(tests_dir),
672 define_macros=[("PY_SSIZE_T_CLEAN", None)],
674 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
675 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
676 add_ext_compiler_flags(ext, compiler)
678 dist = Distribution({"ext_modules": [ext]})
680 build_cmd = dist.get_command_obj("build")
681 build_cmd.build_base = os.path.join(self.build_base, "pygobject_tests")
682 build_cmd.ensure_finalized()
684 cmd = dist.get_command_obj("build_ext")
686 cmd.force = self.force
687 cmd.ensure_finalized()
691 def get_suppression_files_for_prefix(prefix):
692 """Returns a list of valgrind suppression files for a given prefix"""
694 # Most specific first (/usr/share/doc is Fedora, /usr/lib is Debian)
695 # Take the first one found
696 major = str(sys.version_info[0])
697 minor = str(sys.version_info[1])
701 prefix, "share", "doc", "python%s%s" % (major, minor),
702 "valgrind-python.supp"))
704 os.path.join(prefix, "lib", "valgrind", "python%s.supp" % major))
707 prefix, "share", "doc", "python%s-devel" % major,
708 "valgrind-python.supp"))
709 pyfiles.append(os.path.join(prefix, "lib", "valgrind", "python.supp"))
713 if os.path.isfile(f):
717 files.append(os.path.join(
718 prefix, "share", "glib-2.0", "valgrind", "glib.supp"))
719 return [f for f in files if os.path.isfile(f)]
722 def get_real_prefix():
723 """Returns the base Python prefix, even in a virtualenv/venv"""
725 return getattr(sys, "base_prefix", getattr(sys, "real_prefix", sys.prefix))
728 def get_suppression_files():
729 """Returns a list of valgrind suppression files"""
734 pkg_config_parse("--variable=prefix", "glib-2.0")[0],
738 for prefix in prefixes:
739 files.extend(get_suppression_files_for_prefix(prefix))
741 files.append(os.path.join(get_script_dir(), "tests", "valgrind.supp"))
742 return sorted(set(files))
747 ("valgrind", None, "run tests under valgrind"),
748 ("valgrind-log-file=", None, "save logs instead of printing them"),
749 ("gdb", None, "run tests under gdb"),
750 ("no-capture", "s", "don't capture test output"),
753 def initialize_options(self):
755 self.valgrind_log_file = None
757 self.no_capture = None
759 def finalize_options(self):
760 self.valgrind = bool(self.valgrind)
761 if self.valgrind_log_file and not self.valgrind:
762 raise DistutilsOptionError("valgrind not enabled")
763 self.gdb = bool(self.gdb)
764 self.no_capture = bool(self.no_capture)
767 cmd = self.reinitialize_command("build_tests")
768 cmd.ensure_finalized()
771 env = os.environ.copy()
772 env.pop("MSYSTEM", None)
775 env["PYGI_TEST_VERBOSE"] = "1"
777 env["MALLOC_PERTURB_"] = "85"
778 env["MALLOC_CHECK_"] = "3"
779 env["G_SLICE"] = "debug-blocks"
784 env["G_SLICE"] = "always-malloc"
785 env["G_DEBUG"] = "gc-friendly"
786 env["PYTHONMALLOC"] = "malloc"
789 "valgrind", "--leak-check=full", "--show-possibly-lost=no",
790 "--num-callers=20", "--child-silent-after-fork=yes",
791 ] + ["--suppressions=" + f for f in get_suppression_files()]
793 if self.valgrind_log_file:
794 pre_args += ["--log-file=" + self.valgrind_log_file]
797 env["PYGI_TEST_GDB"] = "1"
798 pre_args += ["gdb", "--args"]
801 log.info(" ".join(pre_args))
803 tests_dir = os.path.join(get_script_dir(), "tests")
804 sys.exit(subprocess.call(pre_args + [
806 os.path.join(tests_dir, "runtests.py"),
810 class quality(Command):
811 description = "run code quality tests"
814 def initialize_options(self):
817 def finalize_options(self):
821 status = subprocess.call([
822 sys.executable, "-m", "flake8",
823 ], cwd=get_script_dir())
825 raise SystemExit(status)
828 def get_script_dir():
829 return os.path.dirname(os.path.realpath(__file__))
832 def get_pycairo_include_dir():
833 """Returns the best guess at where to find the pycairo headers.
834 A bit convoluted because we have to deal with multiple pycairo
837 Raises if pycairo isn't found or it's too old.
840 pkg_config_name = get_pycairo_pkg_config_name()
841 min_version = get_version_requirement(pkg_config_name)
842 min_version_info = tuple(int(p) for p in min_version.split("."))
844 def check_path(include_dir):
845 log.info("pycairo: trying include directory: %r" % include_dir)
846 header_path = os.path.join(include_dir, "%s.h" % pkg_config_name)
847 if os.path.exists(header_path):
848 log.info("pycairo: found %r" % header_path)
850 log.info("pycairo: header file (%r) not found" % header_path)
853 def find_path(paths):
854 for p in reversed(paths):
859 log.info("pycairo: new API")
862 if cairo.version_info < min_version_info:
863 raise DistutilsSetupError(
864 "pycairo >= %s required, %s found." % (
865 min_version, ".".join(map(str, cairo.version_info))))
867 if hasattr(cairo, "get_include"):
868 return [cairo.get_include()]
869 log.info("pycairo: no get_include()")
873 log.info("pycairo: old API")
877 if cairo.version_info < min_version_info:
878 raise DistutilsSetupError(
879 "pycairo >= %s required, %s found." % (
880 min_version, ".".join(map(str, cairo.version_info))))
882 location = os.path.dirname(os.path.abspath(cairo.__path__[0]))
883 log.info("pycairo: found %r" % location)
885 def samefile(src, dst):
886 # Python 2 on Windows doesn't have os.path.samefile, so we have to
888 if hasattr(os.path, "samefile"):
889 return os.path.samefile(src, dst)
892 return (os.path.normcase(os.path.abspath(src)) ==
893 os.path.normcase(os.path.abspath(dst)))
895 def get_sys_path(location, name):
896 # Returns the sysconfig path for a distribution, or None
897 for scheme in sysconfig.get_scheme_names():
898 for path_type in ["platlib", "purelib"]:
899 path = sysconfig.get_path(path_type, scheme)
901 if samefile(path, location):
902 return sysconfig.get_path(name, scheme)
903 except EnvironmentError:
906 data_path = get_sys_path(location, "data") or sys.prefix
907 return [os.path.join(data_path, "include", "pycairo")]
909 def find_pkg_config():
910 log.info("pycairo: pkg-config")
911 pkg_config_version_check(pkg_config_name, min_version)
912 return pkg_config_parse("--cflags-only-I", pkg_config_name)
914 # First the new get_include() API added in >1.15.6
915 include_dir = find_path(find_new_api())
916 if include_dir is not None:
919 # Then try to find it in the data prefix based on the module path.
920 # This works with many virtualenv/userdir setups, but not all apparently,
921 # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
922 include_dir = find_path(find_old_api())
923 if include_dir is not None:
926 # Finally, fall back to pkg-config
927 include_dir = find_path(find_pkg_config())
928 if include_dir is not None:
931 raise DistutilsSetupError("Could not find pycairo headers")
934 def add_ext_pkg_config_dep(ext, compiler_type, name):
936 "glib-2.0": ["glib-2.0"],
937 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
938 "gobject-introspection-1.0":
939 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
942 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
946 def add(target, new):
948 if entry not in target:
951 fallback_libs = msvc_libraries[name]
952 if compiler_type == "msvc":
953 # assume that INCLUDE and LIB contains the right paths
954 add(ext.libraries, fallback_libs)
956 min_version = get_version_requirement(name)
957 pkg_config_version_check(name, min_version)
958 add(ext.include_dirs, pkg_config_parse("--cflags-only-I", name))
959 add(ext.library_dirs, pkg_config_parse("--libs-only-L", name))
960 add(ext.libraries, pkg_config_parse("--libs-only-l", name))
963 def add_ext_compiler_flags(ext, compiler, _cache={}):
964 if compiler.compiler_type == "msvc":
965 # MSVC: Just force-include msvc_recommended_pragmas.h so that
966 # we can look out for compiler warnings that we really
967 # want to look out for, and filter out those that don't
968 # really matter to us.
969 ext.extra_compile_args += ['-FImsvc_recommended_pragmas.h']
971 cache_key = compiler.compiler[0]
972 if cache_key not in _cache:
978 "-Wdeclaration-after-statement",
979 "-Wduplicated-branches",
982 "-Wformat-nonliteral",
984 "-Wimplicit-function-declaration",
986 "-Wjump-misses-init",
988 "-Wmissing-declarations",
989 "-Wmissing-format-attribute",
990 "-Wmissing-include-dirs",
991 "-Wmissing-noreturn",
992 "-Wmissing-prototypes",
994 "-Wnull-dereference",
995 "-Wold-style-definition",
1002 "-Wstrict-aliasing",
1003 "-Wstrict-prototypes",
1005 "-Wunused-but-set-variable",
1009 if sys.version_info[:2] != (3, 4):
1015 "-Wno-incompatible-pointer-types-discards-qualifiers",
1016 "-Wno-missing-field-initializers",
1017 "-Wno-unused-parameter",
1018 "-Wno-discarded-qualifiers",
1019 "-Wno-sign-conversion",
1020 "-Wno-cast-function-type",
1021 "-Wno-int-conversion",
1024 # silence clang for unused gcc CFLAGS added by Debian
1026 "-Wno-unused-command-line-argument",
1030 "-fno-strict-aliasing",
1031 "-fvisibility=hidden",
1034 # force GCC to use colors
1035 if hasattr(sys.stdout, "isatty") and sys.stdout.isatty():
1036 args.append("-fdiagnostics-color")
1038 _cache[cache_key] = filter_compiler_arguments(compiler, args)
1040 ext.extra_compile_args += _cache[cache_key]
1043 du_build_ext = get_command_class("build_ext")
1046 class build_ext(du_build_ext):
1048 def initialize_options(self):
1049 du_build_ext.initialize_options(self)
1050 self.compiler_type = None
1052 def finalize_options(self):
1053 du_build_ext.finalize_options(self)
1054 self.compiler_type = new_compiler(compiler=self.compiler).compiler_type
1056 def _write_config_h(self):
1057 script_dir = get_script_dir()
1058 target = os.path.join(script_dir, "config.h")
1059 versions = get_versions()
1061 /* Configuration header created by setup.py - do not edit */
1065 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
1066 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
1067 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
1068 #define VERSION "%(VERSION)s"
1070 #endif /* _CONFIG_H */
1074 with io.open(target, 'r', encoding="utf-8") as h:
1075 if h.read() == content:
1077 except EnvironmentError:
1080 with io.open(target, 'w', encoding="utf-8") as h:
1083 def _setup_extensions(self):
1084 ext = {e.name: e for e in self.extensions}
1086 compiler = new_compiler(compiler=self.compiler)
1087 customize_compiler(compiler)
1089 def add_dependency(ext, name):
1090 add_ext_pkg_config_dep(ext, compiler.compiler_type, name)
1092 def add_pycairo(ext):
1093 ext.include_dirs += [get_pycairo_include_dir()]
1095 gi_ext = ext["gi._gi"]
1096 add_dependency(gi_ext, "glib-2.0")
1097 add_dependency(gi_ext, "gio-2.0")
1098 add_dependency(gi_ext, "gobject-introspection-1.0")
1099 add_dependency(gi_ext, "libffi")
1100 add_ext_compiler_flags(gi_ext, compiler)
1103 gi_cairo_ext = ext["gi._gi_cairo"]
1104 add_dependency(gi_cairo_ext, "glib-2.0")
1105 add_dependency(gi_cairo_ext, "gio-2.0")
1106 add_dependency(gi_cairo_ext, "gobject-introspection-1.0")
1107 add_dependency(gi_cairo_ext, "libffi")
1108 add_dependency(gi_cairo_ext, "cairo")
1109 add_dependency(gi_cairo_ext, "cairo-gobject")
1110 add_pycairo(gi_cairo_ext)
1111 add_ext_compiler_flags(gi_cairo_ext, compiler)
1114 self._write_config_h()
1115 self._setup_extensions()
1116 du_build_ext.run(self)
1119 class install_pkgconfig(Command):
1120 description = "install .pc file"
1123 def initialize_options(self):
1124 self.install_base = None
1125 self.install_platbase = None
1126 self.install_data = None
1127 self.compiler_type = None
1130 def finalize_options(self):
1131 self.set_undefined_options(
1133 ('install_base', 'install_base'),
1134 ('install_data', 'install_data'),
1135 ('install_platbase', 'install_platbase'),
1138 self.set_undefined_options(
1140 ('compiler_type', 'compiler_type'),
1143 def get_outputs(self):
1144 return self.outfiles
1146 def get_inputs(self):
1150 cmd = self.distribution.get_command_obj("bdist_wheel", create=False)
1153 "Python wheels and pkg-config is not compatible. "
1154 "No pkg-config file will be included in the wheel. Install "
1155 "from source if you need one.")
1158 if self.compiler_type == "msvc":
1161 script_dir = get_script_dir()
1162 pkgconfig_in = os.path.join(script_dir, "pygobject-3.0.pc.in")
1163 with io.open(pkgconfig_in, "r", encoding="utf-8") as h:
1167 "prefix": self.install_base,
1168 "exec_prefix": self.install_platbase,
1169 "includedir": "${prefix}/include",
1170 "datarootdir": "${prefix}/share",
1171 "datadir": "${datarootdir}",
1172 "VERSION": PYGOBJECT_VERSION,
1174 for key, value in config.items():
1175 content = content.replace("@%s@" % key, value)
1177 libdir = os.path.dirname(get_python_lib(True, True, self.install_data))
1178 pkgconfig_dir = os.path.join(libdir, "pkgconfig")
1179 self.mkpath(pkgconfig_dir)
1180 target = os.path.join(pkgconfig_dir, "pygobject-3.0.pc")
1181 with io.open(target, "w", encoding="utf-8") as h:
1183 self.outfiles.append(target)
1186 du_install = get_command_class("install")
1189 class install(du_install):
1191 sub_commands = du_install.sub_commands + [
1192 ("install_pkgconfig", lambda self: True),
1197 script_dir = get_script_dir()
1198 pkginfo = parse_pkg_info(script_dir)
1199 gi_dir = os.path.join(script_dir, "gi")
1202 os.path.join("gi", n) for n in os.listdir(gi_dir)
1203 if os.path.splitext(n)[-1] == ".c"
1205 cairo_sources = [os.path.join("gi", "pygi-foreign-cairo.c")]
1206 for s in cairo_sources:
1209 readme = os.path.join(script_dir, "README.rst")
1210 with io.open(readme, encoding="utf-8") as h:
1211 long_description = h.read()
1214 install_requires = []
1218 sources=sorted(sources),
1219 include_dirs=[script_dir, gi_dir],
1220 depends=list_headers(script_dir) + list_headers(gi_dir),
1221 define_macros=[("PY_SSIZE_T_CLEAN", None)],
1223 ext_modules.append(gi_ext)
1226 gi_cairo_ext = Extension(
1227 name='gi._gi_cairo',
1228 sources=cairo_sources,
1229 include_dirs=[script_dir, gi_dir],
1230 depends=list_headers(script_dir) + list_headers(gi_dir),
1231 define_macros=[("PY_SSIZE_T_CLEAN", None)],
1233 ext_modules.append(gi_cairo_ext)
1234 install_requires.append(
1235 "pycairo>=%s" % get_version_requirement(
1236 get_pycairo_pkg_config_name()))
1238 version = pkginfo["Version"]
1239 if is_dev_version():
1240 # This makes it a PEP 440 pre-release and pip will only install it from
1241 # PyPI in case --pre is passed.
1245 name=pkginfo["Name"],
1247 description=pkginfo["Summary"],
1248 url=pkginfo["Home-page"],
1249 author=pkginfo["Author"],
1250 author_email=pkginfo["Author-email"],
1251 maintainer=pkginfo["Maintainer"],
1252 maintainer_email=pkginfo["Maintainer-email"],
1253 license=pkginfo["License"],
1254 long_description=long_description,
1255 platforms=pkginfo.get_all("Platform"),
1256 classifiers=pkginfo.get_all("Classifier"),
1263 ext_modules=ext_modules,
1265 "build_ext": build_ext,
1266 "distcheck": distcheck,
1267 "sdist_gnome": sdist_gnome,
1268 "build_tests": build_tests,
1272 "install_pkgconfig": install_pkgconfig,
1274 install_requires=install_requires,
1276 ('include/pygobject-3.0', ['gi/pygobject.h']),
1282 if __name__ == "__main__":