Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 1 | #! /usr/bin/env python3 |
jvr | 3285b4b | 2001-08-09 18:47:22 +0000 | [diff] [blame] | 2 | |
Denis Jacquerye | db08ee2 | 2013-11-29 14:11:19 +0100 | [diff] [blame] | 3 | from __future__ import print_function |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 4 | import io |
| 5 | import sys |
| 6 | import os |
| 7 | from os.path import isfile, join as pjoin |
| 8 | from glob import glob |
| 9 | from setuptools import setup, find_packages, Command |
| 10 | from distutils import log |
| 11 | from distutils.util import convert_path |
| 12 | import subprocess as sp |
| 13 | import contextlib |
| 14 | import re |
jvr | 3285b4b | 2001-08-09 18:47:22 +0000 | [diff] [blame] | 15 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 16 | # Force distutils to use py_compile.compile() function with 'doraise' argument |
| 17 | # set to True, in order to raise an exception on compilation errors |
| 18 | import py_compile |
| 19 | orig_py_compile = py_compile.compile |
jvr | 3285b4b | 2001-08-09 18:47:22 +0000 | [diff] [blame] | 20 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 21 | def doraise_py_compile(file, cfile=None, dfile=None, doraise=False): |
| 22 | orig_py_compile(file, cfile=cfile, dfile=dfile, doraise=True) |
| 23 | |
| 24 | py_compile.compile = doraise_py_compile |
| 25 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 26 | needs_wheel = {'bdist_wheel'}.intersection(sys.argv) |
| 27 | wheel = ['wheel'] if needs_wheel else [] |
| 28 | needs_bumpversion = {'release'}.intersection(sys.argv) |
| 29 | bumpversion = ['bump2version'] if needs_bumpversion else [] |
| 30 | |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 31 | extras_require = { |
| 32 | # for fontTools.ufoLib: to read/write UFO fonts |
| 33 | "ufo": [ |
Haibo Huang | f08648c | 2019-02-01 17:02:23 -0800 | [diff] [blame] | 34 | "fs >= 2.2.0, < 3", |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 35 | ], |
| 36 | # for fontTools.misc.etree and fontTools.misc.plistlib: use lxml to |
| 37 | # read/write XML files (faster/safer than built-in ElementTree) |
| 38 | "lxml": [ |
| 39 | "lxml >= 4.0, < 5", |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 40 | ], |
| 41 | # for fontTools.sfnt and fontTools.woff2: to compress/uncompress |
| 42 | # WOFF 1.0 and WOFF 2.0 webfonts. |
| 43 | "woff": [ |
| 44 | "brotli >= 1.0.1; platform_python_implementation != 'PyPy'", |
| 45 | "brotlipy >= 0.7.0; platform_python_implementation == 'PyPy'", |
| 46 | "zopfli >= 0.1.4", |
| 47 | ], |
| 48 | # for fontTools.unicode and fontTools.unicodedata: to use the latest version |
| 49 | # of the Unicode Character Database instead of the built-in unicodedata |
| 50 | # which varies between python versions and may be outdated. |
| 51 | "unicode": [ |
| 52 | # the unicodedata2 extension module doesn't work on PyPy. |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 53 | # Python 3.8 already has Unicode 12.1, so the backport is not needed. |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 54 | ( |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 55 | "unicodedata2 >= 12.1.0; " |
Haibo Huang | 4c8220a | 2019-05-31 16:12:23 -0700 | [diff] [blame] | 56 | "python_version < '3.8' and platform_python_implementation != 'PyPy'" |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 57 | ), |
| 58 | ], |
Haibo Huang | 79019a0 | 2019-01-08 14:14:22 -0800 | [diff] [blame] | 59 | # for graphite type tables in ttLib/tables (Silf, Glat, Gloc) |
| 60 | "graphite": [ |
| 61 | "lz4 >= 1.7.4.2" |
| 62 | ], |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 63 | # for fontTools.interpolatable: to solve the "minimum weight perfect |
| 64 | # matching problem in bipartite graphs" (aka Assignment problem) |
| 65 | "interpolatable": [ |
| 66 | # use pure-python alternative on pypy |
| 67 | "scipy; platform_python_implementation != 'PyPy'", |
| 68 | "munkres; platform_python_implementation == 'PyPy'", |
| 69 | ], |
Haibo Huang | 79019a0 | 2019-01-08 14:14:22 -0800 | [diff] [blame] | 70 | # for fontTools.varLib.plot, to visualize DesignSpaceDocument and resulting |
| 71 | # VariationModel |
| 72 | "plot": [ |
| 73 | # TODO: figure out the minimum version of matplotlib that we need |
| 74 | "matplotlib", |
| 75 | ], |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 76 | # for fontTools.misc.symfont, module for symbolic font statistics analysis |
| 77 | "symfont": [ |
| 78 | "sympy", |
| 79 | ], |
| 80 | # To get file creator and type of Macintosh PostScript Type 1 fonts (macOS only) |
| 81 | "type1": [ |
| 82 | "xattr; sys_platform == 'darwin'", |
| 83 | ], |
| 84 | } |
| 85 | # use a special 'all' key as shorthand to includes all the extra dependencies |
| 86 | extras_require["all"] = sum(extras_require.values(), []) |
| 87 | |
| 88 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 89 | # Trove classifiers for PyPI |
| 90 | classifiers = {"classifiers": [ |
| 91 | "Development Status :: 5 - Production/Stable", |
| 92 | "Environment :: Console", |
| 93 | "Environment :: Other Environment", |
| 94 | "Intended Audience :: Developers", |
| 95 | "Intended Audience :: End Users/Desktop", |
| 96 | "License :: OSI Approved :: MIT License", |
| 97 | "Natural Language :: English", |
| 98 | "Operating System :: OS Independent", |
| 99 | "Programming Language :: Python", |
| 100 | "Programming Language :: Python :: 2", |
| 101 | "Programming Language :: Python :: 3", |
| 102 | "Topic :: Text Processing :: Fonts", |
| 103 | "Topic :: Multimedia :: Graphics", |
| 104 | "Topic :: Multimedia :: Graphics :: Graphics Conversion", |
| 105 | ]} |
jvr | 5808f3f | 2001-08-09 23:03:47 +0000 | [diff] [blame] | 106 | |
| 107 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 108 | # concatenate README.rst and NEWS.rest into long_description so they are |
| 109 | # displayed on the FontTols project page on PyPI |
| 110 | with io.open("README.rst", "r", encoding="utf-8") as readme: |
| 111 | long_description = readme.read() |
| 112 | long_description += "\nChangelog\n~~~~~~~~~\n\n" |
| 113 | with io.open("NEWS.rst", "r", encoding="utf-8") as changelog: |
| 114 | long_description += changelog.read() |
jvr | 059cbe3 | 2002-07-01 09:11:01 +0000 | [diff] [blame] | 115 | |
| 116 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 117 | @contextlib.contextmanager |
| 118 | def capture_logger(name): |
| 119 | """ Context manager to capture a logger output with a StringIO stream. |
| 120 | """ |
| 121 | import logging |
jvr | 91bde17 | 2003-01-03 21:01:07 +0000 | [diff] [blame] | 122 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 123 | logger = logging.getLogger(name) |
| 124 | try: |
| 125 | import StringIO |
| 126 | stream = StringIO.StringIO() |
| 127 | except ImportError: |
| 128 | stream = io.StringIO() |
| 129 | handler = logging.StreamHandler(stream) |
| 130 | logger.addHandler(handler) |
| 131 | try: |
| 132 | yield stream |
| 133 | finally: |
| 134 | logger.removeHandler(handler) |
| 135 | |
| 136 | |
| 137 | class release(Command): |
| 138 | """ |
| 139 | Tag a new release with a single command, using the 'bumpversion' tool |
| 140 | to update all the version strings in the source code. |
| 141 | The version scheme conforms to 'SemVer' and PEP 440 specifications. |
| 142 | |
| 143 | Firstly, the pre-release '.devN' suffix is dropped to signal that this is |
| 144 | a stable release. If '--major' or '--minor' options are passed, the |
| 145 | the first or second 'semver' digit is also incremented. Major is usually |
| 146 | for backward-incompatible API changes, while minor is used when adding |
| 147 | new backward-compatible functionalities. No options imply 'patch' or bug-fix |
| 148 | release. |
| 149 | |
| 150 | A new header is also added to the changelog file ("NEWS.rst"), containing |
| 151 | the new version string and the current 'YYYY-MM-DD' date. |
| 152 | |
| 153 | All changes are committed, and an annotated git tag is generated. With the |
| 154 | --sign option, the tag is GPG-signed with the user's default key. |
| 155 | |
| 156 | Finally, the 'patch' part of the version string is bumped again, and a |
| 157 | pre-release suffix '.dev0' is appended to mark the opening of a new |
| 158 | development cycle. |
| 159 | |
| 160 | Links: |
| 161 | - http://semver.org/ |
| 162 | - https://www.python.org/dev/peps/pep-0440/ |
| 163 | - https://github.com/c4urself/bump2version |
| 164 | """ |
| 165 | |
| 166 | description = "update version strings for release" |
| 167 | |
| 168 | user_options = [ |
| 169 | ("major", None, "bump the first digit (incompatible API changes)"), |
| 170 | ("minor", None, "bump the second digit (new backward-compatible features)"), |
| 171 | ("sign", "s", "make a GPG-signed tag, using the default key"), |
| 172 | ("allow-dirty", None, "don't abort if working directory is dirty"), |
| 173 | ] |
| 174 | |
| 175 | changelog_name = "NEWS.rst" |
| 176 | version_RE = re.compile("^[0-9]+\.[0-9]+") |
| 177 | date_fmt = u"%Y-%m-%d" |
| 178 | header_fmt = u"%s (released %s)" |
| 179 | commit_message = "Release {new_version}" |
| 180 | tag_name = "{new_version}" |
| 181 | version_files = [ |
| 182 | "setup.cfg", |
| 183 | "setup.py", |
| 184 | "Lib/fontTools/__init__.py", |
| 185 | ] |
| 186 | |
| 187 | def initialize_options(self): |
| 188 | self.minor = False |
| 189 | self.major = False |
| 190 | self.sign = False |
| 191 | self.allow_dirty = False |
| 192 | |
| 193 | def finalize_options(self): |
| 194 | if all([self.major, self.minor]): |
| 195 | from distutils.errors import DistutilsOptionError |
| 196 | raise DistutilsOptionError("--major/--minor are mutually exclusive") |
| 197 | self.part = "major" if self.major else "minor" if self.minor else None |
| 198 | |
| 199 | def run(self): |
| 200 | if self.part is not None: |
| 201 | log.info("bumping '%s' version" % self.part) |
| 202 | self.bumpversion(self.part, commit=False) |
| 203 | release_version = self.bumpversion( |
| 204 | "release", commit=False, allow_dirty=True) |
| 205 | else: |
| 206 | log.info("stripping pre-release suffix") |
| 207 | release_version = self.bumpversion("release") |
| 208 | log.info(" version = %s" % release_version) |
| 209 | |
| 210 | changes = self.format_changelog(release_version) |
| 211 | |
| 212 | self.git_commit(release_version) |
| 213 | self.git_tag(release_version, changes, self.sign) |
| 214 | |
| 215 | log.info("bumping 'patch' version and pre-release suffix") |
| 216 | next_dev_version = self.bumpversion('patch', commit=True) |
| 217 | log.info(" version = %s" % next_dev_version) |
| 218 | |
| 219 | def git_commit(self, version): |
| 220 | """ Stage and commit all relevant version files, and format the commit |
| 221 | message with specified 'version' string. |
| 222 | """ |
| 223 | files = self.version_files + [self.changelog_name] |
| 224 | |
| 225 | log.info("committing changes") |
| 226 | for f in files: |
| 227 | log.info(" %s" % f) |
| 228 | if self.dry_run: |
| 229 | return |
| 230 | sp.check_call(["git", "add"] + files) |
| 231 | msg = self.commit_message.format(new_version=version) |
| 232 | sp.check_call(["git", "commit", "-m", msg], stdout=sp.PIPE) |
| 233 | |
| 234 | def git_tag(self, version, message, sign=False): |
| 235 | """ Create annotated git tag with given 'version' and 'message'. |
| 236 | Optionally 'sign' the tag with the user's GPG key. |
| 237 | """ |
| 238 | log.info("creating %s git tag '%s'" % ( |
| 239 | "signed" if sign else "annotated", version)) |
| 240 | if self.dry_run: |
| 241 | return |
| 242 | # create an annotated (or signed) tag from the new version |
| 243 | tag_opt = "-s" if sign else "-a" |
| 244 | tag_name = self.tag_name.format(new_version=version) |
| 245 | proc = sp.Popen( |
| 246 | ["git", "tag", tag_opt, "-F", "-", tag_name], stdin=sp.PIPE) |
| 247 | # use the latest changes from the changelog file as the tag message |
| 248 | tag_message = u"%s\n\n%s" % (tag_name, message) |
| 249 | proc.communicate(tag_message.encode('utf-8')) |
| 250 | if proc.returncode != 0: |
| 251 | sys.exit(proc.returncode) |
| 252 | |
| 253 | def bumpversion(self, part, commit=False, message=None, allow_dirty=None): |
| 254 | """ Run bumpversion.main() with the specified arguments, and return the |
| 255 | new computed version string (cf. 'bumpversion --help' for more info) |
| 256 | """ |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 257 | import bumpversion.cli |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 258 | |
| 259 | args = ( |
| 260 | (['--verbose'] if self.verbose > 1 else []) + |
| 261 | (['--dry-run'] if self.dry_run else []) + |
| 262 | (['--allow-dirty'] if (allow_dirty or self.allow_dirty) else []) + |
| 263 | (['--commit'] if commit else ['--no-commit']) + |
| 264 | (['--message', message] if message is not None else []) + |
| 265 | ['--list', part] |
| 266 | ) |
| 267 | log.debug("$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) |
| 268 | |
| 269 | with capture_logger("bumpversion.list") as out: |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 270 | bumpversion.cli.main(args) |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 271 | |
| 272 | last_line = out.getvalue().splitlines()[-1] |
| 273 | new_version = last_line.replace("new_version=", "") |
| 274 | return new_version |
| 275 | |
| 276 | def format_changelog(self, version): |
| 277 | """ Write new header at beginning of changelog file with the specified |
| 278 | 'version' and the current date. |
| 279 | Return the changelog content for the current release. |
| 280 | """ |
| 281 | from datetime import datetime |
| 282 | |
| 283 | log.info("formatting changelog") |
| 284 | |
| 285 | changes = [] |
| 286 | with io.open(self.changelog_name, "r+", encoding="utf-8") as f: |
| 287 | for ln in f: |
| 288 | if self.version_RE.match(ln): |
| 289 | break |
| 290 | else: |
| 291 | changes.append(ln) |
| 292 | if not self.dry_run: |
| 293 | f.seek(0) |
| 294 | content = f.read() |
| 295 | date = datetime.today().strftime(self.date_fmt) |
| 296 | f.seek(0) |
| 297 | header = self.header_fmt % (version, date) |
| 298 | f.write(header + u"\n" + u"-"*len(header) + u"\n\n" + content) |
| 299 | |
| 300 | return u"".join(changes) |
| 301 | |
| 302 | |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 303 | def find_data_files(manpath="share/man"): |
| 304 | """ Find FontTools's data_files (just man pages at this point). |
| 305 | |
| 306 | By default, we install man pages to "share/man" directory relative to the |
| 307 | base installation directory for data_files. The latter can be changed with |
| 308 | the --install-data option of 'setup.py install' sub-command. |
| 309 | |
| 310 | E.g., if the data files installation directory is "/usr", the default man |
| 311 | page installation directory will be "/usr/share/man". |
| 312 | |
| 313 | You can override this via the $FONTTOOLS_MANPATH environment variable. |
| 314 | |
| 315 | E.g., on some BSD systems man pages are installed to 'man' instead of |
| 316 | 'share/man'; you can export $FONTTOOLS_MANPATH variable just before |
| 317 | installing: |
| 318 | |
| 319 | $ FONTTOOLS_MANPATH="man" pip install -v . |
| 320 | [...] |
| 321 | running install_data |
| 322 | copying Doc/man/ttx.1 -> /usr/man/man1 |
| 323 | |
| 324 | When installing from PyPI, for this variable to have effect you need to |
| 325 | force pip to install from the source distribution instead of the wheel |
| 326 | package (otherwise setup.py is not run), by using the --no-binary option: |
| 327 | |
| 328 | $ FONTTOOLS_MANPATH="man" pip install --no-binary=fonttools fonttools |
| 329 | |
| 330 | Note that you can only override the base man path, i.e. without the |
| 331 | section number (man1, man3, etc.). The latter is always implied to be 1, |
| 332 | for "general commands". |
| 333 | """ |
| 334 | |
| 335 | # get base installation directory for man pages |
| 336 | manpagebase = os.environ.get('FONTTOOLS_MANPATH', convert_path(manpath)) |
| 337 | # all our man pages go to section 1 |
| 338 | manpagedir = pjoin(manpagebase, 'man1') |
| 339 | |
| 340 | manpages = [f for f in glob(pjoin('Doc', 'man', 'man1', '*.1')) if isfile(f)] |
| 341 | |
| 342 | data_files = [(manpagedir, manpages)] |
| 343 | return data_files |
| 344 | |
jvr | 91bde17 | 2003-01-03 21:01:07 +0000 | [diff] [blame] | 345 | |
jvr | fdf2d77 | 2002-05-03 18:57:02 +0000 | [diff] [blame] | 346 | setup( |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 347 | name="fonttools", |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 348 | version="4.4.1", |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 349 | description="Tools to manipulate font files", |
| 350 | author="Just van Rossum", |
| 351 | author_email="just@letterror.com", |
| 352 | maintainer="Behdad Esfahbod", |
| 353 | maintainer_email="behdad@behdad.org", |
| 354 | url="http://github.com/fonttools/fonttools", |
| 355 | license="MIT", |
| 356 | platforms=["Any"], |
Haibo Huang | 5406a6a | 2020-02-26 16:36:20 -0800 | [diff] [blame^] | 357 | python_requires=">=3.6", |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 358 | long_description=long_description, |
| 359 | package_dir={'': 'Lib'}, |
| 360 | packages=find_packages("Lib"), |
| 361 | include_package_data=True, |
| 362 | data_files=find_data_files(), |
Elliott Hughes | 69c9aca | 2018-10-30 14:11:58 -0700 | [diff] [blame] | 363 | setup_requires=wheel + bumpversion, |
| 364 | extras_require=extras_require, |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 365 | entry_points={ |
| 366 | 'console_scripts': [ |
| 367 | "fonttools = fontTools.__main__:main", |
| 368 | "ttx = fontTools.ttx:main", |
| 369 | "pyftsubset = fontTools.subset:main", |
| 370 | "pyftmerge = fontTools.merge:main", |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 371 | ] |
| 372 | }, |
| 373 | cmdclass={ |
| 374 | "release": release, |
Haibo Huang | 8b3c57b | 2018-07-03 17:43:11 -0700 | [diff] [blame] | 375 | }, |
| 376 | **classifiers |
| 377 | ) |