Skip to content

build: Fix stale FFmpeg libs not restaged after decoder config change - #1388

Open
bernie-laberge wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
bernie-laberge:fix/ffmpeg-stage-rebuild
Open

build: Fix stale FFmpeg libs not restaged after decoder config change#1388
bernie-laberge wants to merge 1 commit into
AcademySoftwareFoundation:mainfrom
bernie-laberge:fix/ffmpeg-stage-rebuild

Conversation

@bernie-laberge

@bernie-laberge bernie-laberge commented Aug 21, 2026

Copy link
Copy Markdown
Contributor

Linked issues

Related to #689 (may also need a docs fix for the prores_ks vs prores decoder name).

Summarize your change.

When a developer reconfigures with a changed RV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE
(e.g. rvcfg -DRV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE="aac") and rebuilds, the freshly
rebuilt FFmpeg libraries were never copied into the stage directory, so the running app
kept using the old binaries with the decoder still disabled.

Describe the reason for the change.

1. Staging never re-ran (the reported bug).
RV_STAGE_DEPENDENCY_LIBS (in cmake/macros/rv_stage_dependency_libs.cmake) staged
FFmpeg's libraries via a custom command whose DEPENDS only referenced the
RV_DEPS_FFMPEG ExternalProject target name. CMake only creates an order-only
dependency for a target like this (not an executable/library), so once the staged
output filenames already existed from a prior build, Ninja considered the copy command
up to date and skipped it — even though FFmpeg itself was correctly reconfigured and
rebuilt with the new decoder enabled.

The fix adds the resolved source library paths (the same paths already registered as
BUILD_BYPRODUCTS on the FFmpeg ExternalProject) to the staging custom command's
DEPENDS, giving it a real file-level dependency. This macro is shared by 14 other
dependencies (boost, openssl, zlib, etc.), so they all benefit from the same
correctness fix, not just FFmpeg.

2. Staged outputs are now touched.
copy_if_different skips the copy when a rebuilt dependency produced byte-identical
output, which would leave the staged files older than the sources they now depend on
and keep the staging command dirty on every build. macOS happens to avoid this because
install_name_tool + codesign always rewrite the staged file, but Linux and Windows
do not (rv_create_soname_symlink.cmake only creates a symlink). A final
cmake -E touch_nocreate on the staged outputs makes the graph converge on all
platforms.

3. Removed the dead _force_rebuild / BUILD_ALWAYS mechanism in ffmpeg.cmake.
SET(${_force_rebuild} ...) dereferenced an undefined variable, so BUILD_ALWAYS was
silently never set. Actually enabling it turns out to be actively harmful: BUILD_ALWAYS
makes CMake mark the ExternalProject build/install stamps SYMBOLIC (dropping their
cmake -E touch), so FFmpeg's make / make install re-run on every build and dirty
every downstream link edge — a perpetual rebuild loop. It is also unnecessary:
ExternalProject already writes the resolved CONFIGURE_COMMAND to <target>-cfgcmd.txt
and makes it a file dependency of the configure stamp, and that is what actually
triggers the reconfigure+rebuild when the decoder options change. So the whole mechanism
(including the now-unused RV_FFMPEG_CONFIG_OPTIONS_CACHE) is removed, with a comment
to stop it being reintroduced.

Describe what you have tested and on which operating system.

Tested on macOS (arm64, Ninja, FFmpeg 8):

  • pre-commit run cmake-format on both changed files.
  • Reconfigured and confirmed the FFmpeg build/install ninja edges once again end with
    cmake -E touch <stamp> (i.e. BUILD_ALWAYS is gone), matching the DAV1D/OpenSSL edges.
  • No-op check: ran consecutive builds until settled — FFmpeg now contributes
    0 edges to an up-to-date build (previously make install + staging + ~77 relinks
    every single time). The residual edges in an idle build come from the pre-existing
    RV_DEPS_IMGUI git-update cascade, which is unrelated to this PR.
  • Fix still works: touched RV_DEPS_FFMPEG/install/lib/libavcodec.62.dylib and
    confirmed the staging command re-ran and the staged
    stage/app/RV.app/Contents/lib/libavcodec.62.dylib was actually refreshed, then that
    the build converged again on the next run.

Not tested: Windows and Linux. In particular the Windows import-lib branch and the
touch_nocreate convergence path (which only matters on Linux/Windows) were not
exercised locally — worth confirming in CI.

Add a list of changes, and note any that might need special attention during the review.

  • cmake/macros/rv_stage_dependency_libs.cmake
    • add resolved TARGET_LIBS source paths to the staging ADD_CUSTOM_COMMAND's DEPENDS
    • touch_nocreate the staged outputs so the graph converges when copy_if_different skips
  • cmake/dependencies/ffmpeg.cmake
    • remove the _force_rebuild / BUILD_ALWAYS / RV_FFMPEG_CONFIG_OPTIONS_CACHE mechanism

Please pay attention to the Linux/Windows behaviour of the two staging changes, since
both were only verified on macOS.

If possible, provide screenshots.

N/A (build-system change).

@bernie-laberge bernie-laberge changed the title [ Fix stale FFmpeg libs not restaged after decoder config change ] build: Fix stale FFmpeg libs not restaged after decoder config change Aug 21, 2026
@bernie-laberge
bernie-laberge force-pushed the fix/ffmpeg-stage-rebuild branch 2 times, most recently from eb47025 to ca3329b Compare August 22, 2026 10:10
RV_STAGE_DEPENDENCY_LIBS only depended on the FFmpeg ExternalProject's
target name, which CMake treats as an order-only dependency for
non-executable/library targets. So after e.g.
`rvcfg -DRV_FFMPEG_NON_FREE_DECODERS_TO_ENABLE="aac"` and a rebuild,
FFmpeg itself was correctly reconfigured and rebuilt with AAC enabled,
but the freshly rebuilt libavcodec/libavformat were never copied into
the stage directory, since the staged output filenames didn't change
and Ninja considered the copy already up to date.

Add the resolved source library paths (the same paths registered as
BUILD_BYPRODUCTS on the ExternalProject) to the staging custom
command's DEPENDS, giving it a real file-level dependency. This is a
shared macro used by 14 dependencies besides FFmpeg, so all of them
now correctly restage when rebuilt from source.

Also touch the staged outputs at the end of the staging command:
copy_if_different skips the copy when a rebuilt dependency produced
byte-identical output, which would leave the staged files older than
the sources they now depend on and keep the command dirty on every
build. macOS happens to avoid this because install_name_tool and
codesign always rewrite the staged file, but Linux and Windows do not.

Finally, remove the dead _force_rebuild / BUILD_ALWAYS mechanism from
ffmpeg.cmake. `SET(${_force_rebuild} ...)` dereferenced an undefined
variable, so BUILD_ALWAYS was silently never set. Actually enabling it
is harmful: BUILD_ALWAYS marks the build and install stamps SYMBOLIC,
so FFmpeg's make/make install re-run on every single build and dirty
every downstream link edge. It is also unnecessary, since
ExternalProject already writes the resolved CONFIGURE_COMMAND to
<target>-cfgcmd.txt and makes it a file dependency of the configure
stamp, which is what actually triggers the rebuild when the decoder
options change.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Signed-off-by: Bernard Laberge <bernard.laberge@autodesk.com>
@bernie-laberge
bernie-laberge force-pushed the fix/ffmpeg-stage-rebuild branch from ca3329b to d35565f Compare August 22, 2026 10:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants