Cách tạo một SIMPLE C ++ Makefile


303

Chúng tôi được yêu cầu sử dụng Makefile để kết nối mọi thứ lại với nhau cho dự án của chúng tôi, nhưng giáo sư của chúng tôi không bao giờ chỉ cho chúng tôi cách làm.

Tôi chỉ có một tập tin a3driver.cpp. Trình điều khiển nhập một lớp từ một vị trí "/user/cse232/Examples/example32.sequence.cpp",.

Đó là nó. Mọi thứ khác được chứa trong .cpp.

Làm thế nào tôi có thể tạo một Makefile đơn giản tạo ra một tệp thực thi được gọi là a3a.exe?


9
.EXE vì vậy chắc chắn Windows của nó. Suy nghĩ thứ hai ... đường dẫn là kiểu Unix. Có lẽ sử dụng Mingw-32.
Nathan Osman

2
Thở dài. Tôi cho rằng bạn phải học cơ bản của mọi giao dịch, ngay cả khi bạn sẽ không bao giờ sử dụng chúng. Chỉ cần hiểu làm thế nào công cụ hoạt động. Tuy nhiên, rất có thể bạn sẽ luôn phát triển trong một IDE, như Eclipse. Bạn sẽ nhận được câu trả lời ở đây cho trường hợp một dòng đơn giản của mình và có rất nhiều hướng dẫn trên web, nhưng nếu bạn muốn có kiến ​​thức chuyên sâu, bạn không thể đánh bại cuốn sách O'reilly (tương tự cho hầu hết các chủ đề của s / w). amazon.com/Managing-Projects-Make-Nutshell-Handbooks/dp/ Đổi Chọn một bản sao cũ từ amazon, Half.com, bestworldbooks eBay
Mawg nói rằng phục hồi Monica

2
Liên kết được đăng bởi @Dennis hiện đã chết, nhưng tài liệu tương tự có thể được tìm thấy trong trang archive.org này .
Guilherme Salomé

Tôi thích ý tưởng của người này. ( hiltmon.com/blog/2013/07/03/ trên ) Cấu trúc dự án có thể dễ dàng sửa đổi cho phù hợp. Và tôi cũng đồng ý rằng thời gian dành cho nhà phát triển nên dành cho những thứ khác ngoài automake / autoconf. Những công cụ này có vị trí của chúng, nhưng có lẽ không dành cho các dự án nội bộ. Tôi đang xây dựng một kịch bản sẽ tạo ra một cấu trúc dự án như vậy.
Daisuke Aramaki

@ GuilhermeSalomé Cảm ơn, tôi tin rằng đây là hướng dẫn đơn giản và đầy đủ nhất.
Hareen Laks

Câu trả lời:


560

Vì đây là cho Unix, nên các tệp thực thi không có bất kỳ phần mở rộng nào.

Một điều cần lưu ý là root-configmột tiện ích cung cấp các cờ biên dịch và liên kết đúng; và các thư viện phù hợp để xây dựng các ứng dụng chống lại root. Đó chỉ là một chi tiết liên quan đến khán giả ban đầu cho tài liệu này.

Làm em yêu

hoặc bạn không bao giờ quên lần đầu tiên bạn thực hiện

Một cuộc thảo luận giới thiệu về make, và cách viết một makefile đơn giản

Làm là gì? Và tại sao tôi nên quan tâm?

Công cụ có tên Make là một trình quản lý phụ thuộc xây dựng. Đó là, cần quan tâm đến việc biết những lệnh nào cần được thực thi để lấy dự án phần mềm của bạn từ bộ sưu tập tệp nguồn, tệp đối tượng, thư viện, tiêu đề, v.v.-- một số trong đó có thể đã thay đổi gần đây --- và biến chúng thành một phiên bản cập nhật chính xác của chương trình.

Trên thực tế, bạn cũng có thể sử dụng Make cho những thứ khác, nhưng tôi sẽ không nói về điều đó.

Một Makefile tầm thường

Giả sử rằng bạn có một thư mục chứa: tool tool.cc tool.o support.cc support.hhsupport.ophụ thuộc vào rootvà được cho là sẽ được biên dịch thành một chương trình có tên toolvà giả sử rằng bạn đã hack các tệp nguồn (có nghĩa là hiện tooltại đã hết hạn) và muốn biên dịch chương trình.

Để làm điều này bản thân bạn có thể

  1. Kiểm tra xem một trong hai support.cchoặc support.hhmới hơn support.ovà nếu chạy một lệnh như

    g++ -g -c -pthread -I/sw/include/root support.cc
  2. Kiểm tra xem một trong hai support.hhhoặc tool.ccmới hơn tool.ovà nếu chạy một lệnh như

    g++ -g  -c -pthread -I/sw/include/root tool.cc
  3. Kiểm tra nếu tool.omới hơn toolvà nếu chạy một lệnh như

    g++ -g tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
    -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
    -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

Phù! Thật là rắc rối! Có rất nhiều điều cần nhớ và một vài cơ hội để phạm sai lầm. (BTW-- các chi tiết của các dòng lệnh được trình bày ở đây phụ thuộc vào môi trường phần mềm của chúng tôi. Chúng hoạt động trên máy tính của tôi.)

Tất nhiên, bạn chỉ có thể chạy cả ba lệnh mỗi lần. Điều đó sẽ hoạt động, nhưng nó không mở rộng tốt cho một phần mềm đáng kể (như DOGS, mất hơn 15 phút để biên dịch từ nền tảng trên MacBook của tôi).

Thay vào đó bạn có thể viết một tập tin gọi là makefilenhư thế này:

tool: tool.o support.o
    g++ -g -o tool tool.o support.o -L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
        -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz -Wl,-framework,CoreServices \
        -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root -lm -ldl

tool.o: tool.cc support.hh
    g++ -g  -c -pthread -I/sw/include/root tool.cc

support.o: support.hh support.cc
    g++ -g -c -pthread -I/sw/include/root support.cc

và chỉ cần gõ makevào dòng lệnh. Mà sẽ thực hiện ba bước được hiển thị ở trên tự động.

Các dòng không xác định ở đây có dạng "đích: phụ thuộc" và nói với Make rằng các lệnh liên quan (dòng thụt) nên được chạy nếu bất kỳ phụ thuộc nào mới hơn mục tiêu. Đó là, các dòng phụ thuộc mô tả logic của những gì cần được xây dựng lại để phù hợp với những thay đổi trong các tệp khác nhau. Nếu support.ccthay đổi có nghĩa là support.ophải được xây dựng lại, nhưng tool.ocó thể để lại một mình. Khi support.othay đổi toolphải được xây dựng lại.

Các lệnh được liên kết với mỗi dòng phụ thuộc được đặt bằng một tab (xem bên dưới) sẽ sửa đổi mục tiêu (hoặc ít nhất là chạm vào nó để cập nhật thời gian sửa đổi).

Biến, được xây dựng theo quy tắc và các tính năng khác

Tại thời điểm này, tệp thực hiện của chúng tôi chỉ đơn giản là ghi nhớ công việc cần thực hiện, nhưng chúng tôi vẫn phải tìm ra và nhập từng lệnh một cần thiết trong toàn bộ. Không cần phải như vậy: Make là một ngôn ngữ mạnh mẽ với các biến, các hàm thao tác văn bản và một loạt các quy tắc tích hợp có thể giúp chúng ta dễ dàng hơn nhiều.

Biến

Cú pháp để truy cập một biến tạo là $(VAR).

Cú pháp để gán cho biến Make là: VAR = A text value of some kind (hoặc VAR := A different text value but ignore this for the moment).

Bạn có thể sử dụng các biến trong các quy tắc như phiên bản cải tiến này của tệp thực hiện của chúng tôi:

CPPFLAGS=-g -pthread -I/sw/include/root
LDFLAGS=-g
LDLIBS=-L/sw/lib/root -lCore -lCint -lRIO -lNet -lHist -lGraf -lGraf3d -lGpad -lTree -lRint \
       -lPostscript -lMatrix -lPhysics -lMathCore -lThread -lz -L/sw/lib -lfreetype -lz \
       -Wl,-framework,CoreServices -Wl,-framework,ApplicationServices -pthread -Wl,-rpath,/sw/lib/root \
       -lm -ldl

tool: tool.o support.o
    g++ $(LDFLAGS) -o tool tool.o support.o $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

dễ đọc hơn một chút, nhưng vẫn cần gõ nhiều

Tạo chức năng

GNU make hỗ trợ nhiều chức năng để truy cập thông tin từ hệ thống tập tin hoặc các lệnh khác trên hệ thống. Trong trường hợp này, chúng tôi quan tâm đến việc $(shell ...)mở rộng ra đầu ra của (các) đối số và $(subst opat,npat,text)thay thế tất cả các trường hợp opatbằng npattrong văn bản.

Lợi dụng điều này mang lại cho chúng ta:

CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

tool: $(OBJS)
    g++ $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh
    g++ $(CPPFLAGS) -c tool.cc

support.o: support.hh support.cc
    g++ $(CPPFLAGS) -c support.cc

cái nào dễ gõ hơn và dễ đọc hơn nhiều.

Thông báo rằng

  1. Chúng tôi vẫn nêu rõ các phụ thuộc cho từng tệp đối tượng và tệp thực thi cuối cùng
  2. Chúng tôi đã phải gõ rõ ràng quy tắc biên dịch cho cả hai tệp nguồn

Quy tắc ngầm định và mẫu

Chúng tôi thường mong đợi rằng tất cả các tệp nguồn C ++ nên được xử lý theo cùng một cách và Make cung cấp ba cách để nêu điều này:

  1. quy tắc hậu tố (được coi là lỗi thời trong GNU make, nhưng được giữ cho tương thích ngược)
  2. quy tắc ngầm
  3. quy tắc mẫu

Các quy tắc ngầm được xây dựng và một vài quy tắc sẽ được thảo luận dưới đây. Quy tắc mẫu được chỉ định trong một hình thức như

%.o: %.c
    $(CC) $(CFLAGS) $(CPPFLAGS) -c $<

có nghĩa là các tệp đối tượng được tạo từ các tệp nguồn C bằng cách chạy lệnh được hiển thị, trong đó biến "tự động" $<mở rộng thành tên của phụ thuộc đầu tiên.

Quy tắc tích hợp

Make có một loạt các quy tắc dựng sẵn có nghĩa là rất thường xuyên, một dự án có thể được biên dịch bởi một tệp thực hiện rất đơn giản, thực sự.

GNU make được xây dựng theo quy tắc cho các tệp nguồn C là cái được trình bày ở trên. Tương tự, chúng tôi tạo các tệp đối tượng từ các tệp nguồn C ++ với quy tắc như $(CXX) -c $(CPPFLAGS) $(CFLAGS).

Các tệp đối tượng đơn được liên kết bằng cách sử dụng $(LD) $(LDFLAGS) n.o $(LOADLIBES) $(LDLIBS), nhưng điều này sẽ không hoạt động trong trường hợp của chúng tôi, vì chúng tôi muốn liên kết nhiều tệp đối tượng.

Các biến được sử dụng bởi các quy tắc tích hợp

Các quy tắc tích hợp sử dụng một tập hợp các biến tiêu chuẩn cho phép bạn chỉ định thông tin môi trường cục bộ (như nơi tìm ROOT bao gồm các tệp) mà không cần viết lại tất cả các quy tắc. Những thứ có khả năng thú vị nhất đối với chúng ta là:

  • CC - trình biên dịch C để sử dụng
  • CXX - trình biên dịch C ++ để sử dụng
  • LD - trình liên kết để sử dụng
  • CFLAGS - cờ biên dịch cho các tệp nguồn C
  • CXXFLAGS - cờ biên dịch cho các tệp nguồn C ++
  • CPPFLAGS - cờ cho bộ tiền xử lý c (thường bao gồm các đường dẫn tệp và ký hiệu được xác định trên dòng lệnh), được sử dụng bởi C và C ++
  • LDFLAGS - cờ liên kết
  • LDLIBS - thư viện để liên kết

Một Makefile cơ bản

Bằng cách tận dụng các quy tắc tích hợp, chúng ta có thể đơn giản hóa tệp thực hiện của mình để:

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

tool.o: tool.cc support.hh

support.o: support.hh support.cc

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) tool

Chúng tôi cũng đã thêm một số mục tiêu tiêu chuẩn thực hiện các hành động đặc biệt (như dọn dẹp thư mục nguồn).

Lưu ý rằng khi make được gọi mà không có đối số, nó sử dụng đích đầu tiên được tìm thấy trong tệp (trong trường hợp này là tất cả), nhưng bạn cũng có thể đặt tên cho mục tiêu để lấy đó là thứ giúp make cleanloại bỏ các tệp đối tượng trong trường hợp này.

Chúng tôi vẫn có tất cả các phụ thuộc được mã hóa cứng.

Một số cải tiến bí ẩn

CC=gcc
CXX=g++
RM=rm -f
CPPFLAGS=-g $(shell root-config --cflags)
LDFLAGS=-g $(shell root-config --ldflags)
LDLIBS=$(shell root-config --libs)

SRCS=tool.cc support.cc
OBJS=$(subst .cc,.o,$(SRCS))

all: tool

tool: $(OBJS)
    $(CXX) $(LDFLAGS) -o tool $(OBJS) $(LDLIBS)

depend: .depend

.depend: $(SRCS)
    $(RM) ./.depend
    $(CXX) $(CPPFLAGS) -MM $^>>./.depend;

clean:
    $(RM) $(OBJS)

distclean: clean
    $(RM) *~ .depend

include .depend

Thông báo rằng

  1. Không còn bất kỳ dòng phụ thuộc nào cho các tệp nguồn!?!
  2. Có một số phép thuật kỳ lạ liên quan đến .depend và phụ thuộc
  3. Nếu bạn làm như vậy makethì ls -Abạn sẽ thấy một tệp có tên .dependchứa những thứ trông giống như tạo các dòng phụ thuộc

Đọc khác

Biết lỗi và ghi chú lịch sử

Ngôn ngữ nhập cho Make là khoảng trắng nhạy cảm. Cụ thể, các dòng hành động sau phụ thuộc phải bắt đầu bằng một tab . Nhưng một loạt các khoảng trắng có thể trông giống nhau (và thực tế có những trình soạn thảo sẽ âm thầm chuyển đổi các tab thành khoảng trắng hoặc ngược lại), dẫn đến một tệp Make trông có vẻ đúng và vẫn không hoạt động. Điều này đã được xác định là một lỗi sớm, nhưng ( câu chuyện đã xảy ra ) nó không được sửa, vì đã có 10 người dùng.

(Điều này đã được sao chép từ một bài viết wiki tôi đã viết cho sinh viên tốt nghiệp vật lý.)


9
Phương pháp này để tạo ra sự phụ thuộc đã lỗi thời và thực sự có hại. Xem thế hệ phụ thuộc tự động nâng cao .
Maxim Egorushkin

5
-pthreadcờ gây ra gccđể xác định các macro cần thiết, -D_REENTRANTlà không cần thiết.
Maxim Egorushkin

8
@jcoe Nó vượt qua một bộ tiền xử lý bổ sung không cần thiết để tạo ra các phụ thuộc. Làm những việc không cần thiết, nó chỉ làm tiêu tan nhiệt làm tan chảy các cực băng và, ở quy mô lớn hơn, gần với cái chết nhiệt của vũ trụ của chúng ta.
Maxim Egorushkin

2
Có lẽ "có hại" là quá nhiều, nhưng do các giai đoạn hoặc mục tiêu tạo ra sự phụ thuộc rõ ràng đã lỗi thời vì ít nhất là GCC 3, tôi thực sự nghĩ rằng tất cả chúng ta nên vượt qua chúng. bruno.defraine.net/techtips/makefile-auto-dependencies-with-gcc/ gợi
hmijail thương tiếc người từ chức

2
Thực sự, câu trả lời được chấp nhận không nên phụ thuộc vào một phần mềm rất cụ thể ( root-config). Một giải pháp thay thế tổng quát hơn với khả năng tương tự nên được đề xuất nếu có hoặc chỉ nên bỏ qua. Tôi đã không downvote vì danh sách và giải thích về các macro được sử dụng thường xuyên nhất.
diod xanh

56

Tôi đã luôn nghĩ rằng điều này dễ học hơn với một ví dụ chi tiết, vì vậy đây là cách tôi nghĩ về các tệp tạo tệp. Đối với mỗi phần, bạn có một dòng không được thụt lề và nó hiển thị tên của phần tiếp theo là phần phụ thuộc. Các phần phụ thuộc có thể là các phần khác (sẽ được chạy trước phần hiện tại) hoặc các tệp (nếu được cập nhật sẽ khiến phần hiện tại được chạy lại lần sau khi bạn chạy make).

Dưới đây là một ví dụ nhanh (hãy nhớ rằng tôi đang sử dụng 4 khoảng trống mà tôi nên sử dụng một tab, Stack Overflow sẽ không cho phép tôi sử dụng các tab):

a3driver: a3driver.o
    g++ -o a3driver a3driver.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

Khi bạn gõ make, nó sẽ chọn phần đầu tiên (a3do). a3do phụ thuộc vào a3do.o, vì vậy nó sẽ đi đến phần đó. a3do.o phụ thuộc vào a3do.cpp, vì vậy nó sẽ chỉ chạy nếu a3do.cpp đã thay đổi kể từ lần chạy cuối cùng. Giả sử nó đã (hoặc chưa bao giờ được chạy), nó sẽ biên dịch a3do.cpp thành tệp .o, sau đó quay lại a3do và biên dịch tệp thực thi cuối cùng.

Vì chỉ có một tệp, nó thậm chí có thể được giảm xuống thành:

a3driver: a3driver.cpp
    g++ -o a3driver a3driver.cpp

Lý do tôi đưa ra ví dụ đầu tiên là nó cho thấy sức mạnh của makefiles. Nếu bạn cần biên dịch một tập tin khác, bạn có thể chỉ cần thêm một phần khác. Dưới đây là một ví dụ với secondFile.cpp (tải trong tiêu đề có tên secondFile.h):

a3driver: a3driver.o secondFile.o
    g++ -o a3driver a3driver.o secondFile.o

a3driver.o: a3driver.cpp
    g++ -c a3driver.cpp

secondFile.o: secondFile.cpp secondFile.h
    g++ -c secondFile.cpp

Bằng cách này nếu bạn thay đổi một cái gì đó trong secondFile.cpp hoặc secondFile.h và biên dịch lại, nó sẽ chỉ biên dịch lại secondFile.cpp (không phải a3do.cpp). Hoặc thay vào đó, nếu bạn thay đổi một cái gì đó trong a3do.cpp, nó sẽ không biên dịch lại secondFile.cpp.

Hãy cho tôi biết nếu bạn có bất kỳ câu hỏi về nó.

Nó cũng truyền thống để bao gồm một phần có tên "tất cả" và một phần có tên "sạch". "tất cả" thường sẽ xây dựng tất cả các tệp thực thi và "sạch" sẽ xóa "các tạo phẩm xây dựng" như các tệp .o và các tệp thực thi:

all: a3driver ;

clean:
    # -f so this will succeed even if the files don't exist
    rm -f a3driver a3driver.o

EDIT: Tôi đã không nhận thấy bạn trên Windows. Tôi nghĩ rằng sự khác biệt duy nhất là thay đổi -o a3driverthành -o a3driver.exe.


Mã tuyệt đối mà tôi đang cố sử dụng là: p4a.exe: p4do.cpp g ++ -o p4a p4do.cpp NHƯNG, nó cho tôi biết "dấu phân tách bị thiếu". Tôi đang sử dụng TAB, nhưng nó vẫn cho tôi biết điều đó. Bất kỳ ý tưởng?
Befall

2
Theo như tôi có thể nói, thông báo lỗi đó chỉ xuất hiện nếu bạn có khoảng trắng. Đảm bảo rằng bạn không có bất kỳ dòng nào bắt đầu bằng dấu cách (dấu cách + tab sẽ đưa ra lỗi đó). Đó là điều duy nhất tôi có thể nghĩ đến ..
Brendan Long

Lưu ý cho các biên tập viên trong tương lai: StackOverflow không thể hiển thị các tab ngay cả khi bạn chỉnh sửa chúng thành câu trả lời, vì vậy vui lòng không thử "sửa" ghi chú của tôi về điều đó.
Brendan Long

35

Tại sao mọi người thích liệt kê ra các tập tin nguồn? Một lệnh tìm đơn giản có thể chăm sóc điều đó một cách dễ dàng.

Đây là một ví dụ về C ++ Makefile đơn giản. Chỉ cần thả nó vào một thư mục chứa .Ccác tệp và sau đó gõ make...

appname := myapp

CXX := clang++
CXXFLAGS := -std=c++11

srcfiles := $(shell find . -name "*.C")
objects  := $(patsubst %.C, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

2
Một lý do để không tự động tìm các tệp nguồn là người ta có thể có các mục tiêu xây dựng khác nhau cần các tệp khác nhau.
hmijail thương tiếc người từ chức

Đồng ý @hmijail, cũng như các mô hình con có chứa rất nhiều nguồn / tiêu đề bạn không muốn biên dịch / liên kết ... và chắc chắn nhiều trường hợp khác trong đó tìm kiếm / sử dụng toàn diện là không phù hợp.
Kỹ sư

Tại sao nên sử dụng "shell find" chứ không phải "wildcard"?
Nolan

1
@Nolan để tìm tệp nguồn trong cây thư mục nguồn
AlejandroVD

13

Bạn có hai lựa chọn.

Tùy chọn 1: makefile đơn giản nhất = KHÔNG MAKEFILE.

Đổi tên "a3do.cpp" thành "a3a.cpp", rồi trên dòng lệnh ghi:

nmake a3a.exe

Và đó là nó. Nếu bạn đang sử dụng GNU Make, hãy sử dụng "make" hoặc "gmake" hoặc bất cứ điều gì.

Tùy chọn 2: makefile 2 dòng.

a3a.exe: a3driver.obj
    link /out:a3a.exe a3driver.obj

3
Đây sẽ là một câu trả lời tuyệt vời nếu nó không giả định quá nhiều điều về chi tiết về môi trường của OP. Vâng, họ đang ở trên Windows, nhưng điều đó không có nghĩa là họ đang sử dụng nmake. Dòng linklệnh cũng trông rất cụ thể đối với một trình biên dịch cụ thể, và ít nhất nên là tài liệu nào.
tripleee

6

Tệp Make của bạn sẽ có một hoặc hai quy tắc phụ thuộc tùy thuộc vào việc bạn biên dịch và liên kết với một lệnh hay bằng một lệnh cho biên dịch và một cho liên kết.

Sự phụ thuộc là một cây quy tắc trông như thế này (lưu ý rằng thụt lề phải là TAB):

main_target : source1 source2 etc
    command to build main_target from sources

source1 : dependents for source1
    command to build source1

phải là một dòng trống sau khi các lệnh cho một mục tiêu, và có phải không có một dòng trống trước khi các lệnh. Mục tiêu đầu tiên trong tệp thực hiện là mục tiêu tổng thể và các mục tiêu khác chỉ được xây dựng nếu mục tiêu đầu tiên phụ thuộc vào chúng.

Vì vậy, makefile của bạn sẽ trông giống như thế này.

a3a.exe : a3driver.obj 
    link /out:a3a.exe a3driver.obj

a3driver.obj : a3driver.cpp
    cc a3driver.cpp

6

Tôi đề nghị (lưu ý rằng thụt lề là TAB):

tool: tool.o file1.o file2.o
    $(CXX) $(LDFLAGS) $^ $(LDLIBS) -o $@

hoặc là

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
tool: tool.o file1.o file2.o

Gợi ý thứ hai tốt hơn một chút vì nó sử dụng lại các quy tắc ngầm của GNU. Tuy nhiên, để hoạt động, một tệp nguồn phải có cùng tên với tệp thực thi cuối cùng (nghĩa là: tool.ctool).

Thông báo, không cần thiết phải khai báo nguồn. Các tệp đối tượng trung gian được tạo bằng quy tắc ngầm. Do đó, Makefilecông việc này cho C và C ++ (và cả cho Fortran, v.v ...).

Cũng lưu ý, theo mặc định, Makefile sử dụng $(CC)làm trình liên kết. $(CC)không hoạt động để liên kết các tệp đối tượng C ++. Chúng tôi sửa đổi LINK.ochỉ vì điều đó. Nếu bạn muốn biên dịch mã C, bạn không phải ép LINK.ogiá trị.

Chắc chắn, bạn cũng có thể thêm các cờ biên dịch với biến CFLAGSvà thêm thư viện của bạn vào LDLIBS. Ví dụ:

CFLAGS = -Wall
LDLIBS = -lm

Lưu ý một mặt: nếu bạn phải sử dụng các thư viện bên ngoài, tôi khuyên bạn nên sử dụng pkg-config để đặt chính xácCFLAGSLDLIBS:

CFLAGS += $(shell pkg-config --cflags libssl)
LDLIBS += $(shell pkg-config --libs libssl)

Người đọc chu đáo sẽ nhận thấy rằng Makefile không được xây dựng lại đúng nếu một tiêu đề được thay đổi. Thêm các dòng này để khắc phục sự cố:

override CPPFLAGS += -MMD
include $(wildcard *.d)

-MMDcho phép xây dựng các tệp .d chứa các đoạn Makefile về các phụ thuộc của tiêu đề. Dòng thứ hai chỉ sử dụng chúng.

Để chắc chắn, một Makefile được viết tốt cũng nên bao gồm cleandistcleancác quy tắc:

clean:
    $(RM) *.o *.d

distclean: clean
    $(RM) tool

Thông báo, $(RM)tương đương vớirm -f , nhưng đó là một thực hành tốt để không gọi rmtrực tiếp.

Các allquy tắc cũng được đánh giá cao. Để làm việc, nó phải là quy tắc đầu tiên của tệp của bạn:

all: tool

Bạn cũng có thể thêm một installquy tắc:

PREFIX = /usr/local
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

DESTDIRtrống theo mặc định. Người dùng có thể thiết lập nó để cài đặt chương trình của bạn tại một hệ thống thay thế (bắt buộc cho quá trình biên dịch chéo). Gói bảo trì cho nhiều phân phối cũng có thể thay đổiPREFIX để cài đặt gói của bạn /usr.

Một từ cuối cùng: Không đặt các tệp nguồn trong các thư mục con. Nếu bạn thực sự muốn làm điều đó, hãy giữ nó Makefiletrong thư mục gốc và sử dụng các đường dẫn đầy đủ để xác định các tệp của bạn (tức làsubdir/file.o ).

Vì vậy, để tóm tắt, Makefile đầy đủ của bạn sẽ trông như sau:

LINK.o = $(CXX) $(LDFLAGS) $(TARGET_ARCH)
PREFIX = /usr/local
override CPPFLAGS += -MMD
include $(wildcard *.d)

all: tool
tool: tool.o file1.o file2.o
clean:
    $(RM) *.o *.d
distclean: clean
    $(RM) tool
install:
    install -m 755 tool $(DESTDIR)$(PREFIX)/bin

Gần cuối: Không nên có các dòng trống giữa các quy tắc? Câu trả lời của John Knoeller cho rằng.
Peter Mortensen

Không có cách triển khai makenào mà tôi biết (GNU Make và BSD Make) cần các dòng trống giữa các quy tắc. Tuy nhiên, nó tồn tại hàng tấn makeviệc triển khai với lỗi của chính họ ^ Đặc thù.
Jérôme Pouiller

5

Tôi đã sử dụng câu trả lời của Friedmud . Tôi đã xem xét điều này một lúc, và nó có vẻ là một cách tốt để bắt đầu. Giải pháp này cũng có một phương pháp được xác định rõ là thêm cờ trình biên dịch. Tôi đã trả lời lại, vì tôi đã thực hiện các thay đổi để làm cho nó hoạt động trong môi trường của tôi, Ubuntu và g ++. Nhiều ví dụ làm việc là giáo viên tốt nhất, đôi khi.

appname := myapp

CXX := g++
CXXFLAGS := -Wall -g

srcfiles := $(shell find . -maxdepth 1 -name "*.cpp")
objects  := $(patsubst %.cpp, %.o, $(srcfiles))

all: $(appname)

$(appname): $(objects)
    $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(appname) $(objects) $(LDLIBS)

depend: .depend

.depend: $(srcfiles)
    rm -f ./.depend
    $(CXX) $(CXXFLAGS) -MM $^>>./.depend;

clean:
    rm -f $(objects)

dist-clean: clean
    rm -f *~ .depend

include .depend

Makefiles dường như rất phức tạp. Tôi đã sử dụng một, nhưng nó đã tạo ra một lỗi liên quan đến việc không liên kết trong các thư viện g ++. Cấu hình này đã giải quyết vấn đề đó.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.