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
.join(self
.dist_dir
, "distcheck")
184 if os
.path
.exists(distcheck_dir
):
185 dir_util
.remove_tree(distcheck_dir
)
186 self
.mkpath(distcheck_dir
)
188 archive
= self
.get_archive_files()[0]
189 tfile
= tarfile
.open(archive
, "r:gz")
190 tfile
.extractall(distcheck_dir
)
193 name
= self
.distribution
.get_fullname()
194 extract_dir
= os
.path
.join(distcheck_dir
, name
)
196 old_pwd
= os
.getcwd()
197 os
.chdir(extract_dir
)
199 self
.spawn([sys
.executable
, "setup.py", "build"])
200 self
.spawn([sys
.executable
, "setup.py", "install",
201 "--root", "../prefix", "--record", "../log.txt"])
207 self
._check
_manifest
()
211 du_build_ext
= get_command_class("build_ext")
214 class build_ext(du_build_ext
):
216 def initialize_options(self
):
217 du_build_ext
.initialize_options(self
)
218 self
.compiler_type
= None
220 def finalize_options(self
):
221 du_build_ext
.finalize_options(self
)
222 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
224 def _write_config_h(self
):
225 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
226 target
= os
.path
.join(script_dir
, "config.h")
227 versions
= parse_versions(script_dir
)
228 with io
.open(target
, 'w', encoding
="utf-8") as h
:
230 /* Configuration header created by setup.py - do not edit */
234 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
235 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
236 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
237 #define VERSION "%(VERSION)s"
239 #endif /* _CONFIG_H */
242 def _setup_extensions(self
):
243 ext
= {e
.name
: e
for e
in self
.extensions
}
244 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
247 "glib-2.0": ["glib-2.0"],
248 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
249 "gobject-introspection-1.0":
250 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
251 get_pycairo_pkg_config_name(): ["cairo"],
254 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
258 def add_dependency(ext
, name
):
259 fallback_libs
= msvc_libraries
[name
]
261 if self
.compiler_type
== "msvc":
262 # assume that INCLUDE and LIB contains the right paths
263 ext
.libraries
+= fallback_libs
265 # The PyCairo header is installed in a subdir of the
266 # Python installation that we are building for, so
267 # deduce that include path here, and use it
268 ext
.include_dirs
+= [
269 os
.path
.join(sys
.prefix
, "include", "pycairo")]
271 min_version
= get_version_requirement(script_dir
, name
)
272 pkg_config_version_check(name
, min_version
)
273 ext
.include_dirs
+= pkg_config_parse("--cflags-only-I", name
)
274 ext
.library_dirs
+= pkg_config_parse("--libs-only-L", name
)
275 ext
.libraries
+= pkg_config_parse("--libs-only-l", name
)
277 gi_ext
= ext
["gi._gi"]
278 add_dependency(gi_ext
, "glib-2.0")
279 add_dependency(gi_ext
, "gio-2.0")
280 add_dependency(gi_ext
, "gobject-introspection-1.0")
281 add_dependency(gi_ext
, "libffi")
283 gi_cairo_ext
= ext
["gi._gi_cairo"]
284 add_dependency(gi_cairo_ext
, "glib-2.0")
285 add_dependency(gi_cairo_ext
, "gio-2.0")
286 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
287 add_dependency(gi_cairo_ext
, "libffi")
288 add_dependency(gi_cairo_ext
, "cairo")
289 add_dependency(gi_cairo_ext
, "cairo-gobject")
290 add_dependency(gi_cairo_ext
, get_pycairo_pkg_config_name())
293 self
._write
_config
_h
()
294 self
._setup
_extensions
()
295 du_build_ext
.run(self
)
299 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
300 pkginfo
= parse_pkg_info(script_dir
)
301 gi_dir
= os
.path
.join(script_dir
, "gi")
304 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
305 if os
.path
.splitext(n
)[-1] == ".c"
307 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
308 for s
in cairo_sources
:
314 include_dirs
=[script_dir
, gi_dir
],
315 define_macros
=[("HAVE_CONFIG_H", None)],
318 gi_cairo_ext
= Extension(
320 sources
=cairo_sources
,
321 include_dirs
=[script_dir
, gi_dir
],
322 define_macros
=[("HAVE_CONFIG_H", None)],
326 name
=pkginfo
["Name"],
327 version
=pkginfo
["Version"],
328 description
=pkginfo
["Summary"],
329 url
=pkginfo
["Home-page"],
330 author
=pkginfo
["Author"],
331 author_email
=pkginfo
["Author-email"],
332 maintainer
=pkginfo
["Maintainer"],
333 maintainer_email
=pkginfo
["Maintainer-email"],
334 license
=pkginfo
["License"],
335 long_description
=pkginfo
["Description"],
336 platforms
=pkginfo
.get_all("Platform"),
337 classifiers
=pkginfo
.get_all("Classifier"),
338 packages
=find_packages(script_dir
),
344 "build_ext": build_ext
,
345 "distcheck": distcheck
,
348 "pycairo>=%s" % get_version_requirement(
349 script_dir
, get_pycairo_pkg_config_name()),
354 if __name__
== "__main__":