modernc.org/ccgo/v3@v3.16.14/lib/util.go (about)

     1  // Copyright 2020 The CCGO Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  // generator.go helpers
     6  
     7  package ccgo // import "modernc.org/ccgo/v3/lib"
     8  
     9  import (
    10  	"archive/tar"
    11  	"bufio"
    12  	"bytes"
    13  	"compress/gzip"
    14  	"fmt"
    15  	"io"
    16  	"io/ioutil"
    17  	"os"
    18  	"os/exec"
    19  	"path/filepath"
    20  	"runtime/debug"
    21  	"strings"
    22  )
    23  
    24  // CopyFile copies src to dest, preserving permissions and times where/when
    25  // possible. If canOverwrite is not nil, it is consulted whether a destination
    26  // file can be overwritten. If canOverwrite is nil then destination is
    27  // overwritten if permissions allow that, otherwise the function fails.
    28  //
    29  // Src and dst must be in the slash form.
    30  func CopyFile(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (n int64, rerr error) {
    31  	dst = filepath.FromSlash(dst)
    32  	dstDir := filepath.Dir(dst)
    33  	di, err := os.Stat(dstDir)
    34  	switch {
    35  	case err != nil:
    36  		if !os.IsNotExist(err) {
    37  			return 0, err
    38  		}
    39  
    40  		if err := os.MkdirAll(dstDir, 0770); err != nil {
    41  			return 0, err
    42  		}
    43  	case err == nil:
    44  		if !di.IsDir() {
    45  			return 0, fmt.Errorf("cannot create directory, file exists: %s", dst)
    46  		}
    47  	}
    48  
    49  	src = filepath.FromSlash(src)
    50  	si, err := os.Stat(src)
    51  	if err != nil {
    52  		return 0, err
    53  	}
    54  
    55  	if si.IsDir() {
    56  		return 0, fmt.Errorf("cannot copy a directory: %s", src)
    57  	}
    58  
    59  	di, err = os.Stat(dst)
    60  	switch {
    61  	case err != nil && !os.IsNotExist(err):
    62  		return 0, err
    63  	case err == nil:
    64  		if di.IsDir() {
    65  			return 0, fmt.Errorf("cannot overwite a directory: %s", dst)
    66  		}
    67  
    68  		if canOverwrite != nil && !canOverwrite(dst, di) {
    69  			return 0, fmt.Errorf("cannot overwite: %s", dst)
    70  		}
    71  	}
    72  
    73  	s, err := os.Open(src)
    74  	if err != nil {
    75  		return 0, err
    76  	}
    77  
    78  	defer s.Close()
    79  	r := bufio.NewReader(s)
    80  
    81  	d, err := os.Create(dst)
    82  
    83  	defer func() {
    84  		if err := d.Close(); err != nil && rerr == nil {
    85  			rerr = err
    86  			return
    87  		}
    88  
    89  		if err := os.Chmod(dst, si.Mode()); err != nil && rerr == nil {
    90  			rerr = err
    91  			return
    92  		}
    93  
    94  		if err := os.Chtimes(dst, si.ModTime(), si.ModTime()); err != nil && rerr == nil {
    95  			rerr = err
    96  			return
    97  		}
    98  	}()
    99  
   100  	w := bufio.NewWriter(d)
   101  
   102  	defer func() {
   103  		if err := w.Flush(); err != nil && rerr == nil {
   104  			rerr = err
   105  		}
   106  	}()
   107  
   108  	return io.Copy(w, r)
   109  }
   110  
   111  // MustCopyFile is like CopyFile but it executes Fatal(stackTrace, err) if it fails.
   112  func MustCopyFile(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) int64 {
   113  	n, err := CopyFile(dst, src, canOverwrite)
   114  	if err != nil {
   115  		Fatal(stackTrace, err)
   116  	}
   117  
   118  	return n
   119  }
   120  
   121  // CopyDir recursively copies src to dest, preserving permissions and times
   122  // where/when possible. If canOverwrite is not nil, it is consulted whether a
   123  // destination file can be overwritten. If canOverwrite is nil then destination
   124  // is overwritten if permissions allow that, otherwise the function fails.
   125  //
   126  // Src and dst must be in the slash form.
   127  func CopyDir(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (files int, bytes int64, rerr error) {
   128  	dst = filepath.FromSlash(dst)
   129  	src = filepath.FromSlash(src)
   130  	si, err := os.Stat(src)
   131  	if err != nil {
   132  		return 0, 0, err
   133  	}
   134  
   135  	if !si.IsDir() {
   136  		return 0, 0, fmt.Errorf("cannot copy a file: %s", src)
   137  	}
   138  
   139  	return files, bytes, filepath.Walk(src, func(path string, info os.FileInfo, err error) error {
   140  		if err != nil {
   141  			return err
   142  		}
   143  
   144  		rel, err := filepath.Rel(src, path)
   145  		if err != nil {
   146  			return err
   147  		}
   148  
   149  		if info.IsDir() {
   150  			return os.MkdirAll(filepath.Join(dst, rel), 0770)
   151  		}
   152  
   153  		n, err := CopyFile(filepath.Join(dst, rel), path, canOverwrite)
   154  		if err != nil {
   155  			return err
   156  		}
   157  
   158  		files++
   159  		bytes += n
   160  		return nil
   161  	})
   162  }
   163  
   164  // MustCopyDir is like CopyDir, but it executes Fatal(stackTrace, errĂº if it fails.
   165  func MustCopyDir(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) (files int, bytes int64) {
   166  	file, bytes, err := CopyDir(dst, src, canOverwrite)
   167  	if err != nil {
   168  		Fatal(stackTrace, err)
   169  	}
   170  
   171  	return file, bytes
   172  }
   173  
   174  // UntarFile extracts a named tar.gz archive into dst. If canOverwrite is not
   175  // nil, it is consulted whether a destination file can be overwritten. If
   176  // canOverwrite is nil then destination is overwritten if permissions allow
   177  // that, otherwise the function fails.
   178  //
   179  // Src and dst must be in the slash form.
   180  func UntarFile(dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) error {
   181  	f, err := os.Open(filepath.FromSlash(src))
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	defer f.Close()
   187  
   188  	return Untar(dst, bufio.NewReader(f), canOverwrite)
   189  }
   190  
   191  // MustUntarFile is like UntarFile but it executes Fatal(stackTrace, err) if it fails.
   192  func MustUntarFile(stackTrace bool, dst, src string, canOverwrite func(fn string, fi os.FileInfo) bool) {
   193  	if err := UntarFile(dst, src, canOverwrite); err != nil {
   194  		Fatal(stackTrace, err)
   195  	}
   196  }
   197  
   198  // Untar extracts a tar.gz archive into dst. If canOverwrite is not nil, it is
   199  // consulted whether a destination file can be overwritten. If canOverwrite is
   200  // nil then destination is overwritten if permissions allow that, otherwise the
   201  // function fails.
   202  //
   203  // Dst must be in the slash form.
   204  func Untar(dst string, r io.Reader, canOverwrite func(fn string, fi os.FileInfo) bool) error {
   205  	dst = filepath.FromSlash(dst)
   206  	gr, err := gzip.NewReader(r)
   207  	if err != nil {
   208  		return err
   209  	}
   210  
   211  	tr := tar.NewReader(gr)
   212  	for {
   213  		hdr, err := tr.Next()
   214  		if err != nil {
   215  			if err != io.EOF {
   216  				return err
   217  			}
   218  
   219  			return nil
   220  		}
   221  
   222  		switch hdr.Typeflag {
   223  		case tar.TypeDir:
   224  			dir := filepath.Join(dst, hdr.Name)
   225  			if err = os.MkdirAll(dir, 0770); err != nil {
   226  				return err
   227  			}
   228  		case tar.TypeSymlink, tar.TypeXGlobalHeader:
   229  			// skip
   230  		case tar.TypeReg, tar.TypeRegA:
   231  			dir := filepath.Dir(filepath.Join(dst, hdr.Name))
   232  			if _, err := os.Stat(dir); err != nil {
   233  				if !os.IsNotExist(err) {
   234  					return err
   235  				}
   236  
   237  				if err = os.MkdirAll(dir, 0770); err != nil {
   238  					return err
   239  				}
   240  			}
   241  
   242  			fn := filepath.Join(dst, hdr.Name)
   243  			f, err := os.OpenFile(fn, os.O_CREATE|os.O_WRONLY, os.FileMode(hdr.Mode))
   244  			if err != nil {
   245  				return err
   246  			}
   247  
   248  			w := bufio.NewWriter(f)
   249  			if _, err = io.Copy(w, tr); err != nil {
   250  				return err
   251  			}
   252  
   253  			if err := w.Flush(); err != nil {
   254  				return err
   255  			}
   256  
   257  			if err := f.Close(); err != nil {
   258  				return err
   259  			}
   260  
   261  			if err := os.Chtimes(fn, hdr.AccessTime, hdr.ModTime); err != nil {
   262  				return err
   263  			}
   264  		default:
   265  			return fmt.Errorf("unexpected tar header typeflag %#02x", hdr.Typeflag)
   266  		}
   267  	}
   268  
   269  }
   270  
   271  // MustUntar is like Untar but it executes Fatal(stackTrace, err) if it fails.
   272  func MustUntar(stackTrace bool, dst string, r io.Reader, canOverwrite func(fn string, fi os.FileInfo) bool) {
   273  	if err := Untar(dst, r, canOverwrite); err != nil {
   274  		Fatal(stackTrace, err)
   275  	}
   276  }
   277  
   278  // Fatalf prints a formatted message to os.Stderr and performs os.Exit(1). A
   279  // stack trace is added if stackTrace is true.
   280  func Fatalf(stackTrace bool, s string, args ...interface{}) {
   281  	if stackTrace {
   282  		fmt.Fprintf(os.Stderr, "%s\n", debug.Stack())
   283  	}
   284  	fmt.Fprintln(os.Stderr, strings.TrimSpace(fmt.Sprintf(s, args...)))
   285  	os.Exit(1)
   286  }
   287  
   288  // Fatal prints its argumenst to os.Stderr and performs os.Exit(1). A
   289  // stack trace is added if stackTrace is true.
   290  func Fatal(stackTrace bool, args ...interface{}) {
   291  	if stackTrace {
   292  		fmt.Fprintf(os.Stderr, "%s\n", debug.Stack())
   293  	}
   294  	fmt.Fprintln(os.Stderr, strings.TrimSpace(fmt.Sprint(args...)))
   295  	os.Exit(1)
   296  }
   297  
   298  // Mkdirs will create all paths. Paths must be in slash form.
   299  func Mkdirs(paths ...string) error {
   300  	for _, path := range paths {
   301  		path = filepath.FromSlash(path)
   302  		if err := os.MkdirAll(path, 0770); err != nil {
   303  			return err
   304  		}
   305  	}
   306  
   307  	return nil
   308  }
   309  
   310  // MustMkdirs is like Mkdir but if executes Fatal(stackTrace, err) if it fails.
   311  func MustMkdirs(stackTrace bool, paths ...string) {
   312  	if err := Mkdirs(paths...); err != nil {
   313  		Fatal(stackTrace, err)
   314  	}
   315  }
   316  
   317  // InDir executes f in dir. Dir must be in slash form.
   318  func InDir(dir string, f func() error) (err error) {
   319  	var cwd string
   320  	if cwd, err = os.Getwd(); err != nil {
   321  		return err
   322  	}
   323  
   324  	defer func() {
   325  		if err2 := os.Chdir(cwd); err2 != nil {
   326  			err = err2
   327  		}
   328  	}()
   329  
   330  	if err = os.Chdir(filepath.FromSlash(dir)); err != nil {
   331  		return err
   332  	}
   333  
   334  	return f()
   335  }
   336  
   337  // MustInDir is like InDir but it executes Fatal(stackTrace, err) if it fails.
   338  func MustInDir(stackTrace bool, dir string, f func() error) {
   339  	if err := InDir(dir, f); err != nil {
   340  		Fatal(stackTrace, err)
   341  	}
   342  }
   343  
   344  type echoWriter struct {
   345  	w bytes.Buffer
   346  }
   347  
   348  func (w *echoWriter) Write(b []byte) (int, error) {
   349  	os.Stdout.Write(b)
   350  	return w.w.Write(b)
   351  }
   352  
   353  // Shell echoes and executes cmd with args and returns the combined output if the command.
   354  func Shell(cmd string, args ...string) ([]byte, error) {
   355  	cmd, err := exec.LookPath(cmd)
   356  	if err != nil {
   357  		return nil, err
   358  	}
   359  
   360  	wd, err := AbsCwd()
   361  	if err != nil {
   362  		return nil, err
   363  	}
   364  
   365  	fmt.Printf("execute %s %q in %s\n", cmd, args, wd)
   366  	var b echoWriter
   367  	c := exec.Command(cmd, args...)
   368  	c.Stdout = &b
   369  	c.Stderr = &b
   370  	err = c.Run()
   371  	return b.w.Bytes(), err
   372  }
   373  
   374  // MustShell is like Shell but it executes Fatal(stackTrace, err) if it fails.
   375  func MustShell(stackTrace bool, cmd string, args ...string) []byte {
   376  	b, err := Shell(cmd, args...)
   377  	if err != nil {
   378  		Fatalf(stackTrace, "%v %s\noutput: %s\nerr: %s", cmd, args, b, err)
   379  	}
   380  
   381  	return b
   382  }
   383  
   384  // Compile executes Shell with cmd set to "ccgo".
   385  func Compile(args ...string) ([]byte, error) { return Shell("ccgo", args...) }
   386  
   387  // MustCompile is like Compile but if executes Fatal(stackTrace, err) if it fails.
   388  func MustCompile(stackTrace bool, args ...string) []byte {
   389  	return MustShell(stackTrace, "ccgo", args...)
   390  }
   391  
   392  // Run is like Compile, but executes in-process.
   393  func Run(args ...string) ([]byte, error) {
   394  	var b bytes.Buffer
   395  	t := NewTask(append([]string{"ccgo"}, args...), &b, &b)
   396  	err := t.Main()
   397  	return b.Bytes(), err
   398  }
   399  
   400  // MustRun is like Run but if executes Fatal(stackTrace, err) if it fails.
   401  func MustRun(stackTrace bool, args ...string) []byte {
   402  	var b bytes.Buffer
   403  	args = append([]string{"ccgo"}, args...)
   404  	t := NewTask(args, &b, &b)
   405  	if err := t.Main(); err != nil {
   406  		Fatalf(stackTrace, "%v\noutput: %s\nerr: %s", args, b.Bytes(), err)
   407  	}
   408  
   409  	return b.Bytes()
   410  }
   411  
   412  // AbsCwd returns the absolute working directory.
   413  func AbsCwd() (string, error) {
   414  	wd, err := os.Getwd()
   415  	if err != nil {
   416  		return "", err
   417  	}
   418  
   419  	if wd, err = filepath.Abs(wd); err != nil {
   420  		return "", err
   421  	}
   422  
   423  	return wd, nil
   424  }
   425  
   426  // MustAbsCwd is like AbsCwd but executes Fatal(stackTrace, err) if it fails.
   427  func MustAbsCwd(stackTrace bool) string {
   428  	s, err := AbsCwd()
   429  	if err != nil {
   430  		Fatal(stackTrace, err)
   431  	}
   432  
   433  	return s
   434  }
   435  
   436  // Env returns the value of environmental variable key of dflt otherwise.
   437  func Env(key, dflt string) string {
   438  	if s := os.Getenv(key); s != "" {
   439  		return s
   440  	}
   441  
   442  	return dflt
   443  }
   444  
   445  // MustTempDir is like ioutil.TempDir but executes Fatal(stackTrace, err) if it
   446  // fails. The returned path is absolute.
   447  func MustTempDir(stackTrace bool, dir, name string) string {
   448  	s, err := ioutil.TempDir(dir, name)
   449  	if err != nil {
   450  		Fatal(stackTrace, err)
   451  	}
   452  
   453  	if s, err = filepath.Abs(s); err != nil {
   454  		Fatal(stackTrace, err)
   455  	}
   456  
   457  	return s
   458  }