Ejemplo n.º 1
0
 def test_correct_list_order(self):
     parser = ArgumentParserEx(prefix_chars="-/")
     parser.set_defaults(definitions=[])
     parser.add_argument("/c", action="store_true", dest="compile_only")
     parser.add_argument("-D", action="append", dest="definitions")
     parser.add_argument("-I", action="append", dest="include_dirs")
     parser.add_argument("-U", action="append", dest="definitions")
     parser.add_argument("files", nargs="*")
     ns = parser.parse_args(
         ["/c", "-D1", "/U", "2", "/D3", "-I.", "-U4", "a.cpp"])
     self.assertListEqual(["1", "2", "3", "4"], ns.definitions)
Ejemplo n.º 2
0
    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)
Ejemplo n.º 3
0
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,
        )
Ejemplo n.º 4
0
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,
        )
Ejemplo n.º 5
0
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,
        )
Ejemplo n.º 6
0
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,
        )
Ejemplo n.º 7
0
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
Ejemplo n.º 8
0
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
Ejemplo n.º 9
0
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,
        )
Ejemplo n.º 10
0
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,
        )