我经常在几台不同的计算机和几个不同的操作系统上工作,它们是 Mac OS X、Linux 或 Solaris。对于我正在处理的项目,我从远程 git 存储库中提取代码。
无论我在哪个终端,我都希望能够处理我的项目。到目前为止,我已经找到了通过在每次切换计算机时更改生成文件来绕过操作系统更改的方法。然而,这很乏味并且会引起很多头痛。
如何修改我的 makefile 以检测我正在使用的操作系统并相应地修改语法?
这是生成文件:
cc = gcc -g
CC = g++ -g
yacc=$(YACC)
lex=$(FLEX)
all: assembler
assembler: y.tab.o lex.yy.o
$(CC) -o assembler y.tab.o lex.yy.o -ll -l y
assembler.o: assembler.c
$(cc) -o assembler.o assembler.c
y.tab.o: assem.y
$(yacc) -d assem.y
$(CC) -c y.tab.c
lex.yy.o: assem.l
$(lex) assem.l
$(cc) -c lex.yy.c
clean:
rm -f lex.yy.c y.tab.c y.tab.h assembler *.o *.tmp *.debug *.acts
这里已经有很多很好的答案,但我想分享一个更完整的例子:
不假定 Windows 上存在 uname
还检测处理器
此处定义的 CCFLAGS 不一定是推荐的或理想的;它们正是我添加 OS/CPU 自动检测的项目碰巧正在使用的。
ifeq ($(OS),Windows_NT)
CCFLAGS += -D WIN32
ifeq ($(PROCESSOR_ARCHITEW6432),AMD64)
CCFLAGS += -D AMD64
else
ifeq ($(PROCESSOR_ARCHITECTURE),AMD64)
CCFLAGS += -D AMD64
endif
ifeq ($(PROCESSOR_ARCHITECTURE),x86)
CCFLAGS += -D IA32
endif
endif
else
UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Linux)
CCFLAGS += -D LINUX
endif
ifeq ($(UNAME_S),Darwin)
CCFLAGS += -D OSX
endif
UNAME_P := $(shell uname -p)
ifeq ($(UNAME_P),x86_64)
CCFLAGS += -D AMD64
endif
ifneq ($(filter %86,$(UNAME_P)),)
CCFLAGS += -D IA32
endif
ifneq ($(filter arm%,$(UNAME_P)),)
CCFLAGS += -D ARM
endif
endif
不带参数的 uname 命令 (http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/uname.1.html) 应该告诉您操作系统名称。我会使用它,然后根据返回值制作条件。
例子
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
# do something Linux-y
endif
ifeq ($(UNAME), Solaris)
# do something Solaris-y
endif
使用两个简单的技巧检测操作系统:
首先是环境变量 OS
然后是 uname 命令
ifeq ($(OS),Windows_NT) # is Windows_NT on XP, 2000, 7, Vista, 10...
detected_OS := Windows
else
detected_OS := $(shell uname) # same as "uname -s"
endif
或者更安全的方式,如果不在 Windows 上且 uname
不可用:
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell sh -c 'uname 2>/dev/null || echo Unknown')
endif
如果您想区分 Cygwin/MinGW/MSYS/Windows,Ken Jackson 提出了一个有趣的替代方案。请参阅看起来像这样的 his answer:
ifeq '$(findstring ;,$(PATH))' ';'
detected_OS := Windows
else
detected_OS := $(shell uname 2>/dev/null || echo Unknown)
detected_OS := $(patsubst CYGWIN%,Cygwin,$(detected_OS))
detected_OS := $(patsubst MSYS%,MSYS,$(detected_OS))
detected_OS := $(patsubst MINGW%,MSYS,$(detected_OS))
endif
然后您可以根据 detected_OS
选择相关内容:
ifeq ($(detected_OS),Windows)
CFLAGS += -D WIN32
endif
ifeq ($(detected_OS),Darwin) # Mac OS X
CFLAGS += -D OSX
endif
ifeq ($(detected_OS),Linux)
CFLAGS += -D LINUX
endif
ifeq ($(detected_OS),GNU) # Debian GNU Hurd
CFLAGS += -D GNU_HURD
endif
ifeq ($(detected_OS),GNU/kFreeBSD) # Debian kFreeBSD
CFLAGS += -D GNU_kFreeBSD
endif
ifeq ($(detected_OS),FreeBSD)
CFLAGS += -D FreeBSD
endif
ifeq ($(detected_OS),NetBSD)
CFLAGS += -D NetBSD
endif
ifeq ($(detected_OS),DragonFly)
CFLAGS += -D DragonFly
endif
ifeq ($(detected_OS),Haiku)
CFLAGS += -D Haiku
endif
笔记:
命令 uname 与 uname -s 相同,因为选项 -s (--kernel-name) 是默认值。看看为什么 uname -s 比 uname -o 好。
使用 OS(而不是 uname)简化了识别算法。您仍然可以单独使用 uname,但您必须处理 if/else 块来检查所有 MinGW、Cygwin 等变体。
在不同的 Windows 版本上,环境变量 OS 始终设置为“Windows_NT”(参见 Wikipedia 上的 %OS% 环境变量)。
OS 的替代方案是环境变量 MSVC(它检查 MS Visual Studio 的存在,参见使用 Visual C++ 的示例)。
下面我提供了一个使用 make
和 gcc
构建共享库的完整示例:*.so
或 *.dll
,具体取决于平台。该示例尽可能简单以便更易于理解。
要在 Windows 上安装 make
和 gcc
,请参阅 Cygwin 或 MinGW。
我的示例基于五个文件
├── lib
│ └── Makefile
│ └── hello.h
│ └── hello.c
└── app
└── Makefile
└── main.c
提醒: Makefile
使用 tabulation 缩进。复制粘贴下面的示例文件时要小心。
两个 Makefile 文件
1.lib/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = hello.dll
endif
ifeq ($(uname_S), Linux)
target = libhello.so
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -fPIC -o $@
# -c $< => $< is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o $@ => $@ is the target => Output file (-o) is hello.o
$(target): hello.o
gcc $^ -shared -o $@
# $^ => $^ expand to all prerequisites (after ':') => hello.o
# -shared => Generate shared library
# -o $@ => Output file (-o) is $@ (libhello.so or hello.dll)
2.应用程序/Makefile
ifeq ($(OS),Windows_NT)
uname_S := Windows
else
uname_S := $(shell uname -s)
endif
ifeq ($(uname_S), Windows)
target = app.exe
endif
ifeq ($(uname_S), Linux)
target = app
endif
#ifeq ($(uname_S), .....) #See https://stackoverflow.com/a/27776822/938111
# target = .....
#endif
%.o: %.c
gcc -c $< -I ../lib -o $@
# -c $< => compile (-c) $< (first file after :) = main.c
# -I ../lib => search headers (*.h) in directory ../lib
# -o $@ => output file (-o) is $@ (target) = main.o
$(target): main.o
gcc $^ -L../lib -lhello -o $@
# $^ => $^ (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o $@ => output file (-o) is $@ (target) = "app.exe" or "app"
要了解更多信息,请阅读 cfi 指出的 Automatic Variables documentation。
源代码
lib/hello.h
#ifndef HELLO_H_
#define HELLO_H_
const char* hello();
#endif
- lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
- 应用程序/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
构建
修复 Makefile
的复制粘贴(用一个制表符替换前导空格)。
> sed 's/^ */\t/' -i */Makefile
make
命令在两个平台上是相同的。给定的输出在类 Unix 操作系统上:
> make -C lib
make: Entering directory '/tmp/lib'
gcc -c hello.c -fPIC -o hello.o
# -c hello.c => hello.c is first file after ':' => Compile hello.c
# -fPIC => Position-Independent Code (required for shared lib)
# -o hello.o => hello.o is the target => Output file (-o) is hello.o
gcc hello.o -shared -o libhello.so
# hello.o => hello.o is the first after ':' => Link hello.o
# -shared => Generate shared library
# -o libhello.so => Output file (-o) is libhello.so (libhello.so or hello.dll)
make: Leaving directory '/tmp/lib'
> make -C app
make: Entering directory '/tmp/app'
gcc -c main.c -I ../lib -o main.o
# -c main.c => compile (-c) main.c (first file after :) = main.cpp
# -I ../lib => search headers (*.h) in directory ../lib
# -o main.o => output file (-o) is main.o (target) = main.o
gcc main.o -L../lib -lhello -o app
# main.o => main.o (all files after the :) = main.o (here only one file)
# -L../lib => look for libraries in directory ../lib
# -lhello => use shared library hello (libhello.so or hello.dll)
# -o app => output file (-o) is app.exe (target) = "app.exe" or "app"
make: Leaving directory '/tmp/app'
运行
应用程序需要知道共享库在哪里。
在 Windows 上,一个简单的解决方案是复制应用程序所在的库:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
在类 Unix 操作系统上,您可以使用 LD_LIBRARY_PATH
环境变量:
> export LD_LIBRARY_PATH=lib
在 Windows 上运行命令:
> app/app.exe
hello
在类 Unix 操作系统上运行命令:
> app/app
hello
uname
不是 Linux 时,我的脚本假定平台是 Windows。我只是举了一个你可能不需要的例子,但这可能有助于某人(在网络上)搜索一种为两个平台实现 Makefile
的方法 ;-) 我应该在我的答案中改变什么?干杯
lib/Makefile
示例中,target
用于 .so
与 .dll
。 app/makefile
的并行示例对于应用程序文件名的 empty string
与 .exe
比较很有用。例如,我通常不会在类 Unix 操作系统上看到 app.exe
。 ;-)
我最近在做实验,以回答我问自己的这个问题。以下是我的结论:
由于在 Windows 中,您无法确定 uname
命令是否可用,因此您可以使用 gcc -dumpmachine
。这将显示编译器目标。
如果你想做一些交叉编译,在使用 uname
时也可能会出现问题。
以下是 gcc -dumpmachine
可能输出的示例列表:
mingw32
i686-pc-cygwin
x86_64-redhat-linux
您可以像这样检查 makefile 中的结果:
SYS := $(shell gcc -dumpmachine)
ifneq (, $(findstring linux, $(SYS)))
# Do Linux things
else ifneq(, $(findstring mingw, $(SYS)))
# Do MinGW things
else ifneq(, $(findstring cygwin, $(SYS)))
# Do Cygwin things
else
# Do things for others
endif
它对我来说效果很好,但我不确定这是获取系统类型的可靠方法。至少 MinGW 是可靠的,这就是我所需要的,因为它不需要 Windows 中的 uname
命令或 MSYS 包。
总而言之,uname
为您提供了您正在编译的系统 on,而 gcc -dumpmachine
为您提供了您正在编译的系统 for。
uname
不是随 MinGW
一起出现的吗?不过,关于交叉编译的额外说明很棒。
$(shell $(CC) -dumpmachine)
。从 OS X Sierra 开始,-dumpmachine 命令适用于 Clang。
x86_64-apple-darwin16.6.0
,无论您称它为 gcc
、cc
还是 clang
,它都有效,但不是 cl
git makefile 包含许多示例,说明如何在不使用 autoconf/automake 的情况下进行管理,但仍可在众多 unixy 平台上工作。
if (!usesAutotools(git)) aversionTo(autotools) = justified;
我还要澄清一下,这只是我不喜欢的工具。我敢肯定 Autotools 的人都是好人。
更新:我现在认为这个答案已经过时了。我在下面发布了一个新的完美解决方案。
如果您的 makefile 可能在非 Cygwin Windows 上运行,则 uname
可能不可用。这很尴尬,但这是一个潜在的解决方案。您必须首先检查 Cygwin 以排除它,因为它的 PATH
环境变量中也有 WINDOWS。
ifneq (,$(findstring /cygdrive/,$(PATH)))
UNAME := Cygwin
else
ifneq (,$(findstring WINDOWS,$(PATH)))
UNAME := Windows
else
UNAME := $(shell uname -s)
endif
endif
uname
,它会给我 MINGW。我的意思是,我仍然有 uname
而不使用 Cygwin。我也有 git bash,但我没有尝试过 uname(现在我在 Linux 中)。您能告诉我如何将这两者合并到您的代码中吗?
uname
无法运行,我们会理解我们在 Windows 中吗?
这就是 GNU 的 automake/autoconf 旨在解决的工作。你可能想调查他们。
或者,您可以在不同平台上设置环境变量,并使 Makefile 以它们为条件。
make
做我想做的事。我现在也想进入 automake/autoconf 吗? - 不。 可以在 makefile 中完成,当然应该在 makefile 中完成,只要这样我每次想修改 compile & 时就没有几个停止点。关联。
我今天遇到了这个问题,我在 Solaris 上需要它,所以这里有一个 POSIX 标准的方法来做(非常接近)这个。
#Detect OS
UNAME = `uname`
# Build based on OS name
DetectOS:
-@make $(UNAME)
# OS is Linux, use GCC
Linux: program.c
@SHELL_VARIABLE="-D_LINUX_STUFF_HERE_"
rm -f program
gcc $(SHELL_VARIABLE) -o program program.c
# OS is Solaris, use c99
SunOS: program.c
@SHELL_VARIABLE="-D_SOLARIS_STUFF_HERE_"
rm -f program
c99 $(SHELL_VARIABLE) -o program program.c
我终于找到了为我解决这个问题的完美解决方案。
ifeq '$(findstring ;,$(PATH))' ';'
UNAME := Windows
else
UNAME := $(shell uname 2>/dev/null || echo Unknown)
UNAME := $(patsubst CYGWIN%,Cygwin,$(UNAME))
UNAME := $(patsubst MSYS%,MSYS,$(UNAME))
UNAME := $(patsubst MINGW%,MSYS,$(UNAME))
endif
UNAME 变量设置为 Linux、Cygwin、MSYS、Windows、FreeBSD、NetBSD(或者可能是 Solaris、Darwin、OpenBSD、AIX、HP-UX)或未知。然后可以在 Makefile 的其余部分进行比较,以分离任何操作系统敏感的变量和命令。
关键是 Windows 使用分号来分隔 PATH 变量中的路径,而其他所有人都使用冒号。 (可以在名称中创建一个带有 ';' 的 Linux 目录并将其添加到 PATH 中,这会破坏这一点,但谁会这样做呢?)这似乎是检测本机 Windows 风险最小的方法,因为它不需要外壳调用。 Cygwin 和 MSYS PATH 使用冒号,因此为它们调用 uname。
请注意,OS 环境变量可用于检测 Windows,但不能用于区分 Cygwin 和原生 Windows。测试引号的回声是可行的,但它需要一个 shell 调用。
不幸的是,Cygwin 在 uname 的输出中添加了一些版本信息,所以我添加了“patsubst”调用以将其更改为“Cygwin”。此外,MSYS 的 uname 实际上有三个可能的输出,以 MSYS 或 MINGW 开头,但我也使用 patsubst 将所有输出转换为“MSYS”。
如果区分路径上带有和不带有 uname.exe 的本地 Windows 系统很重要,则可以使用这一行来代替简单的赋值:
UNAME := $(shell uname 2>NUL || echo Windows)
当然,在所有情况下都需要 GNU make,或者另一个支持所使用功能的 make。
这是一个简单的解决方案,用于检查您是否处于 Windows 或类似 posix 的 (Linux/Unix/Cygwin/Mac) 环境中:
ifeq ($(shell echo "check_quotes"),"check_quotes")
WINDOWS := yes
else
WINDOWS := no
endif
它利用了 echo 在类 posix 和 Windows 环境中都存在的事实,并且在 Windows 中,shell 不会过滤引号。
$PATH
可能引用另一个 echo
(我的确实...)
-mwindows
标志或在 .dll
或 .so
之间进行选择,这将失败。
请注意,Makefile 对间距非常敏感。这是一个在 OS X 上运行额外命令并在 OS X 和 Linux 上运行的 Makefile 示例。不过,总的来说,autoconf/automake 是解决任何不平凡的事情的方法。
UNAME := $(shell uname -s) CPP = g++ CPPFLAGS = -pthread -ansi -Wall -Werror -pedantic -O0 -g3 -I /nexopia/include LDFLAGS = -pthread -L/nexopia/lib -lboost_system HEADERS = data_structures.h http_client.h load.h lock.h search.h server.h thread.h utility.h OBJECTS = http_client.o load.o lock.o search.o server.o thread.o utility.o vor.o all: vor clean: rm -f $(OBJECTS) vor vor: $(OBJECTS) $(CPP) $(LDFLAGS) -o vor $(OBJECTS) ifeq ($(UNAME),Darwin) # Set the Boost library location install_name_tool -change libboost_system.dylib /nexopia/lib/libboost_system.dylib vor endif %.o: %.cpp $(HEADERS) Makefile $(CPP) $(CPPFLAGS) -c $
另一种方法是使用“配置”脚本。如果您已经在 makefile 中使用了一个,则可以结合使用 uname 和 sed 来解决问题。首先,在您的脚本中,执行以下操作:
UNAME=uname
然后,为了把它放在你的 Makefile 中,从 Makefile.in 开始,它应该有类似的东西
UNAME=@@UNAME@@
在里面。
在 UNAME=uname
位之后的配置脚本中使用以下 sed 命令。
sed -e "s|@@UNAME@@|$UNAME|" < Makefile.in > Makefile
现在您的 makefile 应该已根据需要定义了 UNAME
。剩下的就是 if/elif/else 语句!
我有一个案例,我必须检测两个版本的 Fedora 之间的差异,以调整 inkscape 的命令行选项:
- 在 Fedora 31 中,默认的 inkscape 是 1.0beta,它使用 --export-file
-在 Fedora 中31,默认的 inkscape 是 0.92,它使用 --export-pdf
我的 Makefile 包含以下内容
# set VERSION_ID from /etc/os-release
$(eval $(shell grep VERSION_ID /etc/os-release))
# select the inkscape export syntax
ifeq ($(VERSION_ID),31)
EXPORT = export-file
else
EXPORT = export-pdf
endif
# rule to convert inkscape SVG (drawing) to PDF
%.pdf : %.svg
inkscape --export-area-drawing $< --$(EXPORT)=$@
这是有效的,因为 /etc/os-release
包含一行
VERSION_ID=<value>
因此 Makefile 中的 shell 命令返回字符串 VERSION_ID=<value>
,然后 eval 命令对此进行操作以设置 Makefile 变量 VERSION_ID
。这显然可以针对其他操作系统进行调整,具体取决于元数据的存储方式。请注意,在 Fedora 中没有提供操作系统版本的默认环境变量,否则我会使用它!
我还没有看到任何人谈论的另一种方法是使用内置变量 SHELL
。用作 shell 的程序取自变量 SHELL
。在 MS-Windows 系统上,它很可能是带有 .exe
扩展名的可执行文件(如 sh.exe
)。
在这种情况下,以下条件测试:
ifeq ($(suffix $(SHELL)),.exe)
# Windows system
else
# Non-Windows system
endif
与使用环境变量 OS
的结果相同:
ifeq ($(OS),Windows_NT)
# Windows system
else
# Non-Windows system
endif
然而,后者似乎是最受欢迎的解决方案,所以我建议你坚持下去。
PROCESSOR_ARCHITECTURE
envvar 似乎是虚拟化的,具体取决于进程是 32 位还是 64 位。因此,如果您的make
是 32 位,并且您正在尝试构建 64 位应用程序,它将失败。将它与PROCESSOR_ARCHITEW6432
结合使用对我有用(参见 this 和 that)make
团队添加几个带有 os 和 arch 的魔法变量会很好,可能太麻烦了。OS
无关紧要。将 unset 视为空,这将导致跳转到基于uname
的块。你只需要在那里添加一个 FreeBSD 检查。/bin/sh: -c: line 0: syntax error near unexpected token
,Windows_NT' /bin/sh: -c: 第 0 行:ifeq (,Windows_NT)' make: *** [os] Error 2
if
、else
和endif
不得缩进(在我的实验中)。我也只能在目标块的外部工作