31

I have a simple C program (one source file) which I want to compile on Linux and on Windows via make and nmake, respectively. Is there a possibility to accomplish this with a single makefile?

I thought about something like

ifeq($(MAKE), nmake)
    // nmake code here
else
    // make code here
endif

Unfortunately nmake seems not to understand ifeq, so I cannot use that. I have a working makefile, but that produces very ugly results:

hello: hello.c
    $(CC) hello.c

That works on both systems. The problem is that the outcome depends on the default behaviors of the respective compilers. Under Linux I get an executeable named 'a.out' rather than 'hello'. Under Windows I get 'hello.exe' but there is also 'hello.obj' which I do not want to have.

Is there an alternative way? Or is what I'm trying absolutely impossible?

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
j0ker
  • 4,069
  • 4
  • 43
  • 65

10 Answers10

16

It's probably not impossible, but most likely so hard that it would be easier to write two makefiles anyway.

Both GNU make (used in Linux) and nmake have include directives though, so some common things can be put in a common makefile that is included by the main makefile.

Some programmer dude
  • 400,186
  • 35
  • 402
  • 621
13

I wanted to use the same makefile include to be used by Make and NMAKE. Since make recognises line continuation on comment lines, but NMAKE doesn't, this means that we can have separate instructions for Make and NMAKE. For example:

# 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

You just have to make sure that NMAKE-specific code is encompassed by # \.

Foad S. Farimani
  • 12,396
  • 15
  • 78
  • 193
Bevan Collins
  • 1,531
  • 16
  • 25
13

You should look at using CMake for this. With one source file, it should be quite easy. Here is how you could set up a simple project:

cmake_minimum_required(VERSION 3.10)

# set the project name
project(Hello)

# add the executable
add_executable(Hello hello.c)

To build the simple project, you would do the following (this assumes your source and CMakeLists.txt files are in the same directory as the source file hello.c:

mkdir build
cd build
cmake ..
cmake --build .
Foad S. Farimani
  • 12,396
  • 15
  • 78
  • 193
mevatron
  • 13,911
  • 4
  • 55
  • 72
  • 4
    @JohanBezem When I posted this answer, that comment was not available. However, I think this answer is the correct way to solve the problem when not under artificial constraints of a homework assignment... – mevatron Nov 28 '11 at 05:17
  • 1
    CMake can generate nmake, so its not _that_ far fetched to use CMake. It's at least worth considering. – ideasman42 Jun 17 '15 at 18:41
  • 1
    I'm not sure why no one has criticized this answer so far. Firstly although using CMake is a canonical choice, it is not an answer to the question. Secondly, Windows doesn't have `make` command, hence the question in the first place. If you want to use CMake to build, then `cmake --build ..` is the correct way to do it. I'm gonna edit the post, so it will b ehelpfull for other readers. – Foad S. Farimani Jun 07 '20 at 16:43
  • I disagree on the assertion that "cmake" should be the correct choice. It is a completely different build environment and there can be tons of reason to prefer to something that is the "default" option (make for Linux and nmake for VC). Introducing a (rather complex) build system like cmake just for this seems a misdirection to me. – Remo.D Oct 08 '22 at 11:55
7

I am not able to find a way to use a common makefile to work for both GNU Make and Microsoft NMAKE, mainly because they have an incompatible syntax for "include" and/or "if" directives. Microsoft NMAKE requires to use ! prefix for directives. For example, !if, !include, etc...

If it is allowed to have separate macros, however, it could be tricked around. Here I presents the best way I found so far for making a makefile compatible for both GNU Make and Microsoft NMAKE by observing the followings:

  1. Microsoft NMAKE reads TOOLS.ini file for default macros.
  2. The Microsoft suite uses .obj as the object file extension.
  3. GNU Make reads files defined in a MAKEFILES environment variable.
  4. The GNU suite use .o as the object file extension.
  5. GNU make need not give an executable extension .exe for a target.

Note: The following has been tested using Microsoft Visual Studio 2015 and MINGW32.

Step 1: create a following DOS batch file and let it run whenever the CMD prompt is invoked.

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

Step 2: Create a TOOLS.ini file under your working directory as below: (this file is independent of your project dependencies except the libraries possibly)

[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:$@

Step 3: Create a TOOLS.gcc under your working directory as below: (this file is independent of your project dependencies except the libraries possibly)

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 $@

Step 4: Edit your makefile as below (note $(X) and $(O)) where only dependencies are specified.

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 *~

Step 5: Enjoy GNU Make and Microsoft NMAKE with the same makefile

$ nmake
$ make clean
$ nmake clean
$ make
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Dongsoo Kim
  • 71
  • 1
  • 1
4

My solution is to use two different filenames. (since the Makefile name searching priority in different OSes will not be the same)

For Windows, I use normal "Makefile."

For Linux, I use the special "GNUmakefile" according to this article.

So that nmake (Win) will find "Makefile," and make (Linux) will find "GNUmakefile."

alan23273850
  • 232
  • 2
  • 6
  • Are you trying to answer or to ask a question? Even if both, please remove the asking part. Posts via "Your Answer" field and "Post Your Answer" button should only be answers, not questions. – Yunnosch Nov 25 '19 at 13:42
  • 1
    That's my solution. Because the default Makefile name searching priority in WIndows and Linux are different, this solution will work. – alan23273850 Nov 25 '19 at 15:41
  • 1
    Why don't you two try to understand my reply? – alan23273850 Nov 25 '19 at 15:45
  • 2
    I agree and disagree with @Das_Geek about whether your response answers the posted question. I think that you imply that the answer to "Is there a possibility to accomplish this with a single makefile?" is no. And you go on to provide an alternative approach to the problem -- which I think is nice, but is not strictly what the OP asks about. IMO, the question is a bit loose and your answer is too. GIGO – steve Feb 25 '20 at 15:48
3

Solution: 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
Jay K
  • 129
  • 3
1

Yes, you can do this with a single Makefile. The best source for this material is the O'Reilly book:

Managing Projects with GNU Make, Third Edition By Robert Mecklenburg

See chapter 7: Portable Makefiles.

In summary, the technique is to test the environment variable ComSpec which says if the Windows command interpreter is present:

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

I wrap this with a portable shell script which uses sed to edit the makefile for Nmake or GNU make...

Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
Brian Tompsett - 汤莱恩
  • 5,753
  • 72
  • 57
  • 129
  • 2
    hm, afaict the `?=` syntax is not valid with `nmake`. i think your example only detects whether `GNU make` is being run on W32 or not (rather than being a nmake/GNUmake polyglot)) – umläute Jun 27 '17 at 14:41
  • fair enough; otoh, since the portable shell script is the magic glue that actually answers the question, it might be good to share that as well :-) – umläute Jun 27 '17 at 19:19
  • Downvoted because GCC also exists on Windows, and is commonly used there, meaning Windows users get an extra headache when encountering this kind of makefile than they would otherwise have to endure. – Dúthomhas Aug 27 '17 at 03:20
  • @Dúthomhas Yes; My scripts which use this technique can use GCC on windows or CL. With a properly written script it should not be a problem. – Brian Tompsett - 汤莱恩 Aug 27 '17 at 08:49
1

I just thought of something completely different.

If you stick to your extremely simple Makefile, which, you say, works, and just put the 'standard' variables CC and CFLAGS in your respective environments, say

  export CC=gcc

respectively

  set CC=CL.EXE

and

  export CFLAGS=-o myexecutable

respectively

  set CFLAGS=/out:myexecutable.exe

it might just work.

Be aware, I'm not firm in the exact options to use, you'll have to figure them out yourself. But AFAIK both make variants recognize the same set of flags. You may even set those on the respective command lines (but not in the makefile, since NMAKE uses a different 'ifeq' syntax...)

Johan Bezem
  • 2,582
  • 1
  • 20
  • 47
  • 1
    it's probably a good idea to write a simple shell script and batch file which set up the environment and invoke make... – Christoph Nov 25 '11 at 17:17
0

I've recently experimented with using the C preprocessor to generate a portable Makefile from a template Makefile.cc containing preprocessor symbols. So far it's worked surprisingly well. The first observation is that NMAKE will prescan a Tools.ini file, which I provide in the same directory as

[NMAKE]
MAKECONFIG=-D_NMAKE

Then I have a 'true' Makefile next to it which is written in only the common sub language of GNU Make and 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)

Note that the -E switch is pretty common for compilers (at least the big three I work with: GCC, Clang, and CL) for only preprocessing the file. With GNU Make the $(MAKECONFIG) expands to nothing, but in NMAKE it provides the preprocessor variable declaring itself. Since your template Makefile.cc can check it with #ifdef, as well as check for common variables with which the compiler declares itself, you can customize your Makefile.mk quite a bit for both the 'make' program, your operating system, and the compiler you're using.

If you have any 'make' you probably already have a C compiler too; there's no need to install additional software like CMake or autotools. It uses mechanisms that are old and so likely to work in a lot of environments. And from what I've been able to tell so far, it's really fast. Faster at least than running a configuration step in autotools. The only disadvantage I've faced is that it limits the style of your Make rules to being on the same line, because the preprocessor changes the indentation of the code. Also the preprocessor spits out lines with # tags, but since these start a comment in a Makefile, they get ignored anyway.

A have a somewhat small C++ project with a Makefile.cc that looks like the following snippet. It compiles on GNU Make or NMAKE with either GCC, Clang, or CL and on either Windows or in a POSIX environment. I've yet to support BSD Make or test any other compiler though.

// 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
Peter Mortensen
  • 30,738
  • 21
  • 105
  • 131
-1

Wouldn't it be possible to use a script in gnu sed, or perl to translate the host's Makefile to a Microsoft compatible NMakefile? The Makefiles, after all, are text files to provide input for whichever helper tool you're using. Sed and Perl both exist for Linux and Windows.