From 1dc9b38b3253e28b6986a8c9ee7e59047fcb8786 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:21:21 +0800 Subject: [PATCH 1/7] Spell restrict and forced inlining portably for MSVC `__restrict__` and `__attribute__((always_inline))` are GNU spellings. MSVC accepts neither: it spells the restrict qualifier `__restrict` and forced inlining `__forceinline`, so every declaration carrying one of these failed to parse under cl. Add HPTT_RESTRICT to hptt_types.h and use it in place of `__restrict__` throughout transpose.h and transpose.cpp, and give macros.h an INLINE definition for _MSC_VER. HPTT_RESTRICT lives in hptt_types.h rather than macros.h because transpose.h -- a public, installed header -- applies it to the A_ and B_ data members, and macros.h also defines the unprefixed `INLINE`, which should not leak into consumers' translation units. clang-cl defines _MSC_VER but understands the GNU spelling, so it is excluded from the MSVC branch. Co-Authored-By: Claude --- include/hptt_types.h | 15 +++++++++++++++ include/macros.h | 2 ++ include/transpose.h | 4 ++-- src/transpose.cpp | 34 +++++++++++++++++----------------- 4 files changed, 36 insertions(+), 19 deletions(-) diff --git a/include/hptt_types.h b/include/hptt_types.h index 170288e..eee77d3 100644 --- a/include/hptt_types.h +++ b/include/hptt_types.h @@ -9,6 +9,21 @@ #define REGISTER_BITS 128 // ARM #endif +// `restrict` is a C99 keyword with no C++ equivalent, so every compiler +// exposes it under its own spelling: GCC, Clang and ICC accept +// `__restrict__`, MSVC only accepts `__restrict`. clang-cl defines +// _MSC_VER but still understands the GNU spelling. +// +// This lives in hptt_types.h rather than macros.h because transpose.h -- +// a public, installed header -- uses it on the A_/B_ data members. +// macros.h additionally defines the unprefixed `INLINE`, which must not +// leak into consumers' translation units. +#if defined(_MSC_VER) && !defined(__clang__) +#define HPTT_RESTRICT __restrict +#else +#define HPTT_RESTRICT __restrict__ +#endif + namespace hptt { /** diff --git a/include/macros.h b/include/macros.h index 596af68..19f7fc7 100644 --- a/include/macros.h +++ b/include/macros.h @@ -24,6 +24,8 @@ #define INLINE __forceinline #elif defined(__GNUC__) || defined(__GNUG__) #define INLINE __attribute__((always_inline)) inline +#elif defined(_MSC_VER) +#define INLINE __forceinline #endif #ifdef _OPENMP diff --git a/include/transpose.h b/include/transpose.h index 82f0239..eca08e9 100644 --- a/include/transpose.h +++ b/include/transpose.h @@ -247,8 +247,8 @@ class Transpose void executeEstimate(const Plan *plan) noexcept; // almost identical to execute, but it just executes few iterations and then extrapolates double getTimeLimit() const; - const floatType* __restrict__ A_; //!< rawdata pointer for A - floatType* __restrict__ B_; //!< rawdata pointer for B + const floatType* HPTT_RESTRICT A_; //!< rawdata pointer for A + floatType* HPTT_RESTRICT B_; //!< rawdata pointer for B floatType alpha_; //!< scaling factor for A floatType beta_; //!< scaling factor for B int dim_; //!< dimension of the tensor diff --git a/src/transpose.cpp b/src/transpose.cpp index 83ae31b..a4b7fa4 100644 --- a/src/transpose.cpp +++ b/src/transpose.cpp @@ -34,7 +34,7 @@ namespace hptt { template struct micro_kernel { - static void execute(const floatType* __restrict__ A, const size_t lda, floatType* __restrict__ B, const size_t ldb, const floatType alpha, const floatType beta) + static void execute(const floatType* HPTT_RESTRICT A, const size_t lda, floatType* HPTT_RESTRICT B, const size_t ldb, const floatType alpha, const floatType beta) { constexpr int n = (REGISTER_BITS/8) / sizeof(floatType); @@ -77,7 +77,7 @@ static INLINE void prefetch(const floatType* A, const int lda) template struct micro_kernel { - static void execute(const double* __restrict__ A, const size_t lda, double* __restrict__ B, const size_t ldb, const double alpha ,const double beta) + static void execute(const double* HPTT_RESTRICT A, const size_t lda, double* HPTT_RESTRICT B, const size_t ldb, const double alpha ,const double beta) { __m256d reg_alpha = _mm256_set1_pd(alpha); // do not alter the content of B __m256d reg_beta = _mm256_set1_pd(beta); // do not alter the content of B @@ -134,7 +134,7 @@ struct micro_kernel template struct micro_kernel { - static void execute(const float* __restrict__ A, const size_t lda, float* __restrict__ B, const size_t ldb, const float alpha ,const float beta) + static void execute(const float* HPTT_RESTRICT A, const size_t lda, float* HPTT_RESTRICT B, const size_t ldb, const float alpha ,const float beta) { __m256 reg_alpha = _mm256_set1_ps(alpha); // do not alter the content of B __m256 reg_beta = _mm256_set1_ps(beta); // do not alter the content of B @@ -246,7 +246,7 @@ static INLINE void prefetch(const floatType* A, const int lda) { } template struct micro_kernel { - static void execute(const float* __restrict__ A, const size_t lda, float* __restrict__ B, const size_t ldb, const float alpha ,const float beta) + static void execute(const float* HPTT_RESTRICT A, const size_t lda, float* HPTT_RESTRICT B, const size_t ldb, const float alpha ,const float beta) { float32x4_t reg_alpha = vdupq_n_f32(alpha); float32x4_t reg_beta = vdupq_n_f32(beta); @@ -304,7 +304,7 @@ struct micro_kernel //template //struct micro_kernel //{ -// static void execute(const float* __restrict__ A, const size_t lda, float* __restrict__ B, const size_t ldb, const float alpha ,const float beta) +// static void execute(const float* HPTT_RESTRICT A, const size_t lda, float* HPTT_RESTRICT B, const size_t ldb, const float alpha ,const float beta) // { // vector float reg_alpha = vec_splats(alpha); // @@ -368,8 +368,8 @@ struct micro_kernel template -static INLINE void macro_kernel_scalar(const floatType* __restrict__ A, const size_t lda, int blockingA, - floatType* __restrict__ B, const size_t ldb, int blockingB, +static INLINE void macro_kernel_scalar(const floatType* HPTT_RESTRICT A, const size_t lda, int blockingA, + floatType* HPTT_RESTRICT B, const size_t ldb, int blockingB, const floatType alpha ,const floatType beta) { #ifdef DEBUG @@ -393,8 +393,8 @@ static INLINE void macro_kernel_scalar(const floatType* __restrict__ A, const si } template -static INLINE void macro_kernel(const floatType* __restrict__ A, const floatType* __restrict__ Anext, const size_t lda, - floatType* __restrict__ B, const floatType* __restrict__ Bnext, const size_t ldb, +static INLINE void macro_kernel(const floatType* HPTT_RESTRICT A, const floatType* HPTT_RESTRICT Anext, const size_t lda, + floatType* HPTT_RESTRICT B, const floatType* HPTT_RESTRICT Bnext, const size_t ldb, const floatType alpha ,const floatType beta) { constexpr int blocking_micro_ = REGISTER_BITS/8 / sizeof(floatType); @@ -569,8 +569,8 @@ static INLINE void macro_kernel(const floatType* __restrict__ A, const floatType } template -void transpose_int_scalar( const floatType* __restrict__ A, int sizeStride1A, - floatType* __restrict__ B, int sizeStride1B, const floatType alpha, const floatType beta, const ComputeNode* plan) +void transpose_int_scalar( const floatType* HPTT_RESTRICT A, int sizeStride1A, + floatType* HPTT_RESTRICT B, int sizeStride1B, const floatType alpha, const floatType beta, const ComputeNode* plan) { const int32_t end = plan->end; const size_t lda = plan->lda; @@ -600,8 +600,8 @@ void transpose_int_scalar( const floatType* __restrict__ A, int sizeStride1A, } } template -void transpose_int( const floatType* __restrict__ A, const floatType* __restrict__ Anext, - floatType* __restrict__ B, const floatType* __restrict__ Bnext, const floatType alpha, const floatType beta, +void transpose_int( const floatType* HPTT_RESTRICT A, const floatType* HPTT_RESTRICT Anext, + floatType* HPTT_RESTRICT B, const floatType* HPTT_RESTRICT Bnext, const floatType alpha, const floatType beta, const ComputeNode* plan) { const int32_t end = plan->end - (plan->inc - 1); @@ -681,7 +681,7 @@ void transpose_int( const floatType* __restrict__ A, const floatType* __restrict } template -void transpose_int_constStride1( const floatType* __restrict__ A, floatType* __restrict__ B, const floatType alpha, const floatType beta, +void transpose_int_constStride1( const floatType* HPTT_RESTRICT A, floatType* HPTT_RESTRICT B, const floatType alpha, const floatType beta, const ComputeNode* plan) { const int32_t end = plan->end - (plan->inc - 1); @@ -867,7 +867,7 @@ void Transpose::executeEstimate(const Plan *plan) noexcept template -static void axpy_1D( const floatType* __restrict__ A, floatType* __restrict__ B, const int myStart, const int myEnd, const floatType alpha, const floatType beta, int numThreads) +static void axpy_1D( const floatType* HPTT_RESTRICT A, floatType* HPTT_RESTRICT B, const int myStart, const int myEnd, const floatType alpha, const floatType beta, int numThreads) { if( !betaIsZero ) { @@ -900,8 +900,8 @@ static void axpy_1D( const floatType* __restrict__ A, floatType* __restrict__ B, } template -static void axpy_2D( const floatType* __restrict__ A, const int lda, - floatType* __restrict__ B, const int ldb, +static void axpy_2D( const floatType* HPTT_RESTRICT A, const int lda, + floatType* HPTT_RESTRICT B, const int ldb, const int n0, const int myStart, const int myEnd, const floatType alpha, const floatType beta, int numThreads) { if( !betaIsZero ) From 95006de6a89aae617b83758da3ff948a337affe3 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:21:45 +0800 Subject: [PATCH 2/7] Replace variable-length arrays with std::vector The Transpose constructor sized four int buffers by the runtime `dim` argument, and getBestLoopOrder() sized its cost matrix by `dim_*dim_`. Variable-length arrays are a C99 feature that C++ never adopted; GCC and Clang accept them as an extension, MSVC rejects them with C2131. Use std::vector for all five and pass .data() at the call sites that expect a raw pointer. Co-Authored-By: Claude --- src/transpose.cpp | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/transpose.cpp b/src/transpose.cpp index a4b7fa4..e9f3146 100644 --- a/src/transpose.cpp +++ b/src/transpose.cpp @@ -752,12 +752,15 @@ void transpose_int_constStride1( const floatType* HPTT_RESTRICT A, floatType* HP #ifdef _OPENMP omp_init_lock(&writelock); #endif - int tmpPerm[dim]; - int tmpSizeA[dim]; - int tmpOuterSizeA[dim]; - int tmpOuterSizeB[dim]; - accountForRowMajor(sizeA, outerSizeA, outerSizeB, perm, - tmpSizeA, tmpOuterSizeA, tmpOuterSizeB, tmpPerm, dim, useRowMajor); + // dim is a runtime value, and variable-length arrays are a GNU + // extension that MSVC rejects (C2131). + std::vector tmpPerm(dim); + std::vector tmpSizeA(dim); + std::vector tmpOuterSizeA(dim); + std::vector tmpOuterSizeB(dim); + accountForRowMajor(sizeA, outerSizeA, outerSizeB, perm, + tmpSizeA.data(), tmpOuterSizeA.data(), tmpOuterSizeB.data(), + tmpPerm.data(), dim, useRowMajor); sizeA_.resize(dim); perm_.resize(dim); @@ -775,10 +778,10 @@ void transpose_int_constStride1( const floatType* HPTT_RESTRICT A, floatType* HP threadIds_.push_back(i); } - verifyParameter(tmpSizeA, tmpPerm, tmpOuterSizeA, tmpOuterSizeB, dim); + verifyParameter(tmpSizeA.data(), tmpPerm.data(), tmpOuterSizeA.data(), tmpOuterSizeB.data(), dim); - // initializes dim_, outerSizeA, outerSizeB, sizeA and perm - skipIndices(tmpSizeA, tmpPerm, tmpOuterSizeA, tmpOuterSizeB, dim); + // initializes dim_, outerSizeA, outerSizeB, sizeA and perm + skipIndices(tmpSizeA.data(), tmpPerm.data(), tmpOuterSizeA.data(), tmpOuterSizeB.data(), dim); fuseIndices(); // initializes lda_ and ldb_ @@ -1726,7 +1729,9 @@ void Transpose::getBestLoopOrder( std::vector &loopOrder ) const } // create cost matrix; cost[i,idx] === cost for idx being at loop-level i - double costs[dim_*dim_]; + // dim_ is a runtime value, and variable-length arrays are a GNU + // extension that MSVC rejects (C2131). + std::vector costs(dim_*dim_); for(int i=0;i < dim_ ; ++i){ for(int idx=0;idx < dim_ ; ++idx){ //idx is at loop i double cost = 0; From 8c5095df57393d8926b42c501502dba3c94f6bc8 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:21:45 +0800 Subject: [PATCH 3/7] Declare the _Complex C API only where _Complex exists cTensorTranspose() and zTensorTranspose() are declared with the C99 `_Complex` type specifier. MSVC does not support `_Complex` when compiling C++: in C++ mode resolves to the STL header, which forwards to and never declares the UCRT's _Fcomplex / _Dcomplex. cl therefore rejects both declarations with C2146/C3646/C2062. Nothing in HPTT's own sources includes hptt.h, so this only breaks consumers of the installed headers -- but for those it makes the public header unusable on MSVC entirely. There is no portable spelling for these two signatures, so guard them on compilers that understand `_Complex`. clang-cl defines _MSC_VER but does understand it, so it keeps the declarations. Co-Authored-By: Claude --- include/hptt.h | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/include/hptt.h b/include/hptt.h index dda7f0a..025767a 100644 --- a/include/hptt.h +++ b/include/hptt.h @@ -301,6 +301,12 @@ void dTensorTranspose( const int *perm, const int dim, const double beta, double *B, const int *outerSizeB, const int numThreads, const int useRowMajor = 0); +// MSVC has no C99 `_Complex` type specifier when compiling C++: in C++ mode +// resolves to the STL header, which forwards to and +// never declares the UCRT's _Fcomplex / _Dcomplex. There is no portable +// spelling for these two signatures, so they are declared only where +// `_Complex` is understood. +#if !defined(_MSC_VER) || defined(__clang__) void cTensorTranspose( const int *perm, const int dim, const float _Complex alpha, bool conjA, const float _Complex *A, const int *sizeA, const int *outerSizeA, const float _Complex beta, float _Complex *B, const int *outerSizeB, @@ -310,4 +316,5 @@ void zTensorTranspose( const int *perm, const int dim, const double _Complex alpha, bool conjA, const double _Complex *A, const int *sizeA, const int *outerSizeA, const double _Complex beta, double _Complex *B, const int *outerSizeB, const int numThreads, const int useRowMajor = 0); +#endif } From 4f73ebf64da03cc9349dc6b9093261808e10eda2 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:24:03 +0800 Subject: [PATCH 4/7] Use MSVC's spelling for the architecture flags ENABLE_AVX appended the GCC/Clang flag -mavx unconditionally. cl does not accept it; the MSVC equivalent is /arch:AVX. Select per compiler. ENABLE_ARM appended -mfpu=neon, which is an ARM32 flag with no MSVC counterpart -- on MSVC ARM64 Advanced SIMD is part of the baseline ISA and /arch: only selects newer extensions. Skip the flag there and keep defining HPTT_ARCH_ARM, which is what selects the Neon kernels and REGISTER_BITS=128. Co-Authored-By: Claude --- CMakeLists.txt | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4d3b489..9bf3d6b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -69,10 +69,19 @@ elseif(CMAKE_CXX_COMPILER_ID STREQUAL "PGI") endif() if(ENABLE_AVX) - list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mavx) + if(MSVC) + list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS /arch:AVX) + else() + list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mavx) + endif() list(APPEND HPTT_PUBLIC_COMPILE_DEFINITIONS HPTT_ARCH_AVX) elseif(ENABLE_ARM) - list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mfpu=neon) + # MSVC targeting ARM64 needs no opt-in flag: Advanced SIMD (Neon) is + # part of the baseline ISA there, and cl's /arch: only selects newer + # extensions. + if(NOT MSVC) + list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mfpu=neon) + endif() list(APPEND HPTT_PUBLIC_COMPILE_DEFINITIONS HPTT_ARCH_ARM) elseif(ENABLE_IBM) list(APPEND HPTT_PRIVATE_COMPILE_OPTIONS -mtune=native -maltivec -mabi=altivec) From 7c6526d30feed4e005981d56acc734067b76f713 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:24:24 +0800 Subject: [PATCH 5/7] Add HPTT_BUILD_SHARED to make the shared library optional hptt_dyn was built unconditionally. A parent project that embeds HPTT via add_subdirectory() and links only hptt_static still paid for a second full compile of every source, and on Windows it also inherited the shared target's artifact-naming constraints for a library it never uses. Add HPTT_BUILD_SHARED (default ON, so standalone builds are unchanged) and create hptt_dyn only when it is set. Collect the enabled targets in HPTT_TARGETS so export(TARGETS) and install(TARGETS) both follow the option without naming hptt_dyn directly. Co-Authored-By: Claude --- CMakeLists.txt | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 9bf3d6b..4615759 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -21,6 +21,12 @@ option(FINE_TUNE "Fine-tune for the host CPU (uses -march=native; produces non-p # its headers from its own install layout). option(HPTT_INSTALL "Generate install rules for HPTT" ON) +# HPTT_BUILD_SHARED: ON by default so standalone builds keep producing +# libhptt.so / libhptt.dylib / hptt.dll. Parent projects that only ever +# link the static archive can set it OFF to halve build time and to skip +# the shared library entirely. +option(HPTT_BUILD_SHARED "Build the shared HPTT library (hptt_dyn)" ON) + # ----------------------------------------------------------------------------- # Build flags split by *visibility* to consumers. # @@ -132,15 +138,22 @@ endfunction() add_library(hptt_static STATIC ${HPTT_SRCS}) hptt_configure_target(hptt_static) -add_library(hptt_dyn SHARED ${HPTT_SRCS}) -hptt_configure_target(hptt_dyn) - # Namespaced ALIAS targets. add_subdirectory() consumers can now use the # same `hptt::hptt_static` / `hptt::hptt_dyn` names that find_package(hptt) # consumers would, so swapping integration mechanisms doesn't require # rewriting target_link_libraries() calls. add_library(hptt::hptt_static ALIAS hptt_static) -add_library(hptt::hptt_dyn ALIAS hptt_dyn) + +# Both the export() and install(TARGETS) calls below iterate this list so +# hptt_dyn drops out of them cleanly when HPTT_BUILD_SHARED=OFF. +set(HPTT_TARGETS hptt_static) + +if(HPTT_BUILD_SHARED) + add_library(hptt_dyn SHARED ${HPTT_SRCS}) + hptt_configure_target(hptt_dyn) + add_library(hptt::hptt_dyn ALIAS hptt_dyn) + list(APPEND HPTT_TARGETS hptt_dyn) +endif() # Generate hpttConfig.cmake into the build dir unconditionally so the # build-tree export below works even when HPTT_INSTALL=OFF. Parent @@ -158,12 +171,12 @@ configure_package_config_file( # the binary dir. Uses export(TARGETS ...) rather than export(EXPORT # hpttTargets) so it works without install rules (i.e. when # HPTT_INSTALL=OFF). -export(TARGETS hptt_static hptt_dyn +export(TARGETS ${HPTT_TARGETS} FILE "${CMAKE_CURRENT_BINARY_DIR}/hpttTargets.cmake" NAMESPACE hptt::) if(HPTT_INSTALL) - install(TARGETS hptt_static hptt_dyn + install(TARGETS ${HPTT_TARGETS} EXPORT hpttTargets ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} From 7122f251fab1374bf2720544adb077f6d7176638 Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:24:25 +0800 Subject: [PATCH 6/7] Export all symbols from the Windows DLL No declaration in HPTT is annotated with __declspec(dllexport), so on Windows the DLL exported nothing. The linker then emitted no import library at all, which leaves install(TARGETS hptt_dyn ARCHIVE ...) with no file to install and gives consumers nothing to link against. Set WINDOWS_EXPORT_ALL_SYMBOLS on hptt_dyn. CMake generates the .def file from the object files, which covers the four explicit Transpose instantiations and the create_plan() overloads. The property is ignored on non-Windows platforms. Co-Authored-By: Claude --- CMakeLists.txt | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 4615759..838e366 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -153,6 +153,11 @@ if(HPTT_BUILD_SHARED) hptt_configure_target(hptt_dyn) add_library(hptt::hptt_dyn ALIAS hptt_dyn) list(APPEND HPTT_TARGETS hptt_dyn) + + # No symbol in HPTT carries __declspec(dllexport), so on Windows the DLL + # would export nothing and the linker would emit no import library at + # all -- leaving install(TARGETS ... ARCHIVE) with nothing to install. + set_target_properties(hptt_dyn PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() # Generate hpttConfig.cmake into the build dir unconditionally so the From e6515bac375c7cd0d2d91416e8fd004f37507d8c Mon Sep 17 00:00:00 2001 From: Ivana Date: Thu, 30 Jul 2026 02:24:26 +0800 Subject: [PATCH 7/7] Give the Windows import library a name distinct from the static archive hptt_configure_target() sets OUTPUT_NAME to `hptt` for both targets. On Windows a static library and a DLL's import library are both ARCHIVE artifacts named .lib, so the two targets claimed the same output path. The Ninja generator rejects the resulting build graph with ninja: error: build.ninja:187: multiple rules generate hptt.lib Set ARCHIVE_OUTPUT_NAME on hptt_dyn only. The DLL keeps the name hptt.dll and the static archive keeps hptt.lib; just the import library becomes hptt_dyn.lib. ARCHIVE_OUTPUT_NAME applies only to DLL-platform shared libraries, so libhptt.a, libhptt.so and libhptt.dylib are unaffected. Co-Authored-By: Claude --- CMakeLists.txt | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/CMakeLists.txt b/CMakeLists.txt index 838e366..52f3749 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -158,6 +158,15 @@ if(HPTT_BUILD_SHARED) # would export nothing and the linker would emit no import library at # all -- leaving install(TARGETS ... ARCHIVE) with nothing to install. set_target_properties(hptt_dyn PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON) + + # On Windows both the static library and the DLL's import library are + # ARCHIVE artifacts named .lib, so the shared OUTPUT_NAME + # of `hptt` makes the two targets claim the same path and Ninja aborts + # with "multiple rules generate hptt.lib". Renaming just the import + # library keeps the DLL itself hptt.dll and the static archive + # hptt.lib. ARCHIVE_OUTPUT_NAME is ignored on ELF/Mach-O platforms, so + # libhptt.a / libhptt.so / libhptt.dylib are unaffected. + set_target_properties(hptt_dyn PROPERTIES ARCHIVE_OUTPUT_NAME hptt_dyn) endif() # Generate hpttConfig.cmake into the build dir unconditionally so the