Home

Awesome

REPOSITORY DEPRECATION NOTICE

Lake has been merged into the main Lean repository (at the time of writing, in src/lake). This repository exists solely as an archive of its state prior to that merge.

Lake

Lake (Lean Make) is a new build system and package manager for Lean 4. With Lake, the package's configuration is written in Lean inside a dedicated lakefile.lean stored in the root of the package's directory.

Each lakefile.lean includes a package declaration (akin to main) which defines the package's basic configuration. It also typically includes build configurations for different targets (e.g., Lean libraries and binary executables) and Lean scripts to run on the command line (via lake script run).

This README provides information about Lake relative to the current commit. If you are looking for documentation for the Lake version shipped with a given Lean nightly, you should look at the README of that version.

Table of Contents

Getting Lake

Lake is part of the lean4 repository and is distributed along with its official releases (e.g., as part of the elan toolchain). So if you have installed a semi-recent Lean 4 nightly, you should already have it!

Note that the Lake included with Lean is not updated as frequently as this repository, so some bleeding edge features may be missing. If you want to build the latest version from the source yourself, check out the build instructions at the bottom of this README.

Creating and Building a Package

To create a new package, either run lake init <package-name> [<template>] to setup the package in the current directory or lake new <package-name> [<template>] to create it in a new directory. For example, we could create the package hello like so:

$ mkdir hello
$ cd hello
$ lake init hello

or like so:

$ lake new hello
$ cd hello

Either way, Lake will initialize a git repository in the package directory with a basic .gitignore that ignores the build directory (i.e., build) where Lake outputs build files.

It will also create the root Lean file for the package's library, which uses the capitalized version of the package's name (e.g., Hello.lean in this example), and the root file for the package's binary Main.lean. They contain the following dummy "Hello World" program split across the two files:

Hello.lean

def hello := "world"

Main.lean

import «Hello»

def main : IO Unit :=
  IO.println s!"Hello, {hello}!"

Lake also creates a basic lakefile.lean for the package along with a lean-toolchain file that contains the version string of the currently active Lean, which tells elan to use that Lean toolchain for the package.

lakefile.lean

import Lake
open Lake DSL

package «hello» {
  -- add package configuration options here
}

lean_lib «Hello» {
  -- add library configuration options here
}

@[default_target]
lean_exe «hello» {
  root := `Main
}

The command lake build can then be used to build the package (and its dependencies, if it has them) into a native executable. The result will be placed in build/bin. The command lake clean deletes build.

$ lake build
...
$ ./build/bin/hello
Hello, world!

Examples of different package configurations can be found in the examples folder of this repository. You can also specified a particular configuration file template when using lake init or lake new to control what files Lake creates. See lake help init or lake help new for details.

Glossary of Terms

Lake uses a lot of terms common in software development -- like workspace, package, library, executable, target, etc. -- and some more esoteric ones -- like facet. However, whether common or not, these terms mean different things to different people, so it is important to elucidate how Lake defines these terms:

Package Configuration Options

Lake provides a large assortment of configuration options for packages.

Layout

Build & Run

Cloud Releases

Defining Build Targets

A Lake package can have many build targets, such as different Lean libraries and multiple binary executables. Any number of these declarations can be marked with the @[default_target] attribute to tell Lake to build them on a bare lake build of the package.

Lean Libraries

A Lean library target defines a set of Lean modules available to import and how to build them.

Syntax

lean_lib «target-name» {
  -- configuration options go here
}

Configuration Options

Binary Executables

A Lean executable target builds a binary executable from a Lean module with a main function.

Syntax

lean_exe «target-name» {
  -- configuration options go here
}

Configuration Options

External Libraries

A external library target is a non-Lean static library that will be linked to the binaries of the package and its dependents (e.g., their shared libraries and executables).

Important: For the external library to link properly when precompileModules is on, the static library produced by an extern_lib target must following the platform's naming conventions for libraries (i.e., be named foo.a on Windows and libfoo.a on Unix). To make this easy, there is the Lake.nameToStaticLib utility function to convert a library name into its proper file name for the platform.

Syntax

extern_lib «target-name» (pkg : Package) :=
  -- a build function that produces its static library

The declaration is essentially a wrapper around a System.FilePath target. Like such a target, the pkg parameter and its type specifier are optional and body should be a term of type IndexBuildM (BuildJob System.FilePath) function that builds the static library.

Custom Targets

A arbitrary target that can be built via lake build <target-name>.

Syntax

target «target-name» (pkg : Package) : α :=
  -- a build function that produces a `BuildJob α`

The pkg parameter and its type specifier are optional and the body should be a term of type IndexBuildM (BuildJob α).

Defining New Facets

A Lake package can also define new facets for packages, modules, and libraries. Once defined, the new facet (e.g., facet) can be built on any current or future object of its type (e.g., through lake build pkg:facet for a package facet). Module facets can also be provided to LeanLib.nativeFacets to have Lake build and use them automatically when producing shared libraries.

Syntax

package_facet «facet-name» (pkg : Package) : α :=
  -- a build function that produces a `BuildJob α`

module_facet «facet-name» (mod : Module) : α :=
  -- a build function that produces a `BuildJob α`

library_facet «facet-name» (lib : LeanLib) : α :=
  -- a build function that produces a `BuildJob α`

In all of these, the object parameter and its type specifier are optional and the body should be a term of type IndexBuildM (BuildJob α).

Adding Dependencies

Lake packages can have dependencies. Dependencies are other Lake packages the current package needs in order to function. They can be sourced directly from a local folder (e.g., a subdirectory of the package) or come from remote Git repositories. For example, one can depend on the Lean 4 port of mathlib like so:

package hello

require mathlib from git
  "https://github.com/leanprover-community/mathlib4.git"

The next run of lake build (or refreshing dependencies in an editor like VSCode) will clone the mathlib repository and build it. Information on the specific revision cloned will then be saved to lake-manifest.json to enable reproducibility. To update mathlib after this, you will need to run lake update -- other commands do not update resolved dependencies.

For a theorem proving packages which depend on mathlib, you can also run lake new <package-name> math to generate a package configuration file that already has the mathlib dependency (and no binary executable target).

Syntax of require

The require command has two forms:

require foo from "path"/"to"/"local"/"package" with NameMap.empty
require bar from git "url.git"@"rev"/"optional"/"path-to"/"dir-with-pkg"

The first form adds a local dependency and the second form adds a Git dependency. For a Git dependency, the revision can be a commit hash, branch, or tag. Also, the @"rev" and /"path-to"/"term" parts of the require are optional.

Both forms also support an optional with clause to specify arguments to pass to the dependency's package configuration (i.e., same as args in a lake build -- <args...> invocation). The elements of both the from and with clauses are proper terms so normal computation is supported within them (though parentheses made be required to disambiguate the syntax).

GitHub Release Builds

Lake supports uploading and downloading build artifacts (i.e., the archived build directory) to/from GitHub releases of packages to enable end users to fetch pre-built artifacts from the cloud without needed to rebuild the package from the source themselves.

Downloading

To download artifacts, one should configure the package options releaseRepo? and buildArchive? as necessary to point to the GitHub repository hosting the release and the correct artifact name within it (if the defaults are not sufficient). Then, set preferReleaseBuild := true to tell Lake to fetch and unpack it as an extra package dependency.

Lake will only fetch release builds as part of its standard build process if the package wanting it is a dependency (as the root package is expected to modified and thus not often compatible with this scheme). However, should one wish to fetch a release for a root package (e.g., after cloning the release's source but before editing), one can manually do so via lake build :release.

Lake internally uses curl to download the release and tar to unpack it, so the end user must have both tools installed to use this feature. If Lake fails to fetch a release for any reason, it will move on to building from the source. Also note that this mechanism is not technically limited to GitHub, any Git host that uses the same URL scheme works as well.

Uploading

To upload a built package as an artifact to a GitHub release, Lake provides the lake upload <tag> command as a convenient shorthand. This command uses tar to pack the package's build directory into an archive and uses gh release upload to attach it to a pre-existing GitHub release for tag. Thus, in order to use it, the package uploader (but not the downloader) needs to have gh, the GitHub CLI, installed and in PATH.

Writing and Running Scripts

A configuration file can also contain a number of scripts declaration. A script is an arbitrary (args : List String) → ScriptM UInt32 definition that can be run by lake script run. For example, given the following lakefile.lean:

import Lake
open Lake DSL

package scripts

/--
Display a greeting

USAGE:
  lake run greet [name]

Greet the entity with the given name. Otherwise, greet the whole world.
-/
script greet (args) do
  if h : 0 < args.length then
    IO.println s!"Hello, {args[0]'h}!"
  else
    IO.println "Hello, world!"
  return 0

The script greet can be run like so:

$ lake script run greet
Hello, world!
$ lake script run greet me
Hello, me!

You can print the docstring of a script with lake script doc:

$ lake script doc greet
Display a greeting

USAGE:
  lake run greet [name]

Greet the entity with the given name. Otherwise, greet the whole world.

Building and Running Lake from the Source

If you already have a Lean installation with lake packaged with it, you can build a new lake by just running lake build.

Otherwise, there is a pre-packaged build.sh shell script that can be used to build Lake. It passes it arguments down to a make command. So, if you have more than one core, you will probably want to use a -jX option to specify how many build tasks you want it to run in parallel. For example:

$ ./build.sh -j4

After building, the lake binary will be located at build/bin/lake and the library's .olean files will be located in build/lib.

Building with Nix Flakes

It is also possible to build Lake with the Nix setup buildLeanPackage from the lean4 repository. To do so, you need to have Nix installed with flakes enabled. It is recommended to also set up the Lean 4 binary cache as described in the Lean 4 repository.

It is then possible to build Lake with nix build . or run it from anywhere with nix run github:leanprover/lake.

A development environment with Lean 4 installed can be loaded automatically by running nix develop or automatically on cd with direnv by running direnv allow.

The versions of nixpkgs and lean4 are fixed to specific hashes. They can be updated by running nix flake update.

Thank Anders Christiansen Sørby (@Anderssorby) for this support!

Augmenting Lake's Search Path

The lake executable needs to know where to find the Lean library files (e.g., .olean, .ilean) for the modules used in the package configuration file (and their source files for go-to-definition support in the editor). Lake will intelligently setup an initial search path based on the location of its own executable and lean.

Specifically, if Lake is co-located with lean (i.e., there is lean executable in the same directory as itself), it will assume it was installed with Lean and that both Lean and Lake are located under their shared sysroot. In particular, their binaries are located in <sysroot>/bin, their Lean libraries in <sysroot>/lib/lean, Lean's source files in <sysroot>/src/lean, and Lake's source files in <sysroot>/src/lean/lake. Otherwise, it will run lean --print-prefix to find Lean's sysroot and assume that Lean's files are located as aforementioned, but that lake is at <lake-home>/build/bin/lake with its Lean libraries at <lake-home>/build/lib and its sources directly in <lake-home>.

This search path can be augmented by including other directories of Lean libraries in the LEAN_PATH environment variable (and their sources in LEAN_SRC_PATH). This can allow the user to correct Lake's search when the files for Lean (or Lake itself) are in non-standard locations. However, such directories will not take precedence over the initial search path. This is important during development, as this prevents the Lake version used to build Lake from using the Lake version being built's Lean libraries (instead of its own) to elaborate Lake's lakefile.lean (which can lead to all kinds of errors).