Browse Source

Add Meson build project file.

Example usage:

  # Configure Meson build in directory `build-meson` to generate
  # release binaries comparable to to the ones from the
  # autotools/make build system.
  meson setup build-meson \
        --prefix=/usr/local \
        --buildtype=debugoptimized \
        --strip \
        -Db_ndebug=true

  # After configuring the Meson build with the above command,
  # compile and install to `/usr/local/`; this includes a pkg-config
  # file.
  ninja -C build-meson install

  # Alternatively, compile and install to `/tmp/aa/usr/local/...`
  # for packaging.
  DESTDIR=/tmp/aa ninja -C build-meson install

  # Generate documentation under `build-meson/docs`.
  ninja -C build-meson docs

Library size comparison for stripped `libfreetype.so` generated by
all three build systems:

  - Default build (autotools + libtool): 712 KiB
  - CMake build (RelWithDebInfo):        712 KiB
  - Meson build:                         712 KiB

* meson.build: New top-level Meson build file for the library.

* meson_options.txt: New file.  It holds user-selectable options for
the build, which can be printed with `meson configure`, and selected
at `meson setup` or `meson --reconfigure` time with
`-D<option>=<value>`.

* scripts/parse_modules_cfg.py: A script invoked by `meson.build` to
parse `modules.cfg` and extract important information out of it
(i.e., the list of modules).

* scripts/process_ftoption_h.py: New script invoked by `meson.build`
to process the original `ftoption.h` file.  It enables or disables
configuration macro variables based on the available dependencies.
This is similar to what other build systems are using (i.e., Meson's
`configure_file()` command is not used here).

* scripts/extract_freetype_version.py: New script invoked by
`meson.build` to extract the FreeType version number from
`<freetype/freetype.h>`.

* scripts/extract_libtool_version.py: New script invoked by
`meson.build` to extract the libtool `revision_info` data from
`builds/unix/configure.raw`, and to generate the corresponding
shared library suffix.

* scripts/generate_reference_docs.py: New script invoked by
`meson.build` to generate the FreeType 2 reference documentation
(using the `docwriter` and `mkdocs` packages, which must be already
installed).
master
David Turner 1 year ago
committed by Werner Lemberg
parent
commit
66978a5887
  1. 66
      ChangeLog
  2. 2
      builds/unix/ftsystem.c
  3. 368
      meson.build
  4. 47
      meson_options.txt
  5. 107
      scripts/extract_freetype_version.py
  6. 105
      scripts/extract_libtool_version.py
  7. 79
      scripts/generate_reference_docs.py
  8. 160
      scripts/parse_modules_cfg.py
  9. 105
      scripts/process_ftoption_h.py

66
ChangeLog

@ -1,3 +1,69 @@
2020-09-21 David Turner <david@freetype.org>
Add Meson build project file.
Example usage:
# Configure Meson build in directory `build-meson` to generate
# release binaries comparable to to the ones from the
# autotools/make build system.
meson setup build-meson \
--prefix=/usr/local \
--buildtype=debugoptimized \
--strip \
-Db_ndebug=true
# After configuring the Meson build with the above command,
# compile and install to `/usr/local/`; this includes a pkg-config
# file.
ninja -C build-meson install
# Alternatively, compile and install to `/tmp/aa/usr/local/...`
# for packaging.
DESTDIR=/tmp/aa ninja -C build-meson install
# Generate documentation under `build-meson/docs`.
ninja -C build-meson docs
Library size comparison for stripped `libfreetype.so` generated by
all three build systems:
- Default build (autotools + libtool): 712 KiB
- CMake build (RelWithDebInfo): 712 KiB
- Meson build: 712 KiB
* meson.build: New top-level Meson build file for the library.
* meson_options.txt: New file. It holds user-selectable options for
the build, which can be printed with `meson configure`, and selected
at `meson setup` or `meson --reconfigure` time with
`-D<option>=<value>`.
* scripts/parse_modules_cfg.py: A script invoked by `meson.build` to
parse `modules.cfg` and extract important information out of it
(i.e., the list of modules).
* scripts/process_ftoption_h.py: New script invoked by `meson.build`
to process the original `ftoption.h` file. It enables or disables
configuration macro variables based on the available dependencies.
This is similar to what other build systems are using (i.e., Meson's
`configure_file()` command is not used here).
* scripts/extract_freetype_version.py: New script invoked by
`meson.build` to extract the FreeType version number from
`<freetype/freetype.h>`.
* scripts/extract_libtool_version.py: New script invoked by
`meson.build` to extract the libtool `revision_info` data from
`builds/unix/configure.raw`, and to generate the corresponding
shared library suffix.
* scripts/generate_reference_docs.py: New script invoked by
`meson.build` to generate the FreeType 2 reference documentation
(using the `docwriter` and `mkdocs` packages, which must be already
installed).
2020-09-11 Alexei Podtelezhnikov <apodtele@gmail.com>
[raster] Improve the second pass (#58373).

2
builds/unix/ftsystem.c

@ -18,7 +18,7 @@
#include <ft2build.h>
/* we use our special ftconfig.h file, not the standard one */
#include <ftconfig.h>
#include FT_CONFIG_CONFIG_H
#include <freetype/internal/ftdebug.h>
#include <freetype/ftsystem.h>
#include <freetype/fterrors.h>

368
meson.build

@ -0,0 +1,368 @@
#
# Meson project file for FreeType 2
#
# Copyright (C) 2020 by
# David Turner, Robert Wilhelm, and Werner Lemberg.
#
# This file is part of the FreeType project, and may only be used, modified,
# and distributed under the terms of the FreeType project license,
# LICENSE.TXT. By continuing to use, modify, or distribute this file you
# indicate that you have read the license and understand and accept it
# fully.
project('freetype2', 'c',
meson_version: '>= 0.55.0',
default_options: ['default_library=both'],
)
#
# Rules to compile the FreeType 2 library itself
#
# Apparently meson doesn't provide a read_file() function, so instead
# running an external command is required.
python = import('python')
python_exe = python.find_installation(required: true)
ft2_version = run_command(python_exe,
files('scripts/extract_freetype_version.py'),
files('include/freetype/freetype.h')).stdout().strip()
ft2_libtool_version = run_command(python_exe,
files('scripts/extract_libtool_version.py'),
'--soversion',
files('builds/unix/configure.raw')).stdout().strip()
ft2_includes = include_directories('include')
# Generate a custom `ftmodule.h` version based on the content of
# `modules.cfg`.
ftmodule_h = custom_target('ftmodule.h',
output: 'ftmodule.h',
input: 'modules.cfg',
command: [python_exe, files('scripts/parse_modules_cfg.py'),
'--format=ftmodule.h', '@INPUT@', '--output', '@OUTPUT@'],
install: true,
install_dir: 'include/freetype2/freetype/config',
)
ft2_sources = [ftmodule_h]
# FreeType 2 modules.
ft_main_modules = run_command(python_exe,
files('scripts/parse_modules_cfg.py'),
'--format=main-modules',
files('modules.cfg')).stdout().strip().split()
ft2_sources += files([
'src/base/ftbase.c',
'src/base/ftinit.c',
])
foreach mod: ft_main_modules
source = mod
if mod == 'winfonts'
source = 'winfnt'
elif mod == 'cid'
source = 'type1cid'
endif
ft2_sources += 'src/@0@/@1@.c'.format(mod, source)
endforeach
# NOTE: The `gzip` and `bzip2` aux modules are handled through options.
ft_aux_modules = run_command(python_exe,
files('scripts/parse_modules_cfg.py'),
'--format=aux-modules',
files('modules.cfg')).stdout().strip().split()
foreach auxmod: ft_aux_modules
source = auxmod
# Most sources are named `src/<module>/<module>.c`, but there are a few
# exceptions handled here.
if auxmod == 'cache'
source = 'ftcache'
elif auxmod == 'lzw'
source = 'ftlzw'
elif auxmod == 'gzip' or auxmod == 'bzip2'
# Handled through options instead, see below.
continue
endif
ft2_sources += 'src/@0@/@1@.c'.format(auxmod, source)
endforeach
# FreeType 2 base extensions.
# Normally configured through `modules.cfg`.
base_extensions = run_command(python_exe,
files('scripts/parse_modules_cfg.py'),
'--format=base-extensions-list',
files('modules.cfg')).stdout().split()
foreach ext: base_extensions
ft2_sources += files('src/base/' + ext)
endforeach
# Header files.
ft2_public_headers = files([
'include/freetype/freetype.h',
'include/freetype/ftadvanc.h',
'include/freetype/ftbbox.h',
'include/freetype/ftbdf.h',
'include/freetype/ftbitmap.h',
'include/freetype/ftbzip2.h',
'include/freetype/ftcache.h',
'include/freetype/ftchapters.h',
'include/freetype/ftcolor.h',
'include/freetype/ftdriver.h',
'include/freetype/fterrdef.h',
'include/freetype/fterrors.h',
'include/freetype/ftfntfmt.h',
'include/freetype/ftgasp.h',
'include/freetype/ftglyph.h',
'include/freetype/ftgxval.h',
'include/freetype/ftgzip.h',
'include/freetype/ftimage.h',
'include/freetype/ftincrem.h',
'include/freetype/ftlcdfil.h',
'include/freetype/ftlist.h',
'include/freetype/ftlzw.h',
'include/freetype/ftmac.h',
'include/freetype/ftmm.h',
'include/freetype/ftmodapi.h',
'include/freetype/ftmoderr.h',
'include/freetype/ftotval.h',
'include/freetype/ftoutln.h',
'include/freetype/ftparams.h',
'include/freetype/ftpfr.h',
'include/freetype/ftrender.h',
'include/freetype/ftsizes.h',
'include/freetype/ftsnames.h',
'include/freetype/ftstroke.h',
'include/freetype/ftsynth.h',
'include/freetype/ftsystem.h',
'include/freetype/fttrigon.h',
'include/freetype/fttypes.h',
'include/freetype/ftwinfnt.h',
'include/freetype/t1tables.h',
'include/freetype/ttnameid.h',
'include/freetype/tttables.h',
'include/freetype/tttags.h',
])
ft2_config_headers = files([
'include/freetype/config/ftconfig.h',
'include/freetype/config/ftheader.h',
'include/freetype/config/ftstdlib.h',
'include/freetype/config/integer-types.h',
'include/freetype/config/mac-support.h',
'include/freetype/config/public-macros.h',
])
ft2_defines = []
# System support file.
cc = meson.get_compiler('c')
# NOTE: msys2 on Windows has `unistd.h` and `fcntl.h` but not `sys/mman.h`!
has_unistd_h = cc.has_header('unistd.h')
has_fcntl_h = cc.has_header('fcntl.h')
has_sys_mman_h = cc.has_header('sys/mman.h')
if has_unistd_h
ft2_defines += ['-DHAVE_UNISTD_H=1']
endif
if has_fcntl_h
ft2_defines += ['-DHAVE_FCNTL_H']
endif
mmap_option = get_option('mmap')
if mmap_option.auto()
use_mmap = has_unistd_h and has_fcntl_h and has_sys_mman_h
else
use_mmap = mmap_option.enabled()
endif
if use_mmap
# This version of ftsystem.c uses mmap() to read input font files.
ft2_sources += files(['builds/unix/ftsystem.c',])
else
ft2_sources += files(['src/base/ftsystem.c',])
endif
# Debug support file
#
# NOTE: Some specialized versions exist for other platforms not supported by
# Meson. Most implementation differences are extremely minor, i.e., in the
# implementation of FT_Message() and FT_Panic(), and getting the `FT2_DEBUG`
# value from the environment, when this is supported. A smaller refactor
# might make these platform-specific files much smaller, and could be moved
# into `ftsystem.c` as well.
#
if host_machine.system() == 'windows'
ft2_debug_src = 'builds/windows/ftdebug.c'
else
ft2_debug_src = 'src/base/ftdebug.c'
endif
ft2_sources += files([ft2_debug_src])
ft2_deps = []
# Generate `ftoption.h` based on available dependencies.
ftoption_command = [python_exe,
files('scripts/process_ftoption_h.py'),
'@INPUT@', '--output=@OUTPUT@']
# GZip support
zlib_option = get_option('zlib')
if zlib_option == 'disabled'
ftoption_command += ['--disable=FT_CONFIG_OPTION_USE_ZLIB']
else
ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_ZLIB']
if zlib_option == 'builtin'
ftoption_command += ['--disable=FT_CONFIG_OPTION_SYSTEM_ZLIB']
else
# Probe for the system version.
zlib_system = dependency('zlib', required: zlib_option == 'system')
ft2_deps += [zlib_system]
ftoption_command += ['--enable=FT_CONFIG_OPTION_SYSTEM_ZLIB']
endif
ft2_sources += files(['src/gzip/ftgzip.c',])
endif
# BZip2 support
#
# IMPORTANT NOTE: Without `static: false` here, Meson will find both the
# static library version and the shared library version when they are
# installed on the system, and will try to link them *both* to the final
# library!
bzip2_dep = meson.get_compiler('c').find_library('bz2',
static: false,
required: get_option('bzip2'))
if bzip2_dep.found()
ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_BZIP2']
ft2_sources += files(['src/bzip2/ftbzip2.c',])
ft2_deps += [bzip2_dep]
endif
# PNG support
libpng_dep = dependency('libpng', required: get_option('png'))
ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_PNG']
ft2_deps += [libpng_dep]
# Harfbuzz support
harfbuzz_dep = dependency('harfbuzz',
version: '>= 1.8.0',
required: get_option('harfbuzz'))
ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_HARFBUZZ']
ft2_deps += [harfbuzz_dep]
# Brotli decompression support
brotli_dep = dependency('libbrotlidec', required: get_option('brotli'))
ftoption_command += ['--enable=FT_CONFIG_OPTION_USE_BROTLI']
ft2_deps += [brotli_dep]
# We can now generate `ftoption.h`.
ftoption_h = custom_target('ftoption.h',
input: 'include/freetype/config/ftoption.h',
output: 'ftoption.h',
command: ftoption_command,
install: true,
install_dir: 'include/freetype2/freetype/config',
)
ft2_sources += ftoption_h
# QUESTION: What if the compiler doesn't support `-D` but uses `/D` instead
# as on Windows?
#
# Other build systems have something like c_defines to list defines in a
# more portable way. For now assume the compiler supports `-D` (hint: Visual
# Studio does).
ft2_defines += ['-DFT2_BUILD_LIBRARY=1']
# Ensure that the `ftoption.h` file generated above will be used to build
# FreeType. Unfortunately, and very surprisingly, configure_file() does not
# support putting the output file in a sub-directory, so we have to override
# the default which is `<freetype/config/ftoption.h>`.
#
# It would be cleaner to generate the file directly into
# `${MESON_BUILD_DIR}/freetype/config/ftoption.h`. See
# 'https://github.com/mesonbuild/meson/issues/2320' for details.
ft2_defines += ['-DFT_CONFIG_OPTIONS_H=<ftoption.h>']
ft2_c_args = ft2_defines
if cc.has_function_attribute('visibility:hidden')
ft2_c_args += ['-fvisibility=hidden']
endif
ft2_lib = library('freetype',
sources: ft2_sources + [ftmodule_h],
c_args: ft2_c_args,
include_directories: ft2_includes,
dependencies: ft2_deps,
install: true,
version: ft2_libtool_version,
)
# To be used by other projects including this one through subproject().
freetype2_dep = declare_dependency(
include_directories: ft2_includes,
link_with: ft2_lib,
version: ft2_libtool_version)
# NOTE: Using both `install_dir` and `subdir` doesn't seem to work below,
# i.e., the subdir value seems to be ignored, contrary to examples in the
# Meson documentation.
install_headers('include/ft2build.h',
install_dir: 'include/freetype2')
install_headers(ft2_public_headers,
install_dir: 'include/freetype2/freetype')
install_headers(ft2_config_headers,
install_dir: 'include/freetype2/freetype/config')
# TODO(david): Declare_dependency() for using this in a Meson subproject
#
pkgconfig = import('pkgconfig')
pkgconfig.generate(ft2_lib,
filebase: 'freetype2',
name: 'FreeType 2',
description: 'A free, high-quality, and portable font engine.',
url: 'https://freetype.org',
subdirs: 'freetype2',
version: ft2_libtool_version,
)
# NOTE: Unlike the old `make refdoc` command, this generates the
# documentation under `$BUILD/docs/` since Meson doesn't support modifying
# the source root directory (which is a good thing).
gen_docs = custom_target('freetype2 reference documentation',
output: 'docs',
input: ft2_public_headers + ft2_config_headers,
command: [python_exe,
files('scripts/generate_reference_docs.py'),
'--version=' + ft2_version,
'--input-dir=' + meson.source_root(),
'--output-dir=@OUTPUT@'
],
)
# EOF

47
meson_options.txt

@ -0,0 +1,47 @@
#
# meson_options.txt
#
# Copyright (C) 2020 by
# David Turner, Robert Wilhelm, and Werner Lemberg.
#
# This file is part of the FreeType project, and may only be used, modified,
# and distributed under the terms of the FreeType project license,
# LICENSE.TXT. By continuing to use, modify, or distribute this file you
# indicate that you have read the license and understand and accept it
# fully.
option('zlib',
type: 'combo',
choices: ['disabled', 'auto', 'builtin', 'system'],
value: 'auto',
description: 'Support reading gzip-compressed font files.')
option('bzip2',
type: 'feature',
value: 'auto',
description: 'Support reading bzip2-compressed font files.')
option('png',
type: 'feature',
value: 'auto',
description: 'Support color bitmap glyph formats in the PNG format.'
+ 'Requires libpng.')
option('harfbuzz',
type: 'feature',
value: 'auto',
description: 'Use Harfbuzz library to improve auto-hinting.'
+ ' If available, many glyphs not directly addressable'
+ ' by a font\'s character map will be hinted also.')
option('brotli',
type: 'feature',
value: 'auto',
description: 'Use Brotli library to support decompressing WOFF2 fonts.')
option('mmap',
type: 'feature',
value: 'auto',
description: 'Use mmap() to open font files for faster parsing.')

107
scripts/extract_freetype_version.py

@ -0,0 +1,107 @@
#!/usr/bin/env python
"""Extract the FreeType version numbers from `<freetype/freetype.h>`.
This script parses the header to extract the version number defined there.
By default, the full dotted version number is printed, but `--major`,
`--minor` or `--patch` can be used to only print one of these values
instead.
"""
from __future__ import print_function
import argparse
import os
import re
import sys
# Expected input:
#
# ...
# #define FREETYPE_MAJOR 2
# #define FREETYPE_MINOR 10
# #define FREETYPE_PATCH 2
# ...
RE_MAJOR = re.compile(r"^ #define \s+ FREETYPE_MAJOR \s+ (.*) $", re.X)
RE_MINOR = re.compile(r"^ #define \s+ FREETYPE_MINOR \s+ (.*) $", re.X)
RE_PATCH = re.compile(r"^ #define \s+ FREETYPE_PATCH \s+ (.*) $", re.X)
def parse_freetype_header(header):
major = None
minor = None
patch = None
for line in header.splitlines():
line = line.rstrip()
m = RE_MAJOR.match(line)
if m:
assert major == None, "FREETYPE_MAJOR appears more than once!"
major = m.group(1)
continue
m = RE_MINOR.match(line)
if m:
assert minor == None, "FREETYPE_MINOR appears more than once!"
minor = m.group(1)
continue
m = RE_PATCH.match(line)
if m:
assert patch == None, "FREETYPE_PATCH appears more than once!"
patch = m.group(1)
continue
assert (
major and minor and patch
), "This header is missing one of FREETYPE_MAJOR, FREETYPE_MINOR or FREETYPE_PATCH!"
return (major, minor, patch)
def main():
parser = argparse.ArgumentParser(description=__doc__)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--major",
action="store_true",
help="Only print the major version number.",
)
group.add_argument(
"--minor",
action="store_true",
help="Only print the minor version number.",
)
group.add_argument(
"--patch",
action="store_true",
help="Only print the patch version number.",
)
parser.add_argument(
"input",
metavar="FREETYPE_H",
help="The input freetype.h header to parse.",
)
args = parser.parse_args()
with open(args.input) as f:
header = f.read()
version = parse_freetype_header(header)
if args.major:
print(version[0])
elif args.minor:
print(version[1])
elif args.patch:
print(version[2])
else:
print("%s.%s.%s" % version)
return 0
if __name__ == "__main__":
sys.exit(main())

105
scripts/extract_libtool_version.py

@ -0,0 +1,105 @@
#!/usr/bin/env python
"""Extract the libtool version from `configure.raw`.
This script parses the `configure.raw` file to extract the libtool version
number. By default, the full dotted version number is printed, but
`--major`, `--minor` or `--patch` can be used to only print one of these
values instead.
"""
from __future__ import print_function
import argparse
import os
import re
import sys
# Expected input:
#
# ...
# version_info='23:2:17'
# ...
RE_VERSION_INFO = re.compile(r"^version_info='(\d+):(\d+):(\d+)'")
def parse_configure_raw(header):
major = None
minor = None
patch = None
for line in header.splitlines():
line = line.rstrip()
m = RE_VERSION_INFO.match(line)
if m:
assert major == None, "version_info appears more than once!"
major = m.group(1)
minor = m.group(2)
patch = m.group(3)
continue
assert (
major and minor and patch
), "This input file is missing a version_info definition!"
return (major, minor, patch)
def main():
parser = argparse.ArgumentParser(description=__doc__)
group = parser.add_mutually_exclusive_group()
group.add_argument(
"--major",
action="store_true",
help="Only print the major version number.",
)
group.add_argument(
"--minor",
action="store_true",
help="Only print the minor version number.",
)
group.add_argument(
"--patch",
action="store_true",
help="Only print the patch version number.",
)
group.add_argument(
"--soversion",
action="store_true",
help="Only print the libtool library suffix.",
)
parser.add_argument(
"input",
metavar="CONFIGURE_RAW",
help="The input configure.raw file to parse.",
)
args = parser.parse_args()
with open(args.input) as f:
raw_file = f.read()
version = parse_configure_raw(raw_file)
if args.major:
print(version[0])
elif args.minor:
print(version[1])
elif args.patch:
print(version[2])
elif args.soversion:
# Convert libtool version_info to the library suffix.
# (current,revision, age) -> (current - age, age, revision)
print(
"%d.%s.%s"
% (int(version[0]) - int(version[2]), version[2], version[1])
)
else:
print("%s.%s.%s" % version)
return 0
if __name__ == "__main__":
sys.exit(main())

79
scripts/generate_reference_docs.py

@ -0,0 +1,79 @@
#!/usr/bin/env python
"""Generate FreeType reference documentation."""
from __future__ import print_function
import argparse
import glob
import os
import subprocess
import sys
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--input-dir",
required=True,
help="Top-level FreeType source directory.",
)
parser.add_argument(
"--version", required=True, help='FreeType version (e.g. "2.x.y").'
)
parser.add_argument(
"--output-dir", required=True, help="Output directory."
)
args = parser.parse_args()
# Get the list of input files of interest.
include_dir = os.path.join(args.input_dir, "include")
include_config_dir = os.path.join(include_dir, "config")
include_cache_dir = os.path.join(include_dir, "cache")
all_headers = (
glob.glob(os.path.join(args.input_dir, "include", "freetype", "*.h"))
+ glob.glob(
os.path.join(
args.input_dir, "include", "freetype", "config", "*.h"
)
)
+ glob.glob(
os.path.join(
args.input_dir, "include", "freetype", "cache", "*.h"
)
)
)
if not os.path.exists(args.output_dir):
os.makedirs(args.output_dir)
else:
assert os.path.isdir(args.output_dir), (
"Not a directory: " + args.output_dir
)
cmds = [
sys.executable,
"-m",
"docwriter",
"--prefix=ft2",
"--title=FreeType-" + args.version,
"--site=reference",
"--output=" + args.output_dir,
] + all_headers
print("Running docwriter...")
subprocess.check_call(cmds)
print("Building static site...")
subprocess.check_call(
[sys.executable, "-m", "mkdocs", "build"], cwd=args.output_dir
)
return 0
if __name__ == "__main__":
sys.exit(main())

160
scripts/parse_modules_cfg.py

@ -0,0 +1,160 @@
#!/usr/bin/env python
"""Parse modules.cfg and dump its output either as ftmodule.h or a list of
base extensions.
"""
from __future__ import print_function
import argparse
import os
import re
import sys
# Expected input:
#
# ...
# FONT_MODULES += <name>
# HINTING_MODULES += <name>
# RASTER_MODULES += <name>
# AUX_MODULES += <name>
# BASE_EXTENSIONS += <name>
# ...
def parse_modules_cfg(input_file):
lists = {
"FONT_MODULES": [],
"HINTING_MODULES": [],
"RASTER_MODULES": [],
"AUX_MODULES": [],
"BASE_EXTENSIONS": [],
}
for line in input_file.splitlines():
line = line.rstrip()
# Ignore empty lines and those that start with a comment.
if not line or line[0] == "#":
continue
items = line.split()
assert len(items) == 3 and items[1] == "+=", (
"Unexpected input line [%s]" % line
)
assert items[0] in lists, (
"Unexpected configuration variable name " + items[0]
)
lists[items[0]].append(items[2])
return lists
def generate_ftmodule(lists):
result = "/* This is a generated file. */\n"
for driver in lists["FONT_MODULES"]:
if driver == "sfnt": # Special case for the sfnt 'driver'.
result += "FT_USE_MODULE( FT_Module_Class, sfnt_module_class )\n"
continue
name = {
"truetype": "tt",
"type1": "t1",
"cid": "t1cid",
"type42": "t42",
"winfonts": "winfnt",
}.get(driver, driver)
result += (
"FT_USE_MODULE( FT_Driver_ClassRec, %s_driver_class )\n" % name
)
for module in lists["HINTING_MODULES"]:
result += (
"FT_USE_MODULE( FT_Module_Class, %s_module_class )\n" % module
)
for module in lists["RASTER_MODULES"]:
name = {
"raster": "ft_raster1",
"smooth": "ft_smooth",
}.get(module)
result += (
"FT_USE_MODULE( FT_Renderer_Class, %s_renderer_class )\n" % name
)
for module in lists["AUX_MODULES"]:
if module in ("psaux", "psnames", "otvalid", "gxvalid"):
result += (
"FT_USE_MODULE( FT_Module_Class, %s_module_class )\n" % module
)
result += "/* EOF */\n"
return result
def generate_main_modules(lists):
return "\n".join(
lists["FONT_MODULES"]
+ lists["HINTING_MODULES"]
+ lists["RASTER_MODULES"]
)
def generate_aux_modules(lists):
return "\n".join(lists["AUX_MODULES"])
def generate_base_extensions(lists):
return "\n".join(lists["BASE_EXTENSIONS"])
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--format",
required=True,
choices=(
"ftmodule.h",
"main-modules",
"aux-modules",
"base-extensions-list",
),
help="Select output format.",
)
parser.add_argument(
"input",
metavar="CONFIGURE_RAW",
help="The input configure.raw file to parse.",
)
parser.add_argument("--output", help="Output file (default is stdout).")
args = parser.parse_args()
with open(args.input) as f:
input_data = f.read()
lists = parse_modules_cfg(input_data)
if args.format == "ftmodule.h":
result = generate_ftmodule(lists)
elif args.format == "main-modules":
result = generate_main_modules(lists)
elif args.format == "aux-modules":
result = generate_aux_modules(lists)
elif args.format == "base-extensions-list":
result = generate_base_extensions(lists)
else:
assert False, "Invalid output format!"
if args.output:
with open(args.output, "w") as f:
f.write(result)
else:
print(result)
return 0
if __name__ == "__main__":
sys.exit(main())

105
scripts/process_ftoption_h.py

@ -0,0 +1,105 @@
#!/usr/bin/python
"""Toggle settings in `ftoption.h` file based on command-line arguments.
This script takes an `ftoption.h` file as input and rewrites
`#define`/`#undef` lines in it based on `--enable=CONFIG_VARNAME` or
`--disable=CONFIG_VARNAME` arguments passed to it, where `CONFIG_VARNAME` is
configuration variable name, such as `FT_CONFIG_OPTION_USE_LZW`, that may
appear in the file.
Note that if one of `CONFIG_VARNAME` is not found in the input file, this
script exits with an error message listing the missing variable names.
"""
import argparse
import os
import re
import sys
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"input", metavar="FTOPTION_H", help="Path to input ftoption.h file."
)
parser.add_argument("--output", help="Output to file instead of stdout.")
parser.add_argument(
"--enable",
action="append",
default=[],
help="Enable a given build option (e.g. FT_CONFIG_OPTION_USE_LZW).",
)
parser.add_argument(
"--disable",
action="append",
default=[],
help="Disable a given build option.",
)
args = parser.parse_args()
common_options = set(args.enable) & set(args.disable)
if common_options:
parser.error(
"Options cannot be both enabled and disabled: %s"
% sorted(common_options)
)
return 1
with open(args.input) as f:
input_file = f.read()
options_seen = set()
new_lines = []
for line in input_file.splitlines():
# Expected formats:
# #define <CONFIG_VAR>
# /* #define <CONFIG_VAR> */
# #undef <CONFIG_VAR>
line = line.rstrip()
if line.startswith("/* #define ") and line.endswith(" */"):
option_name = line[11:-3].strip()
option_enabled = False
elif line.startswith("#define "):
option_name = line[8:].strip()
option_enabled = True
elif line.startswith("#undef "):
option_name = line[7:].strip()
option_enabled = False
else:
new_lines.append(line)
continue
options_seen.add(option_name)
if option_enabled and option_name in args.disable:
line = "#undef " + option_name
elif not option_enabled and option_name in args.enable:
line = "#define " + option_name
new_lines.append(line)
result = "\n".join(new_lines)
# Sanity check that all command-line options were actually processed.
cmdline_options = set(args.enable) | set(args.disable)
assert cmdline_options.issubset(
options_seen
), "Could not find options in input file: " + ", ".join(
sorted(cmdline_options - options_seen)
)
if args.output:
with open(args.output, "w") as f:
f.write(result)
else:
print(result)
return 0
if __name__ == "__main__":
sys.exit(main())
Loading…
Cancel
Save