aboutsummaryrefslogtreecommitdiffstats
path: root/build/scripts/create_recursive_library_for_cmake.py
diff options
context:
space:
mode:
authorAlexSm <alex@ydb.tech>2024-01-11 14:49:03 +0100
committerGitHub <noreply@github.com>2024-01-11 14:49:03 +0100
commit2e180154bd6a38b90a128ba0463d0dd2706a5ccf (patch)
tree0e0890fa08e63af33c52c9b6eacee56d037a740b /build/scripts/create_recursive_library_for_cmake.py
parent4366d88bef9360d9754e77eaa1f4a25d046a9cbd (diff)
downloadydb-2e180154bd6a38b90a128ba0463d0dd2706a5ccf.tar.gz
Library import 7 (#937)
Diffstat (limited to 'build/scripts/create_recursive_library_for_cmake.py')
-rw-r--r--build/scripts/create_recursive_library_for_cmake.py201
1 files changed, 201 insertions, 0 deletions
diff --git a/build/scripts/create_recursive_library_for_cmake.py b/build/scripts/create_recursive_library_for_cmake.py
new file mode 100644
index 0000000000..0023e7d02a
--- /dev/null
+++ b/build/scripts/create_recursive_library_for_cmake.py
@@ -0,0 +1,201 @@
+# Custom script is necessary because CMake does not yet support creating static libraries combined with dependencies
+# https://gitlab.kitware.com/cmake/cmake/-/issues/22975
+#
+# This script is intended to be used set as a CXX_LINKER_LAUNCHER property for recursive library targets.
+# It parses the linking command and transforms it to archiving commands combining static libraries from dependencies.
+
+import argparse
+import os
+import shlex
+import subprocess
+import sys
+import tempfile
+
+
+class Opts(object):
+ def __init__(self, args):
+ argparser = argparse.ArgumentParser(allow_abbrev=False)
+ argparser.add_argument('--cmake-binary-dir', required=True)
+ argparser.add_argument('--cmake-ar', required=True)
+ argparser.add_argument('--cmake-ranlib', required=True)
+ argparser.add_argument('--cmake-host-system-name', required=True)
+ argparser.add_argument('--cmake-cxx-standard-libraries')
+ argparser.add_argument('--global-part-suffix', required=True)
+ self.parsed_args, other_args = argparser.parse_known_args(args=args)
+
+ if len(other_args) < 2:
+ # must contain at least '--linking-cmdline' and orginal linking tool name
+ raise Exception('not enough arguments')
+ if other_args[0] != '--linking-cmdline':
+ raise Exception("expected '--linking-cmdline' arg, got {}".format(other_args[0]))
+
+ self.is_msvc_linker = other_args[1].endswith('\\link.exe')
+
+ is_host_system_windows = self.parsed_args.cmake_host_system_name == 'Windows'
+ std_libraries_to_exclude_from_input = (
+ set(self.parsed_args.cmake_cxx_standard_libraries.split())
+ if self.parsed_args.cmake_cxx_standard_libraries is not None
+ else set()
+ )
+ msvc_preserved_option_prefixes = [
+ 'errorreport',
+ 'machine:',
+ 'nodefaultlib',
+ 'nologo',
+ 'subsystem',
+ ]
+
+ self.preserved_options = []
+
+ # these variables can contain paths absolute or relative to CMAKE_BINARY_DIR
+ self.global_libs_and_objects_input = []
+ self.non_global_libs_input = []
+ self.output = None
+
+ def is_external_library(path):
+ """
+ Check whether this library has been built in this CMake project or came from Conan-provided dependencies
+ (these use absolute paths).
+ If it is a library that is added from some other path (like CUDA) return True
+ """
+ return not (os.path.exists(path) or os.path.exists(os.path.join(self.parsed_args.cmake_binary_dir, path)))
+
+ def process_input(args):
+ i = 0
+ is_in_whole_archive = False
+
+ while i < len(args):
+ arg = args[i]
+ if is_host_system_windows and ((arg[0] == '/') or (arg[0] == '-')):
+ arg_wo_specifier_lower = arg[1:].lower()
+ if arg_wo_specifier_lower.startswith('out:'):
+ self.output = arg[len('/out:') :]
+ elif arg_wo_specifier_lower.startswith('wholearchive:'):
+ lib_path = arg[len('/wholearchive:') :]
+ if not is_external_library(lib_path):
+ self.global_libs_and_objects_input.append(lib_path)
+ else:
+ for preserved_option_prefix in msvc_preserved_option_prefixes:
+ if arg_wo_specifier_lower.startswith(preserved_option_prefix):
+ self.preserved_options.append(arg)
+ break
+ # other flags are non-linking related and just ignored
+ elif arg[0] == '-':
+ if arg == '-o':
+ if (i + 1) >= len(args):
+ raise Exception('-o flag without an argument')
+ self.output = args[i + 1]
+ i += 1
+ elif arg == '-Wl,--whole-archive':
+ is_in_whole_archive = True
+ elif arg == '-Wl,--no-whole-archive':
+ is_in_whole_archive = False
+ elif arg.startswith('-Wl,-force_load,'):
+ lib_path = arg[len('-Wl,-force_load,') :]
+ if not is_external_library(lib_path):
+ self.global_libs_and_objects_input.append(lib_path)
+ elif arg == '-isysroot':
+ i += 1
+ # other flags are non-linking related and just ignored
+ elif arg[0] == '@':
+ # response file with args
+ with open(arg[1:]) as response_file:
+ parsed_args = shlex.shlex(response_file, posix=False, punctuation_chars=False)
+ parsed_args.whitespace_split = True
+ args_in_response_file = list(arg.strip('"') for arg in parsed_args)
+ process_input(args_in_response_file)
+ elif not is_external_library(arg):
+ if is_in_whole_archive or arg.endswith('.o') or arg.endswith('.obj'):
+ self.global_libs_and_objects_input.append(arg)
+ elif arg not in std_libraries_to_exclude_from_input:
+ self.non_global_libs_input.append(arg)
+ i += 1
+
+ process_input(other_args[2:])
+
+ if self.output is None:
+ raise Exception("No output specified")
+
+ if (len(self.global_libs_and_objects_input) == 0) and (len(self.non_global_libs_input) == 0):
+ raise Exception("List of input objects and libraries is empty")
+
+
+class FilesCombiner(object):
+ def __init__(self, opts):
+ self.opts = opts
+
+ archiver_tool_path = opts.parsed_args.cmake_ar
+ if sys.platform.startswith('darwin'):
+ # force LIBTOOL even if CMAKE_AR is defined because 'ar' under Darwin does not contain the necessary options
+ arch_type = 'LIBTOOL'
+ archiver_tool_path = 'libtool'
+ elif opts.is_msvc_linker:
+ arch_type = 'LIB'
+ elif opts.parsed_args.cmake_ar.endswith('llvm-ar'):
+ arch_type = 'LLVM_AR'
+ elif opts.parsed_args.cmake_ar.endswith('ar'):
+ arch_type = 'GNU_AR'
+ else:
+ raise Exception('Unsupported arch type for CMAKE_AR={}'.format(opts.parsed_args.cmake_ar))
+
+ self.archiving_cmd_prefix = [
+ sys.executable,
+ os.path.join(os.path.dirname(os.path.abspath(__file__)), 'link_lib.py'),
+ archiver_tool_path,
+ arch_type,
+ 'gnu', # llvm_ar_format, used only if arch_type == 'LLVM_AR'
+ opts.parsed_args.cmake_binary_dir,
+ 'None', # plugin. Unused for now
+ ]
+ # the remaining archiving cmd args are [output, .. input .. ]
+
+ def do(self, output, input_list):
+ input_file_path = None
+ try:
+ if self.opts.is_msvc_linker:
+ # use response file for input (because of Windows cmdline length limitations)
+
+ # can't use NamedTemporaryFile because of permissions issues on Windows
+ input_file_fd, input_file_path = tempfile.mkstemp()
+ try:
+ input_file = os.fdopen(input_file_fd, 'w')
+ for input in input_list:
+ if ' ' in input:
+ input_file.write('"{}" '.format(input))
+ else:
+ input_file.write('{} '.format(input))
+ input_file.flush()
+ finally:
+ os.close(input_file_fd)
+ input_args = ['@' + input_file_path]
+ else:
+ input_args = input_list
+
+ cmd = self.archiving_cmd_prefix + [output] + self.opts.preserved_options + input_args
+ subprocess.check_call(cmd)
+ finally:
+ if input_file_path is not None:
+ os.remove(input_file_path)
+
+ if not self.opts.is_msvc_linker:
+ subprocess.check_call([self.opts.parsed_args.cmake_ranlib, output])
+
+
+if __name__ == "__main__":
+ opts = Opts(sys.argv[1:])
+
+ output_prefix, output_ext = os.path.splitext(opts.output)
+ globals_output = output_prefix + opts.parsed_args.global_part_suffix + output_ext
+
+ if os.path.exists(globals_output):
+ os.remove(globals_output)
+ if os.path.exists(opts.output):
+ os.remove(opts.output)
+
+ files_combiner = FilesCombiner(opts)
+
+ if len(opts.global_libs_and_objects_input) > 0:
+ files_combiner.do(globals_output, opts.global_libs_and_objects_input)
+
+ if len(opts.non_global_libs_input) > 0:
+ files_combiner.do(opts.output, opts.non_global_libs_input)