diff --git a/.github/workflows/tests-ubuntu.yaml b/.github/workflows/tests-ubuntu.yaml index 5c25a06e9..3f5edf398 100644 --- a/.github/workflows/tests-ubuntu.yaml +++ b/.github/workflows/tests-ubuntu.yaml @@ -364,6 +364,23 @@ jobs: -B build \ -S /src/install_test cmake --build build + rm -rf build + + if [ 'xcuda' = 'x${{matrix.backend.name}}' ] + then + export CXX=$Kokkos_ROOT/bin/nvcc_wrapper + fi + + cmake \ + -D CMAKE_EXPORT_COMPILE_COMMANDS=ON \ + -B build \ + -S /src/install_test/check_macros + python3 \ + /src/install_test/check_macros/check_macros.py \ + --with-src /src/install_test/check_macros/with_my_lib.cpp \ + --without-src /src/install_test/check_macros/without_my_lib.cpp \ + --reference /src/install_test/check_macros/whitelist_macros.json \ + -p ./build EOF docker run \ diff --git a/include/ddc/detail/macros.hpp b/include/ddc/detail/macros.hpp index 2ee84cf06..d0ecab059 100644 --- a/include/ddc/detail/macros.hpp +++ b/include/ddc/detail/macros.hpp @@ -6,11 +6,11 @@ #include -#if defined(__NVCC__) +#define DDC_MAKE_PRAGMA(X) _Pragma(#X) -# define DDC_MAKE_PRAGMA(X) _Pragma(#X) +#if defined(__NVCC__) -# if defined __NVCC_DIAG_PRAGMA_SUPPORT__ +# if defined(__NVCC_DIAG_PRAGMA_SUPPORT__) # define DDC_NV_DIAGNOSTIC_PUSH DDC_MAKE_PRAGMA(nv_diagnostic push) # define DDC_NV_DIAGNOSTIC_POP DDC_MAKE_PRAGMA(nv_diagnostic pop) # define DDC_NV_DIAG_SUPPRESS(X) DDC_MAKE_PRAGMA(nv_diag_suppress X) @@ -20,22 +20,21 @@ # define DDC_NV_DIAG_SUPPRESS(X) DDC_MAKE_PRAGMA(diag_suppress X) # endif -# define DDC_IF_NVCC_THEN_PUSH(X) DDC_NV_DIAGNOSTIC_PUSH -# define DDC_IF_NVCC_THEN_SUPPRESS(X) DDC_NV_DIAG_SUPPRESS(X) -# define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X) \ - DDC_NV_DIAGNOSTIC_PUSH \ - DDC_NV_DIAG_SUPPRESS(X) -# define DDC_IF_NVCC_THEN_POP DDC_NV_DIAGNOSTIC_POP - #else -# define DDC_IF_NVCC_THEN_PUSH(X) -# define DDC_IF_NVCC_THEN_SUPPRESS(X) -# define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X) -# define DDC_IF_NVCC_THEN_POP +# define DDC_NV_DIAGNOSTIC_PUSH +# define DDC_NV_DIAGNOSTIC_POP +# define DDC_NV_DIAG_SUPPRESS(X) #endif +#define DDC_IF_NVCC_THEN_PUSH(X) DDC_NV_DIAGNOSTIC_PUSH +#define DDC_IF_NVCC_THEN_SUPPRESS(X) DDC_NV_DIAG_SUPPRESS(X) +#define DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS(X) \ + DDC_NV_DIAGNOSTIC_PUSH \ + DDC_NV_DIAG_SUPPRESS(X) +#define DDC_IF_NVCC_THEN_POP DDC_NV_DIAGNOSTIC_POP + #if defined(__HIP_DEVICE_COMPILE__) # define DDC_CURRENT_KOKKOS_SPACE Kokkos::HIPSpace #elif defined(__CUDA_ARCH__) diff --git a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs.hpp b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs.hpp index d1c1c6d31..e78cb6e2f 100644 --- a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs.hpp +++ b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs.hpp @@ -5,8 +5,7 @@ // clang-format off // NOLINTBEGIN(*) -#ifndef KOKKOSBATCHED_GBTRS_HPP_ -#define KOKKOSBATCHED_GBTRS_HPP_ +#pragma once #include @@ -53,7 +52,5 @@ struct SerialGbtrs { #include "KokkosBatched_Gbtrs_Serial_Impl.hpp" -#endif // KOKKOSBATCHED_GBTRS_HPP_ - // NOLINTEND(*) // clang-format on diff --git a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs_Serial_Impl.hpp b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs_Serial_Impl.hpp index 7758a9b5c..553a412de 100644 --- a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs_Serial_Impl.hpp +++ b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Gbtrs_Serial_Impl.hpp @@ -5,8 +5,7 @@ // clang-format off // NOLINTBEGIN(*) -#ifndef KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_ -#define KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_ +#pragma once #include #include @@ -164,7 +163,5 @@ struct SerialGbtrs { }; } // namespace KokkosBatched -#endif // KOKKOSBATCHED_GBTRS_SERIAL_IMPL_HPP_ - // NOLINTEND(*) // clang-format on diff --git a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs.hpp b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs.hpp index 633b90b8a..58df012d4 100644 --- a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs.hpp +++ b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs.hpp @@ -5,8 +5,7 @@ // clang-format off // NOLINTBEGIN(*) -#ifndef KOKKOSBATCHED_GETRS_HPP_ -#define KOKKOSBATCHED_GETRS_HPP_ +#pragma once #include @@ -42,7 +41,5 @@ struct SerialGetrs { #include "KokkosBatched_Getrs_Serial_Impl.hpp" -#endif // KOKKOSBATCHED_GETRS_HPP_ - // NOLINTEND(*) // clang-format on diff --git a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs_Serial_Impl.hpp b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs_Serial_Impl.hpp index 4840c1280..e9ab71e82 100644 --- a/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs_Serial_Impl.hpp +++ b/include/ddc/kernels/splines/kokkos-kernels-ext/KokkosBatched_Getrs_Serial_Impl.hpp @@ -5,8 +5,7 @@ // clang-format off // NOLINTBEGIN(*) -#ifndef KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_ -#define KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_ +#pragma once #include #include @@ -93,7 +92,5 @@ struct SerialGetrs { }; } // namespace KokkosBatched -#endif // KOKKOSBATCHED_GETRS_SERIAL_IMPL_HPP_ - // NOLINTEND(*) // clang-format on diff --git a/install_test/check_macros/CMakeLists.txt b/install_test/check_macros/CMakeLists.txt new file mode 100644 index 000000000..2602ad5fa --- /dev/null +++ b/install_test/check_macros/CMakeLists.txt @@ -0,0 +1,11 @@ +# Copyright (C) The DDC development team, see COPYRIGHT.md file +# +# SPDX-License-Identifier: MIT + +cmake_minimum_required(VERSION 3.22) +project(test-ddc-macros LANGUAGES CXX) + +find_package(DDC 0.8 REQUIRED COMPONENTS fft pdi splines) + +add_library(check-macros with_my_lib.cpp without_my_lib.cpp) +target_link_libraries(check-macros PRIVATE DDC::core DDC::fft DDC::pdi DDC::splines) diff --git a/install_test/check_macros/check_macros.py b/install_test/check_macros/check_macros.py new file mode 100644 index 000000000..ab5913f9f --- /dev/null +++ b/install_test/check_macros/check_macros.py @@ -0,0 +1,221 @@ +#!/usr/bin/env python3 + +# Copyright (C) The DDC development team, see COPYRIGHT.md file +# +# SPDX-License-Identifier: MIT + +""" +Compare the difference in preprocessor macros between two source files +(one with a library and one without), and check if that diff matches a reference. + +Uses compile_commands.json to simulate compilation and extract preprocessor macros. +The reference file must be a JSON file containing a list of expected #define macros. +""" + +import json +import re +import subprocess +import shlex +from pathlib import Path +import argparse +import sys + + +def extract_command(entry): + """ + Modify compile command to only run preprocessor: remove -c/-o, add -dM -E. + + Args: + entry (dict): An entry from compile_commands.json + + Returns: + tuple[list[str], Path]: The modified command and working directory + """ + parts = shlex.split(entry["command"]) + new_parts = [] + skip_next = False + for part in parts: + if skip_next: + skip_next = False + continue + if part == "-c": + continue + if part == "-o": + skip_next = True + continue + new_parts.append(part) + new_parts += ["-dM", "-E", entry["file"]] + return new_parts, Path(entry["directory"]) + + +def run_preprocessor(command, cwd): + """ + Run the preprocessor and return unique macro lines. + + Args: + command (list[str]): Compiler command + cwd (Path): Working directory + + Returns: + set[str]: Set of macro lines + """ + result = subprocess.run( + command, cwd=cwd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, text=True, check=True + ) + if result.returncode != 0: + raise RuntimeError(f"Command failed:\n{' '.join(command)}\n{result.stderr}") + return set(result.stdout.splitlines()) + + +def find_entry(commands, source_path): + """ + Find compile command for the given source file. + + Args: + commands (list[dict]): compile_commands.json content + source_path (Path): Path to source file + + Returns: + dict: Matching compile command entry + """ + for entry in commands: + if Path(entry["file"]) == source_path: + return entry + raise ValueError(f"Source file {source_path} not found in compile_commands.json") + + +def prepare_commands(args): + """ + Load compilation database and extract preprocessor commands for both source files. + + Args: + args (argparse.Namespace): Parsed command-line arguments + + Returns: + tuple[set[str], set[str]]: Macros with and without the library + """ + commands = json.loads((args.p / "compile_commands.json").read_text()) + + entry_with = find_entry(commands, args.with_src.resolve()) + entry_without = find_entry(commands, args.without_src.resolve()) + + cmd_with, dir_with = extract_command(entry_with) + cmd_without, dir_without = extract_command(entry_without) + + print(f"Running preprocessor for: {args.with_src.name}") + macros_with = run_preprocessor(cmd_with, dir_with) + + print(f"Running preprocessor for: {args.without_src.name}") + macros_without = run_preprocessor(cmd_without, dir_without) + + macros_with_diff = macros_with - macros_without + macros_without_diff = macros_without - macros_with + + # to be removed as soon as possible + whitelist_macros = { + "#define KOKKOS_IMPL_PUBLIC_INCLUDE ", + "#define KOKKOS_IMPL_PUBLIC_INCLUDE_PROFILING_SCOPEDREGION ", + } + + if macros_without_diff - whitelist_macros: + print(macros_without_diff) + raise ValueError("Set should be empty") + + return macros_with_diff, macros_without_diff + + +def extract_macro_diff(macros_with, macros_without): + """ + Compute the difference in #define macros between with/without source. + + Args: + macros_with (set[str]): Macros when library is included + macros_without (set[str]): Macros without library + + Returns: + list[str]: Sorted list of macro differences + """ + macro_pattern = r"#define\s+([A-Za-z_][A-Za-z0-9_]*)(\s+.*)?" + define_with = set() + for line in macros_with: + match = re.match(macro_pattern, line.strip()) + if match: + define_with.add(str(match.group(1))) + else: + raise ValueError(f"Unhandled macro {line}") + define_without = set() + for line in macros_without: + match = re.match(macro_pattern, line.strip()) + if match: + define_without.add(str(match.group(1))) + else: + raise ValueError(f"Unhandled macro {line}") + return sorted(define_with - define_without) + + +def compare_macro_diff(actual_diff, reference_file): + """ + Compare computed macro diff to expected one from JSON reference file. + + Args: + actual_diff (list[str]): Macros computed by diff + reference_file (Path): Path to JSON reference + + Returns: + bool: True if matches, False otherwise + """ + try: + ref_macros = set(json.loads(reference_file.read_text())) + except Exception as exception: + raise ValueError(f"Error reading reference JSON file: {exception}") from exception + + added = sorted([m for m in actual_diff if m not in ref_macros]) + removed = sorted([m for m in ref_macros if m not in actual_diff]) + + has_error = False + + if added: + print("\n--- Unexpected Macros (in diff, not in reference) ---") + print("\n".join(added)) + has_error = True + + if removed: + print("\n--- Missing Macros (in reference, not in diff) ---") + print("\n".join(removed)) + has_error = True + + if not has_error: + print("\n✅ Macro diff matches the reference exactly.") + return not has_error + + +def main(): + """Main entry point: run preprocessor and compare macro diff with reference.""" + parser = argparse.ArgumentParser( + description="Compare macro diff against a JSON reference file." + ) + parser.add_argument("--with-src", required=True, type=Path, help="Source file with the library") + parser.add_argument( + "--without-src", required=True, type=Path, help="Source file without the library" + ) + parser.add_argument( + "--reference", required=True, type=Path, help="Reference JSON file with expected macro diff" + ) + parser.add_argument( + "-p", + default="build", + type=Path, + help="Path to build directory", + ) + + args = parser.parse_args() + + macros_with, macros_without = prepare_commands(args) + actual_diff = extract_macro_diff(macros_with, macros_without) + success = compare_macro_diff(actual_diff, args.reference) + + sys.exit(0 if success else 1) + + +if __name__ == "__main__": + main() diff --git a/install_test/check_macros/whitelist_macros.json b/install_test/check_macros/whitelist_macros.json new file mode 100644 index 000000000..be8521e20 --- /dev/null +++ b/install_test/check_macros/whitelist_macros.json @@ -0,0 +1,14 @@ +[ + "DDC_BUILD_DEPRECATED_CODE", + "DDC_BUILD_DOUBLE_PRECISION", + "DDC_CURRENT_KOKKOS_SPACE", + "DDC_IF_NVCC_THEN_POP", + "DDC_IF_NVCC_THEN_PUSH", + "DDC_IF_NVCC_THEN_PUSH_AND_SUPPRESS", + "DDC_IF_NVCC_THEN_SUPPRESS", + "DDC_MAKE_PRAGMA", + "DDC_MDSPAN_ACCESS_OP", + "DDC_NV_DIAGNOSTIC_POP", + "DDC_NV_DIAGNOSTIC_PUSH", + "DDC_NV_DIAG_SUPPRESS" +] diff --git a/install_test/check_macros/whitelist_macros.json.license b/install_test/check_macros/whitelist_macros.json.license new file mode 100644 index 000000000..6688f417e --- /dev/null +++ b/install_test/check_macros/whitelist_macros.json.license @@ -0,0 +1,3 @@ +Copyright (C) The DDC development team, see COPYRIGHT.md file + +SPDX-License-Identifier: MIT diff --git a/install_test/check_macros/with_my_lib.cpp b/install_test/check_macros/with_my_lib.cpp new file mode 100644 index 000000000..c45886282 --- /dev/null +++ b/install_test/check_macros/with_my_lib.cpp @@ -0,0 +1,8 @@ +// Copyright (C) The DDC development team, see COPYRIGHT.md file +// +// SPDX-License-Identifier: MIT + +#include +#include +#include +#include diff --git a/install_test/check_macros/without_my_lib.cpp b/install_test/check_macros/without_my_lib.cpp new file mode 100644 index 000000000..50e30c6da --- /dev/null +++ b/install_test/check_macros/without_my_lib.cpp @@ -0,0 +1,31 @@ +// Copyright (C) The DDC development team, see COPYRIGHT.md file +// +// SPDX-License-Identifier: MIT + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#if __has_include() +# include +#else +# include +#endif +#include +#include + +#include +#include +#include