| // Copyright 2015 The Go Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style |
| // license that can be found in the LICENSE file. |
| |
| package gl |
| |
| import ( |
| "archive/tar" |
| "compress/gzip" |
| "debug/pe" |
| "fmt" |
| "io" |
| "io/ioutil" |
| "log" |
| "net/http" |
| "os" |
| "path/filepath" |
| "runtime" |
| ) |
| |
| var debug = log.New(ioutil.Discard, "gl: ", log.LstdFlags) |
| |
| func downloadDLLs() (path string, err error) { |
| url := "https://dl.google.com/go/mobile/angle-bd3f8780b-" + runtime.GOARCH + ".tgz" |
| debug.Printf("downloading %s", url) |
| resp, err := http.Get(url) |
| if err != nil { |
| return "", fmt.Errorf("gl: %v", err) |
| } |
| defer func() { |
| err2 := resp.Body.Close() |
| if err == nil && err2 != nil { |
| err = fmt.Errorf("gl: error reading body from %v: %v", url, err2) |
| } |
| }() |
| if resp.StatusCode != http.StatusOK { |
| err := fmt.Errorf("gl: error fetching %v, status: %v", url, resp.Status) |
| return "", err |
| } |
| |
| r, err := gzip.NewReader(resp.Body) |
| if err != nil { |
| return "", fmt.Errorf("gl: error reading gzip from %v: %v", url, err) |
| } |
| tr := tar.NewReader(r) |
| var bytesGLESv2, bytesEGL, bytesD3DCompiler []byte |
| for { |
| header, err := tr.Next() |
| if err == io.EOF { |
| break |
| } |
| if err != nil { |
| return "", fmt.Errorf("gl: error reading tar from %v: %v", url, err) |
| } |
| switch header.Name { |
| case "angle-" + runtime.GOARCH + "/libglesv2.dll": |
| bytesGLESv2, err = ioutil.ReadAll(tr) |
| case "angle-" + runtime.GOARCH + "/libegl.dll": |
| bytesEGL, err = ioutil.ReadAll(tr) |
| case "angle-" + runtime.GOARCH + "/d3dcompiler_47.dll": |
| bytesD3DCompiler, err = ioutil.ReadAll(tr) |
| default: // skip |
| } |
| if err != nil { |
| return "", fmt.Errorf("gl: error reading %v from %v: %v", header.Name, url, err) |
| } |
| } |
| if len(bytesGLESv2) == 0 || len(bytesEGL) == 0 || len(bytesD3DCompiler) == 0 { |
| return "", fmt.Errorf("gl: did not find all DLLs in %v", url) |
| } |
| |
| writeDLLs := func(path string) error { |
| if err := ioutil.WriteFile(filepath.Join(path, "libglesv2.dll"), bytesGLESv2, 0755); err != nil { |
| return fmt.Errorf("gl: cannot install ANGLE: %v", err) |
| } |
| if err := ioutil.WriteFile(filepath.Join(path, "libegl.dll"), bytesEGL, 0755); err != nil { |
| return fmt.Errorf("gl: cannot install ANGLE: %v", err) |
| } |
| if err := ioutil.WriteFile(filepath.Join(path, "d3dcompiler_47.dll"), bytesD3DCompiler, 0755); err != nil { |
| return fmt.Errorf("gl: cannot install ANGLE: %v", err) |
| } |
| return nil |
| } |
| |
| // First, we attempt to install these DLLs in LOCALAPPDATA/Shiny. |
| // |
| // Traditionally we would use the system32 directory, but it is |
| // no longer writable by normal programs. |
| os.MkdirAll(appdataPath(), 0775) |
| if err := writeDLLs(appdataPath()); err == nil { |
| return appdataPath(), nil |
| } |
| debug.Printf("DLLs could not be written to %s", appdataPath()) |
| |
| // Second, install in GOPATH/pkg if it exists. |
| gopath := os.Getenv("GOPATH") |
| gopathpkg := filepath.Join(gopath, "pkg") |
| if _, err := os.Stat(gopathpkg); err == nil && gopath != "" { |
| if err := writeDLLs(gopathpkg); err == nil { |
| return gopathpkg, nil |
| } |
| } |
| debug.Printf("DLLs could not be written to GOPATH") |
| |
| // Third, pick a temporary directory. |
| tmp := os.TempDir() |
| if err := writeDLLs(tmp); err != nil { |
| return "", fmt.Errorf("gl: unable to install ANGLE DLLs: %v", err) |
| } |
| return tmp, nil |
| } |
| |
| func appdataPath() string { |
| return filepath.Join(os.Getenv("LOCALAPPDATA"), "GoGL", runtime.GOARCH) |
| } |
| |
| func containsDLLs(dir string) bool { |
| compatible := func(name string) bool { |
| file, err := pe.Open(filepath.Join(dir, name)) |
| if err != nil { |
| return false |
| } |
| defer file.Close() |
| |
| switch file.Machine { |
| case pe.IMAGE_FILE_MACHINE_AMD64: |
| return "amd64" == runtime.GOARCH |
| case pe.IMAGE_FILE_MACHINE_ARM: |
| return "arm" == runtime.GOARCH |
| case pe.IMAGE_FILE_MACHINE_I386: |
| return "386" == runtime.GOARCH |
| } |
| return false |
| } |
| |
| return compatible("libglesv2.dll") && compatible("libegl.dll") && compatible("d3dcompiler_47.dll") |
| } |
| |
| func chromePath() string { |
| // dlls are stored in: |
| // <BASE>/<VERSION>/libglesv2.dll |
| |
| var installdirs = []string{ |
| // Chrome User |
| filepath.Join(os.Getenv("LOCALAPPDATA"), "Google", "Chrome", "Application"), |
| // Chrome System |
| filepath.Join(os.Getenv("ProgramFiles(x86)"), "Google", "Chrome", "Application"), |
| // Chromium |
| filepath.Join(os.Getenv("LOCALAPPDATA"), "Chromium", "Application"), |
| // Chrome Canary |
| filepath.Join(os.Getenv("LOCALAPPDATA"), "Google", "Chrome SxS", "Application"), |
| } |
| |
| for _, installdir := range installdirs { |
| versiondirs, err := ioutil.ReadDir(installdir) |
| if err != nil { |
| continue |
| } |
| |
| for _, versiondir := range versiondirs { |
| if !versiondir.IsDir() { |
| continue |
| } |
| |
| versionpath := filepath.Join(installdir, versiondir.Name()) |
| if containsDLLs(versionpath) { |
| return versionpath |
| } |
| } |
| } |
| |
| return "" |
| } |
| |
| func findDLLs() (err error) { |
| load := func(path string) (bool, error) { |
| if path != "" { |
| // don't try to start when one of the files is missing |
| if !containsDLLs(path) { |
| return false, nil |
| } |
| |
| LibD3DCompiler.Name = filepath.Join(path, filepath.Base(LibD3DCompiler.Name)) |
| LibGLESv2.Name = filepath.Join(path, filepath.Base(LibGLESv2.Name)) |
| LibEGL.Name = filepath.Join(path, filepath.Base(LibEGL.Name)) |
| } |
| |
| if err := LibGLESv2.Load(); err == nil { |
| if err := LibEGL.Load(); err != nil { |
| return false, fmt.Errorf("gl: loaded libglesv2 but not libegl: %v", err) |
| } |
| if err := LibD3DCompiler.Load(); err != nil { |
| return false, fmt.Errorf("gl: loaded libglesv2, libegl but not d3dcompiler: %v", err) |
| } |
| if path == "" { |
| debug.Printf("DLLs found") |
| } else { |
| debug.Printf("DLLs found in: %q", path) |
| } |
| return true, nil |
| } |
| |
| return false, nil |
| } |
| |
| // Look in the system directory. |
| if ok, err := load(""); ok || err != nil { |
| return err |
| } |
| |
| // Look in the AppData directory. |
| if ok, err := load(appdataPath()); ok || err != nil { |
| return err |
| } |
| |
| // Look for a Chrome installation |
| if dir := chromePath(); dir != "" { |
| if ok, err := load(dir); ok || err != nil { |
| return err |
| } |
| } |
| |
| // Look in GOPATH/pkg. |
| if ok, err := load(filepath.Join(os.Getenv("GOPATH"), "pkg")); ok || err != nil { |
| return err |
| } |
| |
| // Look in temporary directory. |
| if ok, err := load(os.TempDir()); ok || err != nil { |
| return err |
| } |
| |
| // Download the DLL binary. |
| path, err := downloadDLLs() |
| if err != nil { |
| return err |
| } |
| debug.Printf("DLLs written to %s", path) |
| if ok, err := load(path); !ok || err != nil { |
| return fmt.Errorf("gl: unable to load ANGLE after installation: %v", err) |
| } |
| return nil |
| } |