Win32/NMake builds: Support builds from GIT (#498)

Add Python scripts to generate the full win32/config.h.win32 and
src/hb-version.h which can be used to build directly from a GIT
checkout.  Since the scripts are currently intended for building from a
GIT checkout, these are not distributed in the release tarballs.

Also, support the re-build of Ragel-generated .hh headers using the NMake
build system, and allow one to specify the path of the Ragel executable
if a suitable one cannot be found in the PATH.

Update the Win32/NMake build documentation to let people know about how
these mechanisms can be utilized.
This commit is contained in:
fanc999 2017-06-21 22:19:57 +08:00 committed by Ebrahim Byagowi
parent 3b0e47ca00
commit ad52e044bc
9 changed files with 353 additions and 3 deletions

View File

@ -11,7 +11,22 @@ backends are enabled, and this is the base configuration that is built if no
options (see below) are specified. A 'clean' target is provided-it is recommended
that one cleans the build and redo the build if any configuration option changed.
An 'install' target is also provided to copy the built items in their appropriate
locations under $(PREFIX), which is described below.
locations under $(PREFIX), which is described below. A 'reallyclean' target is
provided that not only does what is done for the 'clean' target, but also removes
the sources/headers that are generated from the Ragel sources. Therefore, if one
is not building from a release tarball, or is rebuilding after using the 'reallyclean'
target or when the Ragel (*.rl) sources are updated, the Ragel state machine
compiler (ragel.exe) is needed, and needs to be passed in via RAGEL=<path_to_ragel_exe>
if ragel.exe is not already in the PATH.
We now support building from a GIT checkout via NMake for convenience. In addition to
the requirements as outlined in the later part of this file, you will need to run the
setup.py (Python 2.7.x or later) script to generate the headers (src\hb-version.h and
win32\config.h.win32) that are normally shipped in a release tarball before running
NMake, and you will need to pass RAGEL=<path_to_ragel_exe> if the Ragel state machine
compiler (ragel.exe) is not in your PATH when invoking NMake. Note that the
'reallyclean' target does not remove these 2 generated headers, so re-run the setup.py
script if necessary.
Invoke the build by issuing the command:
nmake /f Makefile.vc CFG=[release|debug] [PREFIX=...] <option1=1 option2=1 ...>
@ -32,6 +47,7 @@ PREFIX: Optional. Base directory of where the third-party headers, libraries
2012: 11
2013: 12
2015: 14
2017: 15
Explanation of options, set by <option>=1:
------------------------------------------
@ -75,4 +91,6 @@ PYTHON: Full path to the Python interpretor to be used, if it is not in %PATH%.
PERL: Full path to the PERL interpretor to be used, if it is not in %PATH%.
RAGEL: Full path to the Ragel state machine compiler executable, if not in %PATH%
LIBTOOL_DLL_NAME: Enable libtool-style DLL names.

View File

@ -52,7 +52,7 @@ $(CFG)\$(PLAT)\harfbuzz-gobject.lib: $(HARFBUZZ_GOBJECT_DLL_FILENAME).dll
# $(dependent_objects)
# <<
# @-if exist $@.manifest mt /manifest $@.manifest /outputresource:$@;2
$(HARFBUZZ_DLL_FILENAME).dll: config.h $(harfbuzz_dll_OBJS) $(CFG)\$(PLAT)\harfbuzz
$(HARFBUZZ_DLL_FILENAME).dll: config.h $(HB_RAGEL_GENERATED_ACTUAL_SOURCES) $(harfbuzz_dll_OBJS) $(CFG)\$(PLAT)\harfbuzz
link /DLL $(LDFLAGS) $(HB_DEP_LIBS) /implib:$(CFG)\$(PLAT)\harfbuzz.lib -out:$@ @<<
$(harfbuzz_dll_OBJS)
<<
@ -125,3 +125,6 @@ clean:
@-if exist $(CFG)\$(PLAT)\harfbuzz-gobject\hb-gobject-enums.cc del $(CFG)\$(PLAT)\harfbuzz-gobject\hb-gobject-enums.cc
@-del vc$(VSVER)0.pdb
@-del config.h
reallyclean: clean
@-del /f /q $(HB_RAGEL_GENERATED_ACTUAL_SOURCES)

View File

@ -30,6 +30,11 @@ UNISCRIBE_LIB = usp10.lib gdi32.lib rpcrt4.lib user32.lib
# Directwrite is needed for DirectWrite shaping support
DIRECTWRITE_LIB = dwrite.lib
# Full path to Ragel state machine compiler if not already in PATH
!if "$(RAGEL)" == ""
RAGEL = ragel
!endif
# Please do not change anything beneath this line unless maintaining the NMake Makefiles
# Bare minimum features and sources built into HarfBuzz on Windows
HB_DEFINES =
@ -48,6 +53,8 @@ HB_HEADERS = \
$(HB_NODIST_headers) \
$(HB_OT_headers)
RAGEL_RAW_GEN_SRCS = $(HB_OT_RAGEL_GENERATED_sources) $(HB_BASE_RAGEL_GENERATED_sources)
# Minimal set of (system) libraries needed for the HarfBuzz DLL
HB_DEP_LIBS =

View File

@ -132,6 +132,15 @@ NULL=
!if [call create-lists.bat footer hb_srcs.mak]
!endif
!if [call create-lists.bat header hb_srcs.mak HB_RAGEL_GENERATED_ACTUAL_SOURCES]
!endif
!if [for %s in ($(RAGEL_RAW_GEN_SRCS)) do @call create-lists.bat file hb_srcs.mak ..\src\%s]
!endif
!if [call create-lists.bat footer hb_srcs.mak]
!endif
!include hb_srcs.mak
!if [del /f /q hb_srcs.mak]

View File

@ -24,3 +24,9 @@ $(HB_GOBJECT_ENUM_GENERATED_SOURCES): ..\src\hb-gobject-enums.h.tmpl ..\src\hb-g
# Create the build directories
$(CFG)\$(PLAT)\harfbuzz $(CFG)\$(PLAT)\harfbuzz-gobject $(CFG)\$(PLAT)\util:
@-md $@
.SUFFIXES: .c .cc .hh .rl
# Generate headers from Ragel sources
{..\src\}.rl{..\src\}.hh:
$(RAGEL) -e -F1 -o $@ $<

View File

@ -133,10 +133,16 @@ help:
@echo NO_UCDN:
@echo Do not use the bundled Unicode callback, which is the default. GLib or
@echo ICU-based unicode callback is therefore required.
@echo
@echo.
@echo UNISCRIBE:
@echo Enable Uniscribe support.
@echo.
@echo RAGEL:
@echo Set the full path to the Ragel state machine compiler, if not already in
@echo PATH. The Ragel state machine compiler is required if not building from
@echo a release tarball, or a rebuild is to be carried out after using the
@echo 'reallyclean' target.
@echo.
@echo Note that GLib2 support is required for all utility and test programs.
@echo ======
@echo A 'clean' target is supported to remove all generated files, intermediate

124
win32/pc_base.py Normal file
View File

@ -0,0 +1,124 @@
#!/usr/bin/python
#
# Simple utility script to generate the basic info
# needed in a .pc (pkg-config) file, used especially
# for introspection purposes
# This can be used in various projects where
# there is the need to generate .pc files,
# and is copied from GLib's $(srcroot)/win32
# Author: Fan, Chun-wei
# Date: March 10, 2016
import os
import sys
import argparse
class BasePCItems:
def __init__(self):
self.base_replace_items = {}
self.exec_prefix = ''
self.includedir = ''
self.libdir = ''
self.prefix = ''
self.srcdir = os.path.dirname(__file__)
self.top_srcdir = self.srcdir + '\\..'
self.version = ''
def setup(self, argv, parser=None):
if parser is None:
parser = argparse.ArgumentParser(description='Setup basic .pc file info')
parser.add_argument('--prefix', help='prefix of the installed library',
required=True)
parser.add_argument('--exec-prefix',
help='prefix of the installed programs, \
if different from the prefix')
parser.add_argument('--includedir',
help='includedir of the installed library, \
if different from ${prefix}/include')
parser.add_argument('--libdir',
help='libdir of the installed library, \
if different from ${prefix}/lib')
parser.add_argument('--version', help='Version of the package',
required=True)
args = parser.parse_args()
self.version = args.version
# check whether the prefix and exec_prefix are valid
if not os.path.exists(args.prefix):
raise SystemExit('Specified prefix \'%s\' is invalid' % args.prefix)
# use absolute paths for prefix
self.prefix = os.path.abspath(args.prefix).replace('\\','/')
# check and setup the exec_prefix
if getattr(args, 'exec_prefix', None) is None:
exec_prefix_use_shorthand = True
self.exec_prefix = '${prefix}'
else:
if args.exec_prefix.startswith('${prefix}'):
exec_prefix_use_shorthand = True
input_exec_prefix = args.prefix + args.exec_prefix[len('${prefix}'):]
else:
exec_prefix_use_shorthand = False
input_exec_prefix = args.exec_prefix
if not os.path.exists(input_exec_prefix):
raise SystemExit('Specified exec_prefix \'%s\' is invalid' %
args.exec_prefix)
if exec_prefix_use_shorthand is True:
self.exec_prefix = args.exec_prefix.replace('\\','/')
else:
self.exec_prefix = os.path.abspath(input_exec_prefix).replace('\\','/')
# check and setup the includedir
if getattr(args, 'includedir', None) is None:
self.includedir = '${prefix}/include'
else:
if args.includedir.startswith('${prefix}'):
includedir_use_shorthand = True
input_includedir = args.prefix + args.includedir[len('${prefix}'):]
else:
if args.includedir.startswith('${exec_prefix}'):
includedir_use_shorthand = True
input_includedir = input_exec_prefix + args.includedir[len('${exec_prefix}'):]
else:
includedir_use_shorthand = False
input_includedir = args.includedir
if not os.path.exists(input_includedir):
raise SystemExit('Specified includedir \'%s\' is invalid' %
args.includedir)
if includedir_use_shorthand is True:
self.includedir = args.includedir.replace('\\','/')
else:
self.includedir = os.path.abspath(input_includedir).replace('\\','/')
# check and setup the libdir
if getattr(args, 'libdir', None) is None:
self.libdir = '${prefix}/lib'
else:
if args.libdir.startswith('${prefix}'):
libdir_use_shorthand = True
input_libdir = args.prefix + args.libdir[len('${prefix}'):]
else:
if args.libdir.startswith('${exec_prefix}'):
libdir_use_shorthand = True
input_libdir = input_exec_prefix + args.libdir[len('${exec_prefix}'):]
else:
libdir_use_shorthand = False
input_libdir = args.libdir
if not os.path.exists(input_libdir):
raise SystemExit('Specified libdir \'%s\' is invalid' %
args.libdir)
if libdir_use_shorthand is True:
self.libdir = args.libdir.replace('\\','/')
else:
self.libdir = os.path.abspath(input_libdir).replace('\\','/')
# setup dictionary for replacing items in *.pc.in
self.base_replace_items.update({'@VERSION@': self.version})
self.base_replace_items.update({'@prefix@': self.prefix})
self.base_replace_items.update({'@exec_prefix@': self.exec_prefix})
self.base_replace_items.update({'@libdir@': self.libdir})
self.base_replace_items.update({'@includedir@': self.includedir})

115
win32/replace.py Normal file
View File

@ -0,0 +1,115 @@
#!/usr/bin/python
#
# Simple utility script to manipulate
# certain types of strings in a file
# This can be used in various projects where
# there is the need to replace strings in files,
# and is copied from GLib's $(srcroot)/win32
# Author: Fan, Chun-wei
# Date: September 03, 2014
import os
import sys
import re
import string
import argparse
valid_actions = ['remove-prefix',
'replace-var',
'replace-str',
'remove-str']
def open_file(filename, mode):
if sys.version_info[0] < 3:
return open(filename, mode=mode)
else:
return open(filename, mode=mode, encoding='utf-8')
def replace_multi(src, dest, replace_items):
with open_file(src, 'r') as s:
with open_file(dest, 'w') as d:
for line in s:
replace_dict = dict((re.escape(key), value) \
for key, value in replace_items.items())
replace_pattern = re.compile("|".join(replace_dict.keys()))
d.write(replace_pattern.sub(lambda m: \
replace_dict[re.escape(m.group(0))], line))
def replace(src, dest, instring, outstring):
replace_item = {instring: outstring}
replace_multi(src, dest, replace_item)
def check_required_args(args, params):
for param in params:
if getattr(args, param, None) is None:
raise SystemExit('%s: error: --%s argument is required' % (__file__, param))
def warn_ignored_args(args, params):
for param in params:
if getattr(args, param, None) is not None:
print('%s: warning: --%s argument is ignored' % (__file__, param))
def main(argv):
parser = argparse.ArgumentParser(description='Process strings in a file.')
parser.add_argument('-a',
'--action',
help='Action to carry out. Can be one of:\n'
'remove-prefix\n'
'replace-var\n'
'replace-str\n'
'remove-str',
choices=valid_actions)
parser.add_argument('-i', '--input', help='Input file')
parser.add_argument('-o', '--output', help='Output file')
parser.add_argument('--instring', help='String to replace or remove')
parser.add_argument('--var', help='Autotools variable name to replace')
parser.add_argument('--outstring',
help='New String to replace specified string or variable')
parser.add_argument('--removeprefix', help='Prefix of string to remove')
args = parser.parse_args()
input_string = ''
output_string = ''
# We must have action, input, output for all operations
check_required_args(args, ['action','input','output'])
# Build the arguments by the operation that is to be done,
# to be fed into replace()
# Get rid of prefixes from a string
if args.action == 'remove-prefix':
check_required_args(args, ['instring','removeprefix'])
warn_ignored_args(args, ['outstring','var'])
input_string = args.removeprefix + args.instring
output_string = args.instring
# Replace an m4-style variable (those surrounded by @...@)
if args.action == 'replace-var':
check_required_args(args, ['var','outstring'])
warn_ignored_args(args, ['instring','removeprefix'])
input_string = '@' + args.var + '@'
output_string = args.outstring
# Replace a string
if args.action == 'replace-str':
check_required_args(args, ['instring','outstring'])
warn_ignored_args(args, ['var','removeprefix'])
input_string = args.instring
output_string = args.outstring
# Remove a string
if args.action == 'remove-str':
check_required_args(args, ['instring'])
warn_ignored_args(args, ['var','outstring','removeprefix'])
input_string = args.instring
output_string = ''
replace(args.input, args.output, input_string, output_string)
if __name__ == '__main__':
sys.exit(main(sys.argv))

62
win32/setup.py Normal file
View File

@ -0,0 +1,62 @@
#!/usr/bin/python
# vim: encoding=utf-8
#expand *.in files
#this script is only intended for building from git, not for building from the released tarball, which already includes all necessary files
import os
import sys
import re
import string
import subprocess
import optparse
from pc_base import BasePCItems
from replace import replace_multi
def get_version_items(srcroot):
ver = {}
RE_VERSION_LINE_START = re.compile(r'^AC_INIT\(\[(.+)\], *\n')
RE_VERSION_LINE_BODY = re.compile(r'^ \[(.+)\], *\n')
RE_VERSION_LINE_END = re.compile(r'^ \[(.+)\]\) *\n')
# Read from the AC_INIT lines to get the version/name/URLs info
with open(os.path.join(srcroot, 'configure.ac'), 'r') as ac:
for i in ac:
mo_init = RE_VERSION_LINE_START.search(i)
mo_pkg_info = RE_VERSION_LINE_BODY.search(i)
mo_pkg_url = RE_VERSION_LINE_END.search(i)
if mo_init:
ver['@PACKAGE_NAME@'] = mo_init.group(1)
if mo_pkg_info:
if mo_pkg_info.group(1).startswith('http'):
ver['@PACKAGE_BUGREPORT@'] = mo_pkg_info.group(1)
elif mo_pkg_info.group(1)[0].isdigit():
ver['@PACKAGE_VERSION@'] = mo_pkg_info.group(1)
else:
ver['@PACKAGE_TARNAME@'] = mo_pkg_info.group(1)
if mo_pkg_url:
ver['@PACKAGE_URL@'] = mo_pkg_url.group(1)
ver['@HB_VERSION@'] = ver['@PACKAGE_VERSION@']
pkg_ver_parts = ver['@PACKAGE_VERSION@'].split('.')
ver['@HB_VERSION_MAJOR@'] = pkg_ver_parts[0]
ver['@HB_VERSION_MINOR@'] = pkg_ver_parts[1]
ver['@HB_VERSION_MICRO@'] = pkg_ver_parts[2]
return ver
def main(argv):
pc = BasePCItems()
srcroot = pc.top_srcdir
srcdir = pc.srcdir
ver = get_version_items(srcroot)
replace_multi(os.path.join(srcdir, 'config.h.win32.in'),
os.path.join(srcdir, 'config.h.win32'),
ver)
replace_multi(os.path.join(srcroot, 'src', 'hb-version.h.in'),
os.path.join(srcroot, 'src', 'hb-version.h'),
ver)
return 0
if __name__ == '__main__':
sys.exit(main(sys.argv))