diff --git a/.gitignore b/.gitignore index 31e05ef..75e1777 100644 --- a/.gitignore +++ b/.gitignore @@ -11,16 +11,10 @@ strata.lock /.ost-ci/ /.ost-ci-home/ -# Built plugin libraries are staged into each bundle's lib/ by the build, and -# tools into their own bin/. -/plugins/*/lib/ -/tools/*/bin/ - -# Generated into bundle resources by the build: plugInfo.json names this -# platform's library suffix, and buildInfo.json varies per commit, toolchain -# and platform. -/plugins/*/plugin/resources/*/plugInfo.json -/plugins/*/plugin/resources/*/buildInfo.json +# Nothing the build produces lands in the source tree: bundles and tools are +# staged in the binary tree (cmake/UsdMotionStage.cmake), so a lib/, bin/ or +# plugInfo.json appearing under plugins/ or tools/ is a regression for +# `git status` to show, not to hide. # --- CMake / MSVC generated --- CMakeCache.txt diff --git a/CHANGELOG.md b/CHANGELOG.md index f956dc2..cb0e1c0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,21 @@ separate from the package version ### Changed +- **A build writes nothing into the source tree.** `execMotion`'s library and + its generated `plugInfo.json`, and the three CLIs, were built into + `plugins/execMotion/lib/`, `plugins/execMotion/plugin/resources/` and + `tools/*/bin/`, because `ost` packaged them from there. They are now staged in + each member's binary directory (`cmake/UsdMotionStage.cmake`), one stage per + configuration under a multi-config generator, so Debug and Release no longer + overwrite each other. `execMotion` gains install rules, which `ost plugin + build` (0.23.5 and later) installs into the bundle's target-local stage for + `ost plugin test` and `ost plugin package`; its package no longer carries + `plugInfo.json.in`. `motion_convert`'s build stages the profiles beside it, + so a converter run from the build tree still finds them with no flags. The + `.gitignore` entries that hid those outputs are gone: a checkout built before + this change can delete `plugins/execMotion/lib/`, + `plugins/execMotion/plugin/resources/execMotion/plugInfo.json` and + `tools/*/bin/`. - **`ost` is pinned to 0.23.6**, in `openstrata.ci.yaml`, the rendered source CI and the release lane. 0.23.5 and 0.23.6 stage an installable bundle and a root-built tool per target and package from that stage, which is what moving diff --git a/cmake/UsdMotionProject.cmake b/cmake/UsdMotionProject.cmake index ff95ac4..5b88e81 100644 --- a/cmake/UsdMotionProject.cmake +++ b/cmake/UsdMotionProject.cmake @@ -107,3 +107,4 @@ include("${CMAKE_CURRENT_LIST_DIR}/UsdMotionTargets.cmake") include("${CMAKE_CURRENT_LIST_DIR}/UsdMotionUtf8CodePage.cmake") include("${CMAKE_CURRENT_LIST_DIR}/UsdMotionInstall.cmake") include("${CMAKE_CURRENT_LIST_DIR}/UsdMotionTesting.cmake") +include("${CMAKE_CURRENT_LIST_DIR}/UsdMotionStage.cmake") diff --git a/cmake/UsdMotionStage.cmake b/cmake/UsdMotionStage.cmake new file mode 100644 index 0000000..6f4c61e --- /dev/null +++ b/cmake/UsdMotionStage.cmake @@ -0,0 +1,72 @@ +# SPDX-License-Identifier: Apache-2.0 +# +# UsdMotionStage.cmake -- where a build puts what it produces: the binary tree, +# never the source tree. +# +# A member's *stage* is its CMake binary directory, laid out the way the member +# is laid out: a tool's executable in `bin/`, a bundle's library in `lib/` and +# its registration under the `plugin/resources/...` path its manifest names. A +# multi-config generator gets one stage per configuration, `//`, +# so a Debug build and a Release build never overwrite each other's files. +# +# The stage is what the build's own tests run against. It is not what ships: +# +# * a bundle ships what its install rules install. `ost plugin build` (0.23.5 +# and later) installs the bundle into its target-local bundle stage, and +# `ost plugin test` and `ost plugin package` read that; +# * a tool ships the executable `ost build` finds in the root build tree under +# the member's `bin/`, which is this stage under a single-config generator, +# the only kind `ost` configures. +# +# So the source tree stays an input only: a build writes nothing into it, and a +# Debug/Release pair, two targets or a parallel build cannot collide there. +include_guard(GLOBAL) + +# usdmotion_member_stage() +# +# The current member's stage directory, as a path that may hold `$`: +# usable in target properties, file(GENERATE), custom commands and test +# properties, which all evaluate generator expressions. +function(usdmotion_member_stage out_var) + get_property(_multi_config GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) + if(_multi_config) + set(${out_var} "${CMAKE_CURRENT_BINARY_DIR}/$" PARENT_SCOPE) + else() + set(${out_var} "${CMAKE_CURRENT_BINARY_DIR}" PARENT_SCOPE) + endif() +endfunction() + +# usdmotion_stage_tool() +# +# A tool's executable goes to `/bin/`, the directory its +# openstrata.tool.yaml declares. The path carries a generator expression under +# a multi-config generator, which also stops CMake appending a configuration +# directory of its own. +function(usdmotion_stage_tool target) + usdmotion_member_stage(_stage) + set_target_properties(${target} PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${_stage}/bin") +endfunction() + +# usdmotion_stage_bundle( PLUG_INFO RESOURCES ) +# +# A bundle's library goes to `/lib/`, and the registration PLUG_INFO +# names -- already configured -- to `//plugInfo.json`, so the +# stage is a loadable bundle: its plugInfo.json finds the library by the same +# relative LibraryPath the installed bundle uses. Point PXR_PLUGINPATH_NAME at +# `$>/../` to load it. +function(usdmotion_stage_bundle target) + cmake_parse_arguments(ARG "" "PLUG_INFO;RESOURCES" "" ${ARGN}) + if(NOT ARG_PLUG_INFO OR NOT ARG_RESOURCES) + message(FATAL_ERROR "usdmotion_stage_bundle: PLUG_INFO and RESOURCES are required") + endif() + usdmotion_member_stage(_stage) + # RUNTIME is the DLL on Windows, LIBRARY the shared object elsewhere. A + # plugin is loaded rather than linked, so its import library, if any, stays + # out of the stage. + set_target_properties(${target} PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${_stage}/lib" + LIBRARY_OUTPUT_DIRECTORY "${_stage}/lib") + file(GENERATE + OUTPUT "${_stage}/${ARG_RESOURCES}/plugInfo.json" + INPUT "${ARG_PLUG_INFO}") +endfunction() diff --git a/docs/architecture/WORKSPACE.md b/docs/architecture/WORKSPACE.md index d9248cf..d0cf08c 100644 --- a/docs/architecture/WORKSPACE.md +++ b/docs/architecture/WORKSPACE.md @@ -182,9 +182,18 @@ Code arrives from `usd-vrm-plugins` under that repository's moving rules `UsdMotionOpenUsd.cmake` (the pin, OpenUSD's targets as `usdmotion::pxr::`, and the OpenExec probe), `UsdMotionTargets.cmake` (a sibling package, and `/utf-8`), `UsdMotionInstall.cmake`, - `UsdMotionTesting.cmake` and `UsdMotionUtf8CodePage.cmake`. A component's - `CMakeLists.txt` keeps what is its own: its sources, its `PUBLIC` and - `PRIVATE` links, its definitions, its install and its tests. + `UsdMotionStage.cmake`, `UsdMotionTesting.cmake` and + `UsdMotionUtf8CodePage.cmake`. A component's `CMakeLists.txt` keeps what is + its own: its sources, its `PUBLIC` and `PRIVATE` links, its definitions, its + install and its tests. +- A build writes nothing into the source tree. Each member is staged in its + binary directory, laid out as the member is (a tool's `bin/`, a bundle's + `lib/` beside its `plugin/resources/`), one stage per configuration under a + multi-config generator; the build's tests run against that stage. What ships + is what the install rules install: `ost plugin build` installs the bundle + into its target-local stage, which `ost plugin test` and `ost plugin package` + read, and `ost build` takes each tool from its member's `bin/` in the root + build tree. ## 5. Invariants diff --git a/openstrata.ci.yaml b/openstrata.ci.yaml index 595c7d7..3de201b 100644 --- a/openstrata.ci.yaml +++ b/openstrata.ci.yaml @@ -26,9 +26,9 @@ # rules, and a root-built tool, per target instead of reading them from the # member's source tree (usd-mmd-plugins' ost report 01), and 0.23.6 packages a # workspace bundle from that same stage (usd-vrm-plugins' ost report 47). That -# is the precondition for moving `execMotion`'s `lib/` and `plugInfo.json` and -# the CLIs' `bin/` out of the source tree; until they move, both versions read -# them where they are, as 0.23.4 did. 0.23.4 was re-pinned across the +# is what lets `execMotion`'s `lib/` and `plugInfo.json` and the CLIs' `bin/` +# live in the binary tree (cmake/UsdMotionStage.cmake) rather than the source +# tree: a build writes nothing into a checkout. 0.23.4 was re-pinned across the # ecosystem together with usd-vrm-plugins, motion-connectors and # usd-mmd-plugins, and lets a consumer pin a published bundle (`requires.bundles[].artifact`) and a published # tool for its tests (`requires.tools[]`), which is what `execMotion` and the diff --git a/plugins/execMotion/CMakeLists.txt b/plugins/execMotion/CMakeLists.txt index a0c94d5..d7707bb 100644 --- a/plugins/execMotion/CMakeLists.txt +++ b/plugins/execMotion/CMakeLists.txt @@ -105,24 +105,36 @@ target_link_libraries(${PLUGIN_NAME} PRIVATE usdmotion::pxr::exec usdmotion::pxr::execUsd) +# The library is named lib on every platform, which is the name +# plugInfo.json.in spells. set(OPENSTRATA_PLUGIN_LIBRARY_PREFIX "lib") set_target_properties(${PLUGIN_NAME} PROPERTIES - PREFIX "${OPENSTRATA_PLUGIN_LIBRARY_PREFIX}" - ARCHIVE_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/lib") -foreach(_cfg DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) - set_target_properties(${PLUGIN_NAME} PROPERTIES - ARCHIVE_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/lib" - LIBRARY_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/lib" - RUNTIME_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/lib") -endforeach() + PREFIX "${OPENSTRATA_PLUGIN_LIBRARY_PREFIX}") +# The registration names this platform's library suffix, so it is generated, +# into the binary tree; nothing here writes into the source tree. +set(_exec_motion_resources "plugin/resources/execMotion") configure_file( - "${CMAKE_CURRENT_SOURCE_DIR}/plugin/resources/execMotion/plugInfo.json.in" - "${CMAKE_CURRENT_SOURCE_DIR}/plugin/resources/execMotion/plugInfo.json" + "${CMAKE_CURRENT_SOURCE_DIR}/${_exec_motion_resources}/plugInfo.json.in" + "${CMAKE_CURRENT_BINARY_DIR}/plugInfo.json" @ONLY) +# The build's own stage, which the tests load (cmake/UsdMotionStage.cmake). +usdmotion_stage_bundle(${PLUGIN_NAME} + PLUG_INFO "${CMAKE_CURRENT_BINARY_DIR}/plugInfo.json" + RESOURCES "${_exec_motion_resources}") + +# What ships: the library in lib/ beside the registration at the path +# openstrata.plugin.yaml's `usd.plug_info` names, so plugInfo.json's +# LibraryPath (../../../lib/...) holds in any prefix. `ost plugin build` +# installs this into the bundle's target-local stage, and `ost plugin test` and +# `ost plugin package` read it from there. +install(TARGETS ${PLUGIN_NAME} + RUNTIME DESTINATION lib + LIBRARY DESTINATION lib) +install(FILES "${CMAKE_CURRENT_BINARY_DIR}/plugInfo.json" + DESTINATION "${_exec_motion_resources}") + if(USDMOTION_BUILD_TESTS AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/tests/CMakeLists.txt") enable_testing() usdmotion_find_test_python() diff --git a/plugins/execMotion/tests/CMakeLists.txt b/plugins/execMotion/tests/CMakeLists.txt index 8d060a0..ce702d4 100644 --- a/plugins/execMotion/tests/CMakeLists.txt +++ b/plugins/execMotion/tests/CMakeLists.txt @@ -314,13 +314,14 @@ endif() # --------------------------------------------------------------------------- # Environment # --------------------------------------------------------------------------- -# The bundle's own resource directory is what PXR_PLUGINPATH_NAME points at, and -# its plugInfo.json resolves LibraryPath relative to itself -- so the staged -# tree the bundle already produces is what the test loads, not a copy. +# The bundle's staged resource directory is what PXR_PLUGINPATH_NAME points at, +# and its plugInfo.json resolves LibraryPath relative to itself -- so the stage +# the build already produces in the binary tree is what the test loads, not a +# copy (cmake/UsdMotionStage.cmake). usdmotion_openusd_root(_usd_root) -set(_bundle_dir "${CMAKE_CURRENT_SOURCE_DIR}/..") -set(_plug_resources "${_bundle_dir}/plugin/resources/execMotion") +set(_bundle_lib "$") +set(_plug_resources "${_bundle_lib}/../plugin/resources/execMotion") # motionCore is static, so nothing of it is loaded at run time; OpenUSD is not, # and on Windows a plain-CMake ctest run finds none of it without this. Same @@ -329,7 +330,7 @@ set(_plug_resources "${_bundle_dir}/plugin/resources/execMotion") set(_exec_motion_env "PATH=path_list_prepend:${_usd_root}/bin" "PATH=path_list_prepend:${_usd_root}/lib" - "PATH=path_list_prepend:${_bundle_dir}/lib" + "PATH=path_list_prepend:${_bundle_lib}" "PXR_PLUGINPATH_NAME=path_list_prepend:${_plug_resources}") set_tests_properties(execMotion_mechanism execMotion_sample execMotion_filter diff --git a/tools/motionBvhInspect/CMakeLists.txt b/tools/motionBvhInspect/CMakeLists.txt index 6da7b06..64826d2 100644 --- a/tools/motionBvhInspect/CMakeLists.txt +++ b/tools/motionBvhInspect/CMakeLists.txt @@ -35,14 +35,9 @@ target_link_libraries(motion_bvh_inspect PRIVATE motionBvh::motionBvh) usdmotion_target_utf8(motion_bvh_inspect PRIVATE) -# Staged into the member root's bin/: `ost plugin package` reads the -# directories openstrata.tool.yaml declares rather than a build tree. -set_target_properties(motion_bvh_inspect PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") -foreach(_cfg DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) - set_target_properties(motion_bvh_inspect PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/bin") -endforeach() +# Staged into the member's binary-tree bin/, the directory openstrata.tool.yaml +# declares, where `ost build` finds it (cmake/UsdMotionStage.cmake). +usdmotion_stage_tool(motion_bvh_inspect) usdmotion_install_tool(motion_bvh_inspect) diff --git a/tools/motionConvert/CMakeLists.txt b/tools/motionConvert/CMakeLists.txt index 31287ff..6bf5e4e 100644 --- a/tools/motionConvert/CMakeLists.txt +++ b/tools/motionConvert/CMakeLists.txt @@ -57,17 +57,31 @@ if(MSVC) target_compile_definitions(motion_convert PRIVATE NOMINMAX _USE_MATH_DEFINES) endif() -# Staged into the member root's bin/: `ost plugin package` reads the -# directories openstrata.tool.yaml declares rather than a build tree. -set_target_properties(motion_convert PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") -foreach(_cfg DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) - set_target_properties(motion_convert PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/bin") -endforeach() +# Staged into the member's binary-tree bin/, the directory openstrata.tool.yaml +# declares, where `ost build` finds it (cmake/UsdMotionStage.cmake). +usdmotion_stage_tool(motion_convert) usdmotion_install_tool(motion_convert) +# The profiles are staged beside the tool as an install puts them, in +# /share/, so a converter run from the build tree finds them by the rule +# that finds them in an installed prefix: /../share/... (ProfileLocator.cpp). +# Copied only when they differ, and re-globbed when one is added. +file(GLOB _motion_convert_profiles CONFIGURE_DEPENDS + "${CMAKE_CURRENT_SOURCE_DIR}/../../profiles/motion/*.yaml") +if(_motion_convert_profiles) + usdmotion_member_stage(_motion_convert_stage) + set(_motion_convert_profile_stage + "${_motion_convert_stage}/share/usd-motion-plugins/profiles/motion") + add_custom_target(motion_convert_profiles ALL + COMMAND "${CMAKE_COMMAND}" -E make_directory "${_motion_convert_profile_stage}" + COMMAND "${CMAKE_COMMAND}" -E copy_if_different + ${_motion_convert_profiles} "${_motion_convert_profile_stage}" + COMMENT "Staging the motion profiles beside motion_convert" + VERBATIM) + add_dependencies(motion_convert motion_convert_profiles) +endif() + # The profiles ship beside the tool, at the location ProfileLocator.cpp derives # from the executable's own path: a converter with no profile available refuses # every file it is given. diff --git a/tools/motionConvert/README.md b/tools/motionConvert/README.md index 005ccbc..d9efacd 100644 --- a/tools/motionConvert/README.md +++ b/tools/motionConvert/README.md @@ -111,11 +111,13 @@ first hit wins: 1. every `--profile-dir`, in the order given 2. `USDMOTION_PROFILE_PATH`, a list in the platform's PATH separator 3. `/../share/usd-motion-plugins/profiles/motion` — a `cmake --install` - prefix, where the tools land in `/bin/` + prefix, where the tools land in `/bin/`, and this repository's build + tree, which stages the profiles beside the tool the same way 4. `/../../../share/usd-motion-plugins/profiles/motion` — an installed product, where `ost plugin product install` puts a tool member in `/tools//bin/` and the product's data in `/share/` -5. `/../../../profiles/motion` — this repository +5. `/../../../profiles/motion` — a tool placed in this repository's own + `tools//bin/`, which no build does any more The last two are offered only when the executable really is in a `tools//bin/`; from anywhere else they would climb past a prefix diff --git a/tools/motionConvert/src/ProfileLocator.cpp b/tools/motionConvert/src/ProfileLocator.cpp index a470c17..5d71ccc 100644 --- a/tools/motionConvert/src/ProfileLocator.cpp +++ b/tools/motionConvert/src/ProfileLocator.cpp @@ -145,11 +145,12 @@ ProfileSearchPath(const std::vector& extraDirs) if (!executableDir.empty()) { // /bin/ -> /share/... : a `cmake --install` - // prefix, and a member archive unpacked on its own. + // prefix, a member archive unpacked on its own, and this repository's + // build tree, which stages the profiles beside the tool. directories.push_back(executableDir.parent_path() / "share" / "usd-motion-plugins" / "profiles" / "motion"); // /tools//bin/ -> /share/... : an - // installed product, and this repository's own build tree. The two + // installed product. The two // installed layouts agree about where the data is relative to the // prefix and disagree about how deep the tool sits inside it, so each // needs its own rule. diff --git a/tools/motionRecord/CMakeLists.txt b/tools/motionRecord/CMakeLists.txt index d128adf..d65b8eb 100644 --- a/tools/motionRecord/CMakeLists.txt +++ b/tools/motionRecord/CMakeLists.txt @@ -53,14 +53,9 @@ if(MSVC) target_compile_definitions(motion_record PRIVATE NOMINMAX _USE_MATH_DEFINES) endif() -# Staged into the member root's bin/: `ost plugin package` reads the -# directories openstrata.tool.yaml declares rather than a build tree. -set_target_properties(motion_record PROPERTIES - RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin") -foreach(_cfg DEBUG RELEASE RELWITHDEBINFO MINSIZEREL) - set_target_properties(motion_record PROPERTIES - RUNTIME_OUTPUT_DIRECTORY_${_cfg} "${CMAKE_CURRENT_SOURCE_DIR}/bin") -endforeach() +# Staged into the member's binary-tree bin/, the directory openstrata.tool.yaml +# declares, where `ost build` finds it (cmake/UsdMotionStage.cmake). +usdmotion_stage_tool(motion_record) usdmotion_install_tool(motion_record)