0
votes

My C++ program consists of three files:

  • two source files 'main.cpp' and 'hellolib.cpp'
  • a header file 'hellolib.h'

I am creating a makefile for this program. For my assignment I need one target ('hello') that compiles all source files in an executable. Another target ('obj') should compile all '.cpp' files into objects and link them together in an executable.

When running make I prefer the object files to be created in a seperate folder called 'bin'. The source files are would be in a folder called 'src'. These folders are siblings, the makefile is in it's parent folder.

My makefile works fine but I wish two combine the two targets 'bin/main.o' and 'bin/hellolib.o' into one to reduce the amount of rules, especially for later when I am dealing with more source files. I imagined the replacement would look something like this, but it doesn't seem to work.

It gives me the error: "*** No rule ot make target 'bin/main.o', needed by 'obj'. Stop.

bin/%.o : src/%.cpp
    $(CC) -c $< -o $@

Working Makefile:

CC      = g++
SOURCES = ./src/main.cpp \
          ./src/hellolib.cpp
OBJECTS = ./bin/main.o \
          ./bin/hellolib.o

hello : $(SOURCES)
    $(CC) -o $@ $^

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

bin/main.o : src/main.cpp
    $(CC) -c $< -o $@

bin/hellolib.o : src/hellolib.cpp
    $(CC) -c $< -o $@

clean:
    @rm -rf hello obj bin/*.o

main.cpp:

#include "hellolib.h"

int main() {
    Hello h("Name");
    h.Print();
    return 0;
}

hellolib.cpp

#include "hellolib.h"

#include <iostream>

Hello::Hello(std::string name) {
    if (name.empty()) {
        cout << "Name is not valid!";
        return;
    }
    _name = name;
}

void Hello::Print() {
    cout << "Hello " << _name << endl;
}

hellolib.h

#ifndef HELLO_LIB_H
#define HELLO_LIB_H

#include <string>
using namespace std;

class Hello {
    std::string _name;
    public:
        Hello(std::string name);
        void Print();
};

#endif
2

2 Answers

0
votes

You need to change:

OBJECTS = ./bin/main.o \
          ./bin/hellolib.o

to:

OBJECTS = bin/main.o \
          bin/hellolib.o

(Removing leading "./"). Either that, or change your pattern rule to include the leading "./":

./bin/%.o : src/%.cpp
        $(CC) -c $< -o $@

Make rule matching uses text matching. It's not based on filenames, so "./././foo" and "foo" are not the same thing.

Personally I recommend rewriting like this:

SOURCES = src/main.cpp \
          src/hellolib.cpp

OBJECTS = $(patsubst src/%.cpp,bin/%.o,$(SOURCES))

so you only need to keep the list of files in one place.

0
votes

You can make a rule that builds anything conforming to a specific pattern like this:

bin/%.o : src/%.cpp
    $(CC) -c -o $@ $<

That will compile any bin/%.o dependency from the corresponding source src/%.cpp.

Also it is standard when compiling C++ to use CXX rather than CC (which is for C code).