
cmake_minimum_required(VERSION 3.10)

project(CompatibleInterface)

include(GenerateExportHeader)
set(CMAKE_INCLUDE_CURRENT_DIR ON)

add_library(iface1 INTERFACE)
set_property(TARGET iface1 APPEND PROPERTY
  COMPATIBLE_INTERFACE_BOOL
    BOOL_PROP1
    BOOL_PROP2
    BOOL_PROP3
    BOOL_PROP4
    BOOL_PROP5
)
set_property(TARGET iface1 APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING
    STRING_PROP1
    STRING_PROP2
    STRING_PROP3
    STRING_PROP4
)
set_property(TARGET iface1 APPEND PROPERTY
  COMPATIBLE_INTERFACE_NUMBER_MIN
    NUMBER_MIN_PROP1
    NUMBER_MIN_PROP2
    NUMBER_MIN_PROP3
    NUMBER_MIN_PROP4
    NUMBER_MIN_PROP5
    NUMBER_MIN_PROP6
)
set_property(TARGET iface1 APPEND PROPERTY
  COMPATIBLE_INTERFACE_NUMBER_MAX
    NUMBER_MAX_PROP1
    NUMBER_MAX_PROP2
    NUMBER_MAX_PROP3
    NUMBER_MAX_PROP4
)

set(CMAKE_DEBUG_TARGET_PROPERTIES
  BOOL_PROP1 BOOL_PROP2 BOOL_PROP3 BOOL_PROP4 BOOL_PROP5
  STRING_PROP1 STRING_PROP2 STRING_PROP3 STRING_PROP4
  NUMBER_MIN_PROP1 NUMBER_MIN_PROP2 NUMBER_MIN_PROP3 NUMBER_MIN_PROP4 NUMBER_MIN_PROP5 NUMBER_MIN_PROP6
  NUMBER_MAX_PROP1 NUMBER_MAX_PROP2 NUMBER_MAX_PROP3 NUMBER_MAX_PROP4
)

set_property(TARGET iface1 PROPERTY INTERFACE_BOOL_PROP1 ON)
set_property(TARGET iface1 PROPERTY INTERFACE_BOOL_PROP2 ON)
set_property(TARGET iface1 PROPERTY INTERFACE_BOOL_PROP5 ON)
set_property(TARGET iface1 PROPERTY INTERFACE_STRING_PROP1 prop1)
set_property(TARGET iface1 PROPERTY INTERFACE_STRING_PROP2 prop2)
set_property(TARGET iface1 PROPERTY INTERFACE_STRING_PROP4 prop4)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP1 100)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP2 200)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP3 0x10)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP4 0x10)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP5 5)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MIN_PROP6 6)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MAX_PROP1 100)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MAX_PROP2 200)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MAX_PROP3 3)
set_property(TARGET iface1 PROPERTY INTERFACE_NUMBER_MAX_PROP4 4)

add_executable(CompatibleInterface main.cpp)
target_link_libraries(CompatibleInterface iface1)

add_library(foo STATIC foo.cpp)
add_library(bar SHARED bar.cpp)
set_property(TARGET foo APPEND PROPERTY COMPATIBLE_INTERFACE_BOOL SOMEPROP)
set_property(TARGET foo PROPERTY INTERFACE_SOMEPROP ON)
# Use LINK_ONLY to suppress usage requirements and allow the check to pass.
set_property(TARGET bar PROPERTY INTERFACE_LINK_LIBRARIES $<LINK_ONLY:foo>)
set_property(TARGET CompatibleInterface PROPERTY SOMEPROP OFF)
target_link_libraries(CompatibleInterface bar)

set_property(TARGET CompatibleInterface PROPERTY BOOL_PROP2 ON)
set_property(TARGET CompatibleInterface PROPERTY BOOL_PROP3 ON)
set_property(TARGET CompatibleInterface PROPERTY STRING_PROP2 prop2)
set_property(TARGET CompatibleInterface PROPERTY STRING_PROP3 prop3)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MIN_PROP1 50)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MIN_PROP2 250)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MIN_PROP3 0xa)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MIN_PROP4 0x1A)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MAX_PROP1 50)
set_property(TARGET CompatibleInterface PROPERTY NUMBER_MAX_PROP2 250)

target_compile_definitions(CompatibleInterface
  PRIVATE
    $<$<BOOL:$<TARGET_PROPERTY:BOOL_PROP1>>:BOOL_PROP1>
    $<$<BOOL:$<TARGET_PROPERTY:BOOL_PROP2>>:BOOL_PROP2>
    $<$<BOOL:$<TARGET_PROPERTY:BOOL_PROP3>>:BOOL_PROP3>
    $<$<STREQUAL:$<TARGET_PROPERTY:STRING_PROP1>,prop1>:STRING_PROP1>
    $<$<STREQUAL:$<TARGET_PROPERTY:STRING_PROP2>,prop2>:STRING_PROP2>
    $<$<STREQUAL:$<TARGET_PROPERTY:STRING_PROP3>,prop3>:STRING_PROP3>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MIN_PROP1>,50>:NUMBER_MIN_PROP1=50>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MIN_PROP2>,200>:NUMBER_MIN_PROP2=200>
    $<$<EQUAL:$<TARGET_PROPERTY:NUMBER_MIN_PROP3>,0xA>:NUMBER_MIN_PROP3=0xA>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MIN_PROP4>,0x10>:NUMBER_MIN_PROP4=0x10>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MIN_PROP5>,5>:NUMBER_MIN_PROP5=5>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MAX_PROP1>,100>:NUMBER_MAX_PROP1=100>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MAX_PROP2>,250>:NUMBER_MAX_PROP2=250>
    $<$<STREQUAL:$<TARGET_PROPERTY:NUMBER_MAX_PROP3>,3>:NUMBER_MAX_PROP3=3>

    # Static libraries compute COMPATIBLE_INTERFACE_ properties transitively.
    $<$<BOOL:$<TARGET_PROPERTY:static1,BOOL_PROP1>>:STATIC1_BOOL_PROP1>
    $<$<STREQUAL:$<TARGET_PROPERTY:static1,STRING_PROP1>,prop1>:STATIC1_STRING_PROP1>
    $<$<EQUAL:$<TARGET_PROPERTY:static1,NUMBER_MAX_PROP3>,3>:STATIC1_NUMBER_MAX_PROP3>
    $<$<EQUAL:$<TARGET_PROPERTY:static1,NUMBER_MIN_PROP5>,5>:STATIC1_NUMBER_MIN_PROP5>

    # Object libraries do not compute COMPATIBLE_INTERFACE_ properties transitively.
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:object1,BOOL_PROP1>,>>:OBJECT1_BOOL_PROP1>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:object1,STRING_PROP1>,>>:OBJECT1_STRING_PROP1>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:object1,NUMBER_MAX_PROP3>,>>:OBJECT1_NUMBER_MAX_PROP3>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:object1,NUMBER_MIN_PROP5>,>>:OBJECT1_NUMBER_MIN_PROP5>

    # Interface libraries do not compute COMPATIBLE_INTERFACE_ properties transitively.
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:iface3,BOOL_PROP1>,>>:IFACE3_BOOL_PROP1>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:iface3,STRING_PROP1>,>>:IFACE3_STRING_PROP1>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:iface3,NUMBER_MAX_PROP3>,>>:IFACE3_NUMBER_MAX_PROP3>
    $<$<NOT:$<STREQUAL:$<TARGET_PROPERTY:iface3,NUMBER_MIN_PROP5>,>>:IFACE3_NUMBER_MIN_PROP5>

    # Static libraries compute COMPATIBLE_INTERFACE_ properties transitively.
    $<$<BOOL:$<TARGET_PROPERTY:static1,BOOL_PROP5>>:STATIC1_BOOL_PROP5>
    $<$<STREQUAL:$<TARGET_PROPERTY:static1,STRING_PROP4>,prop4>:STATIC1_STRING_PROP4>
    $<$<EQUAL:$<TARGET_PROPERTY:static1,NUMBER_MIN_PROP6>,6>:STATIC1_NUMBER_MIN_PROP6>
    $<$<EQUAL:$<TARGET_PROPERTY:static1,NUMBER_MAX_PROP4>,4>:STATIC1_NUMBER_MAX_PROP4>

    # Object libraries do not compute COMPATIBLE_INTERFACE_ properties transitively,
    # but can have properties set on them.
    $<$<BOOL:$<TARGET_PROPERTY:object1,BOOL_PROP5>>:OBJECT1_BOOL_PROP5>
    $<$<STREQUAL:$<TARGET_PROPERTY:object1,STRING_PROP4>,prop4>:OBJECT1_STRING_PROP4>
    $<$<EQUAL:$<TARGET_PROPERTY:object1,NUMBER_MIN_PROP6>,7>:OBJECT1_NUMBER_MIN_PROP6>
    $<$<EQUAL:$<TARGET_PROPERTY:object1,NUMBER_MAX_PROP4>,1>:OBJECT1_NUMBER_MAX_PROP4>

    # Interface libraries do not compute COMPATIBLE_INTERFACE_ properties transitively,
    # but can have properties set on them.
    $<$<BOOL:$<TARGET_PROPERTY:iface3,BOOL_PROP5>>:IFACE3_BOOL_PROP5>
    $<$<STREQUAL:$<TARGET_PROPERTY:iface3,STRING_PROP4>,prop4>:IFACE3_STRING_PROP4>
    $<$<EQUAL:$<TARGET_PROPERTY:iface3,NUMBER_MIN_PROP6>,7>:IFACE3_NUMBER_MIN_PROP6>
    $<$<EQUAL:$<TARGET_PROPERTY:iface3,NUMBER_MAX_PROP4>,1>:IFACE3_NUMBER_MAX_PROP4>
)


add_library(iface2 SHARED iface2.cpp)
generate_export_header(iface2)

set_property(TARGET iface2 APPEND PROPERTY
  COMPATIBLE_INTERFACE_STRING
    Iface2_PROP
)

# For the LINK_LIBRARIES and related properties, we should not evaluate
# properties defined only in the interface - they should be implicitly zero
set_property(TARGET iface2
  APPEND PROPERTY
    LINK_INTERFACE_LIBRARIES $<$<BOOL:$<TARGET_PROPERTY:BOOL_PROP4>>:nonexistent>
)
target_link_libraries(CompatibleInterface iface2
      $<$<BOOL:$<TARGET_PROPERTY:Iface2_PROP>>:nonexistent>
)
# Test that this does not segfault:
target_compile_definitions(CompatibleInterface
  PRIVATE
    $<$<BOOL:$<TARGET_PROPERTY:Iface2_PROP>>:SOME_DEFINE>
)

# The COMPATIBLE_INTERFACE_* properties are only read from dependencies
# in the interface. Populating it on the CompatibleInterface target does
# not have any effect on the interpretation of the INTERFACE variants
# in dependencies.
set_property(TARGET iface1 PROPERTY
  INTERFACE_NON_RELEVANT_PROP ON
)
set_property(TARGET iface2 PROPERTY
  INTERFACE_NON_RELEVANT_PROP ON
)
set_property(TARGET CompatibleInterface APPEND PROPERTY
  COMPATIBLE_INTERFACE_BOOL
    NON_RELEVANT_PROP
)

add_library(static1 STATIC foo.cpp)
set_property(TARGET static1 PROPERTY BOOL_PROP5 ON)
set_property(TARGET static1 PROPERTY STRING_PROP4 prop4)
set_property(TARGET static1 PROPERTY NUMBER_MIN_PROP6 7)
set_property(TARGET static1 PROPERTY NUMBER_MAX_PROP4 1)
target_link_libraries(static1 PUBLIC iface1)

add_library(object1 OBJECT foo.cpp)
set_property(TARGET object1 PROPERTY BOOL_PROP5 ON)
set_property(TARGET object1 PROPERTY STRING_PROP4 prop4)
set_property(TARGET object1 PROPERTY NUMBER_MIN_PROP6 7)
set_property(TARGET object1 PROPERTY NUMBER_MAX_PROP4 1)
target_link_libraries(object1 PUBLIC iface1)

add_library(iface3 INTERFACE)
set_property(TARGET iface3 PROPERTY BOOL_PROP5 ON)
set_property(TARGET iface3 PROPERTY STRING_PROP4 prop4)
set_property(TARGET iface3 PROPERTY NUMBER_MIN_PROP6 7)
set_property(TARGET iface3 PROPERTY NUMBER_MAX_PROP4 1)
target_link_libraries(iface3 INTERFACE iface1)

# Test COMPATIBLE_INTERFACE_* property evaluation outside of usage requirements.
add_custom_target(check ALL VERBATIM
  COMMAND CompatibleInterface
  # expect     actual
    "1"        "$<TARGET_PROPERTY:CompatibleInterface,BOOL_PROP1>"
    "prop1"    "$<TARGET_PROPERTY:CompatibleInterface,STRING_PROP1>"
    "3"        "$<TARGET_PROPERTY:CompatibleInterface,NUMBER_MAX_PROP3>"
    "5"        "$<TARGET_PROPERTY:CompatibleInterface,NUMBER_MIN_PROP5>"

    "1"        "$<TARGET_PROPERTY:static1,BOOL_PROP1>"
    "prop1"    "$<TARGET_PROPERTY:static1,STRING_PROP1>"
    "3"        "$<TARGET_PROPERTY:static1,NUMBER_MAX_PROP3>"
    "5"        "$<TARGET_PROPERTY:static1,NUMBER_MIN_PROP5>"

    ""         "$<TARGET_PROPERTY:object1,BOOL_PROP1>"
    ""         "$<TARGET_PROPERTY:object1,STRING_PROP1>"
    ""         "$<TARGET_PROPERTY:object1,NUMBER_MAX_PROP3>"
    ""         "$<TARGET_PROPERTY:object1,NUMBER_MIN_PROP5>"

    ""         "$<TARGET_PROPERTY:iface3,BOOL_PROP1>"
    ""         "$<TARGET_PROPERTY:iface3,STRING_PROP1>"
    ""         "$<TARGET_PROPERTY:iface3,NUMBER_MAX_PROP3>"
    ""         "$<TARGET_PROPERTY:iface3,NUMBER_MIN_PROP5>"

    "ON"       "$<TARGET_PROPERTY:static1,BOOL_PROP5>"
    "prop4"    "$<TARGET_PROPERTY:static1,STRING_PROP4>"
    "6"        "$<TARGET_PROPERTY:static1,NUMBER_MIN_PROP6>"
    "4"        "$<TARGET_PROPERTY:static1,NUMBER_MAX_PROP4>"

    "ON"       "$<TARGET_PROPERTY:object1,BOOL_PROP5>"
    "prop4"    "$<TARGET_PROPERTY:object1,STRING_PROP4>"
    "7"        "$<TARGET_PROPERTY:object1,NUMBER_MIN_PROP6>"
    "1"        "$<TARGET_PROPERTY:object1,NUMBER_MAX_PROP4>"

    "ON"       "$<TARGET_PROPERTY:iface3,BOOL_PROP5>"
    "prop4"    "$<TARGET_PROPERTY:iface3,STRING_PROP4>"
    "7"        "$<TARGET_PROPERTY:iface3,NUMBER_MIN_PROP6>"
    "1"        "$<TARGET_PROPERTY:iface3,NUMBER_MAX_PROP4>"
  )
