dependency.readBuildTags   F
last analyzed

Complexity

Conditions 15

Size

Total Lines 47
Code Lines 27

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 27
nop 1
dl 0
loc 47
rs 2.9998
c 0
b 0
f 0

How to fix   Complexity   

Complexity

Complex classes like dependency.readBuildTags often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
package dependency
2
3
import (
4
	"bytes"
5
	"io"
6
	"os"
7
	"path/filepath"
8
	"strings"
9
	"text/scanner"
10
11
	"github.com/Masterminds/glide/msg"
12
	"github.com/Masterminds/glide/util"
13
)
14
15
var osList []string
16
var archList []string
17
18
func init() {
19
	// The supported systems are listed in
20
	// https://github.com/golang/go/blob/master/src/go/build/syslist.go
21
	// The lists are not exported so we need to duplicate them here.
22
	osListString := "android darwin dragonfly freebsd linux nacl netbsd openbsd plan9 solaris windows"
23
	osList = strings.Split(osListString, " ")
24
25
	archListString := "386 amd64 amd64p32 arm armbe arm64 arm64be ppc64 ppc64le mips mipsle mips64 mips64le mips64p32 mips64p32le ppc s390 s390x sparc sparc64"
26
	archList = strings.Split(archListString, " ")
27
}
28
29
// IterativeScan attempts to obtain a list of imported dependencies from a
30
// package. This scanning is different from ImportDir as part of the go/build
31
// package. It looks over different permutations of the supported OS/Arch to
32
// try and find all imports. This is different from setting UseAllFiles to
33
// true on the build Context. It scopes down to just the supported OS/Arch.
34
//
35
// Note, there are cases where multiple packages are in the same directory. This
36
// usually happens with an example that has a main package and a +build tag
37
// of ignore. This is a bit of a hack. It causes UseAllFiles to have errors.
38
func IterativeScan(path string) ([]string, []string, error) {
39
40
	// TODO(mattfarina): Add support for release tags.
41
42
	tgs, _ := readBuildTags(path)
43
	// Handle the case of scanning with no tags
44
	tgs = append(tgs, "")
45
46
	var pkgs []string
47
	var testPkgs []string
48
	for _, tt := range tgs {
49
50
		// split the tag combination to look at permutations.
51
		ts := strings.Split(tt, ",")
52
		var ttgs []string
53
		var arch string
54
		var ops string
55
		for _, ttt := range ts {
56
			dirty := false
57
			if strings.HasPrefix(ttt, "!") {
58
				dirty = true
59
				ttt = strings.TrimPrefix(ttt, "!")
60
			}
61
			if isSupportedOs(ttt) {
62
				if dirty {
63
					ops = getOsValue(ttt)
64
				} else {
65
					ops = ttt
66
				}
67
			} else if isSupportedArch(ttt) {
68
				if dirty {
69
					arch = getArchValue(ttt)
70
				} else {
71
					arch = ttt
72
				}
73
			} else {
74
				if !dirty {
75
					ttgs = append(ttgs, ttt)
76
				}
77
			}
78
		}
79
80
		// Handle the case where there are no tags but we need to iterate
81
		// on something.
82
		if len(ttgs) == 0 {
83
			ttgs = append(ttgs, "")
84
		}
85
86
		b, err := util.GetBuildContext()
87
		if err != nil {
88
			return []string{}, []string{}, err
89
		}
90
91
		// Make sure use all files is off
92
		b.UseAllFiles = false
93
94
		// Set the OS and Arch for this pass
95
		b.GOARCH = arch
96
		b.GOOS = ops
97
		b.BuildTags = ttgs
98
		msg.Debug("Scanning with Arch(%s), OS(%s), and Build Tags(%v)", arch, ops, ttgs)
99
100
		pk, err := b.ImportDir(path, 0)
101
102
		// If there are no buildable souce with this permutation we skip it.
103
		if err != nil && strings.HasPrefix(err.Error(), "no buildable Go source files in") {
104
			continue
105
		} else if err != nil && strings.HasPrefix(err.Error(), "found packages ") {
106
			// A permutation may cause multiple packages to appear. For example,
107
			// an example file with an ignore build tag. If this happens we
108
			// ignore it.
109
			// TODO(mattfarina): Find a better way.
110
			msg.Debug("Found multiple packages while scanning %s: %s", path, err)
111
			continue
112
		} else if err != nil {
113
			msg.Debug("Problem parsing package at %s for %s %s", path, ops, arch)
114
			return []string{}, []string{}, err
115
		}
116
117
		for _, dep := range pk.Imports {
118
			found := false
119
			for _, p := range pkgs {
120
				if p == dep {
121
					found = true
122
				}
123
			}
124
			if !found {
125
				pkgs = append(pkgs, dep)
126
			}
127
		}
128
129
		for _, dep := range pk.TestImports {
130
			found := false
131
			for _, p := range pkgs {
132
				if p == dep {
133
					found = true
134
				}
135
			}
136
			if !found {
137
				testPkgs = append(testPkgs, dep)
138
			}
139
		}
140
141
		for _, dep := range pk.XTestImports {
142
			found := false
143
			for _, p := range pkgs {
144
				if p == dep {
145
					found = true
146
				}
147
			}
148
			if !found {
149
				testPkgs = append(testPkgs, dep)
150
			}
151
		}
152
	}
153
154
	return pkgs, testPkgs, nil
155
}
156
157
func readBuildTags(p string) ([]string, error) {
158
	_, err := os.Stat(p)
159
	if err != nil {
160
		return []string{}, err
161
	}
162
163
	d, err := os.Open(p)
164
	if err != nil {
165
		return []string{}, err
166
	}
167
168
	objects, err := d.Readdir(-1)
169
	if err != nil {
170
		return []string{}, err
171
	}
172
173
	var tags []string
174
	for _, obj := range objects {
175
176
		// only process Go files
177
		if strings.HasSuffix(obj.Name(), ".go") {
178
			fp := filepath.Join(p, obj.Name())
179
180
			co, err := readGoContents(fp)
181
			if err != nil {
182
				return []string{}, err
183
			}
184
185
			// Only look at places where we had a code comment.
186
			if len(co) > 0 {
187
				t := findTags(co)
188
				for _, tg := range t {
189
					found := false
190
					for _, tt := range tags {
191
						if tt == tg {
192
							found = true
193
						}
194
					}
195
					if !found {
196
						tags = append(tags, tg)
197
					}
198
				}
199
			}
200
		}
201
	}
202
203
	return tags, nil
204
}
205
206
// Read contents of a Go file up to the package declaration. This can be used
207
// to find the the build tags.
208
func readGoContents(fp string) ([]byte, error) {
209
	f, err := os.Open(fp)
210
	defer f.Close()
211
	if err != nil {
212
		return []byte{}, err
213
	}
214
215
	var s scanner.Scanner
216
	s.Init(f)
217
	var tok rune
218
	var pos scanner.Position
219
	for tok != scanner.EOF {
220
		tok = s.Scan()
221
222
		// Getting the token text will skip comments by default.
223
		tt := s.TokenText()
224
		// build tags will not be after the package declaration.
225
		if tt == "package" {
226
			pos = s.Position
227
			break
228
		}
229
	}
230
231
	buf := bytes.NewBufferString("")
232
	f.Seek(0, 0)
233
	_, err = io.CopyN(buf, f, int64(pos.Offset))
234
	if err != nil {
235
		return []byte{}, err
236
	}
237
238
	return buf.Bytes(), nil
239
}
240
241
// From a byte slice of a Go file find the tags.
242
func findTags(co []byte) []string {
243
	p := co
244
	var tgs []string
245
	for len(p) > 0 {
246
		line := p
247
		if i := bytes.IndexByte(line, '\n'); i >= 0 {
248
			line, p = line[:i], p[i+1:]
249
		} else {
250
			p = p[len(p):]
251
		}
252
		line = bytes.TrimSpace(line)
253
		// Only look at comment lines that are well formed in the Go style
254
		if bytes.HasPrefix(line, []byte("//")) {
255
			line = bytes.TrimSpace(line[len([]byte("//")):])
256
			if len(line) > 0 && line[0] == '+' {
257
				f := strings.Fields(string(line))
258
259
				// We've found a +build tag line.
260
				if f[0] == "+build" {
261
					for _, tg := range f[1:] {
262
						tgs = append(tgs, tg)
263
					}
264
				}
265
			}
266
		}
267
	}
268
269
	return tgs
270
}
271
272
// Get an OS value that's not the one passed in.
273
func getOsValue(n string) string {
274
	for _, o := range osList {
275
		if o != n {
276
			return o
277
		}
278
	}
279
280
	return n
281
}
282
283
func isSupportedOs(n string) bool {
284
	for _, o := range osList {
285
		if o == n {
286
			return true
287
		}
288
	}
289
290
	return false
291
}
292
293
// Get an Arch value that's not the one passed in.
294
func getArchValue(n string) string {
295
	for _, o := range archList {
296
		if o != n {
297
			return o
298
		}
299
	}
300
301
	return n
302
}
303
304
func isSupportedArch(n string) bool {
305
	for _, o := range archList {
306
		if o == n {
307
			return true
308
		}
309
	}
310
311
	return false
312
}
313