CMake project review and suggestions (closed)

Good question, I would like to expand on this. here is an example:

mkdir "build;dir" ; cd "build;dir"
cmake ..

leading to:

CMake Error at /usr/local/share/cmake-3.17/Modules/CMakeDetermineSystem.cmake:173 (file):
  file failed to open for writing (Is a directory):

    ./build
Call Stack (most recent call first):
  CMakeLists.txt:8 (project)

it fails at configure stage before loading the language features at line:

project( issue768 LANGUAGES NONE )

The semicolon ; is a reserved character in FAT file systems so if it breaks is actually a good thing as using semicolons prevents portability of the project.

also, where did you get the recommendation from to

?

1 Like

Yes. A list is a list, and a list element is a list element. This leads to confusion sometimes.

set_target_properties(target1 target2 ...
                      PROPERTIES prop1 value1
                      prop2 value2 ...)

does not expect a list for PROPERTIES, it expects value1 ...

foreach(<loop_var> IN [LISTS [<lists>]] )

foreach expects a list for LISTS.

1 Like

Good point, and

even better! It means my project won’t be the one preventing that.

Eh you’re right. Nobody told me. I did it because of all the scripts I read online, hardly anyone used quotation marks regularly. Another lesson on how in Cmake is often a bad idea to assume practices from random scripts online. Everyone seems working with it’s personal conception of the language.

I concur.

Un-escaped “;” in file paths / strings are not supported by CMake itself. Therefore you really can’t try to support the use case.

1 Like

I can’t understand what is the difference between the exporto() command EXPORT and the EXPORT argument to the install() command. I’ve read the install() documentation page:

EXPORT
This option associates the installed target files with an export called <export-name> . It must appear before any target options. To actually install the export file itself, call install(EXPORT)

and

The EXPORT form generates and installs a CMake file containing code to import targets from the installation tree into another project. Target installations are associated with the export <export-name> using the EXPORT option of the install(TARGETS) signature documented above.

What I got from here, is that I can group targets in named “exports”. (I’ll call them “export groups”, like I did in the very first post). I still don’t know what to do with this grouping feature, so I’ll stick to one for now. I haven’t seen a project that uses more than one export groups yet.

An export group translates to a generated file (usually called <export-name>.cmake) which contains CMake code to “import” the targets in the export group. (it basically mirrors everything with an IMPORTED flag? I’m not sure of what it does exactly).
This step is done by the install(EXPORT <export-name> ... command.

This is what I understood from the documentation. But I’m not using just the documentation because it mostly says how things work, not how you should actually proceed to do the things. So, I’ve read this nice article, which was recommended and praised by a lot of people.
He calls:

install(TARGETS JSONUtils
    EXPORT jsonutils-export # creates an export group called "jsonutils-export"
    LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
    ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
)

and

install(EXPORT jsonutils-targets # I don't know what "jsonutils-targets" is
  FILE
    JSONUtilsTargets.cmake 
    ...

I understand that I didn’t understand. And I still don’t understand.

The reason why the targets are IMPORTED is because they are part of another CMake project. Therefore, they do not need to be compiled by the consuming project. For imported targets, the build step is skipped and their build artifacts are not removed on clean.

Install targets installs your targets. The options EXPORT jsonutils-export additionally registers your targets for CMake. Install export creates a file for jsonutils-targets which contains all registered targets. The resulting file from this process can then be used in consuming CMake projects.

A very comprehensive explanation on the difference between using export directly vs installing an export is given in the book “Professional CMake”. It is very comprehensive in general and a lot easier than guiding yourself from the online documentation and what else you can find on the internet.

1 Like

Exactly

Shouldn’t the <export-name> parameter be the name of the export group we want to write to file? (yes it’s also used as file name, except if specified otherwise)

By default the generated file will be called <export-name>.cmake but the FILE option may be used to specify a different name.

We should specify to install(EXPORT) what export group to install, somehow. If not with <export-name>, how?

Edit

Nevermind (#・_・#). I’ve discovered the article had a comment section that didn’t load till now. It was a mistake on the article. It seems there are even more mistakes left there. I made you waste time for nothing, I’m sorry.


I’ll tell you, I’m actually a book guy. Reading manuals can be a pain, but I’m not really a fan of learning by doing. I think that if a tool has many ways to do something, there’s always one that does what you need the best. And the only way to have that kind of awareness is to read a good book.
But in this case I was strongly advised against picking up a CMake book, because everyone has different “modernity” in their modern CMake, and because they often came in conflict with the documentation itself (by promoting old/inefficient practices or by skipping features entirely).
I’m now of the idea that I should’ve picked up a good one and proceed from that, but what is done is done, and now I don’t have the time to start anew, maybe in the future. So here I am :]

How do you install and export a custom target? I was pondering.
You don’t, because as it’s said here, a custom target exists to define a shared build step, usable by other targets. If you have a custom target that generates files that need to be installed, you use install(FILES or install(DIRECTORY. No export group involved and of course no IMPORTED targets are generated. What would the user’s CMake do with those loose files anyway?

That said, I need to install the project’s documentation. The docs building pipeline is composed, as now, of three custom targets: doxygen_generate_xml, sphinx_generate_html (created by the respective find modules), and documentation. Each of these targets depends on the previous one.

I initially thought that when a target was installed with install(TARGETS, CMake would only build the specified targets. Instead, I’ve read that it builds for “ALL”. (otherwise custom targets couldn’t be built!)

I’ve seen that the doxygen_generate_xml and sphinx_generate_html targets have both the ALL flag enabled by their find modules. My idea was to have just documentation with the ALL flag, and have everything below built automatically as dependencies. What does CMake exactly do if each of them have the flag enabled? Does it still follow the correct build order?

The approach used in the article here is different. She creates a custom_command, which has the added bonus of being able to run only if the files have changed.
I’m not really happy of leaving the target-based dependencies approach for a file-based one.
Like it’s said here, I see the custom_command as a way to add a step to the build process of one target, without creating a dependant custom target to do that.
That’s is the only usage I see that doesn’t break the target-based approach. I dunno.

You are correct, the install has a dependency to ALL. Custom targets can be build regardless of the targets ALL or install, though.

It’s not a flag, it’s an option. CMake defines build orders. As long as you do not accidentally set cyclic dependencies CMake honors all dependencies.

Not sure what you mean. You have file level dependencies and target level dependencies. You decide which is which, then implement in CMake accordingly.

Again, this is your design decision. CMake provides support for both.

1 Like

I might be wrong, but it is my understanding that an installation places build artifacts on the right destination.

Therefore, in the case of documentation all you need is a list of folders and files, and where to put them. So, install( TARGET ... ) is not the correct form to use here. It is not like you are trying to execute your documentation. Use install( DIRECTORY ... ), instead.

1 Like

True, bad phrasing, I meant “otherwise standalone custom targets couldn’t be installed, because install(TARGETS doesn’t accept them”.

Mmm, still a bit weird to me how it does that. It would pick the targets at the top of the dependency chains first, and then the remaining. Or something like that. Sounds complicated, but oh well. :slight_smile:

Yes, that’s what I was saying. And even more importantly, it’s not like CMake is going to do anything with the built documentation.

yes, I was putting it through the “targets good everything else bad” philosophy. I don’t know whether doing both is wise or bad practice anymore. I like “targets good everything else bad” tho

In fact, I was thinking about making a custom command that launches a build of documentation when the files are changed. But since I’m forced to set ALL to documentation (or it could potentially cause undefined behaviour), that would be pointless.
I think that custom targets could benefit from a “last modified check” too, like standard targets do. And like custom commands do. I wonder why they choose force the “always outdated” mechanic.

custom targets cannot be installed, ever. Installing custom targets is bad practice.

If CMake is going to do anything always use exports instead of installs.

This is the correct way of doing it. You define a custom target without ALL and add a dependency to a custom command. That command generates output products every time source files have changed.

1 Like

I didn’t know add_custom_command(OUTPUT could accept targets as DEPENDS, I thought only add_custom_command(TARGET could.
…one of the first things you see is “Use the add_dependencies() command to add dependencies between top-level targets.”, “use DEPENDS to specify file dependencies”.
It’s even quite buried in the description.

That’s why I was saying I liked only the second signature, because I thought it was like the first, but for targets.
How I see them now is: the first signature creates basically a custom target on steroids, without properties and without the word “target”. They can do everything custom targets can, and more. But they exist as separate things. Why? I get how they differ, but not why.

The second signature is nice tho. Seems useful to avoid creating too many custom targets that perform setup steps before building a target.


Having said that, you suggested to create a “loose” (without ALL) custom target dependant on a custom command, to get the best of both worlds. The only way I know of making a custom target dependant on a custom command is to have them both in the same file (ok in this case), and to list in the target’s DEPENDS some files in the command’s OUTPUT.

set(watched_files "fileA" "fileB" "fileC") # source and configuration files that could cause a change in the documentation
build_command(sphinx_generate_html_command
              TARGET sphinx_generate_html)
add_custom_target(documentation
                  DEPENDS "${watched_files}")
add_custom_command(OUTPUT "${watched_files}"
                   COMMAND "sphinx_generate_html_command")

But what happens if I download the project and install? As far as I know “undefined behaviour”, because documentation doesn’t get built (no ALL).
Also, the command doesn’t personally create any output, the custom targets in the dependency chain do. And to be fair, they aren’t even outputs, they already exist and don’t get changed by the command; they would make more sense in the command’s DEPENDS, but then the command would never be ran.

Edit 1

I wonder if you meant to use the expected final documentation files in DEPENDS and OUTPUT, and the “watched files” in the custom command’s DEPENDS.
Like some index.html. I can’t do that. I’d like to be unaware of how the built documentation files are laid off. I’d just need to know the parent directory to install them.

Edit 2

The sphinx_generate_html custom target generated by the FindSphinx.cmake I’m currently using has the ALL option…
So I think that any attempt to avoid unnecessary documentation builds would be pointless. It was the only find module that defined a target, like the official Doxygen one.
The official FindDoxygen.cmake seems to have some logic to decide the ALL option, but I’m not experienced enough to tell what it does.

I searched up the title out of desperation, and first I was a bit horrified by the amount of editions made in a such short period of time, but then I read that’s not a “standard book”, you’re able to access updated editions for free once you’ve paid.
That’s probably why it’s so expensive (it costs more than “The C++ Programming Language”, cmon).
I personally don’t think the documentation was particularly bad as it’s depicted on reddit, I’ve seen worse. It’s just terribly unhelpful at telling when to use the tools. To learn that you: a) spend a terrible month b) give up and write hideous code that works c) pay to get “Professional CMake”, choose one. I’ll find myself to have ran through all three of them soon

It happened to me a bunch of time to get thrown in some new language or tool, and being said “here, make it work”, but with CMake, trying to get a bit more than that revealed to be a nightmare.

I’m extremely grateful to everyone of you who kept answering my questions, I’ve learned so much while the thread got longer and longer, and I think continuing would be abuse of the forum, so I’ll stop here. I’ll continue with the book, off and on.
thank you!

oof I can’t change the title to “(closed)”

I changed it for you.