SCons and / or CMake: any way to automatically display from the header included at compile time to "the corresponding object file must be linked"?

Super-simple, absolutely boring setup: I have a directory full of .hpp and .cpp files. Some of these .cpp files must be embedded in executable files; naturally, these .cpp files # include some of the .hpp files in the same directory, which can then include others, etc. etc. Most of these .hpp files have corresponding .cpp files, namely if some_application.cpp #include foo.hpp, either directly or in transit, then there is a chance that the foo.cpp file should be compiled and linked to the some_application executable.

Super-simple, but I still don’t know what the “best” way to create it is, either in SCons or in CMake (none of them have any experience yet, except that you look at the documentation for the last day or so, and it becomes sad). I'm afraid that the solution I want may actually be impossible (or at least overly complicated) to come out on most build systems, but if so, it would be nice to know that I can just give up and to be less picky, Naturally, I hope that I am mistaken, which is not surprising, given how much I am not aware of the build systems (in general, about CMake and SCons in particular).

CMake and SCons can, of course, automatically detect that some_application.cpp needs to be recompiled when any of the header files it depends on (directly or transitively) changes because they can “parse” C ++ files well enough to select these dependencies. Good, great: we don’t need to list each .cpp- # include-.hpp dependency manually. But: we still need to decide which subset of the object files to send to the linker when the time comes to create each executable.

As I understand it, the two easiest alternatives to solve this part of the problem are:

  • A. By explicitly and painstakingly listing "anything using this object file, you also need to use these other object files" manually, although these dependencies are accurately reflected by the corresponding .cpp-transitive-includes- -corresponding-.hpp that the build system already ran into a problem of asking for us . What for? Because computers.
  • C. Dumping all object files in this directory into a single "library", and then depend on all executable files and links in this library. It is much simpler, and I understand that most people will do it, but it is also careless. Most executable files do not actually need everything in this library, and in fact they will not need to be rebuilt if only the contents of one or two .cpp files have changed. Isn’t this the setting of precisely this kind of unnecessary calculations, which should be avoided by the supposed "build system"? (Perhaps they do not need to be rebuilt if the library is dynamically linked, but suffice it to say that I do not like dynamically linked libraries for other reasons.)

Can CMake or SCons do this better than possible in any way? I see several limited ways to envelop an automatically created dependency graph, but not a universal way to do it interactively ("OK, build system, what do you think Dependencies:" A. "Based on this, add the following dependencies and think again: ...") . I'm not too surprised. I have not yet found a special-purpose mechanism in any build system to deal with the superfamily case when the binding time dependencies should reflect the corresponding #include turn-on dependencies. Did I miss something in my (albeit slightly superficial) reading of the documentation, or does everyone just go with option (B) and calmly hate themselves and / or their build systems?

+4
source share
2 answers

Your statement in point A) “everything that uses this object file should also use these other object files” is what you really need to do manually. Compilers cannot automatically search for object files needed for a binary file. You must explicitly specify them during the link. If I understand your question correctly, you don’t want to explicitly specify the objects needed for the binary, but you want the build tool to automatically find them. I doubt there is any kind of assembly that does this: SCons and Cmake definitely do not.

If you have some_application.cpp application that includes foo.hpp (or other headers used by these cpp files) and then you need to link the foo.cpp object, then in SCons you will need to do something like this

 env = Environment() env.Program(target = 'some_application', source = ['some_application.cpp', 'foo.cpp']) 

This link will only be when "some_application.cpp", "foo.hpp" or "foo.cpp" is changed. Assuming g ++, this will effectively translate to something like the following, regardless of SCons or Cmake.

 g++ -c foo.cpp -o foo.o g++ some_application.cpp foo.o -o some_application 

You mentioned that you have a "directory full of .hpp and .cpp files", I would suggest that you organize these files in libraries. Not all in one library, but logically organize them into smaller, cohesive libraries. Then your applications / binaries will link the libraries they need, thereby minimizing recompilation due to unused objects.

+2
source

I had more or less the same problem as yours, and I solved it as follows:

 import SCons.Scanner import os def header_to_source(header_file): """Specify the location of the source file corresponding to a given header file.""" return header_file.replace('include/', 'src/').replace('.hpp', '.cpp') def source_files(main_file, env): """Returns list of source files the given main_file depends on. With the function header_to_source one must specify where to look for the source file corresponding to a given header. The resulting list is filtered for existing files. The resulting list contains main_file as first element.""" ## get the dependencies node = File(main_file) scanner = SCons.Scanner.C.CScanner() path = SCons.Scanner.FindPathDirs("CPPPATH")(env) deps = node.get_implicit_deps(env, scanner, path) ## collect corresponding source files root_path = env.Dir('#').get_abspath() res = [main_file] for dep in deps: source_path = header_to_source( os.path.relpath(dep.get_abspath(), root_path)) if os.path.exists(os.path.join(root_path, source_path)): res.append(source_path) return res 

The header_to_source method is the one you need to change so that it returns the source file corresponding to the given header file. Then, the source_file method provides you with all the source files needed to create this main file (including main_file as the first element). Non existing files are automatically deleted. To determine the purpose for the executable file should be enough:

 env.Program(source_files('main.cpp', env)) 

I'm not sure if this works in all possible settings, but at least for me it works.

+1
source

Source: https://habr.com/ru/post/1496092/


All Articles