Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
# Contributing

## Developer Setup

See the [maintainer scripts guide](docs/internals/scripts.md#first-checkout) for the initial checkout,
development commands, tests, and platform-specific desktop packaging prerequisites.

## Read This First

We are not actively accepting contributions right now.
Expand Down
73 changes: 73 additions & 0 deletions docs/internals/scripts.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,79 @@ authenticated.
- `vp run dist:desktop:win`: Builds a Windows NSIS installer into `./release`. `:arm64` and `:x64`
variants exist.

### Linux AppImage prerequisites

Linux AppImage packaging compiles the Rust resource monitor. Install a Rust toolchain, the standard
C/C++ build tools, and ImageMagick before running `vp run dist:desktop:linux`.

Ubuntu and Debian:

```bash
sudo apt-get update
sudo apt-get install cargo rustc build-essential imagemagick
```

Fedora:

```bash
sudo dnf install rust cargo gcc gcc-c++ make ImageMagick
```

Arch Linux:

```bash
sudo pacman -S rust base-devel imagemagick
```

The artifact script checks these capabilities before starting the web and desktop builds. If
anything is unavailable, it reports every failed check together and prints the Ubuntu/Debian
installation command. The check compiles and links tiny temporary programs, so it also catches
installed runtime X11 libraries that are missing their development headers or linker symlinks.

### macOS DMG prerequisites

Install the Xcode Command Line Tools and Rust before building a DMG:

```bash
xcode-select --install
```

Install Rust from [rustup.rs](https://rustup.rs). The artifact script checks Cargo, Clang, Make,
`sips`, and `iconutil`. Universal builds additionally require `lipo`. It also verifies that Rust
has every requested target; add missing targets with:

```bash
rustup target add aarch64-apple-darwin x86_64-apple-darwin
```

Unsigned local builds need no Apple credentials. Builds using `--signed` additionally require the
certificate, provisioning profile, team ID, and notarization configuration described below.

### Windows installer prerequisites

Install Rust from [rustup.rs](https://rustup.rs), Python 3, and Visual Studio Build Tools. In the
Visual Studio Installer, select **Desktop development with C++** and include:

- MSVC x64/x86 build tools
- A Windows 10 or Windows 11 SDK
- MSVC Spectre-mitigated libraries

ARM64 installers require the corresponding MSVC ARM64 build tools and Spectre-mitigated libraries
instead of the x64/x86 components.

Add the Rust target matching the installer architecture:

```powershell
rustup target add x86_64-pc-windows-msvc
# For an arm64 installer:
rustup target add aarch64-pc-windows-msvc
```

Windows supplies `tar.exe`; it is checked when `--wsl-prebuild` makes the artifact include the WSL
runtime. NSIS is downloaded by electron-builder and does not need a separate installation.
Unsigned local builds need no Azure credentials. Builds using `--signed` additionally require the
Azure Trusted Signing configuration described below.

### Desktop `.dmg` packaging notes

- Default build is unsigned/not notarized for local sharing.
Expand Down
117 changes: 117 additions & 0 deletions scripts/build-desktop-artifact.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,14 @@ import {
UnsupportedDesktopBuildArchitectureError,
isMacPasskeySigningConfigurationError,
LinuxIconResizeError,
LinuxDesktopBuildPrerequisitesMissingError,
MacDesktopBuildPrerequisitesMissingError,
MacPasskeySigningConfigurationResolutionError,
MissingMacPasskeyProvisioningProfileError,
packWindowsServerAsar,
preflightLinuxDesktopBuild,
preflightMacDesktopBuild,
preflightWindowsDesktopBuild,
renderMacPasskeyEntitlements,
resolveClerkPasskeyNativeArtifacts,
resolveMacPasskeySigningConfiguration,
Expand Down Expand Up @@ -63,6 +68,7 @@ import {
copyDirectoryPreservingSymlinks,
validateWindowsPackagedPayload,
WindowsPrimaryNativeProbeError,
WindowsDesktopBuildPrerequisitesMissingError,
WindowsPackagedPayloadValidationError,
WINDOWS_PACKAGED_PAYLOAD_FILE_LIMIT,
WINDOWS_SERVER_ASAR_IGNORE_GLOBS,
Expand Down Expand Up @@ -812,6 +818,117 @@ it.layer(NodeServices.layer)("build-desktop-artifact", (it) => {
),
);

it.effect("reports every missing Linux desktop build prerequisite with an install command", () =>
Effect.scoped(
Effect.gen(function* () {
const commands: Array<{ readonly command: string; readonly args: ReadonlyArray<string> }> =
[];
const spawner = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make((command) => {
const childProcess = command as unknown as {
readonly command: string;
readonly args: ReadonlyArray<string>;
};
commands.push(childProcess);
const fails = childProcess.command === "cargo" || childProcess.command === "rustc";
return Effect.succeed(mockProcess(fails ? 1 : 0));
}),
);

const error = yield* preflightLinuxDesktopBuild("arm64").pipe(
Effect.provide(spawner),
Effect.flip,
);

assert.instanceOf(error, LinuxDesktopBuildPrerequisitesMissingError);
assert.deepStrictEqual(error.missing, ["cargo", "rust-target"]);
assert.include(error.message, "Rust compiler and Cargo (cargo, rustc)");
assert.include(error.message, "Requested Rust standard library");
assert.include(error.message, "sudo apt-get install cargo rustc");
assert.include(error.message, "rustup target add aarch64-unknown-linux-gnu");
assert.isTrue(
commands.some(
(command) =>
command.command === "rustc" && command.args.includes("aarch64-unknown-linux-gnu"),
),
);
}),
),
);

it.effect("reports missing macOS tools and Rust targets before building", () =>
Effect.scoped(
Effect.gen(function* () {
const spawner = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make((command) => {
const childProcess = command as unknown as {
readonly command: string;
readonly args: ReadonlyArray<string>;
};
const fails =
childProcess.command === "rustc" ||
(childProcess.command === "xcrun" && childProcess.args.includes("iconutil"));
return Effect.succeed(mockProcess(fails ? 1 : 0));
}),
);
const error = yield* preflightMacDesktopBuild("universal").pipe(
Effect.provide(spawner),
Effect.flip,
);

assert.instanceOf(error, MacDesktopBuildPrerequisitesMissingError);
assert.deepStrictEqual(error.missing, ["rust", "iconutil"]);
assert.deepStrictEqual(error.rustTargets, ["aarch64-apple-darwin", "x86_64-apple-darwin"]);
assert.include(error.message, "xcode-select --install");
assert.include(error.message, "rustup target add aarch64-apple-darwin x86_64-apple-darwin");
}),
),
);

it.effect("reports missing Windows toolchain capabilities before building", () =>
Effect.scoped(
Effect.gen(function* () {
const fs = yield* FileSystem.FileSystem;
const path = yield* Path.Path;
const tempDir = yield* fs.makeTempDirectoryScoped({ prefix: "t3-windows-preflight-" });
const pythonPath = path.join(tempDir, "python.exe");
yield* fs.writeFileString(pythonPath, "python");
const spawner = Layer.succeed(
ChildProcessSpawner.ChildProcessSpawner,
ChildProcessSpawner.make((command) => {
const childProcess = command as unknown as { readonly command: string };
const fails =
childProcess.command === "rustc" ||
childProcess.command === "powershell.exe" ||
childProcess.command === pythonPath;
return Effect.succeed(mockProcess(fails ? 1 : 0));
}),
);
const error = yield* preflightWindowsDesktopBuild({
arch: "x64",
bundlesWslRuntime: true,
}).pipe(
Effect.provide(
Layer.merge(
spawner,
ConfigProvider.layer(
ConfigProvider.fromEnv({ env: { npm_config_python: pythonPath } }),
),
),
),
Effect.flip,
);

assert.instanceOf(error, WindowsDesktopBuildPrerequisitesMissingError);
assert.deepStrictEqual(error.missing, ["rust", "python", "msvc"]);
assert.equal(error.rustTarget, "x86_64-pc-windows-msvc");
assert.include(error.message, "Visual Studio Build Tools components");
}),
),
);

it.effect("validates every ASAR-unpacked native in the packaged Windows payload", () =>
Effect.scoped(
Effect.gen(function* () {
Expand Down
Loading
Loading