tests: move testmodule content into test_internal_api
[pygobject.git] / setup.py
blobe44377f8e77019af6ee3578b162e8efd6a695b0f
1 #!/usr/bin/env python3
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
17 # USA
19 import io
20 import os
21 import re
22 import sys
23 import errno
24 import subprocess
25 import tarfile
26 import sysconfig
27 from email import parser
29 import pkg_resources
30 from setuptools import setup
31 from distutils.core import Extension, Distribution, Command
32 from distutils.errors import DistutilsSetupError, DistutilsOptionError
33 from distutils.ccompiler import new_compiler
34 from distutils.sysconfig import get_python_lib, customize_compiler
35 from distutils import dir_util, log
38 def get_command_class(name):
39 # Returns the right class for either distutils or setuptools
40 return Distribution({}).get_command_class(name)
43 def get_pycairo_pkg_config_name():
44 return "py3cairo" if sys.version_info[0] == 3 else "pycairo"
47 def get_version_requirement(conf_dir, pkg_config_name):
48 """Given a pkg-config module name gets the minimum version required"""
50 if pkg_config_name in ["cairo", "cairo-gobject"]:
51 return "0"
53 mapping = {
54 "gobject-introspection-1.0": "introspection",
55 "glib-2.0": "glib",
56 "gio-2.0": "gio",
57 get_pycairo_pkg_config_name(): "pycairo",
58 "libffi": "libffi",
60 assert pkg_config_name in mapping
62 configure_ac = os.path.join(conf_dir, "configure.ac")
63 with io.open(configure_ac, "r", encoding="utf-8") as h:
64 text = h.read()
65 conf_name = mapping[pkg_config_name]
66 res = re.findall(
67 r"%s_required_version,\s*([\d\.]+)\)" % conf_name, text)
68 assert len(res) == 1
69 return res[0]
72 def parse_versions(conf_dir):
73 configure_ac = os.path.join(conf_dir, "configure.ac")
74 with io.open(configure_ac, "r", encoding="utf-8") as h:
75 version = re.findall(r"pygobject_[^\s]+_version,\s*(\d+)\)", h.read())
76 assert len(version) == 3
78 versions = {
79 "PYGOBJECT_MAJOR_VERSION": version[0],
80 "PYGOBJECT_MINOR_VERSION": version[1],
81 "PYGOBJECT_MICRO_VERSION": version[2],
82 "VERSION": ".".join(version),
84 return versions
87 def parse_pkg_info(conf_dir):
88 """Returns an email.message.Message instance containing the content
89 of the PKG-INFO file. The version info is parsed from configure.ac
90 """
92 versions = parse_versions(conf_dir)
94 pkg_info = os.path.join(conf_dir, "PKG-INFO.in")
95 with io.open(pkg_info, "r", encoding="utf-8") as h:
96 text = h.read()
97 for key, value in versions.items():
98 text = text.replace("@%s@" % key, value)
100 p = parser.Parser()
101 message = p.parse(io.StringIO(text))
102 return message
105 def _run_pkg_config(args, _cache={}):
106 command = tuple(["pkg-config"] + args)
108 if command not in _cache:
109 try:
110 result = subprocess.check_output(command)
111 except OSError as e:
112 if e.errno == errno.ENOENT:
113 raise SystemExit(
114 "%r not found.\nArguments: %r" % (command[0], command))
115 raise SystemExit(e)
116 except subprocess.CalledProcessError as e:
117 raise SystemExit(e)
118 else:
119 _cache[command] = result
121 return _cache[command]
124 def pkg_config_version_check(pkg, version):
125 _run_pkg_config([
126 "--print-errors",
127 "--exists",
128 '%s >= %s' % (pkg, version),
132 def pkg_config_parse(opt, pkg):
133 ret = _run_pkg_config([opt, pkg])
134 if sys.version_info[0] == 3:
135 output = ret.decode()
136 else:
137 output = ret
138 opt = opt[-2:]
139 return [x.lstrip(opt) for x in output.split()]
142 def filter_compiler_arguments(compiler, args):
143 """Given a compiler instance and a list of compiler warning flags
144 returns the list of supported flags.
147 if compiler.compiler_type == "msvc":
148 # TODO
149 return []
151 extra = []
153 def check_arguments(compiler, args):
154 p = subprocess.Popen(
155 [compiler.compiler[0]] + args + extra + ["-x", "c", "-E", "-"],
156 stdin=subprocess.PIPE,
157 stdout=subprocess.PIPE,
158 stderr=subprocess.PIPE)
159 stdout, stderr = p.communicate(b"int i;\n")
160 if p.returncode != 0:
161 text = stderr.decode("ascii", "replace")
162 return False, [a for a in args if a in text]
163 else:
164 return True, []
166 def check_argument(compiler, arg):
167 return check_arguments(compiler, [arg])[0]
169 # clang doesn't error out for unknown options, force it to
170 if check_argument(compiler, '-Werror=unknown-warning-option'):
171 extra += ['-Werror=unknown-warning-option']
172 if check_argument(compiler, '-Werror=unused-command-line-argument'):
173 extra += ['-Werror=unused-command-line-argument']
175 # first try to remove all arguments contained in the error message
176 supported = list(args)
177 while 1:
178 ok, maybe_unknown = check_arguments(compiler, supported)
179 if ok:
180 return supported
181 elif not maybe_unknown:
182 break
183 for unknown in maybe_unknown:
184 if not check_argument(compiler, unknown):
185 supported.remove(unknown)
187 # hm, didn't work, try each argument one by one
188 supported = []
189 for arg in args:
190 if check_argument(compiler, arg):
191 supported.append(arg)
192 return supported
195 du_sdist = get_command_class("sdist")
198 class distcheck(du_sdist):
199 """Creates a tarball and does some additional sanity checks such as
200 checking if the tarball includes all files, builds successfully and
201 the tests suite passes.
204 def _check_manifest(self):
205 # make sure MANIFEST.in includes all tracked files
206 assert self.get_archive_files()
208 if subprocess.call(["git", "status"],
209 stdout=subprocess.PIPE,
210 stderr=subprocess.PIPE) != 0:
211 return
213 included_files = self.filelist.files
214 assert included_files
216 process = subprocess.Popen(
217 ["git", "ls-tree", "-r", "HEAD", "--name-only"],
218 stdout=subprocess.PIPE, universal_newlines=True)
219 out, err = process.communicate()
220 assert process.returncode == 0
222 tracked_files = out.splitlines()
223 for ignore in [".gitignore"]:
224 tracked_files.remove(ignore)
226 diff = set(tracked_files) - set(included_files)
227 assert not diff, (
228 "Not all tracked files included in tarball, check MANIFEST.in",
229 diff)
231 def _check_dist(self):
232 # make sure the tarball builds
233 assert self.get_archive_files()
235 distcheck_dir = os.path.abspath(
236 os.path.join(self.dist_dir, "distcheck"))
237 if os.path.exists(distcheck_dir):
238 dir_util.remove_tree(distcheck_dir)
239 self.mkpath(distcheck_dir)
241 archive = self.get_archive_files()[0]
242 tfile = tarfile.open(archive, "r:gz")
243 tfile.extractall(distcheck_dir)
244 tfile.close()
246 name = self.distribution.get_fullname()
247 extract_dir = os.path.join(distcheck_dir, name)
249 old_pwd = os.getcwd()
250 os.chdir(extract_dir)
251 try:
252 self.spawn([sys.executable, "setup.py", "build"])
253 self.spawn([sys.executable, "setup.py", "install",
254 "--root",
255 os.path.join(distcheck_dir, "prefix"),
256 "--record",
257 os.path.join(distcheck_dir, "log.txt"),
259 self.spawn([sys.executable, "setup.py", "test"])
260 finally:
261 os.chdir(old_pwd)
263 def run(self):
264 du_sdist.run(self)
265 self._check_manifest()
266 self._check_dist()
269 class build_tests(Command):
270 description = "build test libraries and extensions"
271 user_options = [
272 ("force", "f", "force a rebuild"),
275 def initialize_options(self):
276 self.build_temp = None
277 self.build_base = None
278 self.force = False
280 def finalize_options(self):
281 self.set_undefined_options(
282 'build_ext',
283 ('build_temp', 'build_temp'))
284 self.set_undefined_options(
285 'build',
286 ('build_base', 'build_base'))
288 def _newer_group(self, sources, *targets):
289 assert targets
291 from distutils.dep_util import newer_group
293 if self.force:
294 return True
295 else:
296 for target in targets:
297 if not newer_group(sources, target):
298 return False
299 return True
301 def run(self):
302 cmd = self.reinitialize_command("build_ext")
303 cmd.inplace = True
304 cmd.ensure_finalized()
305 cmd.run()
307 gidatadir = pkg_config_parse(
308 "--variable=gidatadir", "gobject-introspection-1.0")[0]
309 g_ir_scanner = pkg_config_parse(
310 "--variable=g_ir_scanner", "gobject-introspection-1.0")[0]
311 g_ir_compiler = pkg_config_parse(
312 "--variable=g_ir_compiler", "gobject-introspection-1.0")[0]
314 script_dir = get_script_dir()
315 tests_dir = os.path.join(script_dir, "tests")
316 gi_tests_dir = os.path.join(gidatadir, "tests")
318 schema_xml = os.path.join(tests_dir, "org.gnome.test.gschema.xml")
319 schema_bin = os.path.join(tests_dir, "gschemas.compiled")
320 if self._newer_group([schema_xml], schema_bin):
321 subprocess.check_call([
322 "glib-compile-schemas",
323 "--targetdir=%s" % tests_dir,
324 "--schema-file=%s" % schema_xml,
327 compiler = new_compiler()
328 customize_compiler(compiler)
330 if os.name == "nt":
331 compiler.shared_lib_extension = ".dll"
333 if sys.platform == "darwin":
334 compiler.shared_lib_extension = ".dylib"
335 if "-bundle" in compiler.linker_so:
336 compiler.linker_so = list(compiler.linker_so)
337 i = compiler.linker_so.index("-bundle")
338 compiler.linker_so[i] = "-dynamiclib"
340 def build_ext(ext):
341 if compiler.compiler_type == "msvc":
342 raise Exception("MSVC support not implemented")
344 libname = compiler.shared_object_filename(ext.name)
345 ext_paths = [os.path.join(tests_dir, libname)]
346 if os.name == "nt":
347 implibname = libname + ".a"
348 ext_paths.append(os.path.join(tests_dir, implibname))
350 if self._newer_group(ext.sources + ext.depends, *ext_paths):
351 objects = compiler.compile(
352 ext.sources,
353 output_dir=self.build_temp,
354 include_dirs=ext.include_dirs)
356 if os.name == "nt":
357 postargs = ["-Wl,--out-implib=%s" %
358 os.path.join(tests_dir, implibname)]
359 else:
360 postargs = []
362 compiler.link_shared_object(
363 objects,
364 compiler.shared_object_filename(ext.name),
365 output_dir=tests_dir,
366 libraries=ext.libraries,
367 library_dirs=ext.library_dirs,
368 extra_postargs=postargs)
370 return ext_paths
372 ext = Extension(
373 name='libgimarshallingtests',
374 sources=[
375 os.path.join(gi_tests_dir, "gimarshallingtests.c"),
376 os.path.join(tests_dir, "gimarshallingtestsextra.c"),
378 include_dirs=[
379 gi_tests_dir,
380 tests_dir,
382 depends=[
383 os.path.join(gi_tests_dir, "gimarshallingtests.h"),
384 os.path.join(tests_dir, "gimarshallingtestsextra.h"),
387 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
388 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
389 ext_paths = build_ext(ext)
391 gir_path = os.path.join(tests_dir, "GIMarshallingTests-1.0.gir")
392 typelib_path = os.path.join(
393 tests_dir, "GIMarshallingTests-1.0.typelib")
395 if self._newer_group(ext_paths, gir_path):
396 subprocess.check_call([
397 g_ir_scanner,
398 "--no-libtool",
399 "--include=Gio-2.0",
400 "--namespace=GIMarshallingTests",
401 "--nsversion=1.0",
402 "--symbol-prefix=gi_marshalling_tests",
403 "--warn-all",
404 "--warn-error",
405 "--library-path=%s" % tests_dir,
406 "--library=gimarshallingtests",
407 "--pkg=glib-2.0",
408 "--pkg=gio-2.0",
409 "--output=%s" % gir_path,
410 ] + ext.sources + ext.depends)
412 if self._newer_group([gir_path], typelib_path):
413 subprocess.check_call([
414 g_ir_compiler,
415 gir_path,
416 "--output=%s" % typelib_path,
419 ext = Extension(
420 name='libregress',
421 sources=[
422 os.path.join(gi_tests_dir, "regress.c"),
423 os.path.join(tests_dir, "regressextra.c"),
425 include_dirs=[
426 gi_tests_dir,
428 depends=[
429 os.path.join(gi_tests_dir, "regress.h"),
430 os.path.join(tests_dir, "regressextra.h"),
433 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
434 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
435 add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo")
436 add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo-gobject")
437 ext_paths = build_ext(ext)
439 gir_path = os.path.join(tests_dir, "Regress-1.0.gir")
440 typelib_path = os.path.join(tests_dir, "Regress-1.0.typelib")
442 if self._newer_group(ext_paths, gir_path):
443 subprocess.check_call([
444 g_ir_scanner,
445 "--no-libtool",
446 "--include=cairo-1.0",
447 "--include=Gio-2.0",
448 "--namespace=Regress",
449 "--nsversion=1.0",
450 "--warn-all",
451 "--warn-error",
452 "--library-path=%s" % tests_dir,
453 "--library=regress",
454 "--pkg=glib-2.0",
455 "--pkg=gio-2.0",
456 "--pkg=cairo",
457 "--pkg=cairo-gobject",
458 "--output=%s" % gir_path,
459 ] + ext.sources + ext.depends)
461 if self._newer_group([gir_path], typelib_path):
462 subprocess.check_call([
463 g_ir_compiler,
464 gir_path,
465 "--output=%s" % typelib_path,
468 ext = Extension(
469 name='tests.testhelper',
470 sources=[
471 os.path.join(tests_dir, "testhelpermodule.c"),
472 os.path.join(tests_dir, "test-floating.c"),
473 os.path.join(tests_dir, "test-thread.c"),
474 os.path.join(tests_dir, "test-unknown.c"),
476 include_dirs=[
477 os.path.join(script_dir, "gi"),
478 tests_dir,
480 depends=[
481 os.path.join(tests_dir, "test-thread.h"),
482 os.path.join(tests_dir, "test-unknown.h"),
483 os.path.join(tests_dir, "test-floating.h"),
486 add_ext_pkg_config_dep(ext, compiler.compiler_type, "glib-2.0")
487 add_ext_pkg_config_dep(ext, compiler.compiler_type, "gio-2.0")
488 add_ext_pkg_config_dep(ext, compiler.compiler_type, "cairo")
489 add_ext_warn_flags(ext, compiler)
491 dist = Distribution({"ext_modules": [ext]})
493 build_cmd = dist.get_command_obj("build")
494 build_cmd.build_base = os.path.join(self.build_base, "pygobject_tests")
495 build_cmd.ensure_finalized()
497 cmd = dist.get_command_obj("build_ext")
498 cmd.inplace = True
499 cmd.force = self.force
500 cmd.ensure_finalized()
501 cmd.run()
504 class test(Command):
505 user_options = [
506 ("valgrind", None, "run tests under valgrind"),
507 ("valgrind-log-file=", None, "save logs instead of printing them"),
508 ("gdb", None, "run tests under gdb"),
511 def initialize_options(self):
512 self.valgrind = None
513 self.valgrind_log_file = None
514 self.gdb = None
516 def finalize_options(self):
517 self.valgrind = bool(self.valgrind)
518 if self.valgrind_log_file and not self.valgrind:
519 raise DistutilsOptionError("valgrind not enabled")
520 self.gdb = bool(self.gdb)
522 def run(self):
523 cmd = self.reinitialize_command("build_tests")
524 cmd.ensure_finalized()
525 cmd.run()
527 env = os.environ.copy()
528 env.pop("MSYSTEM", None)
530 env["MALLOC_PERTURB_"] = "85"
531 env["MALLOC_CHECK_"] = "3"
532 env["G_SLICE"] = "debug-blocks"
534 def get_suppression_files():
535 files = []
536 if sys.version_info[0] == 2:
537 files.append(os.path.join(
538 sys.prefix, "lib", "valgrind", "python.supp"))
539 else:
540 files.append(os.path.join(
541 sys.prefix, "lib", "valgrind", "python3.supp"))
542 files.append(os.path.join(
543 sys.prefix, "share", "glib-2.0", "valgrind", "glib.supp"))
544 return [f for f in files if os.path.isfile(f)]
546 pre_args = []
548 if self.valgrind:
549 env["G_SLICE"] = "always-malloc"
550 env["G_DEBUG"] = "gc-friendly"
551 env["PYTHONMALLOC"] = "malloc"
553 pre_args += [
554 "valgrind", "--leak-check=full", "--show-possibly-lost=no",
555 "--num-callers=20", "--child-silent-after-fork=yes",
556 ] + ["--suppressions=" + f for f in get_suppression_files()]
558 if self.valgrind_log_file:
559 pre_args += ["--log-file=" + self.valgrind_log_file]
561 if self.gdb:
562 env["PYGI_TEST_GDB"] = "1"
563 pre_args += ["gdb", "--args"]
565 if pre_args:
566 log.info(" ".join(pre_args))
568 tests_dir = os.path.join(get_script_dir(), "tests")
569 sys.exit(subprocess.call(pre_args + [
570 sys.executable,
571 os.path.join(tests_dir, "runtests.py"),
572 ], env=env))
575 class quality(Command):
576 description = "run code quality tests"
577 user_options = []
579 def initialize_options(self):
580 pass
582 def finalize_options(self):
583 pass
585 def run(self):
586 status = subprocess.call([
587 sys.executable, "-m", "flake8",
588 ], cwd=get_script_dir())
589 if status != 0:
590 raise SystemExit(status)
593 def get_script_dir():
594 return os.path.dirname(os.path.realpath(__file__))
597 def get_pycairo_include_dir():
598 """Returns the best guess at where to find the pycairo headers.
599 A bit convoluted because we have to deal with multiple pycairo
600 versions.
602 Raises if pycairo isn't found or it's too old.
605 script_dir = get_script_dir()
606 pkg_config_name = get_pycairo_pkg_config_name()
607 min_version = get_version_requirement(script_dir, pkg_config_name)
609 def check_path(include_dir):
610 log.info("pycairo: trying include directory: %r" % include_dir)
611 header_path = os.path.join(include_dir, "%s.h" % pkg_config_name)
612 if os.path.exists(header_path):
613 log.info("pycairo: found %r" % header_path)
614 return True
615 log.info("pycairo: header file (%r) not found" % header_path)
616 return False
618 def find_path(paths):
619 for p in reversed(paths):
620 if check_path(p):
621 return p
623 def find_new_api():
624 log.info("pycairo: new API")
625 import cairo
627 pkg_version = pkg_resources.parse_version(cairo.version)
628 pkg_min_version = pkg_resources.parse_version(min_version)
629 if pkg_version < pkg_min_version:
630 raise DistutilsSetupError(
631 "pycairo >=%s required, %s found." % (
632 pkg_min_version, pkg_version))
634 if hasattr(cairo, "get_include"):
635 return [cairo.get_include()]
636 log.info("pycairo: no get_include()")
637 return []
639 def find_old_api():
640 log.info("pycairo: old API")
641 dist = pkg_resources.get_distribution("pycairo>=%s" % min_version)
642 log.info("pycairo: found %r" % dist)
644 def samefile(src, dst):
645 # Python 2 on Windows doesn't have os.path.samefile, so we have to
646 # provide a fallback
647 if hasattr(os.path, "samefile"):
648 return os.path.samefile(src, dst)
649 os.stat(src)
650 os.stat(dst)
651 return (os.path.normcase(os.path.abspath(src)) ==
652 os.path.normcase(os.path.abspath(dst)))
654 def get_sys_path(dist, name):
655 # Returns the sysconfig path for a distribution, or None
656 location = dist.location
657 for scheme in sysconfig.get_scheme_names():
658 for path_type in ["platlib", "purelib"]:
659 path = sysconfig.get_path(path_type, scheme)
660 try:
661 if samefile(path, location):
662 return sysconfig.get_path(name, scheme)
663 except EnvironmentError:
664 pass
666 data_path = get_sys_path(dist, "data") or sys.prefix
667 return [os.path.join(data_path, "include", "pycairo")]
669 def find_pkg_config():
670 log.info("pycairo: pkg-config")
671 pkg_config_version_check(pkg_config_name, min_version)
672 return pkg_config_parse("--cflags-only-I", pkg_config_name)
674 # First the new get_include() API added in >1.15.6
675 include_dir = find_path(find_new_api())
676 if include_dir is not None:
677 return include_dir
679 # Then try to find it in the data prefix based on the module path.
680 # This works with many virtualenv/userdir setups, but not all apparently,
681 # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
682 include_dir = find_path(find_old_api())
683 if include_dir is not None:
684 return include_dir
686 # Finally, fall back to pkg-config
687 include_dir = find_path(find_pkg_config())
688 if include_dir is not None:
689 return include_dir
691 raise DistutilsSetupError("Could not find pycairo headers")
694 def add_ext_pkg_config_dep(ext, compiler_type, name):
695 script_dir = get_script_dir()
697 msvc_libraries = {
698 "glib-2.0": ["glib-2.0"],
699 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
700 "gobject-introspection-1.0":
701 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
702 "cairo": ["cairo"],
703 "cairo-gobject":
704 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
705 "libffi": ["ffi"],
708 fallback_libs = msvc_libraries[name]
709 if compiler_type == "msvc":
710 # assume that INCLUDE and LIB contains the right paths
711 ext.libraries += fallback_libs
712 else:
713 min_version = get_version_requirement(script_dir, name)
714 pkg_config_version_check(name, min_version)
715 ext.include_dirs += pkg_config_parse("--cflags-only-I", name)
716 ext.library_dirs += pkg_config_parse("--libs-only-L", name)
717 ext.libraries += pkg_config_parse("--libs-only-l", name)
720 def add_ext_warn_flags(ext, compiler, _cache={}):
721 cache_key = compiler.compiler[0]
722 if cache_key not in _cache:
724 args = [
725 "-Wall",
726 "-Warray-bounds",
727 # "-Wcast-align",
728 # "-Wdeclaration-after-statement",
729 # "-Wdouble-promotion",
730 "-Wduplicated-branches",
731 # "-Wduplicated-cond",
732 "-Wextra",
733 "-Wformat=2",
734 "-Wformat-nonliteral",
735 "-Wformat-security",
736 "-Wimplicit-function-declaration",
737 "-Winit-self",
738 "-Winline",
739 # "-Wjump-misses-init",
740 # "-Wlogical-op",
741 "-Wmissing-declarations",
742 "-Wmissing-format-attribute",
743 "-Wmissing-include-dirs",
744 "-Wmissing-noreturn",
745 "-Wmissing-prototypes",
746 "-Wnested-externs",
747 # "-Wnull-dereference",
748 "-Wold-style-definition",
749 "-Wpacked",
750 "-Wpointer-arith",
751 "-Wredundant-decls",
752 "-Wrestrict",
753 "-Wreturn-type",
754 "-Wshadow",
755 "-Wsign-compare",
756 "-Wstrict-aliasing",
757 "-Wstrict-prototypes",
758 "-Wswitch-default",
759 "-Wundef",
760 "-Wunused-but-set-variable",
761 "-Wwrite-strings",
764 args += [
765 "-Wno-incompatible-pointer-types-discards-qualifiers",
766 "-Wno-missing-field-initializers",
767 "-Wno-unused-parameter",
768 "-Wno-discarded-qualifiers",
771 # silence clang for unused gcc CFLAGS added by Debian
772 args += [
773 "-Wno-unused-command-line-argument",
776 _cache[cache_key] = filter_compiler_arguments(compiler, args)
778 ext.extra_compile_args += _cache[cache_key]
781 du_build_ext = get_command_class("build_ext")
784 class build_ext(du_build_ext):
786 def initialize_options(self):
787 du_build_ext.initialize_options(self)
788 self.compiler_type = None
790 def finalize_options(self):
791 du_build_ext.finalize_options(self)
792 self.compiler_type = new_compiler(compiler=self.compiler).compiler_type
794 def _write_config_h(self):
795 script_dir = get_script_dir()
796 target = os.path.join(script_dir, "config.h")
797 versions = parse_versions(script_dir)
798 with io.open(target, 'w', encoding="utf-8") as h:
799 h.write("""
800 /* Configuration header created by setup.py - do not edit */
801 #ifndef _CONFIG_H
802 #define _CONFIG_H 1
804 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
805 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
806 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
807 #define VERSION "%(VERSION)s"
809 #endif /* _CONFIG_H */
810 """ % versions)
812 def _setup_extensions(self):
813 ext = {e.name: e for e in self.extensions}
815 compiler = new_compiler(compiler=self.compiler)
816 customize_compiler(compiler)
818 def add_dependency(ext, name):
819 add_ext_pkg_config_dep(ext, compiler.compiler_type, name)
821 def add_pycairo(ext):
822 ext.include_dirs += [get_pycairo_include_dir()]
824 gi_ext = ext["gi._gi"]
825 add_dependency(gi_ext, "glib-2.0")
826 add_dependency(gi_ext, "gio-2.0")
827 add_dependency(gi_ext, "gobject-introspection-1.0")
828 add_dependency(gi_ext, "libffi")
829 add_ext_warn_flags(gi_ext, compiler)
831 gi_cairo_ext = ext["gi._gi_cairo"]
832 add_dependency(gi_cairo_ext, "glib-2.0")
833 add_dependency(gi_cairo_ext, "gio-2.0")
834 add_dependency(gi_cairo_ext, "gobject-introspection-1.0")
835 add_dependency(gi_cairo_ext, "libffi")
836 add_dependency(gi_cairo_ext, "cairo")
837 add_dependency(gi_cairo_ext, "cairo-gobject")
838 add_pycairo(gi_cairo_ext)
839 add_ext_warn_flags(gi_cairo_ext, compiler)
841 def run(self):
842 self._write_config_h()
843 self._setup_extensions()
844 du_build_ext.run(self)
847 class install_pkgconfig(Command):
848 description = "install .pc file"
849 user_options = []
851 def initialize_options(self):
852 self.install_base = None
853 self.install_platbase = None
854 self.install_data = None
855 self.compiler_type = None
856 self.outfiles = []
858 def finalize_options(self):
859 self.set_undefined_options(
860 'install',
861 ('install_base', 'install_base'),
862 ('install_data', 'install_data'),
863 ('install_platbase', 'install_platbase'),
866 self.set_undefined_options(
867 'build_ext',
868 ('compiler_type', 'compiler_type'),
871 def get_outputs(self):
872 return self.outfiles
874 def get_inputs(self):
875 return []
877 def run(self):
878 cmd = self.distribution.get_command_obj("bdist_wheel", create=False)
879 if cmd is not None:
880 log.warn(
881 "Python wheels and pkg-config is not compatible. "
882 "No pkg-config file will be included in the wheel. Install "
883 "from source if you need one.")
884 return
886 if self.compiler_type == "msvc":
887 return
889 script_dir = get_script_dir()
890 pkgconfig_in = os.path.join(script_dir, "pygobject-3.0.pc.in")
891 with io.open(pkgconfig_in, "r", encoding="utf-8") as h:
892 content = h.read()
894 config = {
895 "prefix": self.install_base,
896 "exec_prefix": self.install_platbase,
897 "includedir": "${prefix}/include",
898 "datarootdir": "${prefix}/share",
899 "datadir": "${datarootdir}",
900 "VERSION": self.distribution.get_version(),
902 for key, value in config.items():
903 content = content.replace("@%s@" % key, value)
905 libdir = os.path.dirname(get_python_lib(True, True, self.install_data))
906 pkgconfig_dir = os.path.join(libdir, "pkgconfig")
907 self.mkpath(pkgconfig_dir)
908 target = os.path.join(pkgconfig_dir, "pygobject-3.0.pc")
909 with io.open(target, "w", encoding="utf-8") as h:
910 h.write(content)
911 self.outfiles.append(target)
914 du_install = get_command_class("install")
917 class install(du_install):
919 sub_commands = du_install.sub_commands + [
920 ("install_pkgconfig", lambda self: True),
924 def main():
925 script_dir = get_script_dir()
926 pkginfo = parse_pkg_info(script_dir)
927 gi_dir = os.path.join(script_dir, "gi")
929 sources = [
930 os.path.join("gi", n) for n in os.listdir(gi_dir)
931 if os.path.splitext(n)[-1] == ".c"
933 cairo_sources = [os.path.join("gi", "pygi-foreign-cairo.c")]
934 for s in cairo_sources:
935 sources.remove(s)
937 readme = os.path.join(script_dir, "README.rst")
938 with io.open(readme, encoding="utf-8") as h:
939 long_description = h.read()
941 gi_ext = Extension(
942 name='gi._gi',
943 sources=sources,
944 include_dirs=[script_dir, gi_dir],
945 define_macros=[("HAVE_CONFIG_H", None)],
948 gi_cairo_ext = Extension(
949 name='gi._gi_cairo',
950 sources=cairo_sources,
951 include_dirs=[script_dir, gi_dir],
952 define_macros=[("HAVE_CONFIG_H", None)],
955 setup(
956 name=pkginfo["Name"],
957 version=pkginfo["Version"],
958 description=pkginfo["Summary"],
959 url=pkginfo["Home-page"],
960 author=pkginfo["Author"],
961 author_email=pkginfo["Author-email"],
962 maintainer=pkginfo["Maintainer"],
963 maintainer_email=pkginfo["Maintainer-email"],
964 license=pkginfo["License"],
965 long_description=long_description,
966 platforms=pkginfo.get_all("Platform"),
967 classifiers=pkginfo.get_all("Classifier"),
968 packages=[
969 "pygtkcompat",
970 "gi",
971 "gi.repository",
972 "gi.overrides",
974 ext_modules=[
975 gi_ext,
976 gi_cairo_ext,
978 cmdclass={
979 "build_ext": build_ext,
980 "distcheck": distcheck,
981 "build_tests": build_tests,
982 "test": test,
983 "quality": quality,
984 "install": install,
985 "install_pkgconfig": install_pkgconfig,
987 install_requires=[
988 "pycairo>=%s" % get_version_requirement(
989 script_dir, get_pycairo_pkg_config_name()),
991 data_files=[
992 ('include/pygobject-3.0', ['gi/pygobject.h']),
994 zip_safe=False,
998 if __name__ == "__main__":
999 main()