def test_prefix_chars(self): parser = ArgumentParserEx(prefix_chars="/-") parser.add_argument("-attr1", action="store") ns, remaining = parser.parse_known_args(["/attr1", "val"]) self.assertListEqual([], remaining) self.assertEqual("val", ns.attr1) ns, remaining = parser.parse_known_args(["-attr1", "val"]) self.assertListEqual([], remaining) self.assertEqual("val", ns.attr1) parser.set(prefix_chars="+") parser.add_argument("+attr2", action="store") ns, remaining = parser.parse_known_args( ["-attr1", "val1", "+attr2", "val2"]) self.assertListEqual([], remaining) self.assertEqual("val1", ns.attr1) self.assertEqual("val2", ns.attr2) ns, remaining = parser.parse_known_args( ["/attr1", "val1", "+attr2", "val2"]) self.assertListEqual([], remaining) self.assertEqual("val1", ns.attr1) self.assertEqual("val2", ns.attr2) ns, remaining = parser.parse_known_args( ["+attr1", "val1", "-attr2", "val2"]) self.assertListEqual(["+attr1", "val1", "-attr2", "val2"], remaining) self.assertIsNone(vars(ns).get("attr1")) self.assertIsNone(vars(ns).get("attr2"))
def test_msvc_flag_with_value(self): parser = ArgumentParserEx(prefix_chars="/-") parser.set(ignore_case=True) parser.add_argument("/flag", action="msvc_flag_with_value") parser.add_argument("/ENABLE", action="msvc_flag_with_value", append=True, dest="list") ns, remaining = parser.parse_known_args(["/FLAG"]) self.assertListEqual(["/FLAG"], remaining) self.assertIsNone(vars(ns).get("flag")) ns, remaining = parser.parse_known_args(["/flag"]) self.assertListEqual(["/flag"], remaining) self.assertIsNone(vars(ns).get("flag")) ns, remaining = parser.parse_known_args(["/flag:"]) self.assertListEqual(["/flag:"], remaining) self.assertIsNone(vars(ns).get("flag")) ns = parser.parse_args(["/flag:value"]) self.assertEqual("value", ns.flag) ns = parser.parse_args(["-FLAG:1,2,3"]) self.assertEqual("1,2,3", ns.flag) ns = parser.parse_args(["/ENABLE:1", "-FlAg:2", "-enable:3"]) self.assertEqual("2", ns.flag) self.assertEqual(["1", "3"], ns.list)
class MsvcMc(ParserBase): priority = 7 @staticmethod def add_arguments(arg_parser): pass def __init__(self, context, platform=None): ParserBase.__init__(self, context) self.platform = get_platform(platform) self.program_re = self.platform.get_program_path_re("mc") # https://docs.microsoft.com/en-us/windows/windows/wes/message-compiler--mc-exe- # Currently, only small subset of flags is supported self.parser = ArgumentParserEx() self.parser.set(raw_dest="args") self.parser.add_argument("-h", raw_handler=self.input_dir, dest="header_dir") self.parser.add_argument("-r", raw_handler=self.input_dir, dest="resource_dir") self.parser.add_argument("manifest_file", raw_handler=self.input_file, dest="manifest_file") def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.program_re.match(tokens[0]): return target tokens.pop(0) namespace = self.parser.parse_args(tokens) if namespace.header_dir is None: namespace.header_dir = target["working_dir"] header_dir = self.context.get_dir_arg( self.context.normalize_path(namespace.header_dir)) if namespace.resource_dir is None: namespace.resource_dir = target["working_dir"] resource_dir = self.context.get_dir_arg( self.context.normalize_path(namespace.resource_dir)) filename_no_ext = os.path.basename(namespace.manifest_file) filename_no_ext = os.path.splitext(filename_no_ext)[0] output = [ header_dir + "/" + filename_no_ext + ".h", resource_dir + "/" + filename_no_ext + ".rc", ] return get_command_target(None, "mc", namespace.args, output, namespace.dependencies)
def test_msvc_flag_with_value_with_append(self): parser = ArgumentParserEx(prefix_chars="/-") parser.set(ignore_case=True) parser.add_argument("/flag", action="msvc_flag_with_value", dest="flags", append=True) ns = parser.parse_args(["/flag:a", "/flag:b"]) self.assertListEqual(["a", "b"], ns.flags)
class Icupkg(ParserBase): priority = 7 def __init__(self, context, platform=platform.system().lower()): ParserBase.__init__(self, context) self.platform = get_platform(platform) self.program_re = self.platform.get_program_path_re("icupkg") # https://helpmanual.io/help/icupkg/ self.parser = ArgumentParserEx() self.parser.set(dest=None, raw_dest="args") self.parser.add_argument("--type", "-t", choices=["l", "b", "e"]) self.parser.add_argument("--copyright", "-c") self.parser.add_argument("--comment", "-C") self.parser.add_argument("--add", "-a") self.parser.add_argument("--remove", "-r") self.parser.add_argument("--extract", "-x") self.parser.add_argument("--writepkg", "-w", action="store_true") self.parser.add_argument("--matchmode", "-m") self.parser.add_argument("--auto_toc_prefix", action="store_true") self.parser.add_argument("--auto_toc_prefix_with_type", action="store_true") self.parser.add_argument("--sourcedir", "-s", raw_handler=self.input_dir) self.parser.add_argument("--destdir", "-d", raw_handler=self.input_dir) self.parser.add_argument("--list", "-l", action="store_true") self.parser.add_argument("--outlist", "-o", raw_handler=self.output_file) self.parser.add_argument("infilename", raw_handler=self.input_file) self.parser.add_argument("outfilename", nargs="?", raw_handler=self.output_file) def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.program_re.match(tokens[0]): return target tokens.pop(0) namespace = self.parser.parse_args(tokens) return get_command_target( None, program="icupkg", args=namespace.args, output=namespace.output, dependencies=namespace.dependencies, )
def test_type_conversion(self): parser = ArgumentParserEx() parser.add_argument("--a1", type=int) # disable prefix chars to allow negative numbers as positional arguments parser.set(prefix_chars="") parser.add_argument("a2", type=float) ns, remaining = parser.parse_known_args(["--a1", "h", "0.1"]) self.assertListEqual(["--a1", "h"], remaining) self.assertIsNone(vars(ns).get("a1")) self.assertEqual(0.1, ns.a2) ns, remaining = parser.parse_known_args(["-1", "--a1=123"]) self.assertListEqual([], remaining) self.assertEqual(123, ns.a1) self.assertEqual(-1, ns.a2) ns, remaining = parser.parse_known_args(["--a1", "-5", "-1"]) self.assertListEqual([], remaining) self.assertEqual(-5, ns.a1) self.assertEqual(-1, ns.a2)
class MsvcLib(Parser): filename_re = os_ext.Windows.get_program_path_re("lib") link_re = os_ext.Windows.get_program_path_re("link") lld_link_re = os_ext.Windows.get_program_path_re("lld-link") priority = 7 @staticmethod def add_arguments(arg_parser): pass @staticmethod def is_applicable(project=None, log_type=None): return True def __init__(self, context, project_version=None): self.context = context self.project_version = project_version # Visual Studio lib.exe arguments # https://docs.microsoft.com/en-us/cpp/build/reference/running-lib?view=vs-2019 self.parser = ArgumentParserEx(prefix_chars="-/") self.parser.set_defaults(compile_flags=[], link_flags=[], include_dirs=[], infiles=[]) # TODO: publish all meaningful flags self.parser.set(ignore_case=True, dest=None, raw_dest="link_flags") self.parser.add_argument("/ignore", action="msvc_flag_with_value") self.parser.add_argument("/libpath", action="msvc_flag_with_value", append=True, raw_dest=None) self.parser.add_argument("/ltcg", action="store_true") self.parser.add_argument("/machine", action="msvc_flag_with_value") self.parser.add_argument("/out", action="msvc_flag_with_value", dest="output", raw_dest=None) self.parser.add_argument("/nologo", action="store_true") self.parser.add_argument("/wx", action="msvc_flag", msvc_false_suffix=":no") self.parser.add_argument("infiles", dest="infiles", nargs="*", raw_dest=None) # lld-link.exe arguments (/lib mode) # Cannot find documentation online self.lld_link_parser = deepcopy(self.parser) self.lld_link_parser.add_argument("/llvmlibthin", action="store_true") self.lld_link_parser.set(prefix_chars="-") self.lld_link_parser.add_argument("--color-diagnostics", action="store_true") def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target is_lld_link = False if self.link_re.match(tokens[0]) and is_lib_shim(tokens): is_lld_link = bool(self.lld_link_re.match(tokens[0])) tokens = tokens[2:] elif not self.filename_re.match(tokens[0]): return target else: tokens.pop(0) if len(tokens) > 0: if tokens[0] == ":": # skipping parsing output of utilities, like: `lib : warning ...` return target if is_lld_link: namespace = self.lld_link_parser.parse_args(tokens) else: namespace = self.parser.parse_args(tokens) dependencies = [] libs = [] objects = [] for infile in namespace.infiles: if os_ext.is_static_lib(infile): libs.append(self.context.get_lib_arg(infile, dependencies, [])) else: path = next( os_ext.Windows.get_obj_file_locations( infile, [], self.context.working_dir)) if self.context.find_target(self.context.get_file_arg( path)) or os.path.exists(path): objects.append( self.context.get_file_arg(path, dependencies)) else: # System object file, treat it as a linker flag # https://docs.microsoft.com/en-us/cpp/c-runtime-library/link-options objects.append(infile) output = self.context.normalize_path(namespace.output) descr = os_ext.parse_static_lib(output) module_name = descr["module_name"] name = descr["target_name"] version = descr["version"] or self.project_version output = self.context.get_output(output, dependencies) return get_module_target( ModuleTypes.static_lib, name, output, dependencies=dependencies, module_name=module_name, libs=libs, objects=objects, version=version, link_flags=namespace.link_flags, )
class MsvcMl(CompilerParser): filename_re = os_ext.Windows.get_program_path_re("ml", "ml64") priority = 7 def __init__(self, context, ignore_compile_flags=None): CompilerParser.__init__(self, context, ignore_compile_flags=ignore_compile_flags) # Visual Studio ml.exe arguments # https://docs.microsoft.com/en-us/cpp/assembler/masm/ml-and-ml64-command-line-reference?view=vs-2017 self.parser = ArgumentParserEx(prefix_chars="-/") self.parser.set_defaults(compile_flags=[], link_flags=[], include_dirs=[], infiles=[]) # TODO: publish all meaningful flags self.parser.set(dest=None, raw_dest="compile_flags") self.parser.add_argument("/c", action="store_true", raw_dest=None, dest="compile_only") # Generates common object file format (COFF) type of object module self.parser.add_argument("/coff", action="store_true") # Preserves case of all user identifiers self.parser.add_argument("/Cp", action="store_true") # Maps all identifiers to upper case self.parser.add_argument("/Cu", action="store_true") # Preserves case in public and extern symbols self.parser.add_argument("/Cx", action="store_true") self.parser.add_argument("/D", raw_format=format_flag_gnu) self.parser.add_argument("/errorreport", prefix=True, nargs="?", ignore_case=True) self.parser.add_argument("/Fo", prefix=True, raw_dest=None, dest="output") # stack size self.parser.add_argument("/F", prefix=True) # Generates emulator fix-ups for floating-point arithmetic self.parser.add_argument("/FPi", action="store_true") # Specifies use of C-style function calling and naming conventions self.parser.add_argument("/Gd", action="store_true") # Specifies use of __stdcall function calling and naming conventions self.parser.add_argument("/GZ", action="store_true") self.parser.add_argument("/I", action="append", raw_dest=None, dest="include_dirs") self.parser.add_argument("/nologo", action="store_true", ignore_case=True) # Generates object module file format (OMF) type of object module self.parser.add_argument("/omf", action="store_true") self.parser.add_argument("/Ta", prefix=True, action="append", raw_dest=None, dest="infiles") self.parser.add_argument("/safeseh", action="store_true", ignore_case=True) # Warning flags self.parser.add_argument(flags=["/W", "/WX"], action="store_true") self.parser.add_argument("/W", choices="0123") # line-number information in object file self.parser.add_argument("/Zd", action="store_true") # Generate CodeView information in object file self.parser.add_argument("/Zi", action="store_true") # Makes all symbols public self.parser.add_argument("/Zf", action="store_true") # Packs structures on the specified byte boundary self.parser.add_argument("/Zp", prefix=True, type=int) self.parser.add_argument("infiles", nargs="*", dest="infiles", raw_dest=None) include_re = re.compile(r"(?:\b|%)include\s+(?P<path>[^\s\n][^\n]*)", re.IGNORECASE) def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.filename_re.match(tokens[0]): return target if len(tokens) < 2 or tokens[1] == ":": # skip warning message return target tokens.pop(0) namespace = self.parser.parse_args(tokens) assert namespace.compile_only dependencies = [] self.process_namespace(namespace) # ml.exe doesn't support /showIncludes or anything similar add_included_dependencies( self.context, self.include_re, dependencies, namespace.include_dirs, namespace.infiles, self.context.working_dir, ) include_dirs = [] for dir in namespace.include_dirs: dir = self.context.normalize_path(dir) arg = self.context.get_dir_arg(dir, dependencies) if arg: include_dirs.append(arg) sources = [] for infile in namespace.infiles: sources.append( get_source_file_reference( self.context.get_file_arg( self.context.normalize_path(infile), dependencies), "MASM", )) output = self.context.get_output( self.context.normalize_path(namespace.output)) return get_module_target( ModuleTypes.object_lib, None, output, compile_flags=namespace.compile_flags, include_dirs=include_dirs, dependencies=dependencies, sources=sources, )
class Yasm(CompilerParser): filename_re = os_ext.Windows.get_program_path_re("yasm") priority = 7 def __init__(self, context, ignore_compile_flags=None): CompilerParser.__init__(self, context, ignore_compile_flags=ignore_compile_flags) # YASM arguments # http://www.tortall.net/projects/yasm/manual/html/yasm-options.html self.parser = ArgumentParserEx() self.parser.set_defaults(compile_flags=[], include_dirs=[]) self.parser.set(dest=None, raw_dest="compile_flags") # select architecture self.parser.add_argument("--arch", "-a", raw_format=ReplacePrefixArg("-a")) # select parser self.parser.add_argument("--parser", "-p", raw_format=ReplacePrefixArg("-p")) # select preprocessor self.parser.add_argument("--preproc", "-r", raw_format=ReplacePrefixArg("-r")) # object format self.parser.add_argument("--oformat", "-f", raw_format=ReplacePrefixArg("-f")) # debugging format self.parser.add_argument("--dformat", "-g", raw_format=ReplacePrefixArg("-d")) # name of object-file output self.parser.add_argument("--objfile", "-o", dest="output", raw_dest=None) # select machine self.parser.add_argument("--machine", "-m", raw_format=ReplacePrefixArg("-m")) # treat all sized operands as if `strict' was used self.parser.add_argument("--force-strict", action="store_true") # add include path self.parser.add_argument("-I", action="append", dest="include_dirs", ignore_case=True, raw_dest=None) # pre-include file self.parser.add_argument("-P", action="append", dest="preinclude_files", default=[], raw_dest=None) # pre-define a macro self.parser.add_argument("-D", ignore_case=True, raw_format=prefix_arg_toupper) # undefine a macro self.parser.add_argument("-U", ignore_case=True, raw_format=prefix_arg_toupper) # disable all warnings self.parser.add_argument("-w", action="store_true") # enables/disables warning self.parser.add_argument("-W", prefix=True) # redirect error messages to file self.parser.add_argument("-E") # redirect error messages to stdout self.parser.add_argument("-s", action="store_true") # select error/warning message style (`gnu' or `vc') self.parser.add_argument("-X") # prepend argument to name of all external symbols self.parser.add_argument("--prefix") # append argument to name of all external symbols self.parser.add_argument("--suffix", "--postfix") self.parser.add_argument("file", dest="file", raw_dest=None) # TODO: this function is mostly the same for GCC, NASM, YASM. Make a base class. def _add_implicit_dependencies( self, compiler, dependencies, compile_flags, include_dirs, preinclude_files, source, cwd, ): include_dir_args = ["-I" + d for d in include_dirs] preinclude_args = ["-P" + p for p in preinclude_files] cmd = ([compiler, "--preproc-only", "-M"] + compile_flags + preinclude_args + include_dir_args + [source]) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) stdout, stderr = p.communicate() if type(stdout) is not str: stdout = stdout.decode("utf-8", "replace") if type(stderr) is not str: stderr = stderr.decode("utf-8", "replace") retcode = p.poll() if retcode: logger.error( "Command '{}' returned non-zero exit code {}\n{}".format( " ".join(cmd), retcode, stderr)) implicit_dependencies = [] for line in stdout.splitlines(): files = line.rstrip("\\").lstrip().split(" ") for f in files: if not f or f.endswith(":") or f == source: continue implicit_dependencies.append(f) return [ self.context.get_file_arg(self.context.normalize_path(dep), dependencies) for dep in implicit_dependencies ] # TODO: this function is mostly the same for all compiler parsers. Make a base class. def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.filename_re.match(tokens[0]): return target compiler = tokens.pop(0) compiler_nrm = self.context.platform.normalize_path(compiler) if compiler_nrm in self.context.path_aliases: compiler = self.context.path_aliases[compiler_nrm] namespace = self.parser.parse_args(tokens) dependencies = [] output = self.context.get_output( self.context.normalize_path(namespace.output)) self.process_namespace(namespace) self._add_implicit_dependencies( compiler, dependencies, namespace.compile_flags, namespace.include_dirs, namespace.preinclude_files, namespace.file, self.context.working_dir, ) compile_flags = namespace.compile_flags for preinclude in namespace.preinclude_files: path = self.context.get_file_arg( self.context.normalize_path(preinclude), dependencies) compile_flags.append("-P" + path) source = get_source_file_reference( self.context.get_file_arg( self.context.normalize_path(namespace.file), dependencies), "YASM", compile_flags=compile_flags, ) self.process_namespace(source) include_dirs = [] for dir in namespace.include_dirs: # include dir may not exist, it's normal dir = self.context.normalize_path(dir) arg = self.context.get_dir_arg(dir, dependencies) if arg: include_dirs.append(arg) return get_module_target( ModuleTypes.object_lib, None, output, dependencies=dependencies, compile_flags=[], sources=[source], include_dirs=include_dirs, )
class Nasm(CompilerParser): filename_re = os_ext.Windows.get_program_path_re("nasm") priority = 7 def __init__(self, context, ignore_compile_flags=None): CompilerParser.__init__(self, context, ignore_compile_flags=ignore_compile_flags) # NASM arguments # https://www.nasm.us/doc/nasmdoc2.html#section-2.1 self.parser = ArgumentParserEx() self.parser.set_defaults(compile_flags=[], include_dirs=[]) # TODO: publish all meaningful flags self.parser.set(dest=None, raw_dest="compile_flags") # Define a Macro self.parser.add_argument("-D", raw_format=format_flag_gnu, ignore_case=True) # Select Debug Information Format self.parser.add_argument( "-F", raw_format=format_flag_gnu, ) # Output File Format self.parser.add_argument("-f", required=True, raw_format=format_flag_gnu) # Enabling Debug Information self.parser.add_argument("-g", action="store_true") # Include File Search Directories self.parser.add_argument("-I", action="append", dest="include_dirs", ignore_case=True, raw_dest=None) # Assemble and Generate Dependencies self.parser.add_argument("-MD", raw_dest=None) # Multipass Optimization self.parser.add_argument("-O") # Output File Name self.parser.add_argument("-o", dest="output", raw_dest=None) # Pre-Include a File self.parser.add_argument( "-P", "--include", action="append", default=[], dest="preincludes", raw_dest=None, ) # Undefine a Macro self.parser.add_argument("-U", raw_format=format_flag_gnu, ignore_case=True) # Warnings self.parser.add_argument("-W", "-w") self.parser.add_argument("infile", dest="infile", raw_dest=None) def _add_implicit_dependencies( self, compiler, dependencies, compile_flags, include_dirs, preincludes, source, cwd, ): include_dir_args = ["-I" + d for d in include_dirs] preinclude_args = ["-P" + p for p in preincludes] cmd = ([compiler, "-E", "-M"] + compile_flags + preinclude_args + include_dir_args + [source]) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) stdout, stderr = p.communicate() if type(stdout) is not str: stdout = stdout.decode("utf-8", "replace") if type(stderr) is not str: stderr = stderr.decode("utf-8", "replace") retcode = p.poll() if retcode: logger.error( "Command '{}' returned non-zero exit code {}\n{}".format( " ".join(cmd), retcode, stderr)) implicit_dependencies = [] for line in stdout.splitlines(): files = line.rstrip("\\").lstrip().split(" ") if len(files) > 1 and files[1] == ":": files = [files[0] + files[1]] + files[2:] for f in files: if not f or f.endswith(":") or f == source: continue implicit_dependencies.append(f) return [ self.context.get_file_arg(self.context.normalize_path(dep), dependencies) for dep in implicit_dependencies ] def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.filename_re.match(tokens[0]): return target compiler = tokens.pop(0) namespace = self.parser.parse_args(tokens) dependencies = [] infile = namespace.infile if not infile: assert len(namespace.preincludes) > 0 infile = namespace.preincludes.pop() self.process_namespace(namespace) source_compile_flags = copy(namespace.compile_flags) for preinclude in namespace.preincludes: path = self.context.get_file_arg( self.context.normalize_path(preinclude), dependencies) source_compile_flags.append("-P" + path) source = get_source_file_reference( self.context.get_file_arg(self.context.normalize_path(infile), dependencies), "NASM", compile_flags=source_compile_flags, ) self.process_namespace(source) output = self.context.get_output( self.context.normalize_path(namespace.output)) include_dirs = [] for dir in namespace.include_dirs: # include dir may not exist, it's normal dir = self.context.normalize_path(dir) arg = self.context.get_dir_arg(dir, dependencies) if arg: include_dirs.append(arg) self._add_implicit_dependencies( compiler, dependencies, namespace.compile_flags, namespace.include_dirs, namespace.preincludes, infile, self.context.working_dir, ) return get_module_target( ModuleTypes.object_lib, None, output, dependencies=dependencies, sources=[source], include_dirs=include_dirs, )
class Objcopy(ParserBase): def __init__(self, context, platform=None): ParserBase.__init__(self, context) self.platform = get_platform(platform) self.program_re = self.platform.get_program_path_re("objcopy") # https://sourceware.org/binutils/docs/binutils/objcopy.html self.parser = ArgumentParserEx() self.parser.set(dest=None, raw_dest="args") self.parser.add_argument("--redefine-syms", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--keep-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--strip-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--strip-unneeded-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--keep-global-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--keep-global-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--localize-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--globalize-symbols", raw_handler=self.input_file_after_equals_sign) self.parser.add_argument("--weaken-symbols", raw_handler=self.input_file_after_equals_sign) obj_re = re.compile(r"^[^-=+:]+$") self.parser.add_argument("infile", dest="infile", raw_handler=self.input_file, args_regexp=obj_re) self.parser.add_argument( "outfile", dest="outfile", nargs="?", raw_handler=self.output_file, args_regexp=obj_re, ) def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.program_re.match(tokens[0]): return target tokens.pop(0) namespace, _ = self.parser.parse_known_args(tokens, unknown_dest="args") inplace = False if namespace.outfile: if namespace.outfile[0] == namespace.infile: inplace = True else: inplace = True if inplace: namespace.infile = self.context.get_file_arg(namespace.infile) infile_target = self.context.target_index.get(namespace.infile) if infile_target and infile_target["type"] == "module": if infile_target.get("post_build_commands") is None: infile_target["post_build_commands"] = [] infile_target["post_build_commands"].append({ "program": "objcopy", "args": namespace.args }) infile_target["dependencies"].extend(namespace.dependencies) infile_target["dependencies"].remove(infile_target["output"]) self.context.update_target(infile_target) return [] return get_command_target( None, program="objcopy", args=namespace.args, output=namespace.output, dependencies=namespace.dependencies, )
class Pkgdata(ParserBase): # Should be lower than MsvcRc (because we expect that rc target should present) priority = 7.1 def __init__(self, context, platform=None): ParserBase.__init__(self, context) self.platform = get_platform(platform) self.program_re = self.platform.get_program_path_re("pkgdata") # https://helpmanual.io/help/icupkg/ self.parser = ArgumentParserEx() self.parser.set(raw_dest="args") self.parser.add_argument("--name", "-p") self.parser.add_argument("--bldopt", "-O", raw_handler=self.input_file) self.parser.add_argument( "--mode", "-m", choices=["files", "dll", "library", "common", "archive", "static"], ) self.parser.add_argument("--verbose", "-v", action="store_true") self.parser.add_argument("--copyright", "-c", action="store_true") self.parser.add_argument("--comment", "-C", action="store_true") self.parser.add_argument("--destdir", "-d", raw_handler=self.input_dir) self.parser.add_argument("--rebuild", "-F", action="store_true") self.parser.add_argument("--tempdir", "-T", raw_handler=self.input_dir) self.parser.add_argument("--install", "-I") self.parser.add_argument("--sourcedir", "-s", raw_handler=self.input_dir) self.parser.add_argument("--entrypoint", "-e") self.parser.add_argument("--revision", "-r") self.parser.add_argument("--force-prefix", "-f") self.parser.add_argument("--libname", "-L") self.parser.add_argument("--quiet", "-q", action="store_true") self.parser.add_argument("--without-assembly", "-w", action="store_true") self.parser.add_argument("--zos-pds-build", "-z", action="store_true") self.parser.add_argument("packageFile", raw_handler=self.input_file) def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.program_re.match(tokens[0]): return target tokens.pop(0) namespace = self.parser.parse_args(tokens) create_symlinks = False base_target_name = None main_target_name = None if namespace.name is not None and namespace.libname is None: namespace.libname = namespace.name if namespace.mode in ["dll", "library"]: output = self.platform.get_library_filenames( namespace.libname, shared=True, static=False, revision=namespace.revision, ) base_target_name = self.platform.parse_shared_lib( output[0])["target_name"] main_target_name = base_target_name if namespace.revision: if self.platform != Windows: create_symlinks = True else: idx = -len(self.platform.shared_lib_ext) # add revision to .dll only output[0] = output[0][:idx] + namespace.revision + output[ 0][idx:] main_target_name += "." + namespace.revision if self.platform == Windows: rc_output_filename = os.path.join(namespace.tempdir, "icudata.res") rc_target = self.context.find_target_by_path( rc_output_filename) namespace.dependencies.append(rc_target["output"]) elif namespace.mode in ["static"]: prefix = "" if self.platform == Windows and namespace.libname[0] != "s": prefix = "s" output = self.platform.get_library_filenames(prefix + namespace.libname, shared=False, static=True) base_target_name = self.platform.parse_static_lib( output[0])["target_name"] main_target_name = base_target_name else: output = [namespace.name + ".dat"] for idx, o in enumerate(output): o = output[idx] o = os.path.join(namespace.destdir, o) o = self.context.get_output(self.context.normalize_path(o), namespace.dependencies) output[idx] = o symlink_targets = [] if create_symlinks: major = namespace.revision.split(".")[0] for rev in [major, None]: destination = self.platform.get_library_filenames( namespace.libname, shared=True, static=False, revision=rev)[0] destination = os.path.join(namespace.destdir, destination) dependencies = [output[0]] destination = self.context.get_output( self.context.normalize_path(destination), dependencies) name = base_target_name if rev: name += "." + rev symlink_targets.append( get_copy_target(name, output[0], destination, dependencies=dependencies)) self.platform.get_library_filenames target = get_command_target(main_target_name, "pkgdata", namespace.args, output, namespace.dependencies) return [target] + symlink_targets
class MsvcCl(CompilerParser, LinkerParser): filename_re = os_ext.Windows.get_program_path_re("cl") clang_cl_re = os_ext.Windows.get_program_path_re("clang-cl") c_exts = [".c"] cpp_exts = [".cc", ".cpp", ".cxx"] source_exts = c_exts + cpp_exts priority = 7 @staticmethod def add_arguments(arg_parser): CompilerParser.add_arguments(arg_parser) LinkerParser.add_arguments(arg_parser) def __init__(self, context, ignore_compile_flags=None, ignore_link_flags=None): CompilerParser.__init__(self, context, ignore_compile_flags=ignore_compile_flags) LinkerParser.__init__(self, context, ignore_link_flags=ignore_link_flags) # /showIncludes flag doesn't allow filtering toolchain includes, # se we have to do that ourselves. msvc_include_dirs = os.environ.get("INCLUDE", []) if msvc_include_dirs: self.msvc_include_dirs = prepare_toolchain_include_dirs( msvc_include_dirs.split(";"), context) else: self.msvc_include_dirs = [] logger.debug("MSVC include dirs: %r" % self.msvc_include_dirs) self.clang_cl_include_dirs = None # Visual Studio cl.exe arguments # https://docs.microsoft.com/en-us/cpp/build/reference/compiler-options-listed-alphabetically?view=vs-2017 self.parser = ArgumentParserEx(prefix_chars="-/") self.parser.set_defaults(compile_flags=[], link_flags=[], include_dirs=[], infiles=[]) # TODO: publish all meaningful flags # TODO: specify raw_format= for each argument self.parser.set(dest=None, raw_dest="compile_flags") # Enables code analysis and control options self.parser.add_argument("/analyze", prefix=True) self.parser.add_argument("/arch", action="msvc_flag_with_value") self.parser.add_argument( "/bigobj", action="store_true", raw_format=format_flag_msvc_lowercase, ignore_case=True, ) # Emit an object file which can be reproduced over time self.parser.add_argument("/Brepro", action="msvc_flag") self.parser.add_argument("/c", action="store_true", dest="compile_only", raw_dest=None) self.parser.add_argument("/D", raw_format=format_flag_gnu) # /d1xxx = undocumented frontend options self.parser.add_argument(prefixes=["/d1", "-d1"]) # /d2xxx = undocumented backend options self.parser.add_argument(prefixes=["/d2", "-d2"]) self.parser.add_argument("/diagnostics", action="msvc_flag_with_value") # Exception Handling Model self.parser.add_argument(prefixes=["/EH", "-EH"]) self.parser.add_argument("/errorreport", action="msvc_flag_with_value", ignore_case=True) self.parser.add_argument("/favor", action="msvc_flag_with_value") self.parser.add_argument("/FC", action="store_true") # Force include # TODO: process path arg declaratively (type= argument?) self.parser.add_argument("/FI", prefix=True) self.parser.add_argument("/FR", prefix=True) # IDE minimal rebuild self.parser.add_argument("/FD", action="store_true") # Detect 64-Bit Portability Issues self.parser.add_argument("/Wp64", action="store_true") # Floating point behavior self.parser.add_argument("/fp", action="msvc_flag_with_value") # Force Synchronous PDB Writes self.parser.add_argument("/FS", action="store_true") # Program Database File Name self.parser.add_argument("/Fd", prefix=True) # Output: exe, dll self.parser.add_argument("/Fe", prefix=True, dest="output", raw_dest=None) # Output: obj self.parser.add_argument("/Fo", prefix=True, dest="output", raw_dest=None) # Precompiled header (.pch) file name # TODO: process path arg self.parser.add_argument("/Fp", prefix=True) # Eliminate Duplicate Strings self.parser.add_argument("/GF", action="store_true") # Whole Program Optimization self.parser.add_argument("/GL", action="msvc_flag") # Full path of source code file in diagnostics self.parser.add_argument("/FC", action="msvc_flag") # Runtime Type Information self.parser.add_argument("/GR", action="msvc_flag", raw_format=format_flag_msvc) # Calling convention self.parser.add_argument("/Gd", "/Gr", "/Gv", "/Gz", action="store_true") # Minimal rebuild self.parser.add_argument("/Gm", action="msvc_flag", raw_dest=None) # /sdl - Enable additional security check self.parser.add_argument("/sdl-", action="msvc_flag") # Buffer Security Check self.parser.add_argument("/GS", action="msvc_flag") # Control Stack Checking Calls self.parser.add_argument("/Gs", action="store_true") self.parser.add_argument(prefixes=["/Gs", "-Gs"], type=int) # Control Flow Guard self.parser.add_argument("/guard:cf", action="msvc_flag") # Optimize Global Data self.parser.add_argument("/Gw", action="msvc_flag") # Enable Function-Level Linking self.parser.add_argument("/Gy", action="msvc_flag") # TODO: process path arg self.parser.add_argument("/I", action="append", dest="include_dirs", raw_dest=None) # Create dll self.parser.add_argument("/LD", "/LDd", action="store_true", raw_dest=None, dest="is_dll") # Passes one or more linker options to the linker # The /link option and its linker options must appear after # any file names and CL options self.parser.add_argument("/link", nargs="+", raw_dest="link_flags") # Dynamic runtime self.parser.add_argument("/MD", "/MDd", action="store_true") self.parser.add_argument("/MP", action="store_true") self.parser.add_argument(prefixes=["/MP", "-MP"], type=int) # Static runtime self.parser.add_argument("/MT", "/MTd", action="store_true") self.parser.add_argument("/nologo", action="store_true", ignore_case=True) # Optimize Code self.parser.add_argument(prefixes=["/O", "-O"], choices="12bdgistxy") # Inline Function Expansion self.parser.add_argument(prefixes=["/Ob", "-Ob"], choices="012") # Frame-Pointer Omission self.parser.add_argument("/Oy", action="msvc_flag") self.parser.add_argument("/showIncludes", action="store_true") self.parser.add_argument("/source-charset", action="msvc_flag_with_value") self.parser.add_argument("/std", action="msvc_flag_with_value") self.parser.add_argument("/TC", action="store_true") self.parser.add_argument("/TP", action="store_true") # C file # TODO: process path arg self.parser.add_argument("/Tc") # C++ file # TODO: process path arg self.parser.add_argument("/Tp") self.parser.add_argument("/utf-8", action="store_true") self.parser.add_argument("/U", raw_format=format_flag_gnu) # Ignore system PATH and INCLUDE self.parser.add_argument("/X", action="store_true") # Create Precompiled Header File self.parser.add_argument("/Yc", action="store_true") self.parser.add_argument("/Yc", prefix=True) # Use Precompiled Header File self.parser.add_argument("/Yu", action="store_true") self.parser.add_argument("/Yu", prefix=True) # Debug Information Format self.parser.add_argument("/Z7", "/Zi", "/Zl", action="store_true") # Conformance self.parser.add_argument("/Zc", action="msvc_flag_with_value") # Syntax Check Only self.parser.add_argument("/Zs", dest="syntax_check_only", raw_dest=None, action="store_true") # Precompiled Header Memory Allocation Limit self.parser.add_argument("/Zm", prefix=True) self.parser.add_argument("/w", "/W0", "/W1", "/W2", "/W3", "/W4", "/Wall", action="store_true") self.parser.add_argument("/WX", action="msvc_flag") self.parser.add_argument(prefixes=[ "/Wv", "/w1", "/w2", "/w3", "/w4", "/wd", "/we", "/wo", "-Wv", "-w1", "-w2", "-w3", "-w4", "-wd", "-we", "-wo", ]) # TODO: process path arg self.parser.add_argument("infiles", nargs="*", dest="infiles", raw_dest=None) # clang-cl.exe arguments # https://clang.llvm.org/docs/UsersManual.html#id9 self.clang_cl_parser = deepcopy(self.parser) self.clang_cl_parser.set(prefix_chars="-") self.clang_cl_parser.add_argument("-imsvc", prefix=True) self.clang_cl_parser.add_argument("-Xclang") self.clang_cl_parser.add_argument(prefixes=["-f"]) # Processor options self.clang_cl_parser.add_argument(prefixes=["-m"]) self.clang_cl_parser.add_argument("-no-canonical-prefixes", action="store_true") self.clang_cl_parser.add_argument(prefixes=["-std="]) self.clang_cl_parser.add_argument(prefixes=["-W"]) self.link_parser = get_msvc_link_parser( context, ignore_link_flags=ignore_link_flags) re_str = r"^Note: including file:\s+(?P<path>[^\s].*)$" include_note_re = re.compile(re_str, re.IGNORECASE) def _add_implicit_dependencies( self, compiler, dependencies, compile_flags, include_dirs, sources, cwd, is_clang_cl=False, ): if not sources: return _compiler = os.path.join(cwd, compiler) if os.path.exists(_compiler): compiler = _compiler cmd = [compiler, "/Zs", "/showIncludes"] cmd += ["-I" + d for d in include_dirs] + flatten_list(compile_flags) + sources try: stdout, stderr = subprocess_ex.check_output(cmd, cwd=cwd) except subprocess_ex.CalledProcessError as e: logger.error("{}\nstderr:\n{}stdout:\n{}".format( e, e.stderr, e.stdout)) return toolchain_include_dirs = self.msvc_include_dirs if is_clang_cl and self.clang_cl_include_dirs: toolchain_include_dirs = self.clang_cl_include_dirs for line in stdout.splitlines(): m = self.include_note_re.match(line.strip()) if m: try: path = self.context.normalize_path(m.group("path")) is_toolchain_header = False for d in toolchain_include_dirs: if path.startswith(d): is_toolchain_header = True break if not is_toolchain_header: self.context.get_file_arg(path, dependencies) except ValueError: # Path is on drive c:, build dir on drive d: pass def _get_clang_cl_toolchain_include_dirs(self, compiler): try: clang_cl_include_dirs_c = get_gcc_toolchain_include_dirs( compiler, self.context, "c") except subprocess_ex.CalledProcessError as e: logger.error("{}\nstderr:\n{}stdout:\n{}".format( e, e.stderr, e.stdout)) return [] try: clang_cl_include_dirs_cpp = get_gcc_toolchain_include_dirs( compiler, self.context, "c++") except subprocess_ex.CalledProcessError as e: logger.error("{}\nstderr:\n{}stdout:\n{}".format( e, e.stderr, e.stdout)) return [] include_dirs = set(clang_cl_include_dirs_c) include_dirs.update(clang_cl_include_dirs_cpp) return [self.context.normalize_path(d) for d in include_dirs] def parse(self, target): tokens = target.get("tokens") if not tokens: return target if not self.filename_re.match(tokens[0]): return target if len(tokens) < 2 or tokens[1] == ":": # skip warning message return target compiler = tokens.pop(0) compiler_nrm = self.context.platform.normalize_path(compiler) is_clang_cl = bool(self.clang_cl_re.match(compiler)) if compiler_nrm in self.context.path_aliases: compiler = self.context.path_aliases[compiler_nrm] if not is_clang_cl: namespace, _ = self.parser.parse_known_args( tokens, unknown_dest="compile_flags") else: namespace, _ = self.clang_cl_parser.parse_known_args( tokens, unknown_dest="compile_flags") if self.clang_cl_include_dirs is None: self.clang_cl_include_dirs = self._get_clang_cl_toolchain_include_dirs( compiler) logger.debug("clang-cl include dirs: %r" % self.clang_cl_include_dirs) lib_dirs = [] dependencies = [] if namespace.link_flags: link_flags = namespace.link_flags[0] namespace.link_flags = [] namespace, _ = self.link_parser.parse_known_args( link_flags[1:], namespace, unknown_dest=["link_flags"]) # Copied from msvc_link.py # TODO: unify msvc_cl.py, msvc_link.py, msvc_lib.py lib_dirs = list( map( lambda p: p[len("/LIBPATH:"):], filter_flags( self.ignore_link_flags_rxs, ["/LIBPATH:" + d for d in namespace.lib_dirs], ), )) for d in lib_dirs: d = self.context.normalize_path(d) relocatable_path = self.context.get_dir_arg(d) if relocatable_path.startswith("@"): # ignore non-relocatable lib dirs self.context.get_dir_arg(d, dependencies) if namespace.syntax_check_only: return target sources = [] src_info = {} objects = [] libs = [] for infile in namespace.infiles: ext = os.path.splitext(infile)[1].lower() language = None if ext in self.c_exts: language = "C" elif ext in self.cpp_exts: language = "C++" if language: src_deps = [] relocatable_path = self.context.get_file_arg( self.context.normalize_path(infile), src_deps) src_info[relocatable_path] = { "unmodified_path": infile, "dependencies": src_deps, } sources.append( get_source_file_reference(relocatable_path, language)) else: # Copied from msvc_link.py # TODO: unify msvc_cl.py, msvc_link.py, msvc_lib.py if os_ext.Windows.is_static_lib(infile): libs.append( self.context.get_lib_arg(infile, dependencies, lib_dirs)) else: paths = os_ext.Windows.get_obj_file_locations( infile, lib_dirs, self.context.working_dir) found = False for path in paths: if self.context.find_target( self.context.get_file_arg( path)) or os.path.exists(path): objects.append( self.context.get_file_arg(path, dependencies)) found = True break if not found: # System object file, treat it as a linker flag # https://docs.microsoft.com/en-us/cpp/c-runtime-library/link-options objects.append(infile) CompilerParser.process_namespace(self, namespace) include_dirs_not_relocatable = namespace.include_dirs include_dirs = list( map( lambda d: self.context.get_dir_arg( self.context.normalize_path(d), dependencies), include_dirs_not_relocatable, )) namespace.include_dirs = list(filter(lambda d: bool(d), include_dirs)) output = None if namespace.output[-1] in ["/", "\\"]: # output is a directory output_dir = self.context.normalize_path(namespace.output) else: output = self.context.normalize_path(namespace.output) output_dict = {} if output: output_dict[output] = sources else: assert namespace.compile_only # put .obj files in output_dir # separate .obj file for each source for s in sources: basename = os.path.basename(s["path"]) objfile = os.path.splitext(basename)[0] + ".obj" cur_output = os.path.join(output_dir, objfile) output_dict[cur_output] = [s] targets = [] for output, sources in output_dict.items(): deps_local = deepcopy(dependencies) original_sources = [] for s in sources: deps_local.extend(src_info[s["path"]]["dependencies"]) original_sources.append(src_info[s["path"]]["unmodified_path"]) self._add_implicit_dependencies( compiler, deps_local, namespace.compile_flags, include_dirs_not_relocatable, original_sources, self.context.working_dir, is_clang_cl=is_clang_cl, ) import_lib = None descr = {} if namespace.compile_only: module_type = ModuleTypes.object_lib elif namespace.is_dll: assert os_ext.Windows.is_shared_lib(output), output module_type = ModuleTypes.shared_lib descr = os_ext.Windows.parse_shared_lib(output) import_lib = self.context.get_output( os.path.splitext(output)[0] + ".lib", deps_local) else: module_type = ModuleTypes.executable descr = os_ext.Windows.parse_executable(output) output = self.context.get_output(output, deps_local) module_name = descr.get("module_name") name = descr.get("target_name") LinkerParser.process_namespace(self, namespace) targets.append( get_module_target( module_type, name, output, msvc_import_lib=import_lib, module_name=module_name, compile_flags=namespace.compile_flags, dependencies=deps_local, include_dirs=namespace.include_dirs, link_flags=namespace.link_flags, objects=objects, libs=libs, sources=sources, )) return targets
class Clang_Gcc(CompilerParser, LinkerParser): asm_exts = [".s", ".S"] c_exts = [".c", ".m"] cpp_exts = [".cc", ".cpp", ".cxx", ".mm"] source_exts = asm_exts + c_exts + cpp_exts class Mode: link = "link" assemble = "assemble" # don't link compile = "compile" # don't assemble & link preprocess = "preprocess" # don't compile, assemble & link class WholeArchive: enable = "--whole-archive" disable = "--no-whole-archive" class LinkType: static = "-Bstatic" dynamic = "-Bdynamic" priority = 7 @staticmethod def add_arguments(arg_parser): CompilerParser.add_arguments(arg_parser) LinkerParser.add_arguments(arg_parser) def __init__( self, context, project_version=None, platform=platform.system().lower(), ignore_link_flags=None, ignore_compile_flags=None, ): CompilerParser.__init__(self, context, ignore_compile_flags=ignore_compile_flags) LinkerParser.__init__(self, context, ignore_link_flags=ignore_link_flags) self.platform_name = platform self.platform = get_platform(platform) self.project_version = project_version self.program_re = self.platform.get_program_path_re( "cc", "c++", "clang", "clang++", "gcc", "g++") # Clang/GCC arguments # See https://linux.die.net/man/1/gcc # https://gcc.gnu.org/onlinedocs/gcc/Option-Summary.html # https://gcc.gnu.org/onlinedocs/gcc/Invoking-GCC.html # https://clang.llvm.org/docs/ClangCommandLineReference.html self.parser = ArgumentParserEx(prog="gcc") self.parser.set_defaults( mode=self.Mode.link, lib_dirs=[], libs=[], include_dirs=[], link_flags=[], compile_flags=[], ) # control flags self.parser.set(dest=None) self.parser.add_argument("-E", action="store_const", const=self.Mode.preprocess, dest="mode") self.parser.add_argument("-MD", action="store_true") self.parser.add_argument("-MF") self.parser.add_argument("-MMD", action="store_true") self.parser.add_argument("-MP", action="store_true") self.parser.add_argument("-MT") self.parser.add_argument("-S", action="store_const", const=self.Mode.compile, dest="mode") self.parser.add_argument("-c", action="store_const", const=self.Mode.assemble, dest="mode") self.parser.add_argument("-o", dest="output") self.parser.add_argument("-pipe", action="store_true") # linker flags self.parser.set(raw_dest="link_flags") self.parser.add_argument("-L", action="append", dest="lib_dirs", raw_dest=None) self.parser.add_argument("-Q", dest="driver_arguments") self.parser.add_argument(prefixes=["-Wl,"]) self.parser.add_argument("-all_load", action="store_true") self.parser.add_argument("-compatibility_version") self.parser.add_argument("-current_version") self.parser.add_argument("-version-info") self.parser.add_argument( "-dynamiclib", "-dynamic", action="store_true", raw_dest=None, dest="is_shared", ) self.parser.add_argument("-exported_symbols_list") self.parser.add_argument("-framework") self.parser.add_argument("-rpath") self.parser.add_argument("-headerpad_max_install_names", action="store_true") self.parser.add_argument("-no-undefined", action="store_true") self.parser.add_argument("-install_name") self.parser.add_argument(prefixes=["-l:", "-l"]) self.parser.add_argument("-nolibc", action="store_true") self.parser.add_argument("-nostdlib++", action="store_true") self.parser.add_argument("-no-canonical-prefixes", action="store_true") self.parser.add_argument("-nostdlib", action="store_true") self.parser.add_argument("-single_module", action="store_true") self.parser.add_argument("-pie", action="store_true") self.parser.add_argument("-rdynamic", action="store_true") self.parser.add_argument("-shared", action="store_true", raw_dest=None, dest="is_shared") self.parser.add_argument("-static", action="store_true") self.parser.add_argument("-static-libgcc", action="store_true") self.parser.add_argument("-static-libstdc++", action="store_true") self.parser.add_argument("-stdlib") self.parser.add_argument(flags=["-z"]) self.parser.add_argument("static_libs", nargs="*", args_regexp=re.compile(r"^(?!-Wl).+\.a$")) if platform == "linux": self.parser.add_argument( "shared_libs", nargs="*", args_regexp=re.compile(r"^(?!-Wl).+\.so$")) if platform == "darwin": self.parser.add_argument( "shared_libs", nargs="*", args_regexp=re.compile(r"^(?!-Wl).+\.dylib(?:\.\d+)*$"), ) # compiler flags self.parser.set(raw_dest="compile_flags") self.parser.add_argument("-D", raw_format=format_flag_gnu) self.parser.add_argument(prefixes=["-B"]) # Aurora stuff self.parser.add_argument("--target", "-target") self.parser.add_argument("--gcc-toolchain", "-gcc-toolchain") self.parser.add_argument("--sysroot") self.parser.add_argument("-I", action="append", dest="include_dirs", raw_dest=None) self.parser.add_argument(prefixes=["-O"]) self.parser.add_argument("-Q") self.parser.add_argument("-U", raw_format=format_flag_gnu) self.parser.add_argument(prefixes=["-W"], args_regexp=re.compile("^(?!l,)")) self.parser.add_argument(prefixes=["-Wa,"]) self.parser.add_argument("-arch") self.parser.add_argument("-w", action="store_true") self.parser.add_argument(prefixes=["-f"]) self.parser.add_argument(prefixes=["-W"]) self.parser.add_argument("-W", action="store_true") self.parser.add_argument("-pedantic", action="store_true") self.parser.add_argument(prefixes=["-std=", "--std="]) self.parser.add_argument("-x", dest="language_mode") self.parser.add_argument("-isystem", action="append") # compiler + linker flags # TODO: HACK: list support in raw_dest is temporary until a better solution comes up self.parser.set(raw_dest=["compile_flags", "link_flags"]) self.parser.add_argument("-g", nargs="?") self.parser.add_argument("-isysroot") self.parser.add_argument(prefixes=["-m"]) self.parser.add_argument("-pthread", action="store_true") self.parser.add_argument("infiles", nargs="*", dest="infiles", raw_dest=None) # Split compile flag list with multiple -arch flags into multiple lists, # each with only one -arch. # This fixes clang preprocessor error: cannot use 'dependencies' output with multiple -arch options def _split_compile_flags_for_multiarch(self, compile_flags): arch_args = { idx: arg for idx, arg in enumerate(compile_flags) if isinstance(arg, list) and arg[0] == "-arch" } if len(arch_args) > 1: compile_flags = [ arg for idx, arg in enumerate(compile_flags) if idx not in arch_args ] result = [compile_flags] for _ in range(0, len(arch_args) - 1): result.append(deepcopy(result[0])) compile_flags_iter = iter(result) for idx, arg in arch_args.items(): next(compile_flags_iter).insert(idx, arg) return result else: return [compile_flags] def _add_implicit_dependencies( self, compiler, dependencies, compile_flags, include_dirs, sources, target_platform, cwd, ): if not sources: return include_dir_args = ["-I" + d for d in include_dirs] sources = [s for s in sources] host_system = platform.system().lower() if compiler.find("clang") != -1: if host_system == "windows" and target_platform != host_system: # to avoid errors like: # * clang++.exe: error: unsupported option '-fPIC' for target 'x86_64-pc-windows-msvc' if target_platform == "linux": compile_flags = compile_flags + [ "--target=i686-pc-linux-gnu" ] if target_platform == "darwin": compile_flags = compile_flags + [ "--target=i686-apple-darwin10" ] implicit_dependencies = [] implicit_dependencies_set = set() for compile_flags in self._split_compile_flags_for_multiarch( compile_flags): cmd = ([compiler, "-M"] + flatten_list(compile_flags) + include_dir_args + sources) p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) stdout, stderr = p.communicate() if type(stdout) is not str: stdout = stdout.decode("utf-8", "replace") if type(stderr) is not str: stderr = stderr.decode("utf-8", "replace") retcode = p.poll() if retcode: cmd_str = " ".join(cmd) logger.error( "Command '{}' returned non-zero exit code {}\n{}".format( cmd_str, retcode, stderr)) for line in stdout.splitlines(): files = line.rstrip("\\").lstrip().split(" ") for f in files: if not f or f.endswith(":") or f in sources: continue if f not in implicit_dependencies_set: implicit_dependencies.append(f) implicit_dependencies_set.add(f) return [ self.context.get_file_arg(self.context.normalize_path(dep), dependencies) for dep in implicit_dependencies ] # filter libs from linker flags def _filter_lib_args(self, ns, dependencies): libs = [] remaining_flags = [] whole_archive = False static_only = False for arg in ns.link_flags: skip = False is_lib = False if not isinstance(arg, str): pass elif arg.endswith(self.WholeArchive.enable): whole_archive = True skip = True elif arg.endswith(self.WholeArchive.disable): whole_archive = False skip = True elif arg.endswith(self.LinkType.static): static_only = True skip = True elif arg.endswith(self.LinkType.dynamic): static_only = False skip = True else: if arg.startswith("-l"): arg = arg[2:] if arg[0] == ":": arg = self.context.get_file_arg(arg[1:], dependencies) else: arg = self.context.get_lib_arg(arg, dependencies, ns.lib_dirs, static_only=static_only) is_lib = True skip = True elif arg.startswith("-Wl,"): pass elif self.platform.is_static_lib( arg) or self.platform.is_shared_lib(arg): arg = self.context.get_file_arg(arg, dependencies) is_lib = True skip = True if is_lib: if whole_archive: libs.append({"gcc_whole_archive": True, "value": arg}) else: libs.append(arg) if not skip: remaining_flags.append(arg) ns.link_flags = remaining_flags ns.libs.extend(libs) _capture_next_arg = [ "-Wl,-soname", "-Wl,-compatibility_version", "-Wl,-current_version", "-Wl,-z", "-Wl,-version-script", "-Wl,--version-script", "-Wl,-rpath", "-Wl,--rpath", ] _file_arg = { "-exported_symbols_list": "", "-Wl,-version-script": "-Wl,", "-Wl,--version-script": "-Wl,", } _dir_arg = { "-Wl,-rpath": "-Wl,", "-Wl,--rpath": "-Wl,", "-rpath": "", } _arg_set = set(_file_arg.keys()) | set(_dir_arg.keys()) def _process_link_flags(self, flags, dependencies): result = [] it = iter(flags) for f in it: if isinstance(f, str): if f in self._capture_next_arg: f = [f, next(it)] else: for s in self._arg_set: if f.startswith(s): delim = f[len(s)] tmp = f.split(delim) if s in self._file_arg: tmp[-1] = self.context.get_file_arg( tmp[-1], dependencies) else: tmp[-1] = self.context.get_dir_arg( tmp[-1], dependencies) f = delim.join(tmp) continue if isinstance(f, list): prefix = None get_arg_func = None if f[0] in self._file_arg: prefix = self._file_arg[f[0]] get_arg_func = self.context.get_file_arg if f[0] in self._dir_arg: prefix = self._dir_arg[f[0]] get_arg_func = self.context.get_dir_arg if get_arg_func: if not f[-1].startswith(prefix): prefix = "" else: f[-1] = f[-1][len(prefix):] f[-1] = prefix + get_arg_func(f[-1], dependencies) result.append(f) return result def parse(self, target): tokens = target.get("tokens") if not tokens: return target # .strip('{}$') is a workaround for paths like ${LDCMD:-gcc} # TODO: parameter expansion parser: http://wiki.bash-hackers.org/syntax/pe if not self.program_re.match(tokens[0].strip("{}$")): return target gcc = tokens.pop(0) gcc_nrm = self.context.platform.normalize_path(gcc) if gcc_nrm in self.context.path_aliases: gcc = self.context.path_aliases[gcc_nrm] namespace, _ = self.parser.parse_known_args( tokens, unknown_dest=["compile_flags", "link_flags"]) if namespace.mode not in [self.Mode.link, self.Mode.assemble]: return target if namespace.mode != self.Mode.link: namespace.link_flags = [] namespace.lib_dirs = [] namespace.libs = [] dependencies = [] namespace.lib_dirs = list( map( lambda p: p[2:], filter_flags(self.ignore_link_flags_rxs, ["-L" + d for d in namespace.lib_dirs]), )) LinkerParser.process_namespace(self, namespace) self._filter_lib_args(namespace, dependencies) namespace.link_flags = self._process_link_flags( namespace.link_flags, dependencies) original_srcs = [] objects = [] sources = [] for infile in namespace.infiles: infile = self.context.normalize_path(infile) assert not (self.platform.is_static_lib(infile) or self.platform.is_shared_lib(infile)), infile language = None ext = os.path.splitext(infile)[1] if ext in self.c_exts: language = "C" elif ext in self.cpp_exts: language = "C++" elif ext in self.asm_exts: language = "GASM" if language is None: objects.append(self.context.get_file_arg(infile, dependencies)) else: original_srcs.append(infile) sources.append( get_source_file_reference( self.context.get_file_arg(infile, dependencies), language)) if not sources: # Ignore compiler flags if no source files specified namespace.compile_flags = [] namespace.include_dirs = [] CompilerParser.process_namespace(self, namespace) include_dirs_not_relocatable = namespace.include_dirs namespace.include_dirs = [ self.context.get_dir_arg(self.context.normalize_path(d), dependencies) for d in include_dirs_not_relocatable ] if namespace.mode == self.Mode.assemble: if namespace.output is None: namespace.output = (os.path.basename( os.path.splitext(namespace.infiles[0])[0]) + ".o") module_type = ModuleTypes.object_lib name = None module_name = None version = None elif namespace.mode == self.Mode.link: if namespace.output is None: namespace.output = "a.out" descr = None if namespace.is_shared: descr = self.platform.parse_shared_lib(namespace.output) module_type = ModuleTypes.shared_lib else: descr = self.platform.parse_executable(namespace.output) module_type = ModuleTypes.executable if descr: module_name = descr["module_name"] name = descr["target_name"] version = descr["version"] or self.project_version else: module_name = None name = None version = self.project_version namespace.output = self.context.normalize_path(namespace.output) self._add_implicit_dependencies( gcc, dependencies, namespace.compile_flags, include_dirs_not_relocatable, original_srcs, self.platform_name, self.context.working_dir, ) namespace.output = self.context.get_output(namespace.output, dependencies) target = get_module_target( module_type, name, namespace.output, compile_flags=namespace.compile_flags, dependencies=dependencies, module_name=module_name, include_dirs=namespace.include_dirs, link_flags=namespace.link_flags, libs=namespace.libs, objects=objects, sources=sources, version=version, ) return target
class MsvcRc(CompilerParser): filename_re = os_ext.Windows.get_program_path_re("rc") priority = 7 def __init__(self, context, ignore_compile_flags=None): CompilerParser.__init__( self, context, ignore_compile_flags=ignore_compile_flags ) # Visual Studio rc.exe arguments # https://docs.microsoft.com/en-us/windows/desktop/menurc/using-rc-the-rc-command-line- self.parser = ArgumentParserEx(prefix_chars="-/") self.parser.set_defaults(compile_flags=[], link_flags=[], include_dirs=[]) # TODO: publish all meaningful flags self.parser.set(ignore_case=True, dest=None, raw_dest="compile_flags") self.parser.add_argument("/d", raw_format=ReplacePrefixArg("-D")) self.parser.add_argument("/u", raw_format=ReplacePrefixArg("-U")) self.parser.add_argument("/ln", raw_format=ReplacePrefixArg("/ln")) self.parser.add_argument("/l", raw_format=ReplacePrefixArg("/l")) self.parser.add_argument("/gn", raw_format=ReplacePrefixArg("/gn")) self.parser.add_argument("/g", raw_format=ReplacePrefixArg("/g")) self.parser.add_argument( "/i", action="append", dest="include_dirs", raw_dest=None ) self.parser.add_argument("/nologo", action="store_true") self.parser.add_argument( "/w", action="store_true", raw_format=ReplacePrefixArg("/w") ) self.parser.add_argument("/fo", prefix=True, dest="output", raw_dest=None) self.parser.add_argument("infiles", dest="infiles", nargs="*", raw_dest=None) include_re = re.compile(r'\s*#\s*include\s+["<](?P<path>[^\s][^<>"]*)[>"]') dependency_re = re.compile( r'\s*[_A-Za-z0-9]+\s+(?:BITMAP|CURSOR|FONT|HTML|ICON)\s+(?:DISCARDABLE\s+)?["<](?P<path>[^\s][^<>"]*)[>"]' ) def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.filename_re.match(tokens[0]): return target if len(tokens) < 2 or tokens[1] == ":": # skip warning message return target tokens.pop(0) namespace = self.parser.parse_args(tokens) dependencies = [] self.process_namespace(namespace) # rc.exe doesn't support /showIncludes or anything similar add_included_dependencies( self.context, self.include_re, dependencies, namespace.include_dirs, namespace.infiles, self.context.working_dir, include_cwd=True, dependency_re=self.dependency_re, ) sources = [] for infile in namespace.infiles: sources.append( get_source_file_reference( self.context.get_file_arg( self.context.normalize_path(infile), dependencies ), "RC", ) ) output = self.context.get_output(self.context.normalize_path(namespace.output)) include_dirs = [] for dir in namespace.include_dirs: # include dir may not exist, it's normal dir = self.context.normalize_path(dir) arg = self.context.get_dir_arg(dir, dependencies) if arg: include_dirs.append(arg) return get_module_target( ModuleTypes.object_lib, None, output, include_dirs=include_dirs, compile_flags=namespace.compile_flags, dependencies=dependencies, sources=sources, )
class MsvcLink(LinkerParser): filename_re = os_ext.Windows.get_program_path_re("link") lld_link_re = os_ext.Windows.get_program_path_re("lld-link") priority = 7 def __init__(self, context, project_version=None, ignore_link_flags=None): LinkerParser.__init__(self, context, ignore_link_flags=ignore_link_flags) self.project_version = project_version # Visual Studio link.exe arguments # https://docs.microsoft.com/en-us/cpp/build/reference/linker-options?view=vs-2017 self.parser = ArgumentParserEx(prefix_chars="/-") self.parser.set_defaults( compile_flags=[], link_flags=[], include_dirs=[], infiles=[], lib_dirs=[] ) # TODO: publish all meaningful flags self.parser.set( ignore_case=True, action="msvc_flag_with_value", raw_dest="link_flags", dest=None, ) # TODO: handle /wholearchive self.parser.add_argument("/base", action="msvc_flag_with_value") self.parser.add_argument("/debug") self.parser.add_argument("/debug", action="store_true") self.parser.add_argument("/fixed", action="msvc_flag", msvc_false_suffix=":no") self.parser.add_argument("/delayload", action="msvc_flag_with_value") self.parser.add_argument("/def") self.parser.add_argument( "/dll", action="store_true", dest="is_dll", raw_dest=None ) self.parser.add_argument( "/dynamicbase", action="msvc_flag", msvc_false_suffix=":no" ) self.parser.add_argument("/errorreport") self.parser.add_argument("/fastfail", action="store_true") self.parser.add_argument("/guard", action="msvc_flag_with_value") # Ignore Specific Warnings self.parser.add_argument("/ignore", action="msvc_flag_with_value") self.parser.add_argument("/implib", dest="implib", raw_dest=None) self.parser.add_argument( "/incremental", action="msvc_flag", msvc_false_suffix=":no" ) self.parser.add_argument( "/largeaddressaware", action="msvc_flag", msvc_false_suffix=":no" ) self.parser.add_argument( "/libpath", append=True, dest="lib_dirs", raw_dest=None ) self.parser.add_argument("/ltcg") self.parser.add_argument("/ltcg", action="store_true") self.parser.add_argument("/machine") self.parser.add_argument("/manifest") self.parser.add_argument("/manifest", action="store_true") self.parser.add_argument( "/manifestinput", raw_dest=None, dest="manifest_files", append=True ) self.parser.add_argument( "/manifestfile", raw_dest=None, dest="manifest_files", append=True ) self.parser.add_argument("/manifestuac") self.parser.add_argument("/manifestuac", action="store_true") self.parser.add_argument("/map") self.parser.add_argument("/map", action="store_true") self.parser.add_argument("/mapinfo") self.parser.add_argument("/noentry", action="store_true") self.parser.add_argument("/nologo", action="store_true") self.parser.add_argument("/natvis", action="msvc_flag_with_value") self.parser.add_argument( "/nxcompat", action="msvc_flag", msvc_false_suffix=":no" ) self.parser.add_argument("/opt", append=True) self.parser.add_argument("/out", dest="output", raw_dest=None) self.parser.add_argument("/pdb") self.parser.add_argument("/pdbaltpath") self.parser.add_argument("/profile", action="store_true") self.parser.add_argument("/safeseh", action="store_true") self.parser.add_argument("/stack") self.parser.add_argument("/subsystem") self.parser.add_argument("/timestamp") self.parser.add_argument("/tlbid") self.parser.add_argument("/version") self.parser.add_argument("/wholearchive", append=True) self.parser.add_argument("/wholearchive", action="store_true") self.parser.add_argument("/WX", action="msvc_flag", msvc_false_suffix=":no") self.parser.set(action=None) self.parser.add_argument("infiles", nargs="*", raw_dest=None, dest="infiles") # lld-link.exe arguments # Cannot find documentation online self.lld_link_parser = deepcopy(self.parser) self.lld_link_parser.add_argument("/llvmlibthin", action="store_true") self.lld_link_parser.set(prefix_chars="-") self.lld_link_parser.add_argument("--color-diagnostics", action="store_true") def _process_link_flags(self, flags, dependencies): for idx, flag in enumerate(flags): flag_lower = flag.lower() if flag_lower[0] == "/": flag_lower = "-" + flag_lower[1:] if flag_lower.startswith("-def:"): flags[idx] = flag[:5] + self.context.get_file_arg( flag[5:], dependencies ) return flags def parse(self, target): tokens = target.get("tokens") or [] if not tokens: return target if not self.filename_re.match(tokens[0]): return target else: if is_lib_shim(tokens): return target if len(tokens) < 2 or tokens[1] == ":": # skip warning message return target is_lld_link = bool(self.lld_link_re.match(tokens[0])) tokens.pop(0) if is_lld_link: namespace, _ = self.lld_link_parser.parse_known_args( tokens, unknown_dest=["compile_flags", "link_flags"] ) else: namespace, _ = self.parser.parse_known_args( tokens, unknown_dest=["compile_flags", "link_flags"] ) dependencies = [] lib_dirs = list( map( lambda p: p[len("/LIBPATH:"):], filter_flags( self.ignore_link_flags_rxs, ["/LIBPATH:" + d for d in namespace.lib_dirs], ), ) ) for d in lib_dirs: d = self.context.normalize_path(d) relocatable_path = self.context.get_dir_arg(d) if relocatable_path.startswith("@"): # ignore non-relocatable lib dirs self.context.get_dir_arg(d, dependencies) objects = [] libs = [] for infile in namespace.infiles: if os_ext.Windows.is_static_lib(infile): libs.append(self.context.get_lib_arg(infile, dependencies, lib_dirs)) else: paths = os_ext.Windows.get_obj_file_locations( infile, lib_dirs, self.context.working_dir ) found = False for path in paths: if self.context.find_target( self.context.get_file_arg(path) ) or os.path.exists(path): objects.append(self.context.get_file_arg(path, dependencies)) found = True break if not found: # System object file, treat it as a linker flag # https://docs.microsoft.com/en-us/cpp/c-runtime-library/link-options objects.append(infile) namespace.link_flags = self._process_link_flags( namespace.link_flags, dependencies ) sources = [] for manifest in vars(namespace).get("manifest_files") or []: sources.append( get_source_file_reference( self.context.get_file_arg( self.context.normalize_path(manifest), dependencies ) ) ) import_lib = None if vars(namespace).get("implib"): import_lib = self.context.get_output(namespace.implib, dependencies) output = self.context.normalize_path(namespace.output) if os_ext.Windows.is_shared_lib(output): # DLL file is created if: # 1. /DLL flag was specified, or # 2. LIBRARY statement is present in provided .def file # TODO: validate these conditions here descr = os_ext.Windows.parse_shared_lib(output) module_type = ModuleTypes.shared_lib if not import_lib: import_lib = self.context.get_output( os.path.splitext(namespace.output)[0] + ".lib", dependencies ) else: descr = os_ext.Windows.parse_executable(output) module_type = ModuleTypes.executable module_name = descr["module_name"] name = descr["target_name"] version = descr["version"] or self.project_version output = self.context.get_output(output, dependencies) self.process_namespace(namespace) return get_module_target( module_type, name, output, msvc_import_lib=import_lib, dependencies=dependencies, module_name=module_name, objects=objects, libs=libs, link_flags=namespace.link_flags, sources=sources, version=version, )