在Linux和Windows上使用相同的Makefile进行make和nmake

31
我有一个简单的C程序(一个源文件),我想通过make在Linux上编译,在Windows上通过nmake编译。是否可能使用一个单独的Makefile实现这一点?
我考虑了以下内容:
ifeq($(MAKE), nmake)
    // nmake code here
else
    // make code here
endif
很遗憾,nmake似乎不理解ifeq,所以我无法使用它。 我有一个工作的makefile,但是那会产生非常丑陋的结果:
hello: hello.c
    $(CC) hello.c

这在两个系统上都可以工作。问题是结果取决于各自编译器的默认行为。在Linux下,我得到一个名为'a.out'的可执行文件,而不是'hello'。在Windows下,我得到了'hello.exe',但也有一个我不想要的'hello.obj'。

是否有另一种选择?或者我所尝试的绝对不可能实现吗?


1
你可以在Windows系统上构建或获取GNU make。 - Basile Starynkevitch
8
对于跨平台项目,我可以推荐使用CMake(http://www.cmake.org)。 - cli_hlt
我已经了解了cmake。但是我需要在大学的练习中使用nmake。 - j0ker
10个回答

16

这可能不是不可能,但很可能非常困难,反而编写两个makefile会更容易。

GNU make(在Linux中使用)和nmake都有包含指令,因此一些常见的内容可以放在一个通用的makefile中,然后通过主要的makefile进行包含。


我认为Qt在非Windows项目中使用gnu-make,在Windows项目中使用make,是吗? - KcFnMi

13

我希望使用同一个makefile包含文件,同时被Make和NMAKE使用。由于make识别注释行上的行继续符,但NMAKE不识别,这意味着我们可以为Make和NMAKE设置单独的指令。例如:

# NMAKE code here \
!ifndef 0 # \    
MV=move # \
RM=del # \
CP=copy # \
!else
# Make code here
MV=mv -f
RM=rm -f
CP=cp -f
# \
!endif

你只需要确保 NMAKE 特定的代码被 # \ 包含。


这个方法是可行的,尽管nmake不允许在命令上添加注释。你只能在宏和单行命令中使用这种技术。你可以使用 ; 将多个命令连接起来,使它们保持在一行上。 - Kuba hasn't forgotten Monica
这在 VS2017 中不起作用 - 反斜杠被解释为预期的内容,遗憾的是。 - Whinger
这太棒了! - ryanpcmcquen

13

你应该考虑使用 CMake 来完成这个任务。对于一个源文件,这将非常容易。以下是如何设置一个简单项目的方法:

cmake_minimum_required(VERSION 3.10)

# set the project name
project(Hello)

# add the executable
add_executable(Hello hello.c)
为了构建简单项目,您需要执行以下步骤(假设源文件和CMakeLists.txt文件与源文件hello.c在同一目录中):
mkdir build
cd build
cmake ..
cmake --build .

4
当我发布这个答案时,那条评论还没有出现。不过,我认为在没有作业限制的情况下,这个答案是解决问题的正确方式... - mevatron
1
CMake可以生成nmake,因此使用CMake并不是那么牵强附会。至少值得考虑。 - ideasman42
1
我不确定为什么迄今为止没有人批评这个答案。首先,尽管使用CMake是一个经典的选择,但它并不是问题的答案。其次,Windows没有make命令,因此首先提出了这个问题。如果你想使用CMake进行构建,那么cmake --build ..是正确的方法。我将编辑帖子,以便其他读者可以受益。 - Foad S. Farimani
我不同意“cmake”应该是正确选择的说法。它是完全不同的构建环境,有很多理由可以优先选择默认选项(Linux的make和VC的nmake)。为此引入一个(相当复杂的)构建系统,如cmake,似乎对我来说是一个错误的方向。 - Remo.D

7

我找不到一种使用通用的makefile在GNU Make和Microsoft NMAKE中同时工作的方法,主要是因为它们对于"include"和/或"if"指令的语法不兼容。Microsoft NMAKE需要使用!前缀来表示指令。例如:!if、!include等……

然而,如果允许使用单独的宏,则可以绕过此问题。以下是我目前找到的使makefile兼容GNU Make和Microsoft NMAKE的最佳方法:

  1. Microsoft NMAKE读取TOOLS.ini文件以获取默认的宏。
  2. Microsoft套件将.obj用作目标文件扩展名。
  3. GNU Make读取在MAKEFILES环境变量中定义的文件。
  4. GNU套件使用.o作为目标文件扩展名。
  5. GNU make不需要为目标指定可执行文件扩展名.exe。

注意:以下内容已在Microsoft Visual Studio 2015和MINGW32中进行了测试。

步骤1:创建以下DOS批处理文件,并在CMD提示符调用时运行它。

set MAKEFILES=TOOLS.gcc
call "C:\Program Files (x86)\Microsoft Visual Studio 14.0\VC\vcvarsall.bat"

步骤 2: 在您的工作目录下创建一个 TOOLS.ini 文件,如下所示:(此文件与您的项目依赖无关,除非可能涉及到库)

[NMAKE]
LDLIBS  =
CDEBUG  = /Zi
LDEBUG  = /debug:full
WDFLAGS = /wd4996 /wd4774 /wd4018 /wd4710 /wd4820
CFLAGS  = /nologo $(CDEBUG) /EHsc /Wall $(WDFLAGS)
LDFLAGS = /nologo $(LDEBUG)
RM      = del /F /Q
LINK     = "$(VCINSTALLDIR)bin\link" $(LDFLAGS)
CP    = copy
CC    = cl
CPP = $(CC) /P
X    = .exe
O    = .obj

.obj.exe:
    $(LINK) $** $(LOADLIBES) $(LDLIBS) /Out:$@

第三步:在您的工作目录下创建一个TOOLS.gcc文件,如下所示:(该文件与您的项目依赖项无关,除了可能的库)
(Note: 请注意,这里的“可能的库”指需要在此文件中指定的库,以保证代码能够正确编译运行。)
LD_LIBS =
LDLIBS  =
CDEBUG  = -g
LDEBUG  = -g
CFLAGS  = $(CDEBUG)
LDFLAGS = $(LDEBUG)
RM      = rm -f
LINK     = gcc $(LDFLAGS)
CP        = cp
CC        = gcc
CPP     = $(CC) -E
X        =
O        = .o

%: %.o
    $(LINK) $^ $(LOADLIBES) $(LDLIBS) -o $@

步骤4:按照下面的方式编辑您的makefile(注意$(X)和$(O)),只指定依赖项。

SHELL    = /usr/bin/sh
app: app1$(X) app2$(X)
app1$(X): app1$(O)
app2$(X): app2$(O)

clean:
    $(RM) *.exe *.o *.obj *.ilk *.pdb *.tmp *.i *~

步骤五:使用相同的 makefile,同时享受 GNU Make 和 Microsoft NMAKE 的便利。
$ nmake
$ make clean
$ nmake clean
$ make

5

我的解决方案是使用两个不同的文件名。(因为在不同的操作系统中,Makefile名称搜索的优先级不同)

对于Windows,我使用普通的“Makefile”。

对于Linux,根据这篇文章,我使用特殊的“GNUmakefile”。

这样nmake(Win)将会找到“Makefile”,而make(Linux)将会找到“GNUmakefile”。


你是在尝试回答问题还是提出问题?即使两者都是,请删除提问部分。通过“你的答案”字段和“发布你的答案”按钮发布的帖子应该只是答案,而不是问题。 - Yunnosch
1
这是我的解决方案。由于在Windows和Linux中默认的Makefile名称搜索优先级不同,这个解决方案会起作用。 - alan23273850
1
为什么你们两个不试着理解我的回复呢? - alan23273850
2
我同意和不同意@Das_Geek的看法,关于你的回答是否回答了发布的问题。我认为你暗示“是否有可能用单个makefile完成这个任务”的答案是否定的。然后你继续提供了解决该问题的另一种方法-我觉得这很好,但严格来说并不完全是OP所问的。在我看来,这个问题有点模糊,而你的回答也是如此。垃圾输入,垃圾输出。 - steve

3
解决方案:https://github.com/jaykrell/w3/blob/master/Makefile
# This one Makefile works with Microsoft nmake and GNU make.
# They use different conditional syntax, but each can be
# nested and inverted within the other.

all: default

ifdef MAKEDIR: # gmake: false; nmake: unused target
!ifdef MAKEDIR # gmake: not seen; nmake: true

#
# Microsoft nmake.
#

!else # and now the other
else

#
# GNU (Posix?) make.
#

endif    # gmake: close condition; nmake: not seen
!endif : # gmake: unused target; nmake close conditional

default: # default target for both

1

是的,您可以使用单个Makefile来完成此操作。这方面最好的资料是O'Reilly出版社的书籍:

GNU Make管理项目第三版,作者Robert Mecklenburg

请参阅第7章:可移植的Makefile。

简而言之,该技术是测试环境变量ComSpec,该变量指示Windows命令解释器是否存在:

ifdef COMSPEC
  MV ?= move
  RM ?= del
else
  MV ?= mv -f
  RM ?= rm -f
endif

我使用一个便携的Shell脚本将其包装起来,该脚本使用sed编辑Nmake或GNU make的makefile...

2
嗯,据我所知,?= 语法在 nmake 中无效。我认为你的例子只是检测是否在 W32 上运行 GNU make(而不是 nmake/GNUmake 多语言) - umläute
很好;另一方面,由于便携式Shell脚本是真正回答问题的魔力胶水,分享它也是一个不错的选择 :-) - umläute
因为GCC也存在于Windows上,并且在那里被广泛使用,这意味着当遇到这种类型的makefile时,Windows用户会比他们本来就要忍受的多一个头疼问题。 - Dúthomhas
@Dúthomhas 是的;我的脚本可以在Windows上使用GCC或CL。只要脚本编写得当,这不应该是个问题。 - Brian Tompsett - 汤莱恩

1

我突然想到了一些完全不同的东西。

如果你坚持使用你那个极其简单的Makefile,并且只是在你的环境变量中设置“标准”的变量CC和CFLAGS,就像这样:

  export CC=gcc

分别

  set CC=CL.EXE

并且

  export CFLAGS=-o myexecutable

分别

  set CFLAGS=/out:myexecutable.exe

可能会奏效。

请注意,我对要使用的确切选项并不确定,您需要自己找出来。但据我所知,两种make变体都识别相同的标志集。您甚至可以在各自的命令行上设置这些标志(但不能在makefile中设置,因为NMAKE使用不同的“ifeq”语法...)


1
最好编写一个简单的shell脚本和批处理文件来设置环境并调用make。 - Christoph

0

最近我尝试使用C预处理器从一个包含预处理器符号的模板Makefile.cc生成可移植的Makefile。到目前为止,效果出奇的好。第一个观察结果是NMAKE将会预扫描Tools.ini文件,而我提供在同一目录下。

[NMAKE]
MAKECONFIG=-D_NMAKE

然后我旁边有一个“真正的” Makefile,它仅使用 GNU Make 和 NMAKE 的公共子语言编写。

MAKEFILE=Makefile.mk
TEMPLATE=Makefile.cc

all: $(MAKEFILE)
    $(MAKE) -f $(MAKEFILE)

clean: $(MAKEFILE)
    $(MAKE) -f $(MAKEFILE) clean

$(MAKEFILE): $(TEMPLATE)
    $(CXX) $(MAKECONFIG) -E $(TEMPLATE) > $(MAKEFILE)

请注意,-E开关在编译器中非常普遍(至少是我所使用的三个主要编译器:GCC、Clang和CL),只用于预处理文件。在GNU Make中,$(MAKECONFIG)扩展为空,但在NMAKE中它提供了预处理器变量来声明自身。由于你的模板Makefile.cc可以使用#ifdef检查它,以及检查编译器声明自身的常见变量,因此你可以为'make'程序、操作系统和使用的编译器定制你的Makefile.mk。
如果你有任何'make',你可能已经有了C编译器;不需要安装额外的软件如CMake或autotools。它使用古老的机制,因此可能适用于许多环境。从我到目前为止了解到的情况来看,它确实很快。至少比在autotools中运行配置步骤要快。我面临的唯一缺点是它限制了你的Make规则的样式必须在同一行上,因为预处理器会更改代码的缩进。此外,预处理器会输出带有#标签的行,但由于这些在Makefile中开始注释,因此它们会被忽略。
我有一个相对较小的C++项目,其中包含一个名为Makefile.cc的文件,其代码片段如下所示。它可以在GNU Make或NMAKE上使用GCC、Clang或CL编译,并且可以在Windows或POSIX环境中运行。但是我还没有支持BSD Make或测试任何其他编译器。
// Make Version

#ifdef _NMAKE
# define ifdef !ifdef
# define ifndef !ifndef
# define else !else
# define endif !endif
# define err(x) !error x
# define cat(x, y) x=$(x) y
#else // GNU Make
# define err(x) $(error x)
# define cat(x, y) x += y
#endif

// System Commands

ifdef SHELL
RM=rm -f
else
ifdef COMSPEC
RM=del /f
else
err("Cannot determine your system commands.")
endif // COMSPEC
endif // SHELL

// Project Variables

STD=c++17
SRC=test.cpp dbg.cpp dir.cpp dll.cpp env.cpp err.cpp fifo.cpp file.cpp shm.cpp sig.cpp socket.cpp sys.cpp xdg.cpp
BIN=test

.SUFFIXES: .cpp .hpp .o .d .obj .pdb .lib .exp .ilk .log .i .db

// Operating system

#ifdef _WIN32
cat(CFLAGS, -D_WIN32)
EXE=$(BIN).exe
#else
cat(CFLAGS, -D_POSIX_C_SOURCE)
cat(LDFLAGS, -ldl -lrt -lpthread)
EXE=$(BIN)
#endif

// Make Targets

all: $(EXE)

clean: ; $(RM) $(EXE) *.o *.d *.obj *.pdb *.lib *.exp *.ilk *.log *.i

// Compiler Options

#ifdef _MSC_VER

cat(CFLAGS, -nologo -std:$(STD) -W4 -DNOMINMAX -D_CRT_SECURE_NO_WARNINGS -EHsc -permissive-)
ifndef NDEBUG
cat(CFLAGS, -Zi)
endif
cat(LDFLAGS, -nologo)

OBJ=$(SRC:.cpp=.obj)

$(EXE): $(OBJ); $(CXX) $(LDFLAGS) $(OBJ) -Fe$@
.cpp.obj: ; $(CXX) $(CFLAGS) -c $<

#elif defined(__GNUC__) || defined(__llvm__) || defined(__clang__)

cat(CFLAGS, -std=$(STD) -Wall -Wextra -Wpedantic -MP -MMD)
ifndef NDEBUG
cat(CFALGS, -g)
endif
cat(LDFLAGS, -rdynamic)

OBJ=$(SRC:.cpp=.o)

$(EXE): $(OBJ); $(CXX) $(LDFLAGS) $(OBJ) -o $@
.cpp.o: ; $(CXX) $(CFLAGS) -c $<

# ifndef _NMAKE
-include $(SRC:.cpp=.d)
# endif
#else
# error "Cannot determine your compiler."
#endif

-1

有没有可能使用gnu sed或perl脚本将主机的Makefile翻译成Microsoft兼容的NMakefile?毕竟,Makefiles是文本文件,为您正在使用的任何辅助工具提供输入。Sed和Perl都适用于Linux和Windows。


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