示例#1
0
def _getCcacheStatistics(ccache_logfile):
    data = {}

    if os.path.exists(ccache_logfile):
        re_command = re.compile(r"\[.*? (\d+) *\] Command line: (.*)$")
        re_result = re.compile(r"\[.*? (\d+) *\] Result: (.*)$")
        re_anything = re.compile(r"\[.*? (\d+) *\] (.*)$")

        # Remember command from the pid, so later decision logged against pid
        # can be matched against it.
        commands = {}

        for line in getFileContentByLine(ccache_logfile):
            match = re_command.match(line)

            if match:
                pid, command = match.groups()
                commands[pid] = command

            match = re_result.match(line)
            if match:
                pid, result = match.groups()
                result = result.strip()

                try:
                    command = data[commands[pid]]
                except KeyError:
                    # It seems writing to the file can be lossy, so we can have results for
                    # unknown commands, but we don't use the command yet anyway, so just
                    # be unique.
                    command = "unknown command leading to " + line

                # Older ccache on e.g. RHEL6 wasn't explicit about linking.
                if result == "unsupported compiler option":
                    if " -o " in command or "unknown command" in command:
                        result = "called for link"

                # But still try to catch this with log output if it happens.
                if result == "unsupported compiler option":
                    scons_logger.warning(
                        "Encountered unsupported compiler option for ccache in '%s'."
                        % command
                    )

                    all_text = []

                    for line2 in open(ccache_logfile):
                        match = re_anything.match(line2)

                        if match:
                            pid2, result = match.groups()
                            if pid == pid2:
                                all_text.append(result)

                    scons_logger.warning("Full scons output: %s" % all_text)

                if result != "called for link":
                    data[command] = result

    return data
示例#2
0
def addClangClPathFromMSVC(env, target_arch, show_scons_mode):
    cl_exe = getExecutablePath("cl", env=env)

    if cl_exe is None:
        scons_logger.warning(
            "Visual Studio required for for Clang on Windows.")
        return

    clang_dir = cl_exe = os.path.join(cl_exe[:cl_exe.lower().rfind("msvc")],
                                      "Llvm")

    if target_arch == "x86_64":
        clang_dir = os.path.join(clang_dir, "x64", "bin")
    else:
        clang_dir = os.path.join(clang_dir, "bin")

    if os.path.exists(clang_dir):
        if show_scons_mode:
            scons_logger.info("Adding MSVC directory %r for Clang to PATH." %
                              clang_dir)

        addToPATH(env, clang_dir, prefix=True)
    else:
        if show_scons_mode:
            scons_logger.info("No Clang component for MSVC found." % clang_dir)
示例#3
0
def checkCachingSuccess(source_dir):
    ccache_logfile = getSconsReportValue(source_dir=source_dir,
                                         key="CCACHE_LOGFILE")

    if ccache_logfile is not None:
        stats = _getCcacheStatistics(ccache_logfile)

        if not stats:
            scons_logger.warning("You are not using ccache.")
        else:
            counts = defaultdict(int)

            for _command, result in stats.items():
                # These are not important to our users, time based decisions differentiate these.
                if result in ("cache hit (direct)",
                              "cache hit (preprocessed)"):
                    result = "cache hit"

                # Newer ccache has these, but they duplicate:
                if result in (
                        "direct_cache_hit",
                        "direct_cache_miss",
                        "preprocessed_cache_hit",
                        "preprocessed_cache_miss",
                        "primary_storage_miss",
                ):
                    continue
                if result == "primary_storage_hit":
                    result = "cache hit"
                if result == "cache_miss":
                    result = "cache miss"

                # Usage of incbin causes this for the constants blob integration.
                if result in ("unsupported code directive", "disabled"):
                    continue

                counts[result] += 1

            scons_logger.info("Compiled %d C files using ccache." % len(stats))
            for result, count in counts.items():
                scons_logger.info(
                    "Cached C files (using ccache) with result '%s': %d" %
                    (result, count))

    if os.name == "nt":
        clcache_stats_filename = getSconsReportValue(source_dir=source_dir,
                                                     key="CLCACHE_STATS")

        if clcache_stats_filename is not None and os.path.exists(
                clcache_stats_filename):
            stats = eval(  # lazy, pylint: disable=eval-used
                getFileContents(clcache_stats_filename))

            clcache_hit = stats["CacheHits"]
            clcache_miss = stats["CacheMisses"]

            scons_logger.info(
                "Compiled %d C files using clcache with %d cache hits and %d cache misses."
                % (clcache_hit + clcache_miss, clcache_hit, clcache_miss))
示例#4
0
def extractClcacheLogFromOutput(data):
    clcache_output = []
    normal_output = []

    for line in data.split(b"\n"):
        # Remove the "\r", clcache and compiler may or may not output it.
        line = line.strip()

        if b"clcache.py" in line:
            clcache_output.append(line)
        else:
            normal_output.append(line)

    # Make sure we have Windows new lines for the compiler output though.
    data = b"\r\n".join(normal_output)
    if data:
        data += b"\r\n"

    for clcache_line in clcache_output:
        match = re.search(b"Reusing cached object.*?for object file (.*)",
                          clcache_line)

        if match:
            _writeClcacheLog(match.group(1), "cache hit")
            return data

        match = re.search(b"Adding file (.*?) to cache", clcache_line)
        if match:
            _writeClcacheLog(match.group(1), "cache miss")
            return data

        match = re.search(b"Real compiler returned code (\\d+)", clcache_line)
        if match and match.group(1) != b"0":
            _writeClcacheLog(match.group(1), "compile error")
            return data

        match = re.search(b"Compiler source files: \\['(.*?)'\\]",
                          clcache_line)
        if match:
            _writeClcacheLog(match.group(1), "cache miss")
            return data

    if clcache_output:
        # Sometimes no message at all might be recognized.
        scons_logger.warning(
            "Caching with clcache could not be decoded, got this:")
        scons_logger.warning(b"\n".join(clcache_output))

    return data
示例#5
0
def checkCachingSuccess(source_dir):
    ccache_logfile = getSconsReportValue(source_dir, "CCACHE_LOGFILE")

    if ccache_logfile is not None:
        stats = _getCcacheStatistics(ccache_logfile)

        if not stats:
            scons_logger.warning("You are not using ccache.")
        else:
            counts = defaultdict(int)

            for _command, result in stats.items():
                # These are not important to our users, time based decisions differentiate these.
                if result in ("cache hit (direct)", "cache hit (preprocessed)"):
                    result = "cache hit"

                counts[result] += 1

            scons_logger.info("Compiled %d C files using ccache." % len(stats))
            for result, count in counts.items():
                scons_logger.info(
                    "Cached C files (using ccache) with result '%s': %d"
                    % (result, count)
                )

    if os.name == "nt":
        clcache_stats_filename = getSconsReportValue(source_dir, "CLCACHE_STATS")

        if clcache_stats_filename is not None and os.path.exists(
            clcache_stats_filename
        ):
            stats = eval(  # lazy, pylint: disable=eval-used
                getFileContents(clcache_stats_filename)
            )

            clcache_hit = stats["CacheHits"]
            clcache_miss = stats["CacheMisses"]

            scons_logger.info(
                "Compiled %d C files using clcache with %d cache hits and %d cache misses."
                % (clcache_hit + clcache_miss, clcache_hit, clcache_miss)
            )
示例#6
0
def checkCachingSuccess(source_dir):
    ccache_logfile = getSconsReportValue(source_dir, "CCACHE_LOGFILE")

    if ccache_logfile is not None:
        stats = _getCcacheStatistics(ccache_logfile)

        if not stats:
            scons_logger.warning("You are not using ccache.")
        else:
            counts = defaultdict(int)

            for _command, result in stats.items():
                counts[result] += 1

            scons_logger.info("Compiled %d C files using ccache." % len(stats))
            for result, count in counts.items():
                scons_logger.info(
                    "Cached C files (using ccache) with result '%s': %d" %
                    (result, count))

    clcache_logfile = getSconsReportValue(source_dir, "CLCACHE_LOG")

    if clcache_logfile is not None:
        stats = _getClcacheStatistics(clcache_logfile)

        if not stats:
            scons_logger.warning("You are not using clcache.")
        else:
            counts = defaultdict(int)

            for _command, result in stats.items():
                counts[result] += 1

            scons_logger.info("Compiled %d C files using clcache." %
                              len(stats))
            for result, count in counts.items():
                scons_logger.info(
                    "Cached C files (using clcache) with result '%s': %d" %
                    (result, count))
示例#7
0
def extractClcacheLogFromOutput(data):
    clcache_output = []
    normal_output = []

    for line in data.split(b"\r\n"):
        if b"clcache.py" in line:
            clcache_output.append(line)
        else:
            normal_output.append(line)

    data = b"\r\n".join(normal_output)
    if data:
        data += b"\r\n"

    for clcache_line in clcache_output:
        match = re.search(b"Reusing cached object.*?for object file (.*?)\\r",
                          clcache_line)

        if match:
            _writeClcacheLog(match.group(1), "cache hit")
            return data

        match = re.search(b"Adding file (.*?) to cache", clcache_line)
        if match:
            _writeClcacheLog(match.group(1), "cache miss")
            return data

        match = re.search(b"Compiler source files: \\['(.*?)'\\]",
                          clcache_line)
        if match:
            _writeClcacheLog(match.group(1), "cache miss")
            return data

    if clcache_output:
        # Sometimes no message at all might be recognized.
        scons_logger.warning("Caching with clcache could not be decoded.")

    return data
示例#8
0
def _injectCcache(the_compiler, cc_path, env, python_prefix, assume_yes_for_downloads):
    ccache_binary = os.environ.get("NUITKA_CCACHE_BINARY")

    # If not provided, search it in PATH and guessed directories.
    if ccache_binary is None:
        ccache_binary = getExecutablePath("ccache", env=env)

        if ccache_binary is None:
            for candidate in _getCcacheGuessedPaths(python_prefix):
                scons_details_logger.info(
                    "Checking if ccache is at '%s' guessed path." % candidate
                )

                if os.path.exists(candidate):
                    ccache_binary = candidate

                    scons_details_logger.info(
                        "Using ccache '%s' from guessed path." % ccache_binary
                    )

                    break

        if ccache_binary is None and os.name == "nt":
            url = "https://github.com/ccache/ccache/releases/download/v3.7.12/ccache-3.7.12-windows-32.zip"
            ccache_binary = getCachedDownload(
                url=url,
                is_arch_specific=False,
                specifity=url.rsplit("/", 2)[1],
                flatten=True,
                binary="ccache.exe",
                message="Nuitka will make use of ccache to speed up repeated compilation.",
                reject=None,
                assume_yes_for_downloads=assume_yes_for_downloads,
            )

    else:
        scons_details_logger.info(
            "Using ccache '%s' from NUITKA_CCACHE_BINARY environment variable."
            % ccache_binary
        )

    if ccache_binary is not None and os.path.exists(ccache_binary):
        # Make sure the
        # In case we are on Windows, make sure the Anaconda form runs outside of Anaconda
        # environment, by adding DLL folder to PATH.
        assert getExecutablePath(os.path.basename(the_compiler), env=env) == cc_path

        # We use absolute paths for CC, pass it like this, as ccache does not like absolute.
        env["CXX"] = env["CC"] = '"%s" "%s"' % (ccache_binary, cc_path)

        # Spare ccache the detection of the compiler, seems it will also misbehave when it's
        # prefixed with "ccache" on old gcc versions in terms of detecting need for C++ linkage.
        env["LINK"] = cc_path

        scons_details_logger.info(
            "Found ccache '%s' to cache C compilation result." % ccache_binary
        )
        scons_details_logger.info(
            "Providing real CC path '%s' via PATH extension." % cc_path
        )
    else:
        if isWin32Windows():
            scons_logger.warning(
                "Didn't find ccache for C level caching, follow Nuitka user manual description."
            )
示例#9
0
def _enableLtoSettings(
    env,
    lto_mode,
    pgo_mode,
    job_count,
):
    # This is driven by branches on purpose and pylint: disable=too-many-branches,too-many-statements

    orig_lto_mode = lto_mode

    if lto_mode == "no":
        lto_mode = False
        reason = "disabled"
    elif lto_mode == "yes":
        lto_mode = True
        reason = "enabled"
    elif pgo_mode in ("use", "generate"):
        lto_mode = True
        reason = "PGO implies LTO"
    elif env.msvc_mode and getMsvcVersion(env) >= 14:
        lto_mode = True
        reason = "known to be supported"
    elif env.nuitka_python:
        lto_mode = True
        reason = "known to be supported (Nuitka-Python)"
    elif (env.debian_python and env.gcc_mode and not env.clang_mode
          and env.gcc_version >= (6, )):
        lto_mode = True
        reason = "known to be supported (Debian)"
    elif env.gcc_mode and env.the_cc_name == "gnu-cc":
        lto_mode = True
        reason = "known to be supported (CondaCC)"
    elif env.mingw_mode and env.clang_mode:
        lto_mode = False
        reason = "known to not be supported (new MinGW64 Clang)"
    elif env.gcc_mode and env.mingw_mode and env.gcc_version >= (11, 2):
        lto_mode = True
        reason = "known to be supported (new MinGW64)"
    else:
        lto_mode = False
        reason = "not known to be supported"

    if lto_mode and env.gcc_mode and not env.clang_mode and env.gcc_version < (
            4, 6):
        scons_logger.warning("""\
The gcc compiler %s (version %s) doesn't have the sufficient \
version for lto mode (>= 4.6). Disabled.""" % (env["CXX"], env["CXXVERSION"]))

        lto_mode = False
        reason = "gcc 4.6 is doesn't have good enough LTO support"

    if env.gcc_mode and lto_mode:
        env.Append(CCFLAGS=["-flto"])

        if env.clang_mode:
            env.Append(LINKFLAGS=["-flto"])
        else:
            env.Append(CCFLAGS=["-fuse-linker-plugin", "-fno-fat-lto-objects"])
            env.Append(LINKFLAGS=["-fuse-linker-plugin"])

            env.Append(LINKFLAGS=["-flto=%d" % job_count])

            # Need to tell the linker these things are OK.
            env.Append(LINKFLAGS=["-fpartial-inlining", "-freorder-functions"])

    # Tell compiler to use link time optimization for MSVC
    if env.msvc_mode and lto_mode:
        env.Append(CCFLAGS=["/GL"])

        if not env.clangcl_mode:
            env.Append(LINKFLAGS=["/LTCG"])

    if orig_lto_mode == "auto":
        scons_details_logger.info(
            "LTO mode auto was resolved to mode: '%s' (%s)." %
            ("yes" if lto_mode else "no", reason))

    env.lto_mode = lto_mode

    # PGO configuration
    _enablePgoSettings(env, pgo_mode)
示例#10
0
def enableClcache(the_compiler, env, source_dir, python_prefix,
                  show_scons_mode):
    # Many branches to deal with, pylint: disable=too-many-branches

    # The ccache needs absolute path, otherwise it will not work.
    clcache_logfile = os.path.abspath(
        os.path.join(source_dir, "clcache-%d.txt" % os.getpid()))

    # Our spawn function will pick it up from the output.
    setEnvironmentVariable(env, "CLCACHE_LOG", clcache_logfile)
    env["CLCACHE_LOG"] = clcache_logfile

    clcache_binary = os.environ.get("NUITKA_CLCACHE_BINARY")

    # If not provided, search it in PATH and guessed directories.
    if clcache_binary is None:
        clcache_binary = getExecutablePath("clcache", env)

        if clcache_binary is None:
            for candidate in _getClcacheGuessedPaths(python_prefix):
                if show_scons_mode:
                    scons_logger.info(
                        "Checking if clcache is at '%s' guessed path." %
                        candidate)

                if os.path.exists(candidate):
                    clcache_binary = candidate

                    if show_scons_mode:
                        scons_logger.info(
                            "Using clcache '%s' from guessed path." %
                            clcache_binary)

                    break

        if clcache_binary is None and os.name == "nt":

            def checkClcache(install_dir):
                candidate = os.path.join(install_dir, "scripts", "clcache.exe")

                if show_scons_mode:
                    scons_logger.info(
                        "Checking if clcache is at '%s' python installation path."
                        % candidate)

                if os.path.exists(candidate):
                    return True

            candidate = getPythonInstallPathWindows(supported=("3.6", "3.7",
                                                               "3.8", "3.9"),
                                                    decider=checkClcache)

            if candidate is not None:
                clcache_binary = os.path.join(candidate, "scripts",
                                              "clcache.exe")

                if show_scons_mode:
                    scons_logger.info(
                        "Using clcache '%s' from registry path." %
                        clcache_binary)

    else:
        if show_scons_mode:
            scons_logger.info(
                "Using clcache '%s' from NUITKA_CLCACHE_BINARY environment variable."
                % clcache_binary)

    if clcache_binary is not None and os.path.exists(clcache_binary):
        cl_binary = getExecutablePath(the_compiler, env)

        # The compiler is passed via environment.
        setEnvironmentVariable(env, "CLCACHE_CL", cl_binary)
        env["CXX"] = env["CC"] = clcache_binary

        if show_scons_mode:
            scons_logger.info(
                "Found clcache '%s' to cache C compilation result." %
                clcache_binary)
            scons_logger.info(
                "Providing real cl.exe path '%s' via environment." % cl_binary)

        result = True
    else:
        scons_logger.warning(
            "Didn't find clcache for C level caching, follow Nuitka user manual description."
        )

        result = False

    return result
示例#11
0
def _injectCcache(the_compiler, cc_path, env, python_prefix, show_scons_mode):
    # We deal with a lot of cases here, pylint: disable=too-many-branches

    ccache_binary = os.environ.get("NUITKA_CCACHE_BINARY")

    # If not provided, search it in PATH and guessed directories.
    if ccache_binary is None:
        ccache_binary = getExecutablePath("ccache", env=env)

        if ccache_binary is None:
            for candidate in _getCcacheGuessedPaths(python_prefix):
                if show_scons_mode:
                    scons_logger.info(
                        "Checking if ccache is at '%s' guessed path." %
                        candidate)

                if os.path.exists(candidate):
                    ccache_binary = candidate

                    if show_scons_mode:
                        scons_logger.info(
                            "Using ccache '%s' from guessed path." %
                            ccache_binary)

                    break
    else:
        if show_scons_mode:
            scons_logger.info(
                "Using ccache '%s' from NUITKA_CCACHE_BINARY environment variable."
                % ccache_binary)

    if ccache_binary is not None and os.path.exists(ccache_binary):
        # In case we are on Windows, make sure the Anaconda form runs outside of Anaconda
        # environment, by adding DLL folder to PATH.
        if os.name == "nt":
            conda_dll_dir = os.path.normpath(
                os.path.join(ccache_binary, "..", "..", "Library", "mingw-w64",
                             "bin"))

            if os.path.exists(conda_dll_dir):
                addToPATH(env, conda_dll_dir, prefix=False)

        assert getExecutablePath(os.path.basename(the_compiler),
                                 env=env) == cc_path

        # Since we use absolute paths for CC, pass it like this, as ccache does not like absolute.
        env["CXX"] = env["CC"] = "%s %s" % (ccache_binary,
                                            os.path.basename(cc_path))

        # Spare ccache the detection of the compiler, seems it will also misbehave when it's
        # prefixed with "ccache" on old gcc versions in terms of detecting need for C++ linkage.
        env["LINK"] = cc_path

        # Do not consider scons cache anymore.
        if show_scons_mode:
            scons_logger.info(
                "Found ccache '%s' to cache C compilation result." %
                ccache_binary)
            scons_logger.info(
                "Providing real CC path '%s' via PATH extension." % cc_path)

        result = True
    else:
        if isWin32Windows():
            scons_logger.warning(
                "Didn't find ccache for C level caching, follow Nuitka user manual description."
            )

        result = False

    return result