Make/CVS example 1

From Wiki**3

THIS PAGE IS OBSOLETE

The Problem (in Portuguese)

Considere quatro ficheiros recurs.cpp (contendo a função factrecurs), recurs.h (contendo a declaração da função factrecurs), iter.cpp (contendo a função factiter ), iter.h (contendo a declaração da função factiter) e main.cpp (contendo a função main que invoca sequencialmente ambas as funções anteriores e que inclui recurs.h e iter.h).

  1. Construa, com estes cinco ficheiros, um projecto designado factorial, indicando os comandos para o colocar sob o controlo de versões do CVS. Admita que o repositório já existe em /cvs.
  2. Construa uma Makefile para gerar o executável fact, a partir dos ficheiros anteriores. Indique os comandos necessários para colocar a Makefile no repositório CVS.
  3. Considere agora que vai ter dois executáveis, cada um invocando a sua função. Indique as alterações à Makefile para que passe a gerar os executáveis recurs e iter, utilizando ambos o mesmo ficheiro main.cpp (mas diferentes implementações do factorial). Indique as operações do CVS para criar a nova versão do projecto, assumindo que o projecto está checked-out no directório corrente.
  4. Considere uma nova versão do projecto: pretende-se construir um único executável fact, mas a escolha da implementação do factorial é efectuada em tempo de execução. Considere que os ficheiros recurs.cpp e recurs.h existem num subdirectório recurs e, analogamente, existe um directório iter para a outra versão. Cada um dos conjuntos apresenta agora a mesma interface para a função que calcula o factorial: fact. Construa duas bibliotecas dinâmicas, ambas designadas por libfact.so, cada uma em seu directório (recurs/libfact.so e iter/libfact.so). A selecção da variante a utilizar faz-se definindo a variável de ambiente LD_LIBRARY_PATH com o directório apropriado. Indique quais os ficheiros iniciais modificados, face à alínea anterior, a nova Makefile e os comandos CVS necessários para que o repositório passe a conter a nova versão.
  5. Modifique a alínea anterior para que o utilizador possa escolher a variante a utilizar através de um argumento na linha do comando fact, respectivamente recurs ou iter. Indique quais os ficheiros iniciais modificados, a nova Makefile e os comandos CVS necessários para registar a nova versão.

Implementation

The following sections present possible implementations.

All shell commands are prefixed with prompt% (their output is presented for explanation purposes only and is not required to answer the problem).

It is assumed that the CVSROOT variable has been defined: <bash>

 export CVSROOT=/cvs

</bash>

Step 1: Building the Project

Assuming that all files are in directory /some/temporary/directory, the following commands may be used to create the factorial project ().

 prompt% cd /some/temporary/directory
 prompt% cvs import factorial david initial
 N factorial/recurs.cpp
 N factorial/recurs.h
 N factorial/iter.cpp
 N factorial/iter.h
 N factorial/main.cpp

Then, after import is complete, we can check-out a fresh copy of the project and change to that directory:

 prompt% cvs co factorial
 U factorial/iter.cpp
 U factorial/iter.h
 U factorial/main.cpp
 U factorial/recurs.cpp
 U factorial/recurs.h
 prompt% cd factorial

Step 2: Building the Makefile

The following is a simple Makefile for building the executable. <text> fact: main.o iter.o recurs.o

recurs.o: recurs.cpp

       g++ -c recurs.cpp

iter.o: iter.cpp

       g++ -c iter.cpp

main.o: main.cpp iter.h recurs.h

       g++ -c main.cpp

</text>

The following is a slightly more complex Makefile for doing exactly the same thing. The possible advanytage of using this version is that it automates a few error-prone tasks (such as keeping dependencies up to date). <text> .PHONY: all depend

CXXFILES = $(wildcard *.cpp) CXXHEADS = $(wildcard *.h) OFILES = $(CXXFILES:%.cpp=%.o) PROG = fact

%.o: %.cpp

       $(CXX) $(CXXFLAGS) -c $< -o $@

all: depend $(PROG)

$(PROG): $(OFILES)

       $(CXX) -o $@ $^

depend: .makedeps

.makedeps: $(CXXFILES) $(CXXHEADS)

       $(CXX) $(CXXFLAGS) -MM $(CXXFILES) > .makedeps

-include .makedeps </text>

.PHONY indicates targets that do not correspond to actual files and attempts should not be made to build them (e.g. from implicit rules). all depends on depend to force the Makefile to keep its dependencies updated without explicitly calling "make depend".

Finally, to put the Makefile under CVS control, the following two commands would be needed:

 prompt% cvs add Makefile
 cvs add: use `cvs commit' to add this file permanently
 prompt% cvs commit
 /cvs/factorial/Makefile,v  <--  Makefile
 initial revision: 1.1

Step 3: The New Makefile

The following Makefile (a straightforward adaptation of the previous one) would solve the problem: <text> .PHONY: all depend

CXXFILES = $(wildcard *.cpp) CXXHEADS = $(wildcard *.h) ITER = iter RECURS = recurs

%.o: %.cpp

       $(CXX) $(CXXFLAGS) -c $< -o $@

all: depend $(ITER) $(RECURS)

$(ITER): main.o iter.o

       $(CXX) -o $@ $^

$(RECURS): main.o recurs.o

       $(CXX) -o $@ $^

depend: .makedeps

.makedeps: $(CXXFILES) $(CXXHEADS)

       $(CXX) $(CXXFLAGS) -MM $(CXXFILES) > .makedeps

-include .makedeps </text>

Note that we are assuming that the appropriate changes have been made to the code in the C++ files to allow for a single interface (the function is now called simply "fact") and multiple implementations.

Regarding CVS, only updates and commits are needed at this stage (new versions of the files will be sent to the repository).

Step 4: The New Project Organization

In this case, we will need to reorganize the project. Since CVS does not support changes in file names or locations we will have to remove them from the old location and add them from the new location.

 prompt% mkdir iter recurs
 prompt% mv iter.cpp iter.h iter
 prompt% mv recurs.cpp recurs.h recurs
 prompt% cvs remove iter.cpp iter.h recurs.cpp recurs.h
 cvs remove: use `cvs commit' to remove these files permanently
 prompt% cvs add iter recurs
 Directory /cvs/factorial/iter added to the repository
 Directory /cvs/factorial/recurs added to the repository
 prompt% cvs add iter/iter.cpp iter/iter.h recurs/recurs.cpp recurs/recurs.h
 cvs add: use `cvs commit' to add these files permanently
 

After updating the main file and the Makefile, the project's status is as follows:

 prompt% cvs update
 M Makefile
 R iter.cpp
 R iter.h
 M main.cpp
 R recurs.cpp
 R recurs.h
 A iter/iter.cpp
 A iter/iter.h
 A recurs/recurs.cpp
 A recurs/recurs.h

The contents of the Makefile are as follows (we link with the "iter" version, but this is actually irrelevant, since the choice will always be dependent on LD_LIBRARY_PATH. Note the use of position-independent code, so that dynamic libraries do not cause problems (the linker will refuse to create the libraries if the code in the .o files is not position independent): <text> .PHONY: all depend

CXXFLAGS = -fPIC # use position-independent code CXXFILES = $(wildcard *.cpp) $(wildcard iter/*.cpp) $(wildcard recurs/*.cpp) CXXHEADS = $(wildcard *.h) $(wildcard iter/*.h) $(wildcard recurs/*.h) LIBITER = iter/libfact.so LIBRECURS = recurs/libfact.so PROG = fact

%.o: %.cpp

       $(CXX) $(CXXFLAGS) -c $< -o $@

all: depend $(LIBITER) $(LIBRECURS) $(PROG)

$(PROG): main.o

       $(CXX) -o $@ $^ -Liter -lfact

$(LIBITER): iter/iter.o

       $(CXX) -shared -o $@ $^

$(LIBRECURS): recurs/recurs.o

       $(CXX) -shared -o $@ $^

depend: .makedeps

.makedeps: $(CXXFILES) $(CXXHEADS)

       $(CXX) $(CXXFLAGS) -MM $(CXXFILES) > .makedeps

-include .makedeps </text>

Inpecting the produced executable (in a Linux machine), without specifying a value for LD_LIBRARY_PATH and specifying the two possibilities:

 prompt% ldd fact
       libfact.so => not found
       libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00002b5a6869c000)
       libm.so.6 => /lib64/libm.so.6 (0x00002b5a689a4000)
       libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002b5a68bf8000)
       libc.so.6 => /lib64/libc.so.6 (0x00002b5a68e06000)
       /lib64/ld-linux-x86-64.so.2 (0x00002b5a6847f000)
 prompt% LD_LIBRARY_PATH=iter ldd fact
       libfact.so => iter/libfact.so (0x00002b804d64e000)
       libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00002b804d87e000)
       libm.so.6 => /lib64/libm.so.6 (0x00002b804db86000)
       libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002b804ddd9000)
       libc.so.6 => /lib64/libc.so.6 (0x00002b804dfe8000)
       /lib64/ld-linux-x86-64.so.2 (0x00002b804d431000)
 prompt% LD_LIBRARY_PATH=recurs ldd fact
       libfact.so => recurs/libfact.so (0x00002b553e03e000)
       libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00002b553e26e000)
       libm.so.6 => /lib64/libm.so.6 (0x00002b553e576000)
       libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00002b553e7c9000)
       libc.so.6 => /lib64/libc.so.6 (0x00002b553e9d8000)
       /lib64/ld-linux-x86-64.so.2 (0x00002b553de21000)

Step 5: User Control on Dynamic Linking

In this case, the main file is not linked with either of the two libraries. These are, instead explicitly open by the user's code and the appropriate symbol is called.

Note that we could have left the choice to LD_LIBRARY_PATH instead of providing an argument to the main program.

This is the new main.cpp file (note the name of the symbol corresponding to the "fact" function name in C++). A better style would perhaps be to use the extern "C" declaration. <cpp>

  1. include <iostream>
  2. include <dlfcn.h>

static void usage(const char *myname) {

 std::cerr << "Usage: " << myname << " (iter|recurs)" << std::endl;
 exit(1);

}

int main(int argc, char **argv) {

 if (argc != 2) usage(argv[0]);
 void *handle;
 if (!strcmp(argv[1], "iter")) handle = dlopen("iter/libfact.so", RTLD_LAZY);
 else if (!strcmp(argv[1], "recurs")) handle = dlopen("recurs/libfact.so", RTLD_LAZY);
 else usage(argv[0]);
 void *fun = dlsym(handle, "_Z4facti");   // this is "fact" in C++
 // cast void pointer into the appropriate function pointer
 ((int(*)(int))fun)(5);  // call function for 5!

} </cpp>

The Makefile is the following: <text> .PHONY: all depend

CXXFLAGS = -fPIC # use position independent code CXXFILES = $(wildcard *.cpp) $(wildcard iter/*.cpp) $(wildcard recurs/*.cpp) CXXHEADS = $(wildcard *.h) $(wildcard iter/*.h) $(wildcard recurs/*.h) LIBITER = iter/libfact.so LIBRECURS = recurs/libfact.so PROG = fact

%.o: %.cpp

       $(CXX) $(CXXFLAGS) -c $< -o $@

all: depend $(LIBITER) $(LIBRECURS) $(PROG)

$(PROG): main.o

       $(CXX) -o $@ $^ -ldl

$(LIBITER): iter/iter.o

       $(CXX) -shared -o $@ $^

$(LIBRECURS): recurs/recurs.o

       $(CXX) -shared -o $@ $^

depend: .makedeps

.makedeps: $(CXXFILES) $(CXXHEADS)

       $(CXX) $(CXXFLAGS) -MM $(CXXFILES) > .makedeps

-include .makedeps </text> Regarding CVS, only the Makefile and the main file had to be changed: all there is to do is updating and commiting the changes:

 prompt% cvs update
 M Makefile
 M main.cpp
 prompt% cvs commit
 /cvs/factorial/Makefile,v  <--  Makefile
 new revision: 1.2; previous revision: 1.1
 /cvs/factorial/main.cpp,v  <--  main.cpp
 new revision: 1.3; previous revision: 1.2