diff --git a/configure.py b/configure.py index db3c662851..71e6a64afc 100755 --- a/configure.py +++ b/configure.py @@ -368,6 +368,87 @@ def find_ninja(): sys.exit(1) +def find_compiler(name): + """ + Find a compiler by name, skipping ccache wrapper directories. + + This is useful when using sccache to avoid double-caching through ccache. + + Args: + name: The compiler name (e.g., 'clang++', 'clang', 'gcc') + + Returns: + Path to the compiler, skipping ccache directories, or None if not found. + """ + ccache_dirs = {'/usr/lib/ccache', '/usr/lib64/ccache'} + for path_dir in os.environ.get('PATH', '').split(os.pathsep): + # Skip ccache wrapper directories + if os.path.realpath(path_dir) in ccache_dirs or path_dir in ccache_dirs: + continue + candidate = os.path.join(path_dir, name) + if os.path.isfile(candidate) and os.access(candidate, os.X_OK): + return candidate + return None + + +def resolve_compilers_for_sccache(args, compiler_cache): + """ + When using sccache, resolve compiler paths to avoid ccache directories. + + This prevents double-caching when ccache symlinks are in PATH. + + Args: + args: The argument namespace with cc and cxx attributes. + compiler_cache: Path to the compiler cache binary, or None. + """ + if not compiler_cache or 'sccache' not in compiler_cache: + return + if not os.path.isabs(args.cxx): + real_cxx = find_compiler(args.cxx) + if real_cxx: + args.cxx = real_cxx + if not os.path.isabs(args.cc): + real_cc = find_compiler(args.cc) + if real_cc: + args.cc = real_cc + + +def find_compiler_cache(preference): + """ + Find a compiler cache based on the preference. + + Args: + preference: One of 'auto', 'sccache', 'ccache', 'none', or a path to a binary. + + Returns: + Path to the compiler cache binary, or None if not found/disabled. + """ + if preference == 'none': + return None + + if preference == 'auto': + # Prefer sccache over ccache + for cache in ['sccache', 'ccache']: + path = which(cache) + if path: + return path + return None + + if preference in ('sccache', 'ccache'): + path = which(preference) + if path: + return path + print(f"Warning: {preference} not found on PATH, disabling compiler cache") + return None + + # Assume it's a path to a binary + if os.path.isfile(preference) and os.access(preference, os.X_OK): + return preference + + print(f"Warning: compiler cache '{preference}' not found or not executable, disabling compiler cache") + return None + + modes = { 'debug': { 'cxxflags': '-DDEBUG -DSANITIZE -DDEBUG_LSA_SANITIZER -DSCYLLA_ENABLE_ERROR_INJECTION', @@ -732,6 +813,8 @@ arg_parser.add_argument('--compiler', action='store', dest='cxx', default='clang help='C++ compiler path') arg_parser.add_argument('--c-compiler', action='store', dest='cc', default='clang', help='C compiler path') +arg_parser.add_argument('--compiler-cache', action='store', dest='compiler_cache', default='auto', + help='Compiler cache to use: auto (default, prefers sccache), sccache, ccache, none, or a path to a binary') add_tristate(arg_parser, name='dpdk', dest='dpdk', default=False, help='Use dpdk (from seastar dpdk sources)') arg_parser.add_argument('--dpdk-target', action='store', dest='dpdk_target', default='', @@ -2014,7 +2097,7 @@ def semicolon_separated(*flags): def real_relpath(path, start): return os.path.relpath(os.path.realpath(path), os.path.realpath(start)) -def configure_seastar(build_dir, mode, mode_config): +def configure_seastar(build_dir, mode, mode_config, compiler_cache=None): seastar_cxx_ld_flags = mode_config['cxx_ld_flags'] # We want to "undo" coverage for seastar if we have it enabled. if args.coverage: @@ -2061,6 +2144,10 @@ def configure_seastar(build_dir, mode, mode_config): '-DSeastar_IO_URING=ON', ] + if compiler_cache: + seastar_cmake_args += [f'-DCMAKE_CXX_COMPILER_LAUNCHER={compiler_cache}', + f'-DCMAKE_C_COMPILER_LAUNCHER={compiler_cache}'] + if args.stack_guards is not None: stack_guards = 'ON' if args.stack_guards else 'OFF' seastar_cmake_args += ['-DSeastar_STACK_GUARDS={}'.format(stack_guards)] @@ -2092,7 +2179,7 @@ def configure_seastar(build_dir, mode, mode_config): subprocess.check_call(seastar_cmd, shell=False, cwd=cmake_dir) -def configure_abseil(build_dir, mode, mode_config): +def configure_abseil(build_dir, mode, mode_config, compiler_cache=None): abseil_cflags = mode_config['lib_cflags'] cxx_flags = mode_config['cxxflags'] if '-DSANITIZE' in cxx_flags: @@ -2118,6 +2205,10 @@ def configure_abseil(build_dir, mode, mode_config): '-DABSL_PROPAGATE_CXX_STD=ON', ] + if compiler_cache: + abseil_cmake_args += [f'-DCMAKE_CXX_COMPILER_LAUNCHER={compiler_cache}', + f'-DCMAKE_C_COMPILER_LAUNCHER={compiler_cache}'] + cmake_args = abseil_cmake_args[:] abseil_build_dir = os.path.join(build_dir, mode, 'abseil') abseil_cmd = ['cmake', '-G', 'Ninja', real_relpath('abseil', abseil_build_dir)] + cmake_args @@ -2290,10 +2381,15 @@ def write_build_file(f, scylla_product, scylla_version, scylla_release, + compiler_cache, args): use_precompiled_header = not args.disable_precompiled_header warnings = get_warning_options(args.cxx) rustc_target = pick_rustc_target('wasm32-wasi', 'wasm32-wasip1') + # If compiler cache is available, prefix the compiler with it + cxx_with_cache = f'{compiler_cache} {args.cxx}' if compiler_cache else args.cxx + # For Rust, sccache is used via RUSTC_WRAPPER environment variable + rustc_wrapper = f'RUSTC_WRAPPER={compiler_cache} ' if compiler_cache and 'sccache' in compiler_cache else '' f.write(textwrap.dedent('''\ configure_args = {configure_args} builddir = {outdir} @@ -2356,7 +2452,7 @@ def write_build_file(f, command = clang --target=wasm32 --no-standard-libraries -Wl,--export-all -Wl,--no-entry $in -o $out description = C2WASM $out rule rust2wasm - command = cargo build --target={rustc_target} --example=$example --locked --manifest-path=test/resource/wasm/rust/Cargo.toml --target-dir=$builddir/wasm/ $ + command = {rustc_wrapper}cargo build --target={rustc_target} --example=$example --locked --manifest-path=test/resource/wasm/rust/Cargo.toml --target-dir=$builddir/wasm/ $ && wasm-opt -Oz $builddir/wasm/{rustc_target}/debug/examples/$example.wasm -o $builddir/wasm/$example.wasm $ && wasm-strip $builddir/wasm/$example.wasm description = RUST2WASM $out @@ -2372,7 +2468,7 @@ def write_build_file(f, command = llvm-profdata merge $in -output=$out ''').format(configure_args=configure_args, outdir=outdir, - cxx=args.cxx, + cxx=cxx_with_cache, user_cflags=user_cflags, warnings=warnings, defines=defines, @@ -2380,6 +2476,7 @@ def write_build_file(f, user_ldflags=user_ldflags, libs=libs, rustc_target=rustc_target, + rustc_wrapper=rustc_wrapper, link_pool_depth=link_pool_depth, seastar_path=args.seastar_path, ninja=ninja, @@ -2464,10 +2561,10 @@ def write_build_file(f, description = TEST {mode} # This rule is unused for PGO stages. They use the rust lib from the parent mode. rule rust_lib.{mode} - command = CARGO_BUILD_DEP_INFO_BASEDIR='.' cargo build --locked --manifest-path=rust/Cargo.toml --target-dir=$builddir/{mode} --profile=rust-{mode} $ + command = CARGO_BUILD_DEP_INFO_BASEDIR='.' {rustc_wrapper}cargo build --locked --manifest-path=rust/Cargo.toml --target-dir=$builddir/{mode} --profile=rust-{mode} $ && touch $out description = RUST_LIB $out - ''').format(mode=mode, antlr3_exec=args.antlr3_exec, fmt_lib=fmt_lib, test_repeat=args.test_repeat, test_timeout=args.test_timeout, **modeval)) + ''').format(mode=mode, antlr3_exec=args.antlr3_exec, fmt_lib=fmt_lib, test_repeat=args.test_repeat, test_timeout=args.test_timeout, rustc_wrapper=rustc_wrapper, **modeval)) f.write( 'build {mode}-build: phony {artifacts} {wasms} {vector_search_validator_bins}\n'.format( mode=mode, @@ -2927,6 +3024,9 @@ def create_build_system(args): os.makedirs(outdir, exist_ok=True) + compiler_cache = find_compiler_cache(args.compiler_cache) + resolve_compilers_for_sccache(args, compiler_cache) + scylla_product, scylla_version, scylla_release = generate_version(args.date_stamp) for mode, mode_config in build_modes.items(): @@ -2943,8 +3043,8 @@ def create_build_system(args): # {outdir}/{mode}/seastar/build.ninja, and # {outdir}/{mode}/seastar/seastar.pc is queried for building flags for mode, mode_config in build_modes.items(): - configure_seastar(outdir, mode, mode_config) - configure_abseil(outdir, mode, mode_config) + configure_seastar(outdir, mode, mode_config, compiler_cache) + configure_abseil(outdir, mode, mode_config, compiler_cache) user_cflags += ' -isystem abseil' for mode, mode_config in build_modes.items(): @@ -2967,6 +3067,7 @@ def create_build_system(args): scylla_product, scylla_version, scylla_release, + compiler_cache, args) generate_compdb('compile_commands.json', ninja, args.buildfile, selected_modes) @@ -3009,6 +3110,10 @@ def configure_using_cmake(args): selected_modes = args.selected_modes or default_modes selected_configs = ';'.join(build_modes[mode].cmake_build_type for mode in selected_modes) + + compiler_cache = find_compiler_cache(args.compiler_cache) + resolve_compilers_for_sccache(args, compiler_cache) + settings = { 'CMAKE_CONFIGURATION_TYPES': selected_configs, 'CMAKE_CROSS_CONFIGS': selected_configs, @@ -3026,6 +3131,14 @@ def configure_using_cmake(args): 'Scylla_WITH_DEBUG_INFO' : 'ON' if args.debuginfo else 'OFF', 'Scylla_USE_PRECOMPILED_HEADER': 'OFF' if args.disable_precompiled_header else 'ON', } + + if compiler_cache: + settings['CMAKE_CXX_COMPILER_LAUNCHER'] = compiler_cache + settings['CMAKE_C_COMPILER_LAUNCHER'] = compiler_cache + # For Rust, sccache is used via RUSTC_WRAPPER + if 'sccache' in compiler_cache: + settings['Scylla_RUSTC_WRAPPER'] = compiler_cache + if args.date_stamp: settings['Scylla_DATE_STAMP'] = args.date_stamp if args.staticboost: @@ -3057,7 +3170,7 @@ def configure_using_cmake(args): if not args.dist_only: for mode in selected_modes: - configure_seastar(build_dir, build_modes[mode].cmake_build_type, modes[mode]) + configure_seastar(build_dir, build_modes[mode].cmake_build_type, modes[mode], compiler_cache) cmake_command = ['cmake'] cmake_command += [f'-D{var}={value}' for var, value in settings.items()] diff --git a/rust/CMakeLists.txt b/rust/CMakeLists.txt index 38db71c2ac..88cd6c8aaa 100644 --- a/rust/CMakeLists.txt +++ b/rust/CMakeLists.txt @@ -1,6 +1,13 @@ find_program(CARGO cargo REQUIRED) +# Set up RUSTC_WRAPPER for sccache support if configured +if(Scylla_RUSTC_WRAPPER) + set(RUSTC_WRAPPER_ENV "RUSTC_WRAPPER=${Scylla_RUSTC_WRAPPER}") +else() + set(RUSTC_WRAPPER_ENV "") +endif() + function(add_rust_library name) # used for profiles defined in Cargo.toml if(CMAKE_CONFIGURATION_TYPES) @@ -16,7 +23,7 @@ function(add_rust_library name) set(library ${target_dir}/lib${name}.a) add_custom_command( OUTPUT ${library} - COMMAND ${CMAKE_COMMAND} -E env CARGO_BUILD_DEP_INFO_BASEDIR=. ${CARGO} build --locked --target-dir=${target_dir} --profile=${profile} + COMMAND ${CMAKE_COMMAND} -E env CARGO_BUILD_DEP_INFO_BASEDIR=. ${RUSTC_WRAPPER_ENV} ${CARGO} build --locked --target-dir=${target_dir} --profile=${profile} COMMAND ${CMAKE_COMMAND} -E copy ${target_dir}/${profile}/lib${name}.a ${library} DEPENDS Cargo.lock WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR} diff --git a/test/resource/wasm/rust/CMakeLists.txt b/test/resource/wasm/rust/CMakeLists.txt index 0cf2dada65..4ecff4d445 100644 --- a/test/resource/wasm/rust/CMakeLists.txt +++ b/test/resource/wasm/rust/CMakeLists.txt @@ -3,6 +3,13 @@ find_program(CARGO cargo find_program(RUSTC rustc REQUIRED) +# Set up RUSTC_WRAPPER for sccache support if configured +if(Scylla_RUSTC_WRAPPER) + set(RUSTC_WRAPPER_ENV "RUSTC_WRAPPER=${Scylla_RUSTC_WRAPPER}") +else() + set(RUSTC_WRAPPER_ENV "") +endif() + function(pick_rustc_target output_var candidates) execute_process( COMMAND @@ -33,6 +40,7 @@ function(compile_rust_to_wasm target input) add_custom_command( OUTPUT ${output} COMMAND + ${CMAKE_COMMAND} -E env ${RUSTC_WRAPPER_ENV} ${CARGO} build --target=${target} --example=${basename}