Skip to content

Commit

Permalink
Merge branch 'SigSeg-V-master'
Browse files Browse the repository at this point in the history
  • Loading branch information
tristanisham committed Oct 31, 2023
2 parents 639dca3 + dd77480 commit 4e575f7
Show file tree
Hide file tree
Showing 7 changed files with 294 additions and 18 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@
/build
/dist
/demo
zvm
zvm
testdata
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,13 +100,16 @@ like to suggest
## Install

```sh
zvm install <version>
zvm install <version> [--zls]
# Or
zvm i <version>
zvm i <version> [-z]
```

Use `install` or `i` to download a specific version of Zig. To install the
latest version, use "master".
latest version, use "master". Optionally, the `--zls` or `-z` flag can be added to
install Zig Language Server (ZLS) as well. If you intend to install a fixed
release of ZLS, you may need to also install [zstd](https://github.com/facebook/zstd/releases),
which is available through the package manager for your system or on GitHub.

```sh
# Example
Expand Down
213 changes: 206 additions & 7 deletions cli/install.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@ package cli

import (
"archive/zip"
"bytes"
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io"
Expand Down Expand Up @@ -35,7 +37,7 @@ func (z *ZVM) Install(version string) error {
tarPath, err := getTarPath(version, &rawVersionStructure)
if err != nil {
if errors.Is(err, ErrUnsupportedVersion) {
log.Fatal(err)
log.Fatalf("%s: %q", err, version)
} else {
return err
}
Expand Down Expand Up @@ -193,15 +195,175 @@ func (z *ZVM) Install(version string) error {
}
}

z.createSymlink(version)

return nil
}

type githubTaggedReleaseResponse struct {
Assets []gitHubAsset // json array of platform binaries
}

type gitHubAsset struct {
Url string // download url for asset
Name string // contains platform information about binary
}

func (z *ZVM) InstallZls(version string) error {
if version != "master" && strings.Count(version, ".") != 2 {
return fmt.Errorf("%w: versions are SEMVER (MAJOR.MINOR.MINUSCULE)", ErrUnsupportedVersion)
}

fmt.Println("Finding ZLS executable...")
// make sure dir exists
installDir := filepath.Join(z.zvmBaseDir, version)
err := os.MkdirAll(installDir, 0755)
if err != nil {
return err
}

arch, osType := zigStyleSysInfo()

filename := "zls"
if osType == "windows" {
filename += ".exe"
}

// master does not need unzipping, zpm just serves full binary
if version == "master" {

installFile := filepath.Join(installDir, filename)
out, err := os.Create(installFile)
if err != nil {
return err
}
defer out.Close()
if err := os.Chmod(installFile, 0755); err != nil {
return err
}

zpmBaseUrl := "https://zig.pm/zls/downloads"
dlUrl := fmt.Sprintf("%v/%v-%v/bin/%v", zpmBaseUrl, arch, osType, filename)

resp, err := http.Get(dlUrl)
if err != nil {
return err
}
defer resp.Body.Close()

pbar := progressbar.DefaultBytes(
int64(resp.ContentLength),
"Downloading ZLS",
)

if _, err := io.Copy(io.MultiWriter(pbar, out), resp.Body); err != nil {
return err
}
} else {
// build url for tagged version
releaseUrl := fmt.Sprintf("https://api.github.com/repos/zigtools/zls/releases/tags/%v", version)

// get release information
resp, err := http.Get(releaseUrl)
if err != nil {
return err
}
defer resp.Body.Close()

var releaseBuffer bytes.Buffer
releaseBuffer.ReadFrom(resp.Body)
if err != nil {
return err
}

// some github releases use tar.gz, some tar.xz
expectedArchOs := fmt.Sprintf("%v-%v", arch, osType)
zipName := ""
var taggedReleaseResponse githubTaggedReleaseResponse
// getting list of assets
if err := json.Unmarshal(releaseBuffer.Bytes(), &taggedReleaseResponse); err != nil {
return err
}

// getting platform information
downloadUrl := ""
for _, asset := range taggedReleaseResponse.Assets {
if strings.Contains(asset.Name, expectedArchOs) {
downloadUrl = asset.Url
zipName = asset.Name
break
}
}

// couldn't find the file
if downloadUrl == "" {
return fmt.Errorf("could not find zls-%v", expectedArchOs)
}

client := &http.Client{}

// download tarball to temp
req, err := http.NewRequest("GET", downloadUrl, nil)
if err != nil {
return err
}
req.Header.Set("Accept", "application/octet-stream")
resp, err = client.Do(req)
if err != nil {
return err
}
defer resp.Body.Close()

// creating file to place contents
_, pathEnding, _ := strings.Cut(zipName, ".")

tempDir, err := os.CreateTemp(z.zvmBaseDir, "*."+pathEnding)
if err != nil {
return err
}

defer tempDir.Close()
defer os.RemoveAll(tempDir.Name())

pbar := progressbar.DefaultBytes(
int64(resp.ContentLength),
"Downloading ZLS",
)

if _, err := io.Copy(io.MultiWriter(pbar, tempDir), resp.Body); err != nil {
return err
}
// untar to destination
fmt.Println("Extracting ZLS...")
versionPath := filepath.Join(z.zvmBaseDir, version)
if err := ExtractBundle(tempDir.Name(), filepath.Join(z.zvmBaseDir, version)); err != nil {
log.Fatal(err)
}
if err := os.Rename(filepath.Join(versionPath, "bin", filename), filepath.Join(versionPath, filename)); err != nil {
return err
}
if err := os.Chmod(filepath.Join(versionPath, filename), 0755); err != nil {
return err
}
}

z.createSymlink(version)
fmt.Println("Done! 🎉")
return nil
}

func (z *ZVM) createSymlink(version string) {
if _, err := os.Lstat(filepath.Join(z.zvmBaseDir, "bin")); err == nil {
os.Remove(filepath.Join(z.zvmBaseDir, "bin"))
fmt.Println("Removing old symlink")
if err := os.RemoveAll(filepath.Join(z.zvmBaseDir, "bin")); err != nil {
log.Fatal("could not remove bin", err)
}

}

if err := os.Symlink(filepath.Join(z.zvmBaseDir, version), filepath.Join(z.zvmBaseDir, "bin")); err != nil {
log.Fatal(err)
}

return nil
}

func getTarPath(version string, data *map[string]map[string]any) (string, error) {
Expand Down Expand Up @@ -275,14 +437,22 @@ func zigStyleSysInfo() (arch string, os string) {
}

func ExtractBundle(bundle, out string) error {
if runtime.GOOS == "windows" {
// get extension
replacedBundle := strings.ReplaceAll(bundle, "\\", "/")
splitPath := strings.Split(replacedBundle, "/")
_, extension, _ := strings.Cut(splitPath[len(splitPath)-1], ".")

if strings.Contains(extension, "tar") {
return untarXZ(bundle, out)
} else if strings.Contains(extension, "zip") {
return unzipSource(bundle, out)
}
return untarXZ(bundle, out)

return fmt.Errorf("unknown format %v", extension)
}

func untarXZ(in, out string) error {
tar := exec.Command("tar", "-xmf", in, "-C", out)
tar := exec.Command("tar", "-xf", in, "-C", out)
tar.Stdout = os.Stdout
tar.Stderr = os.Stderr
if err := tar.Run(); err != nil {
Expand Down Expand Up @@ -354,3 +524,32 @@ func unzipFile(f *zip.File, destination string) error {
}
return nil
}

type InstallRequest struct {
Site, Package, Version string
}

func ExtractInstall(input string) InstallRequest {
var req InstallRequest
colonIdx := strings.Index(input, ":")
atIdx := strings.Index(input, "@")

if colonIdx != -1 {
req.Site = input[:colonIdx]
if atIdx != -1 {
req.Package = input[colonIdx+1 : atIdx]
req.Version = input[atIdx+1:]
} else {
req.Package = input[colonIdx+1:]
}
} else {
if atIdx != -1 {
req.Package = input[:atIdx]
req.Version = input[atIdx+1:]
} else {
req.Package = input
}
}

return req
}
54 changes: 54 additions & 0 deletions cli/install_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package cli

import (
"testing"
)

func TestExtract(t *testing.T) {
copy := "zpm:zl:s@te@st"
result := ExtractInstall(copy)
if result.Site != "zpm" {
t.Fatalf("Recieved '%q'. Wanted %q", result.Site, "zpm")
}

if result.Package != "zl:s" {
t.Fatalf("Recieved %q. Wanted %q", result.Package, "zl:s")
}

if result.Version != "te@st" {
t.Fatalf("Recieved %q. Wanted %q", result.Version, "te@st")
}
}


func TestSitePkg(t *testing.T) {
copy := "zpm:zl:s"
result := ExtractInstall(copy)
if result.Site != "zpm" {
t.Fatalf("Recieved '%q'. Wanted %q", result.Site, "zpm")
}

if result.Package != "zl:s" {
t.Fatalf("Recieved %q. Wanted %q", result.Package, "zl:s")
}

if result.Version != "" {
t.Fatalf("Recieved %q. Wanted %q", result.Version, "")
}
}

func TestPkg(t *testing.T) {
copy := "zls@11"
result := ExtractInstall(copy)
if result.Site != "" {
t.Fatalf("Recieved '%q'. Wanted %q", result.Site, "")
}

if result.Package != "zls" {
t.Fatalf("Recieved %q. Wanted %q", result.Package, "zls")
}

if result.Version != "11" {
t.Fatalf("Recieved %q. Wanted %q", result.Version, "11")
}
}
2 changes: 1 addition & 1 deletion cli/meta/version.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package meta

const VERSION = "v0.2.4"
const VERSION = "v0.2.5"

3 changes: 2 additions & 1 deletion help.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@ zvm (Zig Version Manager) {{.Version}}
© 2023-present Tristan Isham
--------------------------------

install, i <version>
install, i <version> [--zls]
Use `install` or `i` to download a specific version of Zig.
To install the latest version, use "master".
To install Zig Language server, add the flag --zls or -z

use <version>
Use `use` to switch between versions of Zig.
Expand Down
Loading

0 comments on commit 4e575f7

Please sign in to comment.