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()
607 ("valgrind", None, "run tests under valgrind"),
608 ("valgrind-log-file=", None, "save logs instead of printing them"),
609 ("gdb", None, "run tests under gdb"),
610 ("no-capture", "s", "don't capture test output"),
613 def initialize_options(self
):
615 self
.valgrind_log_file
= None
617 self
.no_capture
= None
619 def finalize_options(self
):
620 self
.valgrind
= bool(self
.valgrind
)
621 if self
.valgrind_log_file
and not self
.valgrind
:
622 raise DistutilsOptionError("valgrind not enabled")
623 self
.gdb
= bool(self
.gdb
)
624 self
.no_capture
= bool(self
.no_capture
)
627 cmd
= self
.reinitialize_command("build_tests")
628 cmd
.ensure_finalized()
631 env
= os
.environ
.copy()
632 env
.pop("MSYSTEM", None)
635 env
["PYGI_TEST_VERBOSE"] = "1"
637 env
["MALLOC_PERTURB_"] = "85"
638 env
["MALLOC_CHECK_"] = "3"
639 env
["G_SLICE"] = "debug-blocks"
641 def get_suppression_files():
643 if sys
.version_info
[0] == 2:
644 files
.append(os
.path
.join(
645 sys
.prefix
, "lib", "valgrind", "python.supp"))
647 files
.append(os
.path
.join(
648 sys
.prefix
, "lib", "valgrind", "python3.supp"))
649 files
.append(os
.path
.join(
650 sys
.prefix
, "share", "glib-2.0", "valgrind", "glib.supp"))
651 return [f
for f
in files
if os
.path
.isfile(f
)]
656 env
["G_SLICE"] = "always-malloc"
657 env
["G_DEBUG"] = "gc-friendly"
658 env
["PYTHONMALLOC"] = "malloc"
661 "valgrind", "--leak-check=full", "--show-possibly-lost=no",
662 "--num-callers=20", "--child-silent-after-fork=yes",
663 ] + ["--suppressions=" + f
for f
in get_suppression_files()]
665 if self
.valgrind_log_file
:
666 pre_args
+= ["--log-file=" + self
.valgrind_log_file
]
669 env
["PYGI_TEST_GDB"] = "1"
670 pre_args
+= ["gdb", "--args"]
673 log
.info(" ".join(pre_args
))
675 tests_dir
= os
.path
.join(get_script_dir(), "tests")
676 sys
.exit(subprocess
.call(pre_args
+ [
678 os
.path
.join(tests_dir
, "runtests.py"),
682 class quality(Command
):
683 description
= "run code quality tests"
686 def initialize_options(self
):
689 def finalize_options(self
):
693 status
= subprocess
.call([
694 sys
.executable
, "-m", "flake8",
695 ], cwd
=get_script_dir())
697 raise SystemExit(status
)
700 def get_script_dir():
701 return os
.path
.dirname(os
.path
.realpath(__file__
))
704 def get_pycairo_include_dir():
705 """Returns the best guess at where to find the pycairo headers.
706 A bit convoluted because we have to deal with multiple pycairo
709 Raises if pycairo isn't found or it's too old.
712 pkg_config_name
= get_pycairo_pkg_config_name()
713 min_version
= get_version_requirement(pkg_config_name
)
714 min_version_info
= tuple(int(p
) for p
in min_version
.split("."))
716 def check_path(include_dir
):
717 log
.info("pycairo: trying include directory: %r" % include_dir
)
718 header_path
= os
.path
.join(include_dir
, "%s.h" % pkg_config_name
)
719 if os
.path
.exists(header_path
):
720 log
.info("pycairo: found %r" % header_path
)
722 log
.info("pycairo: header file (%r) not found" % header_path
)
725 def find_path(paths
):
726 for p
in reversed(paths
):
731 log
.info("pycairo: new API")
734 if cairo
.version_info
< min_version_info
:
735 raise DistutilsSetupError(
736 "pycairo >= %s required, %s found." % (
737 min_version
, ".".join(map(str, cairo
.version_info
))))
739 if hasattr(cairo
, "get_include"):
740 return [cairo
.get_include()]
741 log
.info("pycairo: no get_include()")
745 log
.info("pycairo: old API")
749 if cairo
.version_info
< min_version_info
:
750 raise DistutilsSetupError(
751 "pycairo >= %s required, %s found." % (
752 min_version
, ".".join(map(str, cairo
.version_info
))))
754 location
= os
.path
.dirname(os
.path
.abspath(cairo
.__path
__[0]))
755 log
.info("pycairo: found %r" % location
)
757 def samefile(src
, dst
):
758 # Python 2 on Windows doesn't have os.path.samefile, so we have to
760 if hasattr(os
.path
, "samefile"):
761 return os
.path
.samefile(src
, dst
)
764 return (os
.path
.normcase(os
.path
.abspath(src
)) ==
765 os
.path
.normcase(os
.path
.abspath(dst
)))
767 def get_sys_path(location
, name
):
768 # Returns the sysconfig path for a distribution, or None
769 for scheme
in sysconfig
.get_scheme_names():
770 for path_type
in ["platlib", "purelib"]:
771 path
= sysconfig
.get_path(path_type
, scheme
)
773 if samefile(path
, location
):
774 return sysconfig
.get_path(name
, scheme
)
775 except EnvironmentError:
778 data_path
= get_sys_path(location
, "data") or sys
.prefix
779 return [os
.path
.join(data_path
, "include", "pycairo")]
781 def find_pkg_config():
782 log
.info("pycairo: pkg-config")
783 pkg_config_version_check(pkg_config_name
, min_version
)
784 return pkg_config_parse("--cflags-only-I", pkg_config_name
)
786 # First the new get_include() API added in >1.15.6
787 include_dir
= find_path(find_new_api())
788 if include_dir
is not None:
791 # Then try to find it in the data prefix based on the module path.
792 # This works with many virtualenv/userdir setups, but not all apparently,
793 # see https://gitlab.gnome.org/GNOME/pygobject/issues/150
794 include_dir
= find_path(find_old_api())
795 if include_dir
is not None:
798 # Finally, fall back to pkg-config
799 include_dir
= find_path(find_pkg_config())
800 if include_dir
is not None:
803 raise DistutilsSetupError("Could not find pycairo headers")
806 def add_ext_pkg_config_dep(ext
, compiler_type
, name
):
808 "glib-2.0": ["glib-2.0"],
809 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
810 "gobject-introspection-1.0":
811 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
814 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
818 fallback_libs
= msvc_libraries
[name
]
819 if compiler_type
== "msvc":
820 # assume that INCLUDE and LIB contains the right paths
821 ext
.libraries
+= fallback_libs
823 min_version
= get_version_requirement(name
)
824 pkg_config_version_check(name
, min_version
)
825 ext
.include_dirs
+= pkg_config_parse("--cflags-only-I", name
)
826 ext
.library_dirs
+= pkg_config_parse("--libs-only-L", name
)
827 ext
.libraries
+= pkg_config_parse("--libs-only-l", name
)
830 def add_ext_compiler_flags(ext
, compiler
, _cache
={}):
831 cache_key
= compiler
.compiler
[0]
832 if cache_key
not in _cache
:
838 "-Wdeclaration-after-statement",
839 # "-Wdouble-promotion",
840 "-Wduplicated-branches",
841 # "-Wduplicated-cond",
844 "-Wformat-nonliteral",
846 "-Wimplicit-function-declaration",
849 "-Wjump-misses-init",
851 "-Wmissing-declarations",
852 "-Wmissing-format-attribute",
853 "-Wmissing-include-dirs",
854 "-Wmissing-noreturn",
855 "-Wmissing-prototypes",
857 # "-Wnull-dereference",
858 "-Wold-style-definition",
866 "-Wstrict-prototypes",
869 "-Wunused-but-set-variable",
875 "-Wno-incompatible-pointer-types-discards-qualifiers",
876 "-Wno-missing-field-initializers",
877 "-Wno-unused-parameter",
878 "-Wno-discarded-qualifiers",
879 "-Wno-sign-conversion",
882 # silence clang for unused gcc CFLAGS added by Debian
884 "-Wno-unused-command-line-argument",
888 "-fno-strict-aliasing",
889 "-fvisibility=hidden",
892 _cache
[cache_key
] = filter_compiler_arguments(compiler
, args
)
894 ext
.extra_compile_args
+= _cache
[cache_key
]
897 du_build_ext
= get_command_class("build_ext")
900 class build_ext(du_build_ext
):
902 def initialize_options(self
):
903 du_build_ext
.initialize_options(self
)
904 self
.compiler_type
= None
906 def finalize_options(self
):
907 du_build_ext
.finalize_options(self
)
908 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
910 def _write_config_h(self
):
911 script_dir
= get_script_dir()
912 target
= os
.path
.join(script_dir
, "config.h")
913 versions
= get_versions()
915 /* Configuration header created by setup.py - do not edit */
919 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
920 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
921 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
922 #define VERSION "%(VERSION)s"
924 #endif /* _CONFIG_H */
928 with io
.open(target
, 'r', encoding
="utf-8") as h
:
929 if h
.read() == content
:
931 except EnvironmentError:
934 with io
.open(target
, 'w', encoding
="utf-8") as h
:
937 def _setup_extensions(self
):
938 ext
= {e
.name
: e
for e
in self
.extensions
}
940 compiler
= new_compiler(compiler
=self
.compiler
)
941 customize_compiler(compiler
)
943 def add_dependency(ext
, name
):
944 add_ext_pkg_config_dep(ext
, compiler
.compiler_type
, name
)
946 def add_pycairo(ext
):
947 ext
.include_dirs
+= [get_pycairo_include_dir()]
949 gi_ext
= ext
["gi._gi"]
950 add_dependency(gi_ext
, "glib-2.0")
951 add_dependency(gi_ext
, "gio-2.0")
952 add_dependency(gi_ext
, "gobject-introspection-1.0")
953 add_dependency(gi_ext
, "libffi")
954 add_ext_compiler_flags(gi_ext
, compiler
)
956 gi_cairo_ext
= ext
["gi._gi_cairo"]
957 add_dependency(gi_cairo_ext
, "glib-2.0")
958 add_dependency(gi_cairo_ext
, "gio-2.0")
959 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
960 add_dependency(gi_cairo_ext
, "libffi")
961 add_dependency(gi_cairo_ext
, "cairo")
962 add_dependency(gi_cairo_ext
, "cairo-gobject")
963 add_pycairo(gi_cairo_ext
)
964 add_ext_compiler_flags(gi_cairo_ext
, compiler
)
967 self
._write
_config
_h
()
968 self
._setup
_extensions
()
969 du_build_ext
.run(self
)
972 class install_pkgconfig(Command
):
973 description
= "install .pc file"
976 def initialize_options(self
):
977 self
.install_base
= None
978 self
.install_platbase
= None
979 self
.install_data
= None
980 self
.compiler_type
= None
983 def finalize_options(self
):
984 self
.set_undefined_options(
986 ('install_base', 'install_base'),
987 ('install_data', 'install_data'),
988 ('install_platbase', 'install_platbase'),
991 self
.set_undefined_options(
993 ('compiler_type', 'compiler_type'),
996 def get_outputs(self
):
999 def get_inputs(self
):
1003 cmd
= self
.distribution
.get_command_obj("bdist_wheel", create
=False)
1006 "Python wheels and pkg-config is not compatible. "
1007 "No pkg-config file will be included in the wheel. Install "
1008 "from source if you need one.")
1011 if self
.compiler_type
== "msvc":
1014 script_dir
= get_script_dir()
1015 pkgconfig_in
= os
.path
.join(script_dir
, "pygobject-3.0.pc.in")
1016 with io
.open(pkgconfig_in
, "r", encoding
="utf-8") as h
:
1020 "prefix": self
.install_base
,
1021 "exec_prefix": self
.install_platbase
,
1022 "includedir": "${prefix}/include",
1023 "datarootdir": "${prefix}/share",
1024 "datadir": "${datarootdir}",
1025 "VERSION": PYGOBJECT_VERISON
,
1027 for key
, value
in config
.items():
1028 content
= content
.replace("@%s@" % key
, value
)
1030 libdir
= os
.path
.dirname(get_python_lib(True, True, self
.install_data
))
1031 pkgconfig_dir
= os
.path
.join(libdir
, "pkgconfig")
1032 self
.mkpath(pkgconfig_dir
)
1033 target
= os
.path
.join(pkgconfig_dir
, "pygobject-3.0.pc")
1034 with io
.open(target
, "w", encoding
="utf-8") as h
:
1036 self
.outfiles
.append(target
)
1039 du_install
= get_command_class("install")
1042 class install(du_install
):
1044 sub_commands
= du_install
.sub_commands
+ [
1045 ("install_pkgconfig", lambda self
: True),
1050 script_dir
= get_script_dir()
1051 pkginfo
= parse_pkg_info(script_dir
)
1052 gi_dir
= os
.path
.join(script_dir
, "gi")
1055 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
1056 if os
.path
.splitext(n
)[-1] == ".c"
1058 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
1059 for s
in cairo_sources
:
1062 readme
= os
.path
.join(script_dir
, "README.rst")
1063 with io
.open(readme
, encoding
="utf-8") as h
:
1064 long_description
= h
.read()
1069 include_dirs
=[script_dir
, gi_dir
],
1070 depends
=list_headers(script_dir
) + list_headers(gi_dir
),
1071 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
1074 gi_cairo_ext
= Extension(
1075 name
='gi._gi_cairo',
1076 sources
=cairo_sources
,
1077 include_dirs
=[script_dir
, gi_dir
],
1078 depends
=list_headers(script_dir
) + list_headers(gi_dir
),
1079 define_macros
=[("PY_SSIZE_T_CLEAN", None)],
1082 version
= pkginfo
["Version"]
1083 if is_dev_version():
1084 # This makes it a PEP 440 pre-release and pip will only install it from
1085 # PyPI in case --pre is passed.
1089 name
=pkginfo
["Name"],
1091 description
=pkginfo
["Summary"],
1092 url
=pkginfo
["Home-page"],
1093 author
=pkginfo
["Author"],
1094 author_email
=pkginfo
["Author-email"],
1095 maintainer
=pkginfo
["Maintainer"],
1096 maintainer_email
=pkginfo
["Maintainer-email"],
1097 license
=pkginfo
["License"],
1098 long_description
=long_description
,
1099 platforms
=pkginfo
.get_all("Platform"),
1100 classifiers
=pkginfo
.get_all("Classifier"),
1112 "build_ext": build_ext
,
1113 "distcheck": distcheck
,
1114 "sdist_gnome": sdist_gnome
,
1115 "build_tests": build_tests
,
1119 "install_pkgconfig": install_pkgconfig
,
1122 "pycairo>=%s" % get_version_requirement(
1123 get_pycairo_pkg_config_name()),
1126 ('include/pygobject-3.0', ['gi/pygobject.h']),
1132 if __name__
== "__main__":