# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
# vim: set filetype=python:
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.


@template
@imports("textwrap")
@imports(_from="mozbuild.configure", _import="SandboxDependsFunction")
def compiler_class(compiler, host_or_target):
    is_target = host_or_target is target

    class Compiler(SandboxDependsFunction):
        def _try_compile_or_link(
            self,
            includes,
            body,
            flags,
            ldflags,
            check_msg,
            when,
            onerror,
        ):
            if ldflags is None:

                @depends(dependable(flags))
                def flags(flags):
                    flags = list(flags or [])
                    flags.append("-c")
                    return flags

            else:

                @depends(compiler, dependable(ldflags), dependable(flags), when=when)
                def flags(compiler, ldflags, flags):
                    if compiler.type == "clang-cl":
                        configure_error(
                            "checking linker flags for clang-cl is not supported yet"
                        )

                    flags = list(flags or [])
                    flags.extend(ldflags)
                    return flags

            @depends(dependable(includes))
            def header(includes):
                includes = includes or []
                return ["#include <%s>" % f for f in includes]

            return self.try_run(
                header=header,
                body=body,
                flags=flags,
                check_msg=check_msg,
                when=when,
                onerror=onerror,
            )

        # Generates a test program and attempts to compile it. In case of
        # failure, the resulting check will return None. If the test program
        # succeeds, it will return the output of the test program.
        # - `includes` are the includes (as file names) that will appear at the
        #   top of the generated test program.
        # - `body` is the code that will appear in the main function of the
        #   generated test program. `return 0;` is appended to the function
        #   body automatically.
        # - `flags` are the flags to be passed to the compiler, in addition to
        #   `-c`.
        # - `check_msg` is the message to be printed to accompany compiling the
        #   test program.
        def try_compile(
            self,
            includes=None,
            body="",
            flags=None,
            check_msg=None,
            when=None,
            onerror=lambda: None,
        ):
            return self._try_compile_or_link(
                includes=includes,
                body=body,
                flags=flags,
                ldflags=None,
                check_msg=check_msg,
                when=when,
                onerror=onerror,
            )

        # Same steps as try_compile but doesn't add "-c"
        def try_link(
            self,
            ldflags,
            includes=None,
            body="",
            flags=None,
            check_msg=None,
            when=None,
            onerror=lambda: None,
        ):
            return self._try_compile_or_link(
                includes=includes,
                body=body,
                flags=flags,
                ldflags=ldflags,
                check_msg=check_msg,
                when=when,
                onerror=onerror,
            )

        # Generates a test program and run the compiler against it. In case of
        # failure, the resulting check will return None.
        # - `header` is code that will appear at the top of the generated test
        #   program.
        # - `body` is the code that will appear in the main function of the
        #   generated test program. `return 0;` is appended to the function
        #   body automatically.
        # - `flags` are the flags to be passed to the compiler.
        # - `check_msg` is the message to be printed to accompany compiling the
        #   test program.
        # - `onerror` is a function called when the check fails.
        def try_run(
            self,
            header=None,
            body="",
            flags=None,
            check_msg=None,
            when=None,
            onerror=lambda: None,
        ):
            source = textwrap.dedent(
                """\
                int
                main(void)
                {
                %s
                  ;
                  return 0;
                }
            """
                % body
            )

            if check_msg:

                def checking_fn(fn):
                    return checking(check_msg)(fn)

            else:

                def checking_fn(fn):
                    return fn

            # We accept onerror being a @depends function that returns a callable.
            # So, create a similar @depends function when it's not already one.
            if not isinstance(onerror, SandboxDependsFunction):
                onerror = dependable(lambda: onerror)

            @depends(
                self,
                dependable(flags),
                extra_toolchain_flags,
                dependable(header),
                onerror,
                configure_cache,
                when=when,
            )
            @checking_fn
            def func(
                compiler,
                flags,
                extra_flags,
                header,
                onerror,
                configure_cache,
            ):
                flags = list(flags or [])
                if is_target:
                    flags += extra_flags or []
                header = header or ""
                if isinstance(header, (list, tuple)):
                    header = "\n".join(header)
                if header:
                    header += "\n"

                if (
                    try_invoke_compiler(
                        configure_cache,
                        [compiler.compiler] + compiler.flags,
                        compiler.language,
                        header + source,
                        flags,
                        onerror=onerror,
                        wrapper=compiler.wrapper,
                    )
                    is not None
                ):
                    return True

            return func

    compiler.__class__ = Compiler
    return compiler