Marenz
Marenz

Reputation: 2882

Is it better to specify source files with GLOB or each file individually in CMake?

CMake offers several ways to specify the source files for a target. One is to use globbing (documentation), for example:

FILE(GLOB MY_SRCS dir/*)

Another method is to specify each file individually.

Which way is preferred? Globbing seems easy, but I heard it has some downsides.

Upvotes: 213

Views: 139984

Answers (8)

excitoon
excitoon

Reputation: 450

One more pro to globs, they automatically make sure you have all files mentioned in coverage.

Upvotes: -1

AndrasLiptak
AndrasLiptak

Reputation: 59

This might be a useful cog:

It's in powershell, but any other scripting language will do... It's just one possible addition to the stuff mentioned above.

Get a recursive list of the code files: $res=$( Get-ChildItem -Path $root -Recurse -Attributes !Directory -Name -Include *.h,*.c,CMakeLists.txt )

Concatenate each line / element from the returning Object[] into a single string and compute a hash for it. Store the hash in a file on the root (any) what you will query. Typically it's the components,main,etc folder(s). Each compile script will check a freshly computed hash against the stored one and in case of mismatch (there was a change in the file layout) a cmake reconfigure is required and naturally store the fresh hash (still melts a bit) then goto 10.

Hash from a string:

function stringhash {
    PARAM (
        [Parameter(Mandatory, Position = 0)]
        [string]
        $source)

    $stringAsStream = [System.IO.MemoryStream]::new()
    $writer = [System.IO.StreamWriter]::new($stringAsStream)
    $writer.write("$($source)")
    $writer.Flush()
    $stringAsStream.Position = 0
    $res = (Get-FileHash -InputStream $stringAsStream | Select-Object Hash)
    $writer.Close()
    $stringAsStream.Close()
    return $res.Hash.ToUpper()
}

Upvotes: -1

ideasman42
ideasman42

Reputation: 48258

The best way to specify sourcefiles in CMake is by listing them explicitly.

The creators of CMake themselves advise not to use globbing.

See: Filesystem

(We do not recommend using GLOB to collect a list of source files from your source tree. If no CMakeLists.txt file changes when a source is added or removed then the generated build system cannot know when to ask CMake to regenerate.)

Of course, you might want to know what the downsides are - read on!


When Globbing Fails:

The big disadvantage to globbing is that creating/deleting files won't automatically update the build system.

If you are the person adding the files, this may seem an acceptable trade-off. However, this causes problems for other people building your code; they update the project from version control, run build, and then contact you, complaining that
"the build's broken".

To make matters worse, the failure typically gives some linking error which doesn't give any hints to the cause of the problem and time is lost troubleshooting it.

In a project I worked on, we started off globbing, but we got so many complaints when new files were added that it was enough reason to explicitly list files instead of globbing.

This also breaks common Git workflows
(git bisect and switching between feature branches).

So I couldn't recommend this. The problems it causes far outweigh the convenience. When someone can't build your software because of this, they may lose a lot of time to track down the issue or just give up.

And another note. Just remembering to touch CMakeLists.txt isn't always enough. With automated builds that use globbing, I had to run cmake before every build since files might have been added/removed since last building *.

Exceptions to the rule:

There are times where globbing is preferable:

  • For setting up a CMakeLists.txt files for existing projects that don't use CMake.
    it’s a fast way to get all the source referenced (once the build system's running - replace globbing with explicit file-lists).
  • When CMake isn't used as the primary build system, if for example you're using a project who aren't using CMake, and you would like to maintain your own build-system for it.
  • For any situation where the file list changes so often that it becomes impractical to maintain. In this case, it could be useful, but then you have to accept running cmake to generate build-files every time to get a reliable/correct build (which goes against the intention of CMake - the ability to split configuration from building).

*Yes, I could have written a code to compare the tree of files on disk before and after an update, but this is not such a nice workaround and something better left up to the build system.

Upvotes: 152

palfi
palfi

Reputation: 298

Specify each file individually!

I use a conventional CMakeLists.txt and a Python script to update it. I run the python script manually after adding files.

See my answer at How to collect source files with CMake without globbing?.

Upvotes: 2

Glen Knowles
Glen Knowles

Reputation: 608

You can safely glob (and probably should) at the cost of an additional file to hold the dependencies.

Add functions like these somewhere:

# Compare the new contents with the existing file, if it exists and is the
# same we don't want to trigger a make by changing its timestamp.
function(update_file path content)
    set(old_content "")
    if(EXISTS "${path}")
        file(READ "${path}" old_content)
    endif()
    if(NOT old_content STREQUAL content)
        file(WRITE "${path}" "${content}")
    endif()
endfunction(update_file)

# Creates a file called CMakeDeps.cmake next to your CMakeLists.txt with
# the list of dependencies in it - this file should be treated as part of
# CMakeLists.txt (source controlled, etc.).
function(update_deps_file deps)
    set(deps_file "CMakeDeps.cmake")
    # Normalize the list so it's the same on every machine
    list(REMOVE_DUPLICATES deps)
    foreach(dep IN LISTS deps)
        file(RELATIVE_PATH rel_dep ${CMAKE_CURRENT_SOURCE_DIR} ${dep})
        list(APPEND rel_deps ${rel_dep})
    endforeach(dep)
    list(SORT rel_deps)
    # Update the deps file
    set(content "# generated by make process\nset(sources ${rel_deps})\n")
    update_file(${deps_file} "${content}")
    # Include the file so it's tracked as a generation dependency we don't
    # need the content.
    include(${deps_file})
endfunction(update_deps_file)

And then go globbing:

file(GLOB_RECURSE sources LIST_DIRECTORIES false *.h *.cpp)
update_deps_file("${sources}")
add_executable(test ${sources})

You're still carting around the explicit dependencies (and triggering all the automated builds!) like before, only it's in two files instead of one.

The only change in procedure is after you've created a new file. If you don't glob, the workflow is to modify CMakeLists.txt from inside Visual Studio and rebuild. If you do glob, you run cmake explicitly - or just touch CMakeLists.txt.

Upvotes: 11

Justin
Justin

Reputation: 25387

In CMake 3.12, the file(GLOB ...) and file(GLOB_RECURSE ...) commands gained a CONFIGURE_DEPENDS option which reruns cmake if the glob's value changes. As that was the primary disadvantage of globbing for source files, it is now okay to do so:

# Whenever this glob's value changes, cmake will rerun and update the build with the
# new/removed files.
file(GLOB_RECURSE sources CONFIGURE_DEPENDS "*.cpp")

add_executable(my_target ${sources})

However, some people still recommend avoiding globbing for sources. Indeed, the documentation states:

We do not recommend using GLOB to collect a list of source files from your source tree. ... The CONFIGURE_DEPENDS flag may not work reliably on all generators, or if a new generator is added in the future that cannot support it, projects using it will be stuck. Even if CONFIGURE_DEPENDS works reliably, there is still a cost to perform the check on every rebuild.

Personally, I consider the benefits of not having to manually manage the source file list to outweigh the possible drawbacks. If you do have to switch back to manually listed files, this can be easily achieved by just printing the globbed source list and pasting it back in.

Upvotes: 84

Anton Pantuykhin
Anton Pantuykhin

Reputation: 27

I'm not a fan of globbing and never used it for my libraries. But recently I've looked a presentation by Robert Schumacher (vcpkg developer) where he recommends to treat all your library sources as separate components (for example, private sources (.cpp), public headers (.h), tests, examples - are all separate components) and use separate folders for all of them (similarly to how we use C++ namespaces for classes). In that case I think globbing makes sense, because it allows you to clearly express this components approach and stimulate other developers to follow it. For example, your library directory structure can be the following:

  • /include - for public headers
  • /src - for private headers and sources
  • /tests - for tests

You obviously want other developers to follow your convention (i.e., place public headers under /include and tests under /tests). file(glob) gives a hint for developers that all files from a directory have the same conceptual meaning and any files placed to this directory matching the regexp will also be treated in the same way (for example, installed during 'make install' if we speak about public headers).

Upvotes: 1

richq
richq

Reputation: 56488

Full disclosure: I originally preferred the globbing approach for its simplicity, but over the years I have come to recognise that explicitly listing the files is less error-prone for large, multi-developer projects.

Original answer:


The advantages to globbing are:

  • It's easy to add new files as they are only listed in one place: on disk. Not globbing creates duplication.

  • Your CMakeLists.txt file will be shorter. This is a big plus if you have lots of files. Not globbing causes you to lose the CMake logic amongst huge lists of files.

The advantages of using hardcoded file lists are:

  • CMake will track the dependencies of a new file on disk correctly - if we use glob then files not globbed first time round when you ran CMake will not get picked up

  • You ensure that only files you want are added. Globbing may pick up stray files that you do not want.

In order to work around the first issue, you can simply "touch" the CMakeLists.txt that does the glob, either by using the touch command or by writing the file with no changes. This will force CMake to re-run and pick up the new file.

To fix the second problem you can organize your code carefully into directories, which is what you probably do anyway. In the worst case, you can use the list(REMOVE_ITEM) command to clean up the globbed list of files:

file(GLOB to_remove file_to_remove.cpp)
list(REMOVE_ITEM list ${to_remove})

The only real situation where this can bite you is if you are using something like git-bisect to try older versions of your code in the same build directory. In that case, you may have to clean and compile more than necessary to ensure you get the right files in the list. This is such a corner case, and one where you already are on your toes, that it isn't really an issue.

Upvotes: 241

Related Questions