Tools
Projects and packages
A Burn project is a folder with a burn.toml. Projects share code as packages: any git repository with a
burn.toml at the top is one, named after where it lives.
github.com/owner/project
└──┬─────┘ └─┬─┘ └──┬──┘
domain owner projectThere is nothing to register. Push the code, tag a version, and everyone can install it with ash, the package manager, which comes with the toolchain.
Creating a project
burn init github.com/you/hello # an app that builds to a native executable
burn init github.com/you/hello --target bvm # an app that builds to bvm bytecode
burn init github.com/you/hello --target bar # an app that builds to a runnable archive
burn init github.com/you/hello --target js # an app that builds to JavaScript for Node.js
burn init github.com/you/colors --lib # a libraryash init <name> does the same. ash init without a name asks step by step instead, with a suggestion for every
answer: the package name (from your git user and the folder), whether to create an app, a library or a workspace,
what an app builds to, the directory and whether to create a git repository. It shows what it will create and asks
before creating it:
$ ash init
Create a new Burn project
? Package name (github.com/you/my-project)
? What do you want to create?
› 1 app a program you run
2 lib a package others import
3 workspace a shared library and one app per target
? What should `ash build` make?
› 1 native an executable for this computer
2 js a JavaScript file for Node.js
3 bvm portable bytecode (.bvmc)
4 bar a runnable archive (.bar)
? Directory (my-project)
? Create a git repository? (Y/n)An app gets src/main.bn, a library gets src/lib.bn and a test in tests/main.bn.
Both get a burn.toml, a .gitignore, a README.md and a git repository (skip it with --no-git). Pass a
directory after the name to create the project somewhere else, or . for the current folder.
Project layout
Every project has the same shape, so tools and people always know where things are:
hello/
├── burn.toml the manifest
├── burn.lock exact versions of the dependencies (written by ash)
├── src/ the sources; folders in it are packages
│ ├── main.bn the program (an app), or lib.bn (a library)
│ ├── net/
│ │ ├── mod.bn the module `net` (optional)
│ │ └── http.bn the module `net/http`
│ └── bin/ more programs in the same project
│ ├── tool.bn `burn run --bin tool`
│ └── server/
│ └── main.bn `burn run --bin server`
├── tests/ every file is a test program: `burn test`
├── examples/ `burn run --example <name>`
└── build/ what `burn build` writes (ignored by git)Sources import each other through the project's source root, wherever they are, with @/. Other projects use
the package name, the same path written out in full:
import "@/net/http" // inside the project
import "github.com/you/hello/net/http" // from anywheresrc/ is the folder of the main file, so a project whose main is somewhere else has its source root there.
Inside a project, burn commands work without a file name:
| Command | What it does |
|---|---|
burn run [args] |
run main on bvm |
burn run --bin <name> |
run src/bin/<name>.bn (or src/bin/<name>/main.bn) |
burn run --example <name> |
run examples/<name>.bn |
burn build |
build main and every program in src/bin for the project's target into build/; --target overrides it, --bin and --example build one program |
burn check |
type-check every source, program, test and example of the project |
burn test [filter] |
run each file in tests/; a test fails when it exits with an error, such as a failed assert |
In a workspace, each of these takes -p <member>.
Workspaces
A workspace is one repository with several projects that are built together, like a Gradle multi-project build or a Cargo workspace. The usual shape keeps the shared code in one library and adds one app per target, so the same program ships as a native executable, a JavaScript file and bvm bytecode:
burn init github.com/you/game --workspace # common + native, js and bvm apps
burn init github.com/you/game --workspace --targets native,bargame/
├── burn.toml [workspace] with the members
├── burn.lock one lock file for every member
├── common/ github.com/you/game/common, a library
│ └── src/lib.bn
├── native/ github.com/you/game/native, target = "native"
│ └── src/main.bn import "github.com/you/game/common"
├── js/ github.com/you/game/js, target = "js"
└── bvm/ github.com/you/game/bvm, target = "bvm"[workspace]
name = "github.com/you/game"
members = ["common", "native", "js", "bvm"]- Members are ordinary projects named under the workspace (
github.com/you/game/common). They import each other by name without listing each other as dependencies; apathdependency works too. - All members share the
burn.lockat the root, so they always use the same versions of their packages. - At the root,
burn build,burn checkandburn testwork on every member, and-p <member>picks one (burn run -p native,burn build -p js). Inside a member's folder, commands work on that member alone. burn init <name>inside a workspace creates a new member and adds it tomembers; a plain name such asburn init tools --libis named after the workspace (github.com/you/game/tools).- Other projects can install a member like any package:
ash install github.com/you/game/commondownloads the repositorygithub.com/you/gameand uses itscommonfolder. A member that others install should list the members it uses aspathdependencies; ash pins them to the same commit.
burn.toml
[package]
name = "github.com/you/hello"
version = "0.1.0"
kind = "app"
target = "native"
main = "src/main.bn"
description = "Says hello"
[dependencies]
"github.com/you/colors" = "^1.2"
"github.com/you/local" = { path = "../local" }
"example.org/team/tool" = { version = "^0.3", git = "https://example.org/team/tool.git" }
[scripts]
dev = "burni src/main.bn"
release = "burnc src/main.bn -o build/hello.bvmc"
start = "burn build/hello.bvmc"| Key | Meaning |
|---|---|
name |
where the code lives: <domain>.<tld>/<owner>/<project> |
version |
the version, tagged in git as v<version> when it is published |
kind |
"app" (a program) or "lib" (a library that others import) |
target |
what burn build makes: "native" (an executable, the default), "js" (JavaScript for Node.js), "bvm" (bytecode) or "bar" (a runnable archive) |
main |
the entry file (default src/main.bn, or src/lib.bn for a library) |
output |
where burn build writes, relative to the project (default build/<project>) |
std |
false builds without the standard library (default true) |
burn |
the Burn version the project uses, installed and selected by burnup |
[dependencies] |
packages, with a version, or a table with version, git and path |
[scripts] |
shell commands for ash run <name>, or just ash <name> |
Installing packages
ash install github.com/you/colors # newest version, saved as "^x.y.z"
ash install github.com/you/colors@1.2 # a version range
ash install github.com/you/colors@main # a branch
ash install ../local # a folder on this machine
ash install # everything in burn.toml, as burn.lock pins it
ash update # newest versions burn.toml allows
ash remove github.com/you/colors
ash list # the installed packages as a treeVersions are git tags such as v1.2.0:
| Written | Allows |
|---|---|
"^1.2" |
1.2.0 up to, not including, 2.0.0 |
"~1.2" |
1.2.x |
"=1.2.3" |
exactly 1.2.3 |
">=1.2" |
1.2.0 and anything newer |
"*" |
the newest version, or the newest commit when there are no tags |
"main" |
the newest commit on a branch |
"#1a2b3c" |
one exact commit |
burn.lock
burn.lock is the index of what is installed: the exact commit of every package, including the packages your
packages need. ash writes it; commit it so everyone builds the same code.
version = 1
[[package]]
name = "github.com/you/colors"
version = "v1.2.0"
rev = "4f1c2b0e9a7d3c5b8e6f1a2d3c4b5a6f7e8d9c0b"
source = "git+https://github.com/you/colors"
dependencies = []Downloads are kept in ~/.burn/packages/<name>@<commit>, so each version is fetched once and shared by every
project. Local packages (source = "path+../local") are used where they are.
Importing a package
import "github.com/you/colors"
import "github.com/you/colors/extra"
print(paint("hello"))The package name loads the package's main file; a longer path loads a module from its src/ folder
(extra is src/extra.bn, and the older form with src/ written out still works). The compiler finds
packages through burn.lock, so imports never touch the network. A project can import its own files by its name
too, which is how a library's tests use it: import "github.com/you/colors".
Apps as packages, and mixins
Apps can be packages too. Importing an app's name loads its main file like a library; the importing program's own
main is the one that runs. To change an app without touching its source, import it as bytecode and use
mixins:
import "github.com/you/game.bvmc"
@Inject(target: "score", at: "return")
fun doubled(points: int, result: int): int {
return result * 2
}.bvmc after a package name compiles the package to a bytecode library (into its build/ folder, again only when
its sources change) and links it in. Its top-level code and main run first, so running this program runs the game
with the change applied, on bvm and in .bar archives alike.
Scripts and commands
ash run release # run a script from burn.toml
ash release # the same, when no ash command has that name
ash build # the build script, or burn build
ash start # the start script, or burn run
ash test # the test script, or every file in tests/
ash install -g github.com/you/tool # build an app and install it as a command in ~/.burn/binPublishing
ash publish --tagchecks that the project type-checks, that git's origin matches the package name and that nothing is
uncommitted, then tags v<version> and pushes the tag. From then on ash install <name> works for everyone.
To be found by ash search, open an
Add a package issue on the ash index
with the git URL. A check clones the repository, confirms that it is a Burn project named after where it is
hosted, and the burnt bot adds it and closes the issue; if something is missing, the bot says what in the issue.
The standard library for tools
std/process and std/fs give Burn programs what tools like ash need: running programs (Process.run,
Process.output, Process.shell), the working directory, and managing files and folders (Files.makeDir,
Files.list, Files.remove, ...). See the standard library.