workspace-cache 0.1.0

A CLI to generate cached Docker builds for Rust workspaces
# workspace-cache

Like [cargo-chef](https://github.com/LukeMathWalker/cargo-chef) but focused on **Rust workspaces with multiple binaries** (microservices). Generates optimized Dockerfiles with proper layer caching.

## Installation

```sh
cargo install workspace-cache
```

Or install from git for the latest development version:

```sh
cargo install --git https://github.com/preiter93/workspace-cache workspace-cache
```

## Quick Start

Generate a Dockerfile for your service:

```sh
workspace-cache dockerfile --bin user -o Dockerfile
```

This produces an optimized multi-stage Dockerfile:

```dockerfile
# Stage 1: Install workspace-cache tool
FROM rust:1.94-bookworm AS base
WORKDIR /app
RUN cargo install workspace-cache@0.1.0

# Stage 2: Generate minimal workspace with stub sources
FROM base AS planner
COPY . .
RUN workspace-cache deps --bin user

# Stage 3: Build dependencies only (cached until Cargo.toml/Cargo.lock change)
FROM base AS deps
COPY --from=planner /app/.workspace-cache .
RUN cargo build --release

# Stage 4: Build the actual binary with real source code
FROM deps AS builder
RUN rm -rf crates/user/src crates/common/src
COPY crates/user crates/user
COPY crates/common crates/common
RUN cargo clean --release -p user -p common
RUN cargo build --release --bin user

# Stage 5: Minimal runtime image
FROM debian:bookworm-slim AS runtime
COPY --from=builder /app/target/release/user /usr/local/bin/user
ENTRYPOINT ["/usr/local/bin/user"]
```

## Build & Run

```sh
docker build -f Dockerfile -t user .
docker run --rm user
```

## How It Works

1. **planner** - Creates a minimal workspace with stub sources for dependency resolution
2. **deps** - Builds dependencies (cached until Cargo.toml/Cargo.lock changes)
3. **builder** - Copies real source and builds binary
4. **runtime** - Minimal image with just the binary

When source files change but dependencies don't, Docker skips the `deps` stage entirely.

## Usage

The main command is `dockerfile`. It generates an optimized Dockerfile for your binary:

```sh
workspace-cache dockerfile --bin <binary> [OPTIONS]
```

Options:
- `--bin <binary>` - Binary to build (required)
- `--profile <profile>` - Build profile: `release` or `debug` (default: `release`)
- `-o, --output <path>` - Output path (default: stdout)
- `--base-image <image>` - Base image for build stages (default: `rust:1.94-bookworm`)
- `--runtime-image <image>` - Runtime image (default: `debian:bookworm-slim`)
- `--tool-version <version>` - Version of workspace-cache to install (default: current version)
- `--from-git` - Install workspace-cache from git instead of crates.io
- `--fast` - Fast mode: skip dependency resolution for faster generation

Examples:
```sh
# Generate release Dockerfile (default)
workspace-cache dockerfile --bin user -o Dockerfile

# Generate debug Dockerfile
workspace-cache dockerfile --bin user --profile debug -o Dockerfile.debug

# Use a specific version
workspace-cache dockerfile --bin user --tool-version 0.1.0 -o Dockerfile

# Install from git (latest dev version)
workspace-cache dockerfile --bin user --from-git -o Dockerfile

# Custom base image
workspace-cache dockerfile --bin user --base-image rust:1.80-alpine -o Dockerfile
```

### Fast Mode

Use `--fast` to skip dependency resolution. This results in a less optimized cache, but speeds up Docker builds as long as no dependencies have changed.

```sh
workspace-cache dockerfile --bin user --fast -o Dockerfile
```

## CI Usage (without Docker)

### GitHub Action (Recommended)

The simplest way to use workspace-cache in CI is with the provided composite actions:

```yaml
- name: Install workspace-cache
  uses: preiter93/workspace-cache/.github/actions/install-workspace-cache@main

- name: Build my binary
  uses: preiter93/workspace-cache/.github/actions/build-workspace@main
  with:
    binary: user
```

This handles all the caching and build steps automatically. The `install-workspace-cache` action caches the tool binary for faster subsequent runs. See the action READMEs for more options:
- [install-workspace-cache](.github/actions/install-workspace-cache/README.md)
- [build-workspace](.github/actions/build-workspace/README.md)

### Manual Setup

You can also set up workspace-cache manually for more control:

```yaml
- name: Install workspace-cache
  run: cargo install workspace-cache

- name: Generate minimal workspace
  run: workspace-cache deps --bin user

- name: Get cache key for dependencies
  id: cache-key
  run: |
    HASH="${{ hashFiles('.workspace-cache/Cargo.lock') }}"
    echo "key=${{ runner.os }}-workspace-cache-deps-${HASH}" >> $GITHUB_OUTPUT

- name: Cache dependencies
  uses: actions/cache@v4
  with:
    path: .workspace-cache/target
    key: ${{ steps.cache-key.outputs.key }}
    restore-keys: |
      ${{ runner.os }}-workspace-cache-deps-

- name: Build dependencies (cached when Cargo.lock unchanged)
  run: cargo build --release
  working-directory: .workspace-cache

- name: Copy real sources
  run: |
    workspace-cache members --bin user | while read path name; do
      rm -rf .workspace-cache/$path/src
      cp -r $path/src .workspace-cache/$path/src
    done

- name: Build binary
  run: |
    PACKAGES=$(workspace-cache members --bin user | awk '{print "-p " $2}' | tr '\n' ' ')
    cargo clean --release $PACKAGES
    cargo build --release --bin user
  working-directory: .workspace-cache
```

The cache key is based on the generated `.workspace-cache/Cargo.lock`, so dependencies are only rebuilt when they change. On cache hits, the dependency build step completes in seconds.

## Other Commands

The following commands are mainly for debugging or understanding how the tool works internally.

### Generate Minimal Workspace

```sh
workspace-cache deps [OPTIONS]
```

Options:
- `--bin <binary>` - Only include dependencies for specific binary/binaries
- `-o, --output <dir>` - Output directory (default: `.workspace-cache`)
- `--fast` - Fast mode: skip dependency resolution

Examples:
```sh
# Generate for all workspace binaries
workspace-cache deps

# Generate for a specific binary
workspace-cache deps --bin user

# Generate for multiple binaries
workspace-cache deps --bin user --bin order

# Custom output directory
workspace-cache deps --bin user -o my-cache
```

### Show Workspace Members

```sh
workspace-cache members --bin <binary>
```

Shows which workspace members a binary depends on, with their paths and names:

```
$ workspace-cache members --bin user
crates/pkg-a pkg_a
crates/pkg-b pkg_b
crates/user user
```

This output can be used in scripts to dynamically copy sources or generate package lists.

### Build Workspace

```sh
workspace-cache build [OPTIONS]
```

Options:
- `--bin <binary>` - Only build specific binary/binaries
- `--release` - Build in release mode

Examples:
```sh
# Build all binaries
workspace-cache build

# Build specific binary in release mode
workspace-cache build --bin user --release
```

## Testing

Run unit tests:

```sh
cargo test
```

Test locally in your workspace:

```sh
# Generate minimal workspace for a binary
workspace-cache deps --bin user

# Build dependencies
cd .workspace-cache
cargo build --release

# Copy real sources and build (deps are cached)
rm -rf crates/user/src crates/common/src
cp -r ../crates/user/src crates/user/src
cp -r ../crates/common/src crates/common/src
cargo build --release --bin user
```

Note: This mirrors how the generated Dockerfile works. The key is building
the final binary from within `.workspace-cache/` after copying real sources.

## License

MIT