diff --git a/CMakeLists.txt b/CMakeLists.txt index dd41b6834f..66e2dcd533 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,7 +347,7 @@ function(common_compile_options TARGET) if(CMAKE_CXX_COMPILER_ID MATCHES "GNU") target_compile_options(${TARGET} PRIVATE "--coverage") target_link_options(${TARGET} PRIVATE "gcov") - elseif(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + elseif(COMPILER_IS_CLANG OR COMPILER_IS_CLANG_CL) target_compile_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping") target_link_options(${TARGET} PRIVATE "-fprofile-instr-generate" "-fcoverage-mapping") else() @@ -451,10 +451,13 @@ if((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (CMAKE_CXX_SIMULATE_ID STREQUAL set(COMPILER_IS_CLANG_CL TRUE) endif() -if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR - (CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR +if((CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang") OR ((CMAKE_CXX_COMPILER_ID STREQUAL "Clang") AND (NOT COMPILER_IS_CLANG_CL))) + set(COMPILER_IS_CLANG TRUE) +endif() + +if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU") OR COMPILER_IS_CLANG) set(COMPILER_IS_LIKE_GNU TRUE) endif() @@ -555,8 +558,7 @@ function(tint_default_compile_options TARGET) ${COMMON_GNU_OPTIONS} ) - if (("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") OR - ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "AppleClang")) + if (COMPILER_IS_CLANG) target_compile_options(${TARGET} PRIVATE ${COMMON_CLANG_OPTIONS} ) @@ -655,9 +657,11 @@ add_custom_target(tint-format COMMENT "Running formatter" VERBATIM) +if (DAWN_EMIT_COVERAGE) + add_subdirectory(tools/src/cmd/turbo-cov) -if (DAWN_EMIT_COVERAGE AND CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # Generates a lcov.info file at the project root. + # The tint-generate-coverage target generates a lcov.info file at the project + # root, holding the code coverage for all the tint_unitests. # This can be used by tools such as VSCode's Coverage Gutters extension to # visualize code coverage in the editor. get_filename_component(CLANG_BIN_DIR ${CMAKE_C_COMPILER} DIRECTORY) diff --git a/src/dawn/node/CMakeLists.txt b/src/dawn/node/CMakeLists.txt index ef0d5c24d6..030cf2851b 100644 --- a/src/dawn/node/CMakeLists.txt +++ b/src/dawn/node/CMakeLists.txt @@ -102,8 +102,14 @@ if (WIN32) # Generate the NapiSymbols.lib from the NapiSymbols.def file set(NAPI_SYMBOLS_LIB "${DAWN_NODE_GEN_DIR}/NapiSymbols.lib") # Resolve path to lib.exe - get_filename_component(VS_BIN_DIR "${CMAKE_LINKER}" DIRECTORY) - set(LIB_EXE "${VS_BIN_DIR}/lib.exe") + get_filename_component(LINKER_BIN_DIR "${CMAKE_LINKER}" DIRECTORY) + if (EXISTS "${LINKER_BIN_DIR}/lib.exe") + set(LIB_EXE "${LINKER_BIN_DIR}/lib.exe") + elseif (EXISTS "${LINKER_BIN_DIR}/lld-link.exe") + set(LIB_EXE "${LINKER_BIN_DIR}/lld-link.exe") + else() + message(FATAL_ERROR "unable to find lib.exe or lld-link.exe") + endif() add_custom_command( COMMAND "${LIB_EXE}" "/DEF:${NAPI_SYMBOLS_DEF}" diff --git a/src/dawn/node/Module.cpp b/src/dawn/node/Module.cpp index 08344b2c6b..96afacc998 100644 --- a/src/dawn/node/Module.cpp +++ b/src/dawn/node/Module.cpp @@ -62,7 +62,7 @@ Napi::Value CreateGPU(const Napi::CallbackInfo& info) { #ifdef DAWN_EMIT_COVERAGE struct Coverage { Coverage() : output_path_{GetOutputPath()} { - __llvm_profile_set_filename(output_path_.c_str()); + __llvm_profile_set_filename(output_path_.string().c_str()); } ~Coverage() { std::filesystem::remove(output_path_); } @@ -75,7 +75,7 @@ struct Coverage { static Napi::Value End(const Napi::CallbackInfo& info) { __llvm_profile_write_file(); auto* coverage = static_cast(info.Data()); - return Napi::String::New(info.Env(), coverage->output_path_.c_str()); + return Napi::String::New(info.Env(), coverage->output_path_.string().c_str()); } private: diff --git a/tools/src/cmd/run-cts/main.go b/tools/src/cmd/run-cts/main.go index 3bd98c6757..e2a6d89fa5 100644 --- a/tools/src/cmd/run-cts/main.go +++ b/tools/src/cmd/run-cts/main.go @@ -259,17 +259,36 @@ func run() error { } if genCoverage { - llvmCov, err := exec.LookPath("llvm-cov") + dawnOutDir := filepath.Dir(dawnNode) + + profdata, err := exec.LookPath("llvm-profdata") if err != nil { - return fmt.Errorf("failed to find LLVM, required for --coverage") + profdata = "" + if runtime.GOOS == "darwin" { + profdata = "/Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/llvm-profdata" + if !fileutils.IsExe(profdata) { + profdata = "" + } + } } - turboCov := filepath.Join(filepath.Dir(dawnNode), "turbo-cov"+fileutils.ExeExt) + if profdata == "" { + return fmt.Errorf("failed to find llvm-profdata, required for --coverage") + } + + llvmCov := "" + turboCov := filepath.Join(dawnOutDir, "turbo-cov"+fileutils.ExeExt) if !fileutils.IsExe(turboCov) { turboCov = "" + if path, err := exec.LookPath("llvm-cov"); err == nil { + llvmCov = path + } else { + return fmt.Errorf("failed to find turbo-cov or llvm-cov") + } } r.covEnv = &cov.Env{ - LLVMBin: filepath.Dir(llvmCov), + Profdata: profdata, Binary: dawnNode, + Cov: llvmCov, TurboCov: turboCov, } } diff --git a/tools/src/cmd/turbo-cov/main.cpp b/tools/src/cmd/turbo-cov/main.cpp index 817111a7e7..5bdd4d97f7 100644 --- a/tools/src/cmd/turbo-cov/main.cpp +++ b/tools/src/cmd/turbo-cov/main.cpp @@ -24,6 +24,11 @@ #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/ProfileData/InstrProfReader.h" +#if defined(_MSC_VER) +#include // _O_BINARY +#include // _setmode +#endif + namespace { template @@ -40,6 +45,13 @@ void emit(const llvm::StringRef& str) { } // namespace int main(int argc, const char** argv) { +#if defined(_MSC_VER) + // Change stdin & stdout from text mode to binary mode. + // This ensures sequences of \r\n are not changed to \n. + _setmode(_fileno(stdin), _O_BINARY); + _setmode(_fileno(stdout), _O_BINARY); +#endif + if (argc < 3) { fprintf(stderr, "turbo-cov \n"); return 1; diff --git a/tools/src/cov/import.go b/tools/src/cov/import.go index 104d604dd7..176c6bfe6e 100644 --- a/tools/src/cov/import.go +++ b/tools/src/cov/import.go @@ -42,9 +42,10 @@ type Coverage struct { // Env holds the environment settings for performing coverage processing. type Env struct { - LLVMBin string // path to the LLVM bin directory + Profdata string // path to the llvm-profdata tool Binary string // path to the executable binary - TurboCov string // path to turbo-cov (optional) + Cov string // path to the llvm-cov tool (one of Cov or TurboCov must be supplied) + TurboCov string // path to the turbo-cov tool (one of Cov or TurboCov must be supplied) } // RuntimeEnv returns the environment variable key=value pair for setting @@ -92,33 +93,37 @@ func (e Env) AllSourceFiles() *Coverage { // Import uses the llvm-profdata and llvm-cov tools to import the coverage // information from a .profraw file. func (e Env) Import(profrawPath string) (*Coverage, error) { - llvmProfdataExe := filepath.Join(e.LLVMBin, "llvm-profdata"+fileutils.ExeExt) - llvmCovExe := filepath.Join(e.LLVMBin, "llvm-cov"+fileutils.ExeExt) - profdata := profrawPath + ".profdata" + defer os.Remove(profdata) - if err := exec.Command( - llvmProfdataExe, + if e.Profdata == "" { + return nil, fmt.Errorf("cov.Env.Profdata must be specified") + } + if e.TurboCov == "" && e.Cov == "" { + return nil, fmt.Errorf("One of cov.Env.TurboCov or cov.Env.Cov must be specified") + } + + if out, err := exec.Command( + e.Profdata, "merge", "-sparse", profrawPath, "-output", - profdata).Run(); err != nil { - return nil, fmt.Errorf("llvm-profdata errored: %w", err) + profdata).CombinedOutput(); err != nil { + return nil, fmt.Errorf("llvm-profdata errored: %w\n%v", err, string(out)) } - defer os.Remove(profdata) if e.TurboCov == "" { data, err := exec.Command( - llvmCovExe, + e.Cov, "export", e.Binary, "-instr-profile="+profdata, "-format=text", "-skip-expansions", - "-skip-functions").Output() + "-skip-functions").CombinedOutput() if err != nil { - return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err) + return nil, fmt.Errorf("llvm-cov errored: %v\n%v", string(data), err) } cov, err := e.parseCov(data) if err != nil { @@ -127,9 +132,9 @@ func (e Env) Import(profrawPath string) (*Coverage, error) { return cov, nil } - data, err := exec.Command(e.TurboCov, e.Binary, profdata).Output() + data, err := exec.Command(e.TurboCov, e.Binary, profdata).CombinedOutput() if err != nil { - return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(err.(*exec.ExitError).Stderr), err) + return nil, fmt.Errorf("turbo-cov errored: %v\n%v", string(data), err) } cov, err := e.parseTurboCov(data) if err != nil { diff --git a/tools/src/cov/serialization.go b/tools/src/cov/serialization.go index d0d11f4e51..76334c173a 100644 --- a/tools/src/cov/serialization.go +++ b/tools/src/cov/serialization.go @@ -19,6 +19,7 @@ import ( "compress/zlib" "fmt" "io" + "path/filepath" "runtime/debug" "sort" "strconv" @@ -156,7 +157,7 @@ func (t *Tree) writeFilesJSON(spansByID map[SpanID]Span, sb *strings.Builder) { sb.WriteString(`,`) } sb.WriteString(`"`) - sb.WriteString(path) + sb.WriteString(filepath.ToSlash(path)) sb.WriteString(`":`) sb.WriteString(`{`) if totalLines := file.allSpans.NumLines(); totalLines > 0 {