attempt to build CLI native for Windows (#3058)

This commit is contained in:
Max Novich
2025-06-24 15:33:06 -07:00
committed by GitHub
parent 0fbc69191c
commit 566796f767
4 changed files with 313 additions and 48 deletions

View File

@@ -1,7 +1,12 @@
# This is a **reuseable** workflow that bundles the Desktop App for macOS.
# This is a **reuseable** workflow that builds the CLI for multiple platforms.
# It doesn't get triggered on its own. It gets used in multiple workflows:
# - release.yml
# - canary.yml
#
# Platform Build Strategy:
# - Linux: Uses Ubuntu runner with cross-compilation
# - macOS: Uses macOS runner with cross-compilation
# - Windows: Uses Ubuntu runner with Docker cross-compilation (same as desktop build)
on:
workflow_call:
inputs:
@@ -9,17 +14,6 @@ on:
required: false
default: ""
type: string
# Let's allow overriding the OSes and architectures in JSON array form:
# e.g. '["ubuntu-latest","macos-latest"]'
# If no input is provided, these defaults apply.
operating-systems:
type: string
required: false
default: '["ubuntu-latest","macos-latest"]'
architectures:
type: string
required: false
default: '["x86_64","aarch64"]'
ref:
type: string
required: false
@@ -30,17 +24,40 @@ name: "Reusable workflow to build CLI"
jobs:
build-cli:
name: Build CLI
runs-on: ${{ matrix.os }}
runs-on: ${{ matrix.build-on }}
strategy:
fail-fast: false
matrix:
os: ${{ fromJson(inputs.operating-systems) }}
architecture: ${{ fromJson(inputs.architectures) }}
include:
# Linux builds
- os: ubuntu-latest
architecture: x86_64
target-suffix: unknown-linux-gnu
build-on: ubuntu-latest
use-cross: true
- os: ubuntu-latest
architecture: aarch64
target-suffix: unknown-linux-gnu
build-on: ubuntu-latest
use-cross: true
# macOS builds
- os: macos-latest
architecture: x86_64
target-suffix: apple-darwin
build-on: macos-latest
use-cross: true
- os: macos-latest
architecture: aarch64
target-suffix: apple-darwin
build-on: macos-latest
use-cross: true
# Windows builds (only x86_64 supported)
- os: windows
architecture: x86_64
target-suffix: pc-windows-gnu
build-on: ubuntu-latest
use-cross: false
use-docker: true
steps:
- name: Checkout code
@@ -56,6 +73,7 @@ jobs:
rm -f Cargo.toml.bak
- name: Install cross
if: matrix.use-cross
run: source ./bin/activate-hermit && cargo install cross --git https://github.com/cross-rs/cross
# Install Go for building temporal-service
@@ -64,7 +82,32 @@ jobs:
with:
go-version: '1.21'
- name: Build CLI
# Cache Cargo registry and git dependencies for Windows builds
- name: Cache Cargo registry (Windows)
if: matrix.use-docker
uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f
with:
path: |
~/.cargo/registry/index
~/.cargo/registry/cache
~/.cargo/git/db
key: ${{ runner.os }}-cargo-registry-${{ hashFiles('Cargo.lock') }}
restore-keys: |
${{ runner.os }}-cargo-registry-
# Cache compiled dependencies (target/release/deps) for Windows builds
- name: Cache Cargo build (Windows)
if: matrix.use-docker
uses: actions/cache@2f8e54208210a422b2efd51efaa6bd6d7ca8920f
with:
path: target
key: ${{ runner.os }}-cargo-build-${{ hashFiles('Cargo.lock') }}-${{ hashFiles('rust-toolchain.toml') }}
restore-keys: |
${{ runner.os }}-cargo-build-${{ hashFiles('Cargo.lock') }}-
${{ runner.os }}-cargo-build-
- name: Build CLI (Linux/macOS)
if: matrix.use-cross
env:
CROSS_NO_WARNINGS: 0
RUST_LOG: debug
@@ -83,7 +126,102 @@ jobs:
echo "Building with explicit PROTOC path..."
cross build --release --target ${TARGET} -p goose-cli -vv
- name: Build temporal-service for target platform using build.sh script
- name: Build CLI (Windows)
if: matrix.use-docker
run: |
echo "🚀 Building Windows CLI executable with enhanced GitHub Actions caching..."
# Create cache directories
mkdir -p ~/.cargo/registry ~/.cargo/git
# Use enhanced caching with GitHub Actions cache mounts
docker run --rm \
-v "$(pwd)":/usr/src/myapp \
-v "$HOME/.cargo/registry":/usr/local/cargo/registry \
-v "$HOME/.cargo/git":/usr/local/cargo/git \
-w /usr/src/myapp \
rust:latest \
bash -c "
set -e
echo '=== Setting up Rust environment with caching ==='
export CARGO_HOME=/usr/local/cargo
export PATH=/usr/local/cargo/bin:\$PATH
# Check if Windows target is already installed in cache
if rustup target list --installed | grep -q x86_64-pc-windows-gnu; then
echo '✅ Windows cross-compilation target already installed'
else
echo '📦 Installing Windows cross-compilation target...'
rustup target add x86_64-pc-windows-gnu
fi
echo '=== Setting up build dependencies ==='
apt-get update
apt-get install -y mingw-w64 protobuf-compiler cmake time
echo '=== Setting up cross-compilation environment ==='
export CC_x86_64_pc_windows_gnu=x86_64-w64-mingw32-gcc
export CXX_x86_64_pc_windows_gnu=x86_64-w64-mingw32-g++
export AR_x86_64_pc_windows_gnu=x86_64-w64-mingw32-ar
export CARGO_TARGET_X86_64_PC_WINDOWS_GNU_LINKER=x86_64-w64-mingw32-gcc
export PKG_CONFIG_ALLOW_CROSS=1
export PROTOC=/usr/bin/protoc
echo '=== Optimized Cargo configuration ==='
mkdir -p .cargo
echo '[build]' > .cargo/config.toml
echo 'jobs = 4' >> .cargo/config.toml
echo '' >> .cargo/config.toml
echo '[target.x86_64-pc-windows-gnu]' >> .cargo/config.toml
echo 'linker = \"x86_64-w64-mingw32-gcc\"' >> .cargo/config.toml
echo '' >> .cargo/config.toml
echo '[net]' >> .cargo/config.toml
echo 'git-fetch-with-cli = true' >> .cargo/config.toml
echo 'retry = 3' >> .cargo/config.toml
echo '' >> .cargo/config.toml
echo '[profile.release]' >> .cargo/config.toml
echo 'codegen-units = 1' >> .cargo/config.toml
echo 'lto = false' >> .cargo/config.toml
echo 'panic = \"abort\"' >> .cargo/config.toml
echo 'debug = false' >> .cargo/config.toml
echo 'opt-level = 2' >> .cargo/config.toml
echo '' >> .cargo/config.toml
echo '[registries.crates-io]' >> .cargo/config.toml
echo 'protocol = \"sparse\"' >> .cargo/config.toml
echo '=== Building with cached dependencies ==='
# Check if we have cached build artifacts
if [ -d target/x86_64-pc-windows-gnu/release/deps ] && [ \"\$(ls -A target/x86_64-pc-windows-gnu/release/deps)\" ]; then
echo '✅ Found cached build artifacts, performing incremental build...'
CARGO_INCREMENTAL=1
else
echo '🔨 No cached artifacts found, performing full build...'
CARGO_INCREMENTAL=0
fi
echo '🔨 Building Windows CLI executable...'
CARGO_INCREMENTAL=\$CARGO_INCREMENTAL \
CARGO_NET_RETRY=3 \
CARGO_HTTP_TIMEOUT=60 \
RUST_BACKTRACE=1 \
cargo build --release --target x86_64-pc-windows-gnu -p goose-cli --jobs 4
echo '✅ Build completed successfully!'
ls -la target/x86_64-pc-windows-gnu/release/
"
# Verify build succeeded
if [ ! -f "./target/x86_64-pc-windows-gnu/release/goose.exe" ]; then
echo "❌ Windows CLI binary not found."
ls -la ./target/x86_64-pc-windows-gnu/release/ || echo "Release directory doesn't exist"
exit 1
fi
echo "✅ Windows CLI binary found!"
ls -la ./target/x86_64-pc-windows-gnu/release/goose.exe
- name: Build temporal-service for target platform using build.sh script (Linux/macOS)
if: matrix.use-cross
run: |
source ./bin/activate-hermit
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
@@ -124,7 +262,28 @@ jobs:
mv "${BINARY_NAME}" "../target/${TARGET}/release/${BINARY_NAME}"
echo "temporal-service built successfully for ${TARGET}"
- name: Package CLI with temporal-service
- name: Build temporal-service for Windows
if: matrix.use-docker
run: |
echo "Building temporal-service for Windows using build.sh script..."
docker run --rm \
-v "$(pwd)":/usr/src/myapp \
-w /usr/src/myapp/temporal-service \
golang:latest \
sh -c "
# Make build.sh executable
chmod +x build.sh
# Set Windows build environment and run build script
GOOS=windows GOARCH=amd64 ./build.sh
"
# Move the built binary to the expected location
mkdir -p target/x86_64-pc-windows-gnu/release
mv temporal-service/temporal-service.exe target/x86_64-pc-windows-gnu/release/temporal-service.exe
echo "temporal-service.exe built successfully for Windows"
- name: Package CLI with temporal-service (Linux/macOS)
if: matrix.use-cross
run: |
source ./bin/activate-hermit
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
@@ -141,6 +300,23 @@ jobs:
tar -cjf "goose-${TARGET}.tar.bz2" -C goose-package .
echo "ARTIFACT=target/${TARGET}/release/goose-${TARGET}.tar.bz2" >> $GITHUB_ENV
- name: Package CLI with temporal-service (Windows)
if: matrix.use-docker
run: |
export TARGET="${{ matrix.architecture }}-${{ matrix.target-suffix }}"
# Create a directory for the package contents
mkdir -p "target/${TARGET}/release/goose-package"
# Copy binaries
cp "target/${TARGET}/release/goose.exe" "target/${TARGET}/release/goose-package/"
cp "target/${TARGET}/release/temporal-service.exe" "target/${TARGET}/release/goose-package/"
# Create the zip archive with both binaries (Windows uses zip instead of tar.bz2)
cd "target/${TARGET}/release"
zip -r "goose-${TARGET}.zip" goose-package/
echo "ARTIFACT=target/${TARGET}/release/goose-${TARGET}.zip" >> $GITHUB_ENV
- name: Upload CLI artifact
uses: actions/upload-artifact@4cec3d8aa04e39d1a68397de0c4cd6fb9dce8ec1 # pin@v4
with:

View File

@@ -89,6 +89,7 @@ jobs:
- [📦 Linux (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-unknown-linux-gnu.zip)
- [📦 macOS (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-apple-darwin.zip)
- [📦 macOS (aarch64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-aarch64-apple-darwin.zip)
- [📦 Windows (x86_64)](https://nightly.link/${{ github.repository }}/actions/runs/${{ github.run_id }}/goose-x86_64-pc-windows-gnu.zip)
These links are provided by nightly.link and will work even if you're not logged into GitHub.

View File

@@ -142,7 +142,32 @@ import LinuxDesktopInstallButtons from '@site/src/components/LinuxDesktopInstall
</div>
</TabItem>
<TabItem value="cli" label="Goose CLI">
There isn't native installation support for Windows CLI, however you can run Goose using WSL (Windows Subsystem for Linux).
Install the Goose CLI directly from the browser using our download script, or use WSL for a Linux-like experience.
<h3 style={{ marginTop: '1rem' }}>Option 1: Native Windows CLI (Recommended)</h3>
Run the following command in **Git Bash**, **MSYS2**, or **PowerShell** to install the latest version of Goose natively on Windows:
```bash
curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | bash
```
This script will fetch the latest version of Goose and set it up on your system.
If you'd like to install without interactive configuration, disable `CONFIGURE`:
```bash
curl -fsSL https://github.com/block/goose/releases/download/stable/download_cli.sh | CONFIGURE=false bash
```
:::note Prerequisites
- **Git Bash** (recommended): Comes with [Git for Windows](https://git-scm.com/download/win)
- **MSYS2**: Available from [msys2.org](https://www.msys2.org/)
- **PowerShell**: Available on Windows 10/11 by default
The script requires `curl` and `unzip` to be available in your environment.
:::
<h3>Option 2: Windows Subsystem for Linux (WSL)</h3>
If you prefer a Linux-like environment, you can run Goose using WSL:
1. Open [PowerShell](https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-windows) as Administrator and install WSL and the default Ubuntu distribution:
@@ -193,7 +218,7 @@ Goose works with a set of [supported LLM providers][providers], and you'll need
Upon installing, Goose will automatically enter its configuration screen. Here is where you can set up your LLM provider.
:::tip Windows Users
Choose to not store to keyring when prompted.
When using the native Windows CLI, choose to not store to keyring when prompted during initial configuration.
:::
Example:
@@ -221,6 +246,12 @@ Goose works with a set of [supported LLM providers][providers], and you'll need
:::info Windows Users
On initial run, you may encounter errors about keyrings when setting your API Keys. Set the needed environment variables manually, e.g.:
**For Native Windows CLI (Git Bash/MSYS2):**
```bash
export OPENAI_API_KEY={your_api_key}
```
**For WSL:**
```bash
export OPENAI_API_KEY={your_api_key}
```
@@ -231,8 +262,17 @@ Goose works with a set of [supported LLM providers][providers], and you'll need
● OPENAI_API_KEY is set via environment variable
```
To make the changes persist in WSL across sessions, add the goose path and export commands to your `.bashrc` or `.bash_profile` file so you can load it later.
**To make the changes persist across sessions:**
**For Native Windows CLI (Git Bash):**
Add the goose path and export commands to your `~/.bashrc` or `~/.bash_profile` file:
```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
echo 'export OPENAI_API_KEY=your_api_key' >> ~/.bashrc
source ~/.bashrc
```
**For WSL:**
```bash
echo 'export PATH="$HOME/.local/bin:$PATH"' >> ~/.bashrc
echo 'export OPENAI_API_KEY=your_api_key' >> ~/.bashrc

View File

@@ -7,7 +7,7 @@ set -eu
# This script downloads the latest stable 'goose' CLI binary from GitHub releases
# and installs it to your system.
#
# Supported OS: macOS (darwin), Linux
# Supported OS: macOS (darwin), Linux, Windows (MSYS2/Git Bash/WSL)
# Supported Architectures: x86_64, arm64
#
# Usage:
@@ -29,9 +29,9 @@ if ! command -v curl >/dev/null 2>&1; then
exit 1
fi
# Check for tar
if ! command -v tar >/dev/null 2>&1; then
echo "Error: 'tar' is required to download Goose. Please install tar and try again."
# Check for tar or unzip (depending on OS)
if ! command -v tar >/dev/null 2>&1 && ! command -v unzip >/dev/null 2>&1; then
echo "Error: Either 'tar' or 'unzip' is required to extract Goose. Please install one and try again."
exit 1
fi
@@ -48,10 +48,14 @@ CONFIGURE="${CONFIGURE:-true}"
OS=$(uname -s | tr '[:upper:]' '[:lower:]')
ARCH=$(uname -m)
# Handle Windows environments (MSYS2, Git Bash, Cygwin, WSL)
case "$OS" in
linux|darwin) ;;
mingw*|msys*|cygwin*)
OS="windows"
;;
*)
echo "Error: Unsupported OS '$OS'. Goose currently only supports Linux and macOS."
echo "Error: Unsupported OS '$OS'. Goose currently supports Linux, macOS, and Windows."
exit 1
;;
esac
@@ -73,8 +77,19 @@ esac
# Build the filename and URL for the stable release
if [ "$OS" = "darwin" ]; then
FILE="goose-$ARCH-apple-darwin.tar.bz2"
EXTRACT_CMD="tar"
elif [ "$OS" = "windows" ]; then
# Windows only supports x86_64 currently
if [ "$ARCH" != "x86_64" ]; then
echo "Error: Windows currently only supports x86_64 architecture."
exit 1
fi
FILE="goose-$ARCH-pc-windows-gnu.zip"
EXTRACT_CMD="unzip"
OUT_FILE="goose.exe"
else
FILE="goose-$ARCH-unknown-linux-gnu.tar.bz2"
EXTRACT_CMD="tar"
fi
DOWNLOAD_URL="https://github.com/$REPO/releases/download/$RELEASE_TAG/$FILE"
@@ -97,27 +112,48 @@ trap 'rm -rf "$TMP_DIR"' EXIT
echo "Extracting $FILE to temporary directory..."
set +e # Disable immediate exit on error
tar -xjf "$FILE" -C "$TMP_DIR" 2> tar_error.log
tar_exit_code=$?
if [ "$EXTRACT_CMD" = "tar" ]; then
tar -xjf "$FILE" -C "$TMP_DIR" 2> tar_error.log
extract_exit_code=$?
# Check for tar errors
if [ $extract_exit_code -ne 0 ]; then
if grep -iEq "missing.*bzip2|bzip2.*missing|bzip2.*No such file|No such file.*bzip2" tar_error.log; then
echo "Error: Failed to extract $FILE. 'bzip2' is required but not installed. See details below:"
else
echo "Error: Failed to extract $FILE. See details below:"
fi
cat tar_error.log
rm tar_error.log
exit 1
fi
rm tar_error.log
else
# Use unzip for Windows
unzip -q "$FILE" -d "$TMP_DIR" 2> unzip_error.log
extract_exit_code=$?
# Check for unzip errors
if [ $extract_exit_code -ne 0 ]; then
echo "Error: Failed to extract $FILE. See details below:"
cat unzip_error.log
rm unzip_error.log
exit 1
fi
rm unzip_error.log
fi
set -e # Re-enable immediate exit on error
# Check for tar errors
if [ $tar_exit_code -ne 0 ]; then
if grep -iEq "missing.*bzip2|bzip2.*missing|bzip2.*No such file|No such file.*bzip2" tar_error.log; then
echo "Error: Failed to extract $FILE. 'bzip2' is required but not installed. See details below:"
else
echo "Error: Failed to extract $FILE. See details below:"
fi
cat tar_error.log
rm tar_error.log
exit 1
fi
rm tar_error.log
rm "$FILE" # clean up the downloaded tarball
rm "$FILE" # clean up the downloaded archive
# Make binary executable
chmod +x "$TMP_DIR/goose"
if [ "$OS" = "windows" ]; then
chmod +x "$TMP_DIR/goose.exe"
else
chmod +x "$TMP_DIR/goose"
fi
# --- 5) Install to $GOOSE_BIN_DIR ---
if [ ! -d "$GOOSE_BIN_DIR" ]; then
@@ -126,13 +162,25 @@ if [ ! -d "$GOOSE_BIN_DIR" ]; then
fi
echo "Moving goose to $GOOSE_BIN_DIR/$OUT_FILE"
mv "$TMP_DIR/goose" "$GOOSE_BIN_DIR/$OUT_FILE"
if [ "$OS" = "windows" ]; then
mv "$TMP_DIR/goose.exe" "$GOOSE_BIN_DIR/$OUT_FILE"
else
mv "$TMP_DIR/goose" "$GOOSE_BIN_DIR/$OUT_FILE"
fi
# Also move temporal-service if it exists (for scheduling functionality)
if [ -f "$TMP_DIR/temporal-service" ]; then
echo "Moving temporal-service to $GOOSE_BIN_DIR/temporal-service"
mv "$TMP_DIR/temporal-service" "$GOOSE_BIN_DIR/temporal-service"
chmod +x "$GOOSE_BIN_DIR/temporal-service"
if [ "$OS" = "windows" ]; then
if [ -f "$TMP_DIR/temporal-service.exe" ]; then
echo "Moving temporal-service to $GOOSE_BIN_DIR/temporal-service.exe"
mv "$TMP_DIR/temporal-service.exe" "$GOOSE_BIN_DIR/temporal-service.exe"
chmod +x "$GOOSE_BIN_DIR/temporal-service.exe"
fi
else
if [ -f "$TMP_DIR/temporal-service" ]; then
echo "Moving temporal-service to $GOOSE_BIN_DIR/temporal-service"
mv "$TMP_DIR/temporal-service" "$GOOSE_BIN_DIR/temporal-service"
chmod +x "$GOOSE_BIN_DIR/temporal-service"
fi
fi
# skip configuration for non-interactive installs e.g. automation, docker