18. Packages and projects

  • chapter 18 of 20
  • 4 min read

Chapter 13's project needed nothing but a directory. Once it depends on code that lives somewhere else, it gets a dawn.toml: an optional manifest for what the directory convention cannot say, which is the project's identity and its dependencies. The layout, the entry point and the module paths stay the directory's business, and a project without one works exactly as before.

schema = 1        # always the first key
name = "myapp"    # the project's identity, [a-z_][a-z0-9_]*

A dependency is a source package

A package is a project of its own, with its own dawn.toml and its own src/. Here is a small one, greet, sitting next to myapp:

greet/
├── dawn.toml          # schema = 1, name = "greet", version = "1.0.0"
└── src/
    ├── hello.dawn
    └── style.dawn
myapp/
├── dawn.toml
└── src/
    └── main.dawn

greet/src/style.dawn:

# visible to every module of the greet package, and to nothing outside it
pub(pkg) fn shout(s: String) -> String = s ++ "!"

greet/src/hello.dawn:

use style.{shout}

pub fn hello(name: String) -> String = shout("hello, ${name}")

Inside the package, modules import each other by their path under the package's own src/, as chapter 13's did. dawn add writes the dependency into myapp's manifest:

$ dawn add ../greet --dir myapp
Added greet as `greet` (path myapp/../greet)
schema = 1
name = "myapp"

[deps]
greet = "../greet"

The key under [deps] is how myapp spells the package: it is the first segment of a use line.

myapp/src/main.dawn:

use greet/hello.{hello}

pub fn main() -> Unit !io = println(hello("Dawn"))

dawn run myapp prints hello, Dawn!.

pub(pkg): shared inside a package, hidden outside it

Between module-private (nothing written) and pub there is a third level. A pub(pkg) declaration is visible to every module of its own package, the unit one dawn.toml describes, and to nothing outside it. shout is a helper the modules of greet share, and myapp cannot reach it:

$ dawn run myapp      # main.dawn now also says: use greet/style.{shout}
error: `shout` is package-private to package `greet`
  --> myapp/src/main.dawn:2:18
  |
2 | use greet/style.{shout}
  |                  ^^^^^
  = hint: only modules of package `greet` may name it

A pub declaration's signature may not mention a pub(pkg) type, by the same rule that keeps a module-private type out of a public signature, and dawn doc lists pub items only. A project with no dawn.toml is one package, and so is the bundled standard library as a whole.

Remote packages, versions and MVS

A path is for code that sits next to yours. A published package is an archive at a URL, pinned by the hash of what it unpacks to:

[deps.json]
url = "https://github.com/dawnop/dawn-lang/archive/refs/tags/v0.7.0.zip"
version = "1.0.0"
hash = "d1:<sha256>"          # content hash of the unpacked file tree
subdir = "packages/json"      # where the package sits inside the archive

Nobody computes that hash by hand. dawn add <url> fetches the archive, hashes it, reads the package's own manifest for its name and version, and writes the entry, leaving the comments and layout of the rest of the file as they were; --subdir says where the package sits in the archive and --as picks a different key. Adding the same package again updates its entry in place, which is how a version is bumped.

When two packages in one program depend on the same third one at different versions, the program gets one copy of it: the highest of the minimum versions asked for. This is minimal version selection (MVS), the algorithm Go uses, and a requirement is only ever a minimum: no upper bounds, no exclusions. For Dawn the single copy is not a convenience. Every trait-and-type pair has exactly one impl in a whole program (chapter 16), and two copies of a package would be two copies of each of its types, with two impls each. A package's identity is the name in its own manifest rather than the key you gave it, so a major version that renames the package (json2) can still be spelled use json/... by keeping the old key.

The third table, [java-deps], lists Maven coordinates for chapter 11's use java (sqlite = "org.xerial:sqlite-jdbc:3.36.0.3", an exact version and nothing else). The JVM toolchain resolves and fetches them; dawnc refuses use java and so has no use for them. The design and its reasons are in package-design.md and package-visibility-design.md, in Chinese.

See also: spec.en.md §10.1, §10.4