diff --git a/.github/workflows/build-cli.yml b/.github/workflows/build-cli.yml index 001afe190d87..dda1e9e77726 100644 --- a/.github/workflows/build-cli.yml +++ b/.github/workflows/build-cli.yml @@ -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: diff --git a/.github/workflows/pr-comment-build-cli.yml b/.github/workflows/pr-comment-build-cli.yml index 44190170485d..c60c1669770d 100644 --- a/.github/workflows/pr-comment-build-cli.yml +++ b/.github/workflows/pr-comment-build-cli.yml @@ -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. \ No newline at end of file diff --git a/documentation/docs/getting-started/installation.md b/documentation/docs/getting-started/installation.md index 015185d05764..85d6353ef5f4 100644 --- a/documentation/docs/getting-started/installation.md +++ b/documentation/docs/getting-started/installation.md @@ -142,7 +142,32 @@ import LinuxDesktopInstallButtons from '@site/src/components/LinuxDesktopInstall - 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. + +

Option 1: Native Windows CLI (Recommended)

+ 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. + ::: + +

Option 2: Windows Subsystem for Linux (WSL)

+ 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 diff --git a/download_cli.sh b/download_cli.sh index d72a86559a4c..aac68c674e68 100755 --- a/download_cli.sh +++ b/download_cli.sh @@ -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=$? -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:" +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 - cat tar_error.log rm tar_error.log - exit 1 +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 -rm tar_error.log -rm "$FILE" # clean up the downloaded tarball +set -e # Re-enable immediate exit on error + +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