Lua project setup with LuaRocks

Coming from the moon
Even though I already wrote an overview of how to set up a Lua project, this article, part of the longer Lua series, will go into more detail on specific aspects with greater focus. Going through this, the goal is to have a solid foundation to further increment on in later articles of this series, or for any project to come.
Running a first Lua program, using LuaRocks for dependency management, code formatting and linting, and setting up an editor or IDE, to support Lua development as good as possible. Remark, this is how I set up my projects, maybe not by the books, but the way it works best for me on all environments, repeatedly.
Lua versions
A note about versions. The latest Lua version as of writing is 5.4 (released ). Generally everything in this article series will work with Lua 5.1 (released ) and up. If this is not the case, a callout as the following will signal that a higher version is required.
For instance, in a later article I will use the meta-method
__close
, added in Lua 5.4, for a simple profiling tool.
An overview of the versions and there respective main features can be found on the official Lua versions history page.
Installing Lua
First things first, let's check if Lua is already installed on the
system, by running Lua's version command lua -v
in a
terminal.
$ lua -v
Lua 5.4.4 Copyright (C) 1994-2022 Lua.org, PUC-Rio
$
is used to show a command will be entered.
If it prints something similar to the above it's already good to go. If not, Lua needs to be installed first.
macOS
Brew on macOS can be used to install an up-to-date version of Lua.
$ brew install lua
Linux
On Linux, installing Lua is as straight forward as using the systems package manager. For example, on Debian, Ubuntu, and Mint:
$ apt install lua5.3
Have a look at Lua's installation guide to build a more recent version from source if needed.
Windows
On Windows there is a list of available Lua binaries, offering pre-compiled Lua versions ready to be used. Scrolling down on that page to the History section the latest available pre-compiled versions can be found. Choosing one, for example version 5.4.2, will lead to SourceForge, clicking the Tools Executables folder next.

Here is the option of picking either the 32-bit or the 64-bit version, depending on the used system.

After the download, the archive contents can be extracted, finding,
depending on the downloaded version, an executable. For version
5.4.x this is called
lua54
.

Double clicking that file will open a terminal, from which Lua can be run. Alternatively, this file can be placed in a folder that is part of the PATH variable, or made available through it.

Hello World
With Lua installed, it's time to
create a small test program to verify everything
works as expected. Creating a source file inside the dedicated
project folder*, src/main.lua
, saying hello to
everyone.
-- src/main.lua
local greeting = "Hello, reader."
print(greeting)
Executing that file via the terminal from inside the project directory will print the greeting.
$ lua src/main.lua
Hello, reader.
Dependency management with LuaRocks
LuaRocks, the de facto standard package manager for Lua, will be used to manage project dependencies. First, the LuaRocks command line tool needs to be installed on the system.
macOS
Again, using brew on macOS, LuaRocks can be installed with a single command.
$ brew install luarocks
Linux
Same for Linux, a single command does the job, using the systems package manager.
$ apt install luarocks
Windows
First the LuaRocks executable needs to be downloaded, either 32-bit
or 64-bit. The downloads are at the top of the
LuaRocks download page
on GitHub. After downloading and extracting the files, the
luarocks.exe
needs to be
added to the PATH
to make it available as luarocks
command from the
terminal.
Project folder setup
With LuaRocks installed, the init
command helps to set
up a project, including
local dependency management. This means
dependencies will be installed inside the project folder in a
lua_modules
directory.
$ luarocks init --lua-versions "5.1,5.2,5.3,5.4"
The init
command will do a couple of things, some of
those are:
-
Creating the
lua_module
folder, storing all installed dependencies. -
Detecting the
src/main.lua
as main entry point of the program. -
Add two shortcuts that can be used for executing
lua
andluarocks
from inside the project, though, I won't use them and rather depend on my system installation. -
Adding a
.luarocks
folder, containing some configuration files, and marking the directory as root folder for LuaRocks.
And two more I'll have a more detailed look at:
-
Creating or adding to an existing
.gitignore
file. -
Creating a
.rockspec
file with the base project settings and name, taken from the folder name LuaRocks was executed in.
.gitignore
The luarocks init
command auto generates a
.gitignore
, if not already present, or
appends its entries to an existing .gitignore
file. The
following entries are added.
/luarocks
/lua
/lua_modules
/.luarocks
The first two files are local shortcuts to execute
lua
and luarocks
— I'll remove both
entries as I also remove the local shortcuts, using my system
installations. Then comes the dependency folder
lua_modules
and the config folder
.luarocks
, both entries I'll keep in, to be ignored by
git.
About .rockspec
The .rockspec
file defines the projects properties,
like name, version, and used dependencies. A complete list of the
supported options can be found on the
official LuaRocks wiki page for build rules.
-- lua-series-dev-1.rockspec
package = "lua-series"
version = "dev-1"
source = {
url = "*** please add URL for source tarball, zip or repository here ***"
}
description = {
homepage = "*** please enter a project homepage ***",
license = "*** please specify a license ***"
}
dependencies = {
"lua >= 5.1, < 5.5"
}
build = {
type = "builtin",
modules = {
main = "src/main.lua"
}
}
At the top of the file the rockspec format will be defined, indicating the format version, the latest being version 3.
rockspec_format = "3.0"
The source.url
can be many things, I usually point it
to the projects git repository, but it can also be other repository
types or URLs.
source = {
url = "https://github.com/MartinHelmut/lua-series.git"
}
The description
can contain a summary, the project home
page, a license and more.
description = {
summary = "Companion repository to the Lua series.",
homepage = "https://martin-fieber.de/series/lua/",
license = "MIT"
}
Next comes the list of dependencies. This will hold all necessary
project dependencies and one special dependency from the start,
lua
. Less an actual dependency and more the projects
supported Lua versions or version range.
dependencies = {
"lua >= 5.1, < 5.5"
}
And at last, the project build settings and modules, not yet utilised.
build = {
type = "builtin",
modules = {
main = "src/main.lua"
}
}
modules.main
here can also be the name
of the project, e.g. modules.luaseries
.
To ensure all changes to a .rockspec
file are valid the
luarocks lint
command can be used, giving it the filename to validate.
$ luarocks lint lua-series-dev-1.rockspec
.rockspec
file.
And here the final .rockspec
file.
-- lua-series-dev-1.rockspec
rockspec_format = "3.0"
package = "lua-series"
version = "dev-1"
source = {
url = "https://github.com/MartinHelmut/lua-series.git"
}
description = {
summary = "The companion repository to my Lua blog series.",
homepage = "https://martin-fieber.de/series/lua/",
license = "MIT"
}
dependencies = {
"lua >= 5.1, < 5.5"
}
build = {
type = "builtin",
modules = {
main = "src/main.lua"
}
}
Install a module
Let's install something, finally! The inspect module is handy in general, giving an easy way to print any value in Lua for inspection or debugging.
$ luarocks install inspect
The new dependency also needs to be added to the
.rockspec
file to keep it on later installs.
dependencies = {
"lua >= 5.1, < 5.5",
"inspect >= 3.1"
}
On a fresh checkout of the project this will ensure that
inspect
is installed again when running
luarocks install --deps-only
on the
.rockspec
file.
$ luarocks install --deps-only lua-series-dev-1.rockspec
.rockspec
file inside a project.
Let's use inspect
to print a test table.
-- src/main.lua
local inspect = require("inspect")
local output = { "Hello, reader.", 42 }
print(inspect(output))
Oh no, running this code will give an error.
$ lua src/main.lua
lua: src/main.lua:1: module 'inspect' not found:
no field package.preload['inspect']
no file '/opt/homebrew/share/lua/5.4/inspect.lua'
no file '/opt/homebrew/share/lua/5.4/inspect/init.lua'
no file '/opt/homebrew/lib/lua/5.4/inspect.lua'
no file '/opt/homebrew/lib/lua/5.4/inspect/init.lua'
no file './inspect.lua'
no file './inspect/init.lua'
no file '/opt/homebrew/lib/lua/5.4/inspect.so'
no file '/opt/homebrew/lib/lua/5.4/loadall.so'
no file './inspect.so'
stack traceback:
[C]: in function 'require'
src/main.lua:1: in main chunk
[C]: in ?
Resolve module paths
Having a way to manage dependencies, Lua still needs to be told
where to find modules for the project. This is done
through the package.path
and
package.cpath
variables. My preferred way is to have a
src/setup.lua
file defining those.
In that file I can also put other project setup options later, as it will be loaded before running any Lua script.
-- src/setup.lua
local version = _VERSION:match("%d+%.%d+")
package.path = 'lua_modules/share/lua/' .. version ..
'/?.lua;lua_modules/share/lua/' .. version ..
'/?/init.lua;' .. package.path
package.cpath = 'lua_modules/lib/lua/' .. version ..
'/?.so;' .. package.cpath
package.path
and
package.cpath
to add local modules.
Now, there are two ways using this setup; one is via the
-l
option of the lua
command.
$ lua -l src/setup src/main.lua
src/main.lua
with required
src/setup.lua
to find local installed modules.
Another option is to set the LUA_INIT
environment
variable. This variable can either execute Lua code before running a
script, or load a file when the value starts with @
.
By defining the environment variable inside the terminal, Lua can be
run without the -l
option.
export LUA_INIT="@src/setup.lua"
Optionally, to not pollute the environment or re-set the variable
every time, I use
direnv,
defining a .envrc
with my projects environment
variables that will only be loaded when working inside the
respective project folder.
There are releases for all platforms, including Windows*, and a comprehensive documentation, so I won't go into detail here as this is an optional setup step.
Having that done, one way or another, I assume from here the
LUA_INIT
variable is set, the test script with the
inspect
module can be run again.
$ lua src/main.lua
{ "Hello, reader.", 42 }
Enable local scripts
Before continuing adding more tools to the project setup, it is
quite handy to enable the execution of local scripts. When
installing an executable with LuaRocks those binaries are installed
in the
lua_modules/bin
folder.
To execute a script the full path needs to be used.
$ ./lua_modules/bin/some-script --argument ...
To simplify this, the local bin folder can be added to the system PATH, especially handy when already using direnv.
# .envrc
export LUA_INIT="@src/setup.lua"
export PATH=$PATH:./lua_modules/bin
.envrc
file when using direnv.
The same example command above then becomes the following when executed inside the project directory.
$ some-script --argument ...
lua_modules/bin
folder to find
the script named some-script
.
Auto format code
I much appreciate auto code formatting from other projects and languages, like Prettier for JavaScript and TypeScript or clang-format for C++, I also want auto code formatting for Lua.
LuaFormatter
the tool of choice, installed via LuaRocks and afterwards available
via the
lua_modules/bin
folder.
$ luarocks install \
--server=https://luarocks.org/dev luaformatter
Custom format options can be defined in a .lua-format
YAML file inside the project root. I'll only change a
few options for my projects.
# .lua-format
keep_simple_control_block_one_line: false
keep_simple_function_one_line: false
column_table_limit: 1
With the local script setup enabled, or using the path to the executable, the formatter can be run, and all Lua files auto formatted.
$ lua-format --in-place src/**/*.lua
src
.
Static analysis
To lint the project code base
Luacheck
can be used, an executable installed via LuaRocks into the
lua_modules/bin
folder.
$ luarocks install luacheck
Keeping the default options it can be run on all files inside
src
. A full list of all options can be found in the
official Luacheck documentation.
$ luacheck src
Checking src/main.lua OK
Checking src/setup.lua OK
Total: 0 warnings / 0 errors in 2 files
Editor setup
No project setup complete without setting up an editor environment. To have some options I picked two well known ones, IntelliJ IDE from JetBrains (paid) and VSCode from Microsoft (free).
JetBrains

Using any JetBrains product (e.g. CLion) for Lua development, I use the plugin Luanalysis, a EmmyLua fork. It comes with Luacheck bundled and offers many convenient functions like Find Usage, Refactor, Go To, Parameter Hints, and Documentation support, to name a few.
I could not find a way to auto format code on save with LuaFormatter, though.
Visual Studio Code

For VSCode three plugins do the heavy lifting — the Lua language server plugin, the plugin for Luacheck, and another plugin for LuaFormatter.
The only plugin that really needs configuration is the LuaFormatter
plugin, setting the path to the LuaFormatter executable and to the
.lua-format
config file.

VSCode's setting "Format on Save" can be used to auto format the code.

Honorable mentions
Even though I focused on those two editors, I wanted to mention some more options for Lua development.
One of those options is ZeroBrane Studio, an IDE focused on Lua development. It has a lot of features focused on Lua, including resources to learn the language.
Neovim is another option that comes with Lua support out-of-the-box. Lua is even built-in and plugins for the editor are written in it.
Close to my heart, Sublime Text is a great options for development in general. For Lua, I use the LuaExtended plugin, bringing even better support to the editor.
What comes next
This article was specifically focused on getting a good environment running to develop with Lua. Everything shown here can be found in the companion repository, branch part-1.
The next article will focus on dependency management; how to use different LuaRock trees, keeping track of dependencies installed from different sources, but also how to create and publish a package.
Until then 👋🏻