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
27 from email
import parser
30 from setuptools
import setup
32 from distutils
.core
import setup
34 from distutils
.core
import Extension
, Distribution
, Command
35 from distutils
.errors
import DistutilsSetupError
, DistutilsOptionError
36 from distutils
.ccompiler
import new_compiler
37 from distutils
.sysconfig
import get_python_lib
, customize_compiler
38 from distutils
import dir_util
, log
39 from distutils
.spawn
import find_executable
42 PYGOBJECT_VERISON
= "3.29.3"
43 GLIB_VERSION_REQUIRED
= "2.38.0"
44 GI_VERSION_REQUIRED
= "1.46.0"
45 PYCAIRO_VERSION_REQUIRED
= "1.11.1"
46 LIBFFI_VERSION_REQUIRED
= "3.0"
50 version
= tuple(map(int, PYGOBJECT_VERISON
.split(".")))
51 return version
[1] % 2 != 0
54 def get_command_class(name
):
55 # Returns the right class for either distutils or setuptools
56 return Distribution({}).get_command_class(name
)
59 def get_pycairo_pkg_config_name():
60 return "py3cairo" if sys
.version_info
[0] == 3 else "pycairo"
63 def get_version_requirement(pkg_config_name
):
64 """Given a pkg-config module name gets the minimum version required"""
67 "gobject-introspection-1.0": GI_VERSION_REQUIRED
,
68 "glib-2.0": GLIB_VERSION_REQUIRED
,
69 "gio-2.0": GLIB_VERSION_REQUIRED
,
70 get_pycairo_pkg_config_name(): PYCAIRO_VERSION_REQUIRED
,
71 "libffi": LIBFFI_VERSION_REQUIRED
,
76 return versions
[pkg_config_name
]
80 version
= PYGOBJECT_VERISON
.split(".")
81 assert len(version
) == 3
84 "PYGOBJECT_MAJOR_VERSION": version
[0],
85 "PYGOBJECT_MINOR_VERSION": version
[1],
86 "PYGOBJECT_MICRO_VERSION": version
[2],
87 "VERSION": ".".join(version
),
92 def parse_pkg_info(conf_dir
):
93 """Returns an email.message.Message instance containing the content
97 versions
= get_versions()
99 pkg_info
= os
.path
.join(conf_dir
, "PKG-INFO.in")
100 with io
.open(pkg_info
, "r", encoding
="utf-8") as h
:
102 for key
, value
in versions
.items():
103 text
= text
.replace("@%s@" % key
, value
)
106 message
= p
.parse(io
.StringIO(text
))
110 def pkg_config_get_install_hint(pkg_name
):
111 """Returns an installation hint for a pkg-config name or None"""
113 if not sys
.platform
.startswith("linux"):
116 if find_executable("apt"):
118 "gobject-introspection-1.0": "libgirepository1.0-dev",
119 "glib-2.0": "libglib2.0-dev",
120 "gio-2.0": "libglib2.0-dev",
121 "cairo": "libcairo2-dev",
122 "cairo-gobject": "libcairo2-dev",
123 "libffi": "libffi-dev",
125 if pkg_name
in dev_packages
:
126 return "sudo apt install %s" % dev_packages
[pkg_name
]
127 elif find_executable("dnf"):
129 "gobject-introspection-1.0": "gobject-introspection-devel",
130 "glib-2.0": "glib2-devel",
131 "gio-2.0": "glib2-devel",
132 "cairo": "cairo-devel",
133 "cairo-gobject": "cairo-gobject-devel",
134 "libffi": "libffi-devel",
136 if pkg_name
in dev_packages
:
137 return "sudo dnf install %s" % dev_packages
[pkg_name
]
140 class PkgConfigError(Exception):
144 class PkgConfigMissingPackageError(PkgConfigError
):
148 def _run_pkg_config(pkg_name
, args
, _cache
={}):
149 """Raises PkgConfigError"""
151 command
= tuple(["pkg-config"] + args
)
153 if command
not in _cache
:
155 result
= subprocess
.check_output(command
)
157 if e
.errno
== errno
.ENOENT
:
158 raise PkgConfigError(
159 "%r not found.\nArguments: %r" % (command
[0], command
))
160 raise PkgConfigError(e
)
161 except subprocess
.CalledProcessError
as e
:
163 subprocess
.check_output(["pkg-config", "--exists", pkg_name
])
164 except (subprocess
.CalledProcessError
, OSError):
165 raise PkgConfigMissingPackageError(e
)
167 raise PkgConfigError(e
)
169 _cache
[command
] = result
171 return _cache
[command
]
174 def _run_pkg_config_or_exit(pkg_name
, args
):
176 return _run_pkg_config(pkg_name
, args
)
177 except PkgConfigMissingPackageError
as e
:
178 hint
= pkg_config_get_install_hint(pkg_name
)
181 "%s\n\nTry installing it with: %r" % (e
, hint
))
184 except PkgConfigError
as e
:
188 def pkg_config_version_check(pkg_name
, version
):
189 _run_pkg_config_or_exit(pkg_name
, [
192 '%s >= %s' % (pkg_name
, version
),
196 def pkg_config_parse(opt
, pkg_name
):
197 ret
= _run_pkg_config_or_exit(pkg_name
, [opt
, pkg_name
])
199 if sys
.version_info
[0] == 3:
200 output
= ret
.decode()
204 return [x
.lstrip(opt
) for x
in output
.split()]
208 return [os
.path
.join(d
, e
) for e
in os
.listdir(d
) if e
.endswith(".h")]
211 def filter_compiler_arguments(compiler
, args
):
212 """Given a compiler instance and a list of compiler warning flags
213 returns the list of supported flags.
216 if compiler
.compiler_type
== "msvc":
222 def check_arguments(compiler
, args
):
223 p
= subprocess
.Popen(
224 [compiler
.compiler
[0]] + args
+ extra
+ ["-x", "c", "-E", "-"],
225 stdin
=subprocess
.PIPE
,
226 stdout
=subprocess
.PIPE
,
227 stderr
=subprocess
.PIPE
)
228 stdout
, stderr
= p
.communicate(b
"int i;\n")
229 if p
.returncode
!= 0:
230 text
= stderr
.decode("ascii", "replace")
231 return False, [a
for a
in args
if a
in text
]
235 def check_argument(compiler
, arg
):
236 return check_arguments(compiler
, [arg
])[0]
238 # clang doesn't error out for unknown options, force it to
239 if check_argument(compiler
, '-Werror=unknown-warning-option'):
240 extra
+= ['-Werror=unknown-warning-option']
241 if check_argument(compiler
, '-Werror=unused-command-line-argument'):
242 extra
+= ['-Werror=unused-command-line-argument']
244 # first try to remove all arguments contained in the error message
245 supported
= list(args
)
247 ok
, maybe_unknown
= check_arguments(compiler
, supported
)
250 elif not maybe_unknown
:
252 for unknown
in maybe_unknown
:
253 if not check_argument(compiler
, unknown
):
254 supported
.remove(unknown
)
256 # hm, didn't work, try each argument one by one
259 if check_argument(compiler
, arg
):
260 supported
.append(arg
)
264 class sdist_gnome(Command
):
265 description
= "Create a source tarball for GNOME"
268 def initialize_options(self
):
271 def finalize_options(self
):
275 # Don't use PEP 440 pre-release versions for GNOME releases
276 self
.distribution
.metadata
.version
= PYGOBJECT_VERISON
278 dist_dir
= tempfile
.mkdtemp()
280 cmd
= self
.reinitialize_command("sdist")
281 cmd
.dist_dir
= dist_dir
282 cmd
.ensure_finalized()
285 base_name
= self
.distribution
.get_fullname().lower()
286 cmd
.make_release_tree(base_name
, cmd
.filelist
.files
)
288 self
.make_archive(base_name
, "xztar", base_dir
=base_name
)
290 dir_util
.remove_tree(base_name
)
292 dir_util
.remove_tree(dist_dir
)
295 du_sdist
= get_command_class("sdist")
298 class distcheck(du_sdist
):
299 """Creates a tarball and does some additional sanity checks such as
300 checking if the tarball includes all files, builds successfully and
301 the tests suite passes.
304 def _check_manifest(self
):
305 # make sure MANIFEST.in includes all tracked files
306 assert self
.get_archive_files()
308 if subprocess
.call(["git", "status"],
309 stdout
=subprocess
.PIPE
,
310 stderr
=subprocess
.PIPE
) != 0:
313 included_files
= self
.filelist
.files
314 assert included_files
316 process
= subprocess
.Popen(
317 ["git", "ls-tree", "-r", "HEAD", "--name-only"],
318 stdout
=subprocess
.PIPE
, universal_newlines
=True)
319 out
, err
= process
.communicate()
320 assert process
.returncode
== 0
322 tracked_files
= out
.splitlines()
324 f
for f
in tracked_files
325 if os
.path
.basename(f
) not in [".gitignore"]]
327 diff
= set(tracked_files
) - set(included_files
)
329 "Not all tracked files included in tarball, check MANIFEST.in",
332 def _check_dist(self
):
333 # make sure the tarball builds
334 assert self
.get_archive_files()
336 distcheck_dir
= os
.path
.abspath(
337 os
.path
.join(self
.dist_dir
, "distcheck"))
338 if os
.path
.exists(distcheck_dir
):
339 dir_util
.remove_tree(distcheck_dir
)
340 self
.mkpath(distcheck_dir
)
342 archive
= self
.get_archive_files()[0]
343 tfile
= tarfile
.open(archive
, "r:gz")
344 tfile
.extractall(distcheck_dir
)
347 name
= self
.distribution
.get_fullname()
348 extract_dir
= os
.path
.join(distcheck_dir
, name
)
350 old_pwd
= os
.getcwd()
351 os
.chdir(extract_dir
)
353 self
.spawn([sys
.executable
, "setup.py", "build"])
354 self
.spawn([sys
.executable
, "setup.py", "install",
356 os
.path
.join(distcheck_dir
, "prefix"),
358 os
.path
.join(distcheck_dir
, "log.txt"),
360 self
.spawn([sys
.executable
, "setup.py", "test"])
366 self
._check
_manifest
()
370 class build_tests(Command
):
371 description
= "build test libraries and extensions"
373 ("force", "f", "force a rebuild"),
376 def initialize_options(self
):
377 self
.build_temp
= None
378 self
.build_base
= None
381 def finalize_options(self
):
382 self
.set_undefined_options(
384 ('build_temp', 'build_temp'))
385 self
.set_undefined_options(
387 ('build_base', 'build_base'))
389 def _newer_group(self
, sources
, *targets
):
392 from distutils
.dep_util
import newer_group
397 for target
in targets
:
398 if not newer_group(sources
, target
):
403 cmd
= self
.reinitialize_command("build_ext")
405 cmd
.force
= self
.force
406 cmd
.ensure_finalized()
409 gidatadir
= pkg_config_parse(
410 "--variable=gidatadir", "gobject-introspection-1.0")[0]
411 g_ir_scanner
= pkg_config_parse(
412 "--variable=g_ir_scanner", "gobject-introspection-1.0")[0]
413 g_ir_compiler
= pkg_config_parse(
414 "--variable=g_ir_compiler", "gobject-introspection-1.0")[0]
416 script_dir
= get_script_dir()
417 gi_dir
= os
.path
.join(script_dir
, "gi")
418 tests_dir
= os
.path
.join(script_dir
, "tests")
419 gi_tests_dir
= os
.path
.join(gidatadir
, "tests")
421 schema_xml
= os
.path
.join(tests_dir
, "org.gnome.test.gschema.xml")
422 schema_bin
= os
.path
.join(tests_dir
, "gschemas.compiled")
423 if self
._newer
_group
([schema_xml
], schema_bin
):
424 subprocess
.check_call([
425 "glib-compile-schemas",
426 "--targetdir=%s" % tests_dir
,
427 "--schema-file=%s" % schema_xml
,
430 compiler
= new_compiler()
431 customize_compiler(compiler
)
434 compiler
.shared_lib_extension
= ".dll"
435 elif sys
.platform
== "darwin":
436 compiler
.shared_lib_extension
= ".dylib"
437 if "-bundle" in compiler
.linker_so
:
438 compiler
.linker_so
= list(compiler
.linker_so
)
439 i
= compiler
.linker_so
.index("-bundle")
440 compiler
.linker_so
[i
] = "-dynamiclib"
442 compiler
.shared_lib_extension
= ".so"
445 if compiler
.compiler_type
== "msvc":
446 raise Exception("MSVC support not implemented")
448 libname
= compiler
.shared_object_filename(ext
.name
)
449 ext_paths
= [os
.path
.join(tests_dir
, libname
)]
451 implibname
= libname
+ ".a"
452 ext_paths
.append(os
.path
.join(tests_dir
, implibname
))
454 if self
._newer
_group
(ext
.sources
+ ext
.depends
, *ext_paths
):
455 objects
= compiler
.compile(
457 output_dir
=self
.build_temp
,
458 include_dirs
=ext
.include_dirs
)
461 postargs
= ["-Wl,--out-implib=%s" %
462 os
.path
.join(tests_dir
, implibname
)]
466 compiler
.link_shared_object(
468 compiler
.shared_object_filename(ext
.name
),
469 output_dir
=tests_dir
,
470 libraries
=ext
.libraries
,
471 library_dirs
=ext
.library_dirs
,
472 extra_postargs
=postargs
)
477 name
='libgimarshallingtests',
479 os
.path
.join(gi_tests_dir
, "gimarshallingtests.c"),
480 os
.path
.join(tests_dir
, "gimarshallingtestsextra.c"),
487 os
.path
.join(gi_tests_dir
, "gimarshallingtests.h"),
488 os
.path
.join(tests_dir
, "gimarshallingtestsextra.h"),
491 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "glib-2.0")
492 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "gio-2.0")
493 ext_paths
= build_ext(ext
)
495 gir_path
= os
.path
.join(tests_dir
, "GIMarshallingTests-1.0.gir")
496 typelib_path
= os
.path
.join(
497 tests_dir
, "GIMarshallingTests-1.0.typelib")
499 if self
._newer
_group
(ext_paths
, gir_path
):
500 subprocess
.check_call([
504 "--namespace=GIMarshallingTests",
506 "--symbol-prefix=gi_marshalling_tests",
509 "--library-path=%s" % tests_dir
,
510 "--library=gimarshallingtests",
514 "-I%s" % gi_tests_dir
,
516 "--output=%s" % gir_path
,
517 ] + ext
.sources
+ ext
.depends
)
519 if self
._newer
_group
([gir_path
], typelib_path
):
520 subprocess
.check_call([
523 "--output=%s" % typelib_path
,
529 os
.path
.join(gi_tests_dir
, "regress.c"),
530 os
.path
.join(tests_dir
, "regressextra.c"),
536 os
.path
.join(gi_tests_dir
, "regress.h"),
537 os
.path
.join(tests_dir
, "regressextra.h"),
540 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "glib-2.0")
541 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "gio-2.0")
542 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo")
543 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo-gobject")
544 ext_paths
= build_ext(ext
)
546 gir_path
= os
.path
.join(tests_dir
, "Regress-1.0.gir")
547 typelib_path
= os
.path
.join(tests_dir
, "Regress-1.0.typelib")
549 if self
._newer
_group
(ext_paths
, gir_path
):
550 subprocess
.check_call([
553 "--include=cairo-1.0",
555 "--namespace=Regress",
559 "--library-path=%s" % tests_dir
,
564 "--pkg=cairo-gobject",
565 "--output=%s" % gir_path
,
566 ] + ext
.sources
+ ext
.depends
)
568 if self
._newer
_group
([gir_path
], typelib_path
):
569 subprocess
.check_call([
572 "--output=%s" % typelib_path
,
576 name
='tests.testhelper',
578 os
.path
.join(tests_dir
, "testhelpermodule.c"),
579 os
.path
.join(tests_dir
, "test-floating.c"),
580 os
.path
.join(tests_dir
, "test-thread.c"),
581 os
.path
.join(tests_dir
, "test-unknown.c"),
587 depends
=list_headers(gi_dir
) + list_headers(tests_dir
),
588 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
590 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "glib-2.0")
591 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "gio-2.0")
592 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo")
593 add_ext_compiler_flags(ext
, compiler
)
595 dist
= Distribution({"ext_modules": [ext
]})
597 build_cmd
= dist
.get_command_obj("build")
598 build_cmd
.build_base
= os
.path
.join(self
.build_base
, "pygobject_tests")
599 build_cmd
.ensure_finalized()
601 cmd
= dist
.get_command_obj("build_ext")
603 cmd
.force
= self
.force
604 cmd
.ensure_finalized()
608 def get_suppression_files_for_prefix(prefix
):
609 """Returns a list of valgrind suppression files for a given prefix"""
611 # Most specific first (/usr/share/doc is Fedora, /usr/lib is Debian)
612 # Take the first one found
613 major
= str(sys
.version_info
[0])
614 minor
= str(sys
.version_info
[1])
618 prefix
, "share", "doc", "python%s%s" % (major
, minor
),
619 "valgrind-python.supp"))
621 os
.path
.join(prefix
, "lib", "valgrind", "python%s.supp" % major
))
624 prefix
, "share", "doc", "python%s-devel" % major
,
625 "valgrind-python.supp"))
626 pyfiles
.append(os
.path
.join(prefix
, "lib", "valgrind", "python.supp"))
630 if os
.path
.isfile(f
):
634 files
.append(os
.path
.join(
635 prefix
, "share", "glib-2.0", "valgrind", "glib.supp"))
636 return [f
for f
in files
if os
.path
.isfile(f
)]
639 def get_real_prefix():
640 """Returns the base Python prefix, even in a virtualenv/venv"""
642 return getattr(sys
, "base_prefix", getattr(sys
, "real_prefix", sys
.prefix
))
645 def get_suppression_files():
646 """Returns a list of valgrind suppression files"""
651 pkg_config_parse("--variable=prefix", "glib-2.0")[0],
655 for prefix
in prefixes
:
656 files
.extend(get_suppression_files_for_prefix(prefix
))
658 files
.append(os
.path
.join(get_script_dir(), "tests", "valgrind.supp"))
659 return sorted(set(files
))
664 ("valgrind", None, "run tests under valgrind"),
665 ("valgrind-log-file=", None, "save logs instead of printing them"),
666 ("gdb", None, "run tests under gdb"),
667 ("no-capture", "s", "don't capture test output"),
670 def initialize_options(self
):
672 self
.valgrind_log_file
= None
674 self
.no_capture
= None
676 def finalize_options(self
):
677 self
.valgrind
= bool(self
.valgrind
)
678 if self
.valgrind_log_file
and not self
.valgrind
:
679 raise DistutilsOptionError("valgrind not enabled")
680 self
.gdb
= bool(self
.gdb
)
681 self
.no_capture
= bool(self
.no_capture
)
684 cmd
= self
.reinitialize_command("build_tests")
685 cmd
.ensure_finalized()
688 env
= os
.environ
.copy()
689 env
.pop("MSYSTEM", None)
692 env
["PYGI_TEST_VERBOSE"] = "1"
694 env
["MALLOC_PERTURB_"] = "85"
695 env
["MALLOC_CHECK_"] = "3"
696 env
["G_SLICE"] = "debug-blocks"
701 env
["G_SLICE"] = "always-malloc"
702 env
["G_DEBUG"] = "gc-friendly"
703 env
["PYTHONMALLOC"] = "malloc"
706 "valgrind", "--leak-check=full", "--show-possibly-lost=no",
707 "--num-callers=20", "--child-silent-after-fork=yes",
708 ] + ["--suppressions=" + f
for f
in get_suppression_files()]
710 if self
.valgrind_log_file
:
711 pre_args
+= ["--log-file=" + self
.valgrind_log_file
]
714 env
["PYGI_TEST_GDB"] = "1"
715 pre_args
+= ["gdb", "--args"]
718 log
.info(" ".join(pre_args
))
720 tests_dir
= os
.path
.join(get_script_dir(), "tests")
721 sys
.exit(subprocess
.call(pre_args
+ [
723 os
.path
.join(tests_dir
, "runtests.py"),
727 class quality(Command
):
728 description
= "run code quality tests"
731 def initialize_options(self
):
734 def finalize_options(self
):
738 status
= subprocess
.call([
739 sys
.executable
, "-m", "flake8",
740 ], cwd
=get_script_dir())
742 raise SystemExit(status
)
745 def get_script_dir():
746 return os
.path
.dirname(os
.path
.realpath(__file__
))
749 def get_pycairo_include_dir():
750 """Returns the best guess at where to find the pycairo headers.
751 A bit convoluted because we have to deal with multiple pycairo
754 Raises if pycairo isn't found or it's too old.
757 pkg_config_name
= get_pycairo_pkg_config_name()
758 min_version
= get_version_requirement(pkg_config_name
)
759 min_version_info
= tuple(int(p
) for p
in min_version
.split("."))
761 def check_path(include_dir
):
762 log
.info("pycairo: trying include directory: %r" % include_dir
)
763 header_path
= os
.path
.join(include_dir
, "%s.h" % pkg_config_name
)
764 if os
.path
.exists(header_path
):
765 log
.info("pycairo: found %r" % header_path
)
767 log
.info("pycairo: header file (%r) not found" % header_path
)
770 def find_path(paths
):
771 for p
in reversed(paths
):
776 log
.info("pycairo: new API")
779 if cairo
.version_info
< min_version_info
:
780 raise DistutilsSetupError(
781 "pycairo >= %s required, %s found." % (
782 min_version
, ".".join(map(str, cairo
.version_info
))))
784 if hasattr(cairo
, "get_include"):
785 return [cairo
.get_include()]
786 log
.info("pycairo: no get_include()")
790 log
.info("pycairo: old API")
794 if cairo
.version_info
< min_version_info
:
795 raise DistutilsSetupError(
796 "pycairo >= %s required, %s found." % (
797 min_version
, ".".join(map(str, cairo
.version_info
))))
799 location
= os
.path
.dirname(os
.path
.abspath(cairo
.__path
__[0]))
800 log
.info("pycairo: found %r" % location
)
802 def samefile(src
, dst
):
803 # Python 2 on Windows doesn't have os.path.samefile, so we have to
805 if hasattr(os
.path
, "samefile"):
806 return os
.path
.samefile(src
, dst
)
809 return (os
.path
.normcase(os
.path
.abspath(src
)) ==
810 os
.path
.normcase(os
.path
.abspath(dst
)))
812 def get_sys_path(location
, name
):
813 # Returns the sysconfig path for a distribution, or None
814 for scheme
in sysconfig
.get_scheme_names():
815 for path_type
in ["platlib", "purelib"]:
816 path
= sysconfig
.get_path(path_type
, scheme
)
818 if samefile(path
, location
):
819 return sysconfig
.get_path(name
, scheme
)
820 except EnvironmentError:
823 data_path
= get_sys_path(location
, "data") or sys
.prefix
824 return [os
.path
.join(data_path
, "include", "pycairo")]
826 def find_pkg_config():
827 log
.info("pycairo: pkg-config")
828 pkg_config_version_check(pkg_config_name
, min_version
)
829 return pkg_config_parse("--cflags-only-I", pkg_config_name
)
831 # First the new get_include() API added in >1.15.6
832 include_dir
= find_path(find_new_api())
833 if include_dir
is not None:
836 # Then try to find it in the data prefix based on the module path.
837 # This works with many virtualenv/userdir setups, but not all apparently,
838 # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
839 include_dir
= find_path(find_old_api())
840 if include_dir
is not None:
843 # Finally, fall back to pkg-config
844 include_dir
= find_path(find_pkg_config())
845 if include_dir
is not None:
848 raise DistutilsSetupError("Could not find pycairo headers")
851 def add_ext_pkg_config_dep(ext
, compiler_type
, name
):
853 "glib-2.0": ["glib-2.0"],
854 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
855 "gobject-introspection-1.0":
856 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
859 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
863 def add(target
, new
):
865 if entry
not in target
:
868 fallback_libs
= msvc_libraries
[name
]
869 if compiler_type
== "msvc":
870 # assume that INCLUDE and LIB contains the right paths
871 add(ext
.libraries
, fallback_libs
)
873 min_version
= get_version_requirement(name
)
874 pkg_config_version_check(name
, min_version
)
875 add(ext
.include_dirs
, pkg_config_parse("--cflags-only-I", name
))
876 add(ext
.library_dirs
, pkg_config_parse("--libs-only-L", name
))
877 add(ext
.libraries
, pkg_config_parse("--libs-only-l", name
))
880 def add_ext_compiler_flags(ext
, compiler
, _cache
={}):
881 cache_key
= compiler
.compiler
[0]
882 if cache_key
not in _cache
:
888 "-Wdeclaration-after-statement",
889 "-Wduplicated-branches",
892 "-Wformat-nonliteral",
894 "-Wimplicit-function-declaration",
896 "-Wjump-misses-init",
898 "-Wmissing-declarations",
899 "-Wmissing-format-attribute",
900 "-Wmissing-include-dirs",
901 "-Wmissing-noreturn",
902 "-Wmissing-prototypes",
904 "-Wnull-dereference",
905 "-Wold-style-definition",
913 "-Wstrict-prototypes",
915 "-Wunused-but-set-variable",
920 if sys
.version_info
[:2] != (3, 4):
926 "-Wno-incompatible-pointer-types-discards-qualifiers",
927 "-Wno-missing-field-initializers",
928 "-Wno-unused-parameter",
929 "-Wno-discarded-qualifiers",
930 "-Wno-sign-conversion",
931 "-Wno-cast-function-type",
934 # silence clang for unused gcc CFLAGS added by Debian
936 "-Wno-unused-command-line-argument",
940 "-fno-strict-aliasing",
941 "-fvisibility=hidden",
944 # force GCC to use colors
945 if hasattr(sys
.stdout
, "isatty") and sys
.stdout
.isatty():
946 args
.append("-fdiagnostics-color")
948 _cache
[cache_key
] = filter_compiler_arguments(compiler
, args
)
950 ext
.extra_compile_args
+= _cache
[cache_key
]
953 du_build_ext
= get_command_class("build_ext")
956 class build_ext(du_build_ext
):
958 def initialize_options(self
):
959 du_build_ext
.initialize_options(self
)
960 self
.compiler_type
= None
962 def finalize_options(self
):
963 du_build_ext
.finalize_options(self
)
964 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
966 def _write_config_h(self
):
967 script_dir
= get_script_dir()
968 target
= os
.path
.join(script_dir
, "config.h")
969 versions
= get_versions()
971 /* Configuration header created by setup.py - do not edit */
975 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
976 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
977 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
978 #define VERSION "%(VERSION)s"
980 #endif /* _CONFIG_H */
984 with io
.open(target
, 'r', encoding
="utf-8") as h
:
985 if h
.read() == content
:
987 except EnvironmentError:
990 with io
.open(target
, 'w', encoding
="utf-8") as h
:
993 def _setup_extensions(self
):
994 ext
= {e
.name
: e
for e
in self
.extensions
}
996 compiler
= new_compiler(compiler
=self
.compiler
)
997 customize_compiler(compiler
)
999 def add_dependency(ext
, name
):
1000 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, name
)
1002 def add_pycairo(ext
):
1003 ext
.include_dirs
+= [get_pycairo_include_dir()]
1005 gi_ext
= ext
["gi._gi"]
1006 add_dependency(gi_ext
, "glib-2.0")
1007 add_dependency(gi_ext
, "gio-2.0")
1008 add_dependency(gi_ext
, "gobject-introspection-1.0")
1009 add_dependency(gi_ext
, "libffi")
1010 add_ext_compiler_flags(gi_ext
, compiler
)
1012 gi_cairo_ext
= ext
["gi._gi_cairo"]
1013 add_dependency(gi_cairo_ext
, "glib-2.0")
1014 add_dependency(gi_cairo_ext
, "gio-2.0")
1015 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
1016 add_dependency(gi_cairo_ext
, "libffi")
1017 add_dependency(gi_cairo_ext
, "cairo")
1018 add_dependency(gi_cairo_ext
, "cairo-gobject")
1019 add_pycairo(gi_cairo_ext
)
1020 add_ext_compiler_flags(gi_cairo_ext
, compiler
)
1023 self
._write
_config
_h
()
1024 self
._setup
_extensions
()
1025 du_build_ext
.run(self
)
1028 class install_pkgconfig(Command
):
1029 description
= "install .pc file"
1032 def initialize_options(self
):
1033 self
.install_base
= None
1034 self
.install_platbase
= None
1035 self
.install_data
= None
1036 self
.compiler_type
= None
1039 def finalize_options(self
):
1040 self
.set_undefined_options(
1042 ('install_base', 'install_base'),
1043 ('install_data', 'install_data'),
1044 ('install_platbase', 'install_platbase'),
1047 self
.set_undefined_options(
1049 ('compiler_type', 'compiler_type'),
1052 def get_outputs(self
):
1053 return self
.outfiles
1055 def get_inputs(self
):
1059 cmd
= self
.distribution
.get_command_obj("bdist_wheel", create
=False)
1062 "Python wheels and pkg-config is not compatible. "
1063 "No pkg-config file will be included in the wheel. Install "
1064 "from source if you need one.")
1067 if self
.compiler_type
== "msvc":
1070 script_dir
= get_script_dir()
1071 pkgconfig_in
= os
.path
.join(script_dir
, "pygobject-3.0.pc.in")
1072 with io
.open(pkgconfig_in
, "r", encoding
="utf-8") as h
:
1076 "prefix": self
.install_base
,
1077 "exec_prefix": self
.install_platbase
,
1078 "includedir": "${prefix}/include",
1079 "datarootdir": "${prefix}/share",
1080 "datadir": "${datarootdir}",
1081 "VERSION": PYGOBJECT_VERISON
,
1083 for key
, value
in config
.items():
1084 content
= content
.replace("@%s@" % key
, value
)
1086 libdir
= os
.path
.dirname(get_python_lib(True, True, self
.install_data
))
1087 pkgconfig_dir
= os
.path
.join(libdir
, "pkgconfig")
1088 self
.mkpath(pkgconfig_dir
)
1089 target
= os
.path
.join(pkgconfig_dir
, "pygobject-3.0.pc")
1090 with io
.open(target
, "w", encoding
="utf-8") as h
:
1092 self
.outfiles
.append(target
)
1095 du_install
= get_command_class("install")
1098 class install(du_install
):
1100 sub_commands
= du_install
.sub_commands
+ [
1101 ("install_pkgconfig", lambda self
: True),
1106 script_dir
= get_script_dir()
1107 pkginfo
= parse_pkg_info(script_dir
)
1108 gi_dir
= os
.path
.join(script_dir
, "gi")
1111 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
1112 if os
.path
.splitext(n
)[-1] == ".c"
1114 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
1115 for s
in cairo_sources
:
1118 readme
= os
.path
.join(script_dir
, "README.rst")
1119 with io
.open(readme
, encoding
="utf-8") as h
:
1120 long_description
= h
.read()
1125 include_dirs
=[script_dir
, gi_dir
],
1126 depends
=list_headers(script_dir
) + list_headers(gi_dir
),
1127 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
1130 gi_cairo_ext
= Extension(
1131 name
='gi._gi_cairo',
1132 sources
=cairo_sources
,
1133 include_dirs
=[script_dir
, gi_dir
],
1134 depends
=list_headers(script_dir
) + list_headers(gi_dir
),
1135 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
1138 version
= pkginfo
["Version"]
1139 if is_dev_version():
1140 # This makes it a PEP 440 pre-release and pip will only install it from
1141 # PyPI in case --pre is passed.
1145 name
=pkginfo
["Name"],
1147 description
=pkginfo
["Summary"],
1148 url
=pkginfo
["Home-page"],
1149 author
=pkginfo
["Author"],
1150 author_email
=pkginfo
["Author-email"],
1151 maintainer
=pkginfo
["Maintainer"],
1152 maintainer_email
=pkginfo
["Maintainer-email"],
1153 license
=pkginfo
["License"],
1154 long_description
=long_description
,
1155 platforms
=pkginfo
.get_all("Platform"),
1156 classifiers
=pkginfo
.get_all("Classifier"),
1168 "build_ext": build_ext
,
1169 "distcheck": distcheck
,
1170 "sdist_gnome": sdist_gnome
,
1171 "build_tests": build_tests
,
1175 "install_pkgconfig": install_pkgconfig
,
1178 "pycairo>=%s" % get_version_requirement(
1179 get_pycairo_pkg_config_name()),
1182 ('include/pygobject-3.0', ['gi/pygobject.h']),
1188 if __name__
== "__main__":