Introduction
Battery is a tool for building Volt programs. In the future it will fetch code and more. But for now, it builds programs.
How To Use
Battery makes some assumptions about project structure. Your source code will be in a directory named src
. If your project is an executable, its entry point will be in src/main.volt
. If you have tests, they will be in the test
directory. If there is a res
folder, Volta will be told to look for string imports (-J
) there. So for a simple executable project that only depends on Watt (Volt's standard library, you might have your code in src/main.volt
:
module main;
import watt.io;
fn main() i32
{
writeln("Hello, world.");
return 0;
}
Hardly earth shattering stuff, but every great project starts somewhere. So to build that, we describe the project briefly in a battery.toml
file in the root directory:
name = "hello"
dependencies = ["watt"]
dependencies
tells battery of a project dependency (something it needs to work) and then next line names it: watt. And that's all. No matter how many modules you add to your project, how labyrinthine your project structure becomes, as long as all the modules are under the src
directory, Battery will find them.
In the future, we'll have jetpacks. Also, Battery will know where watt lives on the internet and will fetch it for you. But for now, we'll have to do a little legwork. Once you have the Volta and Watt source code somewhere on your system, we first invoke the config
command, to get Battery to set up the build:
battery config /path/to/Volta /path/to/Watt .
And if that's gone well, we can now build:
battery build
This will build everything (including Volta!) and you'll have a shiny new program.
Sometimes build steps are a little more complicated. Battery lives in the real world. Here's a real example from a project with a couple more dependencies:
name = "Oleum"
dependencies = ["watt", "amp"]
[platform.msvc]
libraries = ["SDL2.lib"]
[platform.'osx || linux']
commands = ["sdl2-config --libs"]
We'll go in depth on the commands in a later section. For now, notice we can do different things depending on the platform, and even run external tools to get library names and so on.
Projects And Subprojects
Battery organises things in terms of projects. In the example above, Volta
is a project, as is Watt
. They're given using local paths above, but in future URLs, and perhaps just their names will be enough for Battery to locate them. The important thing to understand is that a project is collection of code, that can be 'depended' upon, that is, required for another project to build. That is what the
dependencies = ["watt"]
lines in the above battery.toml
files are saying. "This project depends on watt
, and requires it to build."
Projects can have subprojects. That is, projects that are children of another project. This is useful if individual pieces of your project are useful outside of the context of the larger one. Say you were writing an emulator for the smash arcade hit, Cosmos Attackers. The Silog S81 processor used by Cosmos Attackers was used in other arcade hits of the time, such as Pack Mun, and Dug Dog, so you might want to use your S81 emulator in other projects.
To make a project a child of another, simply place it in its parent's project folder. So your tree might look like
cosmo_em
src // ui code, etc
battery.toml
s81
battery.toml
src
Then, in cosmo_em
's battery.toml
(and any other project that wants to use your S81 emulator), add a dependencies
of ["cosmo_em.s81"]
. Set s81
's name
as "cosmo_em.s81"
. Parents do not automatically depend on their children, nor do children on their parents. Other than the lookup syntax with .
, subprojects behave just as regular projects.
Project Name Restrictions
A project's name is based entirely on its folder name.
Project names must start with an alphabetical character, or an underscore, and the rest of the name must be either an alphabetical character, an underscore, or a digit.
If a project doesn't confirm to the above specification, the config
step will fail with an error displaying the name that failed to validate.
Commands
Battery is built around a few 'commands'. These are our verbs. They are as follows:
config
build
test
help
init
All are invoked using battery <command name>
. We used the config
and build
commands in the previous section.
Help
This command doesn't do anything but give information. This command can give you a quick reminder on how to use Battery, or more in depth documentation on the various commands. battery help config
will give help on the config
command, for instance.
Config
We've seen the config
command already. Here are some flags that can affect it.
--arch arch Selects architecture (x86, x86_64).
--platform plat Selects platform (osx, msvc, linux).
--exe Force an executable target.
--lib Force a library target.
--name name Name the current target. This is used with the --dep parameter, for instance.
--dep depname Add a dependency.
--src-I dir Use a different source directory, instead of src.
--cmd command Run the command and treat the output as parameters.
--cmd-(tool) Specify an external tool location. e.g. Tesla, or nasm.
-l lib Add a library to link with.
-L path Add a path to search with libraries.
-framework name Add a framework to link with. (macOS only)
-F path Add a framework search path. (macOS only)
-J path Define a path for string import to look for files.
-D ident Define a new version flag.
-o outputname Set output (executable, etc) to outputname.
--debug Set debug mode.
--Xld Add an argument when invoking the ld linker.
--Xcc Add an argument when invoking the cc linker.
--Xlink Add an argument when invoking the MSVC link linker.
--Xlinker Add an argument when invoking all different linkers.
--if-(platform) Only do the following for (platform).
--if-(arch) Only do the following for (arch).
You probably recognise quite a few of these from Volta.
Build
Actually performs the build. If there's a test
folder, the tests will be run after the build completes. If the build failed, the return value will be non-zero. This only works after config has been run.
If you pass --verbose
to this command, the exact commands that Battery runs will be printed.
Init
Init will setup a very simple project skeleton for you. If invoked with no arguments, it will ask you for the project name, and whether it's a library or an executable. These answers can be given ahead of time with the --init-name
and --init-type
flags, respectively.
Test
Runs tests if the test
directory is present. Runs build
if it has to. The tests are described in a json file named battery.test.json
.
battery.test.json
The file contains an object. The various properties are documented below.
pattern a string, like "*.volt" or "test.volt" that
describes what files contain tests. (non-optional).
patterns an array of strings, treated as 'pattern above'.
you have to specify 'pattern' or 'patterns',
you shouldn't have both. (non-optional).
testCommandPrefix a string that describes what lines that contain
test commands (more on them later). (non-optional).
macros an object containing lists of commands that can be
invoked with the macro: command. (optional).
requiresAliases an object containing strings that are expanded in
requires commands. (more on them later). (optional).
Commands
Each individual test is described with various commands. The commands consist of a word and an argument, separated by a colon. The lines containing commands are prefixed by a string (usually a comment and a special character) given in battery.test.json
with the testCommandPrefix
property.
run
runs a given command. If the return value is 0, then that run
is considered okay. (This can be overriden with the retval
command). There are some special variables that can be expanded:
%s - the path to the test file
%S - the path to the test directory
%t - a unique name for that test -- useful for output filenames etc.
%T - a unique output directory for that test.
So for instance. Let's say we were writing a C compiler, and we wanted battery to test it. Given a pattern
of hello.c
, and testCommandPrefix
of /*T
, a very simple C test could be as follows:
/*T run:cc -o %t %s */
/*T run:./%t */
int main() { return 0; }
So if the compilation failed, or the output didn't return 0, then the test would be marked as a failure. What if we wanted our program to return 3? Or to test that the compilation failed? How would we do that? Enter the retval
command:
/*T retval:0 */
/*T run:cc -o %t %s */
/*T retval:3 */
/*T run:./%t */
int main() { return 3; }
The retval
command gives the expected return value for the next run
command that appears. Note that if you give one retval command, every run command must have an explicit retval command, even if you're expecting zero.
A good test suite has a lot of tests. And if you have a lot of tests of the same format, you don't want to have to specify the above every single time. macro
will save the day here.
First, in our battery.tests.json
file, add a "macros" property, and add a few entries.
"macros": {
"default": [
"/*T run:cc -o %t %s */",
"/*T run:./%t */"
],
"failure": [
"/*T retval:1 */",
"/*T run:cc -o %t %s */"
]
}
The default
macro will be used for every test with no action. So our simplest test becomes:
int main() { return 0; }
Then, we can check for failure in compilation with our failure
macro. Use macro:
and then the name of the macro to invoke. It will be as if the commands were written out explicitly.
/*T macro:failure */
int main() return 0 }
The check
command checks the stdout and stderr of the runs, and passes if the given string occurs anywhere in that output, and fails otherwise.
/*T check:hello, world */
#include <stdio.h>
int main() { printf("hello, world\n"); return 0; }
Multiple checks can be specified, and they must occur in order. They don't have to be adjacent, just earlier checks must occur before later checks.
The requires
command allows you to check simple platform and architecture conditions, skipping platforms that fail. This allows you to check OS specific things in a clean fashion. The requiresAliases
property of battery.tests.json
allows you to define new keywords with the same expression syntax.
requires:windows && !x86
This test will only be run on windows, and will be skipped on x86. These platforms and architectures are the same used elsewhere in battery, run battery help config
for details.
And finally, has-passed:no
denotes that you are aware that a test is failing, and not to mark it as a regression.
battery.toml
A brief description of the battery toml format.
battery.toml
is in the toml format. It specifies project properties through various keys. Keys can be modified on a per platform basis through platform
tables.
Keys
dependencies An array of name strings that this project depends on.
name The name of the project. (string)
output The name of the executable. (string)
scanForD Internal use only.
isTheRT Internal use only.
srcDir Explicitly set the source directory. (string)
testFiles Specify the test json files. (array of strings)
jsonOutput Filename for JSON output. (string)
libraries Libraries to link with. (array of strings)
libraryPaths Paths that the linker should look in. (array of strings)
frameworks Frameworks to link with. (macOS only) (array of strings)
frameworkPaths Locations of frameworks. (macOS only) (array of strings)
stringPaths The location for Volta's string imports. (array of strings)
ldArguments Arguments to pass ld. (array of strings)
ccArguments Arguments to pass cc. (array of strings)
linkArguments Arguments to pass link. (array of strings)
linkerArguments Arguments to pass the linker. (array of strings)
asmFiles Files to assemble with nasm. (array of strings)
cFiles Files to compile with clang. (array of strings)
objFiles Files to link. (array of strings)
voltFiles Additional files to compile. (array of strings)
versionIdentifiers Version identifiers to set (array of strings)
commands Commands to run, and parse `-l` and `-L` out of their output. (array of strings)
warningsEnabled Tells the Volta compiler to generate warnings. (boolean)
Platform identifiers can be simple: [platform.msvc]
. The second part can be a string: [platform.'msvc']
. You can &&
or ||
platforms, as well !
ing them: [platform.'!msvc && !macOS]
.
llvm.toml
llvmVersion="7.0.0"
clangPath="C:/Path/To/Clang.exe"
This file format is mostly intended for Windows systems where llvm-conf
can't be used. Use --llvmconf
with config
on the command line, or use the llvmConfig
key to specify it.
llvmVersion
specifies the version in semantic versioning. clangPath
specifies the path to the clang executable to use.