mirror of
https://github.com/Tencent/flare.git
synced 2026-10-17 04:41:22 +08:00
6320851cd6
blade-go's front-end is Starlark, which (unlike Python) has no generator expressions or implicit string concatenation. Convert foreign_build.bld to the equivalent Starlark forms so it can be include()'d there, with no behavior change under Python Blade: - generator expr -> list comprehension in the rpath ' '.join(...). - adjacent-string-literal concatenation -> explicit '+' in dyld_path, the autotools configure command, and the cmake generate command (parenthesized so the trailing `% (...)` still applies to the whole concatenation). - fix cmake_cc_library, which was dead-but-unparseable: it referenced undefined package_name/deps/with_packages. Give it deps/with_packages params, pass package_name=name and source_package through (mirrors autotools_cc_library). Same Starlark-compat class as the cc_flare_library change in the build_rules.bld. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
430 lines
19 KiB
Python
430 lines
19 KiB
Python
# vim: filetype=python
|
|
"""
|
|
This file defines rules to build from source package with foreign build tools,
|
|
such as `GNU Autotools(autoconf, automake)`, CMake.
|
|
|
|
`autotools_build` is used to build autotools-like packages.
|
|
`cmake_build` is used to build CMake packages.
|
|
|
|
`autotools_cc_library` and `cmake_cc_library` are used to build source packages
|
|
which contains only one library, if there are more than one libraries in one
|
|
source package, you should use `autotools_build` or `cmake_build` to build the
|
|
package, and then use `foreign_cc_library` to describe each libraries.
|
|
|
|
Normally, we suggest put each foreign package into one subdirectory, such as put
|
|
`openssl` into `//thirdparty/openssl` directory.
|
|
"""
|
|
|
|
# TODO:
|
|
# cmake ninja
|
|
# ccache
|
|
# optimize with_packages
|
|
# static/dynamic library control
|
|
# automatic jobs
|
|
|
|
_BLADE_UNPACK_STAMP = 'BLADE_UNPACK_STAMP'
|
|
|
|
# Provide a pathed include path for each header file in the installed include dir.
|
|
# If you want to debug this code snippet, add '; false' before the last ')'
|
|
_EXPORT_HEADERS = """\
|
|
(cd {install_dir} && \
|
|
for f in $$(find {include_dir} -name '*.h' -or -name '*.hpp'); do \
|
|
nf=$${{f#{include_dir}/}}; \
|
|
d=$$(dirname $$nf); \
|
|
if [ "$$d" != "." ]; then \
|
|
mkdir -p "$$d"; \
|
|
fi; \
|
|
echo "#include \\\"{install_dir}/$$f\\\"" > $$nf; \
|
|
done)"""
|
|
|
|
|
|
def _get_package_name(pkg):
|
|
"""Get software package name from archive file name"""
|
|
for ext in ('tar.gz', 'tar.bz2', 'tar.xz', 'tar.lz', 'tgz', 'tbz', 'zip'):
|
|
if pkg.endswith(ext):
|
|
return pkg[:-(len(ext) + 1)]
|
|
return ''
|
|
|
|
|
|
def _cmd_with_log(cmd, log_file):
|
|
"""Make cmd run silently if success else show log"""
|
|
# NOTE: the inner parenthesis is necessary because bash is left associative
|
|
return '({cmd} > {log_file} 2>&1 || (cat {log_file} && false))'.format(
|
|
cmd=cmd, log_file=log_file)
|
|
|
|
|
|
def _expand_libnames(install_dir, lib_dir, lib_names, generate_dynamic):
|
|
"""Expand library names to library filenames"""
|
|
result = []
|
|
dyn_suffix = blade.cc_toolchain.dynamic_lib_suffix
|
|
for lib_name in lib_names:
|
|
if isinstance(lib_name, tuple):
|
|
libprefix = blade.path.join(lib_name[0], 'lib' + lib_name[1])
|
|
else:
|
|
libprefix = 'lib' + lib_name
|
|
libprefix = blade.path.join(install_dir, lib_dir, libprefix)
|
|
result.append(libprefix + '.a')
|
|
if generate_dynamic:
|
|
result.append(libprefix + dyn_suffix)
|
|
return result
|
|
|
|
|
|
def _unpack_source_package(name, source_package, source_dir, patches):
|
|
"""Unpack a source_package to the target build directory"""
|
|
full_source_dir = blade.path.join(blade.current_target_dir(), source_dir)
|
|
stamp_file = blade.path.join(source_dir, _BLADE_UNPACK_STAMP)
|
|
log_file = blade.path.join(blade.current_target_dir(), name + '.log')
|
|
if source_package.endswith('.zip'):
|
|
unpack_cmd = 'unzip -o -d $OUT_DIR $FIRST_SRC'
|
|
else:
|
|
unpack_cmd = 'tar xf $FIRST_SRC -C $OUT_DIR'
|
|
patch_files = [
|
|
blade.path.join(blade.current_source_dir(), x) for x in patches
|
|
]
|
|
cmds = [
|
|
_cmd_with_log(unpack_cmd, log_file),
|
|
] + [
|
|
_cmd_with_log('patch -p1 -d ' + full_source_dir + ' < ' + x, log_file)
|
|
for x in patch_files
|
|
] + [
|
|
'touch $OUTS',
|
|
]
|
|
gen_rule(name=name,
|
|
srcs=[source_package] + patches,
|
|
outs=stamp_file,
|
|
cmd=' && '.join(cmds),
|
|
cmd_name='UNPACK')
|
|
|
|
|
|
def _configure(name, package_name, source_dir, install_dir, with_packages, deps,
|
|
configure_options, configure_file_name, ld_library_path):
|
|
# Some packages use their homemake configure which doesn't generate Makefile.
|
|
# Force touch it to avoid always rebuilding.
|
|
# The doubled '$' is required to avoid early expansion
|
|
|
|
full_install_dir = blade.path.abspath(
|
|
blade.path.join(blade.current_target_dir(), install_dir))
|
|
lib_path_env = ':'.join(ld_library_path)
|
|
# Build rpath flags so that runtime library checks (e.g. curl's configure)
|
|
# can find the just-built shared libs without LD_LIBRARY_PATH.
|
|
rpath_flags = ' '.join(['-Wl,-rpath,' + p for p in ld_library_path])
|
|
is_darwin = blade.cc_toolchain.target_os == 'darwin'
|
|
dyld_path = (('DYLD_LIBRARY_PATH=%s:$DYLD_LIBRARY_PATH ' +
|
|
'DYLD_FALLBACK_LIBRARY_PATH=%s:$DYLD_FALLBACK_LIBRARY_PATH ') % (
|
|
lib_path_env, lib_path_env)) if is_darwin else ''
|
|
# Pass CC/CXX explicitly from the resolved toolchain rather than letting
|
|
# autoconf pick from $CC/$CXX or its own detection. Matches the cmake
|
|
# path above and keeps the thirdparty layer in sync with whatever
|
|
# blade.cc_toolchain.tool() decided at config time.
|
|
cc = blade.cc_toolchain.tool('cc') or 'cc'
|
|
cxx = blade.cc_toolchain.tool('cxx') or 'c++'
|
|
# Pin the archiver too. blade.cc_toolchain.tool('ar') is the bare name `ar`,
|
|
# so an autotools package falls back to whatever `ar`/`ranlib` is first in
|
|
# PATH. On macOS a stray GNU/LLVM `ar` (e.g. from Homebrew binutils) there
|
|
# produces a GNU-format archive -- with `/` and `//` index members -- that
|
|
# Apple's ld then rejects at link time ("archive member '/' not a mach-o
|
|
# file"). Force the system BSD ar/ranlib, which emit a `__.SYMDEF` archive
|
|
# ld accepts. Off macOS, GNU archives are fine, so honor the toolchain ar.
|
|
ar = '/usr/bin/ar' if is_darwin else (blade.cc_toolchain.tool('ar') or 'ar')
|
|
ranlib = '/usr/bin/ranlib' if is_darwin else 'ranlib'
|
|
configure = ('cd $OUT_DIR/%s && ' +
|
|
'LD_LIBRARY_PATH=%s:$LD_LIBRARY_PATH ' +
|
|
'%s' +
|
|
'CC=%s CXX=%s AR=%s RANLIB=%s ' +
|
|
'LDFLAGS="$LDFLAGS %s" ' +
|
|
'./%s --prefix=%s %s AR=%s RANLIB=%s') % (
|
|
source_dir,
|
|
lib_path_env,
|
|
dyld_path,
|
|
cc, cxx, ar, ranlib,
|
|
rpath_flags,
|
|
configure_file_name,
|
|
full_install_dir,
|
|
configure_options,
|
|
ar, ranlib)
|
|
if with_packages:
|
|
for pkg in with_packages:
|
|
pkg = pkg[1:] # remove prefix ':'
|
|
configure += ' --with-%s=$$PWD/../%s' % (pkg, pkg)
|
|
log_file = blade.path.join(blade.current_target_dir(), name + '.log')
|
|
makefile = blade.path.join(source_dir, 'Makefile')
|
|
cmds = [
|
|
_cmd_with_log('(%s)' % configure, log_file),
|
|
'touch $OUT_DIR/%s' % makefile
|
|
]
|
|
gen_rule(name=name,
|
|
srcs=[blade.path.join(source_dir, _BLADE_UNPACK_STAMP)],
|
|
outs=[makefile],
|
|
cmd=' && '.join(cmds),
|
|
cmd_name='CONFIGURE',
|
|
deps=deps + with_packages)
|
|
|
|
|
|
def autotools_build(name,
|
|
source_package,
|
|
package_name,
|
|
lib_names,
|
|
install_dir='',
|
|
source_dir=None,
|
|
with_packages=[],
|
|
include_dir='include',
|
|
strip_include_prefix='',
|
|
deps=[],
|
|
configure_options="",
|
|
configure_file_name="configure",
|
|
install_target="install",
|
|
generate_dynamic=False,
|
|
patches=[],
|
|
ld_library_path=[]):
|
|
"""Build a autotools(also known as the GNU build system) source package.
|
|
Args:
|
|
name: str, the name of the target, suggest based on the package name with a '_build' suffix
|
|
source_package: str, filename of the source_package, support zip and 'tar.*z'
|
|
package_name: str, name of the package, without version number.
|
|
lib_names: List[str], if the package generates multiple libraries, list them here, without the `lib` prefix
|
|
source_package: Optional[str], dir name of the extracted source package, usually same as
|
|
the package_name without the compression suffix.
|
|
with_packages: List[str], names to be passed to configure --with-xxx
|
|
"""
|
|
source_dir = source_dir or _get_package_name(source_package)
|
|
target_unpack = package_name + '_unpack'
|
|
_unpack_source_package(name=target_unpack,
|
|
source_package=source_package,
|
|
source_dir=source_dir,
|
|
patches=patches)
|
|
|
|
target_configure = package_name + '_configure'
|
|
_configure(name=target_configure,
|
|
package_name=package_name,
|
|
install_dir=install_dir,
|
|
source_dir=source_dir,
|
|
with_packages=with_packages,
|
|
deps=[':' + target_unpack] + deps,
|
|
configure_options=configure_options,
|
|
configure_file_name=configure_file_name,
|
|
ld_library_path=ld_library_path)
|
|
full_install_dir = blade.path.join(blade.current_target_dir(), install_dir)
|
|
build_outs = _expand_libnames(install_dir, 'lib', lib_names,
|
|
generate_dynamic)
|
|
log_file = blade.path.join(blade.current_target_dir(),
|
|
package_name + '_make.log')
|
|
build_cmd = '(make -C {dir} -j8 && make -C {dir} {install_target})'.format(
|
|
dir=blade.path.join('$OUT_DIR', source_dir),
|
|
install_target=install_target)
|
|
strip_include_dir = blade.path.normpath(
|
|
blade.path.join(include_dir, strip_include_prefix))
|
|
cmds = [
|
|
_cmd_with_log(build_cmd, log_file),
|
|
_EXPORT_HEADERS.format(install_dir=full_install_dir,
|
|
include_dir=strip_include_dir),
|
|
]
|
|
gen_rule(name=name,
|
|
srcs=source_package,
|
|
outs=build_outs,
|
|
cmd=' && '.join(cmds),
|
|
cmd_name='MAKE',
|
|
deps=deps + with_packages + [':' + target_configure],
|
|
generated_incs='',
|
|
# Third-party install include dirs go via `-isystem` so that
|
|
# warnings raised inside vendored headers (glog self-deprecation,
|
|
# openssl pedantic notes, etc.) don't fail our `-Werror` build.
|
|
# Requires blade 5b305b0+ (system_export_incs gen_rule param).
|
|
system_export_incs=blade.path.join(install_dir, include_dir),
|
|
heavy=True)
|
|
|
|
|
|
def autotools_cc_library(name,
|
|
source_package,
|
|
source_dir=None,
|
|
lib_name=None,
|
|
with_packages=[],
|
|
include_dir='include',
|
|
deps=[],
|
|
autogen=False,
|
|
enables=[],
|
|
verbose=False):
|
|
target_build = name + '_build'
|
|
autotools_build(name=target_build,
|
|
source_package=source_package,
|
|
package_name=name,
|
|
lib_names=[name],
|
|
source_dir=source_dir,
|
|
with_packages=with_packages,
|
|
include_dir=include_dir)
|
|
install_dir = blade.current_target_dir()
|
|
foreign_cc_library(name=name,
|
|
package_name='',
|
|
deps=[':' + target_build] + with_packages + deps,
|
|
export_incs='//' +
|
|
blade.path.join(install_dir, 'include'))
|
|
|
|
|
|
def _cmake_generate(name, package_name, source_dir, install_dir, deps, options):
|
|
"""Call cmake to generate build scripts"""
|
|
# Many cmake package forbid in source build, so we build all cmake package out of source.
|
|
build_dir_name = package_name + '_build'
|
|
build_dir = blade.path.join(blade.current_target_dir(), build_dir_name)
|
|
# install_dir relative to the cmake build dir (= $OUT_DIR/<build_dir_name>)
|
|
install_dir = blade.path.join('..', install_dir)
|
|
# CMake needs an absolute prefix to generate an absolute install_name on
|
|
# macOS; a relative path produces just the filename. install_dir is always
|
|
# ".." or "../foo": strip the leading "../" and join with the target dir.
|
|
full_install_dir = blade.path.abspath(
|
|
blade.path.join(blade.current_target_dir(), install_dir[3:]))
|
|
log_file = '$OUT_DIR/%s.log' % name
|
|
cmake_options = ' '.join([
|
|
'-DCMAKE_INSTALL_PREFIX=%s' % full_install_dir,
|
|
'-DCMAKE_INSTALL_LIBDIR=lib',
|
|
# Use absolute install names on macOS (matching autotools behavior)
|
|
# instead of @rpath, so consumers don't need per-target rpath setup.
|
|
'-DCMAKE_MACOSX_RPATH=OFF',
|
|
'-DCMAKE_INSTALL_NAME_DIR=%s' % blade.path.join(full_install_dir, 'lib'),
|
|
# Do NOT use user's cmake package registry.
|
|
'-DCMAKE_EXPORT_NO_PACKAGE_REGISTRY=ON',
|
|
'-DCMAKE_FIND_PACKAGE_NO_PACKAGE_REGISTRY=ON',
|
|
'-DCMAKE_FIND_PACKAGE_NO_SYSTEM_PACKAGE_REGISTRY=ON',
|
|
'-DCMAKE_EXPORT_PACKAGE_REGISTRY=OFF',
|
|
'-DCMAKE_FIND_USE_PACKAGE_REGISTRY=OFF',
|
|
'-DCMAKE_FIND_USE_SYSTEM_PACKAGE_REGISTRY=OFF',
|
|
# CMake 4.x dropped compatibility with `cmake_minimum_required(<3.5)`,
|
|
# which several old vendored packages still declare. Allow them to
|
|
# configure (a no-op on the 3.x toolchains used elsewhere).
|
|
'-DCMAKE_POLICY_VERSION_MINIMUM=3.5',
|
|
])
|
|
if options:
|
|
cmake_options += ' '
|
|
cmake_options += ' '.join(options)
|
|
# Read the compiler from the resolved toolchain rather than hard-coding
|
|
# `CC=gcc CXX=g++`. blade has already picked the toolchain at config time
|
|
# (BLADE_ROOT cc_toolchain_config or its fallback to shutil.which); using
|
|
# toolchain.tool() keeps the cmake subbuild in sync with the rest of the
|
|
# build instead of always invoking the distro-default gcc/g++. The
|
|
# fallback strings are only used if the toolchain proxy returns None
|
|
# (which shouldn't happen for cc/cxx, but is the documented contract).
|
|
cc = blade.cc_toolchain.tool('cc') or 'cc'
|
|
cxx = blade.cc_toolchain.tool('cxx') or 'c++'
|
|
# On macOS, the host compiler defaults to the SDK minimum deployment target
|
|
# (e.g. 15.0), while cmake auto-detects the full OS version (e.g. 15.7).
|
|
# Align cmake to the compiler's default to avoid linker warnings.
|
|
cmd = ' && '.join([
|
|
'rm -fr {build_dir}', 'mkdir -p {build_dir}', 'cd {build_dir}',
|
|
'CXX={cxx} CC={cc} cmake {options}' +
|
|
' $$(if [ "$$(uname)" = "Darwin" ]; then' +
|
|
' echo "-DCMAKE_OSX_DEPLOYMENT_TARGET=$$(sw_vers -productVersion | cut -d. -f1).0";' +
|
|
' fi) ../{source_dir}'
|
|
]).format(build_dir=build_dir, options=cmake_options,
|
|
source_dir=source_dir, cc=cc, cxx=cxx)
|
|
build_file = blade.path.join(build_dir_name, 'Makefile')
|
|
gen_rule(name=name,
|
|
srcs=[blade.path.join(source_dir, _BLADE_UNPACK_STAMP)],
|
|
outs=[build_file],
|
|
cmd=_cmd_with_log('(%s)' % cmd, log_file),
|
|
cmd_name='CMAKE GENERATE',
|
|
deps=deps)
|
|
return build_dir, build_file
|
|
|
|
|
|
def cmake_build(name,
|
|
package_name,
|
|
source_package,
|
|
source_dir=None,
|
|
deps=[],
|
|
install_dir='',
|
|
lib_dir='lib',
|
|
lib_names=None,
|
|
include_dir='include',
|
|
strip_include_prefix='',
|
|
cmake_options=None,
|
|
generate_dynamic=False,
|
|
shared_cmake_options=None,
|
|
patches=[]):
|
|
source_dir = source_dir or _get_package_name(source_package)
|
|
lib_names = lib_names or [package_name]
|
|
target_unpack = package_name + '_unpack'
|
|
_unpack_source_package(name=target_unpack,
|
|
source_package=source_package,
|
|
source_dir=source_dir,
|
|
patches=patches)
|
|
target_generate = package_name + '_generate'
|
|
cmake_options = cmake_options or []
|
|
build_dir, build_file = _cmake_generate(name=target_generate,
|
|
package_name=package_name,
|
|
source_dir=source_dir,
|
|
install_dir=install_dir,
|
|
deps=[':' + target_unpack] + deps,
|
|
options=cmake_options)
|
|
build_outs = _expand_libnames(install_dir, lib_dir, lib_names,
|
|
generate_dynamic)
|
|
log_file = blade.path.join(blade.current_target_dir(), name + '.log')
|
|
build_cmd = 'make -C {dir} -j16 install'.format(dir=build_dir)
|
|
full_install_dir = blade.path.join(blade.current_target_dir(), install_dir)
|
|
strip_include_dir = blade.path.normpath(
|
|
blade.path.join(include_dir, strip_include_prefix))
|
|
src_files = [build_file]
|
|
generate_deps = [':' + target_generate]
|
|
cmds = [_cmd_with_log(build_cmd, log_file)]
|
|
# Packages whose CMake can only emit static *or* shared in a single
|
|
# configure (e.g. googletest, which keys solely off BUILD_SHARED_LIBS)
|
|
# need a second pass to produce both. `shared_cmake_options` triggers an
|
|
# extra configure+install into the SAME prefix with those extra options
|
|
# appended; the static pass installs the `.a`, the shared pass the
|
|
# dynamic lib, and headers are installed by both (idempotent). Other
|
|
# `generate_dynamic` packages emit both natively and leave this unset.
|
|
if shared_cmake_options:
|
|
shared_build_dir, shared_build_file = _cmake_generate(
|
|
name=package_name + '_generate_shared',
|
|
package_name=package_name + '_shared',
|
|
source_dir=source_dir,
|
|
install_dir=install_dir,
|
|
deps=[':' + target_unpack] + deps,
|
|
options=cmake_options + shared_cmake_options)
|
|
src_files.append(shared_build_file)
|
|
generate_deps.append(':' + package_name + '_generate_shared')
|
|
cmds.append(_cmd_with_log(
|
|
'make -C {dir} -j16 install'.format(dir=shared_build_dir),
|
|
log_file))
|
|
cmds.append(_EXPORT_HEADERS.format(install_dir=full_install_dir,
|
|
include_dir=strip_include_dir))
|
|
gen_rule(name=name,
|
|
srcs=src_files,
|
|
outs=build_outs,
|
|
cmd=' && '.join(cmds),
|
|
cmd_name='CMAKE BUILD',
|
|
deps=deps + generate_deps,
|
|
generated_incs=package_name,
|
|
# Same rationale as autotools_build above: third-party install
|
|
# include dirs go via `-isystem` (blade 5b305b0+).
|
|
system_export_incs=blade.path.join(install_dir, include_dir),
|
|
heavy=True)
|
|
|
|
|
|
def cmake_cc_library(name,
|
|
source_package,
|
|
source_dir=None,
|
|
out_of_source_build=True,
|
|
install_dir='',
|
|
include_dir='include',
|
|
strip_include_prefix='',
|
|
deps=[],
|
|
with_packages=[],
|
|
verbose=False):
|
|
target_build = name + '_build'
|
|
cmake_build(name=target_build,
|
|
package_name=name,
|
|
source_package=source_package,
|
|
source_dir=source_dir,
|
|
deps=deps,
|
|
lib_names=[name],
|
|
include_dir=include_dir,
|
|
strip_include_prefix=strip_include_prefix)
|
|
foreign_cc_library(name=name,
|
|
package_name=name,
|
|
deps=[':' + target_build] + with_packages + deps,
|
|
export_incs=blade.path.join(install_dir, include_dir))
|
|
|
|
|
|
def get_install_dir(name):
|
|
return blade.path.join(
|
|
blade.path.dirname(blade.path.abspath(blade.current_target_dir())),
|
|
name)
|