Martin Helmut Fieber

Part 1 of 9 → show all

Lua project setup with LuaRocks

Posted on — Updated

Showing a VS Code window in dark mode with an open Lua file and the sidebar on the left showing the contained project files that get built up in the series.
A look at the project setup created in this article.

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 build 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 well as possible.

Remark: This is how I set up my projects, maybe not by the books, but the way it works best for me in all environments, repeatedly. So please don't take this as gospel.


Lua versions

A note about versions. The latest current Lua version is 5.5 (released in ). Generally, everything in this article series will work with Lua 5.1 (released in ) and up. If this is not the case, a callout such 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 their respective main features can be found on the official Lua version 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.8  Copyright (C) 1994-2022 Lua.org, PUC-Rio
The $ 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 system package manager. For example, on Debian, Ubuntu, and Mint:

$ apt install lua5.3

Take 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; click the Tools Executables folder next.

A screenshot of the SourceForge website, showing multiple download entries for Lua, with the 'Tools Executables' entry highlighted.
The Tools Executables folder is where Lua is to be found.

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

A screenshot of the SourceForge website, showing a list of Lua downloads for different system versions.
Select the version matching the OS.

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.

An open Windows Explorer window, the Downloads folder is selected showing the extracted files from the Lua download. The file 'lua54' is highlighted.
The contents of the extracted archive.

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.

Part of a black windows terminal showing the Lua welcome message when running in interactive mode.
A terminal ready to run Lua.

Hello World

With Lua installed, it's time to create a small test program to verify that everything works as expected. Creating a source file inside a dedicated project folder*, src/main.lua, saying hello to everyone.

-- src/main.lua
local greeting = "Hello, reader."
print(greeting)
A simple "Hello World" program in Lua.

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 system 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,5.5"
This will mark the project's Lua support for version 5.1 and up.

The init command will do a couple of things, some of which are:

  • Creating the lua_module folder and storing all installed dependencies.
  • Detecting the src/main.lua as the main entry point of the program.
  • Add two shortcuts that can be used for executing lua and luarocks from inside the project.
  • Adding a .luarocks folder containing some configuration files and marking the directory as the 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 from.

.gitignore

The luarocks init command automatically generates a .gitignore, if not already present, or appends its entries to an existing .gitignore file. The following entries will be added.

/luarocks
/lua
/lua_modules
/.luarocks
.gitignore

The first two files are local shortcuts to execute lua and luarocks. Then comes the dependency folder lua_modules and the config folder .luarocks.

About .rockspec

The .rockspec file defines the project's 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.6"
}
build = {
   type = "builtin",
   modules = {
      main = "src/main.lua"
   }
}
Some entries are not filled yet. The file format is technically a Lua file.

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 project's git repository, but it can also be other repository types or URLs.

source = {
   url = "https://github.com/MartinHelmut/lua-series.git"
}
Adding a git repository URL.

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"
}
Adding some base description items.

Next comes the list of dependencies. This will hold all necessary project dependencies and one special dependency from the start: lua. Less of an actual dependency; more of the projects supported Lua versions or version range.

dependencies = {
   "lua >= 5.1, < 5.6"
}

And at last, the project's build settings and modules — not yet utilized.

build = {
   type = "builtin",
   modules = {
      main = "src/main.lua"
   }
}
What is called 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
This will indicate if there are problems with the .rockspec file.

And here is 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.6"
}
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.6",
   "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
This will install all dependencies defined in the given .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 ?
Continue reading for the solution.

Resolve module paths

Despite 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
Overriding package.path and package.cpath to add local modules.

Now, there are two ways to use this setup: one is via the -l option of the lua command.

$ lua -l src/setup src/main.lua
Run src/main.lua with required src/setup.lua to find locally 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 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 that the LUA_INIT variable is set and the test script with the inspect module can be run again.

$ lua src/main.lua
{ "Hello, reader.", 42 }
Success, at last.

Remark: The package.path variable can also be used to solve inner project module resolution. Let's say all files will be located inside a src/ folder, adding 'src/?.lua' at the end will enable looking up modules from that directory.

-- src/setup.lua
-- ...

package.path = 'lua_modules/share/lua/' .. version ..
    '/?.lua;lua_modules/share/lua/' .. version ..
    '/?/init.lua;' .. package.path .. ';src/?.lua'
Adding .. ';src/?.lua' at the end to tell Lua to search for modules in src/.

Rock trees

It is important to note where packages are installed when using LuaRocks. So far, having the project set up via luarocks init, all packages were installed locally, inside the project folder. This is only the case due to the presence of the .luarocks and lua_modules folders. When this is not the case, packages are installed globally by default when a config file is not present.

Another option when not in a project is using the --local flag. Be aware: this does not mean local as in "local folder", this means local as in "the user's home directory". To really get a local installation, for example, inside the current working directory, the --tree flag needs to be used, passing the path to a folder.

$ luarocks install --tree=lua_modules inspect
Install inspect inside in lua_modules inside the current working directory.

Enable local scripts

Before continuing to add 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, which is especially handy when already using direnv.

# .envrc
export LUA_INIT="@src/setup.lua"
export PATH=$PATH:./lua_modules/bin
Example .envrc file when using direnv.

The same example command above then becomes the following when executed inside the project directory.

$ some-script --argument ...
Accessing the local 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 automatic code formatting for Lua.

LuaFormatter, my tool of choice, installed via LuaRocks and thereafter 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
List of default format options for LuaFormatter.

With the local script setup enabled or using the path to the executable, the formatter can be run, and all Lua files are automatically formatted.

$ lua-format --in-place src/**/*.lua
This will format and rewrite all files in src.

Static analysis

To lint the project code base, Luacheck can be used, which is 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 is complete without setting up an editor environment. To have some options, I picked five well-known ones: an IntelliJ IDE, in this case CLion (paid), VS Code or VSCodium (free), Sublime Text 4 (paid), and ZeroBrane (free).

JetBrains (CLion)

Screenshot of IntelliJ IDE with the open Lua series project.
The Lua series project open in CLion.

Using any JetBrains product (e.g. CLion) for Lua development, I use the plugin LSP4IJ to support language server clients with SumnekoLua, the Lua language server plugin for IntelliJ products. It comes with dynamic type checking, formatting, find reference, simple refactoring capabilities, and documentation support, to name a few.

It does not support debugging though, so breakpoints won't work and other ways to debug need to be utilized.

To then run Lua files directly from the IDE, a run configuration needs to be provided.

To get a run configuration going, CLion needs a build target defined. In settings look for Build, Execution, DeploymentCustom Build Target.

The CLion settings window, showing the 'Custom Build Target' section.

Create a new build target and name it "Lua", leaving all other options as they are.

The CLion settings window, showing the 'Custom Build Target' section with a new build called 'Lua'.

That's for the target definition. Closing the settings window, the actual run configuration now needs to be defined.

On the right side in the top toolbar is, when no configuration exists, a button saying "Edit Configurations…". If a configuration already exists the same option is in the dropdown where the configuration is selected.

Opening that configuration will show a window like the following.

The CLion Run and Debug configuration window.

This is where the run configuration will be created. Add a new configuration as "Native Application".

The CLion Run and Debug configuration window, with an open sub-window for creating a new configuration.

Select the newly created "Lua" target as runner target. Name the new configuration "Lua", executable is lua*. The program argument should be the current open file, set via the variable $FilePathRelativeToProjectRoot$. Working directory is the project directory.

The last but very important setting is the environment variable LUA_INIT. As discussed in the first part, setting up Lua, this is to resolve LuaRocks module paths based on the setup.lua file.

The CLion Run and Debug configuration window, with a new configuration showing how to setup a Lua runner.
This is how the final configuration looks like.

After creating this configuration it is now possible to run Lua directly within CLion.

A CLion window showing how Lua is run directly from within the IDE. An open example Lua file with some simple print statements, a runner window at the bottom.

Visual Studio Code

Screenshot of VS Code editor with the open Lua series project.
The Lua series project open in VS Code.

For VS Code or VSCodium two plugins do the heavy lifting — the Lua language server providing language support including type checking, autocomplete, code formatting, documentation, and lots more; and the Lua Debug plugin for breakpoints and debugging support.

Both plugins work out-of-the-box. Nevertheless, VS Code's setting "Format on Save" can be used to automatically format the code.

A screenshot of the settings in VS Code to enable 'format on save'. The checkbox is checked.
Makes life that much easier.

Thanks to the Lua Debug plugin it is possible to run Lua right from VS Code.

But, not when using LuaRocks due to modules not being resolved. To fix this, the Lua Debug extension supports setting path and cpath either through a launch.json or the workspace settings, nevertheless, this did not work for me in any capacity*.

To make this work, loading modules installed through LuaRocks, I had to require the setup.lua file at the start of my main entry.

-- src/main.lua
require "src/setup"

local inspect = require "inspect"
local output = {
  "Hello, reader.",
  42
}

print(inspect(output))

With this I'm able to run and debug Lua directly in VS Code.

Sublime Text 4

Screenshot of Sublime Text 4 editor with the open Lua series project.
The Lua series project open in Sublime Text 4.

Three plugins will enable working with Lua in Sublime Text, through package control: LSP, LSP-lua, and Direnv.

LSP is a general package to enable language server protocol support in Sublime. LSP-lua then is the Lua language server.

The package Direnv is to enable direnv support. Direnv is what I use to set environment variables like LUA_INIT to load the src/setup.lua when running Lua to enable a smooth LuaRocks integration.

I tried different packages but could not get support for debugging or breakpoints, therefore other ways to debug need to be utilized.

To properly integrate Lua into Sublime it is best done through a Sublime project. Any open folder can be saved as a project, either by going through ProjectSave Project as … in the menu or by creating a .sublime-project file.

{
  "folders": [
    {
      "path": "."
    }
  ]
}
Defining a project via a *.sublime-project file.

Autocomplete and the language server can be set up through the project file.

{
  "folders": [
    {
      "path": "."
    }
  ],
  "settings": {
    "auto_complete": true,
    "LSP": {
      "lua": {
        "enabled": true
      },
    },
  }
}
some-name.sublime-project

Through the "direnv" extension the LUA_INIT variable defined in .envrc is loaded. This will try to run the src/setup.lua file whenever Lua is run, to enable LuaRocks integration.

To get this working it needs a custom build setup for Lua, defining the working_dir property to find the src/setup.lua file when running the build through Sublime.

This gets defined through the project file. It will need a name, e.g. "Lua", the working_dir that will be a variable called $project_path, the Lua command, a file regular expression, and source selector, telling sublime for what type of code the build is defined.

{
  "folders": [
    {
      "path": "."
    }
  ],
  "build_systems": [
    {
      "name": "Lua",
      "working_dir": "$project_path",
      "cmd": ["lua", "$file"],
      "file_regex": "^(?:lua:)?[\t ](...*?):([0-9]*):?([0-9]*)",
      "selector": "source.lua"
    }
  ],
  "settings": {
    "auto_complete": true,
    "LSP": {
      "lua": {
        "enabled": true
      },
    },
  }
}
some-name.sublime-project
Screenshot of Sublime Text 4 editor with the project setting file.
The whole project setting file.

The file regular expression ^(?:lua:)?[\t ](...*?):([0-9]*):?([0-9]*) will be run against the build result and extract information to enable navigating the result on error in editor.

Now, running Lua directly through Sublime is enabled through ToolsBuild or the related shortcut.

Screenshot of Sublime Text 4 editor running the project code directly through the editor.
Showing an example how Lua was run through Sublime.

ZeroBrane

The start screen of ZeroBrane Studio with the Lua series project opened.
The Lua series project open in ZeroBrane Studio.

ZeroBrane Studio is an IDE focused on Lua development. It has a lot of features focused on Lua, including resources to learn the language right in the editor. It comes with everything needed to write and run Lua.

The only thing I needed to provide separately was LuaRocks support. What worked best for me was to include the src/setup.lua file in my main entry.

Running the Lua series project in ZeroBrane Studio. The editor open with the main file, at the bottom of the screen is some console output from running code.
The Lua series project open in ZeroBrane Studio.

Honorable mention

Neovim is another option that comes with Lua support out-of-the-box. Lua is built-in and plugins for the editor are written in it.

Defold is a game engine using Lua as its scripting language. I comes with an editor that fully supports Lua including debugging capabilities.

What comes next

This article was specifically focused on getting a good environment running to develop Lua. Everything shown here can be found in the companion repository, branch part-1.

The next article focuses on creating, building, and publishing new packages; how to manage versions; and how to add documentation to them.

Until then 👋🏻

← Show all posts | Show all in series