diff --git a/CMakeLists.txt b/CMakeLists.txt index 78fce9a5d2..3c7627e7e0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -590,6 +590,10 @@ if(MSVC) # warning level 3 set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W3") + # Remove unreferenced COMDATs (functions/data) before they reach the linker. + # Shrinks object files; does NOT reduce dllexport'd symbol counts (verified empirically). + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /Zc:inline") + # warning level 4 - DLM: we should shoot for this #set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /W4") @@ -1671,7 +1675,9 @@ if (MSVC) # within the same library. This may result in less efficient function calls. # The potential inefficiency goes away if we use LTO linking - target_link_libraries(openstudiolib PRIVATE -IGNORE:4217,4049) + foreach(_os_lib openstudiolib openstudiomodellib openstudioepmodellib openstudioutilitieslib) + target_link_libraries(${_os_lib} PRIVATE -IGNORE:4217,4049) + endforeach() if(CMAKE_SIZEOF_VOID_P EQUAL 8) # only applies to 64 bit windows platforms if (BUILD_RUBY_BINDINGS) target_link_libraries(openstudio_rb PRIVATE -IGNORE:4217,4049) diff --git a/python/engine/CMakeLists.txt b/python/engine/CMakeLists.txt index 970c7b74f6..4b0bbf6a81 100644 --- a/python/engine/CMakeLists.txt +++ b/python/engine/CMakeLists.txt @@ -71,7 +71,7 @@ if(BUILD_TESTING) set(pythonengine_test_depends openstudio_scriptengine openstudiolib - openstudio_epmodel + openstudio_epmodel_headers fmt::fmt ) diff --git a/ruby/engine/CMakeLists.txt b/ruby/engine/CMakeLists.txt index 335637ac40..8a2b2b734d 100644 --- a/ruby/engine/CMakeLists.txt +++ b/ruby/engine/CMakeLists.txt @@ -255,7 +255,7 @@ if(BUILD_TESTING) set(rubyengine_test_depends openstudio_scriptengine openstudiolib - openstudio_epmodel + openstudio_epmodel_headers fmt::fmt ) diff --git a/src/epmodel/CMakeLists.txt b/src/epmodel/CMakeLists.txt index a0fc88506c..c2e9fa481e 100644 --- a/src/epmodel/CMakeLists.txt +++ b/src/epmodel/CMakeLists.txt @@ -2751,6 +2751,23 @@ set(${target_name}_depends set(${target_name}_depends ${${target_name}_depends} PARENT_SCOPE) target_compile_definitions(${target_name} PRIVATE openstudio_epmodel_EXPORTS SHARED_OS_LIBS) + +# Headers-only INTERFACE target: gives consumers epmodel's per-subfolder include +# directories without pulling in its OBJECT library's actual object files. On MSVC, +# openstudio_epmodel's objects already live in openstudioepmodellib.dll; any consumer +# that links both openstudio_epmodel directly AND openstudiolib (which PUBLIC-links +# openstudioepmodellib) gets the same symbols twice -- once as real definitions, once +# as the DLL's import-lib thunks -- which MSVC rejects (LNK2005). Consumers that only +# need to compile against epmodel headers (not embed its objects) should link this +# instead of openstudio_epmodel. +add_library(openstudio_epmodel_headers INTERFACE) +target_include_directories(openstudio_epmodel_headers INTERFACE + ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/HVACComponent + ${CMAKE_CURRENT_SOURCE_DIR}/ModelObject + ${CMAKE_CURRENT_SOURCE_DIR}/scaffolds + ${CMAKE_CURRENT_SOURCE_DIR}/StraightComponent + ${CMAKE_CURRENT_SOURCE_DIR}/ShadingMaterial) + target_include_directories(${target_name} PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/HVACComponent ${CMAKE_CURRENT_SOURCE_DIR}/ModelObject ${CMAKE_CURRENT_SOURCE_DIR}/scaffolds @@ -3626,7 +3643,7 @@ set(${target_name}_test_src set(${target_name}_test_depends openstudiolib - openstudio_epmodel + openstudio_epmodel_headers ) CREATE_SRC_GROUPS("${${target_name}_test_src}") diff --git a/src/epmodel/EPModelSwig.cmake b/src/epmodel/EPModelSwig.cmake index 4ae77a4f52..dca8e98c93 100644 --- a/src/epmodel/EPModelSwig.cmake +++ b/src/epmodel/EPModelSwig.cmake @@ -159,7 +159,12 @@ macro(make_epmodel_swig_bindings NAME SIMPLENAME KEY_I_FILE I_FILES PARENT_TARGE target_include_directories(${python_target} PRIVATE ${common_swig_include_dirs}) target_include_directories(${python_target} SYSTEM PRIVATE ${Python_INCLUDE_DIRS}) target_compile_definitions(${python_target} PRIVATE SHARED_OS_LIBS SWIG_PYTHON_SILENT_MEMLEAK) - target_link_libraries(${python_target} PUBLIC ${PARENT_TARGET} ${${PARENT_TARGET}_depends}) + # Do NOT link PARENT_TARGET (the openstudio_epmodel OBJECT library) directly here: + # every python binding target already gets the real symbols via openstudiolib + # (linked in python/module/CMakeLists.txt). Linking both directly embeds epmodel's + # object code twice, which MSVC rejects (LNK2005) since its import-lib thunks for + # openstudioepmodellib collide with the directly-linked definitions. + target_link_libraries(${python_target} PUBLIC ${${PARENT_TARGET}_depends}) add_dependencies(${python_target} ${PARENT_TARGET}) if(MSVC) diff --git a/src/lib/CMakeLists.txt b/src/lib/CMakeLists.txt index 84de8f6f92..f663ff6caa 100644 --- a/src/lib/CMakeLists.txt +++ b/src/lib/CMakeLists.txt @@ -1,28 +1,99 @@ # Some operating systems don't like compiling a library with # no source files -add_library(openstudiolib SHARED empty.cpp) +if(MSVC) + # MSVC import libraries (and the DLL export ordinal table) are limited to 65,535 exported + # symbols (LNK1189). model + epmodel together export ~123,000 unique symbols, far beyond + # that limit, so on Windows the object libraries are split across four DLLs: + # + # openstudioutilitieslib + # ▲ ▲ + # openstudiomodellib openstudioepmodellib + # ▲ ▲ + # openstudiolib + # + # openstudiolib keeps its name and PUBLIC-links the others so that no consumer + # (CLI, ruby/python/csharp bindings, tests) needs any change. + # Each OBJECT library still lands in exactly one DLL, so the per-component + # openstudio__EXPORTS / _API macros produce correct dllexport/dllimport. + # + # openstudio_osversion goes in openstudiomodellib (not the top-level openstudiolib) + # because model/Model.cpp calls osversion::VersionTranslator directly (Model::load), + # while osversion links openstudio_model's own dependency chain -- a genuine mutual + # dependency that only compiles when both land in the same DLL. + add_library(openstudioutilitieslib SHARED empty.cpp) + target_link_libraries( + openstudioutilitieslib + PRIVATE + openstudio_utilities + ) -target_link_libraries( - openstudiolib - PRIVATE - openstudio_utilities - openstudio_airflow - openstudio_model - openstudio_modelica - openstudio_energyplus - openstudio_epmodel - openstudio_epjson - openstudio_alfalfa - openstudio_measure - openstudio_osversion - openstudio_sdd - openstudio_isomodel - openstudio_gbxml - openstudio_gltf - openstudio_radiance -) + add_library(openstudiomodellib SHARED empty.cpp) + target_link_libraries( + openstudiomodellib + PRIVATE + openstudio_model + openstudio_energyplus + openstudio_osversion + PUBLIC + openstudioutilitieslib + ) + + add_library(openstudioepmodellib SHARED empty.cpp) + target_link_libraries( + openstudioepmodellib + PRIVATE + openstudio_epmodel + PUBLIC + openstudioutilitieslib + ) + + add_library(openstudiolib SHARED empty.cpp) + target_link_libraries( + openstudiolib + PRIVATE + openstudio_airflow + openstudio_modelica + openstudio_epjson + openstudio_alfalfa + openstudio_measure + openstudio_sdd + openstudio_isomodel + openstudio_gbxml + openstudio_gltf + openstudio_radiance + PUBLIC + openstudiomodellib + openstudioepmodellib + ) + + set(openstudio_lib_targets openstudiolib openstudiomodellib openstudioepmodellib openstudioutilitieslib) +else() + add_library(openstudiolib SHARED empty.cpp) + + target_link_libraries( + openstudiolib + PRIVATE + openstudio_utilities + openstudio_airflow + openstudio_model + openstudio_modelica + openstudio_energyplus + openstudio_epmodel + openstudio_epjson + openstudio_alfalfa + openstudio_measure + openstudio_osversion + openstudio_sdd + openstudio_isomodel + openstudio_gbxml + openstudio_gltf + openstudio_radiance + ) + + set(openstudio_lib_targets openstudiolib) +endif() target_link_libraries( openstudiolib @@ -36,7 +107,9 @@ target_link_libraries( fmt::fmt ) -target_compile_definitions(openstudiolib INTERFACE "-DSHARED_OS_LIBS") +foreach(_os_lib IN LISTS openstudio_lib_targets) + target_compile_definitions(${_os_lib} INTERFACE "-DSHARED_OS_LIBS") +endforeach() # We cannot make the libs that make up the shared library public dependency at all, if we do # then we have to export and install them as well, which we probably don't want to do. @@ -59,7 +132,7 @@ target_include_directories( $ ) -install(TARGETS openstudiolib +install(TARGETS ${openstudio_lib_targets} EXPORT openstudio DESTINATION ${LIB_DESTINATION_DIR} COMPONENT "CLI" # CLI is no longer self-contained, it needs it diff --git a/src/measure/CMakeLists.txt b/src/measure/CMakeLists.txt index e2c2e66aa1..bcd9759b44 100644 --- a/src/measure/CMakeLists.txt +++ b/src/measure/CMakeLists.txt @@ -46,7 +46,7 @@ CREATE_SRC_GROUPS("${${target_name}_src}") CREATE_SRC_GROUPS("${${target_name}_swig_src}") set(${target_name}_depends - openstudio_epmodel + openstudio_epmodel_headers ${openstudio_osversion_depends} ) set(${target_name}_depends ${${target_name}_depends} PARENT_SCOPE) @@ -63,7 +63,7 @@ add_dependencies(${target_name} GenerateIddFactoryRun) set(${target_name}_test_depends ${COREFOUNDATION_LIBRARY} openstudiolib - openstudio_epmodel + openstudio_epmodel_headers fmt::fmt ) diff --git a/src/workflow/CMakeLists.txt b/src/workflow/CMakeLists.txt index dfef68f5c2..83874071c1 100644 --- a/src/workflow/CMakeLists.txt +++ b/src/workflow/CMakeLists.txt @@ -27,7 +27,7 @@ add_library(openstudio_workflow Timer.cpp ) -target_link_libraries(openstudio_workflow PUBLIC openstudiolib openstudio_epmodel) +target_link_libraries(openstudio_workflow PUBLIC openstudiolib openstudio_epmodel_headers) if(BUILD_TESTING) set(openstudio_workflow_test_depends