Jak mogę utworzyć plik Makefile dla projektów C z podkatalogami SRC, OBJ i BIN?

95

Kilka miesięcy temu wymyśliłem następujący ogólny opis Makefilezadań szkolnych:

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2010-11-05
#
# Changelog :
#   0.01 - first version
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc -std=c99 -c
# compiling flags here
CFLAGS   = -Wall -I.

LINKER   = gcc -o
# linking flags here
LFLAGS   = -Wall

SOURCES  := $(wildcard *.c)
INCLUDES := $(wildcard *.h)
OBJECTS  := $(SOURCES:.c=*.o)
rm       = rm -f

$(TARGET): obj
    @$(LINKER) $(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

obj: $(SOURCES) $(INCLUDES)
    @$(CC) $(CFLAGS) $(SOURCES)
    @echo "Compilation complete!"

clean:
    @$(rm) $(TARGET) $(OBJECTS)
    @echo "Cleanup complete!"

To w zasadzie skompiluje wszystkie pliki .ci .h, aby wygenerować .opliki i plik wykonywalny projectnamew tym samym folderze.

Teraz chciałbym to trochę popchnąć. Jak napisać plik Makefile, aby skompilować projekt w języku C o następującej strukturze katalogów?

 ./
 ./Makefile
 ./src/*.c;*.h
 ./obj/*.o
 ./bin/<executable>

Innymi słowy, chciałbym mieć Makefile że źródła C kompiluje ./src/się ./obj/, a następnie połączyć wszystko, aby utworzyć plik wykonywalny w ./bin/.

Próbowałem czytać różne pliki Makefile, ale po prostu nie mogę zmusić ich do pracy z powyższą strukturą projektu; zamiast tego projekt nie kompiluje się z różnymi rodzajami błędów. Jasne, mógłbym użyć pełnego IDE (Monodevelop, Anjuta itp.), Ale szczerze mówiąc wolę pozostać przy gEdit i starym dobrym terminalu.

Czy jest jakiś guru, który może dać mi działające rozwiązanie lub jasne informacje o tym, jak można to zrobić? Dziękuję Ci!

** UPDATE (v4) **

Ostateczne rozwiązanie :

# ------------------------------------------------
# Generic Makefile
#
# Author: [email protected]
# Date  : 2011-08-10
#
# Changelog :
#   2010-11-05 - first version
#   2011-08-10 - added structure : sources, objects, binaries
#                thanks to http://stackoverflow.com/users/128940/beta
#   2017-04-24 - changed order of linker params
# ------------------------------------------------

# project name (generate executable with this name)
TARGET   = projectname

CC       = gcc
# compiling flags here
CFLAGS   = -std=c99 -Wall -I.

LINKER   = gcc
# linking flags here
LFLAGS   = -Wall -I. -lm

# change these to proper directories where each file should be
SRCDIR   = src
OBJDIR   = obj
BINDIR   = bin

SOURCES  := $(wildcard $(SRCDIR)/*.c)
INCLUDES := $(wildcard $(SRCDIR)/*.h)
OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)
rm       = rm -f


$(BINDIR)/$(TARGET): $(OBJECTS)
    @$(LINKER) $(OBJECTS) $(LFLAGS) -o $@
    @echo "Linking complete!"

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    @$(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

.PHONY: clean
clean:
    @$(rm) $(OBJECTS)
    @echo "Cleanup complete!"

.PHONY: remove
remove: clean
    @$(rm) $(BINDIR)/$(TARGET)
    @echo "Executable removed!"
Yanick Rochon
źródło
Jakie jest tutaj konkretne pytanie?
Oliver Charlesworth
Nie jestem pewien, czy rozumiem, co chcesz zrobić.
Tom,
Zaktualizowano Makefile. Zbliżam się, ale mam problem ze zmiennymi automatycznymi, więc wydaje mi się, że tak czy inaczej
Yanick Rochon
Właśnie znalazłem rozwiązanie. Jeśli ktoś chce znaleźć coś lepszego, nadal można ulepszyć Makefile.
Yanick Rochon
2
@YanickRochon Nie chciałem krytykować twoich umiejętności językowych. Ale żeby cele PHONY miały jakikolwiek sens, zdecydowanie nie możesz pisać BANANA;) gnu.org/software/make/manual/html_node/Phony-Targets.html
joni

Odpowiedzi:

34

Po pierwsze, twoja $(OBJECTS)reguła jest problematyczna, ponieważ:

  1. to trochę bezkrytyczne, stawiając wszystkie źródła jako warunki wstępne każdego obiektu,
  2. często korzysta z niewłaściwego źródła (jak odkryłeś z file1.oi file2.o)
  3. próbuje budować pliki wykonywalne zamiast zatrzymywać się na obiektach, a
  4. nazwa target ( foo.o) nie jest tym, co reguła faktycznie wyprodukuje ( obj/foo.o).

Proponuję co następuje:

OBJECTS  := $(SOURCES:$(SRCDIR)/%.c=$(OBJDIR)/%.o)

$(OBJECTS): $(OBJDIR)/%.o : $(SRCDIR)/%.c
    $(CC) $(CFLAGS) -c $< -o $@
    @echo "Compiled "$<" successfully!"

$(TARGET)Reguła ma ten sam problem, że nazwa docelowa faktycznie nie opisać to, co buduje się regułą. Z tego powodu, jeśli wpiszesz makekilka razy, Make za każdym razem odbuduje cel, nawet jeśli nie ma powodu. Mała zmiana naprawia to:

$(BINDIR)/$(TARGET): $(OBJECTS)
    $(LINKER) $@ $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Kiedy wszystko jest w porządku, możesz rozważyć bardziej wyrafinowaną obsługę zależności; jeśli zmodyfikujesz jeden z plików nagłówkowych, ten plik makefile nie będzie wiedział, które obiekty / pliki wykonywalne muszą zostać odbudowane. Ale to może poczekać na kolejny dzień.

EDYCJA:
Przepraszam, pominąłem część $(OBJECTS)powyższej reguły; Poprawiłem to. (Chciałbym móc użyć słowa „strike” w próbce kodu).

Beta
źródło
z twoimi sugerowanymi zmianami, otrzymuję:obj/file1.o: In function 'main': \n main.c:(.text+0x0): multiple definition of 'main' \n obj/main.o:main.c:(.text+0x0): first defined here
Yanick Rochon
@Yanick Rochon: Czy masz wiele mainfunkcji? Może jeden w file1.ci jeden w main.c? Jeśli tak, to nie będziesz w stanie połączyć tych obiektów; mainplik wykonywalny może zawierać tylko jeden plik.
Beta
Nie ja nie. Wszystko działa dobrze z ostatnią wersją, którą zamieściłem w pytaniu. Kiedy zmieniam mój plik Makefile na to, co sugerujesz (i rozumiem korzyści z tego, co mówisz), to właśnie otrzymuję. Właśnie wkleiłem, file1.cale daje to ten sam komunikat do wszystkich plików projektu. I main.cjako jedyny ma główną funkcję ... i main.cimportuje file1.hi file2.h(nie ma związku między file1.ci file2.c), ale wątpię, że problem bierze się stąd.
Yanick Rochon
@Yanick Rochon: Popełniłem błąd, wklejając pierwszą linię mojej $(OBJECTS)reguły; Edytowałem to. Ze złą linią mam błąd, ale nie ten, który masz ...
Beta
6

Możesz dodać -Iflagę do flag kompilatora (CFLAGS), aby wskazać, gdzie kompilator powinien szukać plików źródłowych, oraz flagę -o, aby wskazać, gdzie powinien pozostać plik binarny:

CFLAGS   = -Wall -I./src
TARGETPATH = ./bin

$(TARGET): obj
    @$(LINKER) $(TARGETPATH)/$(TARGET) $(LFLAGS) $(OBJECTS)
    @echo "Linking complete!"

Aby upuścić pliki obiektowe do objkatalogu, użyj -oopcji podczas kompilacji. Spójrz także na zmienne automatyczne$@ i .$<

Na przykład rozważmy ten prosty plik Makefile

CFLAGS= -g -Wall -O3                                                            
OBJDIR= ./obj

SRCS=$(wildcard *.c)
OBJS=$(SRCS:.c=.o )
all:$(OBJS)

%.o: %.c 
   $(CC) $(CFLAGS) -c $< -o $(OBJDIR)/$@

Aktualizuj>

Patrząc na twój plik makefile, zdaję sobie sprawę, że używasz -oflagi. Dobry. Kontynuuj używanie, ale dodaj zmienną katalogu docelowego, aby wskazać, gdzie powinien zostać zapisany plik wyjściowy.

Tomek
źródło
możesz być bardziej dokładny? Czy masz na myśli dodanie -l ...do CFLAGSi ... jest już -oargument do linkera ( LINKER)
Yanick Rochon
Tak, CFLAGS i tak, nadal używaj -o, po prostu dodaj zmienną TARGETPATH.
Tom
Dziękuję, dokonałem modyfikacji, ale wydaje mi się, że wciąż czegoś mi brakuje (zobacz aktualizację pytania)
Yanick Rochon
właśnie makez miejsca, w którym znajduje się
plik
Nie możesz odczytać wykonywanej komendy? na przykład gcc -c yadayada. Jestem całkiem pewien, że istnieje zmienna, która nie zawiera tego, czego się spodziewasz
Tom
-1

W dzisiejszych czasach przestałem pisać makefile, jeśli zamierzasz się uczyć, śmiało, w przeciwnym razie masz dobry generator makefile, który jest dostarczany z eclipse CDT. Jeśli chcesz mieć możliwość utrzymania / obsługę wielu projektów w swoim drzewie kompilacji, spójrz na następujące -

https://github.com/dmoulding/boilermake Znalazłem to całkiem nieźle ...!

Kamath
źródło
3
Oparte na opinii. Nie odpowiada na pytanie OP. Zakłada środowisko Eclipse.
Nathaniel Johnson