-
Notifications
You must be signed in to change notification settings - Fork 1.6k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
cmake: preseed CMake cache on MSVC to speed up configuration #9570
Conversation
The cmake script was generated by this python script: find-common-cmake-cache-variables.py#!/usr/bin/env python
import argparse
import dataclasses
import os
from pathlib import Path
@dataclasses.dataclass
class CMakeVariable:
name: str
value: str
type: str
doc: str
@property
def bool_value(self) -> bool:
assert self.value in ("", "1", "ON", "OFF"), f"{repr(self.value)} is not a bool"
return self.value in ("1", "ON")
def print_cmake_set_bool(self, indent: int, *, value:str=None):
if value is None:
value = self.value
assert value in ("", "1", "ON", "OFF"), f"{repr(value)} is not a bool"
value_str = f"\"{value}\""
indent_str = " " * indent
print(f"{indent_str}set({self.name:<48} {value_str:<5} CACHE {self.type:<8} \"{self.doc}\")")
def __hash__(self):
return hash((self.name, self.value))
def find_cmakecache_txt_files(folder: Path) -> list[Path]:
result = []
for root, _, files in os.walk(folder):
if "CMakeCache.txt" in files:
result.append(Path(root) / "CMakeCache.txt")
return result
def read_cmakecache_txt(path: Path) -> dict[str, CMakeVariable]:
result = {}
doc = None
with path.open() as f:
for line in f.readlines():
line = line.strip()
if not line:
continue
if line.startswith("#"):
continue
if line.startswith("//"):
doc = line[2:]
continue
key_type, value = line.split("=", 1)
key, vtype = key_type.split(":", 1)
assert key not in result, f"{key} must be unique"
result[key] = CMakeVariable(name=key, value=value, type=vtype, doc=doc)
doc = None
return result
def filter_common_variables(vars1: dict[str, CMakeVariable], vars2: dict[str, CMakeVariable]) -> dict[str, CMakeVariable]:
result = {}
common_keys = set(vars1.keys()).intersection(set(vars2.keys()))
for common_key in common_keys:
if vars1[common_key].value == vars2[common_key].value:
result[common_key] = vars1[common_key]
return result
MSVC_TO_CL_VERSION = {
2015: "19.0",
2017: "19.1",
2019: "19.2",
2022: "19.3",
}
KNOWN_ARCHS = {
"x86",
"x64",
}
def main():
parser = argparse.ArgumentParser(allow_abbrev=False)
parser.add_argument("folder", type=Path)
args = parser.parse_args()
files = find_cmakecache_txt_files(args.folder)
all_variables = {}
constant_variables = {}
version_variables = {version: None for version in MSVC_TO_CL_VERSION.keys()}
arch_variables = {arch: None for arch in KNOWN_ARCHS}
bool_variable_names = set()
for file in files:
rel = file.relative_to(args.folder).parent
msvc_version = int(str(rel.parent).rsplit("-", 1)[1])
assert msvc_version in MSVC_TO_CL_VERSION
arch = str(rel.name)
assert arch in KNOWN_ARCHS
variables = read_cmakecache_txt(file)
all_variables[str(rel)] = variables
if not constant_variables:
constant_variables = dict(variables)
constant_variables = filter_common_variables(constant_variables, variables)
if not version_variables[msvc_version]:
version_variables[msvc_version] = dict(variables)
version_variables[msvc_version] = filter_common_variables(version_variables[msvc_version], variables)
if not arch_variables[arch]:
arch_variables[arch] = dict(variables)
arch_variables[arch] = filter_common_variables(arch_variables[arch], variables)
for v in variables.values():
if v.value in ("", "1", "0") and v.type in ("INTERNAL", "BOOL") and not "ADVANCED" in v.name and not "CMAKE" in v.name:
bool_variable_names.add(v.name)
def arch_dependent_filter(key: str) -> bool:
return any(simd in key for simd in ("SSE", "AVX", "MMX", "ALTIVEC", "LSX", "LASX", "ARM"))
def independent_filter(key: str) -> bool:
return not arch_dependent_filter(key) # and key.startswith("HAVE_") or key.startswith("LIBC_HAS") or "_IS_" in key or "_IN_" in key
print("cmake_dependent_option(SDL_MSVC_PRESEED \"Preseed CMake cache for MSVC to speed up configuration\" ON \"MSVC;NOT WINDOWS_STORE\" OFF)")
print("")
print("if(SDL_MSVC_PRESEED)")
remaining_variables = set(bool_variable_names)
remaining_variables = set(filter(lambda v: not v.startswith("CHECK_CPU_ARCHITECTURE"), remaining_variables))
for variable in sorted(list(remaining_variables)):
if independent_filter(variable) and variable in constant_variables:
constant_variables[variable].print_cmake_set_bool(indent=2)
remaining_variables.remove(variable)
bool_arch_variables = set(filter(arch_dependent_filter, remaining_variables))
for arch in KNOWN_ARCHS:
print()
print(f" if(CHECK_CPU_ARCHITECTURE_{arch.upper()})")
for variable in sorted(list(arch_variables[arch])):
if not arch_dependent_filter(variable) or variable not in remaining_variables:
continue
arch_variables[arch][variable].print_cmake_set_bool(indent=4)
print(f" endif()")
remaining_variables.difference_update(bool_arch_variables)
variables_min_msvc_version = dict()
for var in remaining_variables:
min_msvc_version = min(msvc_version for msvc_version, msvc_variables in version_variables.items() if msvc_variables[var].bool_value)
assert all(msvc_variables[var].bool_value for msvc_version, msvc_variables in version_variables.items() if msvc_version >= min_msvc_version)
variables_min_msvc_version.setdefault(min_msvc_version, []).append(var)
for msvc_version, variables in variables_min_msvc_version.items():
cl_version = MSVC_TO_CL_VERSION[msvc_version]
print()
print(f" if(CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL \"{cl_version}\")")
for variable in sorted(variables):
version_variables[msvc_version][variable].print_cmake_set_bool(indent=4)
print(f" else()")
for variable in sorted(variables):
version_variables[msvc_version][variable].print_cmake_set_bool(indent=4, value="")
print(f" endif()")
print("endif()")
return 0
if __name__ == "__main__":
raise SystemExit(main()) |
Oh man, can we do this for more platforms? There's really no reason we should be spending time asking macOS if it has a function called "malloc" in its C runtime, right? |
(Maybe it's better said we should just assume platforms has a standard C runtime for non-controversial things, and the weird embedded platform can enable the symbols_to_check tests. This doesn't count things we know are different, like strlcpy not being standardized, etc) |
Yes please! :) |
1e6b5d3
to
e76a049
Compare
Anything holding this back? Did you want to implement it for more platforms? |
No, because I don't know much about the apple toolchains, I'd prefer to not apply this to other platforms (yet). I have a poc repo that can significantly speed things up, but is UB and does not work on clang. |
e76a049
to
ac1bc52
Compare
Silly naming question... what's the difference between "preseed" and "seed" in this case? Is it fine to use the word "seed"? |
I'm not sure. I only know about seeds in CS in the context of random generators or torrents. In our context, preseed looks like the correct term. wdyt? |
Sure, fair enough. |
Is this off by default? I just did a fresh build under MSVC and it took a long time for the configure step. |
It's enabled by default (only on SDL3). When you don't see lots of tests for stdlib symbols, then the preseeding is active. |
Ah, okay, it's slow, but not as slow as it was. :) Thanks! |
This preseeds the CMake cache for MSVC CMake projects:
On CI, CMake configuration time goes from:
I disabled it for UWP (
WINDOWS_STORE
),To disable this, configure with
-DSDL_MSVC_PRESEED=OFF --fresh
.(
--fresh
performs a fresh configuration of the build tree, removing theCMakeCache.txt
file)Description
Existing Issue(s)
#9355