# 18. Projects and packaging

> Chapter 18: split a program into a workspace of packages, depend on them, pack and publish a library to NuGet, and ship a single native executable with NativeAOT.

Canonical: https://alice-lang.org/documentation/learn/packaging/

[Documentation index](https://alice-lang.org/documentation/llms.txt) · [Complete book](https://alice-lang.org/llms-full.txt)

The tea shop has grown. Its pricing rules are worth sharing with the shop's website and with a partner's C# till. This chapter splits the program into packages, publishes one of them, and ships the program as a single file.

## 18.1 Workspaces

A **workspace** is a directory of packages that are built together. Create one, then the packages inside it:

```sh
alice new tea --workspace
cd tea
alice new pricing --lib
alice new shop
```

`alice new` inside a workspace registers each package in the root manifest:

```toml
[workspace]
members = ["pricing", "shop"]
```

`pricing` is a library: its `alice.toml` says `kind = "lib"`, and `alice new --lib` adds `naming = "dotnet"` so that C# callers see .NET names ([chapter 14](https://alice-lang.org/documentation/learn/dotnet.md)). Put the pricing code in `pricing/src/pricing.al`:

```alice
module Pricing
  def self.with_tax(cents : Int64, percent : Int32) : Int64
    cents + cents * percent / 100
  end
end
```

The shop depends on it by name. In `shop/alice.toml`:

```toml
[dependencies]
pricing = { workspace = true }
```

And `shop/src/main.al` uses it like any module:

```alice skip="a workspace build: see the commands below"
puts Pricing.with_tax(1000_i64, 7)
```

```sh
alice build   # builds every member, dependencies first
alice run     # runs the workspace's single program
```

```text
1070
```

With several programs in one workspace, name the one to run: `alice run shop`. `alice.lock` lives at the workspace root and pins NuGet versions for all members.

Dependencies can also point at a directory outside the workspace, `geo = { path = "../geo" }`, or at a C# project, as chapter 14 showed.

## 18.2 Packing and publishing a library

To let other projects install `pricing` from NuGet, describe it in its manifest:

```toml
[package]
name = "pricing"
id = "TeaShop.Pricing"        # the NuGet id; defaults to name
version = "1.0.0"
kind = "lib"
naming = "dotnet"
description = "Prices, taxes and discounts for tea shops"
authors = ["The Hatter"]
license = "Apache-2.0"
repository = "https://github.com/example/tea"
```

Then pack it:

```sh
alice pack pricing
```

```text
      Packed /home/you/tea/pricing/bin/package/TeaShop.Pricing.1.0.0.nupkg (1.22s)
```

The `.nupkg` contains the library and the Alice runtime it needs, so a C# project consumes it with an ordinary `PackageReference`. To push it to nuget.org:

```sh
export NUGET_API_KEY=...      # from nuget.org/account/apikeys
alice publish pricing --dry-run
alice publish pricing
```

`--dry-run` packs and stops before pushing, so you can inspect the `.nupkg` first. `alice publish` sends the package over HTTPS with the key in a request header, and skips a version that already exists. Prefer the environment variable to `--api-key`: a command-line argument is visible to other users in the process list. `--source` pushes to another feed, such as a company server or a local directory.

A package with `kind = "tool"` becomes a .NET tool that users install with `dotnet tool install -g`.

## 18.3 Release builds and native executables

`alice build --release` produces an optimized .NET assembly. It needs the .NET runtime on the machine that runs it.

`alice build --native` uses **NativeAOT**: it compiles the whole program ahead of time to machine code, in one executable file with no .NET runtime needed:

```sh
alice build hello.al --native
./hello
```

```text
Hello from a native binary
```

On Linux x64 that hello-world executable is about 1.4 MB and starts in a few milliseconds. Native builds suit command-line tools, containers and anything where startup time matters. Two limits apply: the executable runs only on the operating system and CPU it was built for, and code that relies on runtime reflection may need source-generated alternatives, such as the `[JsonSerializable]` context from chapter 14.

For a project, set it once in `alice.toml`:

```toml
[build]
native = true
```

## Project: the shop as a workspace

The tea shop now has the layout of a real product:

```text
tea/
  alice.toml          # [workspace] members = ["pricing", "shop"]
  alice.lock
  pricing/            # kind = "lib", naming = "dotnet": published as TeaShop.Pricing
    alice.toml
    src/pricing.al
    RULES.al          # the rules from chapter 13
    PROOF.al
  shop/               # kind = "exe": the command-line shop
    alice.toml
    src/main.al
    test/
```

The rules and their proof live with the code they protect. CI runs `alice build`, `alice test`, `alice fmt --check` and `alice prove --check` at the root.

:::caution\[Common mistakes]

- **Forgetting `{ workspace = true }`.** A member is not visible to another until it is listed in `[dependencies]`.
- **Committing `bin/`, `obj/` or `.alice/`.** They are generated. Commit the manifests, `alice.lock`, sources, tests, rules and `PROOF.al`.
- **Passing the API key as an argument on a shared machine.** Use `NUGET_API_KEY`.
- **Copying a native executable to another OS.** Build once per target platform.
- **Packing a game.** `kind = "game"` packages are not packable; ship them as builds.
  :::

## Exercises

1. Add a third member `report` (an exe) to the workspace that prints `Pricing.with_tax(250_i64, 10)`. What do you need to change, and how do you run it?
2. Which files of the workspace above belong in version control?
3. Why is `NUGET_API_KEY` safer than `--api-key`?

<details>
<summary>Answers</summary>

1. Run `alice new report` inside the workspace, which adds it to `members`. Add `pricing = { workspace = true }` to `report/alice.toml`, write the `puts` in `report/src/main.al`, and run it with `alice run report`, since the workspace now has two programs. It prints `275`.
2. Every `alice.toml`, `alice.lock`, the `src/` and `test/` directories, `RULES.al` and `PROOF.al`. Not `bin/`, `obj/` or `.alice/`.
3. Command-line arguments of a running process can be read by other users of the machine, for example with `ps`. Environment variables are visible only to the process and its owner.

</details>

## Summary

- A workspace (`alice new NAME --workspace`) builds several packages together; members depend on each other with `{ workspace = true }`.
- `alice pack` builds a `.nupkg`; `alice publish` pushes it, with the key from `NUGET_API_KEY`.
- `kind = "lib"` with `naming = "dotnet"` gives C# and F# a .NET-style API; `kind = "tool"` makes a .NET tool.
- `alice build --native` produces a single native executable with NativeAOT.

[Chapter 19](https://alice-lang.org/documentation/learn/applications.md) builds applications on top of .NET's frameworks: web, desktop and games.

## Continue reading

- Previous: [17. Native code](https://alice-lang.org/documentation/learn/native.md)
- Next: [19. Web, desktop and games](https://alice-lang.org/documentation/learn/applications.md)
