Test the support of a linker flag - makefile

I need to test the support of a specific linker flag (--no-undefined) before eventually making it part of a Makefile recipe.
This linker flag is not guaranteed to be supported on all platforms (as a matter of fact, it breaks macosx link stage), so it's important to only enable it when it's actually supported.
I lean towards a runtime test, which seems preferable to a static list of compilers / systems which would be more difficult to maintain.
Preferably, the test would be run from the Makefile, which would then conditionally set the flag.

The most reliable test would be a linking test, i.e. try to actually link something. This kind of test would depend on whether you are linking through the compiler or directly with the linker. My approach would be to create a generic template for testing arbitrary flag so it could be reused for different flags in different places, e.g.:
$ cat Makefile
CHECK_CC_FLAGS := -Wl,--no-undefined -Wl,--whatever
CHECK_LD_FLAGS := --no-undefined --whatever
define check_cc_flag
$(shell echo 'int main() { return 0; }' | $(CC) $(1) -xc - 2>/dev/null && echo $(1))
endef
define check_ld_flag
$(shell $(LD) $(1) -e 0 /dev/null 2>/dev/null && echo $(1))
endef
# If linking with $(CC)
test: LDFLAGS += $(foreach flag,$(CHECK_CC_FLAGS),$(call check_cc_flag,$(flag)))
# If linking with $(LD)
test_ld: LDFLAGS += $(foreach flag,$(CHECK_LD_FLAGS),$(call check_ld_flag,$(flag)))
test_ld: test.o
$(LD) $(LDFLAGS) -o $# $<
The template tries to run the compiler or linker and if it succeeds (i.e. exits with 0) it will print out the flag, otherwise the output will be empty. May be more cumbersome if the compiler and/or linker do not behave good (return 0 on failed attempts).
Actual output on Ubuntu 20.04 LTS:
$ make test
cc -c -o test.o test.c
cc -Wl,--no-undefined test.o -o test
$ make test_ld
ld --no-undefined -o test_ld test.o
ld: warning: cannot find entry symbol _start; defaulting to 0000000000401000

Related

File not found when using variable file names

I've just started trying to use static pattern rules and for loops together within makefiles, I'm still relatively new to using makefiles so please forgive me if I've missed something obvious.
In the code below I have tried to use a for loop to create 6 executables, two for each unique file.
Here is the makefile:
vpath %.h ../headers/
CXX := g++
CXXFLAGS := -std=c++11 -I../headers/
LDFLAGS :=
SUFFIX := fileA fileB fileC
memory-%.exe: primary-%.o memory.cpp
$(CXX) $(CXXFLAGS) $^ -o $#
timing-%.exe: primary-%.o timing.cpp
$(CXX) $(CXXFLAGS) $^ -o $#
all: for i in $(SUFFIX); \
do \
testing-$$i.exe: primary-$$i.o; \
memory-$$i.exe: primary-$$i.o; \
done
I am met with the error:
\bin\sh: 3: memory-fileA.exe:: not found
\bin\sh: 4: timing-fileA.exe:: not found
\bin\sh: 3: memory-fileB.exe:: not found
\bin\sh: 4: timing-fileB.exe:: not found
\bin\sh: 3: memory-fileC.exe:: not found
\bin\sh: 4: timing-fileC.exe:: not found
make: *** [all] Error 127
Is this even possible in the first place? I was just wondering if it were possible to be efficient using this method.
Any help is appreciated as I'd like to know more about the possibilities that makefiles allow.
Thank you.
You are mixing shell and make constructs. As tripleee pointed the recipes of make rules are shell scripts, not other make rules.
Moreover, there are a few issues with your Makefile:
You explain that you want to use static pattern rules but what you wrote is "simple" pattern rules.
You do not need to quote your suffixes. And you should not, make is not the shell, it preserves them. You will get errors because of this.
Your use of the standard CXXFLAGS make variable is extremely unusual. Traditionally it is limited to the compiler's flags, not the compiler itself for which CXX is used.
You are compiling source files and linking simultaneously. This too is not that usual. It causes useless re-compilations.
The c++11 option of g++ is new to me. Are you sure it is not -std=c++11?
The vpath directive is useless because you do not express dependencies on the header files. But let's keep it, I guess you do not show everything.
All-in-all, you can probably achieve want you want with:
vpath %.h ../headers/
CXX := g++
CXXFLAGS := -std=c++11 -I../headers/
LDFLAGS :=
SUFFIX := fileA fileB fileC
TESTING := $(patsubst %,testing-%.exe,$(SUFFIX))
MEMORY := $(patsubst %,memory-%.exe,$(SUFFIX))
.PHONY: all
all: $(TESTING) $(MEMORY)
%.o: %.cpp
$(CXX) $(CXXFLAGS) -c $^ -o $#
$(TESTING): testing-%.exe: primary-%.o memory.o
$(CXX) $(LDFLAGS) $^ -o $#
$(MEMORY): memory-%.exe: primary-%.o timing.o
$(CXX) $(LDFLAGS) $^ -o $#
The %.o: %.cpp... rule is a pattern rule. It tells make how to produce any object file from the corresponding C++ source file. The two last rules are really static pattern rules. The first of the two, for instance, declares that each target testing-<suffix>.exe listed in $(TESTING) depends on the corresponding primary-<suffix>.o and on memory.o. This single static pattern rule is thus equivalent to these 3 simple rules:
testing-fileA.exe: primary-fileA.o memory.o
g++ primary-fileA.o memory.o -o testing-fileA.exe
testing-fileB.exe: primary-fileB.o memory.o
g++ primary-fileB.o memory.o -o testing-fileB.exe
testing-fileC.exe: primary-fileC.o memory.o
g++ primary-fileC.o memory.o -o testing-fileC.exe
No need for loops. Note that, if you correctly use the standard make variables CXX and CXXFLAGS, you can drop the pattern rule (%.o: %.cpp...), it is one of the many implicit rules that make knows already.

make: *** No rule to make target - gfortran

I am trying to compile a code -
this code uses a few libraries and for starters I am trying to create a makefile to get one library
I am having difficulties.
this is the makefile
HOME = $(shell pwd)
LIBNA = libbv.a
LIBZP = $(HOME)/$(LIBNA)
# FFLAGC = -Mextend -Msave -g -C -Mchkfpstk -Mchkptr -fpic -Ktrap=fp
FC = gfortran
ifeq ($(OSTYPE),linux)
FC = pgf95 -Msave -fpic
endif
# per il gfortran
FFLAGC = -g -Wall-ffixed-line-length-0 -Mextend -Msave -g -C -Mchkfpstk -Mchkptr -fpic -Ktrap=fp
# FC = gfortran
#
SOURCE = \
filename1.f\
filename2.f\
...
filenamen.f
.SUFFIXES: .f
OBJ = $(SRCS:.f=.o)
.f.o:
$(FC) $(FFLAG) -c $< $#
$(LIBZP): $(LIBZP)($(OBJ))
ar -r $(LIBZP) $?
rm -f $?
this is the makefile I am using.
I get the error
make: *** No rule to make target absolutepath/libbv.a()', needed by
absolute_path/libbv.a'. Stop.
I was wondering if any of you can help
Well, your error message shows this:
absolutepath/libbv.a()
with nothing inside the parentheses. But your makefile has this:
$(LIBZP): $(LIBZP)($(OBJ))
with $(OBJ) in the parentheses. So clearly, $(OBJ) is expanding to the empty string. Why is that?
Well, OBJ is set here:
OBJ = $(SRCS:.f=.o)
based on SRCS. Well, what does that variable contain?
Aha. Nothing, because it's never set. You set this though:
SOURCE = \
...
SOURCE != SRCS, so you're modifying an empty variable and OBJ is the empty string.
I'm not sure why you're prefixing the target with the current directory... that's where it will go by default if you don't specify any directory. In any event, you can use $(CURDIR) rather than running $(shell pwd).
If you're going to use GNU make anyway, I recommend you use pattern rules rather than suffix rules: they're much simpler to read/understand:
%.o : %.f
$(FC) $(FFLAG) -c $< $#
Also don't you need a -o here before $#? I don't use Fortran compilers but I would imagine they work more or less the same as C/C++ compilers.

Compiling and linking fortran code with MakeFile using Intel compiler

I have a code that is about 8,000 lines and is contained in the same one big bigcode.f90 file. It compiles without problems from the command line with intel compiler with the options:
mpif90 bigcode.f90 -r8 -O2 -xHost -override-limits -o output.out
I want to split this large file into modules and link them using makefile. Below I am listing the makefile. (So here I have main.f90, and also 8 modules linked to it, such as modparams.o and other 7). So I just broke the bigcode.f90 into 9 files. The problem is that when I compile with the makefile, it complains about things like
subroutine ended with "end" statement instead of "end subroutine"
integer passed to reals
some name conflicts between variable names and subroutine names.
All these may well be bad style. However the original bigcode.f90 had all of these, and it did not prevent it from compiling at all. I want to first replicate the results between the bigcode.f90 and the split version - and then I will make other changes.
I also made a copy of this makefile without any OBJECTS to link (just the single bigcode.f90), and that also compiled without any complains. Could somebody help me understand why is there inconsistency about what the compiler complains when there is one file versus when there are several linked files?
# DEFAULT FLAGS
CFLAGS := -I$(CPATH) -I$(F90_MODULES_PATH)
F90 := mpif90
# ADD ALL LIBRARIES HERE (with -l$LIBRARY_NAME)
LDFLAGS := -llapack -v
# ADD ALL SOURCE FILES HERE (with .o extension)
OBJECTS := modparams.o
OBJECTS += modleeplex.o
OBJECTS += modutilsmain.o
OBJECTS += modecon.o
OBJECTS += modindirect.o
OBJECTS += modijutils.o
OBJECTS += modselectstata.o
OBJECTS += modfcn.o
# ADD ADDITIONAL COMPILER FLAGS HERE
CFLAGS += -fbounds-check
CFLAGS += -ffree-line-length-350
CFLAGS += -r8
CFLAGS += -O2
CFLAGS += -xHost
CFLAGS += -override-limits
# ADD ADDITIONAL MODULE FOLDERS HERE (with -I$PATH_TO_FOLDER)
F90_MODULES += -I.
all: main
main: main.f90 $(OBJECTS)
$(F90) $(CFLAGS) $(LDFLAGS) $(OBJECTS) $< -o $#
%.o : %.f90
$(F90) -c $< $(CFLAGS) $(F90_MODULES) $(LDFLAGS) -o $#
clean:
rm -rf $(OBJECTS) *.mod
When I compile, I get the following, and then a long list of errors.
mpif90 -c modfcn.f90 -I/opt/intel/composer_xe_2013.5.192/mkl/include:/opt/intel/composer_xe_2013.5.192/tbb/include:/opt/intel/composer_xe_20 13.5.192/mkl/include:/opt/intel/composer_xe_2013.5.192/tbb/include -I -fbounds-check -ffree-line-length-350 -r8 -O2 -xHost -override-lim its -I. -llapack -v -o modfcn.o
ifort: command line warning #10006: ignoring unknown option '-ffree-line-length-350'
ifort: command line remark #10010: option '-pthread' is deprecated and will be removed in a future release. See '-help deprecated'
ifort version 13.1.3

Make: Setting Target Specific Variables With Different Goals

I'm building a makefile that will be used to build a release or debug target library. I want to place the object and auto-generated dependancy files into either a debug or release directory structure, depending on the requested makefile goal. I don't want to specify a testable make command-line argument (i.e. DBG=1), but would prefer to run make -f Makefile, or make -f Makefiel dbg for release and debug target goals, respectively. Got that part down. I understand that I can't assign a target-specific variable containing the name of the object dir (either release or debug) that can be used as part of the Target specification in a rule, like I did in the example shown below. In this example, OBJDIR is the target-specific variable I would like to set depending on the build goal. For that reason, in this example, $(OBJDIR) is empty in the target rule $(OBJDIR)/%.o. Any recommendations on how to perform the suggested steps nicely? (The example shown is simply a copy/paste unverified example...syntax is not verified...in fact, I can't get the tabs to appear correctly...I'm hoping to get some implementation ideas). (Also, $(OBJDIR) is not set in the clean target as shown...since it is not in the dbg/all target dependancy heirarchy...thoughts?) Thanks in advance.
Makefile:
OBJS := a.o b.o c.o
SRCS := $(OBJS:.o=.c)
-- Set up the release and the debug directory paths and object filenames
RELEASE_DIR := ./release
RELEASE_OBJ := $(OBJS:%=$(RELEASE_DIR)/%)
DEBUG_DIR := ./debug
DEBUG_OBJ := $(OBJS:%=$(DEBUG_DIR)/%)
.PHONY : all dbg
all: CFLAGS = -O3
all: OBJDIR := RELEASE_DIR
all: df := $(RELEASE_DIR)/$(*F)
all: init_release lib1.so
dbg: CFLAGS = -g -O0
dbg: OBJDIR := DEBUG_DIR
dbg: df := $(DEBUG_DIR)/$(*F)
dbg: init_debug lib1.so
Lib1.so: $(OBJ)
init_release:
-#mkdir -p $(RELEASE_DIR)
init_debug:
-#mkdir -p $(DEBUG_DIR)
lib1.so: $(OBJ)
#echo '--------------------------------------------------------------'
#echo linking $#
#gcc -shared -o lib1.so $(OBJ)
-Compile including advance dependancy generation alg, per Tom Tromey:
# http://make.paulandlesley.org/autodep.html
$(OBJDIR)/%.o: %.c
echo $#
echo $(OBJDIR)
echo compiling $#
$(COMPILE.c) -MD -o $# $<
cp $(df).d $(df).P; \
sed -e 's/#.*//' -e 's/^[^:]*: *//' -e 's/ *\\$$//' \
-e '/^$$/ d' -e 's/$$/ :/' < $(df).d >> $(df).P; \
rm -f $(df)/$*.d
# If the goal is "clean", don't include these to avoid trying to build them
ifneq($(MAKECMDGOALS),clean)
-include $(SRCS:%.c=$(OBJDIR)/%.P)
endif
clean:
-#rm -f $(OBJDIR)/*.[Pdo] lib1.so
Target specific variables can be tricky. Use indirection instead. Make has lots of syntax to cut-down on boilerplate text. .SECONDEXPANSION is often good. A sketch:
.SECONDEXPANSION:
${DEBUG_OBJ} ${RELEASE_OBJ}: $$(patsubst %.o,%.c,$${#F})
gcc ${copts-${#D}} -c $< -o $#
Here we tell make that ./release/a.o depends on a.c. When make decides to build ./release/a.o it expands the shell line. As it does so, ${#D} is naturally release, so make carries on and expands ${copts-release} which you will have defined usefully.
Similarly, when producing ./debug/a.o make expands ${copts-debug}.
Copious use of $(warning [blah]), $(error [blah blah]) and the mandatory --warn-undefined-variables will help you get this right.
The Makefile you wrote is not valid, and it will not generate your targets as you expect. For instance, you cannot set the CFLAGS variable in the targets definitions all and dbg.
The only solution I can think of is to call make with the same Makefile defining the DBG variable as you wish. E.g.:
ifdef DBG
CFLAGS = -O0 -ggdb
OBJDIR = dbgdir
else
CFLAGS = -O2
OBJDIR = reldir
endif
all: $(OBJDIR)/target
#Your commands here
dbg:
$(MAKE) DBG=1
With this, if you call make, you have the release build. If you call make dbg you have the release build.

OS detecting makefile

I routinely work on several different computers and several different operating systems, which are Mac OS X, Linux, or Solaris. For the project I'm working on, I pull my code from a remote git repository.
I like to be able to work on my projects regardless of which terminal I'm at. So far, I've found ways to get around the OS changes by changing the makefile every time I switch computers. However, this is tedious and causes a bunch of headaches.
How can I modify my makefile so that it detects which OS I'm using and modifies syntax accordingly?
Here is the 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
There are many good answers here already, but I wanted to share a more complete example that both:
doesn't assume uname exists on Windows
also detects the processor
The CCFLAGS defined here aren't necessarily recommended or ideal; they're just what the project to which I was adding OS/CPU auto-detection happened to be using.
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
The uname command (http://developer.apple.com/documentation/Darwin/Reference/ManPages/man1/uname.1.html) with no parameters should tell you the operating system name. I'd use that, then make conditionals based on the return value.
Example
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
# do something Linux-y
endif
ifeq ($(UNAME), Solaris)
# do something Solaris-y
endif
Detect the operating system using two simple tricks:
First the environment variable OS
Then the uname command
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
Or a more safe way, if not on Windows and uname unavailable:
ifeq ($(OS),Windows_NT)
detected_OS := Windows
else
detected_OS := $(shell sh -c 'uname 2>/dev/null || echo Unknown')
endif
Ken Jackson proposes an interesting alternative if you want to distinguish Cygwin/MinGW/MSYS/Windows. See his answer that looks like that:
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
Then you can select the relevant stuff depending on 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
Notes:
Command uname is same as uname -s because option -s (--kernel-name) is the default. See why uname -s is better than uname -o.
The use of OS (instead of uname) simplifies the identification algorithm. You can still use solely uname, but you have to deal with if/else blocks to check all MinGW, Cygwin, etc. variations.
The environment variable OS is always set to "Windows_NT" on different Windows versions (see %OS% environment variable on Wikipedia).
An alternative of OS is the environment variable MSVC (it checks the presence of MS Visual Studio, see example using Visual C++).
Below I provide a complete example using make and gcc to build a shared library: *.so or *.dll depending on the platform. The example is as simplest as possible to be more understandable.
To install make and gcc on Windows see Cygwin or MinGW.
My example is based on five files
├── lib
│ └── Makefile
│ └── hello.h
│ └── hello.c
└── app
└── Makefile
└── main.c
Reminder: Makefile is indented using tabulation. Caution when copy-pasting below sample files.
The two Makefile files
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. app/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"
To learn more, read Automatic Variables documentation as pointed out by cfi.
The source code
- lib/hello.h
#ifndef HELLO_H_
#define HELLO_H_
const char* hello();
#endif
- lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
- app/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
The build
Fix the copy-paste of Makefile (replace leading spaces by one tabulation).
> sed 's/^ */\t/' -i */Makefile
The make command is the same on both platforms. The given output is on Unix-like OSes:
> 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'
The run
The application requires to know where is the shared library.
On Windows, a simple solution is to copy the library where the application is:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
On Unix-like OSes, you can use the LD_LIBRARY_PATH environment variable:
> export LD_LIBRARY_PATH=lib
Run the command on Windows:
> app/app.exe
hello
Run the command on Unix-like OSes:
> app/app
hello
I was recently experimenting in order to answer this question I was asking myself. Here are my conclusions:
Since in Windows, you can't be sure that the uname command is available, you can use gcc -dumpmachine. This will display the compiler target.
There may be also a problem when using uname if you want to do some cross-compilation.
Here's a example list of possible output of gcc -dumpmachine:
mingw32
i686-pc-cygwin
x86_64-redhat-linux
You can check the result in the makefile like this:
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
It worked well for me, but I'm not sure it's a reliable way of getting the system type. At least it's reliable about MinGW and that's all I need since it does not require to have the uname command or MSYS package in Windows.
To sum up, uname gives you the system on which you're compiling, and gcc -dumpmachine gives you the system for which you are compiling.
The git makefile contains numerous examples of how to manage without autoconf/automake, yet still work on a multitude of unixy platforms.
Update: I now consider this answer to be obsolete. I posted a new perfect solution further down.
If your makefile may be running on non-Cygwin Windows, uname may not be available. That's awkward, but this is a potential solution. You have to check for Cygwin first to rule it out, because it has WINDOWS in its PATH environment variable too.
ifneq (,$(findstring /cygdrive/,$(PATH)))
UNAME := Cygwin
else
ifneq (,$(findstring WINDOWS,$(PATH)))
UNAME := Windows
else
UNAME := $(shell uname -s)
endif
endif
That's the job that GNU's automake/autoconf are designed to solve. You might want to investigate them.
Alternatively you can set environment variables on your different platforms and make you Makefile conditional against them.
I ran into this problem today and I needed it on Solaris so here is a POSIX standard way to do (something very close to) this.
#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
I finally found the perfect solution that solves this problem for me.
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
The UNAME variable is set to Linux, Cygwin, MSYS, Windows, FreeBSD, NetBSD (or presumably Solaris, Darwin, OpenBSD, AIX, HP-UX), or Unknown. It can then be compared throughout the remainder of the Makefile to separate any OS-sensitive variables and commands.
The key is that Windows uses semicolons to separate paths in the PATH variable whereas everyone else uses colons. (It's possible to make a Linux directory with a ';' in the name and add it to PATH, which would break this, but who would do such a thing?) This seems to be the least risky method to detect native Windows because it doesn't need a shell call. The Cygwin and MSYS PATH use colons so uname is called for them.
Note that the OS environment variable can be used to detect Windows, but not to distinguish between Cygwin and native Windows. Testing for the echoing of quotes works, but it requires a shell call.
Unfortunately, Cygwin adds some version information to the output of uname, so I added the 'patsubst' calls to change it to just 'Cygwin'. Also, uname for MSYS actually has three possible outputs starting with MSYS or MINGW, but I use also patsubst to transform all to just 'MSYS'.
If it's important to distinguish between native Windows systems with and without some uname.exe on the path, this line can be used instead of the simple assignment:
UNAME := $(shell uname 2>NUL || echo Windows)
Of course in all cases GNU make is required, or another make which supports the functions used.
Here's a simple solution that checks if you are in a Windows or posix-like (Linux/Unix/Cygwin/Mac) environment:
ifeq ($(shell echo "check_quotes"),"check_quotes")
WINDOWS := yes
else
WINDOWS := no
endif
It takes advantage of the fact that echo exists on both posix-like and Windows environments, and that in Windows the shell does not filter the quotes.
Note that Makefiles are extremely sensitive to spacing. Here's an example of a Makefile that runs an extra command on OS X and which works on OS X and Linux. Overall, though, autoconf/automake is the way to go for anything at all non-trivial.
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 $
Another way to do this is by using a "configure" script. If you are already using one with your makefile, you can use a combination of uname and sed to get things to work out. First, in your script, do:
UNAME=uname
Then, in order to put this in your Makefile, start out with Makefile.in which should have something like
UNAME=##UNAME##
in it.
Use the following sed command in your configure script after the UNAME=uname bit.
sed -e "s|##UNAME##|$UNAME|" < Makefile.in > Makefile
Now your makefile should have UNAME defined as desired. If/elif/else statements are all that's left!
I had a case where I had to detect the difference between two versions of Fedora, to tweak the command-line options for inkscape:
- in Fedora 31, the default inkscape is 1.0beta which uses --export-file
- in Fedora < 31, the default inkscape is 0.92 which uses --export-pdf
My Makefile contains the following
# 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)=$#
This works because /etc/os-release contains a line
VERSION_ID=<value>
so the shell command in the Makefile returns the string VERSION_ID=<value>, then the eval command acts on this to set the Makefile variable VERSION_ID.
This can obviously be tweaked for other OS's depending how the metadata is stored. Note that in Fedora there is not a default environment variable that gives the OS version, otherwise I would have used that!
An alternate way that I have not seen anyone talking about is using the built-in variable SHELL. The program used as the shell is taken from the variable SHELL. On MS-Windows systems, it is most likely to be an executable file with .exe extension (like sh.exe).
In that case, the following conditional test:
ifeq ($(suffix $(SHELL)),.exe)
# Windows system
else
# Non-Windows system
endif
Would have the same result as using the environment variable OS:
ifeq ($(OS),Windows_NT)
# Windows system
else
# Non-Windows system
endif
However, it seems the latter is the most popular solution, so I would recommend you stick with it.

Resources