cuda_prefix=${CUDA:0:4}
cuda_prefix=${cuda_prefix/./-}
-retry sudo apt-get install --allow-unauthenticated -y cuda-command-line-tools-${cuda_prefix}
+retry sudo apt-get install --allow-unauthenticated -y \
+ cuda-command-line-tools-${cuda_prefix} \
+ libcurand-dev-${cuda_prefix}
+
retry sudo apt-get clean
cuda_home=/usr/local/cuda-${CUDA:0:4}
#include <ccache/storage/storage.hpp>
#include <ccache/util/assertions.hpp>
#include <ccache/util/bytes.hpp>
+#include <ccache/util/clang.hpp>
#include <ccache/util/conversion.hpp>
#include <ccache/util/defer.hpp>
#include <ccache/util/direntry.hpp>
return *result_key;
}
+// Process a CUDA preprocessed chunk for a specific architecture
+static tl::expected<void, Failure>
+process_cuda_chunk(Context& ctx,
+ Hash& hash,
+ const std::string& chunk,
+ size_t index)
+{
+ // 1. Create a temp file for this CUDA chunk
+ auto tmp_result = util::TemporaryFile::create(
+ FMT("{}/cuda_tmp_{}.i", ctx.config.temporary_dir(), index),
+ FMT(".{}", ctx.config.cpp_extension()));
+ if (!tmp_result) {
+ return tl::unexpected(Statistic::internal_error);
+ }
+
+ const auto& chunk_path = tmp_result->path;
+ tmp_result->fd.close(); // we only need the path, not the open fd
+
+ // 2. Write the chunk contents into the temp file
+ if (!util::write_file(chunk_path, chunk)) {
+ return tl::unexpected(Statistic::internal_error);
+ }
+ // 3. Register the file so it gets cleaned up later
+ ctx.register_pending_tmp_file(chunk_path);
+
+ // 4. Add a unique hash delimiter for this chunk
+ hash.hash_delimiter(FMT("cu_{}", index));
+
+ // 5. Process the chunk just like a normal preprocessed file
+ TRY(process_preprocessed_file(ctx, hash, chunk_path));
+
+ return {};
+}
+
+static bool
+get_clang_cu_enable_verbose_mode(Args& args)
+{
+ for (size_t i = 1; i < args.size(); i++) {
+ if (args[i] == "-v") {
+ return true;
+ }
+ }
+
+ return false;
+}
+
// Find the result key by running the compiler in preprocessor mode and
// hashing the result.
static tl::expected<Hash::Digest, Failure>
{
fs::path preprocessed_path;
util::Bytes cpp_stderr_data;
+ util::Bytes cpp_stdout_data;
+
+ // When Clang runs in verbose mode, it outputs command details to stdout,
+ // which can corrupt the output of precompiled CUDA files.
+ // Therefore, caching is disabled in this scenario.
+ // (Is there a better approach to handle this?)
+ const bool is_clang_cu = ctx.config.is_compiler_group_clang()
+ && ctx.args_info.actual_language == "cu"
+ && !get_clang_cu_enable_verbose_mode(args);
+
+ const bool capture_stdout = is_clang_cu;
if (ctx.args_info.direct_i_file) {
// We are compiling a .i or .ii file - that means we can skip the cpp stage
args.push_back(FMT("-Fi{}", preprocessed_path));
} else {
args.push_back("-E");
- args.push_back("-o");
- args.push_back(preprocessed_path);
+ if (!is_clang_cu) {
+ args.push_back("-o");
+ args.push_back(preprocessed_path);
+ }
}
args.push_back(
add_prefix(ctx, args, ctx.config.prefix_command_cpp());
LOG_RAW("Running preprocessor");
- const auto result = do_execute(ctx, args, false);
+ const auto result = do_execute(ctx, args, capture_stdout);
args.pop_back(args.size() - orig_args_size);
if (!result) {
}
cpp_stderr_data = result->stderr_data;
+ cpp_stdout_data = result->stdout_data;
}
- hash.hash_delimiter("cpp");
- TRY(process_preprocessed_file(ctx, hash, preprocessed_path));
+ if (is_clang_cu) {
+ util::write_file(preprocessed_path, cpp_stdout_data);
+
+ auto chunks =
+ util::split_preprocess_file_in_clang_cuda(preprocessed_path.string());
+
+ for (size_t i = 0; i < chunks.size(); ++i) {
+ TRY(process_cuda_chunk(ctx, hash, chunks[i], i));
+ }
+
+ } else {
+ hash.hash_delimiter("cpp");
+
+ TRY(process_preprocessed_file(ctx, hash, preprocessed_path));
+ }
hash.hash_delimiter("cppstderr");
hash.hash(util::to_string_view(cpp_stderr_data));
tokenizer.cpp
umaskscope.cpp
zstd.cpp
+ clang.cpp
)
file(GLOB headers *.hpp)
--- /dev/null
+#include "clang.hpp"
+
+#include <ccache/util/logging.hpp>
+
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <vector>
+
+namespace util {
+
+std::vector<std::string>
+split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path)
+{
+ std::ifstream infile(mixed_preprocessed_path);
+ std::vector<std::string> split_preprocess_file_list;
+
+ if (!infile) {
+ LOG("Can't open file {}", mixed_preprocessed_path);
+ return split_preprocess_file_list;
+ }
+
+ std::string delimiter;
+ if (!std::getline(infile, delimiter)) {
+ return split_preprocess_file_list;
+ }
+
+ std::string currentPart = delimiter + "\n";
+ std::string line;
+
+ while (std::getline(infile, line)) {
+ if (line == delimiter) {
+ split_preprocess_file_list.push_back(currentPart);
+ currentPart = delimiter + "\n";
+ } else {
+ currentPart += line + "\n";
+ }
+ }
+
+ if (!currentPart.empty()) {
+ split_preprocess_file_list.push_back(currentPart);
+ }
+
+ return split_preprocess_file_list;
+}
+
+} // namespace util
\ No newline at end of file
--- /dev/null
+#pragma once
+
+#include <string>
+#include <vector>
+
+namespace util {
+
+std::vector<std::string>
+split_preprocess_file_in_clang_cuda(const std::string& mixed_preprocessed_path);
+
+} // namespace util
addtest(base)
addtest(basedir)
addtest(cache_levels)
+addtest(clang_cu)
+addtest(clang_cu_direct)
+addtest(clang_cu_nocpp2)
addtest(cleanup)
addtest(color_diagnostics)
addtest(config)
addtest(stats_log)
addtest(trim_dir)
addtest(upgrade)
+
--- /dev/null
+setup_clang() {
+ local CUDA_PATH="--cuda-path=/usr/local/cuda"
+ if [ ! -z "$CUDA_HOME" ]; then
+ local CUDA_PATH="--cuda-path=$CUDA_HOME"
+ fi
+
+ export REAL_CLANG="clang $CUDA_PATH"
+}
+
+clang_cu_PROBE() {
+ if [ -z "$REAL_NVCC" ]; then
+ echo "nvcc is not available"
+ return
+ elif ! command -v cuobjdump >/dev/null; then
+ echo "cuobjdump is not available"
+ return
+ elif ! command -v clang >/dev/null; then
+ echo "clang is not available"
+ return
+ fi
+
+ setup_clang
+
+ touch test.cu
+ if ! $REAL_CLANG -c -x cu test.cu >/dev/null 2>&1; then
+ echo "Clang's CUDA support is not compatible."
+ fi
+
+}
+
+clang_cu_SETUP() {
+ # Test code using only c++ (option -x c++). Faster than compiling cuda.
+ cat <<EOF > test_cpp.cu
+#ifndef NUM
+#define NUM 10000
+#endif
+
+void caller() {
+ for (int i = 0; i < NUM; ++i);
+}
+EOF
+
+
+ # Test code using cuda.
+ cat <<EOF >test_cuda.cu
+#ifndef NUM
+#define NUM 10000
+#endif
+
+__global__
+void add(int *a, int *b) {
+ int i = blockIdx.x;
+ if (i < NUM) {
+ b[i] = 2 * a[i];
+ }
+}
+
+void caller() {
+ add<<<NUM, 1>>>(NULL,NULL);
+}
+EOF
+}
+
+clang_cu_tests() {
+ setup_clang
+
+ clang_opts_cpp="-c -x c++"
+ clang_opts_cuda="-c -x cu"
+ clang_opts_gpu1="--cuda-gpu-arch=sm_50"
+ clang_opts_gpu2="--cuda-gpu-arch=sm_75"
+ ccache_clang_cpp="$CCACHE $REAL_CLANG $clang_opts_cpp"
+ ccache_clang_cuda="$CCACHE $REAL_CLANG $clang_opts_cuda"
+ cuobjdump="cuobjdump -all -elf -symbols -ptx -sass"
+
+ # -------------------------------------------------------------------------
+ TEST "Simple mode"
+
+ $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu
+
+ # First compile.
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_content reference_test1.o test_cpp.o
+
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # -------------------------------------------------------------------------
+ TEST "Different GPU architectures"
+
+ $REAL_CLANG $clang_opts_cuda -o reference_test1.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 -o reference_test2.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu2 -o reference_test3.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 $clang_opts_gpu2 -o reference_test4.o test_cuda.cu
+
+ $cuobjdump reference_test1.o > reference_test1.dump
+ $cuobjdump reference_test2.o > reference_test2.dump
+ $cuobjdump reference_test3.o > reference_test3.dump
+ $cuobjdump reference_test4.o > reference_test4.dump
+ expect_different_content reference_test1.dump reference_test2.dump
+ expect_different_content reference_test1.dump reference_test3.dump
+ expect_different_content reference_test2.dump reference_test3.dump
+ expect_different_content reference_test4.dump reference_test3.dump
+ expect_different_content reference_test4.dump reference_test2.dump
+
+ $ccache_clang_cuda test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test1.dump test1.dump
+
+ # Other GPU.
+ $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test2.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test2.dump test1.dump
+
+ # Another GPU.
+ $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 3
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test3.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 3
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test3.dump test1.dump
+
+ # Multi GPU
+ $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 4
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test4.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu
+ expect_stat preprocessed_cache_hit 3
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 4
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test4.dump test1.dump
+
+ # -------------------------------------------------------------------------
+ TEST "Option -fgpu-rdc"
+
+ $REAL_CLANG $clang_opts_cuda -fgpu-rdc -o reference_test4.o test_cuda.cu
+ $cuobjdump reference_test4.o > reference_test4.dump
+
+ $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ $cuobjdump test_cuda.o > test4.dump
+ expect_equal_content test4.dump reference_test4.dump
+
+ $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ $cuobjdump test_cuda.o > test4.dump
+ expect_equal_content test4.dump reference_test4.dump
+
+ # -------------------------------------------------------------------------
+ TEST "Different defines"
+
+ $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu
+ $REAL_CLANG $clang_opts_cpp -DNUM=10 -o reference_test2.o test_cpp.cu
+ expect_different_content reference_test1.o reference_test2.o
+
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # Specified define, but unused. Can only be found by preprocessed mode.
+ $ccache_clang_cpp -DDUMMYENV=1 test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # Specified used define.
+ $ccache_clang_cpp -DNUM=10 test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
+ expect_equal_content reference_test2.o test_cpp.o
+
+ $ccache_clang_cpp -DNUM=10 test_cpp.cu
+ expect_stat preprocessed_cache_hit 2
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 2
+ expect_equal_content reference_test2.o test_cpp.o
+
+ TEST "No cache(preprocess failed)"
+
+ $ccache_clang_cuda -DNUM=i test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
+
+ $ccache_clang_cuda -DNUM=i test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
+
+ TEST "verbose mode"
+
+ $REAL_CLANG $clang_opts_cuda -o reference_test5.o test_cuda.cu
+ $cuobjdump reference_test5.o > reference_test5.dump
+
+ # First compile.
+ $ccache_clang_cuda -v test_cuda.cu
+ expect_stat preprocessed_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ $cuobjdump test_cuda.o > test5_1.dump
+ expect_equal_content test5_1.dump reference_test5.dump
+
+ $ccache_clang_cuda -v test_cuda.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 1
+ $cuobjdump test_cuda.o > test5_2.dump
+ expect_equal_content test5_2.dump reference_test5.dump
+}
+
+SUITE_clang_cu_PROBE() {
+ clang_cu_PROBE
+}
+
+SUITE_clang_cu_SETUP() {
+ clang_cu_SETUP
+}
+
+SUITE_clang_cu() {
+ clang_cu_tests
+}
--- /dev/null
+SUITE_clang_cu_direct_PROBE() {
+ clang_cu_PROBE
+}
+
+SUITE_clang_cu_direct_SETUP() {
+ unset CCACHE_NODIRECT
+
+ clang_cu_SETUP
+}
+
+SUITE_clang_cu_direct() {
+ setup_clang
+
+ clang_opts_cpp="-c -x c++"
+ clang_opts_cuda="-c -x cu"
+ clang_opts_gpu1="--cuda-gpu-arch=sm_50"
+ clang_opts_gpu2="--cuda-gpu-arch=sm_75"
+ ccache_clang_cpp="$CCACHE $REAL_CLANG $clang_opts_cpp"
+ ccache_clang_cuda="$CCACHE $REAL_CLANG $clang_opts_cuda"
+ cuobjdump="cuobjdump -all -elf -symbols -ptx -sass"
+
+ # -------------------------------------------------------------------------
+ TEST "Simple mode"
+
+ $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu
+
+ # First compile.
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_equal_content reference_test1.o test_cpp.o
+
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # -------------------------------------------------------------------------
+ TEST "Different GPU architectures"
+
+ $REAL_CLANG $clang_opts_cuda -o reference_test1.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 -o reference_test2.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu2 -o reference_test3.o test_cuda.cu
+ $REAL_CLANG $clang_opts_cuda $clang_opts_gpu1 $clang_opts_gpu2 -o reference_test4.o test_cuda.cu
+
+ $cuobjdump reference_test1.o > reference_test1.dump
+ $cuobjdump reference_test2.o > reference_test2.dump
+ $cuobjdump reference_test3.o > reference_test3.dump
+ $cuobjdump reference_test4.o > reference_test4.dump
+ expect_different_content reference_test1.dump reference_test2.dump
+ expect_different_content reference_test1.dump reference_test3.dump
+ expect_different_content reference_test2.dump reference_test3.dump
+ expect_different_content reference_test4.dump reference_test3.dump
+ expect_different_content reference_test4.dump reference_test2.dump
+
+ $ccache_clang_cuda test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test1.dump test1.dump
+
+ # Other GPU.
+ $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test2.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu1 test_cuda.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 4
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test2.dump test1.dump
+
+ # Another GPU.
+ $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 6
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test3.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu2 test_cuda.cu
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 3
+ expect_stat files_in_cache 6
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test3.dump test1.dump
+
+ # Multi GPU
+ $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu
+ expect_stat direct_cache_hit 2
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 8
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test4.dump test1.dump
+
+ $ccache_clang_cuda $clang_opts_gpu1 $clang_opts_gpu2 test_cuda.cu
+ expect_stat direct_cache_hit 3
+ expect_stat cache_miss 4
+ expect_stat files_in_cache 8
+ $cuobjdump test_cuda.o > test1.dump
+ expect_equal_content reference_test4.dump test1.dump
+
+ # -------------------------------------------------------------------------
+ TEST "Option -fgpu-rdc"
+
+ $REAL_CLANG $clang_opts_cuda -fgpu-rdc -o reference_test4.o test_cuda.cu
+ $cuobjdump reference_test4.o > reference_test4.dump
+
+ $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test4.dump
+ expect_equal_content test4.dump reference_test4.dump
+
+ $ccache_clang_cuda -fgpu-rdc -o test_cuda.o test_cuda.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test4.dump
+ expect_equal_content test4.dump reference_test4.dump
+
+ # -------------------------------------------------------------------------
+ TEST "Different defines"
+
+ $REAL_CLANG $clang_opts_cpp -o reference_test1.o test_cpp.cu
+ $REAL_CLANG $clang_opts_cpp -DNUM=10 -o reference_test2.o test_cpp.cu
+ expect_different_content reference_test1.o reference_test2.o
+
+ $ccache_clang_cpp test_cpp.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # Specified define, but unused. Can only be found by preprocessed mode.
+ $ccache_clang_cpp -DDUMMYENV=1 test_cpp.cu
+ expect_stat preprocessed_cache_hit 1
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 3
+ expect_equal_content reference_test1.o test_cpp.o
+
+ # Specified used define.
+ $ccache_clang_cpp -DNUM=10 test_cpp.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 5
+ expect_equal_content reference_test2.o test_cpp.o
+
+ $ccache_clang_cpp -DNUM=10 test_cpp.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 2
+ expect_stat files_in_cache 5
+ expect_equal_content reference_test2.o test_cpp.o
+
+ TEST "No cache(preprocess failed)"
+
+ $ccache_clang_cuda -DNUM=i test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
+
+ $ccache_clang_cuda -DNUM=i test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 0
+ expect_stat files_in_cache 0
+
+ TEST "verbose mode"
+
+ $REAL_CLANG $clang_opts_cuda -v -o reference_test5.o test_cuda.cu
+ $cuobjdump reference_test5.o > reference_test5.dump
+
+ # First compile.
+ $ccache_clang_cuda -v test_cuda.cu
+ expect_stat direct_cache_hit 0
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test5_1.dump
+ expect_equal_content test5_1.dump reference_test5.dump
+
+ $ccache_clang_cuda -v test_cuda.cu
+ expect_stat direct_cache_hit 1
+ expect_stat cache_miss 1
+ expect_stat files_in_cache 2
+ $cuobjdump test_cuda.o > test5_2.dump
+ expect_equal_content test5_2.dump reference_test5.dump
+}
--- /dev/null
+SUITE_clang_cu_nocpp2_PROBE() {
+ clang_cu_PROBE
+}
+
+SUITE_clang_cu_nocpp2_SETUP() {
+ export CCACHE_NOCPP2=1
+
+ clang_cu_SETUP
+}
+
+SUITE_clang_cu_nocpp2() {
+ clang_cu_tests
+}
test_storage_local_util.cpp
test_util_bitset.cpp
test_util_bytes.cpp
+ test_util_clang.cpp
test_util_conversion.cpp
test_util_direntry.cpp
test_util_duration.cpp
--- /dev/null
+// Copyright (C) 2021-2024 Joel Rosdahl and other contributors
+//
+// See doc/AUTHORS.adoc for a complete list of contributors.
+//
+// This program is free software; you can redistribute it and/or modify it
+// under the terms of the GNU General Public License as published by the Free
+// Software Foundation; either version 3 of the License, or (at your option)
+// any later version.
+//
+// This program is distributed in the hope that it will be useful, but WITHOUT
+// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+// FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
+// more details.
+//
+// You should have received a copy of the GNU General Public License along with
+// this program; if not, write to the Free Software Foundation, Inc., 51
+// Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+
+#include <ccache/util/clang.hpp>
+
+#include <doctest/doctest.h>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <ostream> // https://github.com/doctest/doctest/issues/618
+#include <string>
+#include <vector>
+
+TEST_SUITE_BEGIN("util");
+
+// RAII helper class for test file management
+class TestFileGuard
+{
+private:
+ std::string filename_;
+
+public:
+ explicit TestFileGuard(const std::string& filename) : filename_(filename)
+ {
+ }
+
+ ~TestFileGuard()
+ {
+ try {
+ if (std::filesystem::exists(filename_)) {
+ std::filesystem::remove(filename_);
+ }
+ } catch (...) {
+ // Ignore cleanup errors in destructor
+ }
+ }
+
+ void
+ create_file(const std::string& content)
+ {
+ std::ofstream outfile;
+ outfile.open(filename_);
+ if (outfile.is_open()) {
+ outfile << content;
+ outfile.close();
+ }
+ }
+
+ const std::string&
+ filename() const
+ {
+ return filename_;
+ }
+};
+
+TEST_CASE("util::split_preprocess_file_in_clang_cuda")
+{
+ SUBCASE("normal")
+ {
+ TestFileGuard guard("test_normal.txt");
+ std::string content = R"(# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 3
+void caller() {
+ add<<<10000, 1>>>(__null,__null);
+}
+# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 3
+)";
+ guard.create_file(content);
+
+ auto result = util::split_preprocess_file_in_clang_cuda(guard.filename());
+
+ REQUIRE(result.size() == 2);
+ CHECK(result[0] == R"(# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 3
+void caller() {
+ add<<<10000, 1>>>(__null,__null);
+}
+)");
+ CHECK(result[1] == R"(# 1 "test_cuda.cu"
+# 1 "<built-in>" 1
+# 1 "<built-in>" 3
+)");
+ }
+
+ SUBCASE("non-existent file")
+ {
+ std::string nonexistent_file = "nonexistent_file_12345.txt";
+
+ auto result = util::split_preprocess_file_in_clang_cuda(nonexistent_file);
+
+ CHECK(result.empty());
+ }
+
+ SUBCASE("empty file")
+ {
+ TestFileGuard guard("test_empty.txt");
+ guard.create_file("");
+
+ auto result = util::split_preprocess_file_in_clang_cuda(guard.filename());
+
+ CHECK(result.empty());
+ }
+}
+
+TEST_SUITE_END();