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.0"
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()
323 for ignore
in [".gitignore"]:
324 if ignore
in tracked_files
:
325 tracked_files
.remove(ignore
)
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",
513 "--output=%s" % gir_path
,
514 ] + ext
.sources
+ ext
.depends
)
516 if self
._newer
_group
([gir_path
], typelib_path
):
517 subprocess
.check_call([
520 "--output=%s" % typelib_path
,
526 os
.path
.join(gi_tests_dir
, "regress.c"),
527 os
.path
.join(tests_dir
, "regressextra.c"),
533 os
.path
.join(gi_tests_dir
, "regress.h"),
534 os
.path
.join(tests_dir
, "regressextra.h"),
537 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "glib-2.0")
538 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "gio-2.0")
539 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo")
540 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo-gobject")
541 ext_paths
= build_ext(ext
)
543 gir_path
= os
.path
.join(tests_dir
, "Regress-1.0.gir")
544 typelib_path
= os
.path
.join(tests_dir
, "Regress-1.0.typelib")
546 if self
._newer
_group
(ext_paths
, gir_path
):
547 subprocess
.check_call([
550 "--include=cairo-1.0",
552 "--namespace=Regress",
556 "--library-path=%s" % tests_dir
,
561 "--pkg=cairo-gobject",
562 "--output=%s" % gir_path
,
563 ] + ext
.sources
+ ext
.depends
)
565 if self
._newer
_group
([gir_path
], typelib_path
):
566 subprocess
.check_call([
569 "--output=%s" % typelib_path
,
573 name
='tests.testhelper',
575 os
.path
.join(tests_dir
, "testhelpermodule.c"),
576 os
.path
.join(tests_dir
, "test-floating.c"),
577 os
.path
.join(tests_dir
, "test-thread.c"),
578 os
.path
.join(tests_dir
, "test-unknown.c"),
584 depends
=list_headers(gi_dir
) + list_headers(tests_dir
),
585 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
587 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "glib-2.0")
588 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "gio-2.0")
589 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, "cairo")
590 add_ext_compiler_flags(ext
, compiler
)
592 dist
= Distribution({"ext_modules": [ext
]})
594 build_cmd
= dist
.get_command_obj("build")
595 build_cmd
.build_base
= os
.path
.join(self
.build_base
, "pygobject_tests")
596 build_cmd
.ensure_finalized()
598 cmd
= dist
.get_command_obj("build_ext")
600 cmd
.force
= self
.force
601 cmd
.ensure_finalized()
605 def get_suppression_files_for_prefix(prefix
):
606 """Returns a list of valgrind suppression files for a given prefix"""
608 # Most specific first (/usr/share/doc is Fedora, /usr/lib is Debian)
609 # Take the first one found
610 major
= str(sys
.version_info
[0])
611 minor
= str(sys
.version_info
[1])
615 prefix
, "share", "doc", "python%s%s" % (major
, minor
),
616 "valgrind-python.supp"))
618 os
.path
.join(prefix
, "lib", "valgrind", "python%s.supp" % major
))
621 prefix
, "share", "doc", "python%s-devel" % major
,
622 "valgrind-python.supp"))
623 pyfiles
.append(os
.path
.join(prefix
, "lib", "valgrind", "python.supp"))
627 if os
.path
.isfile(f
):
631 files
.append(os
.path
.join(
632 prefix
, "share", "glib-2.0", "valgrind", "glib.supp"))
633 return [f
for f
in files
if os
.path
.isfile(f
)]
636 def get_real_prefix():
637 """Returns the base Python prefix, even in a virtualenv/venv"""
639 return getattr(sys
, "base_prefix", getattr(sys
, "real_prefix", sys
.prefix
))
642 def get_suppression_files():
643 """Returns a list of valgrind suppression files"""
648 pkg_config_parse("--variable=prefix", "glib-2.0")[0],
652 for prefix
in prefixes
:
653 files
.extend(get_suppression_files_for_prefix(prefix
))
654 return sorted(set(files
))
659 ("valgrind", None, "run tests under valgrind"),
660 ("valgrind-log-file=", None, "save logs instead of printing them"),
661 ("gdb", None, "run tests under gdb"),
662 ("no-capture", "s", "don't capture test output"),
665 def initialize_options(self
):
667 self
.valgrind_log_file
= None
669 self
.no_capture
= None
671 def finalize_options(self
):
672 self
.valgrind
= bool(self
.valgrind
)
673 if self
.valgrind_log_file
and not self
.valgrind
:
674 raise DistutilsOptionError("valgrind not enabled")
675 self
.gdb
= bool(self
.gdb
)
676 self
.no_capture
= bool(self
.no_capture
)
679 cmd
= self
.reinitialize_command("build_tests")
680 cmd
.ensure_finalized()
683 env
= os
.environ
.copy()
684 env
.pop("MSYSTEM", None)
687 env
["PYGI_TEST_VERBOSE"] = "1"
689 env
["MALLOC_PERTURB_"] = "85"
690 env
["MALLOC_CHECK_"] = "3"
691 env
["G_SLICE"] = "debug-blocks"
696 env
["G_SLICE"] = "always-malloc"
697 env
["G_DEBUG"] = "gc-friendly"
698 env
["PYTHONMALLOC"] = "malloc"
701 "valgrind", "--leak-check=full", "--show-possibly-lost=no",
702 "--num-callers=20", "--child-silent-after-fork=yes",
703 ] + ["--suppressions=" + f
for f
in get_suppression_files()]
705 if self
.valgrind_log_file
:
706 pre_args
+= ["--log-file=" + self
.valgrind_log_file
]
709 env
["PYGI_TEST_GDB"] = "1"
710 pre_args
+= ["gdb", "--args"]
713 log
.info(" ".join(pre_args
))
715 tests_dir
= os
.path
.join(get_script_dir(), "tests")
716 sys
.exit(subprocess
.call(pre_args
+ [
718 os
.path
.join(tests_dir
, "runtests.py"),
722 class quality(Command
):
723 description
= "run code quality tests"
726 def initialize_options(self
):
729 def finalize_options(self
):
733 status
= subprocess
.call([
734 sys
.executable
, "-m", "flake8",
735 ], cwd
=get_script_dir())
737 raise SystemExit(status
)
740 def get_script_dir():
741 return os
.path
.dirname(os
.path
.realpath(__file__
))
744 def get_pycairo_include_dir():
745 """Returns the best guess at where to find the pycairo headers.
746 A bit convoluted because we have to deal with multiple pycairo
749 Raises if pycairo isn't found or it's too old.
752 pkg_config_name
= get_pycairo_pkg_config_name()
753 min_version
= get_version_requirement(pkg_config_name
)
754 min_version_info
= tuple(int(p
) for p
in min_version
.split("."))
756 def check_path(include_dir
):
757 log
.info("pycairo: trying include directory: %r" % include_dir
)
758 header_path
= os
.path
.join(include_dir
, "%s.h" % pkg_config_name
)
759 if os
.path
.exists(header_path
):
760 log
.info("pycairo: found %r" % header_path
)
762 log
.info("pycairo: header file (%r) not found" % header_path
)
765 def find_path(paths
):
766 for p
in reversed(paths
):
771 log
.info("pycairo: new API")
774 if cairo
.version_info
< min_version_info
:
775 raise DistutilsSetupError(
776 "pycairo >= %s required, %s found." % (
777 min_version
, ".".join(map(str, cairo
.version_info
))))
779 if hasattr(cairo
, "get_include"):
780 return [cairo
.get_include()]
781 log
.info("pycairo: no get_include()")
785 log
.info("pycairo: old API")
789 if cairo
.version_info
< min_version_info
:
790 raise DistutilsSetupError(
791 "pycairo >= %s required, %s found." % (
792 min_version
, ".".join(map(str, cairo
.version_info
))))
794 location
= os
.path
.dirname(os
.path
.abspath(cairo
.__path
__[0]))
795 log
.info("pycairo: found %r" % location
)
797 def samefile(src
, dst
):
798 # Python 2 on Windows doesn't have os.path.samefile, so we have to
800 if hasattr(os
.path
, "samefile"):
801 return os
.path
.samefile(src
, dst
)
804 return (os
.path
.normcase(os
.path
.abspath(src
)) ==
805 os
.path
.normcase(os
.path
.abspath(dst
)))
807 def get_sys_path(location
, name
):
808 # Returns the sysconfig path for a distribution, or None
809 for scheme
in sysconfig
.get_scheme_names():
810 for path_type
in ["platlib", "purelib"]:
811 path
= sysconfig
.get_path(path_type
, scheme
)
813 if samefile(path
, location
):
814 return sysconfig
.get_path(name
, scheme
)
815 except EnvironmentError:
818 data_path
= get_sys_path(location
, "data") or sys
.prefix
819 return [os
.path
.join(data_path
, "include", "pycairo")]
821 def find_pkg_config():
822 log
.info("pycairo: pkg-config")
823 pkg_config_version_check(pkg_config_name
, min_version
)
824 return pkg_config_parse("--cflags-only-I", pkg_config_name
)
826 # First the new get_include() API added in >1.15.6
827 include_dir
= find_path(find_new_api())
828 if include_dir
is not None:
831 # Then try to find it in the data prefix based on the module path.
832 # This works with many virtualenv/userdir setups, but not all apparently,
833 # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
834 include_dir
= find_path(find_old_api())
835 if include_dir
is not None:
838 # Finally, fall back to pkg-config
839 include_dir
= find_path(find_pkg_config())
840 if include_dir
is not None:
843 raise DistutilsSetupError("Could not find pycairo headers")
846 def add_ext_pkg_config_dep(ext
, compiler_type
, name
):
848 "glib-2.0": ["glib-2.0"],
849 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
850 "gobject-introspection-1.0":
851 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
854 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
858 fallback_libs
= msvc_libraries
[name
]
859 if compiler_type
== "msvc":
860 # assume that INCLUDE and LIB contains the right paths
861 ext
.libraries
+= fallback_libs
863 min_version
= get_version_requirement(name
)
864 pkg_config_version_check(name
, min_version
)
865 ext
.include_dirs
+= pkg_config_parse("--cflags-only-I", name
)
866 ext
.library_dirs
+= pkg_config_parse("--libs-only-L", name
)
867 ext
.libraries
+= pkg_config_parse("--libs-only-l", name
)
870 def add_ext_compiler_flags(ext
, compiler
, _cache
={}):
871 cache_key
= compiler
.compiler
[0]
872 if cache_key
not in _cache
:
878 "-Wdeclaration-after-statement",
879 # "-Wdouble-promotion",
880 "-Wduplicated-branches",
881 # "-Wduplicated-cond",
884 "-Wformat-nonliteral",
886 "-Wimplicit-function-declaration",
889 "-Wjump-misses-init",
891 "-Wmissing-declarations",
892 "-Wmissing-format-attribute",
893 "-Wmissing-include-dirs",
894 "-Wmissing-noreturn",
895 "-Wmissing-prototypes",
897 "-Wnull-dereference",
898 "-Wold-style-definition",
906 "-Wstrict-prototypes",
908 "-Wunused-but-set-variable",
913 if sys
.version_info
[:2] != (3, 4):
919 "-Wno-incompatible-pointer-types-discards-qualifiers",
920 "-Wno-missing-field-initializers",
921 "-Wno-unused-parameter",
922 "-Wno-discarded-qualifiers",
923 "-Wno-sign-conversion",
926 # silence clang for unused gcc CFLAGS added by Debian
928 "-Wno-unused-command-line-argument",
932 "-fno-strict-aliasing",
933 "-fvisibility=hidden",
936 # force GCC to use colors
937 if hasattr(sys
.stdout
, "isatty") and sys
.stdout
.isatty():
938 args
.append("-fdiagnostics-color")
940 _cache
[cache_key
] = filter_compiler_arguments(compiler
, args
)
942 ext
.extra_compile_args
+= _cache
[cache_key
]
945 du_build_ext
= get_command_class("build_ext")
948 class build_ext(du_build_ext
):
950 def initialize_options(self
):
951 du_build_ext
.initialize_options(self
)
952 self
.compiler_type
= None
954 def finalize_options(self
):
955 du_build_ext
.finalize_options(self
)
956 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
958 def _write_config_h(self
):
959 script_dir
= get_script_dir()
960 target
= os
.path
.join(script_dir
, "config.h")
961 versions
= get_versions()
963 /* Configuration header created by setup.py - do not edit */
967 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
968 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
969 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
970 #define VERSION "%(VERSION)s"
972 #endif /* _CONFIG_H */
976 with io
.open(target
, 'r', encoding
="utf-8") as h
:
977 if h
.read() == content
:
979 except EnvironmentError:
982 with io
.open(target
, 'w', encoding
="utf-8") as h
:
985 def _setup_extensions(self
):
986 ext
= {e
.name
: e
for e
in self
.extensions
}
988 compiler
= new_compiler(compiler
=self
.compiler
)
989 customize_compiler(compiler
)
991 def add_dependency(ext
, name
):
992 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, name
)
994 def add_pycairo(ext
):
995 ext
.include_dirs
+= [get_pycairo_include_dir()]
997 gi_ext
= ext
["gi._gi"]
998 add_dependency(gi_ext
, "glib-2.0")
999 add_dependency(gi_ext
, "gio-2.0")
1000 add_dependency(gi_ext
, "gobject-introspection-1.0")
1001 add_dependency(gi_ext
, "libffi")
1002 add_ext_compiler_flags(gi_ext
, compiler
)
1004 gi_cairo_ext
= ext
["gi._gi_cairo"]
1005 add_dependency(gi_cairo_ext
, "glib-2.0")
1006 add_dependency(gi_cairo_ext
, "gio-2.0")
1007 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
1008 add_dependency(gi_cairo_ext
, "libffi")
1009 add_dependency(gi_cairo_ext
, "cairo")
1010 add_dependency(gi_cairo_ext
, "cairo-gobject")
1011 add_pycairo(gi_cairo_ext
)
1012 add_ext_compiler_flags(gi_cairo_ext
, compiler
)
1015 self
._write
_config
_h
()
1016 self
._setup
_extensions
()
1017 du_build_ext
.run(self
)
1020 class install_pkgconfig(Command
):
1021 description
= "install .pc file"
1024 def initialize_options(self
):
1025 self
.install_base
= None
1026 self
.install_platbase
= None
1027 self
.install_data
= None
1028 self
.compiler_type
= None
1031 def finalize_options(self
):
1032 self
.set_undefined_options(
1034 ('install_base', 'install_base'),
1035 ('install_data', 'install_data'),
1036 ('install_platbase', 'install_platbase'),
1039 self
.set_undefined_options(
1041 ('compiler_type', 'compiler_type'),
1044 def get_outputs(self
):
1045 return self
.outfiles
1047 def get_inputs(self
):
1051 cmd
= self
.distribution
.get_command_obj("bdist_wheel", create
=False)
1054 "Python wheels and pkg-config is not compatible. "
1055 "No pkg-config file will be included in the wheel. Install "
1056 "from source if you need one.")
1059 if self
.compiler_type
== "msvc":
1062 script_dir
= get_script_dir()
1063 pkgconfig_in
= os
.path
.join(script_dir
, "pygobject-3.0.pc.in")
1064 with io
.open(pkgconfig_in
, "r", encoding
="utf-8") as h
:
1068 "prefix": self
.install_base
,
1069 "exec_prefix": self
.install_platbase
,
1070 "includedir": "${prefix}/include",
1071 "datarootdir": "${prefix}/share",
1072 "datadir": "${datarootdir}",
1073 "VERSION": PYGOBJECT_VERISON
,
1075 for key
, value
in config
.items():
1076 content
= content
.replace("@%s@" % key
, value
)
1078 libdir
= os
.path
.dirname(get_python_lib(True, True, self
.install_data
))
1079 pkgconfig_dir
= os
.path
.join(libdir
, "pkgconfig")
1080 self
.mkpath(pkgconfig_dir
)
1081 target
= os
.path
.join(pkgconfig_dir
, "pygobject-3.0.pc")
1082 with io
.open(target
, "w", encoding
="utf-8") as h
:
1084 self
.outfiles
.append(target
)
1087 du_install
= get_command_class("install")
1090 class install(du_install
):
1092 sub_commands
= du_install
.sub_commands
+ [
1093 ("install_pkgconfig", lambda self
: True),
1098 script_dir
= get_script_dir()
1099 pkginfo
= parse_pkg_info(script_dir
)
1100 gi_dir
= os
.path
.join(script_dir
, "gi")
1103 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
1104 if os
.path
.splitext(n
)[-1] == ".c"
1106 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
1107 for s
in cairo_sources
:
1110 readme
= os
.path
.join(script_dir
, "README.rst")
1111 with io
.open(readme
, encoding
="utf-8") as h
:
1112 long_description
= h
.read()
1117 include_dirs
=[script_dir
, gi_dir
],
1118 depends
=list_headers(script_dir
) + list_headers(gi_dir
),
1119 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
1122 gi_cairo_ext
= Extension(
1123 name
='gi._gi_cairo',
1124 sources
=cairo_sources
,
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 version
= pkginfo
["Version"]
1131 if is_dev_version():
1132 # This makes it a PEP 440 pre-release and pip will only install it from
1133 # PyPI in case --pre is passed.
1137 name
=pkginfo
["Name"],
1139 description
=pkginfo
["Summary"],
1140 url
=pkginfo
["Home-page"],
1141 author
=pkginfo
["Author"],
1142 author_email
=pkginfo
["Author-email"],
1143 maintainer
=pkginfo
["Maintainer"],
1144 maintainer_email
=pkginfo
["Maintainer-email"],
1145 license
=pkginfo
["License"],
1146 long_description
=long_description
,
1147 platforms
=pkginfo
.get_all("Platform"),
1148 classifiers
=pkginfo
.get_all("Classifier"),
1160 "build_ext": build_ext
,
1161 "distcheck": distcheck
,
1162 "sdist_gnome": sdist_gnome
,
1163 "build_tests": build_tests
,
1167 "install_pkgconfig": install_pkgconfig
,
1170 "pycairo>=%s" % get_version_requirement(
1171 get_pycairo_pkg_config_name()),
1174 ('include/pygobject-3.0', ['gi/pygobject.h']),
1180 if __name__
== "__main__":