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
20 ATTENTION DISTRO PACKAGERS: This is not a valid replacement for autotools.
21 It does not install headers, pkgconfig files and does not support running
22 tests. Its main use case atm is installation in virtualenvs and via pip.
32 from email
import parser
34 from setuptools
import setup
, find_packages
35 from distutils
.core
import Extension
, Distribution
36 from distutils
.ccompiler
import new_compiler
37 from distutils
import dir_util
40 def get_command_class(name
):
41 # Returns the right class for either distutils or setuptools
42 return Distribution({}).get_command_class(name
)
45 def get_pycairo_pkg_config_name():
46 return "py3cairo" if sys
.version_info
[0] == 3 else "pycairo"
49 def get_version_requirement(conf_dir
, pkg_config_name
):
50 """Given a pkg-config module name gets the minimum version required"""
52 if pkg_config_name
in ["cairo", "cairo-gobject"]:
56 "gobject-introspection-1.0": "introspection",
59 get_pycairo_pkg_config_name(): "pycairo",
62 assert pkg_config_name
in mapping
64 configure_ac
= os
.path
.join(conf_dir
, "configure.ac")
65 with io
.open(configure_ac
, "r", encoding
="utf-8") as h
:
67 conf_name
= mapping
[pkg_config_name
]
69 r
"%s_required_version,\s*([\d\.]+)\)" % conf_name
, text
)
74 def parse_versions(conf_dir
):
75 configure_ac
= os
.path
.join(conf_dir
, "configure.ac")
76 with io
.open(configure_ac
, "r", encoding
="utf-8") as h
:
77 version
= re
.findall(r
"pygobject_[^\s]+_version,\s*(\d+)\)", h
.read())
78 assert len(version
) == 3
81 "PYGOBJECT_MAJOR_VERSION": version
[0],
82 "PYGOBJECT_MINOR_VERSION": version
[1],
83 "PYGOBJECT_MICRO_VERSION": version
[2],
84 "VERSION": ".".join(version
),
89 def parse_pkg_info(conf_dir
):
90 """Returns an email.message.Message instance containing the content
91 of the PKG-INFO file. The version info is parsed from configure.ac
94 versions
= parse_versions(conf_dir
)
96 pkg_info
= os
.path
.join(conf_dir
, "PKG-INFO.in")
97 with io
.open(pkg_info
, "r", encoding
="utf-8") as h
:
99 for key
, value
in versions
.items():
100 text
= text
.replace("@%s@" % key
, value
)
103 message
= p
.parse(io
.StringIO(text
))
107 def _run_pkg_config(args
):
108 command
= ["pkg-config"] + args
110 # Add $prefix/share/pkgconfig to PKG_CONFIG_PATH so we use the right
111 # pycairo in case we are in a virtualenv
112 env
= dict(os
.environ
)
113 paths
= env
.get("PKG_CONFIG_PATH", "").split(os
.pathsep
)
114 paths
= [p
for p
in paths
if p
]
115 paths
.insert(0, os
.path
.join(sys
.prefix
, "share", "pkgconfig"))
116 env
["PKG_CONFIG_PATH"] = os
.pathsep
.join(paths
)
119 return subprocess
.check_output(command
, env
=env
)
121 if e
.errno
== errno
.ENOENT
:
123 "%r not found.\nArguments: %r" % (command
[0], command
))
125 except subprocess
.CalledProcessError
as e
:
129 def pkg_config_version_check(pkg
, version
):
133 '%s >= %s' % (pkg
, version
),
137 def pkg_config_parse(opt
, pkg
):
138 ret
= _run_pkg_config([opt
, pkg
])
139 output
= ret
.decode()
141 return [x
.lstrip(opt
) for x
in output
.split()]
144 du_sdist
= get_command_class("sdist")
147 class distcheck(du_sdist
):
148 """Creates a tarball and does some additional sanity checks such as
149 checking if the tarballs includes all files and builds.
152 def _check_manifest(self
):
153 # make sure MANIFEST.in includes all tracked files
154 assert self
.get_archive_files()
156 if subprocess
.call(["git", "status"],
157 stdout
=subprocess
.PIPE
,
158 stderr
=subprocess
.PIPE
) != 0:
161 included_files
= self
.filelist
.files
162 assert included_files
164 process
= subprocess
.Popen(
165 ["git", "ls-tree", "-r", "HEAD", "--name-only"],
166 stdout
=subprocess
.PIPE
, universal_newlines
=True)
167 out
, err
= process
.communicate()
168 assert process
.returncode
== 0
170 tracked_files
= out
.splitlines()
171 for ignore
in [".gitignore"]:
172 tracked_files
.remove(ignore
)
174 diff
= set(tracked_files
) - set(included_files
)
176 "Not all tracked files included in tarball, check MANIFEST.in",
179 def _check_dist(self
):
180 # make sure the tarball builds
181 assert self
.get_archive_files()
183 distcheck_dir
= os
.path
.abspath(
184 os
.path
.join(self
.dist_dir
, "distcheck"))
185 if os
.path
.exists(distcheck_dir
):
186 dir_util
.remove_tree(distcheck_dir
)
187 self
.mkpath(distcheck_dir
)
189 archive
= self
.get_archive_files()[0]
190 tfile
= tarfile
.open(archive
, "r:gz")
191 tfile
.extractall(distcheck_dir
)
194 name
= self
.distribution
.get_fullname()
195 extract_dir
= os
.path
.join(distcheck_dir
, name
)
197 old_pwd
= os
.getcwd()
198 os
.chdir(extract_dir
)
200 self
.spawn([sys
.executable
, "setup.py", "build"])
201 self
.spawn([sys
.executable
, "setup.py", "install",
203 os
.path
.join(distcheck_dir
, "prefix"),
205 os
.path
.join(distcheck_dir
, "log.txt"),
212 self
._check
_manifest
()
216 du_build_ext
= get_command_class("build_ext")
219 class build_ext(du_build_ext
):
221 def initialize_options(self
):
222 du_build_ext
.initialize_options(self
)
223 self
.compiler_type
= None
225 def finalize_options(self
):
226 du_build_ext
.finalize_options(self
)
227 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
229 def _write_config_h(self
):
230 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
231 target
= os
.path
.join(script_dir
, "config.h")
232 versions
= parse_versions(script_dir
)
233 with io
.open(target
, 'w', encoding
="utf-8") as h
:
235 /* Configuration header created by setup.py - do not edit */
239 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
240 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
241 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
242 #define VERSION "%(VERSION)s"
244 #endif /* _CONFIG_H */
247 def _setup_extensions(self
):
248 ext
= {e
.name
: e
for e
in self
.extensions
}
249 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
252 "glib-2.0": ["glib-2.0"],
253 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
254 "gobject-introspection-1.0":
255 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
256 get_pycairo_pkg_config_name(): ["cairo"],
259 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
263 def add_dependency(ext
, name
):
264 fallback_libs
= msvc_libraries
[name
]
266 if self
.compiler_type
== "msvc":
267 # assume that INCLUDE and LIB contains the right paths
268 ext
.libraries
+= fallback_libs
270 # The PyCairo header is installed in a subdir of the
271 # Python installation that we are building for, so
272 # deduce that include path here, and use it
273 ext
.include_dirs
+= [
274 os
.path
.join(sys
.prefix
, "include", "pycairo")]
276 min_version
= get_version_requirement(script_dir
, name
)
277 pkg_config_version_check(name
, min_version
)
278 ext
.include_dirs
+= pkg_config_parse("--cflags-only-I", name
)
279 ext
.library_dirs
+= pkg_config_parse("--libs-only-L", name
)
280 ext
.libraries
+= pkg_config_parse("--libs-only-l", name
)
282 gi_ext
= ext
["gi._gi"]
283 add_dependency(gi_ext
, "glib-2.0")
284 add_dependency(gi_ext
, "gio-2.0")
285 add_dependency(gi_ext
, "gobject-introspection-1.0")
286 add_dependency(gi_ext
, "libffi")
288 gi_cairo_ext
= ext
["gi._gi_cairo"]
289 add_dependency(gi_cairo_ext
, "glib-2.0")
290 add_dependency(gi_cairo_ext
, "gio-2.0")
291 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
292 add_dependency(gi_cairo_ext
, "libffi")
293 add_dependency(gi_cairo_ext
, "cairo")
294 add_dependency(gi_cairo_ext
, "cairo-gobject")
295 add_dependency(gi_cairo_ext
, get_pycairo_pkg_config_name())
298 self
._write
_config
_h
()
299 self
._setup
_extensions
()
300 du_build_ext
.run(self
)
304 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
305 pkginfo
= parse_pkg_info(script_dir
)
306 gi_dir
= os
.path
.join(script_dir
, "gi")
309 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
310 if os
.path
.splitext(n
)[-1] == ".c"
312 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
313 for s
in cairo_sources
:
319 include_dirs
=[script_dir
, gi_dir
],
320 define_macros
=[("HAVE_CONFIG_H", None)],
323 gi_cairo_ext
= Extension(
325 sources
=cairo_sources
,
326 include_dirs
=[script_dir
, gi_dir
],
327 define_macros
=[("HAVE_CONFIG_H", None)],
331 name
=pkginfo
["Name"],
332 version
=pkginfo
["Version"],
333 description
=pkginfo
["Summary"],
334 url
=pkginfo
["Home-page"],
335 author
=pkginfo
["Author"],
336 author_email
=pkginfo
["Author-email"],
337 maintainer
=pkginfo
["Maintainer"],
338 maintainer_email
=pkginfo
["Maintainer-email"],
339 license
=pkginfo
["License"],
340 long_description
=pkginfo
["Description"],
341 platforms
=pkginfo
.get_all("Platform"),
342 classifiers
=pkginfo
.get_all("Classifier"),
343 packages
=find_packages(script_dir
),
349 "build_ext": build_ext
,
350 "distcheck": distcheck
,
353 "pycairo>=%s" % get_version_requirement(
354 script_dir
, get_pycairo_pkg_config_name()),
359 if __name__
== "__main__":