Building Python with Mill

This page contains a quick introduction to getting start with using Mill to build a simple python program. We will walk through a series of Mill builds of increasing complexity to show you the key features and usage of the Mill build tool.

The other pages of this section on python go into more depth into individual features, with more examples of how to use Mill for python and more details of how the Mill build tool works. They aren’t intended to be read comprehensively top-to-bottom, but rather looked up when you have a particular interest e.g. in testing, linting, publishing, and so on.

Simple Python Module

build.mill (download, browse)
package build
import mill._, pythonlib._

object foo extends PythonModule {

  def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }

  def pythonDeps = Seq("Jinja2==3.1.4")

  object test extends PythonTests with TestModule.Unittest

}

This is a basic Mill build for a single PythonModule, with one dependency and a test suite using the Unittest Library.

build.mill
foo/
    src/
        foo/foo.py
    resources/
        ...
    test/
        src/
            foo/test.py
out/foo/
    run.json
    run.dest/
    ...
    test/
        run.json
        run.dest/
        ...

This example project uses one dependency - Jinja2 for HTML rendering and uses it to wrap a given input string in HTML templates with proper escaping.

Typical usage of a PythonModule is shown below:

> ./mill resolve foo._ # List what tasks are available to run
foo.bundle
...
foo.console
...
foo.run
...
foo.test
...
foo.typeCheck

> ./mill inspect foo.typeCheck  # Show documentation and inputs of a task
...
foo.typeCheck(PythonModule...)
    Run a typechecker on this module.
Inputs:
    foo.pythonExe
    foo.transitivePythonPath
    foo.sources
...

> ./mill foo.typeCheck  # TypeCheck the Python Files and notify errors
Success: no issues found in 1 source file

> ./mill foo.run --text "Hello Mill"  # run the main method with arguments
<h1>Hello Mill</h1>

> ./mill foo.test
...
test_escaping (test.TestScript...) ... ok
test_simple (test.TestScript...) ... ok
...
----------------------------------------------------------------------
Ran 2 tests...
OK
...

> ./mill show foo.bundle # Creates Bundle for the python file
".../out/foo/bundle.dest/bundle.pex"

> out/foo/bundle.dest/bundle.pex --text "Hello Mill" # running the PEX binary outside of Mill
<h1>Hello Mill</h1>

> sed -i.bak 's/print(main())/print(maaain())/g' foo/src/foo.py

> ./mill foo.typeCheck # if we make a typo in a method name, mypy flags it
error: ...Name "maaain" is not defined...

Custom Build Logic

build.mill (download, browse)
package build
import mill._, pythonlib._

object foo extends PythonModule {

  def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }

  /** All Python source files in this module, recursively from the source directories.*/
  def allSourceFiles: T[Seq[PathRef]] = Task {
    sources().flatMap(src => os.walk(src.path).filter(_.ext == "py").map(PathRef(_)))
  }

  /** Total number of lines in module source files */
  def lineCount = Task {
    allSourceFiles().map(f => os.read.lines(f.path).size).sum
  }

  /** Generate resources using lineCount of sources */
  override def resources = Task {
    val resourcesDir = Task.dest / "resources"
    os.makeDir.all(resourcesDir)
    os.write(resourcesDir / "line-count.txt", "" + lineCount())
    super.resources() ++ Seq(PathRef(Task.dest))
  }

  object test extends PythonTests with TestModule.Unittest
}

Multi-Module Project

build.mill (download, browse)
package build
import mill._, pythonlib._

trait MyModule extends PythonModule {
  object test extends PythonTests with TestModule.Unittest
}

object foo extends MyModule {
  def moduleDeps = Seq(bar)
  def mainScript = Task.Source { millSourcePath / "src" / "foo.py" }
}

object bar extends MyModule {
  def mainScript = Task.Source { millSourcePath / "src" / "bar.py" }
  def pythonDeps = Seq("Jinja2==3.1.4")
}
build.mill
foo/
    src/
        foo.py
    test/
         src/
             test.py
bar/
    src/
        bar.py
    test/
         src/
             test.py
out/
    foo/
        run.json
        run.dest/
        ...
    bar/
        run.json
        run.dest/
        ...
        test/
             run.json
             run.dest/
             ...