diff --git a/cl/ctx.go b/cl/ctx.go index ce7e520f..4ded6117 100644 --- a/cl/ctx.go +++ b/cl/ctx.go @@ -274,7 +274,7 @@ func (p *pkgCtx) typeOf(cName string) (types.Type, bool) { } func (p *pkgCtx) isTypeIgnored(cName string) bool { - return contains(cName, p.typeIgnores) + return contains(trimTypeTag(cName), p.typeIgnores) } func (p *pkgCtx) isNSIgnored(ns string) bool { diff --git a/cl/type.go b/cl/type.go index 5b6de45e..2fd30244 100644 --- a/cl/type.go +++ b/cl/type.go @@ -165,7 +165,7 @@ func toTypeEx(ctx *pkgCtx, pkg *types.Package, typ lc.Type, flags int, feats *in return t } case lc.Type_BlockPointer: - ctx.logtf(typ, "C blocks (closures) are unsupported, ignored") + ctx.logtf(typ, "type %s: C blocks (closures) are unsupported, ignored", clang.String(typ)) *feats |= featIgnored return types.Typ[types.UnsafePointer] default: diff --git a/cl/typedef.go b/cl/typedef.go index ab2214fb..38d4e7d3 100644 --- a/cl/typedef.go +++ b/cl/typedef.go @@ -28,7 +28,8 @@ import ( func loadTypedef(ctx *pkgCtx, decl clang.Cursor, ns string, scope *scopeCtx) { cName := clang.String(decl.Type()) - if ctx.isTypeIgnored(cName) { + localName := clang.String(decl) // TODO(xsw): use full name with namespace + if ctx.isTypeIgnored(localName) { if debugCompileDecl { log.Println("typedef", cName, "- ignored") } @@ -36,7 +37,7 @@ func loadTypedef(ctx *pkgCtx, decl clang.Cursor, ns string, scope *scopeCtx) { return } - origName := nameWithNS(clang.String(decl), ns) + origName := nameWithNS(localName, ns) if decl.Kind == lc.Cursor_TypeAliasTemplateDecl { ctx.logf(decl, "typedef %s: template type alias, skipped", origName) return diff --git a/cmd/llcppg/gen.go b/cmd/llcppg/gen.go new file mode 100644 index 00000000..c618343b --- /dev/null +++ b/cmd/llcppg/gen.go @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2026 The XGo Authors (xgo.dev). All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package main + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/goplus/gogen" + "github.com/goplus/llcppg/clang" + "github.com/goplus/llcppg/tool" + "github.com/qiniu/x/errors" +) + +// ----------------------------------------------------------------------------- + +// Gen generates Go files from the C/C++ header files and llcppg.cfg in the specified +// source directory and writes them to the destination directory. It uses the provided +// clang index for parsing. +func Gen(destDir, srcDir string, index clang.Index) (err error) { + cfg, err := tool.LoadConf(filepath.Join(srcDir, "llcppg.cfg")) + if err != nil { + return + } + if strings.Contains(cfg.Name, "/") { + err = errors.New("invalid package name: " + cfg.Name) + return + } + mainPkgName := cfg.Name + + if len(cfg.Pkgs) == 0 { + return genPkg(destDir, srcDir, mainPkgName, index, &cfg) + } + + errPkgCnt := 0 + for _, pkgSel := range cfg.Pkgs { + cfgFile := filepath.Join(srcDir, "llcppg-"+pkgSel+".cfg") + subConf, err := tool.LoadConf(cfgFile) + if err != nil { + fmt.Fprintln(os.Stderr, "LoadConf failed:", err) + errPkgCnt++ + continue + } + if !strings.HasPrefix(subConf.Name, "/") { + fmt.Fprintf(os.Stderr, + "sub package %s: name must start with / but got %s\n", pkgSel, subConf.Name) + errPkgCnt++ + continue + } + subConf.Apply(&cfg) + errPkgCnt += convPkg(destDir, srcDir, mainPkgName, index, &subConf) + } + if errPkgCnt > 0 { + return fmt.Errorf("%d package(s) failed during generation", errPkgCnt) + } + return nil +} + +func convPkg(destDir, srcDir, mainPkgName string, index clang.Index, cfg *tool.Config) int { + err := genPkg(destDir, srcDir, mainPkgName, index, cfg) + if err != nil { + fmt.Fprintln(os.Stderr, err) + return 1 + } + return 0 +} + +func genPkg(destDir, srcDir, mainPkgName string, index clang.Index, cfg *tool.Config) (err error) { + pkgName := cfg.Name + if pos := strings.LastIndex(pkgName, "/"); pos >= 0 { + destDir = filepath.Join(destDir, pkgName[len(mainPkgName)+1:]) + pkgName = pkgName[pos+1:] + } + + err = os.MkdirAll(destDir, 0755) + if err != nil { + return + } + + pkg, _, err := cfg.NewPackage("", pkgName, srcDir, index) + if err != nil { + return + } + + var errs errors.List + var old = gogen.GeneratedHeader + defer func() { + gogen.GeneratedHeader = old + }() + gogen.GeneratedHeader = tool.GeneratedHeader + pkg.ForEachFile(func(fname string, file *gogen.File) { + if file.Empty() { + return // skip empty Go files + } + goFile := filepath.Join(destDir, fname) + e := pkg.WriteFile(goFile, fname) + if e != nil { + errs.Add(e) + } + }) + return errs.ToError() +} + +// ----------------------------------------------------------------------------- diff --git a/cmd/llcppg/llcppg.go b/cmd/llcppg/llcppg.go index 9bb8f8fe..d194e302 100644 --- a/cmd/llcppg/llcppg.go +++ b/cmd/llcppg/llcppg.go @@ -53,7 +53,7 @@ func main() { idx := clang.CreateIndex(1, 1) defer idx.Dispose() - err := tool.Gen(destDir, srcDir, idx) + err := Gen(destDir, srcDir, idx) if err != nil { fmt.Fprintln(os.Stderr, err) os.Exit(1) diff --git a/tool/config.go b/tool/config.go index 623c24f9..3e7966aa 100644 --- a/tool/config.go +++ b/tool/config.go @@ -28,10 +28,10 @@ import ( // ----------------------------------------------------------------------------- type Config struct { - Name string `json:"Name"` // required, sub package name should sarts with '/' + Name string `json:"Name"` // required, sub package name should start with '/' Language string `json:"Language"` // c, c++, etc. required Dir string `json:"Dir"` // dir or dir/... (recursive), required - Files []string `json:"Files"` // selected header files, optional + Files []string `json:"Files"` // selected header files relative to Dir; overrides auto-discovery, optional. Stdlib string `json:"Stdlib"` // C stdlib include dir, optional LLGoPackage string `json:"LLGoPackage"` // optional CFlags string `json:"CFlags"` // optional diff --git a/tool/gen.go b/tool/gen.go index 67225f99..d4b9f289 100644 --- a/tool/gen.go +++ b/tool/gen.go @@ -26,7 +26,6 @@ import ( "sort" "strings" - "github.com/goplus/gogen" "github.com/goplus/gogen/packages" "github.com/goplus/gogen/packages/cache" "github.com/goplus/llcppg/cl" @@ -330,43 +329,3 @@ func DisposeSources(sources []cl.Source) { } // ----------------------------------------------------------------------------- - -// Gen generates Go files from the C/C++ header files and llcppg.cfg in the specified -// source directory and writes them to the destination directory. It uses the provided -// clang index for parsing. -func Gen(destDir, srcDir string, index clang.Index) (err error) { - cfg, err := LoadConf(filepath.Join(srcDir, "llcppg.cfg")) - if err != nil { - return - } - - err = os.MkdirAll(destDir, 0755) - if err != nil { - return - } - - pkg, _, err := cfg.NewPackage("", "", srcDir, index) - if err != nil { - return - } - - var errs errors.List - var old = gogen.GeneratedHeader - defer func() { - gogen.GeneratedHeader = old - }() - gogen.GeneratedHeader = GeneratedHeader - pkg.ForEachFile(func(fname string, file *gogen.File) { - if file.Empty() { - return // skip empty Go files - } - goFile := filepath.Join(destDir, fname) - e := pkg.WriteFile(goFile, fname) - if e != nil { - errs.Add(e) - } - }) - return errs.ToError() -} - -// -----------------------------------------------------------------------------