-
Notifications
You must be signed in to change notification settings - Fork 0
/
compile.go
207 lines (190 loc) · 5.43 KB
/
compile.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
// dcc - dependency-driven C/C++ compiler front end
//
// Copyright © A.Newman 2015.
//
// This source code is released under version 2 of the GNU Public License.
// See the file LICENSE for details.
//
package main
import (
"fmt"
"io"
"log"
"os"
"path/filepath"
"strings"
"github.com/atrn/par"
)
// CompileAll compiles all of the given sources using the supplied
// options and returns true if all were sucessfully compiled.
//
// CompileAll compiles the sources in parallel using, up to, NJOBS
// parallel compilations.
//
func CompileAll(sources []string, options *Options, objdir string) (ok bool) {
// Assume success.
//
ok = true
// The standard error output of each compile is routed via an
// OutputMux which ensures output is not interleaved. We don't
// bother with standard output yet but probably should.
//
mux := NewOutputMux(os.Stderr)
defer mux.Close()
// Our process structure is a simple fan-out that feeds the
// names of the source files to a number of "workers" for
// compilation.
//
// Source file names are fed to a 'filenames' channel which
// is read by N worker tasks. Each worker reads a filename
// from the channel, compiles the source file and sends an
// error value result for compilation on the errors channel.
// An error 'collecter' reads and reports any non-nil errors.
//
// Synchronization is done by par.DO and par.FOR.
//
filenames := make(chan string, len(sources))
errs := make(chan error, len(sources))
par.DO(
func() {
for _, filename := range sources {
filenames <- filename
}
close(filenames)
},
func() {
par.FOR(0, NumJobs, func(int) {
for filename := range filenames {
ofile := ObjectFilename(filename, objdir)
stderr := mux.NewWriter()
errs <- Compile(filename, options, ofile, stderr, objdir)
stderr.Close()
}
})
close(errs)
},
func() {
for err := range errs {
if err != nil {
log.Print(err)
ok = false
}
}
},
)
return
}
// Compile a single source file, if required. Returns a non-nil error if compilation fails.
//
func Compile(filename string, options *Options, ofile string, stderr io.WriteCloser, objdir string) error {
if ofile == "" {
ofile = ObjectFilename(filename, objdir)
}
ofileDir := filepath.Dir(ofile)
if err := Mkdir(ofileDir); err != nil {
return err
}
depsFilename := DepsFilename(ofile)
depsFileDir := filepath.Dir(depsFilename)
if depsFileDir != ofileDir {
if err := Mkdir(depsFileDir); err != nil {
return err
}
}
if !IgnoreDependencies {
sourceInfo, err := Stat(filename)
if err != nil {
return err
}
target, deps, err := ActualCompiler.ReadDependencies(depsFilename)
if os.IsNotExist(err) {
// ignore
} else if err != nil {
return err
} else if filepath.Base(target) != filepath.Base(ofile) {
log.Printf("WARNING: got dependency target %q for object file %q", target, ofile)
}
if err == nil {
uptodate, err := IsUptoDate(ofile, deps, sourceInfo, options)
if err != nil || uptodate {
return err
}
}
}
// Compile the file.
//
ClearCachedStat(ofile) // it will change
// Do we need to output a command line? We don't output
// the raw command as we'll add options to it. So we
// prepare something similar for the user.
//
if !Quiet {
var displayed []string
if Verbose {
displayed = append(displayed, ActualCompiler.Name())
displayed = append(displayed, options.Values...)
displayed = append(displayed, filename)
if objdir != "" {
displayed = append(displayed, "-o", ofile)
}
} else {
displayed = append(displayed, ActualCompiler.Name())
displayed = append(displayed, filename)
}
fmt.Fprintln(os.Stdout, strings.Join(displayed, " "))
}
return ActualCompiler.Compile(filename, ofile, depsFilename, options.Values, stderr)
}
// IsUptoDate determines if a given target file is up to date with
// respect to the input files, and compiler options, that led any
// previous generation of the target file.
//
func IsUptoDate(target string, deps []string, sourceInfo os.FileInfo, options *Options) (bool, error) {
result := func(current bool, err error, caption string) (bool, error) {
if Debug {
currency := "out of"
if current {
currency = "up to"
}
log.Printf(
"DEPS: %q (%q) -> (%s date, %v) - %s",
sourceInfo.Name(),
target,
currency,
err,
caption,
)
}
return current, err
}
outOfDate := func(caption string) (bool, error) {
return result(false, nil, caption)
}
badstat := func(filename string, err error) (bool, error) {
return result(false, err, fmt.Sprintf("%q: %s", filename, err.Error()))
}
targetInfo, err := Stat(target)
switch {
case os.IsNotExist(err):
return outOfDate("target does not exist")
case err != nil:
return badstat(target, err)
case FileIsNewer(sourceInfo, targetInfo):
return outOfDate("source newer than target")
case options.ModTime().After(targetInfo.ModTime()):
return outOfDate("compiler options file newer than target")
case FileIsNewer(options.FileInfo(), targetInfo):
return outOfDate("compiler options file newer than target")
}
for _, filename := range deps {
switch depInfo, err := Stat(filename); {
case os.IsNotExist(err):
return outOfDate(fmt.Sprintf("%q: dependent file does not exist", filename))
case err != nil:
return badstat(filename, err)
case FileIsNewer(depInfo, targetInfo):
return outOfDate(fmt.Sprintf("%q: dependency newer than target", filename))
}
}
return result(true, nil, "target up to date")
}