GNU makeツールを使用してマイクロコントローラー用のCプロジェクトを構築します。ソースファイルがオブジェクトファイルで乱雑にならないように、クリーンな方法で作成したいと思います。ビルド後の他の項目です。「myProject」というプロジェクトフォルダーがあり、その中に2つのフォルダーが含まれているとします。
- myProject
|
|---+ source
|
'---+ build
ビルドフォルダーにはmakefileのみが含まれます。下の図は、GNU makeツールを実行するとどうなるかを示しています。
したがって、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ファイルを更新するときのイベントの正確なチェーンは何ですか?
これが私がドキュメントに追加した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をプロジェクトに適合させるには、次のことを行う必要があります。
TARGET
変数を変更しますSources
およびBuild
内のSOURCEDIR
およびBUILDDIR
フォルダーの名前を変更するmake all VERBOSE=FALSE
_)DIRS
のフォルダーの名前をソースとビルドフォルダーに合わせて変更しますこのMakefileでは、_Folder0
_、_Folder1
_および_Folder2
_は、FolderA
、FolderB
および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
_
このかなり最小限の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)
主なトリッキーな部分は、FolderA
とFolderB
がビルドディレクトリに存在することを確認してから、それらに書き込むコンパイラを実行しようとすることです。上記のコードはビルドではシーケンシャルに動作しますが、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)
これには、コンパイルする他の種類のソースファイルの組み込みルールを再定義する必要があるという欠点があります
これは私がいつも使用している基本的なものです。実際のところスケルトンですが、単純なプロジェクトでは完璧に機能します。より複雑なプロジェクトの場合は、必ずそれを適応させる必要がありますが、私は常にこれを出発点として使用します。
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)