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)
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_unknown_args_dest(self): parser = ArgumentParserEx() parser.add_argument("--flag", action="store_true", raw_dest="args") parser.add_argument("--key", raw_dest="args") parser.add_argument("infile", raw_dest="args") ns, unknown = parser.parse_known_args(["--flag"], unknown_dest="args") self.assertListEqual(["--flag"], ns.args) self.assertListEqual([], unknown) ns, unknown = parser.parse_known_args( ["--flag", "--unknownflag", "--key=value"], unknown_dest="args") self.assertListEqual(["--flag", "--unknownflag", "--key=value"], ns.args) self.assertListEqual(["--unknownflag"], unknown) ns, unknown = parser.parse_known_args( ["--flag", "--unknownflag", "a.txt"], unknown_dest="args") self.assertListEqual(["--flag", "--unknownflag", "a.txt"], ns.args) self.assertListEqual(["--unknownflag"], unknown) ns, unknown = parser.parse_known_args( ["--flag", "a.txt", "--unknownflag"], unknown_dest="args") self.assertListEqual(["--flag", "a.txt", "--unknownflag"], ns.args) self.assertListEqual(["--unknownflag"], unknown) ns, unknown = parser.parse_known_args(["--unknown"], unknown_dest=["args", "extra"]) self.assertListEqual(["--unknown"], ns.args) self.assertListEqual(["--unknown"], ns.extra) self.assertListEqual(["--unknown"], unknown)
def test_raw_dest(self): parser = ArgumentParserEx() parser.add_argument("-arg1", action="store_true", raw_dest="args") parser.add_argument("--arg2", "-f", dest=None, raw_dest="args") parser.add_argument("file", raw_dest="args") parser.add_argument("files", nargs="*", dest=None, raw_dest="args") parser.add_argument("-D", dest=None, raw_dest="args", raw_format="".join) ns = parser.parse_args(["--arg2", "1", "2"]) self.assertListEqual([["--arg2", "1"], "2"], ns.args) ns = parser.parse_args(["--arg2=1", "1", "-arg1", "2", "3"]) self.assertListEqual(["--arg2=1", "1", "-arg1", "2", "3"], ns.args) ns, remaining = parser.parse_known_args( ["1", "-fvalue", "2", "-unknown"]) self.assertListEqual(["-unknown"], remaining) self.assertListEqual(["1", "-fvalue", "2"], ns.args) ns, remaining = parser.parse_known_args( ["1", "-unknown", "-fvalue", "2"]) self.assertListEqual(["-unknown"], remaining) self.assertListEqual(["1", "-fvalue", "2"], ns.args) ns = parser.parse_args(["1", "-D", "2", "3", "-D4"]) self.assertListEqual(["1", "-D2", "3", "-D4"], ns.args)
def test_action_store_false(self): parser = ArgumentParserEx() parser.add_argument("-a", action="store_false") ns, remaining = parser.parse_known_args([]) self.assertListEqual([], remaining) self.assertTrue(ns.a) ns, remaining = parser.parse_known_args(["-a"]) self.assertListEqual([], remaining) self.assertFalse(ns.a)
def test_action_store_positional(self): parser = ArgumentParserEx() parser.add_argument("attr", action="store") ns, remaining = parser.parse_known_args(["-val"]) self.assertListEqual(["-val"], remaining) self.assertIsNone(vars(ns).get("attr")) ns, remaining = parser.parse_known_args(["val"]) self.assertListEqual([], remaining) self.assertEqual("val", ns.attr)
def test_action_append_const(self): parser = ArgumentParserEx() parser.add_argument("-a", action="append_const", const="v") ns, remaining = parser.parse_known_args(["-a", "1", "-a2", "-a"]) self.assertListEqual(["1", "-a2"], remaining) self.assertEqual(["v", "v"], ns.a)
def test_mutually_exclusive_group(self): self.skipTest("not implemented yet") parser = ArgumentParserEx() group = parser.add_mutually_exclusive_group() group.add_argument("-a", action="store_const", const="a", dest="mode") group.add_argument("-b", action="store_const", const="b", dest="mode") ns, remaining = parser.parse_known_args(["-ab"]) self.assertListEqual(["-ab"], remaining) self.assertIsNone(vars(ns).get("mode")) ns, remaining = parser.parse_known_args(["-a", "-b"]) self.assertListEqual(["-b"], remaining) self.assertEqual("a", ns.mode) ns = parser.parse_args(["-b"]) self.assertEqual("b", ns.mode)
def test_action_default(self): parser = ArgumentParserEx() parser.add_argument("attr1") parser.add_argument("--attr2") ns, remaining = parser.parse_known_args(["--attr2", "val1", "val2"]) self.assertListEqual([], remaining) self.assertEqual("val2", ns.attr1) self.assertEqual("val1", ns.attr2)
def test_action_store_const(self): parser = ArgumentParserEx() parser.add_argument("-a", action="store_const", const="a") ns, remaining = parser.parse_known_args([]) self.assertListEqual([], remaining) self.assertIsNone(vars(ns).get("a")) ns, remaining = parser.parse_known_args(["-a"]) self.assertListEqual([], remaining) self.assertEqual("a", ns.a) parser = ArgumentParserEx() parser.add_argument("-a", action="store_const", const="a", default="b") ns, remaining = parser.parse_known_args([]) self.assertListEqual([], remaining) self.assertEqual("b", ns.a)
def test_action_append(self): parser = ArgumentParserEx() parser.add_argument("-a", action="append") ns, remaining = parser.parse_known_args(["-a1", "-a2"]) self.assertListEqual([], remaining) self.assertEqual(["1", "2"], ns.a) ns, remaining = parser.parse_known_args(["-a", "2", "-a", "1"]) self.assertListEqual([], remaining) self.assertEqual(["2", "1"], ns.a) parser.add_argument("b", action="append") ns, remaining = parser.parse_known_args(["-a", "1", "2", "-a", "3"]) self.assertListEqual([], remaining) self.assertEqual(["1", "3"], ns.a) self.assertEqual(["2"], ns.b)
def test_dest_is_none(self): parser = ArgumentParserEx() parser.add_argument("-opt", dest=None, required=True) parser.add_argument("pos", dest=None) ns = parser.parse_args(["-opt", "1", "2"]) self.assertIsNone(vars(ns).get("opt")) self.assertIsNone(vars(ns).get("pos")) ns, remaining = parser.parse_known_args(["-opt"]) self.assertListEqual(["-opt"], remaining) ns, remaining = parser.parse_known_args(["1"]) self.assertListEqual([], remaining) self.assertIsNone(vars(ns).get("opt")) self.assertIsNone(vars(ns).get("pos")) self.assertRaisesRegexp(ValueError, "Required attribute not found", parser.parse_args, ["1"])
def test_shortening(self): parser = ArgumentParserEx() parser.add_argument("--abc") parser.add_argument("--a") # standard ArgumentParser will parse --ab as --abc ns, remaining = parser.parse_known_args( ["--abc", "1", "--ab", "2", "--a", "3"]) self.assertListEqual(["--ab", "2"], remaining) self.assertEqual("1", ns.abc) self.assertEqual("3", ns.a)
def test_choices(self): parser = ArgumentParserEx() parser.add_argument("--a1", choices=["abc", "ab", "a"]) parser.add_argument("a2", choices=["def", "ef"]) ns, remaining = parser.parse_known_args(["--a1=abc", "de"]) self.assertListEqual(["de"], remaining) self.assertEqual("abc", ns.a1) self.assertIsNone(vars(ns).get("a2")) ns, remaining = parser.parse_known_args(["--a1", "abc", "de"]) self.assertListEqual(["de"], remaining) self.assertEqual("abc", ns.a1) self.assertIsNone(vars(ns).get("a2")) ns, remaining = parser.parse_known_args(["--a1", "b", "def"]) self.assertListEqual(["--a1", "b"], remaining) self.assertIsNone(vars(ns).get("a1")) self.assertEqual("def", ns.a2)
def test_values_starting_with_prefix(self): # standard ArgumentParser does not accept values starting with prefix_chars parser = ArgumentParserEx() parser.add_argument("-Xclang") ns = parser.parse_args(["-Xclang", "-no-color-output"]) self.assertEqual("-no-color-output", ns.Xclang) parser = ArgumentParserEx() parser.add_argument("-f", nargs="2") ns = parser.parse_args(["-f", "-1", "-2"]) self.assertListEqual(["-1", "-2"], ns.f) parser = ArgumentParserEx() parser.add_argument("-f", nargs="2", action="append") parser.add_argument("--enable", action="store_true") ns = parser.parse_args(["-f", "-1", "-2"]) self.assertListEqual([["-1", "-2"]], ns.f) parser.add_argument("-f", nargs="2", action="append") ns = parser.parse_args(["-f", "-1", "-2", "-f", "-3", "-4"]) self.assertListEqual([["-1", "-2"], ["-3", "-4"]], ns.f) ns, remaining = parser.parse_known_args(["-f", "-1", "-2", "-f", "-3"]) self.assertListEqual(["-f", "-3"], remaining) self.assertListEqual([["-1", "-2"]], ns.f) ns, remaining = parser.parse_known_args( ["-f", "-1", "-2", "-f", "-3", "--enable", "-4"]) self.assertListEqual(["-4"], remaining) self.assertListEqual([["-1", "-2"], ["-3", "--enable"]], ns.f) self.assertFalse(ns.enable) self.assertRaisesRegexp( ValueError, "Unparsed tokens", parser.parse_args, ["-f", "-1", "-2", "-f", "-3"], ) parser = ArgumentParserEx() parser.add_argument("f", nargs="*") ns, remaining = parser.parse_known_args(["-1"]) self.assertListEqual(["-1"], remaining) ns, remaining = parser.parse_known_args(["1", "2", "-3"]) self.assertListEqual(["-3"], remaining) self.assertListEqual(["1", "2"], ns.f) ns, remaining = parser.parse_known_args(["1", "-2", "3"]) self.assertListEqual(["-2"], remaining) self.assertListEqual(["1", "3"], ns.f) parser = ArgumentParserEx() parser.add_argument("f", nargs="+") ns, remaining = parser.parse_known_args(["-1", "-2"]) self.assertListEqual(["-1", "-2"], remaining) parser = ArgumentParserEx() parser.add_argument("-f", nargs="+") ns, remaining = parser.parse_known_args(["-f", "-1", "-2"]) self.assertListEqual(["-2"], remaining) self.assertListEqual(["-1"], ns.f)
def test_action_store_override(self): parser = ArgumentParserEx() parser.add_argument("attr1", action="store") parser.add_argument("--attr2", action="store") ns, remaining = parser.parse_known_args(["val1", "--attr2", "val2"]) self.assertListEqual([], remaining) self.assertEqual("val1", ns.attr1) self.assertEqual("val2", ns.attr2) ns, remaining = parser.parse_known_args(["--attr2", "val2", "val1"]) self.assertListEqual([], remaining) self.assertEqual("val1", ns.attr1) self.assertEqual("val2", ns.attr2) ns, remaining = parser.parse_known_args( ["--attr2", "val1", "val2", "--attr2", "val3"]) self.assertListEqual([], remaining) self.assertEqual("val2", ns.attr1) self.assertEqual("val3", ns.attr2)
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)
def test_positionals_do_not_consume_optionals(self): parser = ArgumentParserEx() parser.add_argument("positional") parser.add_argument("-optional", action="store_true") ns, remaining = parser.parse_known_args(["-optional"]) self.assertListEqual([], remaining) self.assertTrue(ns.optional) self.assertIsNone(ns.positional) parser = ArgumentParserEx() parser.add_argument("positional", nargs="+") parser.add_argument("-optional", action="store_true") ns, remaining = parser.parse_known_args(["-optional"]) self.assertListEqual([], remaining) self.assertTrue(ns.optional) self.assertIsNone(ns.positional) parser = ArgumentParserEx() parser.add_argument("positional", nargs="*") parser.add_argument("-optional", action="store_true") ns, remaining = parser.parse_known_args(["-optional"]) self.assertListEqual([], remaining) self.assertTrue(ns.optional) self.assertEqual([], ns.positional)
def test_conditional_arguments(self): self.skipTest("not implemented yet") parser = ArgumentParserEx() arg = parser.add_argument("-a") parser.add_argument("-b", after=[arg]) ns = parser.parse_args(["-a", "1", "-b", "2"]) self.assertEqual("1", ns.a) self.assertEqual("2", ns.b) ns = parser.parse_args(["-a", "1"]) self.assertEqual("1", ns.a) ns, remaining = parser.parse_known_args(["-b", "1"]) self.assertListEqual(["-b", "1"], remaining) self.assertIsNone(vars(ns).get("b"))
def test_msvc_flag(self): parser = ArgumentParserEx(prefix_chars="/") parser.add_argument("/GR", action="msvc_flag", dest="rtti") parser.add_argument( "/INCREMENTAL", action="msvc_flag", msvc_false_suffix=":NO", dest="incremental", ) parser.add_argument( "/flag", action="msvc_flag", msvc_true_suffix=":on", msvc_false_suffix=":off", ignore_case=True, ) ns = parser.parse_args(["/GR-"]) self.assertFalse(ns.rtti) ns = parser.parse_args(["/GR"]) self.assertTrue(ns.rtti) ns = parser.parse_args(["/INCREMENTAL"]) self.assertTrue(ns.incremental) ns = parser.parse_args(["/INCREMENTAL:NO"]) self.assertFalse(ns.incremental) ns = parser.parse_args(["/FLAG:ON"]) self.assertTrue(ns.flag) ns = parser.parse_args(["/flag:off"]) self.assertFalse(ns.flag) ns = parser.parse_args([]) self.assertIsNone(vars(ns).get("flag")) self.assertIsNone(vars(ns).get("rtti")) self.assertIsNone(vars(ns).get("incremental")) ns, remaining = parser.parse_known_args(["/flag:unknown"]) self.assertListEqual(["/flag:unknown"], remaining) self.assertIsNone(vars(ns).get("flag")) parser.set_defaults(flag=False) ns = parser.parse_args([]) self.assertFalse(ns.flag)
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, )
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 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, )
def test_nargs(self): parser = ArgumentParserEx() parser.add_argument("--attr2", action="store", nargs="1") parser.add_argument("attr1", action="store", nargs="1") ns, remaining = parser.parse_known_args( ["--attr2", "val1", "val2", "--attr2", "val3"]) self.assertListEqual([], remaining) self.assertEqual(["val2"], ns.attr1) self.assertEqual(["val3"], ns.attr2) parser = ArgumentParserEx() parser.add_argument("--attr2", action="store", nargs="2") parser.add_argument("attr1", action="store", nargs="2") ns, remaining = parser.parse_known_args( ["--attr2", "val1", "val2", "val3", "val4"]) self.assertListEqual([], remaining) self.assertEqual(["val3", "val4"], ns.attr1) self.assertEqual(["val1", "val2"], ns.attr2) ns, remaining = parser.parse_known_args( ["val1", "val2", "--attr2", "val3", "val4", "val5"]) self.assertListEqual(["val5"], remaining) self.assertEqual(["val1", "val2"], ns.attr1) self.assertEqual(["val3", "val4"], ns.attr2) ns, remaining = parser.parse_known_args(["val1", "--attr2", "val2"]) self.assertListEqual(["--attr2"], remaining) self.assertEqual(["val1", "val2"], ns.attr1) self.assertIsNone(vars(ns).get("attr2")) ns, remaining = parser.parse_known_args( ["val1", "--attr2", "val2", "val3", "val4"]) self.assertListEqual([], remaining) self.assertEqual(["val1", "val4"], ns.attr1) self.assertEqual(["val2", "val3"], ns.attr2) ns, remaining = parser.parse_known_args( ["val1", "--attr2", "val2", "val3"]) self.assertListEqual([], remaining) self.assertEqual(["val1"], ns.attr1) self.assertEqual(["val2", "val3"], ns.attr2) parser = ArgumentParserEx() parser.add_argument("attr1", action="store", nargs="?") parser.add_argument("--attr2", action="store", nargs="?") ns, remaining = parser.parse_known_args(["val1", "--attr2"]) self.assertListEqual([], remaining) self.assertEqual(["val1"], ns.attr1) self.assertEqual([], ns.attr2) ns, remaining = parser.parse_known_args(["--attr2", "val1"]) self.assertListEqual([], remaining) self.assertEqual(None, ns.attr1) self.assertEqual(["val1"], ns.attr2) parser = ArgumentParserEx() parser.add_argument("--attr2", action="store", nargs="+") parser.add_argument("attr1", action="store", nargs="+") ns, remaining = parser.parse_known_args(["val1", "val2", "--attr2"]) self.assertListEqual(["--attr2"], remaining) self.assertEqual(["val1", "val2"], ns.attr1) self.assertIsNone(vars(ns).get("attr2")) ns, remaining = parser.parse_known_args(["--attr2", "val1", "val2"]) self.assertListEqual([], remaining) self.assertIsNone(vars(ns).get("attr1")) self.assertEqual(["val1", "val2"], ns.attr2) ns, remaining = parser.parse_known_args( ["val1", "val2", "--attr2", "val3"]) self.assertListEqual([], remaining) self.assertEqual(["val1", "val2"], ns.attr1) self.assertEqual(["val3"], ns.attr2) parser = ArgumentParserEx() parser.add_argument("attr1", action="store", nargs="*") parser.add_argument("--attr2", action="store", nargs="*") ns, remaining = parser.parse_known_args(["val1", "val2", "--attr2"]) self.assertListEqual([], remaining) self.assertEqual(["val1", "val2"], ns.attr1) self.assertEqual([], ns.attr2) ns, remaining = parser.parse_known_args(["--attr2", "val1", "val2"]) self.assertListEqual([], remaining) self.assertEqual([], ns.attr1) self.assertEqual(["val1", "val2"], ns.attr2) ns, remaining = parser.parse_known_args(["val1", "--attr2", "val2"]) self.assertListEqual([], remaining) self.assertEqual(["val1"], ns.attr1) self.assertEqual(["val2"], ns.attr2) parser = ArgumentParserEx() parser.add_argument("-C", action="append", nargs="?") ns = parser.parse_args(["-C", "a1", "-C"]) self.assertListEqual([["a1"], []], ns.C) parser = ArgumentParserEx() parser.add_argument("-C", action="append", nargs="*") ns = parser.parse_args(["-C", "a1", "-C", "b1", "b2", "-C"]) self.assertListEqual([["a1"], ["b1", "b2"], []], ns.C) parser = ArgumentParserEx() parser.add_argument("-C", action="append", nargs="+") ns = parser.parse_args(["-C", "a1", "-C", "b1", "b2"]) self.assertListEqual([["a1"], ["b1", "b2"]], ns.C) parser = ArgumentParserEx() parser.add_argument("C", action="append", nargs="?") ns = parser.parse_args(["1"]) self.assertListEqual([["1"]], ns.C) ns, remaining = parser.parse_known_args(["1", "2"]) self.assertListEqual(["2"], remaining) self.assertListEqual([["1"]], ns.C) ns = parser.parse_args([]) self.assertIsNone(ns.C) parser = ArgumentParserEx() parser.add_argument("C", action="append", nargs="*") ns = parser.parse_args(["1"]) self.assertListEqual([["1"]], ns.C) ns = parser.parse_args(["1", "2"]) self.assertListEqual([["1", "2"]], ns.C) ns, _remaining = parser.parse_known_args(["1", "2", "-a", "3"]) self.assertListEqual([["1", "2"], ["3"]], ns.C) ns, _remaining = parser.parse_known_args(["1", "2", "-a"]) self.assertListEqual([["1", "2"]], ns.C) # Check that empty list does not raise an Exception parser.parse_args([]) parser = ArgumentParserEx() parser.add_argument("C", action="append", nargs="+") ns = parser.parse_args(["1"]) self.assertListEqual([["1"]], ns.C) ns = parser.parse_args(["1", "2"]) self.assertListEqual([["1", "2"]], ns.C) ns, _remaining = parser.parse_known_args(["1", "2", "-a", "3"]) self.assertListEqual([["1", "2"], ["3"]], ns.C) ns, _remaining = parser.parse_known_args(["1", "2", "-a"]) self.assertListEqual([["1", "2"]], ns.C) self.assertRaisesRegexp(ValueError, "Required attribute not found", parser.parse_args, [])
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
def test_multicharacter_short_flag_disables_suffix_value(self): parser = ArgumentParserEx() parser.add_argument("-Xclang") ns, remaining = parser.parse_known_args(["-Xclangvalue"]) self.assertListEqual(["-Xclangvalue"], remaining) self.assertIsNone(vars(ns).get("Xclang"))