Building Projects

Single-File Programs

i
def main : IO Unit := do
  IO.println "Hello from a single file!"
  IO.println s!"2 + 2 = {2 + 2}"
i

What Is Lake?

Creating a Project

i
-- Contents of lakefile.lean created by: lake new MyProject
import Lake
open Lake DSL

package «MyProject» where
  name := "MyProject"

lean_exe «MyProject» where
  root := `Main
i

Minimal Lake File

Building from the Command Line

i
-- Main.lean: entry point for the compiled executable
def main : IO Unit := do
  IO.println "Built with Lake!"
i

Running an Executable

Multiple Source Files

i
-- Helpers.lean: a helper module in the same project
namespace Helpers

def greet (name : String) : String :=
  s!"Hello, {name}!"

def shout (msg : String) : String :=
  msg.toUpper ++ "!!!"

end Helpers
i

Importing Your Own Modules

i
-- Main.lean: imports and uses the Helpers module
import MyProject.Helpers

def main : IO Unit := do
  IO.println (Helpers.greet "Lake")
  IO.println (Helpers.shout "lean")
i
Build completed successfully (0 jobs).
Hello, Lake!
LEAN!!!

Libraries vs. Executables

i
import Lake
open Lake DSL

package «myproject» where
  name := "myproject"

-- A library target: compiled but not directly runnable
lean_lib «MathUtils» where
  srcDir := "lib"

-- An executable target: produces a runnable binary
lean_exe «myproject» where
  root := `Main
i
warning: /private/var/folders/jn/dg894s5542dbrw7ry6sks0r80000gn/T/tmp.c62TtdrWr9/lakefile.lean:5:2: unknown 'PackageConfig' field 'name'
info: myproject: no previous manifest, creating one from scratch
info: toolchain not updated; no toolchain information found
Build completed successfully (0 jobs).

Running Lean in Project Context

i
-- script.lean: a standalone script that uses project modules
import MyProject.Helpers

#eval Helpers.greet "world"
i
Build completed successfully (0 jobs).
"Hello, world!"

Adding a Dependency

i
import Lake
open Lake DSL

package «myproject» where
  name := "myproject"

-- Add an external dependency from a git repository
require Std from git
    "https://github.com/leanprover/std4" @ "main"

lean_exe «myproject» where
  root := `Main
i
warning: /private/var/folders/jn/dg894s5542dbrw7ry6sks0r80000gn/T/tmp.dMhSDydxYA/lakefile.lean:5:2: unknown 'PackageConfig' field 'name'
info: myproject: no previous manifest, creating one from scratch
info: Std: cloning https://github.com/leanprover/std4
info: Std: checking out revision '708b057842c4cd0845fba132bd94b08493f6fc42'
info: updating toolchain to 'leanprover/lean4:v4.31.0-rc1'
info: restarting Lake via Elan
info: Version 4.2.2 of elan is available! Use `elan self update` to update.
info: downloading https://releases.lean-lang.org/lean4/v4.31.0-rc1/lean-4.31.0-rc1-darwin_aarch64.tar.zst
info: installing /Users/gvwilson/.elan/toolchains/leanprover--lean4---v4.31.0-rc1
warning: /private/var/folders/jn/dg894s5542dbrw7ry6sks0r80000gn/T/tmp.dMhSDydxYA/lakefile.lean:5:2: unknown 'PackageConfig' field 'name'
info: myproject: no previous manifest, creating one from scratch
info: toolchain not updated; already up-to-date

Namespaces Across Files

i
-- MathUtils/Basic.lean
-- The module path is MyProject.MathUtils.Basic
-- The namespace should match the module path by convention
namespace MathUtils.Basic

def square (n : Int) : Int := n * n
def cube (n : Int) : Int := n * n * n

end MathUtils.Basic
i

Exercises

Fix: Missing Import

i
-- Fix: this file uses Utils.greet but forgets to import it
-- Add the missing import at the top

def main : IO Unit := do
  IO.println (Utils.greet "world")
hint
  • Utils.greet is called but Utils is never imported
  • Add import MyProject.Utils at the top of the file

Fix: Wrong Target Type

i
-- Fix: this target should be an executable, not a library
import Lake
open Lake DSL

package «app» where
  name := "app"

lean_lib «app» where
  root := `Main
hint
  • lean_lib creates a library; it cannot be run directly
  • Change lean_lib to lean_exe to create an executable target
  • Also change the field from srcDir to root := \Main`

Fix: Wrong Module Name Case

i
-- Fix: the module name is wrong — Lean module names match the filename
-- The file is 'Helpers.lean' but the import uses a lowercase name
import MyProject.helpers

def main : IO Unit := do
  IO.println (Helpers.greet "world")
hint
  • Lean module names are derived from filenames, which start with a capital letter
  • helpers should be Helpers to match Helpers.lean
  • Change import MyProject.helpers to import MyProject.Helpers

Fix: Missing main Function

i
-- Fix: executables need a 'main' function
-- This file is set as the root of a lean_exe but has no main

def greet : String := "Hello!"
hint
  • Executables need def main : IO Unit as their entry point
  • Add def main : IO Unit := IO.println greet to the file
  • Without main, Lake cannot produce a runnable binary

Fix: Wrong Namespace in Call

i
-- Fix: the function is defined in namespace 'Foo' but called as 'Bar.func'
namespace Foo

def func : String := "from Foo"

end Foo

def main : IO Unit := do
  IO.println Bar.func
hint
  • func is defined inside namespace Foo, so it is Foo.func
  • Change Bar.func to Foo.func to match the actual namespace

Fix: Wrong Source Directory

i
-- Fix: srcDir points to the wrong location
-- The .lean files are in 'src/' but lakefile says 'source/'
import Lake
open Lake DSL

package «myapp» where
  name := "myapp"

lean_exe «myapp» where
  root := `Main
  srcDir := "source"
hint
  • srcDir := "source" tells Lake to look for .lean files in source/
  • But the project files are in src/
  • Change srcDir := "source" to srcDir := "src"

Write: Complete a Lake File

i
-- Write: fill in the blanks to create a lakefile for an executable called 'greeter'
import Lake
open Lake DSL

package «greeter» where
  name := "greeter"

-- TODO: add a lean_exe target called 'greeter' with root `Main
hint
  • Replace the TODO comment with lean_exe «greeter» where
  • Add root := \Main` indented under that line
  • The «greeter» must match the name you want to use in lake exe greeter

Write: Add a main Function

i
-- Write: add a main function that prints "Hello, Lake!"
-- def main : IO Unit := ???
hint
  • Add def main : IO Unit := IO.println "Hello, Lake!" to the file
  • IO.println prints a string followed by a newline
  • No do block needed for a single IO action

Write: Implement a Module Function

i
-- Write: implement greet using string interpolation
namespace Greeter

def greet (name : String) : String :=
  ""  -- replace with s!"Hello, {name}!"

end Greeter

#eval Greeter.greet "world"
hint
  • Replace "" with s!"Hello, {name}!"
  • The #eval call at the bottom will print "Hello, world!" when correct

Write: Add an Import

i
-- Write: add the missing import and fix the main function
-- Assume MyProject.Greeter defines Greeter.greet

def main : IO Unit := do
  IO.println (Greeter.greet "World")
hint
  • Add import MyProject.Greeter at the top of the file
  • The import path matches the file path: MyProject/Greeter.lean
  • After importing, Greeter.greet becomes available

Write: Add a Library Target

i
-- Write: add a lean_lib target called 'MathLib'
import Lake
open Lake DSL

package «myproject» where
  name := "myproject"

lean_exe «myproject» where
  root := `Main

-- TODO: add a lean_lib target named 'MathLib'
hint
  • Replace the TODO comment with lean_lib MathLib { }
  • A library target does not need a root unless you want a specific entry module
  • Build it with lake build MathLib

Write: Fix the Namespace Name

i
-- Write: the namespace should match the module name 'StringUtils'
-- Currently it's 'StrUtil' — fix it
namespace StrUtil

def shout (s : String) : String := s.toUpper ++ "!!!"

end StrUtil

#eval StrUtil.shout "hello"
hint
  • The namespace StrUtil doesn't match the module name StringUtils
  • Change both namespace StrUtil and end StrUtil to StringUtils
  • Then callers can use StringUtils.shout which matches the file name