Using make for cross platform compilation - c

I am currently developing a C project under Linux and Win32. The 'deliverable' is a shared library, and all the development is done under Linux with the GNU tool chain. I am using a Makefile to compile the shared library.
Every now and then I have to build a .dll under Win32 from the same src.
I've installed MinGW on the Win32 box such that I can use make and get far fewer complaints from the compiler (in comparison to MSVC). I'm at a stage where the src code compiles on both platforms
But the Linux Makefile and Win32 Makefile are different. I'm curious as how to best handle this - should I:
have 2 makefiles, e.g. Makefile for linux and Makefile.WIN32 and then run make -f Makefile.WIN32 on the Windows box
Should I make a different target in a single Makefile and do something like make WIN32 on the Windows box
Should I ditch make and use CMake (is the juice worth the squeeze for such a simple project, i.e. 1 shared library)

Use a single make file and put the platform-specifics in conditionals, eg
ifeq ($(OS),Windows_NT)
DLLEXT := .dll
else
DLLEXT := .so
endif
DLL := libfoo$(DLLEXT)
lib : $(DLL)

I use UNAME := $(shell uname) within my Makefile to detect the platform (Linux or MS-Windows).
I provide below a complete example based on make and gcc to build a shared library: *.so or *.dll depending on the platform.
The example is basic/simple/stupid to be more understandable :-)
To use make and gcc on MS-Windows, Cygwin or MinGW can be installed.
The example uses five files:
├── app
│ └── Makefile
│ └── main.c
└── lib
└── Makefile
└── hello.h
└── hello.c
The Makefiles
app/Makefile
app.exe: main.o
gcc -o $# $^ -L../lib -lhello
# '-o $#' => output file => $# = the target file (app.exe)
# ' $^' => no options => Link all depended files
# => $^ = main.o and other if any
# '-L../lib' => look for libraries in directory ../lib
# '-lhello => use shared library hello (libhello.so or hello.dll)
%.o: %.c
gcc -o $# -c $< -I ../lib
# '-o $#' => output file => $# = the target file (main.o)
# '-c $<' => COMPILE the first depended file (main.cpp)
# '-I ../lib' => look for headers (*.h) in directory ../lib
clean:
rm -f *.o *.so *.dll *.exe
lib/Makefile
UNAME := $(shell uname)
ifeq ($(UNAME), Linux)
TARGET = libhello.so
else
TARGET = hello.dll
endif
$(TARGET): hello.o
gcc -o $# $^ -shared
# '-o $#' => output file => $# = libhello.so or hello.dll
# ' $^' => no options => Link all depended files => $^ = hello.o
# '-shared' => generate shared library
%.o: %.c
gcc -o $# -c $< -fPIC
# '-o $#' => output file => $# = the target file (main.o)
# '-c $<' => compile the first depended file (main.cpp)
# '-fPIC' => Position-Independent Code (required for shared lib)
clean:
rm -f *.o *.so *.dll *.exe
The source code
app/main.c
#include "hello.h" //hello()
#include <stdio.h> //puts()
int main()
{
const char* str = hello();
puts(str);
}
lib/hello.h
#ifndef __HELLO_H__
#define __HELLO_H__
const char* hello();
#endif
lib/hello.c
#include "hello.h"
const char* hello()
{
return "hello";
}
The build
Fix the copy-paste of Makefiles (replace leading spaces by tabulation).
> sed -i 's/^ */\t/' */Makefile
The make command is the same on both platforms. The given output is for MS-Windows (unnecessary lines removed).
> cd lib
> make clean
> make
gcc -o hello.o -c hello.c -fPIC
gcc -o hello.dll hello.o -shared
> cd ../app
> make clean
> make
gcc -o main.o -c main.c -I ../lib
gcc -o app.exe main.o -L../lib -lhello
The run
The application requires to know where is the shared library.
On MS-Windows, the simple/basic/stupid way is to copy the library where the application is:
> cp -v lib/hello.dll app
`lib/hello.dll' -> `app/hello.dll'
On Linux, use the LD_LIBRARY_PATH environment variable:
> export LD_LIBRARY_PATH=lib
The run command line and output are the same on both platforms:
> app/app.exe
hello

As somebody who has used both autotools and CMake, I would recommend using CMake over rolling your own Makefiles and using autotools. CMake has so many useful, easy to use benefits, even if it is a simple project. For example, CMake will create an NSIS installer, manage production vs. debug compilation and has a nice testing framework. The one knock I had was that it was kind of hard to find real examples of how to use it. So much open source software uses autotools that realworld examples for it are easy to find. However, if you download the CMake source, there are lots of examples in the Example directory and Test directory.
In other words, the Juice is worth the squeeze.

I had a similar issue a few years back, and found that cmake is much easier for cross-platform compilation AND will use whatever compiler is native for that system. Syntax is clearer and abstracts details that are unnecessary for the most part (sometimes that got in the way, but usually there was a way around it)

As a primary advice, I suggest using libtool, autoconf and automake; they make cross-compilation very easy, and much easier than CMake.
If you are going the hand-crafted route, I would suggest going with different targets. Switching between makefiles tends to hide otherwise obvious errors in Makefiles, e.g. duplicately used objects with different rules. Example: The object foo.o is compiled for the DLL target and for the .so target, but with different flags. If someone switches Makefiles, the existing .o file with wrong flags is used, breaking the build. If you are using one Makefile, this will become obvious through rule conflicts.

If you are willing to use MSYS2 on Windows you might get it to run without
making any changes at all compared to your code written for Linux.
This goes for your C/C++ source code as well as for your makefile.(!)
I have been developing code for Linux exclusively. When I tried running it
inside an MSYS2 terminal, the code turned out to work just fine, and
produced a Windows binary executable. I was positively surprised.
You will need to know how to install and use MSYS2, of course. For example,
to install make and g++, in an MSYS2 terminal run the commands:
yes | pacman -Syu msys/make
yes | pacman -Syu gcc
If you want to find out where in Windows g++ has been installed, you can run
where g++ in the MSYS2 terminal.
References:
https://www.msys2.org/wiki/MSYS2-installation/
https://github.com/msys2/MSYS2-packages/issues/293

Related

How can I efficiently patch an external library and compile it in a Makefile for a C project?

I'm working on a C project which needs an external open source library.
In particular, it needs a version I patched myself in order to add some needed features.
At the moment I'm using a Makefile which expects a statically compiled version of the patched library inside the ./lib folder (let's call it libpatched.a), and the corresponding header files in ./include/libpatched.
The following are the main parts of the aforementioned Makefile:
EXECNAME=MyExecutable
CC=gcc
SRC_DIR=src
OBJ_DIR=obj
SRC=$(wildcard $(SRC_DIR)/*.c)
OBJ=$(SRC:$(SRC_DIR)/%.c=$(OBJ_DIR)/%.o)
CFLAGS += -Wall -O2 -Iinclude -Iinclude/libpatched
LDFLAGS += -Llib
LDLIBS += -lpatched
.PHONY: all clean
all: $(EXECNAME)
$(EXECNAME): $(OBJ_CC)
$(CC) $(LDFLAGS) $^ $(LDLIBS) -o $#
$(OBJ_DIR)/%.o: $(SRC_DIR)/%.c
$(CC) $(CFLAGS) -c $< -o $#
clean:
$(RM) $(OBJ_DIR)/*.o
This Makefile is working correctly; however, I was looking for a more flexible solution, which does not need any statically compiled library before make is called.
What I would like to accomplish is having a Makefile which does something like:
Download a specific version of the original library (in order to never have any compatibility problem)
Apply a patch using patch and a diff file (.patch)
Compile the patched library (either statically or dynamically) for the current platform, using cmake, as required by the original library
Compile my project, using libpatched
Are these steps valid in your opinion, or is there a much better way to handle this need for a patched library?
If yes, as I'm not an expert at all in creating Makefiles, is there an easy way to reach this goal by simply leveraging on a properly written Makefile?
Which could be the best way to do so?
Thank you very much in advance.
I've done exactly this before, when building cross compiler etc with my patches for my operating system kernel. You can use the wget or curl commands in the Makefile. For example something like
# foo.tar.gz needs to be downloaded
foo.tar.gz:
wget https://download.source.from/here/foo.tar.gz -O foo.tar.gz
# the makefile requires the downloaded file.
foo_src/CMakeLists.txt: foo.tar.gz
mkdir -p foo_src
cd foo_src && tar xfz ../foo.tar.gz
# patch the library if flag not present
foo_patched.flag:
cd foo_src && patch -p1 ../foo.patch
touch foo_patched.flag
# this depends on patching
libpatched.a: foo_src/CMakeLists.txt foo_patched.flag
cd foo_src && cmake
cp foo_src/libfoo.a libpatched.a
The Makefile format is very simple - unlike CMake! - the rules just say: "to generate the file on the left, please build the prerequisites on the right side first. Then execute these commands to actually generate the file on the left hand side"

Configure automake to target assembly

Is it possible to configure automake to generate a Makefile which, in addition to building the .o files and linked binary, also has targets for %.s? I want to be able to review the compiler output in a text format without having to invoke binutils on the .o files.
Specifically, if I have main.c as a source file, I want to be able to run make main.s. The desired recipe would be the same as that for main.o, but using CC1 := $(CC) -S.
The question is a little XY.
You want to be able make the intermediate assembly file foo.s, where
the source file foo.c is one of the sources in an autotooled project, using
a makefile that is generated by the project's ./configure script. You
assume that to do this you must do something to the automake inputs -
the Makefile.ams? - that will cause ./configure to generate Makefiles
that include assembly targets *.s matching all object targets *.o.
Well you could, but then your project would not be a regular autotooled
project as usually distributed, and there is no need to make it irregular
to get what you want.
The GCC option -save-temps
exists to let developers see the intermediate files of compilation - the preprocessor
output, the assembly.
$ gcc -c -o foo.o foo.c
outputs foo.o
$ gcc -save-temps -c -o foo.o foo.c
outputs:
foo.o
foo.i # preprocessed source
foo.s # assembly
As I expect you know, GNU Make receives compiler options from the make-variable
CFLAGS, and automake respects this convention, independently of and in addition to any compiler
options prescribed by the project's autotooling. So, if you would otherwise generate
makefiles with:
$ ./configure ...
then, to add -save-temps to the C compiler options, generate makefiles instead
with:
$ ./configure CFLAGS=-save-temps ...
And if you are already using CFLAGS, e.g.
$ ./configure CFLAGS="-g -O0" ...
then append -save-temps:
$ ./configure CFLAGS="-g -O0 -save-temps" ...
Then,
$ make main.o
will make main.o, main.i and main.s up-to-date.
To disable -save-temps, of course, rerun ./configure, removing it from
the CFLAGS.
If the project involves C++ compilation, then CXXFLAGS affects the C++
compiler in the same way that CFLAGS affects the C compiler. Note that
the generated preprocessed C++ sources will be called *.ii, not *.i.
With -save-temps enabled, make clean will not delete the *.i and *.s
files. You may not care, since compilation will always clobber them. If you
do care, you may take advantage of automake's standard phony target clean-local,
which is supported to let an autotooling maintainer extend the behaviour of
clean. Add the following recipe to the Makefile.am of each source directory
in the project:
clean-local:
$(RM) *.i *.ii *.s
Then update the autotooling and regenerate Makefiles:
$ autoreconf
$ ./configure ...
While the COMPILE variable in the generated Makefile.in is technically an internal detail, and this solution relies on the compiler to understand -c -S, adding:
.c.s:
$(COMPILE) -c -S $<
to the Makefile.am has worked for as long as I've been using the autotools. It might also be convenient to add:
clean-local:
rm -f *.s
I find this useful in development to have a look at the assembly output for specific configure and CC, CFLAGS options.
The COMPILE variable will be defined as something like:
COMPILE = $(CC) $(DEFS) $(DEFAULT_INCLUDES) $(INCLUDES) $(AM_CPPFLAGS) \
$(CPPFLAGS) $(AM_CFLAGS) $(CFLAGS)
Similarly, for C++ source we have:
.cc.s:
$(CXXCOMPILE) -c -S $<

Using readline statically in C (compilation and linkage)

I would like to link readline statically with my program and I found this page about readline compilation from source http://www.bioinf.org.uk/software/profit/doc/node17.html but I'm a bit confused about the process.
The page talks about a variable READLINELIB in the makefile but I don't find it.
Could someone show me the way to use readline statically in my program, what to put in my Makefile for compiling readline from source and link it with my program?
Thank you.
Finally I figured out the proper way to do it, I using the --prefix option of the configure file I can tell where to put/install the library. The problem about installation was that I don't have the right to access other directories than my $HOME, so no problem doing this:
configure --prefix=$HOME/libreadline && make && make install-static
Then in my program I include the file from $HOME/libreadline/include.
To compile the main program I link the program with the archive libraries $HOME/libreadline/lib/libreadline.a and $HOME/libreadline/lib/libhistory.a.
Also since readline files uses directive like #include <readline/readline.h> which doesn't correspond to the location of the files, I must tell the compiler where to look for included files. To do this, before running gcc, I set the variable C_INCLUDE_PATH to $HOME/libreadline/include.
Finally, since readline uses ncurses dynamic library I must tell the compiler to dynamically link it with my program. It might be the case of termcap too...
The overall process looks like:
configure --prefix=$HOME/libreadline && make && make install-static
export C_INCLUDE_PATH=$HOME/libreadline/include
gcc -o myprogram myprogram.c $HOME/libreadline/lib/libreadline.a $HOME/libreadline/libhistory.a -lncurses -ltermcap
I was confused about what make install do, it only copy files to the location provided by the configure, by default it installs in system directories like /usr/include, etc... but providing the --prefix option make install will copy all files in the specified directory.
Installation is just copying compiled program, libraries, doc, etc to a certain location, by default standart system directories, if you don't have access to those directories like me you could "install" it in your own directory and then do whatever you wan't with it.
I could have installed the dynamic library instead the static one, but then I would have to modify the LD_LIBRARY_PATH environment.
get readline source
wget http://git.savannah.gnu.org/cgit/readline.git/snapshot/readline-master.tar.gz
tar zxvf readline-master.tar.gz
cd readline-master/
examples folder does not have Makefile, which is generated using Makefile.in script.
following steps build static & dynamic libs & puts them inside /usr/local/bin
./configure
make
sudo make install
may have to install curses as "sudo apt-get install libncurses5-dev"
Use following make file (strip down version from examples folder)
(Make sure tab is honored otherwise makefile will not work)
RM = rm -f
CC = gcc
CFLAGS = -g -O
INCLUDES = -I/usr/local/include
LDFLAGS = -g -L/usr/local/lib
READLINE_LIB = -lreadline
TERMCAP_LIB = -ltermcap
.c.o:
${RM} $#
$(CC) $(CFLAGS) $(INCLUDES) -c $<
SOURCES = rlversion.c
EXECUTABLES = rlversion
OBJECTS = rlversion.o
all: $(EXECUTABLES)
everything: all
rlversion: rlversion.o
$(CC) $(LDFLAGS) -o $# rlversion.o $(READLINE_LIB) $(TERMCAP_LIB)
clean mostlyclean:
$(RM) $(OBJECTS) $(OTHEROBJ)
$(RM) $(EXECUTABLES)
rlversion.o: rlversion.c
I was in need of libraries libreadline.a, libhistory.a for both 64 and 32 bit versions.
The answer provided by Rajeev Kumar worked for me. ( Had a little trouble finding and installing libncurses).
For 32-bit versions, using https://packages.ubuntu.com/search?keywords=lib32readline-dev, the following command worked for me.
sudo apt install lib32readline-dev
So it is hoped that for 64 also, it works
sudo apt install libreadline-dev

What is Eclipse CDT is doing with 'make' under the hood

I'm on Windows 7 and have MinGW/gcc installed. I'm using the Eclipse CDT plugin to compile and build my first simple C programs, and am trying to follow what exactly the plugin is doing under the hood.
I create a new "Hello World!" C project with the following directory structure:
helloworld/
src/
helloworld.c
Where helloworld.c is:
#include <stdio.h>
#include <stdlib.h>
int main(void) {
puts("Hello World!");
return EXIT_SUCCESS;
}
So I created a Run Configuration in Debug Mode (as opposed to "Release Mode", not a "Debug Configuration" in typical Eclipse parlance!) and ran my app, and it works beautifully, printing "Hello World!" to the Eclipse console.
Now I'm looking on my file system and the file/project structure is like so:
helloworld/
src/
helloworld.c
Debug/
src/
helloworld.d
helloworld.o
subdir.mk
helloworld.exe
makefile
objects.mk
source.mk
I assume that running my Run Configuration in Eclipse (hence compiling/building/running helloworld inside Eclipse) created everything under Debug. Furthermore I assume that helloworld.d and helloworld.o are compiled binaries, and that helloworld.exe is the packaged executable containing those binaries and everything they'red linked to (stdio and stdlib). I also assume makefile is the actual Make file (buildscript), and that the *.mk files are somehow inputs to that buildscript. So, for starters, if any of those assumptions are wrong, please begin by correcting me!
When I open makefile I see this:
################################################################################
# Automatically-generated file. Do not edit!
################################################################################
-include ../makefile.init
RM := rm -rf
# All of the sources participating in the build are defined here
-include sources.mk
-include src/subdir.mk
-include subdir.mk
-include objects.mk
ifneq ($(MAKECMDGOALS),clean)
ifneq ($(strip $(C_DEPS)),)
-include $(C_DEPS)
endif
endif
-include ../makefile.defs
# Add inputs and outputs from these tool invocations to the build variables
# All Target
all: helloworld
# Tool invocations
helloworld: $(OBJS) $(USER_OBJS)
#echo 'Building target: $#'
#echo 'Invoking: Cross GCC Linker'
gcc -o "helloworld" $(OBJS) $(USER_OBJS) $(LIBS)
#echo 'Finished building target: $#'
#echo ' '
# Other Targets
clean:
-$(RM) $(EXECUTABLES)$(OBJS)$(C_DEPS) helloworld
-#echo ' '
.PHONY: all clean dependents
.SECONDARY:
-include ../makefile.targets
Please note: I am not looking for someone to explain to me how Make works, I can RTFM for that ;-)
I am just trying to understand what it would take to compile, build and run helloworld from the command-line, outside of Eclipse. What command line invocations would I need to accomplish this, and why? Once I see that, combined with perusing Make docs, I should be able to fill in the gaps and understand everything that is going on.
That depends a bit on the paths that Eclipse generates in the files source.mk and objects.mk but most likely you need to cd into the Debug folder.
Inside of that, you can then run make all to compile the project.
If Eclipse generated absolute paths, you can use make -f .../path/to/helloworld/Debug/makefile all from anywhere.
The *.o files are the object file(s) created by compilation. these files are typically build by a command like:
Gcc -ansi -Wall -pedantic -c helloworld.c -o helloworld.o
(apologies foe capitalization of gcc, my iPad insists on correct my typing)
The *.exe is the actual executable, which may or may not contain the library functions. This depends on static versus dynamic linking. The executable is created typically by:
Gcc helloworld.o -o helloworld.exe
The *.d files are dependency files, built by gcc attempting to determine dependencies between files, typically built with the following command
MAKEDEPEND = gcc -M $(CPPFLAGS) -o $*.d $<
(Rule taken from make online documentation).
So,to answer your final question, to compile from the command line, a command like:
Foo gcc -ansi -WAll -pedantic helloworld.c -o helloworld.exe
Should do the trick for you. Note, the flags to the compiler are the minimum that I like to use, you will probably have a different set of switches.
Hopes this help,
T

How to install/compile SDL2 C code on Linux/Ubuntu

I'm currently doing some C programming and I actually want to use the SDL library. I want to build a Small 2D game in C on Linux to sharp my skills a bit.
My issue is I'm not a super Makefile user nor library on Linux super user, I just configure things once when on a project and that's it.
So I have some trouble compiling SDL2 programs on UBUNTU 14.04.
I downloaded the latest SDL library from : http://www.libsdl.org/download-2.0.php
Then I installed it with the default step:
./configure
make
sudo make install
After that I can see that there is something in /usr/include/SDL2 so I guess it is installed.
#include <stdlib.h>
#include <stdio.h>
#include <SDL2/SDL.h>
int main(int argc, char *argv[])
{
printf(“SDL test\n”);
return 0;
}
Because I'm still learning Makefiles and SDL I didn't figure it out to make it.
but I found this Makefile to compile the old SDL not the SDL2
CPP=gcc
CFLAGS=-O3
LDFLAGS=-lSDL -lSDL_mixer #Linker
EXEC=test
all: ${EXEC}
${EXEC}: ${EXEC}.o
${CPP} $(CFLAGS) -o ${EXEC} ${EXEC}.o ${LDFLAGS}
${EXEC}.o: ${EXEC}.c
${CPP} $(CFLAGS) -o ${EXEC}.o -c ${EXEC}.c
clean:
rm -fr *.o
mrproper: clean
rm -fr ${EXEC}
But this Makefile is not working for me it says that it doesn't know the lSDL_Mixer and other stuff.
How can I build a workable Makefile to compile C program with SDL2 using Makefiles and vim editor.
Thanks in advance for your help
I'll go ahead and clean up that Makefile for you.
CFLAGS = -O3
LDFLAGS =
appname = test
all: $(appname)
clean:
rm -f $(appname) *.o
.PHONY: all clean
sdl_cflags := $(shell pkg-config --cflags sdl2 SDL2_mixer)
sdl_libs := $(shell pkg-config --libs sdl2 SDL2_mixer)
override CFLAGS += $(sdl_cflags)
override LIBS += $(sdl_libs)
$(appname): test.o
$(CC) $(LDFLAGS) -o $# $^ $(LIBS)
Explanation
Variables in a makefile should be used with $(...), not ${...}.
pkg-config has an entry for SDL_Mixer, sdl-config does not. pkg-config is much more general.
using override with the SDL flags allows you to run something like make CFLAGS="-O0 -g" without breaking SDL support.
This is actually important: the SDL library flags have to be at the end of the command line, in LIBS, due to the fact that the GNU linker is sensitive to the order in which libraries are specified.
You don't need explicit rules for .c files, because GNU Make has implicit rules which are just fine.
Note that there are some very important features missing from this makefile. For example, it won't automatically recompile things when you change your header files. I would recommend that you use another build system, such as CMake or SCons, which handles this automatically. You can also do it with Make, but you'd have to paste several arcane lines of code into your makefile.
You can use sdl2-config as suggested by the SDL Linux FAQ to get the required compiler and linker flags:
SDL_CFLAGS := $(shell sdl2-config --cflags)
SDL_LDFLAGS := $(shell sdl2-config --libs)
CFLAGS := $(SDL_CFLAGS) -O3
LDFLAGS := $(SDL_LDFLAGS) -lSDL_mixer
It will find the location of your SDL.h and return the corresponding flag (e.g. -I/usr/include/SDL2 in your case) among other flags it deems necessary (e.g. for me it gives -D_THREAD_SAFE). And then you can simply #include <SDL.h>, which is the standard way to include SDL.

Resources