πΆ Manage a Makefile project π
While mulle-sde is most powerful, when setting up new projects, it can also be usefully employed, with an existing project. To illustrate this, a small demo project is created and then lightly adapted to work seamlessly with mulle-sde.
Setup of the demo project
Itβs going to be a C based
make
project, if that doesnβt interest you and you prefercmake
ormeson
, you can skip to Employ mulle-sde to get the custom library and continue from there.
Let AI create a demo Makefile
Let an AI (like ChatGPT) create a simple
Makefile
for an executable, in a form that could be considered somewhat
standard [1]:
# Compiler and linker flags
CC = gcc
CFLAGS = -Wall -Wextra
LDFLAGS = -L/path/to/lib -lmulle-core
INCLUDES = -I/path/to/includes
# Variables for installation prefixes, extra include directories, and library directories
PREFIX = /usr/local
INCDIR = $(PREFIX)/include
LIBDIR = $(PREFIX)/lib
# Source and object files
SRCS = my.c other.c
OBJS = $(SRCS:.c=.o)
EXECUTABLE = my.exe
# Default target
all: $(EXECUTABLE)
# Rule for building the executable
$(EXECUTABLE): $(OBJS)
$(CC) $(LDFLAGS) $(INCLUDES) -o $@ $^
# Rule for building the object files
%.o: %.c
$(CC) $(CFLAGS) $(INCLUDES) -c $< -o $@
# Install target
install: $(EXECUTABLE)
cp $(EXECUTABLE) $(PREFIX)/bin
cp my.h $(INCDIR)
cp libmulle-core.so $(LIBDIR)
# Clean target
clean:
rm -f $(OBJS) $(EXECUTABLE)
Let AI create demo content
I am not sure why the AI wants a header for an executable, but lets play along anyway [2]:
For my.h
:
#ifndef MY_H
#define MY_H
void print_message();
#endif /* MY_H */
For my.c
:
#include "my.h"
#include <stdio.h>
void print_message() {
printf("Hello, World!\n");
}
For other.c
:
#include "my.h"
int main() {
print_message();
return 0;
}
AI: In this example, my.h
is a header file that declares a function prototype for
print_message()
. my.c
is a source file that defines print_message()
, and
other.c
is another source file that uses print_message()
.
Employ mulle-sde
Build project with mulle-make
Run mulle-make
instead of plain make
. Whatβs the advantage ?
mulle-make
will automatically create a suitable -j
argument to
parallelize the build. Also the output of make will be logged separately into
a log file in a build
folder for later perusal with (mulle-make log
).
We do error out though, because -lmulle-core
isnβt installed yet (usually):
$ mulle-make
collect2: error: ld returned 1 exit status
make: *** [Makefile:25: my.exe] Fehler 1
/usr/bin/ld: -lmulle-core kann nicht gefunden werden: Datei oder Verzeichnis nicht gefunden
collect2: error: ld returned 1 exit status
make: *** [Makefile:25: my.exe] Fehler 1
mulle-make fatal error: make failed with 2 (/tmp/xya)
As an intermediate step though, before fixing the link error, it would be
nicer, if all .o
files would also end up in the
build
folder, so we get parts of the Makefile rewritten by the AI [3] too:
...
# Build directory
BUILD_DIR = build
# Source and object files
SRCS = my.c other.c
OBJS = $(addprefix $(BUILD_DIR)/, $(SRCS:.c=.o))
EXECUTABLE = my.exe
...
# Clean target
clean:
rm -rf $(BUILD_DIR) $(EXECUTABLE)
Now letβs get the -lmulle-core
working.
Employ mulle-sde to get the custom library
To get dependencies (third party libraries) happening, we need to initialize
this project for mulle-sde. This will add a .mulle
folder to the project.
As the project already exists, we use:
mulle-sde init none
The project type βnoneβ will add no additional files outside of the .mulle
folder and mulle-sde will not attempt to build the main project.
Now we will link our executable with mulle-core.
mulle-core is nice for demo purposes, because its known to work :) But you can
try out non-mulle libraries as well. If they use autoconf
or cmake
, chances
are good, that they work without tweaks.
mulle-sde add github:mulle-core/mulle-core # add .tar suffix to fetch archives
What can be added ?
In principle anything can be added that is based on
cmake
orautoconf
.meson
andmake
projects can work too. Everything can be made to fit using additional script though. It neednβt be hosted on github, though github is best supported.
So mulle-sde should have cloned the mulle-core
repository from github
and already compiled it. But where is it ? Itβs in the dependency/Debug
folder. A release build would end up in the root of the dependency
(with
default settings). Thatβs more conventional, so lets do a βReleaseβ build
instead and remove the dependency/Debug
folder:
mulle-sde clean tidy
mulle-sde -v craft --release # use the -v flag to see log during build
Now we have to adjust the Makefile
so that the include and library
directories in dependency
are found [4]:
...
DEPENDENCY_DIR = dependency
# Default target
all: $(EXECUTABLE)
# Rule for building the executable
$(EXECUTABLE): $(OBJS)
$(CC) -L$(DEPENDENCY_DIR)/lib -lmulle-core -I$(DEPENDENCY_DIR)/include -o $@ $^
# Rule for building the object files
$(BUILD_DIR)/%.o: %.c
$(CC) $(CFLAGS) -I$(DEPENDENCY_DIR)/include -c $< -o $@
# Install target
install: $(EXECUTABLE)
cp $(EXECUTABLE) $(PREFIX)/bin
cp my.h $(INCDIR)
cp $(DEPENDENCY_DIR)/lib/libmulle-core.so $(LIBDIR)
...
I am not a super-fan of the way the AI coded this, but fine enough.
Lets run mulle-make
again, and there should be a my.exe
in the project
directory.
Tie mulle-make and mulle-sde together
Now we have mulle-sde craft
to build the dependency and mulle-make
to
build the project. Lets tie both together, so that mulle-sde craft
can
do both.
# turn on main project builds
mulle-sde env --project remove MULLE_SDE_CRAFT_TARGET
mulle-sde env --project set PROJECT_TYPE 'executable'
# ensure that dependency is built 'Release'
mulle-sde env --this-user set MULLE_SDE_CRAFTORDER_STYLE 'Release'
Now the project can be built with mulle-sde
. But can it still be built
on remote systems, where there is only a make and mulle-core installed but no
mulle-sde ?
Yes, with a little tweak of the Makefile
[5]:
# Compiler and linker flags
CC = gcc
CFLAGS = -Wall -Wextra
# Variables for installation prefixes, extra include directories, and library directories
PREFIX = /usr/local
INCDIR = $(PREFIX)/include
LIBDIR = $(PREFIX)/lib
# Build directory
BUILD_DIR = build
# Dependency directory
DEPENDENCY_DIR = dependency
# Library and include directories
LIB_DIR = $(DEPENDENCY_DIR)/lib
INC_DIR = $(DEPENDENCY_DIR)/include
# Check if the dependency directory exists
ifeq ($(wildcard $(DEPENDENCY_DIR)),$(DEPENDENCY_DIR))
LIB_DIR = $(DEPENDENCY_DIR)/lib
INC_DIR = $(DEPENDENCY_DIR)/include
else
LIB_DIR = /usr/lib
INC_DIR = /usr/include
endif
# Source and object files
SRCS = my.c other.c
OBJS = $(addprefix $(BUILD_DIR)/, $(SRCS:.c=.o))
EXECUTABLE = my.exe
# Default target
all: $(EXECUTABLE)
# Rule for building the executable
$(EXECUTABLE): $(OBJS)
$(CC) -L$(LIB_DIR) -lmulle-core -I$(INC_DIR) -o $@ $^
# Rule for building the object files
$(BUILD_DIR)/%.o: %.c
$(CC) $(CFLAGS) -I$(INC_DIR) -c $< -o $@
# Install target
install: $(EXECUTABLE)
cp $(EXECUTABLE) $(PREFIX)/bin
cp my.h $(INCDIR)
cp $(LIB_DIR)/libmulle-core.so $(LIBDIR)
# Clean target
clean:
rm -rf $(BUILD_DIR) $(EXECUTABLE)
The AI changed things around for the better IMO.
Now we are done:
mulle-sde craft --clean-all
and thatβs what you should be seeing:
Clean all
Creating xya craftorder
Let cmake/ninja do a Release build of mulle-core ...
Let make/make do a Debug build of xya .
The intermediate build files are no longer in build
, instead the build is now
done also in kitchen
where the temporary mulle-core
object files (and logs)
are placed. So build
is unused now and can be removed.
Spruce up the project with personal preferences
Use shell aliases to reduce type-work
As one will by typing mulle-sde craft
a lot, create aliases like these:
alias sde=`mulle-sde`
alias c=`mulle-sde craft`
Separate Release and Debug directories in dependency
mulle-sde env --this-user set MULLE_CRAFT_DISPENSE_STYLE 'strict'
or do the opposite and use dispense style none
to also not create a Debug
directory.
Get stash
, kitchen
, dependency
out of the way
IDEs who employ indexing, slow down a lot, when they encounter directories with build products and third party sources. There is an easy way, to move mulle-sde directories out of the project.
mulle-sde clean tidy
rm -rf kitchen stash dependency
uuid=`uuidgen`
mulle-sde env --this-host set DEPENDENCY_DIR '${HOME}/.mulle/var/${PROJECT_NAME}-'${uuid}'/dependency'
mulle-sde env --this-host set KITCHEN_DIR '${HOME}/.mulle/var/${PROJECT_NAME}-'${uuid}'/kitchen'
mulle-sde env --this-host set MULLE_SOURCETREE_STASH_DIR '${HOME}/.mulle/var/${PROJECT_NAME}-'${uuid}'/stash'
To guard against clashes with duplicate projects of the same name, use an UUID in the directory path:
The Makefile
should now get the value from mulle-sde either via MULLE_SDK_PATH
,
which will be provided as an argument, or we could be asking:
mulle-sde dependency-dir
:
MULLE_SDK_PATH ?=
# Dependency directory
DEPENDENCY_DIR := $(shell echo $(MULLE_SDK_PATH) | cut -d':' -f1)
Final Words
So what was gained ?
- project users can fetch dependencies and build and project in one step
- dependencies donβt clutter or conflict with the system or other projects
- the possibility of having βbuild localβ environment variables like CC defined
- more opportunities await, as there is a lot more to uncover about mulle-sde
With one dependency, you might shrug it off, but the more dependencies your project has, the more useful mulle-sde will become.
AI Prompts
[1]You are a C and Unix expert. Write me
a Makefile with common conventions that maintains an executable "my.exe" which
links against a custom library "-lmulle-core" library. We need all and install and
clean targets as well. Also need some variables for installation prefixes,
extra include directories and library directories.
[2] Please provide some demo content
for the files in that Makefile: my.h, my.c, other.c.
[3] Can we change OBJS = $(SRCS:.c=.o) so
that the output lands in a "build" folder ?
[4] The /path/to/ is really a local
folder called "dependency", and the header directory in there is called
"include" not "includes". Please use an additional variable so we can move
"dependency" around.
[5] Please modify the Makefile so that,
if there is no dependency folder in the project, that the default system /usr
is searched.