Makefiles với các tệp nguồn trong các thư mục khác nhau


135

Tôi có một dự án trong đó cấu trúc thư mục là như thế này:

                         $projectroot
                              |
              +---------------+----------------+
              |               |                |
            part1/          part2/           part3/
              |               |                |
       +------+-----+     +---+----+       +---+-----+
       |      |     |     |        |       |         |
     data/   src/  inc/  src/     inc/   src/       inc/

Làm thế nào tôi nên viết một tệp thực hiện trong part / src (hoặc bất cứ nơi nào thực sự) có thể biên dịch / liên kết trên các tệp nguồn c / c ++ trong một phần? / Src?

Tôi có thể làm một cái gì đó như -I $ projectroot / part1 / src -I $ projectroot / part1 / inc -I $ projectroot / part2 / src ...

Nếu điều đó sẽ làm việc, có một cách dễ dàng hơn để làm điều đó. Tôi đã thấy các dự án có một tệp thực hiện trong mỗi phần tương ứng chưa? thư mục. [trong bài đăng này, tôi đã sử dụng dấu chấm hỏi như trong cú pháp bash]



1
Trong hướng dẫn sử dụng gnu ban đầu ( gnu.org/software/make/manual/html_node/Phony-Target.html ) trong Mục tiêu Phony có một mẫu trên recursive invocation, khớp nối đó khá thanh lịch.
Frank Nocke

Công cụ nào bạn đã sử dụng để tạo đồ họa văn bản đó?
Khalid Hussain

Câu trả lời:


114

Cách truyền thống là phải có một Makefiletrong mỗi thư mục con ( part1, part2, vv) cho phép bạn xây dựng chúng một cách độc lập. Hơn nữa, có một Makefiletrong thư mục gốc của dự án xây dựng mọi thứ. "Root" Makefilesẽ trông giống như sau:

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Vì mỗi dòng trong một mục tiêu make được chạy trong shell riêng của nó, nên không cần phải lo lắng về việc di chuyển ngược lại cây thư mục hoặc đến các thư mục khác.

Tôi đề nghị xem xét phần GNU làm thủ công 5.7 ; nó rất hữu ích.


26
Điều này nên được +$(MAKE) -C part1vv Điều này cho phép kiểm soát công việc của Make hoạt động vào các thư mục con.
ephemient

26
Đây là một cách tiếp cận cổ điển và được sử dụng rộng rãi, nhưng nó không tối ưu theo nhiều cách trở nên tồi tệ hơn khi dự án phát triển. Dave Hinton có con trỏ để theo dõi.
dmckee --- ex-moderator mèo con

89

Nếu bạn có mã trong một thư mục con phụ thuộc vào mã trong thư mục con khác, có lẽ bạn sẽ tốt hơn với một tệp thực hiện duy nhất ở cấp cao nhất.

Xem đệ quy làm cho có hại cho cho lý do đầy đủ, nhưng về cơ bản bạn muốn thực hiện để có đầy đủ thông tin cần thiết để quyết định xem có cần xây dựng lại một tệp hay không và bạn sẽ không biết điều đó nếu bạn chỉ nói với nó khoảng một phần ba dự án của bạn.

Các liên kết ở trên dường như không thể truy cập. Tài liệu tương tự có thể truy cập ở đây:


3
Cảm ơn bạn, đã không nhận thức được điều này. Rất hữu ích khi biết "cách làm đúng" thay vì cách "chỉ hoạt động" hoặc được chấp nhận là tiêu chuẩn.
tjklemz

3
Làm đệ quy được coi là có hại, trở lại khi nó thực sự không hoạt động tốt. Thực tế, nó không được coi là có hại trong thực tế, đó là cách các autotools / automake quản lý các dự án lớn hơn.
Edwin Buck

36

Tùy chọn VPATH có thể có ích, giúp cho biết các thư mục cần tìm mã nguồn. Tuy nhiên, bạn vẫn cần một tùy chọn -I cho mỗi đường dẫn bao gồm. Một ví dụ:

CXXFLAGS=-Ipart1/inc -Ipart2/inc -Ipart3/inc
VPATH=part1/src:part2/src:part3/src

OutputExecutable: part1api.o part2api.o part3api.o

Điều này sẽ tự động tìm các tệp partXapi.cpp phù hợp trong bất kỳ thư mục nào được chỉ định của VPATH và biên dịch chúng. Tuy nhiên, điều này hữu ích hơn khi thư mục src của bạn được chia thành các thư mục con. Đối với những gì bạn mô tả, như những người khác đã nói, có lẽ bạn tốt hơn với một makefile cho mỗi phần, đặc biệt là nếu mỗi phần có thể đứng một mình.


2
Tôi không thể tin rằng câu trả lời hoàn hảo đơn giản này đã không có nhiều phiếu bầu hơn. Đó là +1 từ tôi.
Nicholas Hamilton

2
Tôi đã có một số tệp nguồn phổ biến trong một thư mục cao hơn cho một số dự án khác nhau trong các thư mục con, VPATH=..đã làm việc cho tôi!
EkriirkE

23

Bạn có thể thêm các quy tắc vào Makefile gốc của bạn để biên dịch các tệp cpp cần thiết trong các thư mục khác. Ví dụ Makefile dưới đây sẽ là một khởi đầu tốt để đưa bạn đến nơi bạn muốn.

CC = g ++
MỤC TIÊU = cppTest
OtherDIR = .. / .. / sometherpath / in / project / src

NGUỒN = cppTest.cpp
NGUỒN = $ (KHÁC) /file.cpp

## Định nghĩa nguồn kết thúc
INCLUDE = -I./ $ (AN_INCLUDE_DIR)  
BAO GỒM = -I. $ (KHÁC) /../ inc
## kết thúc nhiều hơn bao gồm

VPATH = $ (KHÁC)
OBJ = $ (tham gia $ (adduffix ../obj/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .o))) 

## Khắc phục đích phụ thuộc thành ../.dep liên quan đến thư mục src
DEPENDS = $ (tham gia $ (adduffix ../.dep/, $ (dir $ (SOURCE))), $ (notdir $ (SOURCE: .cpp = .d)))

## Quy tắc mặc định được thực thi
tất cả: $ (MỤC TIÊU)
        @thật

## Quy tắc sạch
dọn dẹp:
        @ -rm -f $ (MỤC TIÊU) $ (OBJ) $ (TIỀN GỬI)


## Quy tắc để thực hiện mục tiêu thực tế
$ (MỤC TIÊU): $ (OBJ)
        @echo "============="
        @echo "Liên kết mục tiêu $ @"
        @echo "============="
        @ $ (CC) $ (CFLAGS) -o $ @ $ ^ $ (LIBS)
        @echo - Liên kết hoàn thành -

## Quy tắc biên dịch chung
% .o:% .cpp
        @mkdir -p $ (thư mục $ @)
        @echo "============="
        @echo "Biên dịch $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @


## Quy tắc cho tệp đối tượng từ tệp cpp
## Tệp đối tượng cho mỗi tệp được đặt trong thư mục obj
## một cấp lên từ thư mục nguồn thực tế.
../obj/%.o:% .cpp
        @mkdir -p $ (thư mục $ @)
        @echo "============="
        @echo "Biên dịch $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

# Quy tắc cho "thư mục khác" Bạn sẽ cần một thư mục cho mỗi thư mục "khác"
$ (KHÁC) /../ obj /%. O:% .cpp
        @mkdir -p $ (thư mục $ @)
        @echo "============="
        @echo "Biên dịch $ <"
        @ $ (CC) $ (CFLAGS) -c $ <-o $ @

## Tạo quy tắc phụ thuộc
../.dep/%.d:% .cpp
        @mkdir -p $ (thư mục $ @)
        @echo "============="
        @echo Xây dựng tệp phụ thuộc cho $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ .. / obj / $ *. o ^"> $ @ '

## Quy tắc phụ thuộc cho thư mục "khác"
$ (KHÁC) /../. Dep /%. D:% .cpp
        @mkdir -p $ (thư mục $ @)
        @echo "============="
        @echo Xây dựng tệp phụ thuộc cho $ *. o
        @ $ (SHELL) -ec '$ (CC) -M $ (CFLAGS) $ <| sed "s ^ $ *. o ^ $ (KHÁC) /../ obj / $ *. o ^"> $ @ '

## Bao gồm các tệp phụ thuộc
- bao gồm $ (TIỀN GỬI)


7
Tôi biết điều này khá cũ nhưng bây giờ, cả SOURCE và INCLUDE đều không được ghi đè bởi một bài tập khác sau đó?
skelliam

@skelliam vâng, đúng vậy.
nabroyan

1
Cách tiếp cận này vi phạm nguyên tắc DRY. Thật là một ý tưởng tồi khi sao chép mã cho mỗi "thư mục khác"
Jesus H

20

Nếu các nguồn được trải đều trong nhiều thư mục và thật hợp lý khi có các Makefile riêng lẻ như được đề xuất trước đó, thì đệ quy là một cách tiếp cận tốt, nhưng đối với các dự án nhỏ hơn, tôi thấy việc liệt kê tất cả các tệp nguồn trong Makefile theo đường dẫn tương đối của chúng là dễ dàng hơn với Makefile như thế này:

# common sources
COMMON_SRC := ./main.cpp \
              ../src1/somefile.cpp \
              ../src1/somefile2.cpp \
              ../src2/somefile3.cpp \

Sau đó tôi có thể thiết lập VPATHtheo cách này:

VPATH := ../src1:../src2

Sau đó, tôi xây dựng các đối tượng:

COMMON_OBJS := $(patsubst %.cpp, $(ObjDir)/%$(ARCH)$(DEBUG).o, $(notdir $(COMMON_SRC)))

Bây giờ quy tắc rất đơn giản:

# the "common" object files
$(ObjDir)/%$(ARCH)$(DEBUG).o : %.cpp Makefile
    @echo creating $@ ...
    $(CXX) $(CFLAGS) $(EXTRA_CFLAGS) -c -o $@ $<

Và xây dựng đầu ra thậm chí còn dễ dàng hơn:

# This will make the cbsdk shared library
$(BinDir)/$(OUTPUTBIN): $(COMMON_OBJS)
    @echo building output ...
    $(CXX) -o $(BinDir)/$(OUTPUTBIN) $(COMMON_OBJS) $(LFLAGS)

Người ta thậm chí có thể làm cho VPATHthế hệ tự động bằng cách:

VPATH := $(dir $(COMMON_SRC))

Hoặc sử dụng thực tế sortloại bỏ trùng lặp (mặc dù điều đó không quan trọng):

VPATH := $(sort  $(dir $(COMMON_SRC)))

điều này hoạt động rất tốt cho các dự án nhỏ hơn, nơi bạn chỉ muốn thêm một số thư viện tùy chỉnh và có chúng trong một thư mục riêng. Cảm ơn bạn rất nhiều
zitroneneis

6

Tôi nghĩ tốt hơn là chỉ ra rằng sử dụng Make (đệ quy hay không) là điều mà bạn thường muốn tránh, vì so với các công cụ ngày nay, thật khó để học, duy trì và mở rộng quy mô.

Đó là một công cụ tuyệt vời nhưng việc sử dụng trực tiếp nên bị coi là lỗi thời trong năm 2010+.

Tất nhiên trừ khi bạn làm việc trong một môi trường đặc biệt, ví dụ như với một dự án cũ, v.v.

Sử dụng IDE, CMake hoặc, nếu bạn khó tính, Autotools .

(được chỉnh sửa do downvote, ty Honza đã chỉ ra)


1
Thật tuyệt khi có những lời giải thích. Tôi cũng đã từng tự làm Makefiles.
IlDan

2
Tôi ủng hộ đề xuất "không làm điều đó" - KDevelop có giao diện đồ họa rất cao để định cấu hình Make và các hệ thống xây dựng khác, và trong khi tôi tự viết Makefiles, KDevelop là thứ tôi tặng cho đồng nghiệp của mình - nhưng tôi đừng nghĩ rằng liên kết Autotools giúp. Để hiểu Autotools, bạn cần hiểu m4, libtool, automake, autoconf, shell, make, và về cơ bản là toàn bộ stack.
ephemient

7
Các downvote có thể là do bạn đang trả lời câu hỏi của riêng bạn. Câu hỏi không phải là liệu người ta có nên sử dụng Makefiles hay không. Đó là về cách viết chúng.
Honza

8
Sẽ thật tuyệt nếu bạn có thể, trong một vài câu, giải thích cách các công cụ hiện đại làm cho cuộc sống dễ dàng hơn khi viết Makefile. Họ có cung cấp một mức độ trừu tượng cao hơn? hay cái gì?
Abhishek Anand

1
xterm, vi, bash, makefile == thống trị thế giới, cmake dành cho những người không thể hack mã.
μολὼν.λαβέ

2

Bài viết của RC là SIÊU hữu ích. Tôi chưa bao giờ nghĩ về việc sử dụng hàm $ (dir $ @), nhưng nó đã làm chính xác những gì tôi cần nó để làm.

Trong ParentDir, có một loạt các thư mục chứa các tệp nguồn trong đó: dirA, dirB, dirC. Các tệp khác nhau phụ thuộc vào các tệp đối tượng trong các thư mục khác, vì vậy tôi muốn có thể tạo một tệp từ trong một thư mục và để nó tạo ra sự phụ thuộc đó bằng cách gọi tệp thực hiện liên kết với phụ thuộc đó.

Về cơ bản, tôi đã tạo một Makefile trong ParentDir có (trong số nhiều thứ khác) một quy tắc chung tương tự như RC:

%.o : %.cpp
        @mkdir -p $(dir $@)
        @echo "============="
        @echo "Compiling $<"
        @$(CC) $(CFLAGS) -c $< -o $@

Mỗi thư mục con bao gồm tệp tạo cấp cao hơn này để kế thừa quy tắc chung này. Trong mỗi Makefile của thư mục con, tôi đã viết một quy tắc tùy chỉnh cho mỗi tệp để tôi có thể theo dõi mọi thứ mà mỗi tệp riêng lẻ phụ thuộc vào.

Bất cứ khi nào tôi cần để tạo một tệp, tôi đã sử dụng (về cơ bản) quy tắc này để thực hiện đệ quy bất kỳ / tất cả các phụ thuộc. Hoàn hảo!

LƯU Ý: có một tiện ích gọi là "makepp" dường như thực hiện nhiệm vụ này thậm chí còn trực quan hơn, nhưng vì tính di động và không phụ thuộc vào công cụ khác, tôi đã chọn thực hiện theo cách này.

Hi vọng điêu nay co ich!


2

Sử dụng đệ quy

all:
    +$(MAKE) -C part1
    +$(MAKE) -C part2
    +$(MAKE) -C part3

Điều này cho phép makephân chia thành các công việc và sử dụng nhiều lõi


2
Làm thế nào là tốt hơn so với làm một cái gì đó như thế make -j4nào?
devin

1
@devin như tôi thấy, nó không tốt hơn , nó chỉ cho phép make sử dụng kiểm soát công việc. Khi chạy quy trình thực hiện riêng biệt, nó không chịu sự kiểm soát công việc.
Michael Pankov

1

Tôi đang tìm kiếm một cái gì đó như thế này và sau một vài lần thử và tôi tự tạo ra bộ trang điểm của riêng mình, tôi biết đó không phải là "cách thành ngữ" nhưng đó là một khởi đầu để hiểu về sản phẩm và điều này hiệu quả với tôi, có lẽ bạn có thể thử trong dự án của mình.

PROJ_NAME=mono

CPP_FILES=$(shell find . -name "*.cpp")

S_OBJ=$(patsubst %.cpp, %.o, $(CPP_FILES))

CXXFLAGS=-c \
         -g \
        -Wall

all: $(PROJ_NAME)
    @echo Running application
    @echo
    @./$(PROJ_NAME)

$(PROJ_NAME): $(S_OBJ)
    @echo Linking objects...
    @g++ -o $@ $^

%.o: %.cpp %.h
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS) -o $@

main.o: main.cpp
    @echo Compiling and generating object $@ ...
    @g++ $< $(CXXFLAGS)

clean:
    @echo Removing secondary things
    @rm -r -f objects $(S_OBJ) $(PROJ_NAME)
    @echo Done!

Tôi biết điều đó đơn giản và đối với một số người, cờ của tôi sai, nhưng như tôi đã nói đây là Makefile đầu tiên của tôi để biên dịch dự án của tôi trong nhiều thư mục và liên kết tất cả sau đó với nhau để tạo thùng của tôi.

Tôi đang chấp nhận đường: D


-1

Tôi đề nghị sử dụng autotools:

//## Đặt các tệp đối tượng được tạo (.o) vào cùng thư mục với các tệp nguồn của chúng, để tránh xung đột khi thực hiện không đệ quy được sử dụng.

AUTOMAKE_OPTIONS = subdir-objects

chỉ bao gồm nó trong Makefile.amvới những thứ khá đơn giản khác.

Đây là hướng dẫ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.