Skip to content

Instantly share code, notes, and snippets.

@asmichi
Last active June 15, 2020 03:32
Show Gist options
  • Save asmichi/78fee75d5603623f4b8e50db363a9c36 to your computer and use it in GitHub Desktop.
Save asmichi/78fee75d5603623f4b8e50db363a9c36 to your computer and use it in GitHub Desktop.
order-only prerequisites の使用例: 自動生成されるヘッダファイル

order-only の依存って何に使うの?

GNU Make の order-only prerequisites をはじめとして、多くのビルド自動化ツールが order-only の依存を表現することができる。

order-only の依存とは何か。order-only の依存は、通常の依存と同様、ターゲットを更新する前に更新されているべきターゲットを宣言する。しかし、通常の依存と異なり、そもそもターゲットを更新する必要があるか否かの判定で考慮されない。 Make でいうと、order-only prerequisites のタイムスタンプが新しくなっても、ターゲットを更新するレシピは実行されない。

何に使うのか? 例えば、自動生成されるヘッダファイルが存在する場合に、再コンパイルの量を減らすことができる。具体例として、 protoc で生成するヘッダファイルに依存を張る方法を考える。次の 3 つの方法を比較する。

  1. 何もなし
  2. normal prerequisites
  3. order-only prerequisites

実装は、添付の Makefile を参照せよ。

何もなし

$ make AUTOGEN_PREREQ_STYLE=None clean
  rm -f *.pb.cc *.pb.h *.o *.d app

$ make AUTOGEN_PREREQ_STYLE=None
  g++ -MMD -o app.o -c app.cc
  app.cc:1:10: fatal error: foo.pb.h: No such file or directory
  #include "foo.pb.h"
          ~~~~~~~~
  compilation terminated.
  Makefile:37: recipe for target 'app.o' failed
  make: *** [app.o] Error 1

自動生成されるヘッダファイルに依存を張らなければ、ヘッダファイルが存在しないのだからコンパイルが通らない。

normal prerequisites

$ make AUTOGEN_PREREQ_STYLE=Normal clean
  rm -f *.pb.cc *.pb.h *.o *.d app

$ make AUTOGEN_PREREQ_STYLE=Normal 
  protoc -I. --cpp_out=. foo.proto --dependency_out=foo.proto.d
  protoc -I. --cpp_out=. bar.proto --dependency_out=bar.proto.d
  g++ -MMD -o app.o -c app.cc
  g++ -MMD -o foo.pb.o -c foo.pb.cc
  g++ -MMD -o bar.pb.o -c bar.pb.cc
  g++ -o app app.o foo.pb.o bar.pb.o -lprotobuf

コンパイルできるようになった。

$ touch bar.proto

$ make AUTOGEN_PREREQ_STYLE=Normal 
  protoc -I. --cpp_out=. bar.proto --dependency_out=bar.proto.d
  g++ -MMD -o app.o -c app.cc
  g++ -MMD -o foo.pb.o -c foo.pb.cc
  g++ -MMD -o bar.pb.o -c bar.pb.cc
  g++ -o app app.o foo.pb.o bar.pb.o -lprotobuf

しかし、いずれかのヘッダファイルを更新すると、全ての .cc ファイルが再コンパイルされてしまう。

order-only prerequisites

$ make AUTOGEN_PREREQ_STYLE=OrderOnly clean
  rm -f *.pb.cc *.pb.h *.o *.d app

$ make AUTOGEN_PREREQ_STYLE=OrderOnly
  protoc -I. --cpp_out=. foo.proto --dependency_out=foo.proto.d
  protoc -I. --cpp_out=. bar.proto --dependency_out=bar.proto.d
  g++ -MMD -o app.o -c app.cc
  g++ -MMD -o foo.pb.o -c foo.pb.cc
  g++ -MMD -o bar.pb.o -c bar.pb.cc
  g++ -o app app.o foo.pb.o bar.pb.o -lprotobuf
$ touch bar.proto

$ make AUTOGEN_PREREQ_STYLE=OrderOnly
  protoc -I. --cpp_out=. bar.proto --dependency_out=bar.proto.d
  g++ -MMD -o bar.pb.o -c bar.pb.cc
  g++ -o app app.o foo.pb.o bar.pb.o -lprotobuf

bar.pb.h が更新された場合、 bar.pb.cc だけが再コンパイルされるようになった。

$ touch foo.proto

$ make AUTOGEN_PREREQ_STYLE=OrderOnly
  protoc -I. --cpp_out=. foo.proto --dependency_out=foo.proto.d
  g++ -MMD -o app.o -c app.cc
  g++ -MMD -o foo.pb.o -c foo.pb.cc
  g++ -o app app.o foo.pb.o bar.pb.o -lprotobuf

foo.pb.h が更新された場合は、 app.ccfoo.pb.cc だけが再コンパイルされる。

#include "foo.pb.h"
// これをアンコメントすることを考える。そのような変化でいちいち Makefile を書きかえたくない。
// だから .d ファイルを使っているのだ。
// #include "bar.pb.h"
int main() { }
syntax = "proto3";
package order_only;
message Bar {
string s = 1;
}
syntax = "proto3";
package order_only;
message Foo {
string s = 1;
}
# order-only prerequisites の使用例: 自動生成されるヘッダファイル
# https://www.gnu.org/software/make/manual/html_node/Prerequisite-Types.html#Prerequisite-Types
all: app
PROTO_SRCS = foo.proto bar.proto
CC_SRCS = app.cc foo.pb.cc bar.pb.cc
CC_OBJS = $(CC_SRCS:.cc=.o)
-include $(PROTO_SRCS:.proto=.proto.d)
-include $(CC_OBJS:.o=.d)
app : $(CC_OBJS)
g++ -o $@ $(CC_OBJS) -lprotobuf
app.o : app.cc
foo.pb.o : foo.pb.cc
bar.pb.o : bar.pb.cc
# 自動生成されるヘッダファイルが存在していないとコンパイルが通らない。どう依存を書くか?
ifeq ($(AUTOGEN_PREREQ_STYLE),None)
# 1. 何もなし
else ifeq ($(AUTOGEN_PREREQ_STYLE),Normal)
# 2. normal prerequisites
$(CC_OBJS) : $(PROTO_SRCS:.proto=.pb.h)
else ifeq ($(AUTOGEN_PREREQ_STYLE),OrderOnly)
# 3. order-only prerequisites
$(CC_OBJS) :| $(PROTO_SRCS:.proto=.pb.h)
else
$(error Unknown AUTOGEN_PREREQ_STYLE: $(AUTOGEN_PREREQ_STYLE))
endif
%.pb.cc %.pb.h : %.proto
protoc -I. --cpp_out=. $< --dependency_out=$<.d
%.o : %.cc
g++ -MMD -o $@ -c $<
clean:
rm -f *.pb.cc *.pb.h *.o *.d app
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment