mirror of
https://github.com/signalapp/libsignal.git
synced 2026-05-09 08:33:13 +02:00
This is a speculative log meant to see why the blocking thread pool grows as large as it does for some people.
273 lines
11 KiB
Python
Executable File
273 lines
11 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
#
|
|
# Copyright (C) 2021 Signal Messenger, LLC.
|
|
# SPDX-License-Identifier: AGPL-3.0-only
|
|
#
|
|
|
|
import hashlib
|
|
import mmap
|
|
import optparse
|
|
import os
|
|
import shlex
|
|
import shutil
|
|
import subprocess
|
|
import sys
|
|
|
|
from typing import List, Optional
|
|
|
|
sys.path.append(os.path.join(
|
|
os.path.dirname(os.path.abspath(os.path.dirname(__file__))),
|
|
'bin'))
|
|
import build_helpers # noqa: E402 (we need to modify sys.path before this import)
|
|
|
|
|
|
def check_for_debug_level_logs(src_path: str) -> None:
|
|
with open(src_path, 'rb') as f:
|
|
with mmap.mmap(f.fileno(), length=0, access=mmap.ACCESS_READ) as m:
|
|
# See libsignal-node's logging.rs.
|
|
if m.find(b'-LEVEL LOGS ENABLED') != -1:
|
|
raise AssertionError('debug-level logs found in build that should not have them!')
|
|
|
|
|
|
def maybe_dump_debug_symbols(*, src_path: str, src_checksum_path: str, dst_path: str, dst_checksum_path: str) -> None:
|
|
dump_syms = shutil.which('dump_syms')
|
|
if not dump_syms:
|
|
print("note: dump_syms not installed; skipping debug info processing")
|
|
return
|
|
|
|
with open(src_checksum_path, 'rb') as f:
|
|
digest = hashlib.sha256()
|
|
# Use read1 to use the file object's buffering.
|
|
# We don't want to load the entire input in memory if we can help it.
|
|
while next := f.read1():
|
|
digest.update(next)
|
|
checksum = digest.hexdigest()
|
|
|
|
if os.path.exists(dst_checksum_path):
|
|
with open(dst_checksum_path, 'r') as f:
|
|
if f.read() == checksum:
|
|
print("Debug info did not change")
|
|
return
|
|
|
|
with open(dst_checksum_path, 'w') as f:
|
|
f.write(checksum)
|
|
|
|
print("Dumping debug symbols to %s" % dst_path)
|
|
subprocess.check_call([dump_syms, src_path, '-o', dst_path])
|
|
|
|
|
|
def main(args: Optional[List[str]] = None) -> int:
|
|
if args is None:
|
|
args = sys.argv
|
|
|
|
if sys.platform == 'win32':
|
|
args = shlex.split(' '.join(args), posix=0)
|
|
|
|
print("Invoked with '%s'" % (' '.join(args)))
|
|
|
|
parser = optparse.OptionParser()
|
|
parser.add_option('--out-dir', '-o', default=None, metavar='DIR',
|
|
help='specify destination dir (default build/$CONFIGURATION_NAME)')
|
|
parser.add_option('--configuration', default='Release', metavar='C',
|
|
help='specify build configuration (Release or Debug)')
|
|
# Luckily, Python's sys.platform matches Node's OS names for our supported targets.
|
|
parser.add_option('--os-name', default=sys.platform, metavar='OS',
|
|
help='specify Node OS name')
|
|
parser.add_option('--cargo-build-dir', default=os.path.join('..', 'target'), metavar='PATH',
|
|
help='specify cargo build dir (default %default)')
|
|
parser.add_option('--cargo-target', default=None,
|
|
help='specify cargo target')
|
|
parser.add_option('--node-arch', '--arch', default=None,
|
|
help='specify node arch (x64, ia32, arm64)')
|
|
parser.add_option('--copy-to-prebuilds', action='store_true',
|
|
help='copy library to prebuilds/$OS-$ARCH when finished')
|
|
parser.add_option('--debug-level-logs', action='store_true',
|
|
help='include log levels below INFO (default for Debug builds)')
|
|
|
|
(options, args) = parser.parse_args(args)
|
|
|
|
configuration_name = options.configuration.strip('"')
|
|
if configuration_name is None:
|
|
print('ERROR: --configuration is required')
|
|
return 1
|
|
elif configuration_name not in ['Release', 'Debug']:
|
|
print("ERROR: Unknown value for --configuration '%s'" % (configuration_name))
|
|
return 1
|
|
|
|
node_os_name = options.os_name
|
|
|
|
node_arch = options.node_arch
|
|
if node_arch is None:
|
|
# Python doesn't provide many guarantees about the format of platform.machine(),
|
|
# so we turn to Node instead.
|
|
node_arch = subprocess.check_output(
|
|
['node', '-e', "console.log(require('node:process').arch)"],
|
|
encoding="utf8").strip()
|
|
|
|
cargo_target = options.cargo_target
|
|
if cargo_target is None:
|
|
cargo_arch = {
|
|
'ia32': 'i686',
|
|
'x64': 'x86_64',
|
|
'arm64': 'aarch64',
|
|
}.get(node_arch, node_arch)
|
|
cargo_target_suffix = {
|
|
'darwin': 'apple-darwin',
|
|
'win32': 'pc-windows-msvc',
|
|
'linux': 'unknown-linux-gnu',
|
|
}.get(node_os_name, node_os_name)
|
|
cargo_target = f"{cargo_arch}-{cargo_target_suffix}"
|
|
|
|
out_dir = (options.out_dir or os.path.join('build', configuration_name)).strip('"')
|
|
|
|
# Fetch all dependencies first, so we can check information about them in constructing our
|
|
# command lines.
|
|
subprocess.check_call(['cargo', 'fetch'])
|
|
|
|
features = []
|
|
allow_debug_level_logs = False
|
|
if options.debug_level_logs:
|
|
allow_debug_level_logs = True
|
|
else:
|
|
features.append('log/release_max_level_info')
|
|
|
|
cmdline = ['cargo', 'build', '--target', cargo_target, '-p', 'libsignal-node', '--features', ','.join(features)]
|
|
if configuration_name == 'Release':
|
|
cmdline.append('--release')
|
|
else:
|
|
allow_debug_level_logs = True
|
|
print("Running '%s'" % (' '.join(cmdline)))
|
|
|
|
cargo_env = os.environ.copy()
|
|
cargo_env['RUSTFLAGS'] = cargo_env.get('RUSTFLAGS') or ''
|
|
cargo_env['CARGO_BUILD_TARGET_DIR'] = options.cargo_build_dir
|
|
cargo_env['MACOSX_DEPLOYMENT_TARGET'] = '10.13'
|
|
# Build with debug line tables, but not full debug info.
|
|
cargo_env['CARGO_PROFILE_RELEASE_DEBUG'] = '1'
|
|
# On Linux, cdylibs don't include public symbols from their dependencies,
|
|
# even if those symbols have been re-exported in the Rust source.
|
|
# Using LTO works around this at the cost of a slightly slower build.
|
|
# https://github.com/rust-lang/rfcs/issues/2771
|
|
cargo_env['CARGO_PROFILE_RELEASE_LTO'] = 'thin'
|
|
# Enable ARMv8 cryptography acceleration when available
|
|
cargo_env['RUSTFLAGS'] += ' --cfg aes_armv8'
|
|
# Access tokio's unstable metrics
|
|
cargo_env['RUSTFLAGS'] += ' --cfg tokio_unstable'
|
|
# Strip absolute paths
|
|
for path in build_helpers.rust_paths_to_remap():
|
|
cargo_env['RUSTFLAGS'] += f' --remap-path-prefix {path}='
|
|
|
|
# If set (below), will post-process the build library using this instead of just `cp`-ing it.
|
|
objcopy = None
|
|
|
|
if node_os_name == 'win32':
|
|
# By default, Rust on Windows depends on an MSVC component for the C runtime.
|
|
# Link it statically to avoid propagating that dependency.
|
|
cargo_env['RUSTFLAGS'] += ' -C target-feature=+crt-static'
|
|
|
|
# Hint to the Rust compiler that we're cross-compiling. This shouldn't be necessary
|
|
# since the invoking build script (if any) should be doing that but it's needed
|
|
# since Rust nightly-2024-10-03.
|
|
cargo_env['VSCMD_ARG_TGT_ARCH'] = node_arch
|
|
|
|
# Save the debug info in PDB format...
|
|
cargo_env['CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO'] = 'packed'
|
|
# ...and DLLs don't have anything to strip.
|
|
# (If you turn on stripping the PDB doesn't get generated at all.)
|
|
lib_format = '{}.dll'
|
|
debug_format = '{}.pdb'
|
|
debug_format_for_checksum = debug_format
|
|
|
|
abs_build_dir = os.path.abspath(options.cargo_build_dir)
|
|
if 'GITHUB_WORKSPACE' in cargo_env:
|
|
# Avoid long build directory paths on GitHub Actions,
|
|
# breaking the old Win32 limit of 260 characters.
|
|
# (https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation)
|
|
# We don't do this everywhere because it breaks cleaning.
|
|
#
|
|
# In the long run, Visual Studio's CLI tools will become long-path aware and this should
|
|
# become unnecessary.
|
|
# It would be nice if using extended-length path syntax `\\?\` was sufficient,
|
|
# but that also isn't accepted by all of Visual Studio's CLI tools.
|
|
tmpdir = cargo_env['RUNNER_TEMP']
|
|
if len(tmpdir) < len(abs_build_dir):
|
|
cargo_env['CARGO_BUILD_TARGET_DIR'] = os.path.join(tmpdir, "libsignal")
|
|
|
|
elif node_os_name == 'darwin':
|
|
# Save the debug info in dSYM format...
|
|
cargo_env['CARGO_PROFILE_RELEASE_SPLIT_DEBUGINFO'] = 'packed'
|
|
# ...then have Rust strip the library.
|
|
cargo_env['CARGO_PROFILE_RELEASE_STRIP'] = 'symbols'
|
|
lib_format = 'lib{0}.dylib'
|
|
debug_format = 'lib{0}.dylib.dSYM'
|
|
# The dSYM format is a directory, not a single file.
|
|
# We use the single file that contains the DWARF information for our checksum,
|
|
# since our primary purpose for debug info is symbolicating crashdumps,
|
|
# which uses the line tables stored as DWARF.
|
|
debug_format_for_checksum = os.path.join(debug_format, 'Contents', 'Resources', 'DWARF', lib_format)
|
|
|
|
# macOS has a nice place for us to stash our version number.
|
|
if 'npm_package_version' in cargo_env:
|
|
cargo_env['RUSTFLAGS'] += ' -Clink-arg=-Wl,-current_version,%s' % cargo_env['npm_package_version']
|
|
|
|
else:
|
|
# Assume Linux-like.
|
|
# DWP files don't seem ready for everyday use.
|
|
# We'll just save the whole unstripped binary.
|
|
lib_format = 'lib{}.so'
|
|
debug_format = 'lib{}.so'
|
|
debug_format_for_checksum = debug_format
|
|
|
|
objcopy = shutil.which('%s-linux-gnu-objcopy' % cargo_target.split('-')[0]) or 'objcopy'
|
|
|
|
print("with environment:")
|
|
for (k, v) in cargo_env.items():
|
|
print("%s=%s" % (k, v))
|
|
print("", flush=True)
|
|
|
|
subprocess.check_call(cmdline, env=cargo_env)
|
|
|
|
libs_in = os.path.join(cargo_env['CARGO_BUILD_TARGET_DIR'],
|
|
cargo_target,
|
|
configuration_name.lower())
|
|
|
|
src_path = os.path.join(libs_in, lib_format.format('signal_node'))
|
|
if os.access(src_path, os.R_OK):
|
|
if not allow_debug_level_logs:
|
|
check_for_debug_level_logs(src_path)
|
|
|
|
dst_base = 'libsignal_client_%s_%s' % (node_os_name, node_arch)
|
|
|
|
dst_path = os.path.join(out_dir, dst_base + '.node')
|
|
print("Copying %s to %s" % (src_path, dst_path))
|
|
os.makedirs(out_dir, exist_ok=True)
|
|
if objcopy:
|
|
subprocess.check_call([objcopy, '-S', src_path, dst_path])
|
|
else:
|
|
shutil.copyfile(src_path, dst_path)
|
|
|
|
maybe_dump_debug_symbols(
|
|
src_path=os.path.join(libs_in, debug_format.format('signal_node')),
|
|
src_checksum_path=os.path.join(libs_in, debug_format_for_checksum.format('signal_node')),
|
|
dst_path=os.path.join(out_dir, dst_base + '-debuginfo.sym'),
|
|
dst_checksum_path=os.path.join(out_dir, dst_base + '-debuginfo.sha256'),
|
|
)
|
|
|
|
if options.copy_to_prebuilds:
|
|
prebuild_dir = os.path.join('prebuilds', f"{node_os_name}-{node_arch}")
|
|
prebuild_path = os.path.join(prebuild_dir, '@signalapp+libsignal-client.node')
|
|
print("Copying %s to %s" % (dst_path, prebuild_path))
|
|
os.makedirs(prebuild_dir, exist_ok=True)
|
|
# We copy from dst_path in the build directory because it's already gone through an objcopy pass
|
|
shutil.copyfile(dst_path, prebuild_path)
|
|
else:
|
|
print("ERROR: did not find generated library")
|
|
return 1
|
|
|
|
return 0
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|