go-ml-dev/dyl

View on GitHub
dyl.go

Summary

Maintainability
A
0 mins
Test Coverage
B
83%
//
//Package dl implements loading shared libraries and binding functions
//

package dyl

import (
    "bytes"
    "compress/gzip"
    "fmt"
    "github.com/ulikunitz/xz"
    "golang.org/x/xerrors"
    "io"
    "io/ioutil"
    "net/http"
    "os"
    "path/filepath"
    "reflect"
    "unsafe"
)

/*
    /✱
    int function(int);

    #define DEFINE_JUMPER(x) \
         void *_godl_##x = (void*)0; \
         __asm__(".global "#x"\n\t"#x":\n\tmovq _godl_"#x"(%rip),%rax\n\tjmp *%rax\n")

    DEFINE_JUMPER(function)
    ✱/
    import "C"

    import (
        "github.com/sudachen/go-dl/dl"
        "runtime"
        "unsafe"
    )

    func init() {
        if runtime.GOOS == "linux" && runtime.GOARCH == "amd64"{
            so := dl.Load(
                dl.Cache("dl/go-dl/libfunction.so"),
                dl.LzmaExternal("https://github.com/sudachen/go-dl/releases/download/initial/libfunction_lin64.lzma"))
        } else if runtime.GOOS == "windows" && runtime.GOARCH == "amd64" {
            so := dl.Load(
                dl.Cache("dl/go-dl/function.dll"),
                dl.LzmaExternal("https://github.com/sudachen/go-dl/releases/download/initial/libfunction_win64.lzma"))
        }
        so.Bind("function",unsafe.Pointer(&C._godl_function))
    }

    func main() {
        C.function(0)
    }
*/

/*
Load finds and loads shared library. Can download library form the Internet if it's required
*/
func Load(a ...interface{}) SO {
    so := SO{
        verbose: IfsOption(Verbose(func(text string, vl int) {
            if vl == 0 {
                fmt.Println(text)
            }
        }), a).(Verbose),
        onerror: IfsOption(OnError(func(err error) {
            panic(err.Error())
        }), a).(OnError),
    }

    cached := ""

    for _, x := range a {
        switch q := x.(type) {
        case System, Custom:
            s := reflect.ValueOf(q).String()
            so.verbose(fmt.Sprintf("trying to load `%v`", s), 2)
            so.dlHandle, _ = loadLibrary(s)
            if so.dlHandle != 0 {
                return so
            }
        case Cached:
            cached = expandCache(reflect.ValueOf(q).String())
            so.verbose(fmt.Sprintf("trying to load `%v`", cached), 2)
            so.dlHandle, _ = loadLibrary(cached)
            if so.dlHandle != 0 {
                return so
            }
        }
    }

    if cached != "" {
        if preload(cached, a...) {
            so.verbose(fmt.Sprintf("trying to load `%v`", cached), 2)
            so.dlHandle, _ = loadLibrary(cached)
            if so.dlHandle != 0 {
                return so
            }
        }
    }

    so.onerror(xerrors.Errorf("not found or failed to load dynamic library"))
    return SO{}
}

/*
Bind binds the pointer to the shared library function
*/
func (so SO) Bind(funcname string, ptrptr unsafe.Pointer) {
    if !so.Ok() {
        so.onerror(xerrors.Errorf("dynamic library object is not initialized"))
        return
    }
    so.verbose(fmt.Sprintf("binding SO.'%v' to *(%v)", funcname, ptrptr), 2)
    if err := bindFunction(so.dlHandle, funcname, ptrptr); err != nil {
        so.onerror(err)
    }
}

/*
Ok returns true if SO object is associated with a shared library
*/
func (so SO) Ok() bool {
    return so.dlHandle != 0
}

/*
SO object incapacitates shared library handle
*/
type SO struct {
    dlHandle uintptr
    verbose  Verbose
    onerror  OnError
}

/*
LzmaExternal specifies url to the LZMA2 compressed shared library
*/
type LzmaExternal string

/*
GzipExternal specifies url to the Gzip compressed shared library
*/
type GzipExternal string

/*
External specifies url to the uncompressed shared library
*/
type External string

/*
Verbose defines function to print information messages
*/
type Verbose func(string, int)

/*
OnError defines function for error processing
*/
type OnError func(error)

/*
Cached specifies cache file and enables loading file from the Internet
*/
type Cached string

/*
System specifies system shared dll
*/
type System string

/*
Custom specifies full path to load specific shared library
*/
type Custom string

/*
Preload loads shared library from the Internet
*/
func (c Custom) Preload(a ...interface{}) {
    preload(string(c), a...)
}

/*
Preload loads shared library from the Internet
*/
func (c Cached) Preload(a ...interface{}) {
    preload(expandCache(string(c)), a...)
}

func preload(sopath string, a ...interface{}) (ok bool) {
    verbose := IfsOption(Verbose(func(text string, vl int) {
        if vl == 0 {
            fmt.Println(text)
        }
    }), a).(Verbose)
    onerror := IfsOption(OnError(func(err error) {
        panic(err.Error())
    }), a).(OnError)

    external, zt := StrMultiOption(a, External(""), GzipExternal(""), LzmaExternal(""))
    if external != "" {

        bf := bytes.Buffer{}

        verbose(fmt.Sprintf("downloading `%v`", external), 1)
        if resp, err := http.Get(external); err == nil {
            defer resp.Body.Close()
            if _, err = io.Copy(&bf, resp.Body); err == nil {
                if zt > 0 { // compressed
                    bx := bf
                    bf = bytes.Buffer{}
                    switch zt {
                    case 1: // GzipExternal
                        verbose("unpacking gzip", 2)
                        var z io.ReadCloser
                        if z, err = gzip.NewReader(&bx); err == nil {
                            defer z.Close()
                            _, err = io.Copy(&bf, z)
                        }
                    case 2: // LzmaExternal
                        verbose("unpacking lzma", 2)
                        var z io.Reader
                        if z, err = xz.NewReader(&bx); err == nil {
                            _, err = io.Copy(&bf, z)
                        }
                        //default:
                        //    panic("unknown compression")
                    }
                }
                if err == nil {
                    verbose(fmt.Sprintf("caching as `%v`", sopath), 2)
                    _ = os.MkdirAll(filepath.Dir(sopath), 0755)
                    err = ioutil.WriteFile(sopath, bf.Bytes(), 0644)
                }
            }
            if err != nil {
                onerror(err)
                return
            }
        }
        ok = true
    }
    return
}

/*
Remove removes cached file
*/
func (c Cached) Remove() (err error) {
    s := expandCache(string(c))
    _, err = os.Stat(s)
    if err == nil {
        return os.Remove(s)
    }
    return nil
}