LinuxQuestions.org

LinuxQuestions.org (/questions/)
-   Programming (https://www.linuxquestions.org/questions/programming-9/)
-   -   Makefiles and subdirectories (https://www.linuxquestions.org/questions/programming-9/makefiles-and-subdirectories-794088/)

Jerry Mcguire 03-08-2010 08:57 PM

Makefiles and subdirectories
 
Hi all, I have questions about how to make good use of makefile.

I've used the very basics of a Makefile before such as this:
Code:

SHELL=/bin/bash

all: sts2msw.exe

sts2msw.exe: sts2msw.o sts2msw_cls.o
        g++ -o $@ $^
sts2msw.o: sts2msw.cc sts2msw_cls.h
...

It works fine when the Makefile, the source codes, and the output executables are in the same directory.

If I want to organize the files into subdirectories like this

~\MyProj\
~\MyProj\src\ <- contains the *.cc *.c *.h and Makefile
~\MyProj\bin\ <- contains the output exe's.

How should the above Makefile be changed?

Also, any suggestions about where to store the intermediate *.o files?

Thank you.

grail 03-09-2010 12:56 AM

How about:

Code:

TOPDIR := ~\MyProj
SRCDIR := $(TOPDIR)\src
BINDIR := $(TOPDIR)\bin

SOURCES := $(shell find $(SRCDIR) -name "*.cc") // obviously change this if only c files
OBJECTS := $(patsubst %.c, %.o, $(SOURCES))

CC := g++ // again change to gcc if required

ALLFILES := $(SOURCES) // add headers and the like if required

.PHONY: all

all: sts2msw

sts2msw: $(OBJECTS)
    $(CC) $^ -o $(BINDIR)/$@


YetAnotherDave 03-09-2010 01:18 AM

How about this for a makefile:

Code:

INTERMEDIATE_DIR=../intermediates
OFILES=$(foreach bname, $(basename $(CFILES)), $(INTERMEDIATE_DIR)/$(bname).o)

CFILES=sts2msw.cc sts2msw_cls.cc
EFILE=../bin/sts2ms2.exe

all: $(EFILE)

$(INTERMEDIATE_DIR)/%.o: %.cc
        g++ -c -o $@ $<

$(EFILE): $(OFILES)
        g++ -o $@ $^

I think it does most of what you want.

For dependencies based on include files, see : Advanced Auto-Dependency Generation

grail 03-09-2010 09:35 AM

Well I know I already answered, but I think I got something a bit more robust: (happy for some comments on what I could improve)

Code:

TOPDIR  := ~/MyProj/
SRCDIR  := $(TOPDIR)src/
OBJDIR  := $(TOPDIR)obj/
BINDIR  := $(TOPDIR)bin/
NAME    := sts2msw
EXE    := $(BINDIR)$(NAME)

SFILES  := cc
OFILES  := o
CC      := g++
CFLAGS  := -c -Wall

SOURCES := $(shell find $(SRCDIR) -name "*.$(SFILES)")
OBJECTS := $(patsubst $(SRCDIR)%.$(SFILES), $(OBJDIR)%.$(OFILES), $(SOURCES))

ALLFILES := $(SOURCES)

.PHONY: all clean

all:    $(EXE)

$(EXE): $(OBJECTS)
        @$(CC) $^ -o $@

$(OBJDIR)%$(OFILES):    $(SRCDIR)%$(SFILES)
        @$(CC) $(CFLAGS) $< -o $@

clean:
        @rm -f $(OBJECTS) $(EXE)


YetAnotherDave 03-09-2010 11:34 AM

Nice!

A couple of comments:

If you use a "~" to define TOPDIR, then the shell translates that to a path when you do the find resulting in SOURCES containing full pathnames that don't contain the "~". Then the patsubst command does not match SRCDIR because it *does* contain a "~". This problem goes away if you define TOPDIR as $(HOME)/MyProj/ instead of ~/Myproj/

I like to explicitly list all the sources that should be combined into an executable rather than using find or some other shell command to get them. This way you don't have to worry about some temporary copy of a source file getting pulled into your executable. You also don't pay the price of invoking a shell *every* time you run make, even if you don't even need the list of sources. That's just a personal preference though.

Here's a slightly modified version of your makefile that addresses these two points:

Code:

TOPDIR  := $(HOME)/MyProj/
SRCDIR  := $(TOPDIR)src/
OBJDIR  := $(TOPDIR)obj/
BINDIR  := $(TOPDIR)bin/
NAME    := sts2msw
CC_NAMES := sts2msw.cc  sts2msw_cls.cc
EXE      := $(BINDIR)$(NAME)
all: exe

# Everything below this point is generic and could be put into a common
# make include file to be included in other makefiles.

SFILES  := cc
OFILES  := o
CC      := g++
CFLAGS  := -c -Wall

SOURCES := $(foreach sname, $(CC_NAMES), $(SRCDIR)$(sname))
OBJECTS := $(patsubst $(SRCDIR)%.$(SFILES), $(OBJDIR)%.$(OFILES), $(SOURCES))

ALLFILES := $(SOURCES)

.PHONY: all clean exe

exe:    $(EXE)

$(EXE): $(OBJECTS)
                $(CC) $^ -o $@

$(OBJDIR)%$(OFILES):    $(SRCDIR)%$(SFILES)
                $(CC) $(CFLAGS) $< -o $@

clean:
                rm -f $(OBJECTS) $(EXE


Jerry Mcguire 03-10-2010 12:58 AM

Thanks for replying.

I see that the approach is to resolve the files into their fullpath and name using variables and macros.

e.g.
originally
Code:

a.exe: a.o c.o common.o
        g++ -o $@ $^
b.exe: b.o c.o
        g++ -o $@ $^

a.o: a.cc a.h common.h
b.o: b.cc b.h common.h
c.o: c.cc c.h
common.o: common.cc common.h

may become
Code:

SRCDIR := ./
OBJDR := ../obj/
BINDIR := ../bin/

$(BINDIR)a.exe: $(OBJDR)a.o $(OBJDR)c.o $(OBJDR)common.o
        g++ -o $@ $^
$(BINDIR)b.exe: $(OBJDR)b.o $(OBJDR)c.o
        g++ -o $@ $^

$(OBJDR)a.o: $(SRCDIR)a.cc $(SRCDIR)a.h $(SRCDIR)common.h
$(OBJDR)b.o: $(SRCDIR)b.cc $(SRCDIR)b.h $(SRCDIR)common.h
$(OBJDR)c.o: $(SRCDIR)c.cc $(SRCDIR)c.h
$(OBJDR)common.o: $(SRCDIR)common.cc $(SRCDIR)common.h

It is quite annoying if there are a dozen executables in the project and a hundred sources and headers. I don't think I'm alone out here. Any suggestions to simplify?

What I am looking for, is if there are techniques that can tell MAKE the *.o are located in $(OBJDR), and *.cc *.h are in $(SRCDIR), and *.exe in $(BINDIR), and the like?

grail 03-10-2010 10:02 AM

Ok Jerry ... (I hope you appreciate this as it took me a lot more hours than it probably should have (mainly cause I am doing this for the first time inages ;)))

Anyhoo, here we go:

1. Under the 'src' directory, place all your files in the respective directories named after the executable (ie. src/a, src/b, etc)
2. In each of these place their own Makefile (good part here is that these are all identical files, so make 1 copy/paste)
This file looks like:

Code:

TOPDIR  := $(HOME)
OBJDIR  := $(TOPDIR)obj/
BINDIR  := $(TOPDIR)bin/
NAME    := $(notdir $(SRCNAME)) //Magic happens here
EXE    := $(BINDIR)$(NAME)

SFILES  := cc
OFILES  := o
CC      := g++
CFLAGS  := -c -Wall

SOURCES := $(wildcard *.$(SFILES))
OBJECTS := $(patsubst %$(SFILES), $(OBJDIR)%$(OFILES), $(SOURCES))

ALLFILES := $(SOURCES)

.PHONY: all clean test

all:    $(EXE)

$(EXE): $(OBJECTS)
        @$(CC) $^ -o $@

$(OBJDIR)%$(OFILES):    %$(SFILES)
        @$(CC) $(CFLAGS) $< -o $@

3. Place the following Makefile in the top level dir (ie where src, obj and bin are):

Code:

ALLSOURCES := $(wildcard src/*)

.PHONY:        sources $(ALLSOURCES)

sources:        $(ALLSOURCES)

$(ALLSOURCES):
        export SRCNAME=$(notdir $@); \
        $(MAKE) -C $@

I would lay odds there is a better way, but hey ... this worked to compile a couple of different executables for me :)

Jerry Mcguire 03-11-2010 08:58 PM

Thank you for spending time helping. But it doesn't work yet. Where do you put the common sources?

The tree is like like now
Code:

.
|-- Makefile
|-- bin
|-- obj
|-- src
|  |-- common.cc
|  |-- common.h
|  |-- a
|  |  |-- makefile
|  |  |-- a.cc
|  |  |-- a1.cc
|  |  |-- a1.h
|  |  |-- a2.cc
|  |  `-- a2.h
|  `-- b
|      |-- makefile
|      |-- b.cc
|      |-- b1.cc
|      `-- b1.h
`-- tree.txt

Note the Makefile and makefile's are different for easy reference.

The TOPDIR in makefile has been modified to
TOPDIR := /home/jerry/dev/

At the $TOPDIR, 'make' gives the following error:
Code:

$ make
export SRCNAME=common.cc; \
        make -C /home/jerry/dev/src/common.cc
make: *** /home/jerry/dev/src/common.cc: Not a directory.  Stop.
make: *** [/home/jerry/dev/src/common.cc] Error 2

---

but I think I understand what you are doing. I don't know if I make any sense to you...

Basically, organise the unique source files in a subdirectories and the common source files in the upper level directories. Have the make program traverse to the bottom level subdir so that the dependencies are built in the way.

I will be back with a solution!

YetAnotherDave 03-11-2010 09:34 PM

Jerry, is your intention to build three different executables, one for directory a, one for b, and one for src? Or do you want to build just one executable that combines code from all three directories?

Never mind. You answered that in your post #6 on this thread.

grail 03-12-2010 03:38 AM

Hey Jerry

I see where you are coming from, and again I am still wrapping my own head around some of this stuff, but,
based on the new criteria of "common" files, if they are not creating an executable (ie common.exe)
then I would place them at the top level (ie that is in the TOPDIR) the put COMMON := <you know the rest>
in the top Makefile and export prior to call to compile the rest, this will then get past to all subsequent
calls to make and you can simply combine with SOURCES.

Hmmm ... thinking as I am typing, the other alternative is to place them in SRC directory and have the ALLSOURCES
script look only for items without a suffix, but then in the Makefiles in the sources directories do an include
of all files under dir name (ie src/a) as well as under SRC.

btw. the site that has helped me enormously is the following:

http://www.gnu.org/software/make/manual/make.html

and this part should help with some of the above I have explained:

http://www.gnu.org/software/make/man...Name-Functions

Jerry Mcguire 03-14-2010 10:01 PM

I think the issue is at the rule definition:

target : dependencies

Although dependencies can be searched through VPATH or vpath, one must clearly spell out where to find and put target. Thus,

Code:

VPATH := $(SRCDIR):$(OBJDIR)  #bad example anyway
$(BINDIR)/a.exe : a.o a1.o a2.o common.o
$(BINDIR)/b.exe : b.o b1.o common.o
$(OBJDIR)/a.o : a.cc common.h
$(OBJDIR)/a1.o : a1.cc a1.h
$(OBJDIR)/a2.o : a2.cc a2.h
...

I would be nice if make can just resolve the location of the file by its suffix:

Code:

resolve %.exe,$(BINDIR)  # my humble wish only
resolve %.o,$(OBJDIR)  # my humble wish only
a.exe : a.o a1.o a2.o common.o
b.exe : b.o b1.o common.o
a.o : a.cc common.h
a1.o : a1.cc a1.h
a2.o : a2.cc a2.h
...


YetAnotherDave 03-16-2010 08:07 PM

Hi Jerry,

I don't know if this is still of interest or not.

That said, here are makefiles that should do most of what you have in mind. Include file dependencies are not provided but that can be retrofitted.

At the top level : makefile.include
Code:

OBJDIR  := $(TOPDIR)/obj
BINDIR  := $(TOPDIR)/bin

SFILES  := cc
OFILES  := o
CC      := g++
CFLAGS  := -c -Wall

EXE      := $(BINDIR)/$(EXE_NAME)

SOURCES := $(foreach sname, $(CC_NAMES), $(abspath $(sname)))
OBJECTS := $(patsubst $(SRCDIR)/%.$(SFILES), $(OBJDIR)/%.$(OFILES), $(SOURCES))

.PHONY: all alldefault clean exe

exe:    $(EXE)

$(EXE): $(OBJECTS)
        mkdir -p $(dir $@)
        $(CC) $^ -o $@

$(OBJDIR)/%$(OFILES):    $(SRCDIR)/%$(SFILES)
        mkdir -p $(dir $@)
        $(CC) $(CFLAGS) $< -o $@

clean:
        rm -f $(OBJECTS) $(EXE)

In the "src/a" directory: makefile
Code:

TOPDIR  := $(HOME)/MyProj
EXE_NAME := a
CC_NAMES := a.cc a1.cc a2.cc $(TOPDIR)/src/common.cc

include $(TOPDIR)/makefile.include

In the "src/b" directory: makefile
Code:

TOPDIR  := $(HOME)/MyProj
EXE_NAME := b
CC_NAMES := b.cc b1.cc ../common.cc

include $(TOPDIR)/makefile.include

So, in summary, to build an executable, specify TOPDIR, EXE_NAME, and CC_NAMES and include makefile.include.

Note that in "src/a/makefile" the common.cc file is absolute path and in "src/b/makefile" the same file is specified via relative path. Either one works.

There's room for a lot of polishing here - I kept it minimal for ease of understanding.

In particular, if you want to do something with include file dependencies this is a good reference: Advanced Auto-Dependency Generation


All times are GMT -5. The time now is 02:39 AM.