You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
383 lines
12 KiB
383 lines
12 KiB
#! /usr/bin/env python |
|
|
|
from __future__ import print_function |
|
import io |
|
import sys |
|
import os |
|
from os.path import isfile, join as pjoin |
|
from glob import glob |
|
from setuptools import setup, find_packages, Command |
|
from distutils import log |
|
from distutils.util import convert_path |
|
import subprocess as sp |
|
import contextlib |
|
import re |
|
|
|
# Force distutils to use py_compile.compile() function with 'doraise' argument |
|
# set to True, in order to raise an exception on compilation errors |
|
import py_compile |
|
orig_py_compile = py_compile.compile |
|
|
|
def doraise_py_compile(file, cfile=None, dfile=None, doraise=False): |
|
orig_py_compile(file, cfile=cfile, dfile=dfile, doraise=True) |
|
|
|
py_compile.compile = doraise_py_compile |
|
|
|
needs_wheel = {'bdist_wheel'}.intersection(sys.argv) |
|
wheel = ['wheel'] if needs_wheel else [] |
|
needs_bumpversion = {'release'}.intersection(sys.argv) |
|
bumpversion = ['bump2version'] if needs_bumpversion else [] |
|
|
|
extras_require = { |
|
# for fontTools.ufoLib: to read/write UFO fonts |
|
"ufo": [ |
|
"fs >= 2.2.0, < 3", |
|
"enum34 >= 1.1.6; python_version < '3.4'", |
|
], |
|
# for fontTools.misc.etree and fontTools.misc.plistlib: use lxml to |
|
# read/write XML files (faster/safer than built-in ElementTree) |
|
"lxml": [ |
|
"lxml >= 4.0, < 5", |
|
"singledispatch >= 3.4.0.3; python_version < '3.4'", |
|
# typing >= 3.6.4 is required when using ABC collections with the |
|
# singledispatch backport, see: |
|
# https://github.com/fonttools/fonttools/issues/1423 |
|
# https://github.com/python/typing/issues/484 |
|
"typing >= 3.6.4; python_version < '3.4'", |
|
], |
|
# for fontTools.sfnt and fontTools.woff2: to compress/uncompress |
|
# WOFF 1.0 and WOFF 2.0 webfonts. |
|
"woff": [ |
|
"brotli >= 1.0.1; platform_python_implementation != 'PyPy'", |
|
"brotlipy >= 0.7.0; platform_python_implementation == 'PyPy'", |
|
"zopfli >= 0.1.4", |
|
], |
|
# for fontTools.unicode and fontTools.unicodedata: to use the latest version |
|
# of the Unicode Character Database instead of the built-in unicodedata |
|
# which varies between python versions and may be outdated. |
|
"unicode": [ |
|
# the unicodedata2 extension module doesn't work on PyPy. |
|
# Python 3.7 already has Unicode 11, so the backport is not needed. |
|
( |
|
"unicodedata2 >= 11.0.0; " |
|
"python_version < '3.7' and platform_python_implementation != 'PyPy'" |
|
), |
|
], |
|
# for graphite type tables in ttLib/tables (Silf, Glat, Gloc) |
|
"graphite": [ |
|
"lz4 >= 1.7.4.2" |
|
], |
|
# for fontTools.interpolatable: to solve the "minimum weight perfect |
|
# matching problem in bipartite graphs" (aka Assignment problem) |
|
"interpolatable": [ |
|
# use pure-python alternative on pypy |
|
"scipy; platform_python_implementation != 'PyPy'", |
|
"munkres; platform_python_implementation == 'PyPy'", |
|
], |
|
# for fontTools.varLib.plot, to visualize DesignSpaceDocument and resulting |
|
# VariationModel |
|
"plot": [ |
|
# TODO: figure out the minimum version of matplotlib that we need |
|
"matplotlib", |
|
], |
|
# for fontTools.misc.symfont, module for symbolic font statistics analysis |
|
"symfont": [ |
|
"sympy", |
|
], |
|
# To get file creator and type of Macintosh PostScript Type 1 fonts (macOS only) |
|
"type1": [ |
|
"xattr; sys_platform == 'darwin'", |
|
], |
|
} |
|
# use a special 'all' key as shorthand to includes all the extra dependencies |
|
extras_require["all"] = sum(extras_require.values(), []) |
|
|
|
|
|
# Trove classifiers for PyPI |
|
classifiers = {"classifiers": [ |
|
"Development Status :: 5 - Production/Stable", |
|
"Environment :: Console", |
|
"Environment :: Other Environment", |
|
"Intended Audience :: Developers", |
|
"Intended Audience :: End Users/Desktop", |
|
"License :: OSI Approved :: MIT License", |
|
"Natural Language :: English", |
|
"Operating System :: OS Independent", |
|
"Programming Language :: Python", |
|
"Programming Language :: Python :: 2", |
|
"Programming Language :: Python :: 3", |
|
"Topic :: Text Processing :: Fonts", |
|
"Topic :: Multimedia :: Graphics", |
|
"Topic :: Multimedia :: Graphics :: Graphics Conversion", |
|
]} |
|
|
|
|
|
# concatenate README.rst and NEWS.rest into long_description so they are |
|
# displayed on the FontTols project page on PyPI |
|
with io.open("README.rst", "r", encoding="utf-8") as readme: |
|
long_description = readme.read() |
|
long_description += "\nChangelog\n~~~~~~~~~\n\n" |
|
with io.open("NEWS.rst", "r", encoding="utf-8") as changelog: |
|
long_description += changelog.read() |
|
|
|
|
|
@contextlib.contextmanager |
|
def capture_logger(name): |
|
""" Context manager to capture a logger output with a StringIO stream. |
|
""" |
|
import logging |
|
|
|
logger = logging.getLogger(name) |
|
try: |
|
import StringIO |
|
stream = StringIO.StringIO() |
|
except ImportError: |
|
stream = io.StringIO() |
|
handler = logging.StreamHandler(stream) |
|
logger.addHandler(handler) |
|
try: |
|
yield stream |
|
finally: |
|
logger.removeHandler(handler) |
|
|
|
|
|
class release(Command): |
|
""" |
|
Tag a new release with a single command, using the 'bumpversion' tool |
|
to update all the version strings in the source code. |
|
The version scheme conforms to 'SemVer' and PEP 440 specifications. |
|
|
|
Firstly, the pre-release '.devN' suffix is dropped to signal that this is |
|
a stable release. If '--major' or '--minor' options are passed, the |
|
the first or second 'semver' digit is also incremented. Major is usually |
|
for backward-incompatible API changes, while minor is used when adding |
|
new backward-compatible functionalities. No options imply 'patch' or bug-fix |
|
release. |
|
|
|
A new header is also added to the changelog file ("NEWS.rst"), containing |
|
the new version string and the current 'YYYY-MM-DD' date. |
|
|
|
All changes are committed, and an annotated git tag is generated. With the |
|
--sign option, the tag is GPG-signed with the user's default key. |
|
|
|
Finally, the 'patch' part of the version string is bumped again, and a |
|
pre-release suffix '.dev0' is appended to mark the opening of a new |
|
development cycle. |
|
|
|
Links: |
|
- http://semver.org/ |
|
- https://www.python.org/dev/peps/pep-0440/ |
|
- https://github.com/c4urself/bump2version |
|
""" |
|
|
|
description = "update version strings for release" |
|
|
|
user_options = [ |
|
("major", None, "bump the first digit (incompatible API changes)"), |
|
("minor", None, "bump the second digit (new backward-compatible features)"), |
|
("sign", "s", "make a GPG-signed tag, using the default key"), |
|
("allow-dirty", None, "don't abort if working directory is dirty"), |
|
] |
|
|
|
changelog_name = "NEWS.rst" |
|
version_RE = re.compile("^[0-9]+\.[0-9]+") |
|
date_fmt = u"%Y-%m-%d" |
|
header_fmt = u"%s (released %s)" |
|
commit_message = "Release {new_version}" |
|
tag_name = "{new_version}" |
|
version_files = [ |
|
"setup.cfg", |
|
"setup.py", |
|
"Lib/fontTools/__init__.py", |
|
] |
|
|
|
def initialize_options(self): |
|
self.minor = False |
|
self.major = False |
|
self.sign = False |
|
self.allow_dirty = False |
|
|
|
def finalize_options(self): |
|
if all([self.major, self.minor]): |
|
from distutils.errors import DistutilsOptionError |
|
raise DistutilsOptionError("--major/--minor are mutually exclusive") |
|
self.part = "major" if self.major else "minor" if self.minor else None |
|
|
|
def run(self): |
|
if self.part is not None: |
|
log.info("bumping '%s' version" % self.part) |
|
self.bumpversion(self.part, commit=False) |
|
release_version = self.bumpversion( |
|
"release", commit=False, allow_dirty=True) |
|
else: |
|
log.info("stripping pre-release suffix") |
|
release_version = self.bumpversion("release") |
|
log.info(" version = %s" % release_version) |
|
|
|
changes = self.format_changelog(release_version) |
|
|
|
self.git_commit(release_version) |
|
self.git_tag(release_version, changes, self.sign) |
|
|
|
log.info("bumping 'patch' version and pre-release suffix") |
|
next_dev_version = self.bumpversion('patch', commit=True) |
|
log.info(" version = %s" % next_dev_version) |
|
|
|
def git_commit(self, version): |
|
""" Stage and commit all relevant version files, and format the commit |
|
message with specified 'version' string. |
|
""" |
|
files = self.version_files + [self.changelog_name] |
|
|
|
log.info("committing changes") |
|
for f in files: |
|
log.info(" %s" % f) |
|
if self.dry_run: |
|
return |
|
sp.check_call(["git", "add"] + files) |
|
msg = self.commit_message.format(new_version=version) |
|
sp.check_call(["git", "commit", "-m", msg], stdout=sp.PIPE) |
|
|
|
def git_tag(self, version, message, sign=False): |
|
""" Create annotated git tag with given 'version' and 'message'. |
|
Optionally 'sign' the tag with the user's GPG key. |
|
""" |
|
log.info("creating %s git tag '%s'" % ( |
|
"signed" if sign else "annotated", version)) |
|
if self.dry_run: |
|
return |
|
# create an annotated (or signed) tag from the new version |
|
tag_opt = "-s" if sign else "-a" |
|
tag_name = self.tag_name.format(new_version=version) |
|
proc = sp.Popen( |
|
["git", "tag", tag_opt, "-F", "-", tag_name], stdin=sp.PIPE) |
|
# use the latest changes from the changelog file as the tag message |
|
tag_message = u"%s\n\n%s" % (tag_name, message) |
|
proc.communicate(tag_message.encode('utf-8')) |
|
if proc.returncode != 0: |
|
sys.exit(proc.returncode) |
|
|
|
def bumpversion(self, part, commit=False, message=None, allow_dirty=None): |
|
""" Run bumpversion.main() with the specified arguments, and return the |
|
new computed version string (cf. 'bumpversion --help' for more info) |
|
""" |
|
import bumpversion |
|
|
|
args = ( |
|
(['--verbose'] if self.verbose > 1 else []) + |
|
(['--dry-run'] if self.dry_run else []) + |
|
(['--allow-dirty'] if (allow_dirty or self.allow_dirty) else []) + |
|
(['--commit'] if commit else ['--no-commit']) + |
|
(['--message', message] if message is not None else []) + |
|
['--list', part] |
|
) |
|
log.debug("$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args)) |
|
|
|
with capture_logger("bumpversion.list") as out: |
|
bumpversion.main(args) |
|
|
|
last_line = out.getvalue().splitlines()[-1] |
|
new_version = last_line.replace("new_version=", "") |
|
return new_version |
|
|
|
def format_changelog(self, version): |
|
""" Write new header at beginning of changelog file with the specified |
|
'version' and the current date. |
|
Return the changelog content for the current release. |
|
""" |
|
from datetime import datetime |
|
|
|
log.info("formatting changelog") |
|
|
|
changes = [] |
|
with io.open(self.changelog_name, "r+", encoding="utf-8") as f: |
|
for ln in f: |
|
if self.version_RE.match(ln): |
|
break |
|
else: |
|
changes.append(ln) |
|
if not self.dry_run: |
|
f.seek(0) |
|
content = f.read() |
|
date = datetime.today().strftime(self.date_fmt) |
|
f.seek(0) |
|
header = self.header_fmt % (version, date) |
|
f.write(header + u"\n" + u"-"*len(header) + u"\n\n" + content) |
|
|
|
return u"".join(changes) |
|
|
|
|
|
def find_data_files(manpath="share/man"): |
|
""" Find FontTools's data_files (just man pages at this point). |
|
|
|
By default, we install man pages to "share/man" directory relative to the |
|
base installation directory for data_files. The latter can be changed with |
|
the --install-data option of 'setup.py install' sub-command. |
|
|
|
E.g., if the data files installation directory is "/usr", the default man |
|
page installation directory will be "/usr/share/man". |
|
|
|
You can override this via the $FONTTOOLS_MANPATH environment variable. |
|
|
|
E.g., on some BSD systems man pages are installed to 'man' instead of |
|
'share/man'; you can export $FONTTOOLS_MANPATH variable just before |
|
installing: |
|
|
|
$ FONTTOOLS_MANPATH="man" pip install -v . |
|
[...] |
|
running install_data |
|
copying Doc/man/ttx.1 -> /usr/man/man1 |
|
|
|
When installing from PyPI, for this variable to have effect you need to |
|
force pip to install from the source distribution instead of the wheel |
|
package (otherwise setup.py is not run), by using the --no-binary option: |
|
|
|
$ FONTTOOLS_MANPATH="man" pip install --no-binary=fonttools fonttools |
|
|
|
Note that you can only override the base man path, i.e. without the |
|
section number (man1, man3, etc.). The latter is always implied to be 1, |
|
for "general commands". |
|
""" |
|
|
|
# get base installation directory for man pages |
|
manpagebase = os.environ.get('FONTTOOLS_MANPATH', convert_path(manpath)) |
|
# all our man pages go to section 1 |
|
manpagedir = pjoin(manpagebase, 'man1') |
|
|
|
manpages = [f for f in glob(pjoin('Doc', 'man', 'man1', '*.1')) if isfile(f)] |
|
|
|
data_files = [(manpagedir, manpages)] |
|
return data_files |
|
|
|
|
|
setup( |
|
name="fonttools", |
|
version="3.39.0", |
|
description="Tools to manipulate font files", |
|
author="Just van Rossum", |
|
author_email="just@letterror.com", |
|
maintainer="Behdad Esfahbod", |
|
maintainer_email="behdad@behdad.org", |
|
url="http://github.com/fonttools/fonttools", |
|
license="MIT", |
|
platforms=["Any"], |
|
long_description=long_description, |
|
package_dir={'': 'Lib'}, |
|
packages=find_packages("Lib"), |
|
include_package_data=True, |
|
data_files=find_data_files(), |
|
setup_requires=wheel + bumpversion, |
|
extras_require=extras_require, |
|
entry_points={ |
|
'console_scripts': [ |
|
"fonttools = fontTools.__main__:main", |
|
"ttx = fontTools.ttx:main", |
|
"pyftsubset = fontTools.subset:main", |
|
"pyftmerge = fontTools.merge:main", |
|
] |
|
}, |
|
cmdclass={ |
|
"release": release, |
|
}, |
|
**classifiers |
|
)
|
|
|