-1

Switched to Visual Studio Code in an effort to understand C/C++ dev tools and to stop relying on IDEs. I try to use Makefiles for this purpose because it allows to run compilation via command line on a target device (no GUI == no IDE). So far I've managed to make a generic Makefile that compiles all .cpp files to corresponding .o files and then links it all together. It works for small projects that store all sources in a single directory. Now I must port my working Xcode project.

The problem is that the source files are stored in a directory tree (no single directory) and may be both C and C++. Some files have similar filenames but reside in different folder. From C++ perspective it means classes the have same name, but different namespaces. Various IDEs handle this easily but Makefile gives me all sorts of problems cause I am not yet a make guru.

I've tried to create Makefiles in each directory (I failed), but then I read that is considered a bad approach so I switched to a single Makefile approach. Some say I should switch to CMake but I think that it is pointless without understanding the Makefile creation first because CMake would eventually create one. It would also allow to build and install on devices without CMake and other cool tools (if, for example, there is no Internet connection to download CMake).

Here is a basic idea:

- project-name/
  - application/
    - core/
      - loader.hpp
      - loader.cpp
      - ...
    - loader.hpp
    - loader.cpp
    - ...
  - hardware/
    - loader.hpp
    - loader.cpp
    - ...
  - some-c-library
    - library.h
    - library.c
    - ...
  - main.cpp
- project-name.xcodeproj
- Makefile

TR&DL: Unable to make complicated Makefile. GNU manual is hard to understand and online blogs/tutorials mostly touch only basic stuff.

usr1234567
  • 21,601
  • 16
  • 108
  • 128
CorellianAle
  • 645
  • 8
  • 16
  • This looks pretty helpful http://opensourceforu.com/2012/06/gnu-make-in-detail-for-beginners/ – erik258 Jan 05 '18 at 17:09
  • 1
    I understand that you want to understand Makefiles, but for a scenario of this complexity, I would strongly recommend a makefile generator (like CMake) instead. One reason is that raw Makefiles don't track dependencies automatically: if you add a new `#include`, you must also remember to update the corresponding Makefile to add a dependency on that header, otherwise your source file might not get compiled when you expect it to, and you get a headache trying to figure out linker errors. – Thomas Jan 05 '18 at 17:11
  • 1
    You don't need CMake for auto-generated dependencies -- you can simply use the -M flags in gcc [see here](http://make.mad-scientist.net/papers/advanced-auto-dependency-generation/). – HardcoreHenry Jan 05 '18 at 17:18
  • What sort of problems are you facing because I can't readily see what would be complicated about your current setup - except that the Makefile should be inside the project directory – Chris Turner Jan 05 '18 at 17:21
  • @ChrisTurner I tried to extend this guide to my project. http://www.puxan.com/web/howto-write-generic-makefiles/ – CorellianAle Jan 05 '18 at 17:47
  • It would also help to show us what you've tried so far in your Makefile – Chris Turner Jan 05 '18 at 17:48
  • @ChrisTurner Most generic solutions failed with mix of C and CPP files. I've got lost in % patterns. – CorellianAle Jan 05 '18 at 17:49
  • Basically, i tried to mix previous guide with the following: https://stackoverflow.com/a/3774731/2042546 and https://stackoverflow.com/a/32699423/2042546 – CorellianAle Jan 05 '18 at 17:52
  • @Thomas The reason I started with makefiles is that, being a Mac/Linux user, I sometimes experience issues with `make install` of different libraries/software, when the build script fails because of incorrect paths, flags, etc and I have to change it manually. Also CMake eventually creates Makefile. Several days ago I had to fix it for cpprestsdk because of several incompatible versions of libraries. I thought that, being a C++ developer, knowledge of make is a must. – CorellianAle Jan 05 '18 at 17:57
  • Is there a convincing reason that you carry around files with identical names? I see problems with include paths (you will need different `CXXFLAGS`) as well as dependency generations. – Vroomfondel Jan 05 '18 at 20:34
  • The solution to your problem requires some `make` coding which is tedious to explain in details. In order to solve the problem you faced and a bunch of other problems I had to write a template library for `GNU make`. It basically consists of only one `.mk` file which one has to include to their makefiles. Take a look here https://github.com/igagis/prorab/blob/master/wiki/HomePage.md All the problems you describe in your question are solved there. – igagis Jan 05 '18 at 21:07
  • 1
    @Vroomfondel: Duplicate filenames are not uncommon. Assuming that file names do not (or will not in the future) conflict is not an overly scalable solution, and so I would personally discourage practices that don't handle them. It's simple enough to put path names in the include directives, etc. to avoid conflicts. – HardcoreHenry Jan 05 '18 at 21:29
  • I think that the number of long lived (and successful) C and C++ projects where two source files of the same name are _active project members at the same time_ (that is, compiling for the same final linking target) is vanishingly small and those projects pay a price in build configuration. If you want a project where file names are not carrying information then I think make is not the right tool. Real world C programming is relying on file names (although the standard falls short of utilizing this fact) and it needs a big effort and strange methods to live against this de-facto law. – Vroomfondel Jan 06 '18 at 11:57
  • @Vroomfondel I've renames source file to include class and namespace name into filename. Before it was `hardware/loader.cpp` and now it is `hardware/hardware_loader.cpp` Not sure if it is a bad solution but that's what I did. The downside is that now the `#include` statement may look a bit weird: `#include "hardware/i2c/hardware_i2c_manager.hpp"`. With this approach folders are a lot less useful, although they still organize my source files. – CorellianAle Jan 06 '18 at 14:23
  • Why is it necessary to write down the path in the include directive? Usually you handle compilation of foreign interfaces by giving a selection of include paths on the command line, not inside the files. That way, you keep files decoupled from the actual file system as much as it is possible with C/C++. – Vroomfondel Jan 06 '18 at 16:49
  • @Vroomfondel Project has been created with IDE, which means it handle the building process (you don't handle it by yourself). With XCode, you can create groups (folder-like) of source files, which, by default, also create corresponding folders on the disk. – CorellianAle Jan 09 '18 at 10:00
  • @Vroomfondel I guess the idea is that you can quickly see a general project structure without an IDE if your files are structured the same way as the code inside (classes, namespaces, modules, etc.). And at the same way you can alway figure out a correct file from the `#include`s – CorellianAle Jan 09 '18 at 10:02

1 Answers1

2

I'm really not sure you need to be overly complicated. Sure, you could have the Makefile hunt down your directory tree for every C and C++ file, but what if you've added a new file that you don't want compiled just yet?

You can make things easier without being too complicated though. Assign the list of source files to variables - that way you can refer to them whenever you need them.

CPPSOURCES=hardware/loader.cpp application/core/loader.cpp main.cpp
CSOURCES=some-c-library/library.c

Then you convert those filenames into object filenames - this is just doing a replace of .cpp with .o etc...

CPPOBJS=$(CPPSOURCES:.cpp=.o)
COBJS=$(CSOURCES:.c=.o)

And there are standard variables that make uses for specifying what flags to pass to the C++ and C compilers

CXXFLAGS-g
CFLAGS-g

Then you have a rule to build your project. $@ is the name of the thing the rule is building (ie "myproject") and $^ is the list of dependencies.

myproject: $(COBJS) $(CPPOBJS)
   $(CXX) $(CXXFLAGS) -o $@ $^

Make comes with a bunch of default rules so that it already knows that if you ask it to build "some/where/deep/in/my/project/somefile.o" that it's looking for "some/where/deep/in/my/project/somefile.cpp" for example.

You could add a rule to delete all the object files - this is why using variables comes is handy

clean:
    rm $(COBJS) $(CXXOBJS)

The only other thing you'll need to worry about are dependencies, like the below example which tells make that it needs to build "hardware/loader.o" whenever "hardware/loader.hpp" or "application/core/loader.hpp" are newer.

hardware/loader.o: hardware/loader.hpp application/core/loader.hpp

This is probably the one area where you want to investigate automatically generation as it's very easy to miss out a dependency and then it won't pick up any changes in the headers. As noted in the comments, gcc/g++ can generate files with them in for you when you build the code using the -M command line options which you can use the include directive in the Makefile to add them.

Chris Turner
  • 8,082
  • 1
  • 14
  • 18