【问题标题】:Looking for the perfect Makefile canvas寻找完美的 Makefile 画布
【发布时间】:2012-10-01 01:33:41
【问题描述】:

我是计算机工程专业的学生,​​我还有另一个 C 程序要实现。 到目前为止,我总是从我以前的项目中创建一个新的 Makefile,每次都需要时间来设置。 有没有我可以使用的强大的 Makefile 画布,我只需要提供包含 main 函数的 c 文件的名称?

我的想法是,对于每个新项目,我创建一个包含 Makefile 的文件夹、一个 bin 文件夹和一个 src 文件夹。然后我会将一些变量编辑到 Makefile(包含 main() 函数的 C 文件),然后 make 会自动构建所有内容,同时考虑到依赖关系。

你知道这样的Makefile是否存在吗?

[通过 Alexandre C. 编辑]:对于仅使用标准库并在标准 unix 操作系统上运行的此类项目,Automake/Autoconf 是多余的。 对于我们需要实现的项目,依赖关系(对于要链接的文件)总是可以从“.h”包含中推导出来的,通常涉及的文件很少。

【问题讨论】:

  • 我过去曾提出过各种 Makefile 模板,或多或少的东西(自动依赖生成等)。最好的方法仍然是使用 Gnu Make 的优秀文档来创建自己的。您甚至可以使用src = $(shell find . -name "*.c")$(grep -H "main(") 之类的东西。这不是很难,而且您感觉自己可以控制自己的环境。
  • @AlexandreC。您能否提供一个脚本或 Makefile 供我自己编写?
  • @Mat:网上有很多(例如here)。我强烈建议您浏览 Gnu Make 的文档。不到一个下午就可以成为 Make 专家,这些知识将对您的职业生涯非常有用。特别是,学习使用内置规则(以便您知道何时删除它们)。

标签: c gcc makefile


【解决方案1】:

最接近神奇而完美的Makefile 是使用可移植C 程序和库的事实标准:Automake

作为来自 this page 的示例,这是您将添加到 Makefile.am 的示例:

bin_PROGRAMS = zardoz
zardoz_SOURCES = main.c head.c float.c vortex9.c gun.c
zardoz_LDADD = $(LIBOBJS)

我们在这里添加的 make 目标的等价物称为zardoz。源在zardoz_SOURCES 下指定,任何需要链接的其他库在zardoz_LDADD 下指定。您无需指定main 函数所在的位置,因为链接器会在链接阶段自动找到它。如果它不存在,链接就会失败。

【讨论】:

  • @AlexBrown: 没有人可以为 automake 做一个有效的例子:-(
  • Automake 对于将在典型环境中运行的学生项目来说可能是矫枉过正。 Autotools 用于保证可移植性(这具有很大的设置成本和学习曲线)。
  • Automake 对于我的需要来说太大了。通常我正在处理的项目非常小,只需要十几个 c 文件即可在 mose 2 可执行文件中生成并使用非常标准的库(套接字、pthreads ......)。我很确定可以编写一个非常简单的Makefile,而不需要 automake 或 autoconf。
  • @Mat:使用您当前有效使用的“模板”有什么问题?
  • @MikeKwan :我觉得可以使用一个简单的通用 Makefile,它不需要我手动编写 .o 文件之间的依赖关系(以防我有两个不使用相同的可执行文件.o 文件为例)。我的意思是,任何准备好我的代码(尤其是#includes "abc.h" 和 "int main()")的人都知道如何编译它。为什么计算机不能做这项工作?
【解决方案2】:

我也推荐autotools,你最终会学会欣赏它,它绝对值得,你可以寻找一个关于如何使用它的教程,或者,如果你不介意看一本小书,我建议阅读autobook 但是,如果您只想要一个简单的 Makefile 模板,我使用这个:

BIN     = <binary name>
OBJ     = <object files *.o>
LIB     = <libraries>
CFLAGS  = -O2 -Wall -I. 
LDFLAGS = <ld flags>

CC = gcc
LD = ld
RM = rm -f 

all:: $(BIN)

$(BIN): $(OBJ)
    $(CC) $(LDFLAGS) $(OBJ) $(LIB) -o $(BIN)

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

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

【讨论】:

    【解决方案3】:
    PROGRAM = test-program CFLAGS = -c -Wall OBJECTS += $(patsubst %.c, %.o, $(wildcard ./src/*.c)) OBJECTS += $(patsubst %.c, %.o, $(wildcard ./src/more-sources*.c)) all: $(OBJECTS) gcc -o ./bin/$(PROGRAM) $(OBJECTS) %.o: %.c %.h gcc $(CFLAGS) $< -o $@

    这样的事情就可以解决问题。我显然没有测试过这个.. 您可以选择基于每个文件覆盖默认编译。

    【讨论】:

    • 我更喜欢src = $(shell find . -name "*.c"),因为它比$(wildcard)更灵活,但是这里有很多方法。
    • 有趣。这在运行 MinGW 或类似的 Windows 上是否有效?
    • 你需要一个 bourne-enough shell 和 find 程序。这应该适用于 cygwin,或者任何提供 bash 和 findutils 的东西。
    【解决方案4】:

    您可以像大多数开源项目一样,通过著名的命令./configure 使用 autoconf 自动生成 Makefile。 参考这个链接:configure file template to generate makefile

    【讨论】:

      【解决方案5】:

      您可以轻松编写自己的宏“包”来执行此操作。例如,将此文件创建为样板文件,将其命名为 program.mk 并将其放在树中的中心位置:

      lang.c.objs   = $(patsubst %.c,%.o,$(1))
      lang.c.link   = $(CC) $(CFLAGS) $(LDFLAGS) -o $(1) $(2)
      lang.c++.objs = $(patsubst %.cpp,%.o,$(1))
      lang.c++.link = $(CXX) $(CXXFLAGS) $(LDFLAGS) -o $(1) $(2)
      
      define make-program
        program.$(1).lang    ?= c
        program.$(1).objects ?= $$(call lang.$$(program.$(1).lang).objs,$$(program.$(1).sources))
        $$(program.$(1).name): $$(program.$(1).objects) $$(program.$(1).extra-deps)
            $$(call lang.$$(program.$(1).lang).link,$$@,$$^ $$(program.$(1).ldlibs))
        CLEANABLE    += $$(program.$(1).name)
        ALL_PROGRAMS += $$(program.$(1).name)
      endef
      
      # If the user didn't specify a list of programs, build them all
      ifndef PROGRAMS
        PROGRAMS = $(foreach p,$(filter program.%.name,$(.VARIABLES)),\
          $(patsubst program.%.name,%,$(p)))
      endif
      
      # Generate the rule to build each program
      $(foreach p,$(PROGRAMS),$(eval $(call make-program,$(p))))
      
      .PHONY: all clean
      all: $(ALL_PROGRAMS)
      
      clean: ; rm -f $(CLEANABLE)
      
      .DEFAULT_GOAL := all
      

      现在,在您要构建程序的每个目录中,您的 makefile 可以是这样的:

      program.p.name    = Program
      program.p.sources = Program1.c Program2.c
      
      include path/to/program.mk
      

      类似的library.mk 可用于库。这种方法非常强大,而且很容易扩展。

      【讨论】:

        【解决方案6】:

        如果您对标准 UNIX 环境感兴趣,为什么不坚持使用 POSIX make 提供的功能?

        # Lean and mean.
        .POSIX:
        
        CFLAGS = -DNDEBUG -O
        LDFLAGS = -s
        
        TARGETS = abc def
        
        all: $(TARGETS)
        
        clean:
            rm -f $(TARGETS)
        
        # Some dependencies.
        abc: def.h
        
        # Some special rules.
        def: def.c
            $(CC) $(CFLAGS) $(LDFLAGS) -o $@ def.c -l m
        

        对于大多数小型项目,您不需要比默认设置更复杂的东西;也许您应该专注于项目本身,而不是浪费时间编写复杂的 makefile 逻辑。

        POSIX make 的一个限制是没有模式规则,后缀规则不能处理目录;因此,在不同目录中传播源文件将需要每个文件的单个规则,或者递归制作。就个人而言,对于小型项目,我不会费心将源文件移动到其他地方。坚持使用 POSIX make 还将提供跨其他兼容 POSIX 的构建系统(例如 BSD make)的可移植性。

        关于自动依赖计算,请阅读gcc -M。它生成包含依赖规则的makefile。然后你可以在你的makefile中include他们。但是,在我看来,更好的做法是在单个头文件中跟踪所有重要的依赖关系,并通过后缀规则使每个文件都依赖于该头文件。这并不麻烦,而且您仍然节省了一些便携性。

        总之,我的建议是避免过度担心并发症。他们很少有回报。特别是,我从来没有喜欢过autotools,而且我肯定永远不会喜欢。 UNIX 的一切都是为了简单。不必要地使事情复杂化是违反 UNIX 精神的。 :)

        【讨论】:

          【解决方案7】:

          我想出了以下解决方案:

          # Author Matthieu Sieben (http://matthieusieben.com)
          # Version 23/10/2012
          #
          # This Makefile is licensed under the Creative Commons Attribution
          # Partage dans les Mêmes Conditions 2.0 Belgique License.
          # To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/2.0/be/.
          # Use at your own risk.
          #
          # Use Makefile.inc to write you own rules or to overwrite the default values defined here.
          
          SRCDIR  = ./src
          BINDIR  = ./bin
          
          SHELL   = /bin/bash
          CC      = /usr/bin/gcc
          LIBS    =
          CFLAGS  = -Wall -Wextra -Werror
          LDFLAGS =
          
          .PHONY: default all clean distclean
          .SUFFIXES: .c .o
          
          # make "all" the default target
          default: all
          
          -include Makefile.inc
          -include Makefile.d
          
          CFLAGS += -I$(SRCDIR)
          ifeq ($(DEBUG), 1)
              CFLAGS += -DDEBUG=1 -ggdb
          else
              CFLAGS += -DDEBUG=0 -O2
              LDFLAGS += -O2
          endif
          
          %.o: %.c
              @echo "  Compiling `basename $<`";
              @$(CC) $(CFLAGS) -o $@ -c $<;
          
          clean:
              @echo "Cleaning compiled files";
              @find $(SRCDIR) -name "*.o" -exec rm {} \;
              @[ -e Makefile.d ] && rm Makefile.d;
          
          distclean: clean
              @echo "Removing executables";
              @find $(BINDIR) -type f -exec rm {} \;
          
          Makefile.d: $(shell find $(SRCDIR) -type f -name "*.c")
              @echo "Building dependencies";
              @for file in `find $(SRCDIR) -name "*.c" -print`; do\
                  $(CC) $(CFLAGS) -MM -MT $${file/%.c/.o} $$file | tr -d "\n\\" >> Makefile.d.tmp;\
                  echo -e "\n" >> Makefile.d.tmp;\
              done;\
              for file in `find $(SRCDIR) -name "*.c" -exec grep -Hs "main(" {} \; | cut -f1 -d':'`; do\
                  execname=`basename $$file .c`;\
                  objs="$${file/%.c/.o}";\
                  for header in `$(CC) $(CFLAGS) -MM $$file | tr " " "\n" | grep ".h$$" | sort | uniq | tr "\n" " "`; do\
                      if [ -f $${header/%.h/.c} ]; then\
                          objs+=" $${header/%.h/.o}";\
                      fi;\
                      for obj in `grep "^$${header/%.h/.o}" Makefile.d.tmp | tr " " "\n" | grep ".h$$" | tr "\n" " "`; do\
                          if [ -f $${obj/%.h/.c} ]; then\
                              objs+=" $${obj/%.h/.o}";\
                          fi;\
                      done;\
                  done;\
                  objs=`echo -n "$$objs"  | tr " " "\n" | sort | uniq | tr "\n" " "`;\
                  echo "all: $$execname" >> Makefile.d.tmp;\
                  echo "$$execname: $(BINDIR)/$$execname" >> Makefile.d.tmp;\
                  echo "$(BINDIR)/$$execname: $$objs" >> Makefile.d.tmp;\
                  echo "  @echo \"Linking $$execname\";" >> Makefile.d.tmp;\
                  echo "  @\$$(CC) \$$(LDFLAGS) -o \$$@ $$objs \$$(LIBS);" >> Makefile.d.tmp;\
                  echo >> Makefile.d.tmp;\
              done;\
              mv Makefile.d.tmp $@;\
          

          它不是很健壮,因此请随时提供改进。下面是它的工作原理。 这里的想法是,对于$(SRCDIR) 中的每个 .c 文件,查找那些包含 main 函数 (grep "main(" src/*.c) 的文件。然后,对于每个本地包含 #include "myfile.h"myfile.o 添加到该可执行文件的对象列表中,然后在 Makefile.d 中写入上面的行

          编辑 这个新版本的脚本支持 bin 文件链接的依赖项。

          【讨论】:

          • 嗯...这仍然不完美,因为文件不是递归加载的。如果有人有想法...
          猜你喜欢
          • 2010-11-17
          • 2011-02-08
          • 1970-01-01
          • 1970-01-01
          • 2013-09-15
          • 2016-09-26
          • 1970-01-01
          • 2022-11-09
          相关资源
          最近更新 更多