Writing a Toolchain

Toolchains tell lrc how to configure the underlying C compiler for a specific target platform.

The platform could be an excentric CPU architecture or an operating system/execution environemnt which requires extra processing (such as e.g. WebAssembly).

Setup

To create a custom toolchain, create a new folder containing a file lrocket/toolchain/<name>.lua.

Packaging

Note If you are creating a toolchain in the frame of creating a custom otype, you may skip the following steps and just add a file at lrocket/toolchain/<name.lua> to your otype directory.

If your toolchain is standalone (not part of a custom otype), you may wish to generate a rockspec via luarocks, in order to install, test and distribute your toolchain:

> mkdir -p <lrocket-toolchain-my-toolchain>/lrocket/toolchain
> cd <lrocket-toolchain-my-toolchain>
> touch lrocket/toolchain/<my-toolchain>.lua
> luarocks write_rockspec

Your directory should then look like this:

> tree
.
├── lrocket
│   └── toolchain
│       └── my-toolchain.lua
└── lrocket-toolchain-my-toolchain-dev-1.rockspec

2 directories, 2 files

Implementation

The main file of your toolchain is lrocket/toolchain/<my-toolchain>.lua.

It should export a Lua table which implements the following functions:

Required Functions

Basic Structure

--lr:abi 1.2.0

local <my-toolchain> = {}

-- implementation
...

return <my-toolchain>

function configure (cfg, userargs)

Parameters

  • cfg – the read/write accessible LRocket compiler configuration (lrocket.FinalConf) containing all settings for this toolchain

  • userargs – the build arguments specified by the user (lrocket.BuildConf) e.g. via their rockspec.build config, global settings or lrc command-line arguments

This function is required and should perform any required configuration steps for the toolchain, such as adding global compiler arguments, setting toolchain-specific environment variables (variables such as CC or LDFLAGS are set automatically) or selecting a required Lua version.

To see how exisiting toolchains do it, take a look at the following examples:

SDKs typically use GNU style compilers.

In this case you can often inherit from the pre-shipped gnu Toolchain:

@ 'lrocket/toolchain/<my-toolchain.lua>'

[top of file]
+local gnu = require 'lrocket.toolchain.gnu'
+local utils = require 'lrocket.utils'

[end of file]
-return <my-toolchain>
+return utils.inherit(<my-toolchain>, gnu)

An example configure function based on the gnu Toolchain could look like this:

Example

-- example toolchain 'minimal'
local gnu = require 'lrocket.toolchain.gnu'
local utils = require 'lrocket.utils'

--lr:abi 1.2.0
local minimal = {}

function minimal.configure(cfg, userargs)
	-- example: force LuaJIT
	cfg.lua_version = 'jit'
	cfg.env.LUA_VERSION = cfg.lua_version

	-- set some environment variable
	cfg.env.MINIMAL_BUILDVAR = tostring(cfg.target)

	-- add some compiler arguments
	cfg.cconf:addarg('include', '~/.minimalos/include')
	cfg.cconf:addarg('ldflag', '-lminimalcore')

	-- custom lua library
	cfg.env.LUA_INCDIR = '~/.minimalos/include/minimal-luajit-2.1'
	cfg.env.LUA_LIBDIR = '~/.minimalos/lib/minimal-luajit-2.1'
	cfg.env.LUA_LIBDIR_FILE = 'liblua-jit.a'

	-- configure the toolchain since our toolchain is based on it
	gnu.configure(cfg, userargs)
end

-- inherit from the `gnu` toolchain
return utils.inherit(minimal, gnu)

ABI specification

When you are writing compiler extensions, you will likely be using parts of the compiler API that are not under semantic versioning.

It is therefor recommended to specify the LRocket ABI version by placing a comment in your file:

--lr:abi 1.2.0

This is technically optional, but lrc will throw a warning if the LRocket ABI is not specified.

Testing the Toolchain

For testing your toolchain you need to select (use) it.

There are two common options for activating a toolchain: Telling an otype to force a toolchain or the usually recommended way: defining a Target Rule.

After putting one of the two options in place you can install your toolchain locally in order to test it:

Note If you added your toolchain as part of a custom otype, install your otype locally instead.

> luarocks make  # install the toolchain
# Option 1: using a target rule
> lrc <test.lua> --target x86_64-minimalos-musl -o <output.xxx>

# Option 2: using an otype which forces using the toolchain
> lrc <test.lua> --otype <my-otype> -o <ouptut.xxx>

or in a test .rockspec file:

...
build = {
	type = 'lrocket',
	otype = 'executable'
	entrypoint = 'test.lua',
	output = '<output.xxx>',

	# Option 1: using a target rule
	target = 'x86_64-minimalos-musl'

	# Option 2: using an otype which forces using the toolchain
	otype = '<my-otype>',
}

Publishing

Note If you added your toolchain as part of a custom otype, continue reading at Publishing (Writing an otype) instead.

Once you have tested your toolchain, move on to choosing a license and follow Publishing your code online at the LuaRocks documentation.

Target Rules

Target rules are triggers that are activated when the user builds for a specific operating system, architecture or ABI.

They are the recommended way for selecting a toolchain automatically, when it is implicitly needed (e.g. the android-toolchain when the target OS is android-).

Note Target rule Lua files are run in alphabetical order, so it is recommended to prefix them with with a number for the priority, e.g. lrocket/targetrules/50-<name>.lua

To define a target rule, create a file lrocket/targetrules/<priority>-<name>.lua in your toolchain or otype directory.

The file should export a Lua table which implements the .apply (cfg, userargs) function.

Parameters

  • cfg – the read/write accessible LRocket compiler configuration (lrocket.FinalConf) containing all settings known at the time when the rule is evaluated

  • userargs – the build arguments specified by the user (lrocket.BuildConf) e.g. via their rockspec.build config, global settings or lrc command-line arguments

Example

-- lrocket/targetrules/50-example.lua

--lr:abi 1.2.0
local rule = {}

function rule.apply(cfg, userargs)
	if tostring(cfg.target) ~= 'any-any-any' and (cfg.target:matches 'minimalos') then
		-- automatically select the 'minimal' example toolchain
		cfg.toolchain = 'minimal'
	end
end

return rule