Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
componentize-go
target
*.wasm

Expand Down
9 changes: 9 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
module github.com/bytecodealliance/componentize-go

go 1.25

require github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13

require github.com/gofrs/flock v0.13.0

require golang.org/x/sys v0.37.0 // indirect
17 changes: 17 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13 h1:JtuelWqyixKApmXm3qghhZ7O96P6NKpyrlSIe8Rwnhw=
github.com/apparentlymart/go-userdirs v0.0.0-20200915174352-b0c018a67c13/go.mod h1:7kfpUbyCdGJ9fDRCp3fopPQi5+cKNHgTE4ZuNrO71Cw=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw=
github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0=
github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY=
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
golang.org/x/sys v0.0.0-20190509141414-a5b02f93d862/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
152 changes: 152 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
package main

import (
"archive/tar"
"compress/gzip"
"errors"
"fmt"
"io"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"

"github.com/apparentlymart/go-userdirs/userdirs"
"github.com/gofrs/flock"
)

// This is a simple wrapper program which downloads, caches, and runs the
// appropriate `componentize-go` binary for the current platform.
//
// Although `componentize-go` is written in Rust, we can use this wrapper to
// make it available using e.g. `go install` and/or `go tool`.
func main() {
// This is hard-coded to point to the latest canary release, which is
// appropriate for the `main` branch, but should be changed to the tag
// name for each tagged release.
//
// TODO: Can we automate updating this for each release?
release := "canary"

directories := userdirs.ForApp(
"componentize-go",
"bytecodealliance",
"com.github.bytecodealliance-componentize-go",
)
binDirectory := filepath.Join(directories.CacheDir, "bin")
versionPath := filepath.Join(directories.CacheDir, "version.txt")
lockFilePath := filepath.Join(directories.CacheDir, "lock")
binaryPath := filepath.Join(binDirectory, "componentize-go")

maybeDownload(release, binDirectory, versionPath, lockFilePath, binaryPath)

run(binaryPath)
}

// Download the specified version of `componentize-go` if we haven't already.
func maybeDownload(release, binDirectory, versionPath, lockFilePath, binaryPath string) {
if err := os.MkdirAll(binDirectory, 0755); err != nil {
log.Fatalf("unable to create directory `%v`: %v", binDirectory, err)
}

// Lock the lock file to prevent concurrent downloads.
lockFile := flock.New(lockFilePath)
if err := lockFile.Lock(); err != nil {
log.Fatalf("unable to lock file `%v`: %v", lockFilePath, err)
}
defer lockFile.Unlock()

versionBytes, err := os.ReadFile(versionPath)
var version string
if err != nil {
version = "<unknown>"
} else {
version = strings.TrimSpace(string(versionBytes))
}

// If the binary doesn't already exist and/or the version doesn't match
// the desired release, download it.
if _, err := os.Stat(binaryPath); errors.Is(err, os.ErrNotExist) || version != release {
base := fmt.Sprintf(
"https://github.com/bytecodealliance/componentize-go/releases/download/%v",
release,
)

url := fmt.Sprintf("%v/componentize-go-%v-%v.tar.gz", base, runtime.GOOS, runtime.GOARCH)

fmt.Printf("Downloading `componentize-go` binary from %v and extracting to %v\n", url, binDirectory)

response, err := http.Get(url)
if err != nil {
log.Fatalf("unable to download URL `%v`: %v", url, err)
}
defer response.Body.Close()

if response.StatusCode < 200 || response.StatusCode > 299 {
log.Fatalf("unexpected status for URL `%v`: %v", url, response.StatusCode)
}

uncompressed, err := gzip.NewReader(response.Body)
if err != nil {
log.Fatalf("unable to decompress content of URL `%v`: %v", url, err)
}
defer uncompressed.Close()

untarred := tar.NewReader(uncompressed)
for {
header, err := untarred.Next()
if err == io.EOF {
break
} else if err != nil {
log.Fatalf("unable to untar content of URL `%v`: %v", url, err)
}
path := filepath.Join(binDirectory, header.Name)
file, err := os.Create(path)
if err != nil {
log.Fatalf("unable to create file `%v`: %v", path, err)
}
if _, err := io.Copy(file, untarred); err != nil {
log.Fatalf("unable to untar content of URL `%v`: %v", url, err)
}
file.Close()
}

if err := os.Chmod(binaryPath, 0755); err != nil {
log.Fatalf("unable to make file `%v` executable: %v", binaryPath, err)
}
}

// If we just downloaded a new version, remember which one so we don't
// download it redundantly next time.
if version != release {
if err := os.WriteFile(versionPath, []byte(release), 0600); err != nil {
log.Fatalf("unable to write version to `%v`: %v", versionPath, err)
}
}
}

// Run the specified binary, forwarding all our arguments to it and piping its
// stdout and stderr back to the user.
func run(binaryPath string) {
command := exec.Command(binaryPath, os.Args[1:]...)
command.Stdout = os.Stdout
command.Stderr = os.Stderr

if err := command.Start(); err != nil {
log.Fatalf("unable to start `%v` command: %v", binaryPath, err)
}

if err := command.Wait(); err != nil {
if exiterr, ok := err.(*exec.ExitError); ok {
code := exiterr.ExitCode()
if code != 0 {
os.Exit(code)
}
} else {
log.Fatalf("trouble running `%v` command: %v", binaryPath, err)
}
}
}
2 changes: 2 additions & 0 deletions src/command.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ pub struct WitOpts {
#[arg(long, short = 'w')]
pub world: Vec<String>,

/// If `true`, skip scanning the current Go module's dependencies for
/// `componentize-go.toml` files.
#[arg(long)]
pub ignore_toml_files: bool,

Expand Down