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  }