可移植的Makefile创建目录

6
我希望通过制作一个相对通用的Makefile来减轻后续工作的负担,以便在最小程度地修改Makefile的情况下为我组合相对简单的C++项目。
到目前为止,我已经让它使用同一目录和指定子目录中的所有.cpp文件,并将所有这些文件放置在obj子目录的匹配结构中,并将结果文件放置在名为bin的另一个子目录中。基本上就是我想要的。
然而,尝试创建所需的obj和bin目录(如果它们不存在)在跨平台上提供了困难 - 具体来说,我只是在Windows 7和Ubuntu(无法记住版本)上进行测试,我无法同时在两个平台上使其正常工作。
Windows错误解读mkdir -p dir并创建一个-p目录,显然,两个平台分别使用\和/作为路径分隔符 - 当使用错误的分隔符时,我会收到错误消息。
以下是与Makefile相关的一些选定部分:
# Manually edited directories (in this example with forward slashes)
SRC_DIR = src src/subdir1 src/subdir2

# Automagic object directories + the "fixed" bin directory
OBJ_DIR = obj $(addprefix obj/,$(SRC_DIR))
BIN_DIR = bin

# Example build target
debug: checkdirs $(BIN)

# At actual directory creation
checkdirs: $(BIN_DIR) $(OBJ_DIR)
$(BIN_DIR):
    @mkdir $@

$(OBJ_DIR):
    @mkdir -p $@

以下内容是关于IT技术的翻译:

这篇文章是我在过去一周左右从各种来源(主要是Stack Overflow)收集而来的,所以如果其中存在错误或者任何不合适的做法,请务必告知我。

问题简述:

有没有一种简单的方法可以从一个单一的makefile中实现目录创建,并尽可能地提供可移植性?


那些不理解 auto{make} 的人注定会徒劳无功地重复造轮子。你可以通过一个简单的 Makefile.am 文件来实现这一点。请注意,我并不推荐您立即转向 automake,但是要知道这些问题已经被解决了很多次。最终,您可能会转向一个已经为您解决这些问题的工具,而 automake 就是其中之一。 - William Pursell
@William 好的,这只是一个学习练习(在此之前,我对makefile的理解非常有限 - 几乎所有之前构建的东西都是通过IDE的“让它为您工作”的魔法完成的)。听起来我对makefile的要求过于深入了,所以也许我会把automake添加到我的“待研究”清单中,等我有更多时间可用时再去看看。 - DMA57361
3个回答

11

我不懂autoconf。 我遇到的每一次经历都很烦琐。这个zwol的解决方案的问题是,Windows上的mkdir会返回一个错误,而不像Linux上的mkdir -p那样。 这可能会破坏您的make规则。 解决方法是在命令之前使用-标志来忽略错误,像这样:

-mkdir dir

这个问题在于make仍然会对用户产生难看的警告。解决方法是在mkdir失败后运行一个“总是为真”的命令,如此所述

mkdir dir || true

这个问题在于Windows和Linux对于true的语法不同。

无论如何,我花了太多时间在这上面。我想要一个可以在类POSIX和Windows环境下运行的make文件。最后我得出了以下结果:

ifeq ($(shell echo "check_quotes"),"check_quotes")
   WINDOWS := yes
else
   WINDOWS := no
endif

ifeq ($(WINDOWS),yes)
   mkdir = mkdir $(subst /,\,$(1)) > nul 2>&1 || (exit 0)
   rm = $(wordlist 2,65535,$(foreach FILE,$(subst /,\,$(1)),& del $(FILE) > nul 2>&1)) || (exit 0)
   rmdir = rmdir $(subst /,\,$(1)) > nul 2>&1 || (exit 0)
   echo = echo $(1)
else
   mkdir = mkdir -p $(1)
   rm = rm $(1) > /dev/null 2>&1 || true
   rmdir = rmdir $(1) > /dev/null 2>&1 || true
   echo = echo "$(1)"
endif

这些函数/变量的使用方式如下:

rule:
    $(call mkdir,dir)
    $(call echo,  CC      $@)
    $(call rm,file1 file2)
    $(call rmdir,dir1 dir2)

定义的原理:

  • mkdir: 修复路径并忽略任何错误。
  • del: 在Windows中,如果指定要删除的文件集包含不存在的目录,则del不会删除任何文件。例如,如果您尝试删除一组文件,并且列表中有dir/file.c,但dir不存在,则不会删除任何文件。此实现通过为每个文件调用del来解决该问题。
  • rmdir: 修复路径并忽略任何错误。
  • echo: 保留输出的外观,并在Windows中不显示多余的""

我花了很多时间在这上面。也许我最好把时间花在学习autoconf上。

另见:

  1. 检测操作系统的makefile

1
这正是我一直在寻找的。但由于某种原因,MinGW 崩溃并显示“Interrupt/Exception caught (code = 0xc0000005)”。 调试了一段时间后,我发现它与结束字符有关,所以我将其放在 $(shell) 中,一切都正常工作了。此外,在 Windows 上使用“if not exist”有一个更简单的 mkdir 解决方案: mkdir = $(shell if not exist $(subst /,,$(1)) mkdir $(subst /,,$(1))) - Hugo Aboud

9

在Windows系统中,mkdir命令相当于Unix系统下带有-p选项的mkdir命令。同时,在处理反斜杠问题时,可以使用$(subst)命令。因此,在Windows系统中,您可以使用以下命令:

$(BIN_DIR) $(OBJ_DIR):
        mkdir $(subst /,\\,$@)

在Unix系统中,您需要执行以下操作:

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

选择这些选项在Makefile中实现起来并不实际,这就是Autoconf的作用。
另外需要注意的是,在你的Makefile中永远不要使用@command功能。总有一天,你需要在你不能直接访问的机器上调试你的构建过程,在那一天,你会后悔的。

好的,这很方便,我希望尽可能减少版本之间所需的差异,直到我开始学习任何类型的自动版本。但是,您能否简要解释一下@command功能是做什么的?如我在问题中所述,我已经在最小的先前知识基础上从各种来源构建了makefile,因此必须从某个地方拉取它,但现在无法记住从哪里或相关性... - DMA57361
2
如果在任何Makefile命令前面加上@,Make在执行该命令之前不会将其打印到终端。这似乎是一个很棒的功能--少了一些滚动的噪音,但当你只能使用终端传输记录来调试构建过程时,你就会陷入困境。 - zwol
关于最后一句话:这种行为(回显或不回显命令)可以通过make命令行轻松控制,通过定义(或不定义)一个持有“@”符号的变量,并在每个命令前面加上这个变量来实现。请参阅http://skramm.blogspot.fr/2013/04/writing-portable-makefiles.html,第5节。 - kebs

0

我通过创建一个名为mkdir.py的Python脚本并从Makefile中调用它来解决了可移植性问题。但是这个方案的局限性在于必须安装Python,不过在UNIX的任何版本中都应该能做到。

#!/usr/bin/env python

# Cross-platform mkdir command.

import os
import sys

if __name__=='__main__':
    if len(sys.argv) != 2:
        sys.exit('usage: mkdir.py <directory>')
    directory = sys.argv[1]
    try:
        os.makedirs(directory)
    except OSError:
        pass

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接