web-dev-qa-db-ja.com

GNU makeを使用して「ソースツリー外」にCプログラムをビルドする

GNU makeツールを使用してマイクロコントローラー用のCプロジェクトを構築します。ソースファイルがオブジェクトファイルで乱雑にならないように、クリーンな方法で作成したいと思います。ビルド後の他の項目です。「myProject」というプロジェクトフォルダーがあり、その中に2つのフォルダーが含まれているとします。

- myProject
     |
     |---+ source
     |
     '---+ build

ビルドフォルダーにはmakefileのみが含まれます。下の図は、GNU makeツールを実行するとどうなるかを示しています。

enter image description here

したがって、GNU makeは、ソースフォルダーにある.cソースファイルごとにオブジェクトファイルを作成する必要があります。オブジェクトファイルは、ソースの構造と同様のディレクトリツリーで構造化する必要があります。フォルダ。

GNU makeは、各.cソースファイルに対して.d依存ファイルも作成する必要があります(実際、依存ファイルはある種のmakefileそのものです)。依存関係ファイルについては、GNU makeマニュアルの第4.14章「前提条件の自動生成」を参照してください。

各ソースファイルname.cには、メイクファイルname.dがあります。これは、オブジェクトファイルname.oが依存するファイルをリストします。

次のStackoverflowの質問 からGNU make依存ファイル* .d について、オプションを追加することを学びました-MMDおよび-MP to GNU gccコンパイラのCFLAGSを使用すると、自動化できます。

だから今問題が来ます。このようなソース外のビルドを実行するサンプルmakefileはありますか?または、開始する方法についての良いアドバイスはありますか?

私はそのようなメイクファイルを書いたほとんどの人がLinuxの人々であることをかなり確信しています。しかし、マイクロコントローラープロジェクトはWindowsマシン上でもビルドする必要があります。とにかく、makefileがLinuxのみの場合でも、それは良い出発点になります;-)

PS:CMake、Autotools、またはIDEに関連する何かなどの余分なツールは避けたいです。ただ純粋なGNU makeです。

私は非常に感謝されます :-)


依存関係ファイルの更新
この質問をご覧ください: GNUが.dファイルを更新するときのイベントの正確なチェーンは何ですか?

17
K.Mulier

これが私がドキュメントに追加したMakefileです(現在レビュー中なのでここに投稿します):

_# Set project directory one level above the Makefile directory. $(CURDIR) is a GNU make variable containing the path to the current working directory
PROJDIR := $(realpath $(CURDIR)/..)
SOURCEDIR := $(PROJDIR)/Sources
BUILDDIR := $(PROJDIR)/Build

# Name of the final executable
TARGET = myApp.exe

# Decide whether the commands will be shown or not
VERBOSE = TRUE

# Create the list of directories
DIRS = Folder0 Folder1 Folder2
SOURCEDIRS = $(foreach dir, $(DIRS), $(addprefix $(SOURCEDIR)/, $(dir)))
TARGETDIRS = $(foreach dir, $(DIRS), $(addprefix $(BUILDDIR)/, $(dir)))

# Generate the GCC includes parameters by adding -I before each source folder
INCLUDES = $(foreach dir, $(SOURCEDIRS), $(addprefix -I, $(dir)))

# Add this list to VPATH, the place make will look for the source files
VPATH = $(SOURCEDIRS)

# Create a list of *.c sources in DIRS
SOURCES = $(foreach dir,$(SOURCEDIRS),$(wildcard $(dir)/*.c))

# Define objects for all sources
OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))

# Define dependencies files for all objects
DEPS = $(OBJS:.o=.d)

# Name the compiler
CC = gcc

# OS specific part
ifeq ($(OS),Windows_NT)
    RM = del /F /Q 
    RMDIR = -RMDIR /S /Q
    MKDIR = -mkdir
    ERRIGNORE = 2>NUL || true
    SEP=\\
else
    RM = rm -rf 
    RMDIR = rm -rf 
    MKDIR = mkdir -p
    ERRIGNORE = 2>/dev/null
    SEP=/
endif

# Remove space after separator
PSEP = $(strip $(SEP))

# Hide or not the calls depending of VERBOSE
ifeq ($(VERBOSE),TRUE)
    HIDE =  
else
    HIDE = @
endif

# Define the function that will generate each rule
define generateRules
$(1)/%.o: %.c
    @echo Building $$@
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@) $$(subst /,$$(PSEP),$$<) -MMD
endef

# Indicate to make which targets are not files
.PHONY: all clean directories 

all: directories $(TARGET)

$(TARGET): $(OBJS)
    $(HIDE)echo Linking $@
    $(HIDE)$(CC) $(OBJS) -o $(TARGET)

# Include dependencies
-include $(DEPS)

# Generate rules
$(foreach targetdir, $(TARGETDIRS), $(eval $(call generateRules, $(targetdir))))

directories: 
    $(HIDE)$(MKDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)

# Remove all objects, dependencies and executable files generated during the build
clean:
    $(HIDE)$(RMDIR) $(subst /,$(PSEP),$(TARGETDIRS)) $(ERRIGNORE)
    $(HIDE)$(RM) $(TARGET) $(ERRIGNORE)
    @echo Cleaning done ! 
_

主な機能

  • 指定されたフォルダ内のCソースの自動検出
  • 複数のソースフォルダー
  • オブジェクトおよび依存関係ファイルの複数の対応するターゲットフォルダー
  • 各ターゲットフォルダーの自動ルール生成
  • ターゲットフォルダーが存在しない場合の作成
  • gccによる依存関係管理:必要なものだけを構築
  • UnixおよびDOSシステムで動作します
  • _GNU Make_用に作成

このMakefileの使用方法

このMakefileをプロジェクトに適合させるには、次のことを行う必要があります。

  1. ターゲット名と一致するようにTARGET変数を変更します
  2. SourcesおよびBuild内のSOURCEDIRおよびBUILDDIRフォルダーの名前を変更する
  3. Makefile自体またはMake呼び出しでMakefileの詳細レベルを変更します(_make all VERBOSE=FALSE_)
  4. DIRSのフォルダーの名前をソースとビルドフォルダーに合わせて変更します
  5. 必要に応じて、コンパイラとフラグを変更します

このMakefileでは、_Folder0_、_Folder1_および_Folder2_は、FolderAFolderBおよびFolderCと同等です。

現時点では、Unixシステムでテストする機会はありませんが、Windowsでは正常に動作します。


いくつかのトリッキーな部分の説明:

Windows mkdirエラーを無視します

_ERRIGNORE = 2>NUL || true
_

これには2つの効果があります。最初の_2>NUL_は、コンソールにないため、エラー出力をNULにリダイレクトします。

2番目の_|| true_は、コマンドがエラーレベルを上げるのを防ぎます。これは、Makefileとは無関係のWindowsのものです。既存のフォルダーを作成しようとすると、Windowsのmkdirコマンドがエラーレベルを上げるため、ここに表示されますが、存在しても問題はありません。 。一般的な解決策は_if not exist_構造を使用することですが、これはUNIX互換ではないので、たとえトリッキーな場合でも、私の解決策をより明確に考えています。


すべてのオブジェクトファイルとその正しいパスを含むOBJSの作成

_OBJS := $(subst $(SOURCEDIR),$(BUILDDIR),$(SOURCES:.c=.o))
_

ここでは、OBJSにすべてのオブジェクトファイルとそのパスを含めたいと考えています。また、すでにすべてのソースファイルとそのパスを含むSOURCESがあります。 $(SOURCES:.c=.o)は、すべてのソースに対して* .oの* .cを変更しますが、パスは依然としてソースの1つです。 $(subst $(SOURCEDIR),$(BUILDDIR), ...)は、ビルドパスでソースパス全体を差し引くだけなので、最終的に、パスを含む.oファイルを含む変数ができます。


WindowsおよびUnixスタイルのパス区切り文字の扱い

_SEP=\\
SEP = /
PSEP = $(strip $(SEP))
_

これは、MakefileがUnixおよびWindowsで機能するためにのみ存在します。Windowsではパスにバックスラッシュを使用するのに対し、他のすべてのユーザーはスラッシュを使用するためです。

_SEP=\\_ここでは、2つのバックスラッシュを使用してバックスラッシュ文字をエスケープします。通常、makeは「改行文字を無視する」として扱い、複数行に書き込むことができます。

PSEP = $(strip $(SEP))これにより、自動的に追加されたSEP変数のスペース文字が削除されます。


各ターゲットフォルダーのルールの自動生成

_define generateRules
$(1)/%.o: %.c
    @echo Building $$@
    $(HIDE)$(CC) -c $$(INCLUDES) -o $$(subst /,$$(PSEP),$$@)   $$(subst /,$$(PSEP),$$<) -MMD
endef
_

それは、おそらくあなたのユースケースに最も関連するトリックです。これは、$(eval $(call generateRules, param))で生成できるルールテンプレートです。paramは、テンプレートで$(1)として見つけることができるものです。これにより、基本的に、Makefileに各ターゲットフォルダーの次のようなルールが入力されます。

_path/to/target/%.o: %.c
    @echo Building $@
    $(HIDE)$(CC) -c $(INCLUDES) -o $(subst /,$(PSEP),$@)   $(subst /,$(PSEP),$<) -MMD
_
19
Tim

このかなり最小限のmakefileでうまくいくはずです。

VPATH = ../source
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o
CPPFLAGS = -MMD -MP

all: init myProgram

myProgram: $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

.PHONY: all init

init:
        mkdir -p FolderA
        mkdir -p FolderB

-include $(OBJS:%.o=%.d)

主なトリッキーな部分は、FolderAFolderBがビルドディレクトリに存在することを確認してから、それらに書き込むコンパイラを実行しようとすることです。上記のコードはビルドではシーケンシャルに動作しますが、1つのスレッドのコンパイラが他のスレッドがディレクトリを作成する前に出力ファイルを開こうとする可能性があるため、最初の実行時に-j2で失敗する可能性があります。また、多少汚れています。通常GNUツールを使用すると、makeスクリプトを実行する前に、これらのディレクトリ(およびmakefile)を作成するconfigureスクリプトが作成されます。autoconfとautomakeでビルドできます。

並列ビルドで機能する別の方法は、Cファイルをコンパイルするための標準ルールを再定義することです。

VPATH = ../source
OBJS = FolderA/fileA1.o FolderA/fileA2.o FolderB/fileB1.o
CPPFLAGS = -MMD -MP

myProgram: $(OBJS)
        $(CC) $(LDFLAGS) -o $@ $(OBJS) $(LDLIBS)

%.o: %.c
        mkdir -p $(dir $@)
        $(CC) $(CFLAGS) $(CPPFLAGS) -c -o $@ $<

-include $(OBJS:%.o=%.d)

これには、コンパイルする他の種類のソースファイルの組み込みルールを再定義する必要があるという欠点があります

2
Chris Dodd

これは私がいつも使用している基本的なものです。実際のところスケルトンですが、単純なプロジェクトでは完璧に機能します。より複雑なプロジェクトの場合は、必ずそれを適応させる必要がありますが、私は常にこれを出発点として使用します。

APP=app

SRC_DIR=src
INC_DIR=inc
OBJ_DIR=obj
BIN_DIR=bin

CC=gcc
LD=gcc
CFLAGS=-O2 -c -Wall -pedantic -ansi
LFLGAS=
DFLAGS=-g3 -O0 -DDEBUG
INCFLAGS=-I$(INC_DIR)

SOURCES=$(wildcard $(SRC_DIR)/*.c)
HEADERS=$(wildcard $(INC_DIR)/*.h)
OBJECTS=$(SOURCES:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
DEPENDS=$(OBJ_DIR)/.depends


.PHONY: all
all: $(BIN_DIR)/$(APP)

.PHONY: debug
debug: CFLAGS+=$(DFLAGS)
debug: all


$(BIN_DIR)/$(APP): $(OBJECTS) | $(BIN_DIR)
    $(LD) $(LFLGAS) -o $@ $^

$(OBJ_DIR)/%.o: | $(OBJ_DIR)
    $(CC) $(CFLAGS) $(INCFLAGS) -o $@ $<

$(DEPENDS): $(SOURCES) | $(OBJ_DIR)
    $(CC) $(INCFLAGS) -MM $(SOURCES) | sed -e 's!^!$(OBJ_DIR)/!' >$@

ifneq ($(MAKECMDGOALS),clean)
-include $(DEPENDS)
endif


$(BIN_DIR):
    mkdir -p $@
$(OBJ_DIR):
    mkdir -p $@

.PHONY: clean
clean:
    rm -rf $(BIN_DIR) $(OBJ_DIR)
2
elpato