diff --git a/.travis.yml b/.travis.yml index bb10a85..1ed0916 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,6 +17,7 @@ before_install: script: - make vendor - make package-all + - ls -l ./build - make test deploy: diff --git a/Makefile b/Makefile index 34923d2..2ae8bff 100644 --- a/Makefile +++ b/Makefile @@ -2,20 +2,14 @@ HOSTOS:=$(shell go env GOHOSTOS) SRC_DIR=./src DEPS_C = $(SRC_DIR)/*.h $(SRC_DIR)/*.c -DEPS_GO = $(SRC_DIR)/*.go vendor +DEPS_GO = $(SRC_DIR)/*.go gen_header.go vendor BUILD_MODE=-buildmode=c-shared GO_BUILD=go build CC_WIN64=x86_64-w64-mingw32-gcc CC_WIN32=i686-w64-mingw32-gcc -ifeq ("$(HOSTOS)", 'windows') -CP_HEADERS=copy $(SRC_DIR)/uast.h - -else -CP_HEADERS=cp $(SRC_DIR)/uast.h - -endif +GEN_HEADER=go run gen_header.go -o OUT_NAME=libuast OUT_HEADER=$(OUT_NAME).h @@ -51,34 +45,30 @@ vendor: Gopkg.* build: build-$(HOSTOS) -build-linux: $(DEPS_C) $(DEPS_GO) cut.sh +build-linux: $(DEPS_C) $(DEPS_GO) mkdir -p $(OUT_LINUX) && \ GOOS=linux GOARCH=amd64 $(GO_BUILD) $(BUILD_MODE) -o=$(OUT_LINUX)/$(OUT_NAME).so $(SRC_DIR)/ && \ - ./cut.sh $(OUT_LINUX)/$(OUT_HEADER) && \ - $(CP_HEADERS) $(OUT_LINUX)/ + $(GEN_HEADER) $(OUT_LINUX)/$(OUT_HEADER) -build-darwin: $(DEPS_C) $(DEPS_GO) cut.sh +build-darwin: $(DEPS_C) $(DEPS_GO) mkdir -p $(OUT_OSX) && \ GOOS=darwin GOARCH=amd64 CGO_ENABLED=1 $(GO_BUILD) $(BUILD_MODE) -o=$(OUT_OSX)/$(OUT_NAME).so $(SRC_DIR)/ && \ - ./cut.sh $(OUT_OSX)/$(OUT_HEADER) && \ - $(CP_HEADERS) $(OUT_OSX)/ + $(GEN_HEADER) $(OUT_OSX)/$(OUT_HEADER) ifneq ("$(HOSTOS)", "windows") -build-windows: $(DEPS_C) $(DEPS_GO) cut.sh +build-windows: $(DEPS_C) $(DEPS_GO) mkdir -p $(OUT_WINDOWS) GOOS=windows GOARCH=amd64 CGO_ENABLED=1 CC=$(CC_WIN64) $(GO_BUILD) $(BUILD_MODE) -o=$(OUT_WINDOWS)/$(OUT_NAME).dll $(SRC_DIR)/ - ./cut.sh $(OUT_WINDOWS)/$(OUT_HEADER) - $(CP_HEADERS) $(OUT_WINDOWS)/ + $(GEN_HEADER) $(OUT_WINDOWS)/$(OUT_HEADER) else -build-windows: $(DEPS_C) $(DEPS_GO) cut.sh +build-windows: $(DEPS_C) $(DEPS_GO) mkdir -p $(OUT_WINDOWS) set GOOS=windows set GOARCH=amd64 $(GO_BUILD) $(BUILD_MODE) -o=$(OUT_WINDOWS)\\$(OUT_NAME).dll $(SRC_DIR)/ - bash cut.sh $(OUT_WINDOWS)\\$(OUT_HEADER) - $(CP_HEADERS) $(OUT_WINDOWS)\\ + $(GEN_HEADER) $(OUT_WINDOWS)\\$(OUT_HEADER) endif diff --git a/cut.sh b/cut.sh deleted file mode 100755 index a74704c..0000000 --- a/cut.sh +++ /dev/null @@ -1,33 +0,0 @@ -#!/usr/bin/env bash - -LIBUAST="$1" -SED="sed -i" -if [[ "$OSTYPE" = "darwin"* ]]; then - SED='sed -i ""' -fi - -# Cut cgo prolog since we don't use cgo types in API definition. -$SED '/#line 1 "cgo-builtin-prolog"/,/#line 3 "api.go"/d' $LIBUAST -$SED '/#line 1 "cgo-generated-wrapper"/,/End of boilerplate cgo prologue/d' $LIBUAST -# Cut Go interface implementations that only exists to be used by the libuast itself. -$SED '/uastKind/,/uastSetKeyValue/d' $LIBUAST - -# Rename function parameters - -# UastNew -$SED 's/NodeIface\* p0, UastHandle p1/NodeIface\* impl, UastHandle instHandle/g' $LIBUAST -# UastDecode -$SED 's/void\* p0, size_t p1, UastFormat p2/void\* ptr, size_t sz, UastFormat format/g' $LIBUAST -# UastEncode -$SED 's/Uast\* p0, NodeHandle p1, size_t\* p2, UastFormat p3/Uast\* ctx, NodeHandle node, size_t\* outSize, UastFormat format/g' $LIBUAST -# UastFilter -$SED 's/Uast\* p0, NodeHandle p1, char\* p2/Uast\* ctx, NodeHandle node, char\* query/g' $LIBUAST -# SetError -$SED 's/Uast\* p0, char\* p1/Uast\* ctx, char\* err/g' $LIBUAST - -# Common replacements -$SED 's/Uast\* p0/Uast\* ctx/g' $LIBUAST -$SED 's/NodeHandle p1/NodeHandle node/g' $LIBUAST -$SED 's/TreeOrder p2/TreeOrder order/g' $LIBUAST -$SED 's/UastIterator\* p0/UastIterator\* iter/g' $LIBUAST - diff --git a/gen_header.go b/gen_header.go new file mode 100644 index 0000000..6d4f58d --- /dev/null +++ b/gen_header.go @@ -0,0 +1,145 @@ +package main + +import ( + "bytes" + "flag" + "fmt" + "go/ast" + "go/parser" + "go/token" + "io/ioutil" + "os" + "strings" +) + +var ( + outFile = flag.String("o", "libuast.h", "output file") +) + +func main() { + flag.Parse() + if err := run(); err != nil { + fmt.Fprintln(os.Stderr, err) + os.Exit(1) + } +} + +func run() error { + src, err := ioutil.ReadFile("./src/api.go") + if err != nil { + return err + } + fs := token.NewFileSet() + tree, err := parser.ParseFile(fs, "api.go", src, parser.ParseComments) + if err != nil { + return err + } + buf := new(bytes.Buffer) + buf.WriteString(` +#ifdef __cplusplus +extern "C" { +#endif +`) + for _, dec := range tree.Decls { + fnc, ok := dec.(*ast.FuncDecl) + if !ok { + continue + } + doc := fnc.Doc.Text() + pref := "export " + fnc.Name.Name + if !strings.Contains(doc, pref) { + continue + } + buf.WriteString("\n") + for _, c := range fnc.Doc.List { + if strings.HasPrefix(c.Text, "//"+pref) { + continue + } + buf.WriteString(c.Text + "\n") + } + if err = writeCSignature(buf, fnc); err != nil { + return err + } + } + buf.WriteString(` +#ifdef __cplusplus +} +#endif +`) + return writeHeader(buf.Bytes()) +} + +func writeCSignature(buf *bytes.Buffer, fnc *ast.FuncDecl) error { + typ := fnc.Type + buf.WriteString("extern ") + if typ.Results != nil && len(typ.Results.List) != 0 { + if err := writeCType(buf, typ.Results.List[0].Type); err != nil { + return err + } + } else { + buf.WriteString("void") + } + buf.WriteString(" ") + buf.WriteString(fnc.Name.Name) + buf.WriteString("(") + for i, p := range fnc.Type.Params.List { + if i != 0 { + buf.WriteString(", ") + } + for j, name := range p.Names { + if j != 0 { + buf.WriteString(", ") + } + if err := writeCType(buf, p.Type); err != nil { + return err + } + buf.WriteString(" ") + buf.WriteString(name.Name) + } + } + buf.WriteString(");\n") + return nil +} + +func writeCType(buf *bytes.Buffer, typ ast.Expr) error { + switch typ := typ.(type) { + case *ast.Ident: + buf.WriteString(typ.Name) + case *ast.StarExpr: + if err := writeCType(buf, typ.X); err != nil { + return err + } + buf.WriteString("*") + case *ast.SelectorExpr: + pkg, ok := typ.X.(*ast.Ident) + if !ok { + return fmt.Errorf("unknown package: %T", typ.X) + } + switch pkg.Name { + default: + return fmt.Errorf("unknown package: %v", pkg.Name) + case "unsafe": + if typ.Sel.Name != "Pointer" { + return fmt.Errorf("unknown unsafe type: %v", typ.Sel.Name) + } + buf.WriteString("void*") + return nil + case "C": + } + if err := writeCType(buf, typ.Sel); err != nil { + return err + } + default: + return fmt.Errorf("unsupported type: %T", typ) + } + return nil +} + +func writeHeader(data []byte) error { + hdr, err := ioutil.ReadFile("./src/uast.h") + if err != nil { + return err + } + hdr = bytes.Replace(hdr, []byte("/*GO-HEADER*/"), data, 1) + return ioutil.WriteFile(*outFile, hdr, 0644) +} diff --git a/src/uast.h b/src/uast.h index 52f5b69..7f5bb58 100644 --- a/src/uast.h +++ b/src/uast.h @@ -4,6 +4,7 @@ #include #include #include +#include #define UAST_HASH_SIZE 32 @@ -142,4 +143,6 @@ static NodeHandle UastLoad(const Uast *src, NodeHandle n, const Uast *dst) { return 0; } +/*GO-HEADER*/ + #endif // UAST_H_ \ No newline at end of file