Virtual Developer Workshop: Containerized Development with Docker
Right, you know how to compile helloworld.cpp using the fabulous free C++ compiler you get with http://www.cygwin.com.
The next step is to write a makefile, which will play a similar role to a "project" or "solution" file in your typical Microsoft-style IDE. You write a makefile, and then you type make in the same directory and it will find your makefile and obey the instructions.
Suppose you had a main function in the main.cpp file and a module in the other.h and other.cpp files. You need to compile main.cpp and other.cpp and link the resulting .o files together into an executable. The makefile we need to write, which must be saved with the name makefile with no extension, is surprisingly short:
foolery.exe : main.o other.o
g++ main.o other.o -o foolery.exe
The odd thing is that we haven't even mentioned the .cpp files!
What's In a Makefile?
In simple terms, a makefile is just a list of rules. When you run make with no parameters, the first rule in the file is evaluated, and this will usually in turn trigger off the evaluation of other rules.
A rule starts with a line such as:
foolery.exe : main.o other.o
This literally means the following: Look at the file modification times on main.o and other.o. If either is newer than the modification time of foolery.exe, we need to recreate foolery.exe.
So, you write the name of a target file, then a colon, and then a space-separated list of files that the target file depends on. Next, you need to specify how to re-create foolery.exe in the event that the rule is triggered. This is specified by ordinary shell commands that appear immediately after the dependency line. Important: these lines have to start with a tab character. So in the example, I had:
g++ main.o other.o -o foolery.exe .cpp .o
This tells the compiler to link the two object files together into an executable.
But, we still don't know how the .cpp files get compiled into .o files. The answer is: by a built-in rule of make. Pretty much all C++ compilers will do a pure compilation step on a single file if you specify the -c switch; for example,
g++ -c main.cpp
That will spit out a file called main.o, just as we require. So, we could add a rule to our makefile, like this:
main.o : main.cpp
g++ -c main.cpp
But, we'd have to repeat that for every .cpp file in the project, which quickly becomes very tedious. And, I hate the kind of thing where some repeating pattern has to mention the same name three times (in this case, the word "main"). To avoid this, make has a feature where you can specify how to translate from one type of file to another (where the type of file is described by a wildcard). The rule for .cpp files is:
%.o : %.cpp
g++ -c $<
The % character is the wildcard matching symbol, and the funny-looking $< character sequence is the name that matched the filter on the right. The final piece of the puzzle is that make has this rule built into it. You can override it with your own rule if you want to specify extra switches to the compiler.
Even with this built-in rule doing a lot of the work, I'm still not happy with the makefile because the list of objects and the target name each appear twice. So, if you added a new source file, you'd have to add it to two different places. Fortunately, you can define variables in a makefile and refer to them with an amusing syntax:
objects = main.o other.o
target = foolery.exe
$(target) : $(objects)
g++ $(objects) -o $(target)
So, with that makefile, whenever we want to add a new source file to the project, we just add it to the list in the objects variable.
You also can add dependencies for your header files. For example, if other.cpp includes other.h, you should add a line to the make file:
other.o : other.h
This means that if the timestamp on other.h is newer than that on other.o, the rule to make other.o should be executed—and that's just the built-in rule we saw just now.
The GNU C++ compiler we've been using is part of a "compiler collection" that also covers several other languages. You can find out more from http://gcc.gnu.org/.
The GNU make tool has a great online manual.
Once you start digging into what you can do with Cgywin, it's amazing how capable it is. The graphics system used on UNIX is http://www.x.org/. Try typing this into the Cygwin shell:
This starts an X server and an xterm window. You can now run a famous X sample program:
I remember being pretty impressed by this, but it's nothing compared to what you'll find at http://kde-cygwin.sourceforge.net/—KDE, one of the most popular desktop environments on Linux, and the whole thing runs on Windows! This is partly testament to the completeness of Cygwin and partly to the extremely portable, modular nature of free software.
But, getting back to what we've looked at, now you have experienced the basic building blocks of a UNIX-style C++ development environment. It's nothing fancy so far, no graphical user interfaces and whatnot, but you can write network servers with it. It's how most servers on the Internet are written!
(But you should probably practise for a while before you start hacking the Linux kernel.)