-
Notifications
You must be signed in to change notification settings - Fork 208
Expand file tree
/
Copy pathforeign_build.bld
More file actions
429 lines (399 loc) · 19.2 KB
/
Copy pathforeign_build.bld
File metadata and controls
429 lines (399 loc) · 19.2 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
# 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)