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.
33 from email
import parser
36 from setuptools
import setup
, find_packages
37 from distutils
.core
import Extension
, Distribution
38 from distutils
.ccompiler
import new_compiler
39 from distutils
import dir_util
42 def get_command_class(name
):
43 # Returns the right class for either distutils or setuptools
44 return Distribution({}).get_command_class(name
)
47 def get_pycairo_pkg_config_name():
48 return "py3cairo" if sys
.version_info
[0] == 3 else "pycairo"
51 def get_version_requirement(conf_dir
, pkg_config_name
):
52 """Given a pkg-config module name gets the minimum version required"""
54 if pkg_config_name
in ["cairo", "cairo-gobject"]:
58 "gobject-introspection-1.0": "introspection",
61 get_pycairo_pkg_config_name(): "pycairo",
64 assert pkg_config_name
in mapping
66 configure_ac
= os
.path
.join(conf_dir
, "configure.ac")
67 with io
.open(configure_ac
, "r", encoding
="utf-8") as h
:
69 conf_name
= mapping
[pkg_config_name
]
71 r
"%s_required_version,\s*([\d\.]+)\)" % conf_name
, text
)
76 def parse_versions(conf_dir
):
77 configure_ac
= os
.path
.join(conf_dir
, "configure.ac")
78 with io
.open(configure_ac
, "r", encoding
="utf-8") as h
:
79 version
= re
.findall(r
"pygobject_[^\s]+_version,\s*(\d+)\)", h
.read())
80 assert len(version
) == 3
83 "PYGOBJECT_MAJOR_VERSION": version
[0],
84 "PYGOBJECT_MINOR_VERSION": version
[1],
85 "PYGOBJECT_MICRO_VERSION": version
[2],
86 "VERSION": ".".join(version
),
91 def parse_pkg_info(conf_dir
):
92 """Returns an email.message.Message instance containing the content
93 of the PKG-INFO file. The version info is parsed from configure.ac
96 versions
= parse_versions(conf_dir
)
98 pkg_info
= os
.path
.join(conf_dir
, "PKG-INFO.in")
99 with io
.open(pkg_info
, "r", encoding
="utf-8") as h
:
101 for key
, value
in versions
.items():
102 text
= text
.replace("@%s@" % key
, value
)
105 message
= p
.parse(io
.StringIO(text
))
109 def _run_pkg_config(args
):
110 command
= ["pkg-config"] + args
113 return subprocess
.check_output(command
)
115 if e
.errno
== errno
.ENOENT
:
117 "%r not found.\nArguments: %r" % (command
[0], command
))
119 except subprocess
.CalledProcessError
as e
:
123 def pkg_config_version_check(pkg
, version
):
127 '%s >= %s' % (pkg
, version
),
131 def pkg_config_parse(opt
, pkg
):
132 ret
= _run_pkg_config([opt
, pkg
])
133 output
= ret
.decode()
135 return [x
.lstrip(opt
) for x
in output
.split()]
138 def samefile(src
, dst
):
139 # Python 2 on Windows doesn't have os.path.samefile, so we have to provide
142 if hasattr(os
.path
, "samefile"):
143 return os
.path
.samefile(src
, dst
)
147 return (os
.path
.normcase(os
.path
.abspath(src
)) ==
148 os
.path
.normcase(os
.path
.abspath(dst
)))
151 du_sdist
= get_command_class("sdist")
154 class distcheck(du_sdist
):
155 """Creates a tarball and does some additional sanity checks such as
156 checking if the tarballs includes all files and builds.
159 def _check_manifest(self
):
160 # make sure MANIFEST.in includes all tracked files
161 assert self
.get_archive_files()
163 if subprocess
.call(["git", "status"],
164 stdout
=subprocess
.PIPE
,
165 stderr
=subprocess
.PIPE
) != 0:
168 included_files
= self
.filelist
.files
169 assert included_files
171 process
= subprocess
.Popen(
172 ["git", "ls-tree", "-r", "HEAD", "--name-only"],
173 stdout
=subprocess
.PIPE
, universal_newlines
=True)
174 out
, err
= process
.communicate()
175 assert process
.returncode
== 0
177 tracked_files
= out
.splitlines()
178 for ignore
in [".gitignore"]:
179 tracked_files
.remove(ignore
)
181 diff
= set(tracked_files
) - set(included_files
)
183 "Not all tracked files included in tarball, check MANIFEST.in",
186 def _check_dist(self
):
187 # make sure the tarball builds
188 assert self
.get_archive_files()
190 distcheck_dir
= os
.path
.abspath(
191 os
.path
.join(self
.dist_dir
, "distcheck"))
192 if os
.path
.exists(distcheck_dir
):
193 dir_util
.remove_tree(distcheck_dir
)
194 self
.mkpath(distcheck_dir
)
196 archive
= self
.get_archive_files()[0]
197 tfile
= tarfile
.open(archive
, "r:gz")
198 tfile
.extractall(distcheck_dir
)
201 name
= self
.distribution
.get_fullname()
202 extract_dir
= os
.path
.join(distcheck_dir
, name
)
204 old_pwd
= os
.getcwd()
205 os
.chdir(extract_dir
)
207 self
.spawn([sys
.executable
, "setup.py", "build"])
208 self
.spawn([sys
.executable
, "setup.py", "install",
210 os
.path
.join(distcheck_dir
, "prefix"),
212 os
.path
.join(distcheck_dir
, "log.txt"),
219 self
._check
_manifest
()
223 du_build_ext
= get_command_class("build_ext")
226 class build_ext(du_build_ext
):
228 def initialize_options(self
):
229 du_build_ext
.initialize_options(self
)
230 self
.compiler_type
= None
232 def finalize_options(self
):
233 du_build_ext
.finalize_options(self
)
234 self
.compiler_type
= new_compiler(compiler
=self
.compiler
).compiler_type
236 def _write_config_h(self
):
237 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
238 target
= os
.path
.join(script_dir
, "config.h")
239 versions
= parse_versions(script_dir
)
240 with io
.open(target
, 'w', encoding
="utf-8") as h
:
242 /* Configuration header created by setup.py - do not edit */
246 #define PYGOBJECT_MAJOR_VERSION %(PYGOBJECT_MAJOR_VERSION)s
247 #define PYGOBJECT_MINOR_VERSION %(PYGOBJECT_MINOR_VERSION)s
248 #define PYGOBJECT_MICRO_VERSION %(PYGOBJECT_MICRO_VERSION)s
249 #define VERSION "%(VERSION)s"
251 #endif /* _CONFIG_H */
254 def _setup_extensions(self
):
255 ext
= {e
.name
: e
for e
in self
.extensions
}
256 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
259 "glib-2.0": ["glib-2.0"],
260 "gio-2.0": ["gio-2.0", "gobject-2.0", "glib-2.0"],
261 "gobject-introspection-1.0":
262 ["girepository-1.0", "gobject-2.0", "glib-2.0"],
265 ["cairo-gobject", "cairo", "gobject-2.0", "glib-2.0"],
269 def add_dependency(ext
, name
):
270 fallback_libs
= msvc_libraries
[name
]
272 if self
.compiler_type
== "msvc":
273 # assume that INCLUDE and LIB contains the right paths
274 ext
.libraries
+= fallback_libs
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 def add_pycairo(ext
):
283 min_version
= get_version_requirement(
284 script_dir
, get_pycairo_pkg_config_name())
286 dist
= pkg_resources
.get_distribution("pycairo>=%s" % min_version
)
288 def get_sys_path(dist
, name
):
289 """The sysconfig path for a distribution, or None"""
291 location
= dist
.location
292 for scheme
in sysconfig
.get_scheme_names():
293 for path_type
in ["platlib", "purelib"]:
294 path
= sysconfig
.get_path(path_type
, scheme
)
296 if samefile(path
, location
):
297 return sysconfig
.get_path(name
, scheme
)
298 except EnvironmentError:
301 data_path
= get_sys_path(dist
, "data") or sys
.prefix
302 include_dir
= os
.path
.join(data_path
, "include", "pycairo")
303 ext
.include_dirs
+= [include_dir
]
305 gi_ext
= ext
["gi._gi"]
306 add_dependency(gi_ext
, "glib-2.0")
307 add_dependency(gi_ext
, "gio-2.0")
308 add_dependency(gi_ext
, "gobject-introspection-1.0")
309 add_dependency(gi_ext
, "libffi")
311 gi_cairo_ext
= ext
["gi._gi_cairo"]
312 add_dependency(gi_cairo_ext
, "glib-2.0")
313 add_dependency(gi_cairo_ext
, "gio-2.0")
314 add_dependency(gi_cairo_ext
, "gobject-introspection-1.0")
315 add_dependency(gi_cairo_ext
, "libffi")
316 add_dependency(gi_cairo_ext
, "cairo")
317 add_dependency(gi_cairo_ext
, "cairo-gobject")
318 add_pycairo(gi_cairo_ext
)
321 self
._write
_config
_h
()
322 self
._setup
_extensions
()
323 du_build_ext
.run(self
)
327 script_dir
= os
.path
.dirname(os
.path
.realpath(__file__
))
328 pkginfo
= parse_pkg_info(script_dir
)
329 gi_dir
= os
.path
.join(script_dir
, "gi")
332 os
.path
.join("gi", n
) for n
in os
.listdir(gi_dir
)
333 if os
.path
.splitext(n
)[-1] == ".c"
335 cairo_sources
= [os
.path
.join("gi", "pygi-foreign-cairo.c")]
336 for s
in cairo_sources
:
339 readme
= os
.path
.join(script_dir
, "README.rst")
340 with io
.open(readme
, encoding
="utf-8") as h
:
341 long_description
= h
.read()
346 include_dirs
=[script_dir
, gi_dir
],
347 define_macros
=[("HAVE_CONFIG_H", None)],
350 gi_cairo_ext
= Extension(
352 sources
=cairo_sources
,
353 include_dirs
=[script_dir
, gi_dir
],
354 define_macros
=[("HAVE_CONFIG_H", None)],
358 name
=pkginfo
["Name"],
359 version
=pkginfo
["Version"],
360 description
=pkginfo
["Summary"],
361 url
=pkginfo
["Home-page"],
362 author
=pkginfo
["Author"],
363 author_email
=pkginfo
["Author-email"],
364 maintainer
=pkginfo
["Maintainer"],
365 maintainer_email
=pkginfo
["Maintainer-email"],
366 license
=pkginfo
["License"],
367 long_description
=long_description
,
368 platforms
=pkginfo
.get_all("Platform"),
369 classifiers
=pkginfo
.get_all("Classifier"),
370 packages
=find_packages(script_dir
),
376 "build_ext": build_ext
,
377 "distcheck": distcheck
,
380 "pycairo>=%s" % get_version_requirement(
381 script_dir
, get_pycairo_pkg_config_name()),
386 if __name__
== "__main__":