|
@@ -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)
|