You can skip the full context and go right to the explanation of the proposed makefile.
"Installation" can mean two things: installing packages using package manager (e.g., pacman(8), apt(8))
or copying files from one spot to another. Of course, package managers may copy files during the installation process,
but in the latter case I mean only copying and maybe creating destination directories.
The usual encounter goes like: download some sources, build them, run checks, then run as is or install to
/usr/local
to make it available system-wide and then run.
With make(1) it's like:
$ make
$ make test
# make install
We might see many different installation targets, but if there's any, install
should be one of them.
These targets conventionally use several variables to change their behaviour. Among those we can see:
DESTDIR=
PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
INCLUDEDIR=$(PREFIX)/include
DATADIR=$(PREFIX)/share
MANDIR=$(DATADIR)/man
MAN1DIR=$(MANDIR)/man1
When we run make PREFIX=/usr install
, the PREFIX
will change to /usr
and so
will all downstream directories. Note that nothing uses DESTDIR
yet. DESTDIR
is used for
staging the files with their intended structure before they make it to the real target location. This is commonly used
by package maintainers along with fakeroot(1) or similar method.
For more details about these paths consult
Filesystem Hierarchy Standard, hier(7),
file‑hierarchy(7) and system specific documentation. For more information about names of the variables see
Standard Targets for Users and
Variables for Installation
Directories from GNU Make documentation.
Now consider a simple target definition:
install: all
install -m644 -Dt $(DESTDIR)$(INCLUDEDIR) include/*.h
install -m755 -Dt $(DESTDIR)$(LIBDIR) libuseful.so.1.0.0
ln -s $(DESTDIR)$(LIBDIR)/libuseful.so.1.0.0 $(DESTDIR)$(LIBDIR)/libuseful.so.1
ln -s $(DESTDIR)$(LIBDIR)/libuseful.so.1 $(DESTDIR)$(LIBDIR)/libuseful.so
Note the $(DESTDIR)
use.
First we copy all header files then the shared library. Here I use install(1), but a combination of
mkdir(1), cp(1) and chmod(1) would create an equivalent result. I also make some specific-version
symbolic links for the library. Variables to hold filenames and the version could be used. They are omitted to limit
scope of the example.
What about Lua?
Even with standards, conventions, recommendations makefiles in the wild have a lot of variation. For Lua there are no
conventions around, so we will see even more variation among the projects. I want to respect this variation when
proposing an install target for a Lua project. Let's take a look.
I selected a set of projects available in arch package repository, took a look upstream, took a look at PKGBUILDs,
searched some more through Github. From that I identified four groups of interest:
- Projects that leave the entire installation process to the user (e.g., LPeg or any one-file Lua module).
- Projects that have makefile with
install
target or equivalent (we're interested in these).
-
Projects that have rockspec files and rely on Luarocks builtin installation handling or equivalent (e.g., Penlight,
Busted).
- Projects that have other (Luke, CMake) or hybrid installation definitions (LuaRocks+sth) (e.g., luaposix, luasdl2).
Some projects had both makefiles and rockspecs (using builtin build type). I did not try to dig out why, but I guess
it was either: migration to rockspec, developer preference, or similar. Another scenario are projects that did not have
their own rockspec got one written by a package maintainer for LuaRocks (since it's required) or distro-specific package
repository (due to preference). These kind of rockspecs could get merged to the upstream project.
From projects with makefiles I selected:
-
cldr-lua, shamelessly uses luarocks in
install
target.
-
cosmo,
LUA_DIR
and
LUA_LIBDIR
.
-
LuaRocks,
luadir
, which is
inline with their recommendation.
-
luaexpat,
LUA_V
with
LUA_LDIR
and LUA_CDIR
.
-
lanes,
LUA_SHAREDIR
and
LUA_LIBDIR
-
lgi/lgi, same names with addition of
LUA_VERSION
- lua-repl, :)
-
libmpack-lua, rather unique
LUA_CMOD_INSTALLDIR
.
-
Lua-cURLv3,
LUA_VERSION
with
LUA_LMOD
and LUA_CMOD
-
luasocket,
LUAV
with
LDIR
and CDIR
.
-
luasystem, same but
LUA_VERSION
.
-
lua-term,
LUA_VER
with
LUA_SHARE
and LUA_LIBDIR
-
lua-TestMore,
LUAVER
with LIBDIR
and nothing for shared libraries
How are these makefiles used when their parent project is packaged for different OS distributions? Well, each
distribution has its own packaging processes. Let's take a quick look at two.
Debian/Ubuntu in case of Lua packages will not use them. They have dh(1) and dh_lua(1) which together
take care of the packaging process. We could try to take a look at the helper scripts, but at the time of writing they
didn't appear helpful for this article.
Arch Linux and its PKGBUILDs for lua packages prefer to use LuaRocks to support package making. In some cases if a
makefile from an upstream project was close enough to a conventional makefile, it could and was used. However, at least
one package maintainer later on judged that it's simply easier to use LuaRocks for everything and even recommended
writing rockspecs if not present upstream. Despite that there's a number of packages that simply copy things around
since the upstream projects did not have makefiles, install targets or rockspecs.
I think this is enough information to make decisions. I also looked at some CMakeFiles and pkg-configs.
Let's set goals. Our makefiles and install
targets must be:
- Simple; they must be readable and understandable, so they can be used and trusted.
- Generic; they must be able to work under slightly (or more) different path conventions of different distributions.
-
Targeted; they must build and install our software for only one version of Lua but they need to support choosing
what that version it is (there was an interesting pattern in Arch PKGBUILDs where none of the multi-version builds
were used). Switching between versions must be simple.
- Consistent; they must be similar to other makefiles, Lua and beyond, we have a decent set of samples to attempt it.
-
No duplication; you know me, they must have exactly one set of unique variables that affect all targets, i.a., not
split between build and install stages. Of course, this might backfire in cross-compilation or another similar
context.
Proposition
There's a shorter version better suited for closer inspection and copying below.
|
The first or second spot seems fitting for the version. Before build definitions, too.
I couldn't decide. Full name seems the best. I was thinking about LUAVERSION quite a lot since I was
using it for my projects. In the end underscore is consistent with Lua's LUA_PATH variables. Shorter
variants like LUAV seem to create an impression of a library or utility name (e.g., luvit/luv,
guidanoli/luav).
Change your default value from 5.4 as necessary.
|
PREFIX=/usr/local
The first spot rival. It lost because it wouldn't need to appear before build definitions.
It defines the base path for everything else. Do not mistake or mix it with DESTDIR . The default value
rarely changes, to understand it better consult sources listed somewhere above.
|
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
DATADIR=$(PREFIX)/share
MANDIR=$(DATADIR)/man
MAN1DIR=$(MANDIR)/man1
|
Not-Lua-specific install directories. Like before I skipped some since there's only a low chance of using them.
We want these for consistency and to allow users do more granular path injection, e.g., LIBDIR with a
platform triplet.
|
LUA_CMOD=$(LIBDIR)/lua/$(LUA_VERSION)
LUA_LMOD=$(DATADIR)/lua/$(LUA_VERSION)
|
These are the target locations respectively for shared library and script Lua modules.
These names slowly grow in me every time I review the article. They read nicely: "C language, so
library, MOD -ules for LUA " and similarly "pure L ua" or "L ua, so
pure". These are used mainly by pkg-config files (e.g., Arch Linux, homebrew, chromebrew) and sometimes in projects.
Another candidate for pure modules was LUADIR which is the most popular and recommended by LuaRocks but
it is conflicting with the same name pointing at Lua include files (e.g., LPeg uses it like this). A candidate for
library modules LUA_LIBDIR has the exact same problem (this one is even worse, because it reads as
"LIBDIR for LUA ).
One concern I still think about is the broken parallel between LUA_LMOD /LUA_CMOD and
LUA_PATH /LUA_CPATH .
|
LUA_CFLAGS=\
`pkg-config \
--cflags \
lua$(LUA_VERSION)`
LUA_LDLIBS=\
`pkg-config \
--libs \
lua$(LUA_VERSION)`
|
If you need to compile some modules you will need these. I'm lazy and prefer to use pkg-config(1). It nicely
cooperates with the version variable allowing to meet the simple version switching goal.
Now, since pkg-config is used rather than just writing things by hand, a question appears. More than one
actually, since a simple CFLAGS creates multiple questions about customization (some are answered by e.g.,
+= ), defaults, optimization. However, I don't think this is the time to discuss them.
Here, I only wanted to point out that LUA_VERSION can and most likely should be reused when getting Lua
versioned include directories.
Lines are broken for the sake of the column width, readability of this page and to save me from tweaking the style
more than I need, since I can get lost in an endless loop of moving elements by one pixel in random directions.
|
export LUA_PATH=\
$(PWD)/src/?.lua;\
$(PWD)/src/?/init.lua;;
export LUA_CPATH=;;
|
Occasionally, we may need to execute some Lua code as part of our makefile and use some of our code.
export is not yet part of POSIX, so workaround it with shell or env(1).
LUA_CPATH is an almost no-op for the sake of appearance.
|
|
Traditionally executed commands have their own variables (btw. they are also called macros). This adds just another
opportunity to modify them since make already runs in some environment that can be tweaked. Still, it can be useful for
e.g., injecting LUA_PATH mentioned above.
| |
I had to break the two column view for the last part, because it would look comically. Installation target itself:
install: all
install -m755 -Dt $(DESTDIR)$(BINDIR) wrap
install -m644 -Dt $(DESTDIR)$(MAN1DIR) wrap.1
install -m755 -Dt $(DESTDIR)$(LUA_CMOD) useful.so
install -m644 -Dt $(DESTDIR)$(LUA_LMOD)/wrap wrap/*.lua
This assumes that:
- Build produces an executable
wrap
and shared library Lua module useful.so
in the same
directory makefile resides in.
- There's a
wrap/
subdirectory that contains pure Lua modules that require installation.
- There's
wrap.1
manpage in the makefile directory.
I adjusted two of my unfinished projects to use this convention, so that you can see a makefile without this kind of
assumptions. See activity and
srcinfo.
As an alternative to direct use of install(1) and to increase control over commands executed with make see
Variables for Specifying
Commands.
Finally, the short version of the makefile:
LUA_VERSION=5.4
PREFIX=/usr/local
BINDIR=$(PREFIX)/bin
LIBDIR=$(PREFIX)/lib
DATADIR=$(PREFIX)/share
MANDIR=$(DATADIR)/man
MAN1DIR=$(MANDIR)/man1
LUA_CMOD=$(LIBDIR)/lua/$(LUA_VERSION)
LUA_LMOD=$(DATADIR)/lua/$(LUA_VERSION)
LUA_CFLAGS=`pkg-config --cflags lua$(LUA_VERSION)`
LUA_LDLIBS=`pkg-config --libs lua$(LUA_VERSION)`
CFLAGS=$(LUA_CFLAGS)
LDLIBS=$(LUA_LDLIBS)
all:
touch wrap useful.so
install: all
install -m755 -Dt $(DESTDIR)$(BINDIR) wrap
install -m644 -Dt $(DESTDIR)$(MAN1DIR) wrap.1
install -m755 -Dt $(DESTDIR)$(LUA_CMOD) useful.so
install -m644 -Dt $(DESTDIR)$(LUA_LMOD)/wrap wrap/*.lua
I still think I would prefer them without the underscores, but then LUALMOD
would be one letter away
from LUAMOD
which would be annoying to deal with since it sounds like it would hold a name of a module.
What do you think? What do you use yourself (other than LuaRocks)? Do you like the two column code view thing? Until
I create some proper feedback channel, just feel free to send me an email!