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 | }
|
---|