#
# Copyright 2016 Pixar
#
# Licensed under the terms set forth in the LICENSE.txt file available at
# https://openusd.org/license.
#
include(Private)

function(pxr_build_documentation)
    # Cmake booleans are often, ie, "OFF", while Doxyfile requires booleans
    # to be "YES" or "NO"
    if(PXR_BUILD_HTML_DOCUMENTATION)
        set(DOXYGEN_GENERATE_HTML "YES")
    else()
        set(DOXYGEN_GENERATE_HTML "NO")
    endif()

    configure_file(${PROJECT_SOURCE_DIR}/docs/doxygen/Doxyfile.in
                   ${PROJECT_BINARY_DIR}/Doxyfile)

    add_custom_target(
        documentation
        ALL
        # We need to manually copy pxr.h into the docs/include directory
        # since it's generated outside of the libraries.
        COMMAND
            ${CMAKE_COMMAND} -E copy
            "${PROJECT_BINARY_DIR}/include/pxr/pxr.h"
            "${PROJECT_BINARY_DIR}/docs/include/pxr/pxr.h"
        COMMAND 
            ${CMAKE_COMMAND} -E copy_directory
            "${PROJECT_SOURCE_DIR}/docs"
            "${PROJECT_BINARY_DIR}/docs"
    )

    # Execute doxygen during the install step. All of the files we want
    # doxygen to process should already have been copied to the docs
    # directory during the build step
    install(CODE "execute_process(COMMAND ${DOXYGEN_EXECUTABLE} \"${PROJECT_BINARY_DIR}/Doxyfile\")")

    set(INST_DOCS_ROOT  "${CMAKE_INSTALL_PREFIX}/docs")

    set(BUILT_DOCS_TAG_FILE "${PROJECT_BINARY_DIR}/docs/USD.tag")
    install(
        FILES ${BUILT_DOCS_TAG_FILE}
        DESTINATION ${INST_DOCS_ROOT}
    )

    if(PXR_BUILD_HTML_DOCUMENTATION)
        set(BUILT_HTML_DOCS "${PROJECT_BINARY_DIR}/docs/doxy_html")
        install(
            DIRECTORY ${BUILT_HTML_DOCS}
            DESTINATION ${INST_DOCS_ROOT}
        )
    endif()

    set(BUILT_XML_DOCS "${PROJECT_BINARY_DIR}/docs/doxy_xml")
    install(
        DIRECTORY ${BUILT_XML_DOCS}
        DESTINATION ${INST_DOCS_ROOT}
    )
endfunction()

function(pxr_python_bin BIN_NAME)
    set(oneValueArgs
        PYTHON_FILE
    )
    set(multiValueArgs
        DEPENDENCIES
    )
    cmake_parse_arguments(pb
        ""
        "${oneValueArgs}"
        "${multiValueArgs}"
        ${ARGN}
    )

    # If we can't build Python modules then do nothing.
    if(NOT TARGET python_modules)
        message(STATUS "Skipping Python program ${BIN_NAME}, Python modules required")
        return()
    endif()

    _get_install_dir(bin installDir)

    # Source file.
    if( "${pb_PYTHON_FILE}" STREQUAL "")
        set(infile ${CMAKE_CURRENT_SOURCE_DIR}/${BIN_NAME}.py)
    else()
        set(infile ${CMAKE_CURRENT_SOURCE_DIR}/${pb_PYTHON_FILE})
    endif()

    # Destination file.
    set(outfile ${CMAKE_CURRENT_BINARY_DIR}/${BIN_NAME})

    # /pxrpythonsubst will be replaced with the full path to the configured
    # python executable. This doesn't use the CMake ${...} or @...@ syntax
    # for backwards compatibility with other build systems.
    add_custom_command(
        OUTPUT ${outfile}
        DEPENDS ${infile}
        COMMENT "Substituting Python shebang"
        COMMAND
            ${PYTHON_EXECUTABLE}
            ${PROJECT_SOURCE_DIR}/cmake/macros/shebang.py
            ${PXR_PYTHON_SHEBANG}
            ${infile}
            ${outfile}
    )
    list(APPEND outputs ${outfile})

    install(
        PROGRAMS ${outfile}
        DESTINATION ${installDir}
        RENAME ${BIN_NAME}
    )

    # Windows by default cannot execute Python files so here
    # we create a batch file wrapper that invokes the python
    # files.
    if(WIN32)
        add_custom_command(
            OUTPUT ${outfile}.cmd
            COMMENT "Creating Python cmd wrapper"
            COMMAND
                ${PYTHON_EXECUTABLE}
                ${PROJECT_SOURCE_DIR}/cmake/macros/shebang.py
                ${BIN_NAME}
                ${outfile}.cmd
        )
        list(APPEND outputs ${outfile}.cmd)

        install(
            PROGRAMS ${outfile}.cmd
            DESTINATION ${installDir}
            RENAME ${BIN_NAME}.cmd
        )
    endif()

    # Add the target.
    add_custom_target(${BIN_NAME}_script
        DEPENDS ${outputs} ${pb_DEPENDENCIES}
    )
    add_dependencies(python_modules ${BIN_NAME}_script)

    _get_folder("" folder)
    set_target_properties(${BIN_NAME}_script
        PROPERTIES
            FOLDER "${folder}"
    )
endfunction() # pxr_python_bin

function(pxr_cpp_bin BIN_NAME)
    _get_install_dir(bin installDir)
    
    set(multiValueArgs
        LIBRARIES
        INCLUDE_DIRS
    )

    cmake_parse_arguments(cb
        ""  
        ""
        "${multiValueArgs}"
        ${ARGN}
    )

    add_executable(${BIN_NAME} ${BIN_NAME}.cpp)

    # Turn PIC ON otherwise ArchGetAddressInfo() on Linux may yield
    # unexpected results.
    _get_folder("" folder)
    set_target_properties(${BIN_NAME}
        PROPERTIES
            FOLDER "${folder}"
            POSITION_INDEPENDENT_CODE ON
    )

    # Install and include headers from the build directory.
    get_filename_component(
        PRIVATE_INC_DIR
        "${PROJECT_BINARY_DIR}/include"
        ABSOLUTE
    )

    target_include_directories(${BIN_NAME}
        PRIVATE 
        ${PRIVATE_INC_DIR}
        ${cb_INCLUDE_DIRS}
    )

    _pxr_init_rpath(rpath "${installDir}")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib")
    _pxr_install_rpath(rpath ${BIN_NAME})

    _pxr_target_link_libraries(${BIN_NAME}
        ${cb_LIBRARIES}
        ${PXR_MALLOC_LIBRARY}
    )

    install(
        TARGETS ${BIN_NAME}
        DESTINATION ${installDir}
    )
endfunction()

function(pxr_library NAME)
    set(options
        DISABLE_PRECOMPILED_HEADERS
        INCLUDE_SCHEMA_FILES
    )
    set(oneValueArgs
        TYPE
        PRECOMPILED_HEADER_NAME
    )
    set(multiValueArgs
        PUBLIC_CLASSES
        PUBLIC_HEADERS
        PRIVATE_CLASSES
        PRIVATE_HEADERS
        CPPFILES
        LIBRARIES
        INCLUDE_DIRS
        DOXYGEN_FILES
        RESOURCE_FILES
        PYTHON_PUBLIC_CLASSES
        PYTHON_PRIVATE_CLASSES
        PYTHON_PUBLIC_HEADERS
        PYTHON_PRIVATE_HEADERS
        PYTHON_CPPFILES
        PYMODULE_CPPFILES
        PYMODULE_DIRS
        PYMODULE_FILES
        PYSIDE_UI_FILES
    )

    cmake_parse_arguments(args
        "${options}"
        "${oneValueArgs}"
        "${multiValueArgs}"
        ${ARGN}
    )

    # If python support is enabled, merge the python specific categories
    # with the more general before setting up compilation.
    if(PXR_ENABLE_PYTHON_SUPPORT)
        set(libraryRequiresPython 0)
        if(args_PYTHON_PUBLIC_CLASSES)
            list(APPEND args_PUBLIC_CLASSES ${args_PYTHON_PUBLIC_CLASSES})
            set(libraryRequiresPython 1)
        endif()
        if(args_PYTHON_PUBLIC_HEADERS)
            list(APPEND args_PUBLIC_HEADERS ${args_PYTHON_PUBLIC_HEADERS})
            set(libraryRequiresPython 1)
        endif()
        if(args_PYTHON_PRIVATE_CLASSES)
            list(APPEND args_PRIVATE_CLASSES ${args_PYTHON_PRIVATE_CLASSES})
            set(libraryRequiresPython 1)
        endif()
        if(args_PYTHON_PRIVATE_HEADERS)
            list(APPEND args_PRIVATE_HEADERS ${args_PYTHON_PRIVATE_HEADERS})
            set(libraryRequiresPython 1)
        endif()
        if(args_PYTHON_CPPFILES)
            list(APPEND args_CPPFILES ${args_PYTHON_CPPFILES})
            set(libraryRequiresPython 1)
        endif()

        if(libraryRequiresPython)
            list(APPEND args_LIBRARIES ${PYTHON_LIBRARIES} python)
            list(APPEND args_INCLUDE_DIRS ${PYTHON_INCLUDE_DIRS})
        endif()
    endif()

    # If this is a schema library, add schema classes
    if (args_INCLUDE_SCHEMA_FILES)
        set(filePath "generatedSchema.classes.txt")

        # Register a dependency so that cmake will regenerate the build
        # system if generatedSchema.classes.txt changes
        set_property(
            DIRECTORY 
            APPEND 
            PROPERTY CMAKE_CONFIGURE_DEPENDS 
            ${filePath}
        )

        # Read the generated classes
        file(STRINGS ${filePath} fileContents)

        # fileType potential values:
        # -1: Skip line
        # 0: Public Classes
        # 1: Python Module Files
        # 2: Resource Files
        set(fileType -1)

        foreach(line ${fileContents})
            # Determine which section of the generated file we are in.
            if (${fileType} EQUAL -1)
                string(FIND ${line} "# Public Classes" found)
                if (NOT ${found} EQUAL -1)
                    set(fileType 0)
                    continue()
                endif()
            elseif(${fileType} EQUAL 0)
                string(FIND ${line} "# Python Module Files" found)
                if (NOT ${found} EQUAL -1)
                    set(fileType 1)
                    continue()
                endif()
            elseif(${fileType} EQUAL 1)
                string(FIND ${line} "# Resource Files" found)
                if (NOT ${found} EQUAL -1)
                    set(fileType 2)
                    continue()
                endif()
            endif()

            # Depending on the file type, append to the appropriate list.
            if (${fileType} EQUAL 0)
                list(APPEND args_PUBLIC_CLASSES ${line})
            elseif(${fileType} EQUAL 1)
                list(APPEND args_PYMODULE_CPPFILES ${line})
            elseif(${fileType} EQUAL 2)
                list(APPEND args_RESOURCE_FILES ${line})
            endif()
        endforeach()
    endif()

    # Collect libraries.
    if(NOT args_TYPE STREQUAL "PLUGIN")
        get_property(help CACHE PXR_ALL_LIBS PROPERTY HELPSTRING)
        list(APPEND PXR_ALL_LIBS ${NAME})
        set(PXR_ALL_LIBS "${PXR_ALL_LIBS}" CACHE INTERNAL "${help}")
        if(args_TYPE STREQUAL "STATIC")
            # Note if this library is explicitly STATIC.
            get_property(help CACHE PXR_STATIC_LIBS PROPERTY HELPSTRING)
            list(APPEND PXR_STATIC_LIBS ${NAME})
            set(PXR_STATIC_LIBS "${PXR_STATIC_LIBS}" CACHE INTERNAL "${help}")
        endif()
    endif()

    # Expand classes into filenames.
    _classes(${NAME} ${args_PRIVATE_CLASSES} PRIVATE)
    _classes(${NAME} ${args_PUBLIC_CLASSES} PUBLIC)

    # If building a core plugin for a monolithic build then treat it almost
    # like any other library and include it in the monolithic library.
    if (_building_core AND _building_monolithic AND args_TYPE STREQUAL "PLUGIN")
        set(args_TYPE "OBJECT_PLUGIN")
    endif()

    # Custom tweaks.
    if(args_TYPE STREQUAL "PLUGIN")
        # We can't build plugins if we're not building shared libraries.
        if(NOT TARGET shared_libs)
            message(STATUS "Skipping plugin ${NAME}, shared libraries required")
            return()
        endif()

        set(prefix "")
        set(suffix ${CMAKE_SHARED_LIBRARY_SUFFIX})
    else()
        # If the caller didn't specify the library type then choose the
        # type now.
        if("x${args_TYPE}" STREQUAL "x")
            if(_building_monolithic)
                set(args_TYPE "OBJECT")
            elseif(BUILD_SHARED_LIBS)
                set(args_TYPE "SHARED")
            else()
                set(args_TYPE "STATIC")
            endif()
        endif()

        _get_library_prefix(prefix)
        if(args_TYPE STREQUAL "STATIC")
            set(suffix ${CMAKE_STATIC_LIBRARY_SUFFIX})
        else()
            set(suffix ${CMAKE_SHARED_LIBRARY_SUFFIX})
        endif()
    endif()

    set(pch "ON")
    if(args_DISABLE_PRECOMPILED_HEADERS)
        set(pch "OFF")
    endif()

    if (PXR_ENABLE_PYTHON_SUPPORT AND args_PYMODULE_CPPFILES)
        # If moduleDeps.cpp does not exist, create one
        set(moduleDepsFileName "moduleDeps.cpp")
        list(FIND args_PYTHON_CPPFILES ${moduleDepsFileName} foundModuleDeps)
        if (${foundModuleDeps} EQUAL -1)
            # Add moduleDeps.cpp as a built file
            list(APPEND args_CPPFILES ${moduleDepsFileName})

            # Keep only our libraries in the module dependencies
            foreach(library ${args_LIBRARIES})
                if (TARGET ${library}) 
                    list(APPEND localLibs ${library})
                endif()
            endforeach()

            # Generate moduleDeps.cpp
            _get_python_module_name(${NAME} pyModuleName)
            add_custom_command(
                OUTPUT ${moduleDepsFileName}
                COMMAND ${CMAKE_COMMAND} -DlibraryName=${NAME} -DmoduleName=${pyModuleName} -DsourceDir=${PROJECT_SOURCE_DIR} -Dlibraries="${localLibs}" -Doutfile=${moduleDepsFileName} -P "${PROJECT_SOURCE_DIR}/cmake/macros/genModuleDepsCpp.cmake"
                DEPENDS "CMakeLists.txt")
        endif()
    endif()

    _pxr_library(${NAME}
        TYPE "${args_TYPE}"
        PREFIX "${prefix}"
        SUFFIX "${suffix}"
        SUBDIR "${subdir}"
        CPPFILES "${args_CPPFILES};${${NAME}_CPPFILES}"
        PUBLIC_HEADERS "${args_PUBLIC_HEADERS};${${NAME}_PUBLIC_HEADERS}"
        PRIVATE_HEADERS "${args_PRIVATE_HEADERS};${${NAME}_PRIVATE_HEADERS}"
        LIBRARIES "${args_LIBRARIES}"
        INCLUDE_DIRS "${args_INCLUDE_DIRS}"
        RESOURCE_FILES "${args_RESOURCE_FILES}"
        DOXYGEN_FILES "${args_DOXYGEN_FILES}"
        PRECOMPILED_HEADERS "${pch}"
        PRECOMPILED_HEADER_NAME "${args_PRECOMPILED_HEADER_NAME}"
        LIB_INSTALL_PREFIX_RESULT libInstallPrefix
    )

    if(PXR_ENABLE_PYTHON_SUPPORT AND
       (args_PYMODULE_CPPFILES OR args_PYMODULE_DIRS OR
        args_PYMODULE_FILES OR args_PYSIDE_UI_FILES))
        list(APPEND pythonModuleIncludeDirs ${PYTHON_INCLUDE_DIRS})

        _pxr_python_module(
            ${NAME}
            WRAPPED_LIB_INSTALL_PREFIX "${libInstallPrefix}"
            PYTHON_DIRS ${args_PYMODULE_DIRS}
            PYTHON_FILES ${args_PYMODULE_FILES}
            PYSIDE_UI_FILES ${args_PYSIDE_UI_FILES}
            CPPFILES ${args_PYMODULE_CPPFILES}
            INCLUDE_DIRS "${args_INCLUDE_DIRS};${pythonModuleIncludeDirs}"
            PRECOMPILED_HEADERS ${pch}
            PRECOMPILED_HEADER_NAME ${args_PRECOMPILED_HEADER_NAME}
        )
    endif()
endfunction()

macro(pxr_shared_library NAME)
    pxr_library(${NAME} TYPE "SHARED" ${ARGN})
endmacro(pxr_shared_library)

macro(pxr_static_library NAME)
    pxr_library(${NAME} TYPE "STATIC" ${ARGN})
endmacro(pxr_static_library)

macro(pxr_plugin NAME)
    if(EMSCRIPTEN)
        # Dynamic linking is not supported yet in the usd build toolchain
        message(STATUS "Building ${NAME} plugin as static library for emscripten support")
        pxr_library(${NAME} TYPE "STATIC" ${ARGN})
    else()
        pxr_library(${NAME} TYPE "PLUGIN" ${ARGN})
    endif()
endmacro(pxr_plugin)

function(pxr_setup_python)
    get_property(pxrPythonModules GLOBAL PROPERTY PXR_PYTHON_MODULES)

    # A new list where each python module is quoted
    set(converted "")
    foreach(module ${pxrPythonModules})
        list(APPEND converted "'${module}'")
    endforeach()

    # Join these with a ', '
    string(REPLACE ";" ", " pyModulesStr "${converted}")

    # Install a pxr __init__.py with an appropriate __all__
    _get_install_dir(${PXR_PYTHON_INSTALL_DIR}/pxr installPrefix)

    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/generated_modules_init.py"
         "__all__ = [${pyModulesStr}]\n")

    install(
        FILES "${CMAKE_CURRENT_BINARY_DIR}/generated_modules_init.py"
        DESTINATION ${installPrefix}
        RENAME "__init__.py"
    )
endfunction() # pxr_setup_python

function (pxr_create_test_module MODULE_NAME)
    # If we can't build Python modules then do nothing.
    if(NOT TARGET python_modules)
        return()
    endif()

    if (NOT PXR_BUILD_TESTS) 
        return()
    endif()

    # If the package for this test does not have a target it must not be
    # getting built, in which case we can skip building associated tests.
    if (NOT TARGET ${PXR_PACKAGE})
        return()
    endif()

    cmake_parse_arguments(tm "" "INSTALL_PREFIX;SOURCE_DIR;DEST_DIR" "" ${ARGN})

    if (NOT tm_SOURCE_DIR)
        set(tm_SOURCE_DIR testenv)
    endif()

    if (NOT tm_DEST_DIR)
        set(tm_DEST_DIR ${MODULE_NAME})
    endif()

    # Look specifically for an __init__.py and a plugInfo.json prefixed by the
    # module name. These will be installed without the module prefix.
    set(initPyFile ${tm_SOURCE_DIR}/${MODULE_NAME}__init__.py)
    set(plugInfoFile ${tm_SOURCE_DIR}/${MODULE_NAME}_plugInfo.json)

    # XXX -- We shouldn't have to install to run tests.
    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${initPyFile}")
        install(
            FILES 
                ${initPyFile}
            RENAME 
                __init__.py
            DESTINATION 
                tests/${tm_INSTALL_PREFIX}/lib/python/${tm_DEST_DIR}
        )
    endif()
    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${plugInfoFile}")
        install(
            FILES 
                ${plugInfoFile}
            RENAME 
                plugInfo.json
            DESTINATION 
                tests/${tm_INSTALL_PREFIX}/lib/python/${tm_DEST_DIR}
        )
    endif()
endfunction() # pxr_create_test_module

# Builds a test-only shared library that does not provide public headers, and
# therefore symbols must be loaded from the library by a plugin mechanism
# (e.g. TfType). These plugins can provide a plugInfo.json file to register
# their contents.
function(pxr_build_test_plugin LIBRARY_NAME)
    if (NOT PXR_BUILD_TESTS)
        return()
    endif()

    # If the package for this test does not have a target it must not be
    # getting built, in which case we can skip building associated tests.
    if (NOT TARGET ${PXR_PACKAGE})
        return()
    endif()

    cmake_parse_arguments(bt
        ""
        "INSTALL_PREFIX;SOURCE_DIR"
        "LIBRARIES;CPPFILES"
        ${ARGN}
    )
        
    add_library(${LIBRARY_NAME}
        SHARED
        ${bt_CPPFILES}
    )
    _pxr_target_link_libraries(${LIBRARY_NAME}
        ${bt_LIBRARIES}
    )
    _get_folder("tests/lib" folder)
    set_target_properties(${LIBRARY_NAME}
        PROPERTIES 
            FOLDER "${folder}"
    )

    # Find libraries under the install prefix, which has the core USD
    # libraries. Also find test-only libraries under the install prefix.
    _pxr_init_rpath(rpath "tests/lib")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/tests/lib")
    _pxr_install_rpath(rpath ${LIBRARY_NAME})

    if (NOT bt_SOURCE_DIR)
        set(bt_SOURCE_DIR testenv)
    endif()
    set(testPlugInfoSrcPath ${bt_SOURCE_DIR}/${LIBRARY_NAME}_plugInfo.json)

    if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${testPlugInfoSrcPath}")
        set(TEST_PLUG_INFO_RESOURCE_PATH "Resources")
        set(TEST_PLUG_INFO_ROOT "..")
        set(LIBRARY_FILE "${CMAKE_SHARED_LIBRARY_PREFIX}${LIBRARY_NAME}${CMAKE_SHARED_LIBRARY_SUFFIX}")

        set(testPlugInfoLibDir "tests/${bt_INSTALL_PREFIX}/lib/${LIBRARY_NAME}")
        set(testPlugInfoResourceDir "${testPlugInfoLibDir}/${TEST_PLUG_INFO_RESOURCE_PATH}")
        set(testPlugInfoPath "${PROJECT_BINARY_DIR}/${testPlugInfoResourceDir}/plugInfo.json")

        file(RELATIVE_PATH 
            TEST_PLUG_INFO_LIBRARY_PATH
            "${CMAKE_INSTALL_PREFIX}/${testPlugInfoLibDir}"
            "${CMAKE_INSTALL_PREFIX}/tests/lib/${LIBRARY_FILE}")

        configure_file("${testPlugInfoSrcPath}" "${testPlugInfoPath}")
        # XXX -- We shouldn't have to install to run tests.
        install(
            FILES ${testPlugInfoPath}
            DESTINATION ${testPlugInfoResourceDir})
    endif()

    # We always want this test to build after the package it's under, even if
    # it doesn't link directly. This ensures that this test is able to include
    # headers from its parent package.
    add_dependencies(${LIBRARY_NAME} ${PXR_PACKAGE})

    # Test libraries can include the private headers of their parent PXR_PACKAGE
    # library
    target_include_directories(${LIBRARY_NAME}
        PRIVATE $<TARGET_PROPERTY:${PXR_PACKAGE},INCLUDE_DIRECTORIES>
    )

    # XXX -- We shouldn't have to install to run tests.
    install(
        TARGETS ${LIBRARY_NAME}
        LIBRARY DESTINATION "tests/lib"
        ARCHIVE DESTINATION "tests/lib"
        RUNTIME DESTINATION "tests/lib"
    )
endfunction() # pxr_build_test_plugin

# Builds a test-only library that can be be directly linked to unit-test
# executables. Those executables can consume public headers from the test-only
# library.
function(pxr_build_test_library LIBRARY_NAME)
    set(multiValueArgs
        LIBRARIES
        CPPFILES
        PUBLIC_HEADERS
        PRIVATE_HEADERS
        PUBLIC_CLASSES
        PRIVATE_CLASSES)
    cmake_parse_arguments(args
        "${options}"
        "${oneValueArgs}"
        "${multiValueArgs}"
        ${ARGN}
    )

    # Get list of cpp files
    set(cppFiles ${args_CPPFILES})
    foreach(className IN LISTS args_PUBLIC_CLASSES args_PRIVATE_CLASSES)
        list(APPEND cppFiles ${className}.cpp)
    endforeach()

    add_library(${LIBRARY_NAME}
        SHARED
        ${cppFiles}
    )

    _pxr_target_link_libraries(${LIBRARY_NAME}
        ${args_LIBRARIES}
    )

    _get_folder("tests/lib" folder)
    set_target_properties(${LIBRARY_NAME}
        PROPERTIES 
            FOLDER "${folder}"
    )

    # Add include paths available to all libraries.
    target_include_directories(${LIBRARY_NAME}
        PRIVATE
            "${PROJECT_BINARY_DIR}/include"
            "${PROJECT_BINARY_DIR}/${PXR_INSTALL_SUBDIR}/include"
    )

    # Copy all public and private headers to the build directory. This is also
    # the case for core libraries. Clients linking to an installed tree should
    # not find test-only headers, so the public headers are not installed.
    set(headers ${args_PUBLIC_HEADERS} ${args_PRIVATE_HEADERS})
    foreach(className IN LISTS args_PUBLIC_CLASSES args_PRIVATE_CLASSES)
        list(APPEND headers ${className}.h)
    endforeach()
    _copy_headers(${LIBRARY_NAME}
        FILES ${headers}
        PREFIX ${PXR_PREFIX}
    )

    # Define macros for api.h
    string(TOUPPER ${LIBRARY_NAME} uppercaseName)
    target_compile_definitions(${LIBRARY_NAME}
        PRIVATE ${uppercaseName}_EXPORTS=1
    )

    # Find libraries under the install prefix, which has the core USD
    # libraries. Also find test-only libraries under the install prefix.
    _pxr_init_rpath(rpath "tests/lib")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/tests/lib")
    _pxr_install_rpath(rpath ${LIBRARY_NAME})

    # XXX -- We shouldn't have to install to run tests.
    install(
        TARGETS ${LIBRARY_NAME}
        LIBRARY DESTINATION "tests/lib"
        ARCHIVE DESTINATION "tests/lib"
        RUNTIME DESTINATION "tests/lib"
    )
endfunction() # pxr_build_test_library

function(pxr_build_test TEST_NAME)
    if (NOT PXR_BUILD_TESTS)
        return()
    endif()

    # If the package for this test does not have a target it must not be
    # getting built, in which case we can skip building associated tests.
    if (NOT TARGET ${PXR_PACKAGE})
        return()
    endif()

    cmake_parse_arguments(bt
        "" ""
        "LIBRARIES;CPPFILES"
        ${ARGN}
    )

    add_executable(${TEST_NAME}
        ${bt_CPPFILES}
    )

    # Turn PIC ON otherwise ArchGetAddressInfo() on Linux may yield
    # unexpected results.
    _get_folder("tests/bin" folder)
    set_target_properties(${TEST_NAME}
        PROPERTIES 
            FOLDER "${folder}"
        	POSITION_INDEPENDENT_CODE ON
    )
    target_include_directories(${TEST_NAME}
        PRIVATE $<TARGET_PROPERTY:${PXR_PACKAGE},INCLUDE_DIRECTORIES>
    )
    _pxr_target_link_libraries(${TEST_NAME}
        ${bt_LIBRARIES}
    )

    # Find libraries under the install prefix, which has the core USD
    # libraries. Also find test-only libraries under the install prefix.
    _pxr_init_rpath(rpath "tests")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib")
    _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/tests/lib")
    _pxr_install_rpath(rpath ${TEST_NAME})

    # XXX -- We shouldn't have to install to run tests.
    if(EMSCRIPTEN)
        target_compile_options(${TEST_NAME} PRIVATE "SHELL:-s MAIN_MODULE=1")

        # Note: Using NODEFS allows us to mount the temp test directory set 
        # up by the test runner into the virtual filesystem.  This allows us
        # to access test assets as well as persist any files created during
        # test execution.  This is setup as a pre-run step by test.pre.js.
        target_link_options(${TEST_NAME} PRIVATE 
            "SHELL:-lnodefs.js"
            "SHELL:-sFORCE_FILESYSTEM=1"
            "SHELL:--pre-js '${PROJECT_SOURCE_DIR}/cmake/macros/test.pre.js'"
        )
        install(
            FILES
            ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.wasm
            DESTINATION "tests"
        )
        install(CODE " \n
            file(REMOVE ${CMAKE_INSTALL_PREFIX}/tests/${TEST_NAME}) \n\
            file(COPY_FILE ${CMAKE_CURRENT_BINARY_DIR}/${TEST_NAME}.js ${CMAKE_INSTALL_PREFIX}/tests/${TEST_NAME})")
    else()
        install(TARGETS ${TEST_NAME}
                RUNTIME DESTINATION "tests"
        )
    endif()
endfunction() # pxr_build_test

function(pxr_test_scripts)
    # If we can't build Python modules then do nothing.
    if(NOT TARGET python_modules)
        return()
    endif()

    if (NOT PXR_BUILD_TESTS)
        return()
    endif()

    # If the package for this test does not have a target it must not be
    # getting built, in which case we can skip building associated tests.
    if (NOT TARGET ${PXR_PACKAGE})
        return()
    endif()

    foreach(file ${ARGN})
        # Perform regex match to extract both source resource path and
        # destination resource path.
        # Regex match appropriately takes care of windows drive letter followed
        # by a ":", which is also the token we use to separate the source and
        # destination resource paths.
        string(REGEX MATCHALL "([A-Za-z]:)?([^:]+)" file "${file}")

        list(LENGTH file n)
        if (n EQUAL 1)
            get_filename_component(destFile ${file} NAME_WE)
        elseif (n EQUAL 2)
           list(GET file 1 destFile)
           list(GET file 0 file)
        else()
           message(FATAL_ERROR
               "Failed to parse test file path ${file}")
        endif()        

        # XXX -- We shouldn't have to install to run tests.
        install(
            PROGRAMS ${file}
            DESTINATION tests
            RENAME ${destFile}
        )
    endforeach()
endfunction() # pxr_test_scripts

function(pxr_install_test_dir)
    message(DEPRECATION "Please use the TESTENV parameter of pxr_register_test "
                        "to specify test asset directories.")

    cmake_parse_arguments(bt
        "" 
        "SRC;DEST"
        ""
        ${ARGN}
    )

    _pxr_install_test_dir(
        SRC ${bt_SRC}
        DEST ${bt_DEST}
    )
endfunction() # pxr_install_test_dir

function(pxr_register_test TEST_NAME)
    if (NOT PXR_BUILD_TESTS)
        return()
    endif()

    # If the package for this test does not have a target it must not be
    # getting built, in which case we can skip building associated tests.
    if (NOT TARGET ${PXR_PACKAGE})
        return()
    endif()

    set(OPTIONS RUN_SERIAL PYTHON REQUIRES_SHARED_LIBS REQUIRES_PYTHON_MODULES PERCEPTUAL)
    set(ONE_VALUE_ARGS
            CUSTOM_PYTHON
            COMMAND
            STDOUT_REDIRECT STDERR_REDIRECT
            POST_COMMAND POST_COMMAND_STDOUT_REDIRECT POST_COMMAND_STDERR_REDIRECT
            PRE_COMMAND PRE_COMMAND_STDOUT_REDIRECT PRE_COMMAND_STDERR_REDIRECT
            FILES_EXIST FILES_DONT_EXIST
            CLEAN_OUTPUT
            EXPECTED_RETURN_CODE
            TESTENV TESTENV_DEST
            WARN WARN_PERCENT HARD_WARN FAIL FAIL_PERCENT HARD_FAIL LABELS)
    set(MULTI_VALUE_ARGS DIFF_COMPARE IMAGE_DIFF_COMPARE ENV PRE_PATH POST_PATH)

    cmake_parse_arguments(bt
        "${OPTIONS}" "${ONE_VALUE_ARGS}" "${MULTI_VALUE_ARGS}"
        ${ARGN}
    )

    # Discard tests that required shared libraries.
    if(NOT TARGET shared_libs)
        # Explicit requirement.  This is for C++ tests that dynamically
        # load libraries linked against USD code.  These tests will have
        # multiple copies of symbols and will likely re-execute
        # ARCH_CONSTRUCTOR and registration functions, which will almost
        # certainly cause problems.
        if(bt_REQUIRES_SHARED_LIBS)
            message(STATUS "Skipping test ${TEST_NAME}, shared libraries required")
            return()
        endif()
    endif()

    if(NOT TARGET python_modules)
        # Implicit requirement.  Python modules require shared USD
        # libraries.  If the test runs python it's certainly going
        # to load USD modules.  If the test uses C++ to load USD
        # modules it tells us via REQUIRES_PYTHON_MODULES.
        if(bt_PYTHON OR bt_CUSTOM_PYTHON OR bt_REQUIRES_PYTHON_MODULES)
            message(STATUS "Skipping test ${TEST_NAME}, Python modules required")
            return()
        endif()
    endif()

    # This harness is a filter which allows us to manipulate the test run, 
    # e.g. by changing the environment, changing the expected return code, etc.
    set(testWrapperCmd ${PROJECT_SOURCE_DIR}/cmake/macros/testWrapper.py --verbose)

    # For Emscripten we want to explicitly run the test with the emsdk provided
    # node (this make sure we are using emscripten with compatible node and not
    # use an outdated node available on the system). The test themselves are
    # javascript files which contain a shebang, however if we are trying to run
    # them on windows this will result in errors when trying to spawn the test
    # process.
    if (EMSCRIPTEN)
        if (DEFINED ENV{EMSDK_NODE})
            message(STATUS "Using EMSDK_NODE node for tests: $ENV{EMSDK_NODE}")
            set(testWrapperCmd ${testWrapperCmd} --test-runner $ENV{EMSDK_NODE})
        else()
            message(STATUS "EMSDK_NODE not set, falling back to system provided node")
            set(testWrapperCmd ${testWrapperCmd} --test-runner node)
        endif()
    endif()

    if (bt_STDOUT_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --stdout-redirect=${bt_STDOUT_REDIRECT})
    endif()

    if (bt_STDERR_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --stderr-redirect=${bt_STDERR_REDIRECT})
    endif()

    if (bt_PRE_COMMAND_STDOUT_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --pre-command-stdout-redirect=${bt_PRE_COMMAND_STDOUT_REDIRECT})
    endif()

    if (bt_PRE_COMMAND_STDERR_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --pre-command-stderr-redirect=${bt_PRE_COMMAND_STDERR_REDIRECT})
    endif()

    if (bt_POST_COMMAND_STDOUT_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --post-command-stdout-redirect=${bt_POST_COMMAND_STDOUT_REDIRECT})
    endif()

    if (bt_POST_COMMAND_STDERR_REDIRECT)
        set(testWrapperCmd ${testWrapperCmd} --post-command-stderr-redirect=${bt_POST_COMMAND_STDERR_REDIRECT})
    endif()

    # Determine test environment directory and set up any necessary
    # directory copying during the install step. testenvDir is passed to the
    # test wrapper so it can copy the testenv contents into the run directory.
    if (bt_TESTENV)
        if (bt_TESTENV_DEST)
            # if a dest directory is specified, then we will assume that it
            # will contain a folder with ${TEST_NAME} in its path which will
            # be set as the testenvDir below. This is currently only used
            # for a few specific tests and in general we prefer to use the
            # automatic method below.
            _pxr_install_test_dir(SRC ${bt_TESTENV} DEST ${bt_TESTENV_DEST})
        else()
            # when using the testenv without a specific destination set, we
            # want to set the testenvDir to the folder name of the testenv dir
            # we copied. This allows multiple tests to reference the same set
            # of test files even though their test name will be different.
            cmake_path(GET bt_TESTENV FILENAME testNameDir)
            _pxr_install_test_dir(SRC ${bt_TESTENV} DEST ${testNameDir})
            set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${testNameDir})
        endif()
    endif()

    # By default we will simply use the test name for the testenvDir. In
    # the case that  no testenv is specified, this will point to a non
    # existant folder and no copy will be executed. In the case where a
    # destination was explicitly specified, the directory which matches
    # the test name and its contents will be recursively copied.
    if (NOT DEFINED testenvDir)
        set(testenvDir ${CMAKE_INSTALL_PREFIX}/tests/ctest/${TEST_NAME})
    endif()

    set(testWrapperCmd ${testWrapperCmd} --testenv-dir=${testenvDir})

    if (bt_DIFF_COMPARE)
        foreach(compareFile ${bt_DIFF_COMPARE})
            set(testWrapperCmd ${testWrapperCmd} --diff-compare=${compareFile})
        endforeach()
    endif()

    if (bt_IMAGE_DIFF_COMPARE)
        if (IMAGE_DIFF_TOOL)
            foreach (compareFile ${bt_IMAGE_DIFF_COMPARE})
                set(testWrapperCmd ${testWrapperCmd} --image-diff-compare=${compareFile})
            endforeach ()

            if (bt_WARN)
                set(testWrapperCmd ${testWrapperCmd} --warn=${bt_WARN})
            endif()

            if (bt_WARN_PERCENT)
                set(testWrapperCmd ${testWrapperCmd} --warnpercent=${bt_WARN_PERCENT})
            endif()

            if (bt_HARD_WARN)
                set(testWrapperCmd ${testWrapperCmd} --hardwarn=${bt_HARD_WARN})
            endif()

            if (bt_FAIL)
                set(testWrapperCmd ${testWrapperCmd} --fail=${bt_FAIL})
            endif()

            if (bt_FAIL_PERCENT)
                set(testWrapperCmd ${testWrapperCmd} --failpercent=${bt_FAIL_PERCENT})
            endif()

            if (bt_HARD_FAIL)
                set(testWrapperCmd ${testWrapperCmd} --hardfail=${bt_HARD_FAIL})
            endif()

            if(bt_PERCEPTUAL)
                set(testWrapperCmd ${testWrapperCmd} --perceptual)
            endif()

            # Make sure to add the image diff tool to the PATH so
            # it can be easily found within the testWrapper
            get_filename_component(IMAGE_DIFF_TOOL_PATH ${IMAGE_DIFF_TOOL} DIRECTORY)
            set(testWrapperCmd ${testWrapperCmd} --post-path=${IMAGE_DIFF_TOOL_PATH})
        endif()
    endif()

    if (bt_DIFF_COMPARE OR bt_IMAGE_DIFF_COMPARE)
        # Common settings we only want to set once if either is used

        # For now the baseline directory is assumed by convention from the test
        # name. There may eventually be cases where we'd want to specify it by
        # an argument though.
        set(baselineDir ${testenvDir}/baseline)
        set(testWrapperCmd ${testWrapperCmd} --baseline-dir=${baselineDir})

        # <PXR_CTEST_RUN_ID> will be set by CTestCustom.cmake, and then
        # expanded by testWrapper.py
        set(failuresDir ${PROJECT_BINARY_DIR}/Testing/Failed-Diffs/<PXR_CTEST_RUN_ID>/${TEST_NAME})
        set(testWrapperCmd ${testWrapperCmd} --failures-dir=${failuresDir})
    endif()

    if (bt_CLEAN_OUTPUT)
        set(testWrapperCmd ${testWrapperCmd} --clean-output-paths=${bt_CLEAN_OUTPUT})
    endif()

    if (bt_FILES_EXIST)
        set(testWrapperCmd ${testWrapperCmd} --files-exist=${bt_FILES_EXIST})
    endif()

    if (bt_FILES_DONT_EXIST)
        set(testWrapperCmd ${testWrapperCmd} --files-dont-exist=${bt_FILES_DONT_EXIST})
    endif()

    if (bt_PRE_COMMAND)
        set(testWrapperCmd ${testWrapperCmd} --pre-command=${bt_PRE_COMMAND})
    endif()

    if (bt_POST_COMMAND)
        set(testWrapperCmd ${testWrapperCmd} --post-command=${bt_POST_COMMAND})
    endif()

    if (bt_EXPECTED_RETURN_CODE)
        set(testWrapperCmd ${testWrapperCmd} --expected-return-code=${bt_EXPECTED_RETURN_CODE})
    endif()

    # Ensure that TF_FATAL_VERIFY is enabled for tests, so that failed verifies
    # turn into test failures.
    # Set this first, so that env vars passed to pxr_register_test can turn off
    # TF_FATAL_VERIFY where desired.
    set(testWrapperCmd ${testWrapperCmd} --env-var=TF_FATAL_VERIFY=1)

    # If building on Windows, the tests need to add directories containing DLLs
    # to the PATH. This includes directories for core libraries, plugins, and
    # test-only libraries. On non-Windows platforms, this is handled by the
    # RPATH embedded in the test executable. Note this is a best-effort to add
    # the appropriate DLL paths, but the user may still be responsible for
    # modifying the PATH to find 3rd party DLLs from outside the tree.
    if (WIN32)
        set(testWrapperCmd ${testWrapperCmd}
            "--pre-path=${CMAKE_INSTALL_PREFIX}/lib"
            "--pre-path=${CMAKE_INSTALL_PREFIX}/plugin/usd"
            "--pre-path=${CMAKE_INSTALL_PREFIX}/tests/lib")
    endif()

    # Allow env vars to be set via a global variable to make it easier
    # to affect a set of tests (e.g., all tests in a library). Set
    # this first to allow tests to override these env vars.
    if (PXR_TEST_ENV_VARS)
        foreach(env ${PXR_TEST_ENV_VARS})
            set(testWrapperCmd ${testWrapperCmd} --env-var=${env})
        endforeach()
    endif()

    if (bt_ENV)
        foreach(env ${bt_ENV})
            set(testWrapperCmd ${testWrapperCmd} --env-var=${env})
        endforeach()
    endif()

    if (bt_PRE_PATH)
        foreach(path ${bt_PRE_PATH})
            set(testWrapperCmd ${testWrapperCmd} --pre-path=${path})
        endforeach()
    endif()

    if (bt_POST_PATH)
        foreach(path ${bt_POST_PATH})
            set(testWrapperCmd ${testWrapperCmd} --post-path=${path})
        endforeach()
    endif()
        
    # If we're building static libraries, the C++ tests that link against
    # these libraries will look for resource files in the "usd" subdirectory
    # relative to where the tests are installed. However, the build installs
    # these files in the "lib" directory where the libraries are installed. 
    #
    # We don't want to copy these resource files for each test, so instead
    # we set the PXR_PLUGINPATH_NAME env var to point to the "lib/usd"
    # directory where these files are installed.
    if (NOT TARGET shared_libs)
        set(testWrapperCmd ${testWrapperCmd} --env-var=${PXR_PLUGINPATH_NAME}=${CMAKE_INSTALL_PREFIX}/lib/usd)
    endif()

    if (PXR_TEST_RUN_TEMP_DIR_PREFIX)
          set(testWrapperCmd ${testWrapperCmd} --tempdirprefix=${PXR_TEST_RUN_TEMP_DIR_PREFIX})
    endif()

    # Ensure that Python imports the Python files built by this build.
    # On Windows convert backslash to slash and don't change semicolons
    # to colons.
    if(IS_ABSOLUTE "${PXR_PYTHON_INSTALL_DIR}")
        set(_testPythonPath "${PXR_PYTHON_INSTALL_DIR};$ENV{PYTHONPATH}")
    else()
        set(_testPythonPath "${CMAKE_INSTALL_PREFIX}/${PXR_PYTHON_INSTALL_DIR};$ENV{PYTHONPATH}")
    endif()
    if(WIN32)
        string(REGEX REPLACE "\\\\" "/" _testPythonPath "${_testPythonPath}")
    else()
        string(REPLACE ";" ":" _testPythonPath "${_testPythonPath}")
    endif()

    # Ensure we run with the appropriate python executable.
    if (bt_CUSTOM_PYTHON)
        set(testCmd "${bt_CUSTOM_PYTHON} ${bt_COMMAND}")
    elseif (bt_PYTHON)
        set(testCmd "${PYTHON_EXECUTABLE} ${bt_COMMAND}")
    else()
        set(testCmd "${bt_COMMAND}")
    endif()

    add_test(
        NAME ${TEST_NAME}
        COMMAND ${PYTHON_EXECUTABLE} ${testWrapperCmd}
                "--env-var=PYTHONPATH=${_testPythonPath}" ${testCmd}
    )

    # But in some cases, we need to pass cmake properties directly to cmake
    # run_test, rather than configuring the environment
    if (bt_RUN_SERIAL)
        set_tests_properties(${TEST_NAME} PROPERTIES RUN_SERIAL TRUE)
    endif()
    if (bt_LABELS)
        set_tests_properties(${TEST_NAME} PROPERTIES LABELS "${bt_LABELS}")
    endif()
endfunction() # pxr_register_test

function(pxr_setup_plugins)
    # Install a top-level plugInfo.json in the shared area and into the 
    # top-level plugin area
    _get_resources_dir_name(resourcesDir)

    # Add extra plugInfo.json include paths to the top-level plugInfo.json,
    # relative to that top-level file.
    set(extraIncludes "")
    list(REMOVE_DUPLICATES PXR_EXTRA_PLUGINS)
    foreach(dirName ${PXR_EXTRA_PLUGINS})
        file(RELATIVE_PATH
            relDirName
            "${CMAKE_INSTALL_PREFIX}/lib/usd"
            "${CMAKE_INSTALL_PREFIX}/${dirName}"
        )
        set(extraIncludes "${extraIncludes},\n        \"${relDirName}/\"")
    endforeach()

    set(plugInfoContents "{\n    \"Includes\": [\n        \"*/${resourcesDir}/\"${extraIncludes}\n    ]\n}\n")
    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/plugins_plugInfo.json"
         "${plugInfoContents}")
    install(
        FILES "${CMAKE_CURRENT_BINARY_DIR}/plugins_plugInfo.json"
        DESTINATION lib/usd
        RENAME "plugInfo.json"
    )

    set(plugInfoContents "{\n    \"Includes\": [ \"*/${resourcesDir}/\" ]\n}\n")
    file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/usd_plugInfo.json"
         "${plugInfoContents}")
    install(
        FILES "${CMAKE_CURRENT_BINARY_DIR}/usd_plugInfo.json"
        DESTINATION plugin/usd
        RENAME "plugInfo.json"
    )

    # For emscripten builds, we need to ensure that the top level plugInfo.json
    # file is included in the resulting application bundle.  When installing,
    # we are sure to reference the installed location of this file.
    if (EMSCRIPTEN)
        foreach(lib ${PXR_CORE_LIBS})
          target_link_options(${lib} PUBLIC
              "$<BUILD_INTERFACE:SHELL:--embed-file ${CMAKE_CURRENT_BINARY_DIR}/plugins_plugInfo.json@/usd/plugInfo.json>"
              "$<INSTALL_INTERFACE:SHELL:--embed-file $<INSTALL_PREFIX>/lib/usd/plugInfo.json@/usd/plugInfo.json>")
        endforeach()
    endif()
endfunction() # pxr_setup_plugins

function(pxr_add_extra_plugins PLUGIN_AREAS)
    # Install a top-level plugInfo.json in the given plugin areas.
    _get_resources_dir_name(resourcesDir)
    set(plugInfoContents "{\n    \"Includes\": [ \"*/${resourcesDir}/\" ]\n}\n")

    get_property(help CACHE PXR_EXTRA_PLUGINS PROPERTY HELPSTRING)

    foreach(area ${PLUGIN_AREAS})
        file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/${area}_plugInfo.json"
            "${plugInfoContents}")
        install(
            FILES "${CMAKE_CURRENT_BINARY_DIR}/${area}_plugInfo.json"
            DESTINATION "${PXR_INSTALL_SUBDIR}/${area}"
            RENAME "plugInfo.json"
        )
        list(APPEND PXR_EXTRA_PLUGINS "${PXR_INSTALL_SUBDIR}/${area}")
    endforeach()

    set(PXR_EXTRA_PLUGINS "${PXR_EXTRA_PLUGINS}" CACHE INTERNAL "${help}")
endfunction() # pxr_setup_third_plugins

function(pxr_toplevel_prologue)
    # Generate a namespace declaration header, pxr.h, at the top level of
    # pxr at configuration time.
    configure_file(${PROJECT_SOURCE_DIR}/pxr/pxr.h.in
        ${PROJECT_BINARY_DIR}/include/pxr/pxr.h     
    )  
    install(
        FILES ${PROJECT_BINARY_DIR}/include/pxr/pxr.h
        DESTINATION include/pxr
    )

    # Create a monolithic shared library target if we should import one
    # or create one.
    if(PXR_BUILD_MONOLITHIC)
        if(PXR_MONOLITHIC_IMPORT)
            # Gather the export information for usd_m.
            include("${PXR_MONOLITHIC_IMPORT}" OPTIONAL RESULT_VARIABLE found)

            # If the import wasn't found then create it and import it.
            # This ensures that the build files will be regenerated if
            # the file's contents change.  If this isn't desired or
            # write permissions aren't granted the client can configure
            # first without PXR_MONOLITHIC_IMPORT, build the 'monolithic'
            # target, build their own shared library and export file,
            # then configure again with PXR_MONOLITHIC_IMPORT.
            if(found STREQUAL "NOTFOUND")
                file(WRITE "${PXR_MONOLITHIC_IMPORT}" "")
                include("${PXR_MONOLITHIC_IMPORT}")
            endif()

            # If there's an IMPORTED_LOCATION then its parent must be
            # the install directory ${CMAKE_INSTALL_PREFIX}.  If it
            # isn't then we won't be able to find plugInfo.json files
            # at runtime because they're found relative to the library
            # that contains pxr/base/lib/plug/initConfig.cpp.  The
            # exception is if ${PXR_INSTALL_LOCATION} is set;  in that
            # case we assume the files will be found there regardless
            # of IMPORTED_LOCATION.  Note, however, that the install
            # cannot be relocated in this case.
            if(NOT PXR_INSTALL_LOCATION AND TARGET usd_m)
                get_property(location TARGET usd_m PROPERTY IMPORTED_LOCATION)
                if(location)
                    # Remove filename and directory.
                    get_filename_component(parent "${location}" PATH)
                    get_filename_component(parent "${parent}" PATH)
                    get_filename_component(parent "${parent}" ABSOLUTE)
                    get_filename_component(prefix "${CMAKE_INSTALL_PREFIX}" ABSOLUTE)
                    if(NOT "${parent}" STREQUAL "${prefix}")
                        message("IMPORTED_LOCATION for usd_m ${location} inconsistent with install directory ${CMAKE_INSTALL_PREFIX}.")
                        message(WARNING "May not find plugins at runtime.")
                    endif()
                endif()
            endif()
        else()
            # When building the monolithic library we build all core libraries
            # as OBJECT libs and link to those. We need at least one source file for the
            # library so we create an empty one.
            add_custom_command(
                OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp"
                COMMAND ${CMAKE_COMMAND} -E touch "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp"
            )

            # Our monolithic library.
            if(BUILD_SHARED_LIBS)
                set(libType SHARED)
                set(libName "usd_ms")
            else()
                set(libType STATIC)
                set(libName "usd_m")
            endif()

            add_library(usd_m ${libType} "${CMAKE_CURRENT_BINARY_DIR}/usd_m.cpp")

            _get_folder("" folder)
            _get_library_prefix(libPrefix)
            set_target_properties(usd_m
                PROPERTIES
                    FOLDER "${folder}"
                    PREFIX "${libPrefix}"
                    IMPORT_PREFIX "${libPrefix}"
                    OUTPUT_NAME ${libName}
            )
            _get_install_dir("lib" libInstallPrefix)
            install(
                TARGETS usd_m
                EXPORT pxrTargets
                LIBRARY DESTINATION ${libInstallPrefix}
                ARCHIVE DESTINATION ${libInstallPrefix}
                RUNTIME DESTINATION ${libInstallPrefix}
            )
            if(WIN32 AND BUILD_SHARED_LIBS)
                install(
                    FILES $<TARGET_PDB_FILE:usd_m>
                    DESTINATION ${libInstallPrefix}
                    OPTIONAL
                )
            endif()
        endif()
    endif()

    # Create a target for shared libraries.  We currently use this only
    # to test its existence.
    if(BUILD_SHARED_LIBS)
        add_custom_target(shared_libs)
    endif()

    # Create a target for targets that require Python.  Each should add
    # itself as a dependency to the "python_modules" target.
    if(TARGET shared_libs AND PXR_ENABLE_PYTHON_SUPPORT)
        add_custom_target(python_modules ALL)
    endif()
endfunction() # pxr_toplevel_prologue

function(pxr_toplevel_epilogue)
    # If we're building a shared monolithic library then link it against
    # each internal object library.
    if(TARGET usd_m AND NOT PXR_MONOLITHIC_IMPORT)
        if(APPLE AND PXR_PY_UNDEFINED_DYNAMIC_LOOKUP)
            # When not explicitly linking to the python lib we need to allow
            # the linker to complete without resolving all symbols. This lets
            # python resolve at runtime, and use this to support python
            # versions built with different compilers and point versions.
            # This only needed on macOS; this is not an issue on Windows,
            # and on Linux the equivalent --allow-shlib-undefined option for ld
            # is enabled by default when creating shared libraries.
            target_link_options(usd_m
                PUBLIC
                "LINKER:SHELL:-undefined dynamic_lookup"
            )
        endif()

        # When building a monolithic library (static or shared) we want all
        # API functions to be exported. So add FOO_EXPORTS=1 for every
        # library in PXR_OBJECT_LIBS, where FOO is the uppercase version
        # of the library name, to every library in PXR_OBJECT_LIBS.
        set(exports "")
        foreach(lib ${PXR_OBJECT_LIBS})
            string(TOUPPER ${lib} uppercaseName)
            list(APPEND exports "${uppercaseName}_EXPORTS=1")
        endforeach()

        if (TARGET python)
            # The boost python target uses a different export macro so
            # add that as well.
            list(APPEND exports "PXR_BOOST_PYTHON_SOURCE=1")
        endif()

        foreach(lib ${PXR_OBJECT_LIBS})
            target_compile_definitions(${lib} PRIVATE ${exports})
        endforeach()

        if(BUILD_SHARED_LIBS)
            target_link_libraries(usd_m
                PUBLIC
                    ${PXR_OBJECT_LIBS}
                    ${PXR_MALLOC_LIBRARY}
                    ${PXR_THREAD_LIBS}
            )

            _pxr_init_rpath(rpath "${libInstallPrefix}")
            _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/${PXR_INSTALL_SUBDIR}/lib")
            _pxr_add_rpath(rpath "${CMAKE_INSTALL_PREFIX}/lib")
            _pxr_install_rpath(rpath usd_m)
        else()
            foreach(lib ${PXR_OBJECT_LIBS})
                target_link_libraries(usd_m
                    PUBLIC
                        ${lib}
                )
                target_sources(usd_m PRIVATE "$<TARGET_OBJECTS:${lib}>")
                get_property(libs TARGET ${lib} PROPERTY INTERFACE_LINK_LIBRARIES)
                target_link_libraries(usd_m
                    PRIVATE
                        ${libs}
                )
            endforeach()

            set_target_properties(usd_m
                PROPERTIES
                    POSITION_INDEPENDENT_CODE ON
            )
        endif()
    endif()

    # Setup the plugins in the top epilogue to ensure that everybody has had a
    # chance to update PXR_EXTRA_PLUGINS with their plugin paths.
    pxr_setup_plugins()

    if (PXR_BUILD_APPLE_FRAMEWORK)
        pxr_create_apple_framework()
    endif ()

endfunction() # pxr_toplevel_epilogue

function(pxr_core_prologue)
    set(_building_core TRUE PARENT_SCOPE)
    if(PXR_BUILD_MONOLITHIC)
        set(_building_monolithic TRUE PARENT_SCOPE)
    endif()
endfunction() # pxr_core_prologue

function(pxr_core_epilogue)
    if(_building_core)
        if(_building_monolithic)
            set(_building_monolithic FALSE PARENT_SCOPE)
        endif()
        if(PXR_ENABLE_PYTHON_SUPPORT)
            pxr_setup_python()
        endif()
        set(_building_core FALSE PARENT_SCOPE)
    endif()
endfunction() # pxr_core_epilogue

function(pxr_tests_prologue)
    add_custom_target(
        test_setup
        ALL
        DEPENDS "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
    )
    add_custom_command(
        OUTPUT "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
        COMMAND ${CMAKE_COMMAND} -E copy
            "${CMAKE_CURRENT_SOURCE_DIR}/cmake/defaults/CTestCustom.cmake"
            "${PROJECT_BINARY_DIR}/CTestCustom.cmake"
        DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/defaults/CTestCustom.cmake"
        COMMENT "Copying CTestCustom.cmake"
    )
endfunction() # pxr_tests_prologue

function(pxr_build_python_documentation)
    set(BUILT_XML_DOCS "${PROJECT_BINARY_DIR}/docs/doxy_xml")
    set(CONVERT_DOXYGEN_TO_PYTHON_DOCS_SCRIPT
       "${PROJECT_SOURCE_DIR}/docs/python/convertDoxygen.py")

    # Compute the absolute path to the Python bindings install directory.
    if(IS_ABSOLUTE "${PXR_PYTHON_INSTALL_DIR}")
        set(_pythonInstallDirAbs "${PXR_PYTHON_INSTALL_DIR}")
    else()
        set(_pythonInstallDirAbs "${CMAKE_INSTALL_PREFIX}/${PXR_PYTHON_INSTALL_DIR}")
    endif()
    set(INSTALL_PYTHON_PXR_ROOT "${_pythonInstallDirAbs}/pxr")

    # Get the list of pxr python modules and run a install command for each
    get_property(pxrPythonModules GLOBAL PROPERTY PXR_PYTHON_MODULES)
    # Create string of module names, joined with ","
    string(REPLACE ";" "," pxrPythonModulesStr "${pxrPythonModules}")
    # Run convertDoxygen on the module list, setting PYTHONPATH
    # to the install path for the USD Python modules
    if (WIN32)
        set(DLL_PATH_FLAG "--dllPath \"${CMAKE_INSTALL_PREFIX}/lib;${CMAKE_INSTALL_PREFIX}/bin;${CMAKE_INSTALL_PREFIX}/plugin/usd;${CMAKE_INSTALL_PREFIX}/share/usd/examples/plugin\"")
    else()
        set(DLL_PATH_FLAG "")
    endif()
    install(CODE "\
        execute_process(\
            WORKING_DIRECTORY ${PROJECT_SOURCE_DIR}/cmake \
            RESULT_VARIABLE convert_doxygen_return_code
            COMMAND ${PYTHON_EXECUTABLE} ${CONVERT_DOXYGEN_TO_PYTHON_DOCS_SCRIPT} \
                --package pxr --module ${pxrPythonModulesStr} \
                --inputIndex ${BUILT_XML_DOCS}/index.xml \
                --pythonPath ${_pythonInstallDirAbs} \
                ${DLL_PATH_FLAG} \
                --output ${INSTALL_PYTHON_PXR_ROOT})
        if (NOT \${convert_doxygen_return_code} EQUAL \"0\")
            message( FATAL_ERROR \"Error generating python docstrings - ${CONVERT_DOXYGEN_TO_PYTHON_DOCS_SCRIPT} return code: \${convert_doxygen_return_code} \")
        endif()
    ")

endfunction() # pxr_build_python_documentation

# Adding support for a "docs-only" directory, needed when adding doxygen docs
# not associated with a specific library/etc. 
function(pxr_docs_only_dir NAME)
    # Get list of doxygen files, which could include image files and/or 
    # snippets example cpp files 
    set(multiValueArgs
        DOXYGEN_FILES
    )
    cmake_parse_arguments(args
        ""
        ""
        "${multiValueArgs}"
        ${ARGN}
    )
    if(PXR_BUILD_DOCUMENTATION)
        _copy_doxygen_files(${NAME}
            IS_LIB
                FALSE
            HEADER_INSTALL_PREFIX
                "include/${PXR_PREFIX}"
            DOXYGEN_FILES
                ${args_DOXYGEN_FILES}
        )
    endif()
endfunction() # pxr_docs_only_dir

# Sets rpaths for the specified TARGET to the given RPATHS. The target's
# runtime destination directory is given by ORIGIN. If ORIGIN is not
# absolute it is assumed to be relative to CMAKE_INSTALL_PREFIX.
function(pxr_set_rpaths_for_target TARGET)
    set(oneValueArgs ORIGIN)
    set(multiValueArgs RPATHS)
    cmake_parse_arguments(args "" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

    _pxr_init_rpath(rpath ${args_ORIGIN})

    foreach(path IN LISTS args_RPATHS)
        _pxr_add_rpath(rpath ${path})
    endforeach()

    _pxr_install_rpath(rpath ${TARGET})

endfunction() # pxr_set_rpaths_for_target