Ability to clean individual targets?

Currently I don’t believe you can clean individual targets correct?

I know in Visual Studio you can clean individual targets. And I believe other generators support a similar ability to clean individual targets but I might be wrong. Currently I believe the only way to clean is to just clean all the targets.

# Clean all targets
cmake --build C:/foo/build/ --target clean
1 Like

Depending on your project structure, you may be able to just delete some directories from your build directory and re-run CMake to restore it back to a buildable state. It’s a bit of a poor man’s clean, but it can be good enough in some cases. I do that sometimes in complicated projects where ccache isn’t all that effective but the directory structure is very modular. If ccache is effective, it often doesn’t take long to do a full rebuild of the same sources, so cleaning the whole project is usually no big deal in that case.

1 Like

I basically agree @craig.scott that it’s not too much of an issue.

However, this lack of functionality causes an awkward workflow for IDEs that integrate with CMake.

For example I’m using the CMakeSettings.json and this is what I’m presented with:
image

If you used vanilla visual studio you could clean individual targets, as shown here:
image

I guess because the CMakeSettings.json route tries to use cmake for everything it can’t just clean individual targets because CMake doesn’t provide that functionality.

I’m not sure that the problem is specific to CMake. For any build tool, if there was a “clean target” feature, what would you expect to happen for a target that linked against other libraries? Should it also clean those libraries too? The answer to that will probably be different for different people, making this feature either misleading or dangerous depending on which choice the IDE made.

1 Like

For any build tool, if there was a “clean target” feature, what would you expect to happen for a target that linked against other libraries?

Good point. I guess I didn’t think about this too deeply. Thanks for pointing this out :+1:

I’ll look into what Visual Studio does when I have a chance.

I just check what my generated visual studio solution does when I clean an individual target:

Clean started...
1>------ Clean started: Project: foobar, Configuration: Release x64 ------
2>------ Clean started: Project: foobar2, Configuration: Release x64 ------
3>------ Clean started: Project: foobar3, Configuration: Release x64 ------
4>------ Clean started: Project: foobar4, Configuration: Release x64 ------
...
23>------ Clean started: Project: foobar5, Configuration: Release x64 ------
24>------ Clean started: Project: ZERO_CHECK, Configuration: Release x64 ------
========== Clean: 24 succeeded, 0 failed, 0 skipped ==========

It cleans all the targets in the dependency chain that could affect the target I built.
I think this behavior is pretty reasonable.

In this example I cleaned foobar and foobar has dependencies on foobar2,3,4 etc. So Visual Studio cleaned all my dependencies.

I was a bit confused at first since you are mentioning VS and generators. I am not familiar with VS. To clarify my understanding:

  • Vanilla VS doesn’t use CMake, but allows to clean individual targets.
  • CMake does not allow to clean individual targets using VS generator

I don’t think it is. For an development environment imagine a different scenario where i have 2 top level targets, foobar and foobar2, with common dependencies. I don’t want to clean the dependers (but maybe the dependees - those are rebuild anyways so a clean wouldn’t hurt). Otherwise what difference does it make to a global clean?

Besides the bigger issue is probably when the tool doesn’t work as expected. On the other hand if i set my dependencies wrong i am running into problems, sooner or later anyways (i’d blame it on the tools though, obviously).

When i clean individual targets in CMake it only affects the targets. This is not an extensive test so maybe i am overlooking something.

# Clean individual build directory (i.e. ~ target)
cmake --build C:/foo/build/src/foobar --target clean

If i wanted to propagate the clean to dependers or dependees I suppose I could support this myself using custom targets? I am not sure about the details to implement this.

This doesn’t work for all generators. Ninja, for instance, only has a single ninja -t clean exposed via CMake. You can do ninja -t clean tgt1 tgt2, but this also doesn’t have a way of specifying the same thing in other generators. Makefiles generators, I believe, have a per-directory clean target. VS has its behavior as shown in this thread already. I don’t know what Xcode does.

Anyways, CMake would basically have to implement its own cleaning mechanisms before consistent behavior for all generators could be exposed (other than “clean everything”).

1 Like

A consistent behaviour for all generators could simply be an error message for

# error - not the build directory
cmake --build C:/foo/build/src/foobar --target clean

because I can still do make -C C:/foo/build/src/foobar clean to achieve the same thing as above.

I think that’s already an error case, no? IIRC, --build would expect a CMakeCache.txt which only exists at the top-level.

no,

cd C:/foo/build
cmake ..
# Clean individual build directory (i.e. ~ target)
cmake --build C:/foo/build/src/foobar --target clean

works just fine from anywhere inside the build directory, using Makefile generator. CMakeCache.txt only exists at top-level build directory. No error is shown. For other generators this results in error.

Ah, thanks. News to me (though I generally use the Ninja generator).

Still would be nice to have ability to (clean-)rebuild (main) target since “ninja: no work to do” == no warnings to show. At least allow to add custom ‘actions’ like in Makefile.

These are allowed in the Ninja generator. How do you get something like this in a way that doesn’t work for Ninja?

1 Like

Yeah, I see. That’s sad.

The ninja build tool does not have any model of what outputs belong to what (cmake) targets. It is a monolithic build graph. There is no model for “clean target X”, so CMake has no direct way to express such a clean operation in build.ninja.

Did it through folder deletion as noted above! New targets for clean and rebuild.

# remove_recurse.cmake
message("Files for removal:")
string (REPLACE " " "\n  " FILES_LIST_ "${FILES_}") # <--- ";" is missing without "\;"  
message("  " ${FILES_LIST_})

file(REMOVE_RECURSE "${FILES_}")
# CMakeLists.txt
add_custom_target(${PROJECT_NAME}-clean
  COMMENT "Cleaning `${PROJECT_NAME}` target:"
  WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
  COMMAND ${CMAKE_COMMAND} -DFILES_="${PROJECT_BINARY_DIR}/CMakeFiles/${PROJECT_NAME}.dir/src/"
                           -P "misc/script/remove_recurse.cmake"
)

add_custom_target(${PROJECT_NAME}-rebuild
  COMMENT "Rebuilding `${PROJECT_NAME}` target:"
  DEPENDS ${PROJECT_NAME}-clean
  COMMAND ${CMAKE_COMMAND} --build "." --target ${PROJECT_NAME}
)
Output
[main] Building folder: bplan 
[build] Starting build
[proc] Executing command: "C:\Program Files\CMake\bin\cmake.EXE" --build C:/dan/dev/bplan/out/build/msvc-dbg --parallel 4 --target bplan-rebuild
[build] [0/2] Re-checking globbed directories...
[build] [1/2] Cleaning `bplan` target:
[build] Files for removal:
[build]   C:/dan/dev/bplan/out/build/msvc-dbg/CMakeFiles/bplan.dir/src/
[build] [2/2] Rebuilding `bplan` target:
[build] [0/2] Re-checking globbed directories...
[build] [1/11] Building CXX object CMakeFiles\bplan.dir\src\int\imgui\tools\align_text.cpp.obj
[build] [2/11] Building CXX object CMakeFiles\bplan.dir\src\app\MainViewportMenuBar.cpp.obj
[build] [3/11] Building CXX object CMakeFiles\bplan.dir\src\app.cpp.obj
[build] [4/11] Building CXX object CMakeFiles\bplan.dir\src\app\DataTreeEditor.cpp.obj
[build] [5/11] Building CXX object CMakeFiles\bplan.dir\src\app\MainWindow.cpp.obj
[build] [6/11] Building CXX object CMakeFiles\bplan.dir\src\app\data\data.cpp.obj
[build] [7/11] Building CXX object CMakeFiles\bplan.dir\src\test\<file>.cpp.obj
[build] [8/11] Building CXX object CMakeFiles\bplan.dir\src\test\<file>.cpp.obj
[build] [9/11] Building CXX object CMakeFiles\bplan.dir\src\main.cpp.obj
[build] [10/11] Building CXX object CMakeFiles\bplan.dir\src\test\<file>.cpp.obj
[build] [11/11] Linking CXX executable C:\dan\dev\bplan\bplan.exe
[driver] Build completed: 00:00:06.965
[build] Build finished with exit code 0

Pity that add_custom_target accepts only cmd commands and not cmake functions. Is it an interesting idea to add FUNCTION feature as for COMMAND alternative to CMake?
Also little flaw is that output flushes only after full completion, so no active build tracking :frowning:.

Comments and improvements are welcomed!

That is a ninja design decision; nothing CMake does about that. You can assign commands to the console job pool, but then they cannot run concurrently with any other task either.

No, the context would be all wrong. Put it in a script and use cmake -P script.cmake with -D arguments to add other information as needed.

1 Like

My first guess was like this:

function(add_clean_target target)
    set(clean_target clean.${target})
    add_custom_command(
        OUTPUT ${target}.cleaned
        COMMAND cmake -E rm -f $<TARGET_FILE:${target}>
        COMMAND cmake -E rm -f $<TARGET_OBJECTS:${target}>
        DEPENDS_EXPLICIT_ONLY
        COMMAND_EXPAND_LISTS
    )
    add_custom_target(${clean_target} DEPENDS ${target}.cleaned)
    set_property(TARGET ${clean_target} PROPERTY FOLDER "Utilities")
endfunction()

add_clean_target(my_lib)

But turns out that the name add_clean_target for this function would better sound like clean_or_built_target because in most of my conditions it cleans the target after build, and the next time this target is invoked, it builds the target back. Anyway it does the work when needed, and all dependent targets are rebuilt after when necessary. But I landed with other solution (MSW specific a.t.m.):

function(add_clean_target target)
    set(clean_target clean.${target})
    set(cleanup_script ${target}.clean.bat)

    set(target_output "$<SHELL_PATH:$<TARGET_FILE:${target}>>")
    set(target_objects "$<JOIN:$<SHELL_PATH:$<TARGET_OBJECTS:${target}>>,\" \">")
    set(delete "del /Q /S")
    set(command "${delete} \"${target_output}\" \"${target_objects}\"")

    add_custom_command(
        TARGET ${target}
        POST_BUILD
        COMMENT "Writing `${target}` cleanup script"
        COMMAND cmd /c > ${cleanup_script} echo ${command}
        DEPENDS_EXPLICIT_ONLY
        COMMAND_EXPAND_LISTS
    )

    set(error "Cleanup script ${cleanup_script} not found. Try to [re]build `${target}` target")
    set(command "echo off & if exist ${cleanup_script} (call ${cleanup_script}) else (echo ${error})")
    add_custom_target(${clean_target}
        COMMENT "Cleaning target `${target}`"
        COMMAND cmd /c ${command}
    )

    set_property(TARGET ${clean_target} PROPERTY FOLDER "Utilities")
endfunction()

Comments and improvements are welcomed also! Would be nice to get it right and crossplatform

1 Like

there is an open issue from here: https://gitlab.kitware.com/cmake/cmake/-/issues/23458
I agree with that idea, hope cmake can support that way in the near future, just hope~

1 Like