From 8f15967380989059615e31f6d9fcc4a04873dbd3 Mon Sep 17 00:00:00 2001 From: snkmcb Date: Thu, 24 Sep 2026 12:06:40 +0900 Subject: [PATCH] Stage bundles and tools in the binary tree, never the source tree Phase 6 of the CMake cleanup, which the ost 0.23.6 pin made possible. execMotion's library and generated plugInfo.json, and the three CLIs, were built into plugins/execMotion/lib/, its plugin/resources/ and tools/*/bin/, because ost 0.23.4 packaged them from the source tree. Now: * cmake/UsdMotionStage.cmake stages each member in its binary directory, laid out as the member is: a tool's bin/, a bundle's lib/ beside its plugin/resources//plugInfo.json. A multi-config generator gets one stage per configuration, so Debug and Release no longer overwrite each other. The build's tests run against that stage. * execMotion has install rules -- lib/ beside the registration at the path its manifest's usd.plug_info names -- which `ost plugin build` installs into the bundle's target-local stage, and `ost plugin test` and `ost plugin package` read. The package loses plugInfo.json.in, which only the source-tree layout ever swept in. * `ost build` takes each tool from its member's bin/ in the root build tree, which is the stage under the Ninja generator ost configures. * motion_convert's build stages the profiles in /share/, so a converter run from the build tree finds them with no flags, by the rule that finds them in an installed prefix. The .gitignore lines that hid those outputs are gone, so an output that lands in the source tree again shows in `git status`. buildInfo.json goes with them: nothing here or in ost writes one. Verified on Windows with ost 0.23.6, after deleting the old source-tree outputs: ost build + ost test (53/53, with the ost tool stages filled from the build tree); ost plugin build/test/package execMotion from its bundle stage, and its package-origin test; ost plugin package --workspace (one bundle, three tools); plain CMake with the Visual Studio generator (53/53, per-config stages); the three tools and execMotion built and tested standalone against an installed prefix; motion_convert converting with no profile flags from both build trees. `git status` stayed clean throughout. Co-Authored-By: Claude Opus 5.5 --- .gitignore | 14 ++--- CHANGELOG.md | 15 +++++ cmake/UsdMotionProject.cmake | 1 + cmake/UsdMotionStage.cmake | 72 ++++++++++++++++++++++ docs/architecture/WORKSPACE.md | 15 ++++- openstrata.ci.yaml | 6 +- plugins/execMotion/CMakeLists.txt | 36 +++++++---- plugins/execMotion/tests/CMakeLists.txt | 13 ++-- tools/motionBvhInspect/CMakeLists.txt | 11 +--- tools/motionConvert/CMakeLists.txt | 30 ++++++--- tools/motionConvert/README.md | 6 +- tools/motionConvert/src/ProfileLocator.cpp | 5 +- tools/motionRecord/CMakeLists.txt | 11 +--- 13 files changed, 173 insertions(+), 62 deletions(-) create mode 100644 cmake/UsdMotionStage.cmake 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)