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