- Hey guys. Well I hope you didn't have
too much of a big lunch 'cause I'm gonna talk for
one hour about build files. (audience laughing) Just so I know like to get a better feel of my audience. Who here likes to write build files? (audience laughing) Okay keep your hands up. Get your hands up. Then who likes to write
especially CMake list files? Oh okay. For those watching on YouTube
that was almost everybody. (audience laughing) Alright, yeah I know, I know. It's a pain. We usually think that
it could be delegated to somebody else. I mean for some of my time, maybe not me because I'm like very
super into make files and build system maybe too much, but for most people, I
guess if your manager came to you and told you I have a way that I don't know this
external company or this guy down the hall is gonna
write all your build file for the rest of your life, you would say, "Okay great, great." So yeah, for most of us it's a problem. It's like a hassle,
something that we would like to not do. But, I start working
with that for some years, and I discovered that actually
it can have some purpose. For example, do you ever
start with a small application you built where the dependency
graph looked like this. Well it's very simple. You have four leaves and
a simple diamond pattern. You let that live and after
I don't know a few month, years, you get something like that. Sounds familiar so far? All right, yeah that happens to me too. So what are we gonna talk about today? We're going to see what's the theory behind modular design. The idea that you should keep
a clean graph dependency, a clear code architecture and how we can integrate CMake into that. Technically speaking, any build system worth its salt should allow you to use some patterns that will
give you the same results. I'm just you know use
CMake because that's what everybody else uses if I trust
Github or any Open Source project really. But, really think of that
as just an illustration. Any build system should be able to provide you with the same thing. And in fact, most of what
I will tell you today is taken from other build
systems, some that are usually considered more modern that you also can have simply with
CMake without the, well, the big pain of changing
entirely your build system. A few word about me. I'm Mathieu. I'm from France. You might have heard it already. I work at Murex and
since one year, I've been a contributor to Conan,
and all I got for my pain was this lousy t-shit. Along the way, I discovered
a few things about CMake, about packaging, and about
how do we use all that to make better code. So let's talk a bit about CMake. First thing that most
people think is that CMake is a build system. Well it's not. Technically speaking, it's
a build system generator. You tell him that you
want to build a project, and all he does is generate the build file for somebody else. Make, Ninja on Unix, Visual
Studio or something else on Windows. It just does that. It doesn't build by itself. It just meet that
description for the generator you use. It's, well, almost 20 years old right now. And, it's used in lots
of Open Source projects. You might recognize a few. And, that's not limited to Open Source. Private company also use CMake internally. And, so what's the big deal with CMake? Well basically, it's maybe not the best, but it's the less painful solution we have to handle portability in
C++ because the language can be portable, your
libraries can be portable if your build system is
not, if you have to write like again your build
files for every system, you're not portable. And, what CMake does is that
you can finally write the thing once and have it work everywhere. And, as we all know, it was a big success. For example, if you want
to invoke it on a Windows machine that's how you do it. And, on a Unix machine,
that's how you do it. All right, almost portable I would say. But, we're not so far. So what do I call modern CMake? You might have seen that
was the title of my talk. I'm not entirely fond of the title myself, but you know there's modern C++. Why not modern CMake? It's something that's been
available for some time. The first I would say
first compatible release would be 2.8.12. In practice a few month
after that, they move to the new 3.0 release which
has a few more features, but exactly the same philosophy. And, it's very important
to know that it's 2.8.12 because it's not a batch release. They changed that in the new three branch, but in branch two, the last
one was also a feature release. It's not 17 versioning. So when I say 2.8.0 and 2.8.12, there is like a huge array of
feature that you're missing between the first and the last revision. So how do I know that any project I open on Github is not using modern CMake? Well that's easy. I just have to look at the
top line of your project, and I see require 2.8. None of the feature are
guaranteed to be available. Even more than that,
if you can't have them because you have a more
recent CMake, CMake may disable them because
he wants to be compatible. That's what you're telling him. Well yeah that makes me sad
because you're missing out. Let me explain exactly
what you're missing. What you're missing is the opportunity to have your build system
help you do a better design. But, before I explain that, let me go back to what I call modular design. The idea was taken from John Lakos talk that I had the privilege
to watch last year. Three part talk, very interesting. Like 1,000,000 slide in three hours. It comes from his book which
is about the same length. I think like 1,000 pages. And, I will try to summarize
what I've taken from that for this purpose. So obviously it will not be everything 'cause that's like a huge topic. And, John if you're
watching this, I'm sorry. I'm gonna make mistakes. I'm gonna forget something,
but the idea is there. I hope so. So to explain modular
design, I think the best idea is to take a simple example. Imagine for a second that
you join a small startup. There's like maybe two
or three developers, and you have this big
idea that you are gonna provide a service to compare and find the best taxi ride in your region. So you have this first iteration. You have to deliver a minimal
product just to demonstrate your nice algorithm to your investors and obviously get some
money and get funded for the rest. So if you try to apply
some kind of modular design rules, you try to keep concerns separated. So we'll have a first
library that will handle the taxi prices, the
description of your objects and a few operators to manipulate them, and you make sums or comparison, whatever. You will have a second one that we'll use to describe a client
order what's his name, what's his profile,
where does he come from, where does he want to
go, that kind of stuff. And, the final is the
fare which is the result. That's just a very schematic example. But, the idea is that to
build your calculator, you actually depend on
three modules which are completely independent,
and that you can test even without your super nice algorithm. That's the point. So that part usually we get it right because it's like four libraries, and we are like two developers. Some time passes, you see your investors. We are very happy. And, you say all right, go for it. Develop me a full service now. The proof of concept was okay. Here is your funding. Okay so you have to
take to talk to the rest of the world. So first you write some
kind of JSON passer that's able to retrieve the
prices from all the companies in your area. Okay then you want to
serialize your clients orders, so you had some SQL database, and you add the library to do the serializing. Note that at each time,
we decouple the problem. First with technical
dependency on the JSON, on the SQL library, I still have the class that represent my object. And, then I have one
library that combines both. The idea is that I can
still work with them in isolation. If tomorrow I decide that
SQL is not the future, that it's no SQL or that it's whatever, I can change that with no
impact on the algorithm, on what is making my company money. Of course we finish
with another serializer for the output of our algorithm in JSON. Okay we're still fine. We have followed the practices. Again we're still a small startup. That's fine. Okay we scale up even more. Our clients are very satisfied. We can finally put all
that in one nice service, so we add the dependency
on the REST server library which itself is using some HTTP library. And, we create at the
top the simple router that takes a request, check the prices, and gives you a JSON
response with the best price you can get for what you ask. With me so far? Okay that's a theoretical example. That's maybe what you might
have seen in some classes, but then there's practice. And, in practice. Well it might look more like this. You had some dependency
that are kind of dubious because you maybe were
in a rush, or maybe you just didn't notice it. It just went through code
review, or you were tired. Well we have lots of excuses. Or, I guess we give
ourself lots of excuses. Anyway, in practice your
code is not like this because you have circular dependencies, so in practice your two
modules, only one here, and one there. So directly if you start
looking at your graph, it's not that good. You start to have a mix of concerns, things that are not
really in the same domain that are bundled in the same library, and you cannot already
test one without the other. Well it's starting to get
messy, but you can still test most of it. But, in practice if you
let that pattern go, and go, and go, you end up
with something like this which is one big blob
which is your service and then maybe a few external libraries. And, if you're unlucky,
you may have even circular dependencies between third party libraries or the things that you
consider really technical. Does that seem familiar to anyone? Okay, all right. You were silent. I had a doubt for a moment like is that only me? Now I'm convinced. So what's the basic idea
behind modular design? It's just that you keep control
of your dependency graph. You make sure that when a
library needs another library, it's explicit, and it can be
checked by some kind of tool or reviewer to ensure
that you are not creating a monster, that you are not
going through a huge monolith with circular dependency,
with nothing you can reuse or test in an isolated way. And, of course without so much cost. In this example, you cannot reuse anything of your nice algorithm
without the whole stack. In this one, it's still possible. And, the farthest we go, the better. And, then there are modern build systems. I'm not sure which one I
would call the first one. We might have heard about Bazel at Google or the other clones
that came up afterwards like Buck or Pant, whatever. They all have the same
philosophy behind them. It's that they are all very
explicit in what you say. You are really explicit about what you ask in term of build. You only ask for what
you use, and the system will forbid you to use something
that you did not declare. And, that's the key. If you do not declare
something, but you can use it, you will have a problem
because it will slip through at some point. And, that exactly the idea
of a modern build system. It's that it protects
you against bad patterns, and it helps you leverage
on that to make sure that you keep a clean architecture. So if you come back to
CMake, what happens today in most projects in what I
call not so modern CMake? If I tried to build I
don't know a small project with just a TCP client
and its own TCP library. Very simple, one library,
one executable, that's it. Well, what do you think of that? Does this sound like
something you might have seen or even written? Seems okay. First thing, I had a
subdirectory which is my library. I declare an executable. I add the necessary include directory, possibly some flags if I
need like IPv6 in that case. And, then I tell CMake
that my binary depends on the library. The problem with that,
sorry, the problem with that is that I'm not expressing
myself in terms of modules here. I'm not saying CMake I have this binary. It needs this library. I'm telling CMake I have this binary. It needs those headers. It need those flags. And, then it needs to link to that. I'm not talking about modules. I'm talking about build flags. And, there is a world beyond those two. You can miss a lot of
things, and it doesn't scale. If I build up on my TCP
library for another project. Then I want, I don't
know, an HTTP library, and then a REST library. What will happen to my build flags? Well TCP is quite clean. I just have minus TCP and minus D IPv6. Then I do the lib HTTP. I have to copy paste
those flags plus the flags for the library itself. And, then again same thing
for the library just up ahead. It doesn't scale. Every time you have a public dependency, that means that you will have to take all the public flags from
the library you're using and copy them in your CMake list. And, if you go to like three, four, five iteration, you end up
with like, I don't know, three lines of flags that you have to add to your library or to your
program to be able to compile. I'm afraid that's what
I see in some projects. What do you do usually? You just give up because
it's just a nightmare to maintain if you have to
change the library that's at the root, you have to modify
everything in your system. So what do you do? You go to the top CMake file, and you say all right everybody can
include that directory. Everybody can link to that, done. And, that's the only
sane way you might think you can do it. And, I won't blame you for that. I did the same thing. And, the idea is that
CMake has another way. Most build system have another way. Because if you're thinking about flags, you're not thinking about modules. You're not thinking about architecture. You're just thinking in
terms of minus E, minus D. It's a mess. It has no sense. There is no way for you
to try to reason about it and think about all right
am I including this stuff. Is there like a good
reason for it, or am I just I don't know grabbing
things that I should not, things that maybe depend
on me while I'm depending on them? How can you know? How can you tell? Personally I can't. The other way around. If you just declare
modules, if you just say, I have lib B, and it depends on lib A. Even if you scale that
to 100 or 1,000 module, I mean every intern at
your company can take that, do a graph, output that in
a dot file and just draw you the graph. And, I'm pretty sure it's easy to write some kind of sanitizer or check tool that you can run on your Git commit on your pull request to tell you all right you've introduced a circular dependency, you cannot do that. If you reason in term of
modules, it just become a graph. If you reason in term
of a huge list of flags, well you can't do anything. You're stuck. So the idea behind a modern build system is simply to protect you
from circular dependencies, or at least, tell you
when you're doing it. I didn't think you could
have a good reason, but some of them will
let you override that if you really need to. Of course, like I said,
the idea is that you can reason at module level
really not talk about flags. And, basically it means
that the build system is doing more than you're telling him. It's not just a script file that's doing blindly what you're tell
him like a dumb robot. He is trying to make meaning
on what you're building and say hey, hey, hey
I'm not in your place, but if I were you, I would not do that. I'm pretty sure you're not
doing what you should be doing. The same way your compiler can tell you, even if it doesn't stop
you, that you are returning I don't know the address
of a local variable or something else that in 99% of the case is a bad pattern, and
that you should avoid. So in practice how does it work? It's simple. Each module will define
of course the build flags it needs because well you
have to define them somewhere. They're not going away. They're just localized. You just tell all right this is my module. This is what it needs to build. Then you say all right
this is what I require. This is the dependencies I use. But, you talk about dependency. You don't talk about flags. You do not look at the
other modules internal. That's not your concern. You just say I want lib B. Give me the flags I
need to compile and link with lib B. Do not look at the build file and say okay, in that case, I
may need to put that flag because I'm using that feature. That's not maintainable. Of course, you need to
split your own build flags in two sets, private and public, that's exactly like the object
concern of implementation versus interface. Private flags is your implementation, the flags that you need to build. But, once you're built,
you can throw that away, nobody cares. You just needed that to get a binary. The second set is the public ones. It's like your public includes the define that will affect your
compilation, all that stuff. And, that your clients
will also need to be able to compile. And, of course the
interfaces are transitive. That means that if you
have a private dependency, you take their modules, but then it stops. If you have a public dependency, it adds to your public interface. For example, if we look at what we had in previous example with
TCP, HTTP, and REST, it's all public dependencies. So the flags have to be
repeated at each step. If that was a private dependency, it would stop at the first consumer because it is linked to build, but then since it's private, it's not seen by the rest of the world. The rest of the world does not need to have those flags to build successfully. And, of course, you still have flags. I mean you have a respective set of flags. You only care about the flags you will use in your module, but
you can still put them, all the ones, you still used in the past, you can still have them. And, I'm not telling you
to stop using flags at all. You still need them. I mean just minus E,
just minus W, whatever. You still need them, but
you need to localize them. You need to make sure
that it does not escape your CMake file. So all that is nice, but
how 'bout CMake, right? That's nice. I show you something
that's taken from theory like maybe some nice
system that's going on at another company. But, you are using CMake. I am using CMake. I'm publishing stuff on Github. Can I use that with CMake? And, I would not be here
if the answer was not yes, you can. There is actually an answer. What's the idea? The idea is simple really. Well you still use the old add library, add executable. That part is still the same. I didn't ask your familiarity with CMake, but I hope that everybody's
clear on that so far. Okay, great. And, then every time you
have to put some flag, you sue the target variant
of whatever you need to set some flags. I will give some details after that. Then if you want to declare dependencies, you use target link libraries. It's helpfully named
because if you want to link with like the direct library with a path, and if you want to link with
a module, with a project and take its interface,
it's the same syntax. It's really a limitation of CMake. I would wish there was another keyword so that you can distinguish
between those two. There is not. So yeah, it's called
target link libraries, but in practice, you're
not just telling CMake that you want to link to a library. You're telling CMake I'm
using that dependency. Please give me all the flags I need to build successfully. And, of course every
time you have to specify a property for your files
or your build files, you say if it's public or private. It's an example. Okay first part is easy. You start by requiring, well technically you could put 2.8.12, but I think 3.0 is not something too big to
ask for most of the world. It's five years old now. You should be fine with that. Of course you can go crazy, and you can have 3.9 if you want. I won't stop you. On the contrary, there
are lots of interesting feature in that, but at least 3.0. Then well first counter
example, you might still declare some flags at the top level. Why? Well that comes from my experience. And, in my experience, there are some kind of compilation flags that you do not want to localize for some particular module. You want to put them everywhere. And, the most important is
anything that's a combination of Werror and W something. Because there is nothing worse than changing your code
and having it break because in your header there is something that will not compile
in one of your clients because he's more strict than you. I don't know if it happened to you guys, but it happened to me a lot of time, and that's very painful. So I said that some flags
might still be better off at the top level to make
sure that all the project is on the same page. And, I would ask the
same thing for anything that affects the ABI like a standard level that kind of thing because if not, you will get some incompatibilities. All right then you still
do your add library. Nothing fancy here, the
same thing you've been doing for I don't know
how much you've been using CMake, but that part doesn't change. All right, and now we come
to the meat of the thing. First you want to declare
some public headers, so you say all right
target include directories. You say it's public and you need the path. That tells CMake all right
in the build interface of my project anybody who
want to use my project will need this include
directory to find my headers. If you have some public
headers, for example in your SLC directory, you
just add the same thing with private. You'll be able to include
them in your project. Anybody else won't see them. If you have some settings,
something that depends on the config of CMake
whatever, you can also put them with the compile definition or whatever. In this case, it's a public setting. But, if it would have
been a private setting, it would just have said private instead. That's the same idea. If the setting will have
an impact on your public headers like change
the size of a structure or maybe hide some declaration, you need to make that public. If it's only internal
detail like I'm gonna use this algorithm
internally or that algorithm and that don't change your
API or implementation, public implementation,
you can put it private. It's fine. And of course, then you have to declare your dependency. And again, I'm sad that
there is not a special keyword that's different from
the old one to express that, but trust me that if I
said target link library something, ABC and ABC's
a project, I will get all the public flags that are declared. I will get the link flags I
need, and that's recursive. If I need transitively some include flags or some link flags, I will get them. It will even go across static libraries because as you already know I think static libraries are not linked. So if you have a dependency
inside of static library, CMake will remember that. And, the first shell library or executable that it encounters will link to it. It's taken care of for you. And, of course if you
have a private dependency like something that your
implementation relies on but that's not visible for your headers, you can put it private. It will work too, and it will not be seen by your consumers. Meaning that for example if you use Boost, but if you do not want
the rest of the world to see Boost, or if you want to make sure that the rest of the
world will not use Boost unless it is declared,
you make it private. And, only people with
explicit reference to it will see it. And, that's a huge point
because what happened a lot in my experience is that
everything that can be found through auto
completion inside your IDE will get used by people even
if it's not publicly declared, but it's available through
transitive dependencies. So the less that you have
in your public interface, the more private you got,
you're sure that people will have to be explicit about them, and you reduce the risk
that your architecture will start to go bad like
I said in the first slide. If you got header-only libraries which is kind of the fashion today, you can simply declare an interface library. Interface is a peculiar keyword in CMake that tells it that something belongs to your public interface
but should not be used to build your library. Of course because it's a header only. You have nothing to build. So if you have nothing
to build, but you want your clients to see it,
you make it interface. And, you can even link to something. You can say for example
my header-only libraries will need this client
to be linked to that. You can add a link dependency. It will work. CMake will take care of everything because that may happen. You may have header-only
libraries that needs something that's not header only. It will work. Like I said, nothing to
build, think interface. Now that we've seen most
of the good patterns, I want to take just a
few seconds to recognize the antipatterns, the bad things. That's the kind of thing when you open a CMake list that tells
you that you probably need to rewrite it a bit. So of course everything
that affects all targets like include directories, add
definitions, link libraries. Those kind of thing are evil. If you put them on your top level, everybody in the world
will be able to use them. Everybody in the world
will be able to include that headers, and if
your some reason the code does not require a link, like for example header-only libraries, or
inline templates, whatever, you will not even get a compile error. People will start creating
hidden dependencies through other modules, and
they will not even see them. Of course link libraries, I
think CMake have told people like for 10 years now to stop
using it because it just means every project in the
world should link that. I'm not sure anybody in this
room will be using this. But, just in case, don't. Okay then. Yeah? - [Audience Member] There's a caveat. Those macros that you mention. They actually don't affect all targets. They only affect the targets in that-- - Yeah I know only the targets you declare in this file, and all the
subdirectories you include unless you start a new project. - [Audience Member] Right. So if you use those macros,
there will be a huge list in the subdirectory that I'm calling. - I will not say it's
okay to use at any point because they will not affect
your interface at all. So if you declare an include
directory for example that your clients will also
need because for example they appear in your headers,
they will have to copy paste that also to be able to work. I really not recommend using that at all. - [Audience Member] Well
there are legitimate uses. That's fine. - I will be happy to discuss
that with you afterwards because frankly I've not
seen them in my career. Maybe for a very small
project just like you know the tests or something, for that yeah. But, for a huge application, that's coming to bite me at some point. And, I will get exactly the
same warning against using target include directories to include anything outside your project because if you include
something outside your library, well you're breaking the rules again. You're not trying to use a dependency. You're trying to use
headers from somebody. You're trying to pull something
that's not yours to use it without declaring explicitly
I have a dependency to that module. And, then you're breaking
the rules of talking about modules instead of
talking about implementation. And, that's the thing here. What I encourage you to do is to declare dependencies to modules not build flags because it's awful to
reason about at scale. Since target link
libraries is an old marcos and since CMake did not make a new one, you can forget to put public, private, or interface, and it will work. Which one of the three it
will do if you do not put public, private, or
interface, to be honest, I can't tell you without
looking at the doc. So my recommendation is do not use it. Just always tell if it's
public, private, or interface. And, of course target compile option, I would recommend you to use
caution because if you put like I said some Werror, you
might have some surprises because your dependency
change, and they do not have the same level of pedantic as you do, or maybe simply you do
not use the same ABI. Like I don't know you ask for C++ 11, and for some reason another
library asks for C++ 03, you do not use the same runtime. You're gonna get into trouble. So I would keep that at top level. All right if you remember
this, I think you get like maybe 90% of what I call modern CMake because you get away from the flags. You got away from looking at the bits and the implementation
of all of your modules, and you move to another
layer of abstraction. You're really thinking
in terms of modules, in terms of what is my implementation, what are the interfaces I use. You do not care about how people implement their modules. Because another problem
is that if they change, you have to change too unless
you declare dependency, and then the build
system does that for you. And, that's the whole point. And, since we have some
time, I guess we could talk about the rest because
of course the 10 last percent are the least used but might
still come handy to you. The first example, and I
think the most common case that I did not address yet
is external dependencies because we all use third parties, I think, or most of us use third parties. And, well they are not
always built with CMake or always built with
that instance of CMake you're running at some point. So you have to grab some packages. For example, here I needed GTest. I read the doc about the
find package of GTest which said that GTest should be built with threads because on some platforms, on some compilers you need
to link with threads too. So I require also the thread packages. Then I create my
executable, and since GTest provide me some nice macro, I can add the GTest include to my directories, and I can link to GTest. And, that's wrong. Yeah, that was a trap. The problem is that you are falling back to the old flags approach. You are not telling CMake that you are depending on GTest. You're telling CMake that you want to include GTest headers
that you want to link to GTest, and that you want
to link to GTest dependencies. And, should that change, you
have to rewrite your code. And, fortunately for
you, CMake heard that, CMake saw that, and in
their recent versions, they changed their finders to be able to export targets too. So now if you take at
least 3.5 for example with GTest, you can just
say all right I require GTest GTest which is the
name of the GTest main if you want GTest to generate
the main function for you. And, that's that. It's done. It will call your threads
for you if you need them. If GTest implementation
completely changed tomorrow and requires something else,
it will also take care of it. You will not have to change anything. There has been some effort, like I said, in CMake to add some new finders. The first one was OpenSSL in 3.4. Yeah, that took four release to think that third parties was an issue, but we're getting there. 3.5 was big. We got Boost, we got GTest. Already I guess like half
the dependency I require on new project are there. You got some graphical libraries. 3.6 bring us Package and
Config which is pretty nice because it's not just
find package and config. It's like if you use
CMake to find a PC file, it would automatically
translate that into a CMake project definition for you. So you can just say okay I
require that PC dependency. You're all set. Any Package and Config package
installed on your system, you can require it with modern
CMake just with that patch which is I think pretty
cool at least on Unix. The only problem is that
I never seen anybody using PC on Windows, so
that's not exactly portable. But, well on some environment
that might really be handy. And, more recent version
we add OpenGl, OpenCL, BZip, and all that kind of stuff. So yeah, if your manager
is a bit old on CMake, if your file structures a
bit old, all the more reason for you to ask him to move
to a more recent version. You have all the finders set up for you, and that will simplify your code. But, what happens if there is none? Because as you have seen
there are some of them, but I don't think that's
the whole C++ package community here. Well I think some of them may be missing. So what do you do? Well first option obviously,
you can just contact the maintainer and ask
him nicely to provide one. You may or may not have some results. You may also have some arguments. For example, you can tell
him, as I can tell you today, that if he builds his package with CMake, he just has to add a few
instruction at the end to ask CMake to generate
that finder for him. Because when you do
make install with CMake, you can also tell CMake to
generate a dot CMake file that act as a finder. I think Daniel Pfeifer
did a nice talk on this and a lot of other things
on CMake at C++ Now back in spring, and it
explains all of that. Of course if you are publishing a library, I encourage you to do one
even if it's not with CMake. If it was CMake, it's simple really. Really, you just have two
instruction, and it generates them for you. If it's not CMake, well
it's not that hard. What do I have to do? Well first of all of course I have to do find library to find the
library to just make sure that it's installed on the system. And, CMake will take care of the question is it Windows or Unix? Do I have to have a dot
DLL, or dot SO, or dot LIB depending on the platform? You can tell CMake that
you add the library as an imported target. And, then you say all
right I want to include those directories and these dependencies, and I have a nice build interface with an external project. Easy, well almost too easy. Yeah, I hoped that by writing that that would have worked, but it didn't. Turns out that CMake maintainer left me a little surprise in that in
that it's a bit more complex. For internal reasons you
cannot use target whatever on an imported target. It doesn't work. CMake will throw you out. Fortunately it's only a wrapper. The target whatever is just a wrapper. It's just telling CMake
go through the list of variables that are
defined in my package and set some values. So you can do it by hand. I won't say it's pretty like you can see, but it does the job. Simply, well you still add your library. You have to set a
property for the location. You cannot directly tell that when you add the library, but you
can just set property. And, same thing, all the
things you set like public, or private, or whatever
it just a set of variables that's added to your project. That's how it works with
CMake behind the scenes. That's not even relying
on implementation details. That's something you can
find in the CMake doc. That's not something I
recommend you to use, but in this case, you don't
have a choice unfortunately until maybe CMake patches that. Well okay it's not pretty,
but it fits on one slide. So well that's not that terrible. Can we have easier alternatives? Well yeah, there are some. But, as some French
famous mathematician said it doesn't fit on the margin of this talk, so I won't be able to
give you all the details. Still like I said before
Daniel Pfeifer did a very nice talk about CMake which is
called Effective CMake I think, yeah Effective CMake. It was in Aspen at C++ Now. It explains lots of thing about CMake from generating finders to using CMake to set some compatibility flag for you, checking the standard support, whatever, you name it, it's got it. I think it was voted
like most helpful talk of the whole convention. So I really encourage
you if you're into CMake, if you have to use CMake,
just go look at it. You will see lots of interesting things. So to summarize a bit what
we've seen today together. First things, what's modern build? Modern build is about keeping
your flags to yourself. Do not try to expose
your flags in the code of the others. Do not look at the code of the others to try to get the flags. Just keep that to yourself. It's your private bits. It's your problem. CMake will all know the rest. You think in terms of modules really. You think about the graph. You know it's easier
when you think about it. It's just like take a white
board and do some architecture like we all like to do
when we start a project. And, then we fall back
into flags because we are out of time. But, the idea is that
you can still do that the whole life of your project. You can always go back to
all right let's put that on a whiteboard. Let's make a nice dot
file or whatever and see okay does that architecture make sense? Or, am I trying to insert
circular dependencies or hidden dependencies? And, all the rest, all the
transitivity, all the hidden internal flags that you
need to pull, you let the build system do that. Again, I'm talking about CMake here, but I don't know what you use at home. Anything should be able to handle that. If not, I guess it's time
for a feature request. So for CMake, first of all
take three dot something. The most recent the
better, as you have seen, there are some nice finders to this. Use the target something
alternative for macros unless we're talking maybe
about compile option, but other than that, use
the target alternative. Always specify if a
property is public, private, or interface. You are trying to expose
something to CMake. It needs to know the level of visibility the same way that when you
put something in a class you have to wonder about
is it public or private, well it's not protected in that case. But, is it public, private, or both. And, of course you have
to link against a target to get all these properties. It's still not pretty. I would gladly like to have, I don't know, target require module
or something that sounds more like effectively what we are doing, but it's what it will
do behind the scenes. It's not just I want to
do minus L that library. It's more than that, and
it's what you should do. For external packages,
well your first option is simply to use modern
finders because some of them are provided. If not, write them. Ask the manager to write them. Or, if you cannot, well
there are some options. Like I said CMake can
generate one from you just by the build
definition of your project when you run the configure
and the make install. It can do that for you. If it's an external system, you can ask the maintainer, "Please,
please, please, please, please "could you make the
finder for CMake for me?" If it's a PC file, it's automatic. And, if none of that it's
possible, well I guess you can try a package manager. All right, thank you all for coming. And, I think we have a
bit less than 15 minutes for questions. (audience clapping) We have two mics. I think it would be better for you, you if you could come down. - [Audience Member] So
one pain point for us has been some of the find packages that return hard paths for the library, and then if it gets built somewhere else as part of a different build closure, that path is now wrong due to things like sim link farms. So like OpenSSL has, at some point, been an offender in this. So how would you recommend us be able to take advantage of
dynamic libraries like that without breaking the build further down. - I'm not sure I get
that because when you run find library, it tries to look for files. So it should not return if it's not found. You know you can add the required, and when you do find package,
you can tell required, and if the finder fails because
the library isn't there, it will stop. I don't know if that's your problem. - [Audience Member] So
say you had two libraries, the first one got built against OpenSSL, and it had the actual link on the path to the host for OpenSSL
that you linked against. And, then it goes off to another section in your build fleet. There's another large
section of sim link farms that are built. That path is no longer
the same, so when you go to link against project A,
the path no longer works, and you have linker errors. - You're talking about
two different instance of CMake here right? Like you do a first run
of CMake, you produce some libraries, but they'll
have some dependencies, and then you will import them
into another CMake instance. Well I would tell you that's
what package manager are for. Really I would suggest
you to look at what exist and check with that
because doing that by hand on the same machine it's possible because you can use sub modules in CMake and have everything in the same place. But, if you build in different stages, I really encourage you to
look at package managers because if it's just
one step it's possible, but, as you illustrate,
if it's a few steps, it gets a nightmare to handle recursively. And, the easier way if
you want to make sure that you keep everything, and the package are relatable. It's much easier to do
with a package manager. - [Audience Member] Okay, thank you. - [Audience Member] You
said something about keep your compiler flags to yourself. I cannot appreciate that more. I mean that is such truth. Because a couple weeks
ago, we have a whole bunch of external libraries,
and a couple of weeks ago, we upgraded one of them. And, unfortunately one of
their most recent version, they have a compiler settings file in which they have declared
their compiler flags as public. So I built the externals
and tried to build our code base, and they had
ethno exceptions in there. So my quick and dirty solution was go to the compiler settings
dot TXT and do a CMake string replace except no
exceptions and replace them with empty strings. That was ugly I know, but was
there a better way to do that? - My suggestion would be
that first if possible do not set, inside the
CMake list, do not set compiler flags. I mean do not set like minus STD. You can require. You can like ask CMake is this compiler with the flags I've been supplied able to use C++ 14 or whatever? That's okay. You can make checks. But, I think you should
not try to set things. It should be delegated to
the invocation of CMake when you can do minus D or CMake CXS flags is it there? And, then you can have
a profile that you use to invoke CMake, but
that should be external. I don't think you should
store inside your CMake list flags that define the ABI,
the binary compatibility because, of course,
two libraries will have their own set of flags,
and they will disagree. And, it won't work. It should be possible, it should take something external, some external input to know what kind of runtime, or what kind of minus STD it should be compiled with. It should just make checks. Okay is it possible with
the environment you supply me to build or not and fail if you cannot. But, not try to hijack
the process and decide that you will change
the way it was supposed to be built. - [Audience Member] So the
external library itself builds fine, and we built
that with their own flags. But, since we did find package on that, I think the flags leaked into our build. - That could be this if some public flags are like, I don't know, some minus D. That's what I said when I said do not set compile flags in targets. For me compile flags
that are very platform and infrastructure, it
belongs to a profile that should be shared
by every CMake project you build in the same environment. You should not keep that inside CMake. I think it's a bad idea. You're just setting up
yourself for disaster. - [Audience Member] Okay, thank you. - [Audience Member] Hi,
so could you go back to your example of using
Boost through the target? - [Mathieu] Yep. - [Audience Member] So I
own an Open Source library. - [Mathieu] I think I can,
yeah there is one here. - [Audience Member] Perfect, right. So I own an Open Source library, and I recently wanted to upgrade to use modern CMake. So I switched to using the targets created by the find Boost script that comes in the box with CMake. Then I updated my Boost
version to the latest Boost version the day after it came out. And, then my library didn't work anymore because CMake refused to generate targets for the latest Boost because my belief is that it is because it doesn't know the dependency information
between the Boost libraries, and so it doesn't feel
confident in creating all of the targets because it doesn't know the relationship between them. And, then I have to
wait for another update for CMake to come out in
order to correct that. But, of course that means
that I have a two week delay where no one can use my library. However, if I simply directly
use the old variables again, it works fine. Are you aware of anything
that they are doing to address this problem? - That's something that's completely tied to the fact that the CMake provides the finder for you, and it's decoupled from the version of Boost. They provide that as a
convenience because at the time where the other packages did not. So I think it's more
of a convenience thing, but that comes with the
risk that any convenient finder that's given by
CMake will not be in line with the latest version of the package. - [Audience Member] So
you would not recommend me do this than? - Well that depends. If Boost could provide the
finder, I would say use that. I'm not sure they do
today because they use their own build system. - [Audience Member] They do not. - So that's a tough choice I will agree. Then again I've started
working with the guy at Conan because I really
believe that package management is the way
to solve all that mess. - [Audience Member] Okay, thank you. - [Audience Member] Hey
I'm pretty new to CMake, so this might be a really dumb question. But, if you are writing
a simple application, you depend on lib A, you depend on lib B, and both of them depend
privately on Boost. And, like Boost has three different ways you can build it into your
project which component then my app, or lib A, or lib B is responsible, who owns that decision
of how are you compiling the library in. Or, does that depend I
guess on how your finder is implemented. - Okay, technically speaking,
I would recommend you to run the find at the top
level of your CMake list to make sure that you
find only one version of every third party's. If you start splitting the finders, you will have two problems. First, you might not
require the same version, and you might have like a diamond issue with two different version
like it was just shown in this morning's talk. The second problem you
will have is performance. You will not see it with two libraries, but where I come from,
we had, at some point, a huge code base with CMake. And, if everybody in
each library like three or four level down is
doing find, you're wasting lots of effort especially
because if you use some macros like project
CMake will not catch the result. So it will scan the file
system like 1,000,000 times just to find the same library. So yeah, my recommendation
is find at top level all the third parties to make sure that everybody agrees on what you use. - [Audience Member]
Okay so ideally the libs that you depend on, all they're saying is find package Boost,
and the responsibility of how you find it and which one you're-- - Yeah you can tell
CMake when you do a find to require minimum version. It's possible. And, then it will fail if it's not found. - [Audience Member] Okay, thanks. - Great, thanks everyone. (audience clapping)
Very timely presentation, as this is a problem I am trying to solve at right now.
Here is a link to Daniel Pfeifer's Effective CMake talk. It does a much better job at explaining how to set things up using cmake.
Also, for the first person's question about openssl being in different location. This is already taken care of with imported targets, as
find_package
has to be called again for all downstream libraries, which Daniel explains how to make it transitive in his talk. Pkgconfig works in a similar way.Furthermore, saying to use a package manager doesn't make sense. A package manager takes care of automating building and installing the libraries(and resolving which versions), but if your library can't be built or installed in the first place, its not the package manager's job to fix the build scripts.
I wouldn't set the required cmake version to as low as 2.8.12 or 3.0 nowadays. Something like 3.3 or 3.4 should be the absolute minimum, better go with higher if you can.
The presenter is mixing lowercase and uppercase CMake commands, I think it's recommended nowadays to use all lowercase for commands, i.e.
target_xxx
and notTARGET_xxx
as presented in the slides.Also at around minute 41 to 44, I'm confused why the target_include_directories command is necessary and the ${BAR_DIR}/include. I thought in the target-based approach, doing target_link_libraries is enough and it will take care of the include paths. Using ${BAR_DIR}/include shouldn't be necessary anymore, all you should need is the
bar
target.I think it's a good talk and I've learned some things from it, so thanks to the presenter, nice! But Daniel's Effective CMake talk is somehow better I think. Maybe this talk is better suited for absolute beginners though.
ill need to take a look at the video later, but the core issue is that CMake has no namespacing, so any modularity is a joke. You either use externalproject_add and lose all the modules' local targets and have to juggle awkward exported variables or you use add_subdirectory and hit up against target name collisions in your modules and other issues involving scope
when the second method works it's actually really slick and nice to use, but once you have namespacing/scoping issues you're in a kafkaesque nightmare as you have to edit and maintain other people's CMake files