github.com/gotranspile/cxgo@v0.3.8-0.20240118201721-29871598a6a2/cmd/cxgo/main.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "errors" 6 "fmt" 7 "go/format" 8 "io/ioutil" 9 "log" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "regexp" 14 "strings" 15 16 "github.com/bmatcuk/doublestar" 17 "github.com/spf13/cobra" 18 "gopkg.in/yaml.v3" 19 20 "github.com/gotranspile/cxgo" 21 "github.com/gotranspile/cxgo/internal/git" 22 "github.com/gotranspile/cxgo/libs" 23 "github.com/gotranspile/cxgo/types" 24 ) 25 26 var Root = &cobra.Command{ 27 Use: "cxgo", 28 Short: "transpile a C project to Go", 29 RunE: run, 30 } 31 32 var ( 33 version = "dev" 34 commit = "" 35 date = "" 36 ) 37 38 var configPath = "cxgo.yml" 39 40 func printVersion() { 41 vers := version 42 if s := commit; s != "" { 43 vers = fmt.Sprintf("%s (%s)", vers, s[:8]) 44 } 45 fmt.Printf("version: %s\n", vers) 46 if date != "" { 47 fmt.Printf("built: %s\n", date) 48 } 49 } 50 51 func init() { 52 Root.Flags().StringVarP(&configPath, "config", "c", configPath, "config file path") 53 Root.AddCommand(&cobra.Command{ 54 Use: "version", 55 Short: "print cxgo version", 56 Run: func(cmd *cobra.Command, args []string) { 57 printVersion() 58 }, 59 }) 60 } 61 62 func main() { 63 if err := Root.Execute(); err != nil { 64 fmt.Fprintln(os.Stderr, err) 65 os.Exit(1) 66 } 67 } 68 69 type Replacement struct { 70 Old string `yaml:"old"` 71 Re string `yaml:"regexp"` 72 New string `yaml:"new"` 73 } 74 75 func (r Replacement) Build() (*cxgo.Replacer, error) { 76 if r.Re == "" && r.Old == "" { 77 return nil, errors.New("either 'regexp' or 'old' must be set") 78 } 79 var re *regexp.Regexp 80 if r.Re != "" { 81 reg, err := regexp.Compile(r.Re) 82 if err != nil { 83 return nil, err 84 } 85 re = reg 86 } 87 return &cxgo.Replacer{ 88 Old: r.Old, 89 Re: re, 90 New: r.New, 91 }, nil 92 } 93 94 type SrcFile struct { 95 Name string `yaml:"name"` 96 Content string `yaml:"content"` 97 Perm int `yaml:"perm"` 98 } 99 100 type File struct { 101 Disabled bool `yaml:"disabled"` 102 Name string `yaml:"name"` 103 Content string `yaml:"content"` 104 Predef string `yaml:"predef"` 105 GoFile string `yaml:"go"` 106 FlattenAll *bool `yaml:"flatten_all"` 107 ForwardDecl *bool `yaml:"forward_decl"` 108 MaxDecls int `yaml:"max_decl"` 109 Skip []string `yaml:"skip"` 110 Idents []cxgo.IdentConfig `yaml:"idents"` 111 Replace []Replacement `yaml:"replace"` 112 } 113 114 type Config struct { 115 VCS string `yaml:"vcs"` 116 Branch string `yaml:"branch"` 117 Root string `yaml:"root"` 118 Out string `yaml:"out"` 119 Package string `yaml:"package"` 120 Include []string `yaml:"include"` 121 SysInclude []string `yaml:"sys_include"` 122 IncludeMap map[string]string `yaml:"include_map"` 123 Hooks bool `yaml:"hooks"` 124 Define []cxgo.Define `yaml:"define"` 125 Predef string `yaml:"predef"` 126 SubPackage bool `yaml:"subpackage"` 127 128 IntSize int `yaml:"int_size"` 129 PtrSize int `yaml:"ptr_size"` 130 WcharSize int `yaml:"wchar_size"` 131 UseGoInt bool `yaml:"use_go_int"` 132 133 ForwardDecl bool `yaml:"forward_decl"` 134 FlattenAll bool `yaml:"flatten_all"` 135 FlattenFunc []string `yaml:"flatten"` 136 Skip []string `yaml:"skip"` 137 Replace []Replacement `yaml:"replace"` 138 Idents []cxgo.IdentConfig `yaml:"idents"` 139 ImplicitReturns bool `yaml:"implicit_returns"` 140 IgnoreIncludeDir bool `yaml:"ignore_include_dir"` 141 UnexportedFields bool `yaml:"unexported_fields"` 142 IntReformat bool `yaml:"int_reformat"` 143 KeepFree bool `yaml:"keep_free"` 144 NoLibs bool `yaml:"no_libs"` 145 DoNotEdit bool `yaml:"do_not_edit"` 146 147 SrcFiles []*SrcFile `yaml:"src_files"` 148 FilePref string `yaml:"file_pref"` 149 Files []*File `yaml:"files"` 150 151 ExecBefore []string `yaml:"exec_before"` 152 ExecAfter []string `yaml:"exec_after"` 153 } 154 155 func mergeBool(val *bool, def bool) bool { 156 if val == nil { 157 return def 158 } 159 return *val 160 } 161 162 func run(cmd *cobra.Command, args []string) error { 163 defer cxgo.CallFinals() 164 conf, _ := cmd.Flags().GetString("config") 165 data, err := ioutil.ReadFile(conf) 166 if err != nil { 167 return err 168 } 169 var c Config 170 if err = yaml.Unmarshal(data, &c); err != nil { 171 return err 172 } 173 if c.VCS != "" { 174 name := strings.TrimSuffix(c.VCS, ".git") 175 if i := strings.LastIndex(name, "/"); i > 0 { 176 name = name[i:] 177 } 178 dir := filepath.Join(os.TempDir(), name) 179 _, err := os.Stat(dir) 180 if os.IsNotExist(err) { 181 log.Printf("clonning %s to %s", c.VCS, dir) 182 if err := git.Clone(c.VCS, c.Branch, dir); err != nil { 183 return err 184 } 185 } else if err != nil { 186 return err 187 } else { 188 log.Printf("already cloned %s to %s", c.Root, dir) 189 } 190 c.Root = filepath.Join(dir, c.Root) 191 } 192 if !filepath.IsAbs(c.Root) { 193 c.Root = filepath.Join(filepath.Dir(conf), c.Root) 194 } 195 for _, f := range c.SrcFiles { 196 if f.Name == "" { 197 return errors.New("src_files entry with no name") 198 } 199 perm := os.FileMode(f.Perm) 200 if perm == 0 { 201 perm = 0644 202 } 203 log.Printf("writing %q (%o)", f.Name, perm) 204 if err := os.WriteFile(filepath.Join(c.Root, f.Name), []byte(f.Content), perm); err != nil { 205 return err 206 } 207 } 208 if !filepath.IsAbs(c.Out) { 209 c.Out = filepath.Join(filepath.Dir(conf), c.Out) 210 if abs, err := filepath.Abs(c.Out); err == nil { 211 c.Out = abs 212 } 213 } 214 log.Printf("writing to %s", c.Out) 215 if err := os.MkdirAll(c.Out, 0755); err != nil { 216 return err 217 } 218 tconf := types.Default() 219 if c.UseGoInt { 220 tconf.UseGoInt = c.UseGoInt 221 } 222 if c.IntSize != 0 { 223 tconf.IntSize = c.IntSize 224 } 225 if c.PtrSize != 0 { 226 tconf.PtrSize = c.PtrSize 227 } 228 if c.WcharSize != 0 { 229 tconf.WCharSize = c.WcharSize 230 } 231 for i := range c.Include { 232 if filepath.IsAbs(c.Include[i]) { 233 continue 234 } 235 c.Include[i] = filepath.Join(c.Root, c.Include[i]) 236 } 237 for i := range c.SysInclude { 238 if filepath.IsAbs(c.SysInclude[i]) { 239 continue 240 } 241 c.SysInclude[i] = filepath.Join(c.Root, c.SysInclude[i]) 242 } 243 seen := make(map[string]struct{}) 244 processFile := func(f *File) error { 245 if _, ok := seen[f.Name]; ok { 246 return fmt.Errorf("ducplicate entry for file: %q", f.Name) 247 } 248 seen[f.Name] = struct{}{} 249 if f.Content != "" { 250 data := []byte(f.Content) 251 if fdata, err := format.Source(data); err == nil { 252 data = fdata 253 } 254 return ioutil.WriteFile(filepath.Join(c.Out, f.Name), data, 0644) 255 } 256 idents := make(map[string]cxgo.IdentConfig) 257 for _, v := range c.Idents { 258 idents[v.Name] = v 259 } 260 for _, v := range f.Idents { 261 idents[v.Name] = v 262 } 263 ilist := make([]cxgo.IdentConfig, 0, len(idents)) 264 for _, v := range idents { 265 ilist = append(ilist, v) 266 } 267 268 env := libs.NewEnv(tconf) 269 fc := cxgo.Config{ 270 Root: c.Root, 271 Package: c.Package, 272 GoFile: f.GoFile, 273 GoFilePref: c.FilePref, 274 FlattenAll: mergeBool(f.FlattenAll, c.FlattenAll), 275 ForwardDecl: mergeBool(f.ForwardDecl, c.ForwardDecl), 276 MaxDecls: -1, 277 Hooks: c.Hooks, 278 Define: c.Define, 279 Predef: f.Predef, 280 Idents: ilist, 281 Include: c.Include, 282 SysInclude: c.SysInclude, 283 IncludeMap: c.IncludeMap, 284 FixImplicitReturns: c.ImplicitReturns, 285 IgnoreIncludeDir: c.IgnoreIncludeDir, 286 UnexportedFields: c.UnexportedFields, 287 IntReformat: c.IntReformat, 288 KeepFree: c.KeepFree, 289 DoNotEdit: c.DoNotEdit, 290 } 291 env.NoLibs = c.NoLibs 292 env.Map = c.IncludeMap 293 if f.MaxDecls > 0 { 294 fc.MaxDecls = f.MaxDecls 295 } 296 if fc.Predef == "" { 297 fc.Predef = c.Predef 298 } 299 for _, r := range f.Replace { 300 rp, err := r.Build() 301 if err != nil { 302 return err 303 } 304 fc.Replace = append(fc.Replace, *rp) 305 } 306 for _, r := range c.Replace { 307 rp, err := r.Build() 308 if err != nil { 309 return err 310 } 311 fc.Replace = append(fc.Replace, *rp) 312 } 313 if len(f.Skip) != 0 { 314 fc.SkipDecl = make(map[string]bool) 315 for _, s := range f.Skip { 316 fc.SkipDecl[s] = true 317 } 318 } 319 log.Println(f.Name) 320 if err := cxgo.Translate(c.Root, filepath.Join(c.Root, f.Name), c.Out, env, fc); err != nil { 321 return err 322 } 323 return nil 324 } 325 if err := runCmd(c.Root, c.ExecBefore); err != nil { 326 return err 327 } 328 for _, f := range c.Files { 329 if f.Disabled { 330 seen[f.Name] = struct{}{} 331 continue 332 } 333 if strings.Contains(f.Name, "*") { 334 paths, err := doublestar.Glob(filepath.Join(c.Root, f.Name)) 335 if err != nil { 336 return err 337 } 338 for _, path := range paths { 339 rel, err := filepath.Rel(c.Root, path) 340 if err != nil { 341 return fmt.Errorf("%s: %w", path, err) 342 } 343 if _, ok := seen[rel]; ok { 344 continue 345 } else if _, ok = seen["./"+rel]; ok { 346 continue 347 } 348 f2 := *f 349 f2.Name = rel 350 if err := processFile(&f2); err != nil { 351 return fmt.Errorf("%s: %w", path, err) 352 } 353 } 354 } else { 355 if err := processFile(f); err != nil { 356 return fmt.Errorf("%s: %w", f.Name, err) 357 } 358 } 359 } 360 if !c.SubPackage { 361 if _, err := os.Stat(filepath.Join(c.Out, "go.mod")); os.IsNotExist(err) { 362 var buf bytes.Buffer 363 fmt.Fprintf(&buf, `module %s 364 365 go 1.18 366 367 require ( 368 %s %s 369 ) 370 `, c.Package, libs.RuntimePackage, libs.RuntimePackageVers) 371 if err := ioutil.WriteFile(filepath.Join(c.Out, "go.mod"), buf.Bytes(), 0644); err != nil { 372 return err 373 } 374 } 375 } 376 if err := runCmd(c.Out, c.ExecAfter); err != nil { 377 return err 378 } 379 return nil 380 } 381 382 func runCmd(wd string, args []string) error { 383 if len(args) == 0 { 384 return nil 385 } 386 log.Printf("+ %s", strings.Join(args, " ")) 387 cmd := exec.Command(args[0], args[1:]...) 388 cmd.Dir = wd 389 cmd.Stdout = os.Stderr 390 cmd.Stderr = os.Stderr 391 return cmd.Run() 392 }