CppCon 2017: Mathieu Ropert “Using Modern CMake Patterns to Enforce a Good Modular Design”

Video Statistics and Information

Video
Captions Word Cloud
Reddit Comments

Very timely presentation, as this is a problem I am trying to solve at right now.

👍︎︎ 5 👤︎︎ u/tuskcode 📅︎︎ Oct 13 2017 🗫︎ replies

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.

👍︎︎ 7 👤︎︎ u/pfultz2 📅︎︎ Oct 14 2017 🗫︎ replies

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 not TARGET_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.

👍︎︎ 2 👤︎︎ u/sumo952 📅︎︎ Oct 14 2017 🗫︎ replies

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

👍︎︎ 1 👤︎︎ u/geokon 📅︎︎ Oct 14 2017 🗫︎ replies
Captions
- 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)
Info
Channel: CppCon
Views: 86,453
Rating: undefined out of 5
Keywords: Mathieu Ropert, Computer Science (Field), + C (Programming Language), Bash Films, conference video recording services, conference recording services, nationwide conference recording services, conference videography services, conference video recording, conference filming services, conference services, conference recording, conference live streaming, event videographers, capture presentation slides, record presentation slides, event video recording
Id: eC9-iRN2b04
Channel Id: undefined
Length: 57min 40sec (3460 seconds)
Published: Fri Oct 13 2017
Related Videos
Note
Please note that this website is currently a work in progress! Lots of interesting data and statistics to come.