source: code/trunk/vendor/modernc.org/ccgo/v3/lib/util.go@ 823

Last change on this file since 823 was 822, checked in by yakumo.izuru, 22 months ago

Prefer immortal.run over runit and rc.d, use vendored modules
for convenience.

Signed-off-by: Izuru Yakumo <yakumo.izuru@…>

File size: 10.9 KB
Line 
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
7package ccgo // import "modernc.org/ccgo/v3/lib"
8
9import (
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.
30func 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.
112func 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.
127func 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.
165func 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.
180func 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.
192func 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.
204func 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.
272func 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.
280func 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.
290func 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.
299func 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.
311func 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.
318func 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.
338func MustInDir(stackTrace bool, dir string, f func() error) {
339 if err := InDir(dir, f); err != nil {
340 Fatal(stackTrace, err)
341 }
342}
343
344type echoWriter struct {
345 w bytes.Buffer
346}
347
348func (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.
354func 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.
375func 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".
385func Compile(args ...string) ([]byte, error) { return Shell("ccgo", args...) }
386
387// MustCompile is like Compile but if executes Fatal(stackTrace, err) if it fails.
388func MustCompile(stackTrace bool, args ...string) []byte {
389 return MustShell(stackTrace, "ccgo", args...)
390}
391
392// Run is like Compile, but executes in-process.
393func 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.
401func 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.
413func 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.
427func 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.
437func 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.
447func 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}
Note: See TracBrowser for help on using the repository browser.