A coder's home for Marc "Foddex" Oude Kotte, who used to be located in Enschede, The Netherlands, but now in Stockholm, Sweden!
foddex.net

C++ Lesson 2: Hello World in a Unix environment

This lesson contains two parts. The first part will be about how to compile and this application using a very, very simple makefile. The second part will be about setting up a project using autoconf/automake. Both the concept behind makefiles and the autoconf/automake projects are way more complex than shown in this lesson. However, I'm not a guru in these subjects, I can simply make them do as I please. If you have any suggestions on improving this lesson, feel free to contribute below.

Before we start, note that I'm a Fedora Linux user. I built this tutorial using Fedora 12. However, if anything turns out to work differently for your Linux/Unix distro, I'm pretty sure that since you're using Linux in the first place, you'll be wise enough to work your way around any inconveniences... ;-)

Simple Makefile setup


The code for this lesson is identical to that in lesson 1:

#include <stdio.h> int main( int arg, char** argv ) { printf( "Hello world!\n" ); return 0; }


In any editor that suites you, be it vi, emacs, gedit, anjuta or whatever, copy/paste the above piece of code and save it as "main.cpp" (without the quotes, obviously). Then, create a new file, and copy/paste the following:

all: g++ main.cpp -o helloworld


Save the contents of this file in the same directory as the main.cpp file under the name "Makefile". This name is mandatory. Now, open up your favorite terminal, change directory to the directory that contains main.cpp and Makefile, and execute:

make


If all goes well, no output is shown on the terminal. A new executable file should have been created, named "helloworld". This is your executable! Execute the following to start your application:

./helloworld


If you see an error message stating that g++ is not an executable, you probably don't have g++ installed. This is outside the scope of this application, but on a Red Hat based distro, something like this should probably get you going:

yum install gcc-c++ -y


On a Debian based distro, something like this:

apt-get gcc-c++


For details on installing g++, please JFGI.

Advanced autoconf/automake setup


The very simplistic makefile you created works only for the most basic applications. As soon as you need one external library, you're makefile will stop being useful. If you want your code to compile on any Linux distribution, and also on Unix, OS/X, etc. you have to move to a more complex set of tools: autoconf and automake (and libtool). As stated before, I am by all means no guru with these tools, but I can show you how to build a basic configuration that wil get you going in most situations. Although you have to create way more files, install alot of tools, and probably be struggling with it for a while, the result will be pleasing. Autoconf/automake is supported on many helping you write a cross platform build setup.

Let's start with installing the basic tools. I'm going to focus on Fedora now. If you run on Debian, you're on your own. Start by installing all the necessary tools using the following command:

yum install -y gcc-c++ autoconf automake libtool


After you're done, we continue with building an autoconf configuration file. It will be placed in the same directory as the main.cpp you created in the first part of this lesson. Also, remove the Makefile you created. It will not be required anymore and could only confuse you.

Create a file named "configure.in", and fill it with the following contents:

AC_INIT(main.cpp) AM_INIT_AUTOMAKE(helloworld, "1.0.0") AC_PROG_CC AC_PROG_CXX AC_PROG_INSTALL AC_DISABLE_STATIC AC_PROG_LIBTOOL AC_ARG_ENABLE(debug,[ --enable-debug Turn on debugging],[ case "${enableval}" in yes) debug=true ; CXXFLAGS="$CXXFLAGS -DDEBUG -D_DEBUG -g -ggdb -O0 -fno-inline -fno-inline-functions" ;; no) debug=false ; CXXFLAGS="$CXXFLAGS -DNDEBUG -O3 -Werror" ;; *) AC_MSG_ERROR(bad value ${enableval} for --enable-debug) ;; esac ],[ debug=false CXXFLAGS="$CXXFLAGS -DNDEBUG -O3 -Werror" ]) AC_SUBST(CXXFLAGS) AC_OUTPUT([ Makefile ]) case "$debug" in "true" ) AC_MSG_RESULT([Debugging enabled, optimizations disabled.]) ;; esac AC_MSG_RESULT([CXXFLAGS = $CXXFLAGS])


Autoconf uses macros. Every capitalized statement is a "call" to a macro. For a detailed description of all the possible macros, and all meanings of all macros used here, I refer you to the autoconf online documentation. The most important ones I will describe here.

  • AC_INIT(main.cpp)
    This macro initializes autoconf, and performs a very simplistic file-presence check for the file specified. The file location must be relative to where the configure.in is located. Since you should have placed the configure.in file in the same directory as the main.cpp file, specifying "main.cpp" as parameter works.
  • AM_INIT_AUTOMAKE(helloworld, "1.0.0")This macro will initialize automake, with generic information about the project name and version. I'm not (yet) 100% certain of why you need to specify a name and version number here, as I'm not aware of any place it's in use.
  • AM_ARG_ENABLE(...)This macro defines the --enable-debug parameter, and tells the script what to do when it is, or isn't specified. Since it's always a good idea to easily be able to switch between debug and release builds, I'm always using this flag to be able to indicate the compilation target.
  • AC_OUTPUT(...)This tells the script what files must be generated (first through autoconf, then through automake). If your script contains subdirectories, which contain Makefiles, you need to add them to this new-line separated list.


How this file fits into the whole build-environment will be made more evident later on. For now, create another file, name it "Makefile.am", and place it in the same directory as the "configure.in" and "main.cpp" files. It should contain the following:

bin_PROGRAMS = helloworld helloworld_CXXFLAGS = -Wall -pedantic -Werror helloworld_SOURCES = main.cpp


This tells the buildscript that in this directory (i.e. our "project root"), we have one application we're building, named helloworld. The compiler flags (more on those in a later lesson) are set through CXXFLAGS property, the sources that oughta be built for this application are in the SOURCES parameter. The named of the project (with any non-alpha-numeric characters converted to _ underscores) is prefixed to the variable name. So if you want to built two applications, you'd use the following:

bin_PROGRAMS = app1 app2 app1_CXXFLAGS = -Wall -pedantic app1_SOURCES = myfirst.cpp app2_CXXFLAGS = -Werror app2_SOURCES = mysecond.cpp


Finally, before you can continue, you need a handy script that runs the appropriate tools automatically for you, and that eventually will generate the required build script. I usually name this file "autogen.sh". After you create it, you MUST set it to be executable by running:

chmod +x autogen.sh


The contents of the file usually is the same, regardless of the contents or the complexity of the applications and/or libraries you're building:

#!/bin/sh echo "Checking aclocal..." aclocal --version > /dev/null 2> /dev/null || { echo "error: aclocal not found" exit 1 } echo "Checking automake..." automake --version > /dev/null 2> /dev/null || { echo "error: automake not found" exit 1 } amcheck=`automake --version | grep 'automake (GNU automake) 1.5'` if test "x$amcheck" = "xautomake (GNU automake) 1.5"; then echo "warning: you appear to be using automake 1.5" echo " this version has a bug - GNUmakefile.am dependencies are not generated" fi echo "Running libtoolize..." libtoolize --force --copy || { echo "error: libtoolize failed" exit 1 } echo "Running aclocal..." aclocal || { echo "error: aclocal failed" exit 1 } echo "Running automake..." automake -a -c --foreign || { echo "warning: automake failed" exit 1 } echo "Running autoconf..." autoconf || { echo "error: autoconf failed" exit 1 } echo "Running configure..." if [ $# -eq 0 ]; then echo "" echo "... without any parameters...? Okay, here we go..." echo "" fi ./configure $*


When you're done creating all of these files, you're ready to give it your first try! In the directory that contains the Makefile.am, configure.in and autogen.sh files, execute the following and sit back:

./autogen.sh


Usually, this has to be called only once to setup your build environment. This script produces a lot of output and warnings, which can safely be ignored as long as they aren't fatal error messages. It generates the configure script mentioned earlier, and executes it at the end automatically. Any parameters you pass to the script are automatically passed to the configure script. Examples of such parameters are the aforementioned --enable-debug parameter, or the --prefix parameter which tells the script where the libraries and applications are supposed to be installed when your project is installed. For more details, lookup the autoconf and automake documentation.

When this autogen.sh is done, it will have created a lot of additional files. All of these files can be ignored for SVN. I'm not going to list them here as they are plenty. For SVN, the only files you need are: main.cpp, autogen.sh, configure.in and Makefile.am. After checking out the files from an SVN repository, you execute the autogen.sh script once and you're good to go.

At this point, all you now need to do to compile your application is to execute:

make


If all goes well, you will see output similar to the following:

[foddex@beleriand helloworld]$ make g++ -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\" -DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"helloworld\" -DVERSION=\"1.0.0\" -DSTDC_HEADERS=1 -DHAVE_SYS_TYPES_H=1 -DHAVE_SYS_STAT_H=1 -DHAVE_STDLIB_H=1 -DHAVE_STRING_H=1 -DHAVE_MEMORY_H=1 -DHAVE_STRINGS_H=1 -DHAVE_INTTYPES_H=1 -DHAVE_STDINT_H=1 -DHAVE_UNISTD_H=1 -DHAVE_DLFCN_H=1 -DLT_OBJDIR=\".libs/\" -I. -Wall -pedantic -Werror -g -O2 -DNDEBUG -O3 -Werror -MT helloworld-main.o -MD -MP -MF .deps/helloworld-main.Tpo -c -o helloworld-main.o `test -f 'main.cpp' || echo './'`main.cpp mv -f .deps/helloworld-main.Tpo .deps/helloworld-main.Po /bin/sh ./libtool --tag=CXX --mode=link g++ -Wall -pedantic -Werror -g -O2 -DNDEBUG -O3 -Werror -o helloworld helloworld-main.o libtool: link: g++ -Wall -pedantic -Werror -g -O2 -DNDEBUG -O3 -Werror -o helloworld helloworld-main.o


To run your executable, execute:

./helloworld


Note that this file is not the executable itself, but a script that runs it. To install your application so that it gets globally available, execute:

make install


If you didn't specify an alternative target path, by default this command will try to install your executable in /usr/bin or /usr/local/bin. Ofcourse you need to be root to do this.

To cleanup your build environment, and force all files to be recompiled when you type make, execute:

make clean


This deletes all compiled files. Note that if you execute "make" twice, the second time it will do nothing, since it's configured to compile only the files that have been changed. If you then update one of the source files and execute "make" again, only the changed file will be recompiled. For details on the make tool, check it's only documentation.


0 comment(s)

Name:
URL: (optional!)
Write your comment:
Answer this question to prove you're human:
What's the white stuff on top of mountains called?