xref: /aosp_15_r20/external/fonttools/setup.py (revision e1fe3e4ad2793916b15cccdc4a7da52a7e1dd0e9)
1*e1fe3e4aSElliott Hughes#! /usr/bin/env python3
2*e1fe3e4aSElliott Hughes
3*e1fe3e4aSElliott Hughesfrom __future__ import print_function
4*e1fe3e4aSElliott Hughesimport io
5*e1fe3e4aSElliott Hughesimport sys
6*e1fe3e4aSElliott Hughesimport os
7*e1fe3e4aSElliott Hughesfrom os.path import isfile, join as pjoin
8*e1fe3e4aSElliott Hughesfrom glob import glob
9*e1fe3e4aSElliott Hughesfrom setuptools import setup, find_packages, Command, Extension
10*e1fe3e4aSElliott Hughesfrom setuptools.command.build_ext import build_ext as _build_ext
11*e1fe3e4aSElliott Hughesfrom distutils import log
12*e1fe3e4aSElliott Hughesfrom distutils.util import convert_path
13*e1fe3e4aSElliott Hughesimport subprocess as sp
14*e1fe3e4aSElliott Hughesimport contextlib
15*e1fe3e4aSElliott Hughesimport re
16*e1fe3e4aSElliott Hughes
17*e1fe3e4aSElliott Hughes# Force distutils to use py_compile.compile() function with 'doraise' argument
18*e1fe3e4aSElliott Hughes# set to True, in order to raise an exception on compilation errors
19*e1fe3e4aSElliott Hughesimport py_compile
20*e1fe3e4aSElliott Hughes
21*e1fe3e4aSElliott Hughesorig_py_compile = py_compile.compile
22*e1fe3e4aSElliott Hughes
23*e1fe3e4aSElliott Hughes
24*e1fe3e4aSElliott Hughesdef doraise_py_compile(file, cfile=None, dfile=None, doraise=False):
25*e1fe3e4aSElliott Hughes    orig_py_compile(file, cfile=cfile, dfile=dfile, doraise=True)
26*e1fe3e4aSElliott Hughes
27*e1fe3e4aSElliott Hughes
28*e1fe3e4aSElliott Hughespy_compile.compile = doraise_py_compile
29*e1fe3e4aSElliott Hughes
30*e1fe3e4aSElliott Hughessetup_requires = []
31*e1fe3e4aSElliott Hughes
32*e1fe3e4aSElliott Hughesif {"bdist_wheel"}.intersection(sys.argv):
33*e1fe3e4aSElliott Hughes    setup_requires.append("wheel")
34*e1fe3e4aSElliott Hughes
35*e1fe3e4aSElliott Hughesif {"release"}.intersection(sys.argv):
36*e1fe3e4aSElliott Hughes    setup_requires.append("bump2version")
37*e1fe3e4aSElliott Hughes
38*e1fe3e4aSElliott Hughestry:
39*e1fe3e4aSElliott Hughes    __import__("cython")
40*e1fe3e4aSElliott Hughesexcept ImportError:
41*e1fe3e4aSElliott Hughes    has_cython = False
42*e1fe3e4aSElliott Hugheselse:
43*e1fe3e4aSElliott Hughes    has_cython = True
44*e1fe3e4aSElliott Hughes
45*e1fe3e4aSElliott Hughesenv_with_cython = os.environ.get("FONTTOOLS_WITH_CYTHON")
46*e1fe3e4aSElliott Hugheswith_cython = (
47*e1fe3e4aSElliott Hughes    True
48*e1fe3e4aSElliott Hughes    if env_with_cython in {"1", "true", "yes"}
49*e1fe3e4aSElliott Hughes    else False if env_with_cython in {"0", "false", "no"} else None
50*e1fe3e4aSElliott Hughes)
51*e1fe3e4aSElliott Hughes# --with-cython/--without-cython options override environment variables
52*e1fe3e4aSElliott Hughesopt_with_cython = {"--with-cython"}.intersection(sys.argv)
53*e1fe3e4aSElliott Hughesopt_without_cython = {"--without-cython"}.intersection(sys.argv)
54*e1fe3e4aSElliott Hughesif opt_with_cython and opt_without_cython:
55*e1fe3e4aSElliott Hughes    sys.exit(
56*e1fe3e4aSElliott Hughes        "error: the options '--with-cython' and '--without-cython' are "
57*e1fe3e4aSElliott Hughes        "mutually exclusive"
58*e1fe3e4aSElliott Hughes    )
59*e1fe3e4aSElliott Hugheselif opt_with_cython:
60*e1fe3e4aSElliott Hughes    sys.argv.remove("--with-cython")
61*e1fe3e4aSElliott Hughes    with_cython = True
62*e1fe3e4aSElliott Hugheselif opt_without_cython:
63*e1fe3e4aSElliott Hughes    sys.argv.remove("--without-cython")
64*e1fe3e4aSElliott Hughes    with_cython = False
65*e1fe3e4aSElliott Hughes
66*e1fe3e4aSElliott Hughesif with_cython and not has_cython:
67*e1fe3e4aSElliott Hughes    setup_requires.append("cython")
68*e1fe3e4aSElliott Hughes
69*e1fe3e4aSElliott Hughesext_modules = []
70*e1fe3e4aSElliott Hughesif with_cython is True or (with_cython is None and has_cython):
71*e1fe3e4aSElliott Hughes    ext_modules.append(
72*e1fe3e4aSElliott Hughes        Extension("fontTools.cu2qu.cu2qu", ["Lib/fontTools/cu2qu/cu2qu.py"]),
73*e1fe3e4aSElliott Hughes    )
74*e1fe3e4aSElliott Hughes    ext_modules.append(
75*e1fe3e4aSElliott Hughes        Extension("fontTools.qu2cu.qu2cu", ["Lib/fontTools/qu2cu/qu2cu.py"]),
76*e1fe3e4aSElliott Hughes    )
77*e1fe3e4aSElliott Hughes    ext_modules.append(
78*e1fe3e4aSElliott Hughes        Extension("fontTools.misc.bezierTools", ["Lib/fontTools/misc/bezierTools.py"]),
79*e1fe3e4aSElliott Hughes    )
80*e1fe3e4aSElliott Hughes    ext_modules.append(
81*e1fe3e4aSElliott Hughes        Extension("fontTools.pens.momentsPen", ["Lib/fontTools/pens/momentsPen.py"]),
82*e1fe3e4aSElliott Hughes    )
83*e1fe3e4aSElliott Hughes    ext_modules.append(
84*e1fe3e4aSElliott Hughes        Extension("fontTools.varLib.iup", ["Lib/fontTools/varLib/iup.py"]),
85*e1fe3e4aSElliott Hughes    )
86*e1fe3e4aSElliott Hughes    ext_modules.append(
87*e1fe3e4aSElliott Hughes        Extension("fontTools.feaLib.lexer", ["Lib/fontTools/feaLib/lexer.py"]),
88*e1fe3e4aSElliott Hughes    )
89*e1fe3e4aSElliott Hughes
90*e1fe3e4aSElliott Hughesextras_require = {
91*e1fe3e4aSElliott Hughes    # for fontTools.ufoLib: to read/write UFO fonts
92*e1fe3e4aSElliott Hughes    "ufo": [
93*e1fe3e4aSElliott Hughes        "fs >= 2.2.0, < 3",
94*e1fe3e4aSElliott Hughes    ],
95*e1fe3e4aSElliott Hughes    # for fontTools.misc.etree and fontTools.misc.plistlib: use lxml to
96*e1fe3e4aSElliott Hughes    # read/write XML files (faster/safer than built-in ElementTree)
97*e1fe3e4aSElliott Hughes    "lxml": [
98*e1fe3e4aSElliott Hughes        "lxml >= 4.0",
99*e1fe3e4aSElliott Hughes    ],
100*e1fe3e4aSElliott Hughes    # for fontTools.sfnt and fontTools.woff2: to compress/uncompress
101*e1fe3e4aSElliott Hughes    # WOFF 1.0 and WOFF 2.0 webfonts.
102*e1fe3e4aSElliott Hughes    "woff": [
103*e1fe3e4aSElliott Hughes        "brotli >= 1.0.1; platform_python_implementation == 'CPython'",
104*e1fe3e4aSElliott Hughes        "brotlicffi >= 0.8.0; platform_python_implementation != 'CPython'",
105*e1fe3e4aSElliott Hughes        "zopfli >= 0.1.4",
106*e1fe3e4aSElliott Hughes    ],
107*e1fe3e4aSElliott Hughes    # for fontTools.unicode and fontTools.unicodedata: to use the latest version
108*e1fe3e4aSElliott Hughes    # of the Unicode Character Database instead of the built-in unicodedata
109*e1fe3e4aSElliott Hughes    # which varies between python versions and may be outdated.
110*e1fe3e4aSElliott Hughes    "unicode": [
111*e1fe3e4aSElliott Hughes        ("unicodedata2 >= 15.1.0; python_version <= '3.12'"),
112*e1fe3e4aSElliott Hughes    ],
113*e1fe3e4aSElliott Hughes    # for graphite type tables in ttLib/tables (Silf, Glat, Gloc)
114*e1fe3e4aSElliott Hughes    "graphite": ["lz4 >= 1.7.4.2"],
115*e1fe3e4aSElliott Hughes    # for fontTools.interpolatable: to solve the "minimum weight perfect
116*e1fe3e4aSElliott Hughes    # matching problem in bipartite graphs" (aka Assignment problem)
117*e1fe3e4aSElliott Hughes    "interpolatable": [
118*e1fe3e4aSElliott Hughes        # use pure-python alternative on pypy
119*e1fe3e4aSElliott Hughes        "scipy; platform_python_implementation != 'PyPy'",
120*e1fe3e4aSElliott Hughes        "munkres; platform_python_implementation == 'PyPy'",
121*e1fe3e4aSElliott Hughes        # to output PDF or HTML reports. NOTE: wheels are only available for
122*e1fe3e4aSElliott Hughes        # windows currently, other platforms will need to build from source.
123*e1fe3e4aSElliott Hughes        "pycairo",
124*e1fe3e4aSElliott Hughes    ],
125*e1fe3e4aSElliott Hughes    # for fontTools.varLib.plot, to visualize DesignSpaceDocument and resulting
126*e1fe3e4aSElliott Hughes    # VariationModel
127*e1fe3e4aSElliott Hughes    "plot": [
128*e1fe3e4aSElliott Hughes        # TODO: figure out the minimum version of matplotlib that we need
129*e1fe3e4aSElliott Hughes        "matplotlib",
130*e1fe3e4aSElliott Hughes    ],
131*e1fe3e4aSElliott Hughes    # for fontTools.misc.symfont, module for symbolic font statistics analysis
132*e1fe3e4aSElliott Hughes    "symfont": [
133*e1fe3e4aSElliott Hughes        "sympy",
134*e1fe3e4aSElliott Hughes    ],
135*e1fe3e4aSElliott Hughes    # To get file creator and type of Macintosh PostScript Type 1 fonts (macOS only)
136*e1fe3e4aSElliott Hughes    "type1": [
137*e1fe3e4aSElliott Hughes        "xattr; sys_platform == 'darwin'",
138*e1fe3e4aSElliott Hughes    ],
139*e1fe3e4aSElliott Hughes    # for fontTools.ttLib.removeOverlaps, to remove overlaps in TTF fonts
140*e1fe3e4aSElliott Hughes    "pathops": [
141*e1fe3e4aSElliott Hughes        "skia-pathops >= 0.5.0",
142*e1fe3e4aSElliott Hughes    ],
143*e1fe3e4aSElliott Hughes    # for packing GSUB/GPOS tables with Harfbuzz repacker
144*e1fe3e4aSElliott Hughes    "repacker": [
145*e1fe3e4aSElliott Hughes        "uharfbuzz >= 0.23.0",
146*e1fe3e4aSElliott Hughes    ],
147*e1fe3e4aSElliott Hughes}
148*e1fe3e4aSElliott Hughes# use a special 'all' key as shorthand to includes all the extra dependencies
149*e1fe3e4aSElliott Hughesextras_require["all"] = sum(extras_require.values(), [])
150*e1fe3e4aSElliott Hughes
151*e1fe3e4aSElliott Hughes
152*e1fe3e4aSElliott Hughes# Trove classifiers for PyPI
153*e1fe3e4aSElliott Hughesclassifiers = {
154*e1fe3e4aSElliott Hughes    "classifiers": [
155*e1fe3e4aSElliott Hughes        "Development Status :: 5 - Production/Stable",
156*e1fe3e4aSElliott Hughes        "Environment :: Console",
157*e1fe3e4aSElliott Hughes        "Environment :: Other Environment",
158*e1fe3e4aSElliott Hughes        "Intended Audience :: Developers",
159*e1fe3e4aSElliott Hughes        "Intended Audience :: End Users/Desktop",
160*e1fe3e4aSElliott Hughes        "License :: OSI Approved :: MIT License",
161*e1fe3e4aSElliott Hughes        "Natural Language :: English",
162*e1fe3e4aSElliott Hughes        "Operating System :: OS Independent",
163*e1fe3e4aSElliott Hughes        "Programming Language :: Python",
164*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3.8",
165*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3.9",
166*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3.10",
167*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3.11",
168*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3.12",
169*e1fe3e4aSElliott Hughes        "Programming Language :: Python :: 3",
170*e1fe3e4aSElliott Hughes        "Topic :: Text Processing :: Fonts",
171*e1fe3e4aSElliott Hughes        "Topic :: Multimedia :: Graphics",
172*e1fe3e4aSElliott Hughes        "Topic :: Multimedia :: Graphics :: Graphics Conversion",
173*e1fe3e4aSElliott Hughes    ]
174*e1fe3e4aSElliott Hughes}
175*e1fe3e4aSElliott Hughes
176*e1fe3e4aSElliott Hughes
177*e1fe3e4aSElliott Hughes# concatenate README.rst and NEWS.rest into long_description so they are
178*e1fe3e4aSElliott Hughes# displayed on the FontTols project page on PyPI
179*e1fe3e4aSElliott Hugheswith io.open("README.rst", "r", encoding="utf-8") as readme:
180*e1fe3e4aSElliott Hughes    long_description = readme.read()
181*e1fe3e4aSElliott Hugheslong_description += "\nChangelog\n~~~~~~~~~\n\n"
182*e1fe3e4aSElliott Hugheswith io.open("NEWS.rst", "r", encoding="utf-8") as changelog:
183*e1fe3e4aSElliott Hughes    long_description += changelog.read()
184*e1fe3e4aSElliott Hughes
185*e1fe3e4aSElliott Hughes
186*e1fe3e4aSElliott Hughes@contextlib.contextmanager
187*e1fe3e4aSElliott Hughesdef capture_logger(name):
188*e1fe3e4aSElliott Hughes    """Context manager to capture a logger output with a StringIO stream."""
189*e1fe3e4aSElliott Hughes    import logging
190*e1fe3e4aSElliott Hughes
191*e1fe3e4aSElliott Hughes    logger = logging.getLogger(name)
192*e1fe3e4aSElliott Hughes    try:
193*e1fe3e4aSElliott Hughes        import StringIO
194*e1fe3e4aSElliott Hughes
195*e1fe3e4aSElliott Hughes        stream = StringIO.StringIO()
196*e1fe3e4aSElliott Hughes    except ImportError:
197*e1fe3e4aSElliott Hughes        stream = io.StringIO()
198*e1fe3e4aSElliott Hughes    handler = logging.StreamHandler(stream)
199*e1fe3e4aSElliott Hughes    logger.addHandler(handler)
200*e1fe3e4aSElliott Hughes    try:
201*e1fe3e4aSElliott Hughes        yield stream
202*e1fe3e4aSElliott Hughes    finally:
203*e1fe3e4aSElliott Hughes        logger.removeHandler(handler)
204*e1fe3e4aSElliott Hughes
205*e1fe3e4aSElliott Hughes
206*e1fe3e4aSElliott Hughesclass release(Command):
207*e1fe3e4aSElliott Hughes    """
208*e1fe3e4aSElliott Hughes    Tag a new release with a single command, using the 'bumpversion' tool
209*e1fe3e4aSElliott Hughes    to update all the version strings in the source code.
210*e1fe3e4aSElliott Hughes    The version scheme conforms to 'SemVer' and PEP 440 specifications.
211*e1fe3e4aSElliott Hughes
212*e1fe3e4aSElliott Hughes    Firstly, the pre-release '.devN' suffix is dropped to signal that this is
213*e1fe3e4aSElliott Hughes    a stable release. If '--major' or '--minor' options are passed, the
214*e1fe3e4aSElliott Hughes    the first or second 'semver' digit is also incremented. Major is usually
215*e1fe3e4aSElliott Hughes    for backward-incompatible API changes, while minor is used when adding
216*e1fe3e4aSElliott Hughes    new backward-compatible functionalities. No options imply 'patch' or bug-fix
217*e1fe3e4aSElliott Hughes    release.
218*e1fe3e4aSElliott Hughes
219*e1fe3e4aSElliott Hughes    A new header is also added to the changelog file ("NEWS.rst"), containing
220*e1fe3e4aSElliott Hughes    the new version string and the current 'YYYY-MM-DD' date.
221*e1fe3e4aSElliott Hughes
222*e1fe3e4aSElliott Hughes    All changes are committed, and an annotated git tag is generated. With the
223*e1fe3e4aSElliott Hughes    --sign option, the tag is GPG-signed with the user's default key.
224*e1fe3e4aSElliott Hughes
225*e1fe3e4aSElliott Hughes    Finally, the 'patch' part of the version string is bumped again, and a
226*e1fe3e4aSElliott Hughes    pre-release suffix '.dev0' is appended to mark the opening of a new
227*e1fe3e4aSElliott Hughes    development cycle.
228*e1fe3e4aSElliott Hughes
229*e1fe3e4aSElliott Hughes    Links:
230*e1fe3e4aSElliott Hughes    - http://semver.org/
231*e1fe3e4aSElliott Hughes    - https://www.python.org/dev/peps/pep-0440/
232*e1fe3e4aSElliott Hughes    - https://github.com/c4urself/bump2version
233*e1fe3e4aSElliott Hughes    """
234*e1fe3e4aSElliott Hughes
235*e1fe3e4aSElliott Hughes    description = "update version strings for release"
236*e1fe3e4aSElliott Hughes
237*e1fe3e4aSElliott Hughes    user_options = [
238*e1fe3e4aSElliott Hughes        ("major", None, "bump the first digit (incompatible API changes)"),
239*e1fe3e4aSElliott Hughes        ("minor", None, "bump the second digit (new backward-compatible features)"),
240*e1fe3e4aSElliott Hughes        ("sign", "s", "make a GPG-signed tag, using the default key"),
241*e1fe3e4aSElliott Hughes        ("allow-dirty", None, "don't abort if working directory is dirty"),
242*e1fe3e4aSElliott Hughes    ]
243*e1fe3e4aSElliott Hughes
244*e1fe3e4aSElliott Hughes    changelog_name = "NEWS.rst"
245*e1fe3e4aSElliott Hughes    version_RE = re.compile(r"^[0-9]+\.[0-9]+")
246*e1fe3e4aSElliott Hughes    date_fmt = "%Y-%m-%d"
247*e1fe3e4aSElliott Hughes    header_fmt = "%s (released %s)"
248*e1fe3e4aSElliott Hughes    commit_message = "Release {new_version}"
249*e1fe3e4aSElliott Hughes    tag_name = "{new_version}"
250*e1fe3e4aSElliott Hughes    version_files = [
251*e1fe3e4aSElliott Hughes        "setup.cfg",
252*e1fe3e4aSElliott Hughes        "setup.py",
253*e1fe3e4aSElliott Hughes        "Lib/fontTools/__init__.py",
254*e1fe3e4aSElliott Hughes    ]
255*e1fe3e4aSElliott Hughes
256*e1fe3e4aSElliott Hughes    def initialize_options(self):
257*e1fe3e4aSElliott Hughes        self.minor = False
258*e1fe3e4aSElliott Hughes        self.major = False
259*e1fe3e4aSElliott Hughes        self.sign = False
260*e1fe3e4aSElliott Hughes        self.allow_dirty = False
261*e1fe3e4aSElliott Hughes
262*e1fe3e4aSElliott Hughes    def finalize_options(self):
263*e1fe3e4aSElliott Hughes        if all([self.major, self.minor]):
264*e1fe3e4aSElliott Hughes            from distutils.errors import DistutilsOptionError
265*e1fe3e4aSElliott Hughes
266*e1fe3e4aSElliott Hughes            raise DistutilsOptionError("--major/--minor are mutually exclusive")
267*e1fe3e4aSElliott Hughes        self.part = "major" if self.major else "minor" if self.minor else None
268*e1fe3e4aSElliott Hughes
269*e1fe3e4aSElliott Hughes    def run(self):
270*e1fe3e4aSElliott Hughes        if self.part is not None:
271*e1fe3e4aSElliott Hughes            log.info("bumping '%s' version" % self.part)
272*e1fe3e4aSElliott Hughes            self.bumpversion(self.part, commit=False)
273*e1fe3e4aSElliott Hughes            release_version = self.bumpversion(
274*e1fe3e4aSElliott Hughes                "release", commit=False, allow_dirty=True
275*e1fe3e4aSElliott Hughes            )
276*e1fe3e4aSElliott Hughes        else:
277*e1fe3e4aSElliott Hughes            log.info("stripping pre-release suffix")
278*e1fe3e4aSElliott Hughes            release_version = self.bumpversion("release")
279*e1fe3e4aSElliott Hughes        log.info("  version = %s" % release_version)
280*e1fe3e4aSElliott Hughes
281*e1fe3e4aSElliott Hughes        changes = self.format_changelog(release_version)
282*e1fe3e4aSElliott Hughes
283*e1fe3e4aSElliott Hughes        self.git_commit(release_version)
284*e1fe3e4aSElliott Hughes        self.git_tag(release_version, changes, self.sign)
285*e1fe3e4aSElliott Hughes
286*e1fe3e4aSElliott Hughes        log.info("bumping 'patch' version and pre-release suffix")
287*e1fe3e4aSElliott Hughes        next_dev_version = self.bumpversion("patch", commit=True)
288*e1fe3e4aSElliott Hughes        log.info("  version = %s" % next_dev_version)
289*e1fe3e4aSElliott Hughes
290*e1fe3e4aSElliott Hughes    def git_commit(self, version):
291*e1fe3e4aSElliott Hughes        """Stage and commit all relevant version files, and format the commit
292*e1fe3e4aSElliott Hughes        message with specified 'version' string.
293*e1fe3e4aSElliott Hughes        """
294*e1fe3e4aSElliott Hughes        files = self.version_files + [self.changelog_name]
295*e1fe3e4aSElliott Hughes
296*e1fe3e4aSElliott Hughes        log.info("committing changes")
297*e1fe3e4aSElliott Hughes        for f in files:
298*e1fe3e4aSElliott Hughes            log.info("  %s" % f)
299*e1fe3e4aSElliott Hughes        if self.dry_run:
300*e1fe3e4aSElliott Hughes            return
301*e1fe3e4aSElliott Hughes        sp.check_call(["git", "add"] + files)
302*e1fe3e4aSElliott Hughes        msg = self.commit_message.format(new_version=version)
303*e1fe3e4aSElliott Hughes        sp.check_call(["git", "commit", "-m", msg], stdout=sp.PIPE)
304*e1fe3e4aSElliott Hughes
305*e1fe3e4aSElliott Hughes    def git_tag(self, version, message, sign=False):
306*e1fe3e4aSElliott Hughes        """Create annotated git tag with given 'version' and 'message'.
307*e1fe3e4aSElliott Hughes        Optionally 'sign' the tag with the user's GPG key.
308*e1fe3e4aSElliott Hughes        """
309*e1fe3e4aSElliott Hughes        log.info(
310*e1fe3e4aSElliott Hughes            "creating %s git tag '%s'" % ("signed" if sign else "annotated", version)
311*e1fe3e4aSElliott Hughes        )
312*e1fe3e4aSElliott Hughes        if self.dry_run:
313*e1fe3e4aSElliott Hughes            return
314*e1fe3e4aSElliott Hughes        # create an annotated (or signed) tag from the new version
315*e1fe3e4aSElliott Hughes        tag_opt = "-s" if sign else "-a"
316*e1fe3e4aSElliott Hughes        tag_name = self.tag_name.format(new_version=version)
317*e1fe3e4aSElliott Hughes        proc = sp.Popen(["git", "tag", tag_opt, "-F", "-", tag_name], stdin=sp.PIPE)
318*e1fe3e4aSElliott Hughes        # use the latest changes from the changelog file as the tag message
319*e1fe3e4aSElliott Hughes        tag_message = "%s\n\n%s" % (tag_name, message)
320*e1fe3e4aSElliott Hughes        proc.communicate(tag_message.encode("utf-8"))
321*e1fe3e4aSElliott Hughes        if proc.returncode != 0:
322*e1fe3e4aSElliott Hughes            sys.exit(proc.returncode)
323*e1fe3e4aSElliott Hughes
324*e1fe3e4aSElliott Hughes    def bumpversion(self, part, commit=False, message=None, allow_dirty=None):
325*e1fe3e4aSElliott Hughes        """Run bumpversion.main() with the specified arguments, and return the
326*e1fe3e4aSElliott Hughes        new computed version string (cf. 'bumpversion --help' for more info)
327*e1fe3e4aSElliott Hughes        """
328*e1fe3e4aSElliott Hughes        import bumpversion.cli
329*e1fe3e4aSElliott Hughes
330*e1fe3e4aSElliott Hughes        args = (
331*e1fe3e4aSElliott Hughes            (["--verbose"] if self.verbose > 1 else [])
332*e1fe3e4aSElliott Hughes            + (["--dry-run"] if self.dry_run else [])
333*e1fe3e4aSElliott Hughes            + (["--allow-dirty"] if (allow_dirty or self.allow_dirty) else [])
334*e1fe3e4aSElliott Hughes            + (["--commit"] if commit else ["--no-commit"])
335*e1fe3e4aSElliott Hughes            + (["--message", message] if message is not None else [])
336*e1fe3e4aSElliott Hughes            + ["--list", part]
337*e1fe3e4aSElliott Hughes        )
338*e1fe3e4aSElliott Hughes        log.debug("$ bumpversion %s" % " ".join(a.replace(" ", "\\ ") for a in args))
339*e1fe3e4aSElliott Hughes
340*e1fe3e4aSElliott Hughes        with capture_logger("bumpversion.list") as out:
341*e1fe3e4aSElliott Hughes            bumpversion.cli.main(args)
342*e1fe3e4aSElliott Hughes
343*e1fe3e4aSElliott Hughes        last_line = out.getvalue().splitlines()[-1]
344*e1fe3e4aSElliott Hughes        new_version = last_line.replace("new_version=", "")
345*e1fe3e4aSElliott Hughes        return new_version
346*e1fe3e4aSElliott Hughes
347*e1fe3e4aSElliott Hughes    def format_changelog(self, version):
348*e1fe3e4aSElliott Hughes        """Write new header at beginning of changelog file with the specified
349*e1fe3e4aSElliott Hughes        'version' and the current date.
350*e1fe3e4aSElliott Hughes        Return the changelog content for the current release.
351*e1fe3e4aSElliott Hughes        """
352*e1fe3e4aSElliott Hughes        from datetime import datetime
353*e1fe3e4aSElliott Hughes
354*e1fe3e4aSElliott Hughes        log.info("formatting changelog")
355*e1fe3e4aSElliott Hughes
356*e1fe3e4aSElliott Hughes        changes = []
357*e1fe3e4aSElliott Hughes        with io.open(self.changelog_name, "r+", encoding="utf-8") as f:
358*e1fe3e4aSElliott Hughes            for ln in f:
359*e1fe3e4aSElliott Hughes                if self.version_RE.match(ln):
360*e1fe3e4aSElliott Hughes                    break
361*e1fe3e4aSElliott Hughes                else:
362*e1fe3e4aSElliott Hughes                    changes.append(ln)
363*e1fe3e4aSElliott Hughes            if not self.dry_run:
364*e1fe3e4aSElliott Hughes                f.seek(0)
365*e1fe3e4aSElliott Hughes                content = f.read()
366*e1fe3e4aSElliott Hughes                date = datetime.today().strftime(self.date_fmt)
367*e1fe3e4aSElliott Hughes                f.seek(0)
368*e1fe3e4aSElliott Hughes                header = self.header_fmt % (version, date)
369*e1fe3e4aSElliott Hughes                f.write(header + "\n" + "-" * len(header) + "\n\n" + content)
370*e1fe3e4aSElliott Hughes
371*e1fe3e4aSElliott Hughes        return "".join(changes)
372*e1fe3e4aSElliott Hughes
373*e1fe3e4aSElliott Hughes
374*e1fe3e4aSElliott Hughesdef find_data_files(manpath="share/man"):
375*e1fe3e4aSElliott Hughes    """Find FontTools's data_files (just man pages at this point).
376*e1fe3e4aSElliott Hughes
377*e1fe3e4aSElliott Hughes    By default, we install man pages to "share/man" directory relative to the
378*e1fe3e4aSElliott Hughes    base installation directory for data_files. The latter can be changed with
379*e1fe3e4aSElliott Hughes    the --install-data option of 'setup.py install' sub-command.
380*e1fe3e4aSElliott Hughes
381*e1fe3e4aSElliott Hughes    E.g., if the data files installation directory is "/usr", the default man
382*e1fe3e4aSElliott Hughes    page installation directory will be "/usr/share/man".
383*e1fe3e4aSElliott Hughes
384*e1fe3e4aSElliott Hughes    You can override this via the $FONTTOOLS_MANPATH environment variable.
385*e1fe3e4aSElliott Hughes
386*e1fe3e4aSElliott Hughes    E.g., on some BSD systems man pages are installed to 'man' instead of
387*e1fe3e4aSElliott Hughes    'share/man'; you can export $FONTTOOLS_MANPATH variable just before
388*e1fe3e4aSElliott Hughes    installing:
389*e1fe3e4aSElliott Hughes
390*e1fe3e4aSElliott Hughes    $ FONTTOOLS_MANPATH="man" pip install -v .
391*e1fe3e4aSElliott Hughes        [...]
392*e1fe3e4aSElliott Hughes        running install_data
393*e1fe3e4aSElliott Hughes        copying Doc/man/ttx.1 -> /usr/man/man1
394*e1fe3e4aSElliott Hughes
395*e1fe3e4aSElliott Hughes    When installing from PyPI, for this variable to have effect you need to
396*e1fe3e4aSElliott Hughes    force pip to install from the source distribution instead of the wheel
397*e1fe3e4aSElliott Hughes    package (otherwise setup.py is not run), by using the --no-binary option:
398*e1fe3e4aSElliott Hughes
399*e1fe3e4aSElliott Hughes    $ FONTTOOLS_MANPATH="man" pip install --no-binary=fonttools fonttools
400*e1fe3e4aSElliott Hughes
401*e1fe3e4aSElliott Hughes    Note that you can only override the base man path, i.e. without the
402*e1fe3e4aSElliott Hughes    section number (man1, man3, etc.). The latter is always implied to be 1,
403*e1fe3e4aSElliott Hughes    for "general commands".
404*e1fe3e4aSElliott Hughes    """
405*e1fe3e4aSElliott Hughes
406*e1fe3e4aSElliott Hughes    # get base installation directory for man pages
407*e1fe3e4aSElliott Hughes    manpagebase = os.environ.get("FONTTOOLS_MANPATH", convert_path(manpath))
408*e1fe3e4aSElliott Hughes    # all our man pages go to section 1
409*e1fe3e4aSElliott Hughes    manpagedir = pjoin(manpagebase, "man1")
410*e1fe3e4aSElliott Hughes
411*e1fe3e4aSElliott Hughes    manpages = [f for f in glob(pjoin("Doc", "man", "man1", "*.1")) if isfile(f)]
412*e1fe3e4aSElliott Hughes
413*e1fe3e4aSElliott Hughes    data_files = [(manpagedir, manpages)]
414*e1fe3e4aSElliott Hughes    return data_files
415*e1fe3e4aSElliott Hughes
416*e1fe3e4aSElliott Hughes
417*e1fe3e4aSElliott Hughesclass cython_build_ext(_build_ext):
418*e1fe3e4aSElliott Hughes    """Compile *.pyx source files to *.c using cythonize if Cython is
419*e1fe3e4aSElliott Hughes    installed and there is a working C compiler, else fall back to pure python dist.
420*e1fe3e4aSElliott Hughes    """
421*e1fe3e4aSElliott Hughes
422*e1fe3e4aSElliott Hughes    def finalize_options(self):
423*e1fe3e4aSElliott Hughes        from Cython.Build import cythonize
424*e1fe3e4aSElliott Hughes
425*e1fe3e4aSElliott Hughes        # optionally enable line tracing for test coverage support
426*e1fe3e4aSElliott Hughes        linetrace = os.environ.get("CYTHON_TRACE") == "1"
427*e1fe3e4aSElliott Hughes
428*e1fe3e4aSElliott Hughes        self.distribution.ext_modules[:] = cythonize(
429*e1fe3e4aSElliott Hughes            self.distribution.ext_modules,
430*e1fe3e4aSElliott Hughes            force=linetrace or self.force,
431*e1fe3e4aSElliott Hughes            annotate=os.environ.get("CYTHON_ANNOTATE") == "1",
432*e1fe3e4aSElliott Hughes            quiet=not self.verbose,
433*e1fe3e4aSElliott Hughes            compiler_directives={
434*e1fe3e4aSElliott Hughes                "linetrace": linetrace,
435*e1fe3e4aSElliott Hughes                "language_level": 3,
436*e1fe3e4aSElliott Hughes                "embedsignature": True,
437*e1fe3e4aSElliott Hughes            },
438*e1fe3e4aSElliott Hughes        )
439*e1fe3e4aSElliott Hughes
440*e1fe3e4aSElliott Hughes        _build_ext.finalize_options(self)
441*e1fe3e4aSElliott Hughes
442*e1fe3e4aSElliott Hughes    def build_extensions(self):
443*e1fe3e4aSElliott Hughes        try:
444*e1fe3e4aSElliott Hughes            _build_ext.build_extensions(self)
445*e1fe3e4aSElliott Hughes        except Exception as e:
446*e1fe3e4aSElliott Hughes            if with_cython:
447*e1fe3e4aSElliott Hughes                raise
448*e1fe3e4aSElliott Hughes            from distutils.errors import DistutilsModuleError
449*e1fe3e4aSElliott Hughes
450*e1fe3e4aSElliott Hughes            # optional compilation failed: we delete 'ext_modules' and make sure
451*e1fe3e4aSElliott Hughes            # the generated wheel is 'pure'
452*e1fe3e4aSElliott Hughes            del self.distribution.ext_modules[:]
453*e1fe3e4aSElliott Hughes            try:
454*e1fe3e4aSElliott Hughes                bdist_wheel = self.get_finalized_command("bdist_wheel")
455*e1fe3e4aSElliott Hughes            except DistutilsModuleError:
456*e1fe3e4aSElliott Hughes                # 'bdist_wheel' command not available as wheel is not installed
457*e1fe3e4aSElliott Hughes                pass
458*e1fe3e4aSElliott Hughes            else:
459*e1fe3e4aSElliott Hughes                bdist_wheel.root_is_pure = True
460*e1fe3e4aSElliott Hughes            log.error("error: building extensions failed: %s" % e)
461*e1fe3e4aSElliott Hughes
462*e1fe3e4aSElliott Hughes
463*e1fe3e4aSElliott Hughescmdclass = {"release": release}
464*e1fe3e4aSElliott Hughes
465*e1fe3e4aSElliott Hughesif ext_modules:
466*e1fe3e4aSElliott Hughes    cmdclass["build_ext"] = cython_build_ext
467*e1fe3e4aSElliott Hughes
468*e1fe3e4aSElliott Hughes
469*e1fe3e4aSElliott Hughessetup_params = dict(
470*e1fe3e4aSElliott Hughes    name="fonttools",
471*e1fe3e4aSElliott Hughes    version="4.49.0",
472*e1fe3e4aSElliott Hughes    description="Tools to manipulate font files",
473*e1fe3e4aSElliott Hughes    author="Just van Rossum",
474*e1fe3e4aSElliott Hughes    author_email="[email protected]",
475*e1fe3e4aSElliott Hughes    maintainer="Behdad Esfahbod",
476*e1fe3e4aSElliott Hughes    maintainer_email="[email protected]",
477*e1fe3e4aSElliott Hughes    url="http://github.com/fonttools/fonttools",
478*e1fe3e4aSElliott Hughes    license="MIT",
479*e1fe3e4aSElliott Hughes    platforms=["Any"],
480*e1fe3e4aSElliott Hughes    python_requires=">=3.8",
481*e1fe3e4aSElliott Hughes    long_description=long_description,
482*e1fe3e4aSElliott Hughes    package_dir={"": "Lib"},
483*e1fe3e4aSElliott Hughes    packages=find_packages("Lib"),
484*e1fe3e4aSElliott Hughes    include_package_data=True,
485*e1fe3e4aSElliott Hughes    data_files=find_data_files(),
486*e1fe3e4aSElliott Hughes    ext_modules=ext_modules,
487*e1fe3e4aSElliott Hughes    setup_requires=setup_requires,
488*e1fe3e4aSElliott Hughes    extras_require=extras_require,
489*e1fe3e4aSElliott Hughes    entry_points={
490*e1fe3e4aSElliott Hughes        "console_scripts": [
491*e1fe3e4aSElliott Hughes            "fonttools = fontTools.__main__:main",
492*e1fe3e4aSElliott Hughes            "ttx = fontTools.ttx:main",
493*e1fe3e4aSElliott Hughes            "pyftsubset = fontTools.subset:main",
494*e1fe3e4aSElliott Hughes            "pyftmerge = fontTools.merge:main",
495*e1fe3e4aSElliott Hughes        ]
496*e1fe3e4aSElliott Hughes    },
497*e1fe3e4aSElliott Hughes    cmdclass=cmdclass,
498*e1fe3e4aSElliott Hughes    **classifiers,
499*e1fe3e4aSElliott Hughes)
500*e1fe3e4aSElliott Hughes
501*e1fe3e4aSElliott Hughes
502*e1fe3e4aSElliott Hughesif __name__ == "__main__":
503*e1fe3e4aSElliott Hughes    setup(**setup_params)
504