Skip to content

Tinygo doesn't build bubbletea/huh CLI applications on macOS. #5365

@pjebs

Description

@pjebs

I tried to build a CLI using bubbletea/huh in macOS.

It spits out errors when using -x flag:

../../go/pkg/mod/golang.org/x/sys@v0.43.0/unix/zsyscall_darwin_arm64.go:275: linker could not find symbol _golang.org/x/sys/unix.syscall_syscall6
../../go/pkg/mod/golang.org/x/sys@v0.43.0/unix/zsyscall_darwin_arm64.go:734: linker could not find symbol _golang.org/x/sys/unix.syscall_syscall
../../go/pkg/mod/golang.org/x/sys@v0.43.0/unix/zsyscall_darwin_arm64.go:1080: linker could not find symbol _golang.org/x/sys/unix.syscall_syscall
../../go/pkg/mod/golang.org/x/sys@v0.43.0/unix/zsyscall_darwin_arm64.go:1915: linker could not find symbol _golang.org/x/sys/unix.syscall_syscall
../../go/pkg/mod/golang.org/

When I try to cross-compile for windows:

$ GOOS=windows tinygo build 
open /opt/homebrew/Cellar/tinygo/0.41.1/lib/mingw-w64/mingw-w64-headers/crt/_mingw.h.in: no such file or directory

When I try to cross-compile for linux:

$ GOOS=linux tinygo build 
Reserved registers on the clobber list may not be preserved across the asm statement, and clobbering them may lead to undefined behaviour.
$ tinygo version
tinygo version 0.41.1 darwin/arm64 (using go version go1.26.1 and LLVM version 20.1.1)
module x

go 1.26.1

require (
	charm.land/huh/v2 v2.0.3
	charm.land/lipgloss/v2 v2.0.3
	github.com/charmbracelet/x/exp/strings v0.1.0
)

require (
	charm.land/bubbles/v2 v2.0.0 // indirect
	charm.land/bubbletea/v2 v2.0.2 // indirect
	github.com/atotto/clipboard v0.1.4 // indirect
	github.com/catppuccin/go v0.2.0 // indirect
	github.com/charmbracelet/colorprofile v0.4.3 // indirect
	github.com/charmbracelet/ultraviolet v0.0.0-20260205113103-524a6607adb8 // indirect
	github.com/charmbracelet/x/ansi v0.11.7 // indirect
	github.com/charmbracelet/x/exp/ordered v0.1.0 // indirect
	github.com/charmbracelet/x/term v0.2.2 // indirect
	github.com/charmbracelet/x/termios v0.1.1 // indirect
	github.com/charmbracelet/x/windows v0.2.2 // indirect
	github.com/clipperhouse/displaywidth v0.11.0 // indirect
	github.com/clipperhouse/uax29/v2 v2.7.0 // indirect
	github.com/dustin/go-humanize v1.0.1 // indirect
	github.com/lucasb-eyer/go-colorful v1.4.0 // indirect
	github.com/mattn/go-runewidth v0.0.23 // indirect
	github.com/mitchellh/hashstructure/v2 v2.0.2 // indirect
	github.com/muesli/cancelreader v0.2.2 // indirect
	github.com/rivo/uniseg v0.4.7 // indirect
	github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect
	golang.org/x/sync v0.19.0 // indirect
	golang.org/x/sys v0.43.0 // indirect
)

package main

import (
	"errors"
	"fmt"
	"os"
	"strconv"
	"strings"
	"time"
	
	"charm.land/huh/v2"
	"charm.land/huh/v2/spinner"
	"charm.land/lipgloss/v2"
	xstrings "github.com/charmbracelet/x/exp/strings"
)

type Spice int

const (
	Mild Spice = iota + 1
	Medium
	Hot
)

func (s Spice) String() string {
	switch s {
		case Mild:
		return "Mild "
		case Medium:
		return "Medium-Spicy "
		case Hot:
		return "Spicy-Hot "
		default:
		return ""
	}
}

type Order struct {
	Burger       Burger
	Side         string
	Name         string
	Instructions string
	Discount     bool
}

type Burger struct {
	Type     string
	Toppings []string
	Spice    Spice
}

func main() {
	var burger Burger
	order := Order{Burger: burger}
	
	// Should we run in accessible mode?
	accessible, _ := strconv.ParseBool(os.Getenv("ACCESSIBLE"))
	
	form := huh.NewForm(
		huh.NewGroup(huh.NewNote().
		Title("Charmburger").
		Description("Welcome to _Charmburger™_.\n\nHow may we take your order?").
		Next(true).
		NextLabel("Next"),
	),
	
	// Choose a burger.
	// We'll need to know what topping to add too.
	huh.NewGroup(
		huh.NewSelect[string]().
		Options(huh.NewOptions("Charmburger Classic", "Chickwich", "Fishburger", "Charmpossible™ Burger")...).
		Title("Choose your burger").
		Description("At Charm we truly have a burger for everyone.").
		Validate(func(t string) error {
			if t == "Fishburger" {
				return fmt.Errorf("no fish today, sorry")
			}
			return nil
		}).
		Value(&order.Burger.Type),
		
		huh.NewMultiSelect[string]().
		Title("Toppings").
		Description("Choose up to 4.").
		Options(
			huh.NewOption("Lettuce", "Lettuce").Selected(true),
			huh.NewOption("Tomatoes", "Tomatoes").Selected(true),
			huh.NewOption("Charm Sauce", "Charm Sauce"),
			huh.NewOption("Jalapeños", "Jalapeños"),
			huh.NewOption("Cheese", "Cheese"),
			huh.NewOption("Vegan Cheese", "Vegan Cheese"),
			huh.NewOption("Nutella", "Nutella"),
		).
		Validate(func(t []string) error {
			if len(t) <= 0 {
				return fmt.Errorf("at least one topping is required")
			}
			return nil
		}).
		Value(&order.Burger.Toppings).
		Filterable(true).
		Limit(4),
	),
	
	// Prompt for toppings and special instructions.
	// The customer can ask for up to 4 toppings.
	huh.NewGroup(
		huh.NewSelect[Spice]().
		Title("Spice level").
		Options(
			huh.NewOption("Mild", Mild).Selected(true),
			huh.NewOption("Medium", Medium),
			huh.NewOption("Hot", Hot),
		).
		Value(&order.Burger.Spice),
		
		huh.NewSelect[string]().
		Options(huh.NewOptions("Fries", "Disco Fries", "R&B Fries", "Carrots")...).
		Value(&order.Side).
		Title("Sides").
		Description("You get one free side with this order."),
	),
	
	// Gather final details for the order.
	huh.NewGroup(
		huh.NewInput().
		Value(&order.Name).
		Title("What's your name?").
		Placeholder("Margaret Thatcher").
		Validate(func(s string) error {
			if s == "Frank" {
				return errors.New("no franks, sorry")
			}
			return nil
		}).
		Description("For when your order is ready."),
		
		huh.NewText().
		Value(&order.Instructions).
		Placeholder("Just put it in the mailbox please").
		Title("Special Instructions").
		Description("Anything we should know?").
		CharLimit(400).
		Lines(5),
		
		huh.NewConfirm().
		Title("Would you like 15% off?").
		Value(&order.Discount).
		Affirmative("Yes!").
		Negative("No."),
	),
).WithAccessible(accessible)

err := form.Run()
if err != nil {
	fmt.Println("Uh oh:", err)
	os.Exit(1)
}

prepareBurger := func() {
	time.Sleep(2 * time.Second)
}

_ = spinner.New().Title("Preparing your burger...").WithAccessible(accessible).Action(prepareBurger).Run()

// Print order summary.
{
	var sb strings.Builder
	keyword := func(s string) string {
		return lipgloss.NewStyle().Foreground(lipgloss.Color("212")).Render(s)
	}
	fmt.Fprintf(&sb,
	"%s\n\nOne %s%s, topped with %s with %s on the side.",
	lipgloss.NewStyle().Bold(true).Render("BURGER RECEIPT"),
	keyword(order.Burger.Spice.String()),
	keyword(order.Burger.Type),
	keyword(xstrings.EnglishJoin(order.Burger.Toppings, true)),
	keyword(order.Side),
)

name := order.Name
if name != "" {
	name = ", " + name
}
fmt.Fprintf(&sb, "\n\nThanks for your order%s!", name)

if order.Discount {
	fmt.Fprint(&sb, "\n\nEnjoy 15% off.")
}

fmt.Println(
	lipgloss.NewStyle().
	Width(40).
	BorderStyle(lipgloss.RoundedBorder()).
	BorderForeground(lipgloss.Color("63")).
	Padding(1, 2).
	Render(sb.String()),
)
	}
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions