-
-
Notifications
You must be signed in to change notification settings - Fork 12
/
Copy pathproject.go
358 lines (312 loc) · 13.7 KB
/
project.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
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
// This file is part of Arduino Lint.
//
// Copyright 2020 ARDUINO SA (http://www.arduino.cc/)
//
// This software is released under the GNU General Public License, either
// version 3 of the License, or (at your option) any later version.
// This license covers the main part of Arduino Lint.
// The terms of this license can be found at:
// https://www.gnu.org/licenses/gpl-3.0.en.html
//
// You can be released from the requirements of the above licenses by purchasing
// a commercial license. Buying such a license is mandatory if you want to
// modify or otherwise use the software for commercial activities involving the
// Arduino software without disclosing the source code of your own applications.
// To purchase a commercial license, send an email to [email protected].
// Package project finds and classifies Arduino projects.
package project
import (
"fmt"
"os"
"github.com/arduino/arduino-lint/internal/configuration"
"github.com/arduino/arduino-lint/internal/project/library"
"github.com/arduino/arduino-lint/internal/project/packageindex"
"github.com/arduino/arduino-lint/internal/project/platform"
"github.com/arduino/arduino-lint/internal/project/projecttype"
"github.com/arduino/arduino-lint/internal/project/sketch"
"github.com/arduino/go-paths-helper"
"github.com/sirupsen/logrus"
)
// Type is the type for project definitions.
type Type struct {
Path *paths.Path
ProjectType projecttype.Type
SuperprojectType projecttype.Type
}
// FindProjects searches the target path configured by the user for projects of the type configured by the user as well as the subprojects of those project.
// It returns a slice containing the definitions of each found project.
func FindProjects() ([]Type, error) {
var foundProjects []Type
for _, targetPath := range configuration.TargetPaths() {
foundProjectsForTargetPath, err := findProjects(targetPath)
if err != nil {
return nil, err
}
foundProjects = append(foundProjects, foundProjectsForTargetPath...)
}
return foundProjects, nil
}
// findProjects handles the recursion for FindProjects().
func findProjects(targetPath *paths.Path) ([]Type, error) {
var foundParentProjects []Type
// If targetPath is a file, targetPath itself is the project, so it's only necessary to determine/verify the type.
if targetPath.IsNotDir() {
logrus.Debug("Projects path is file")
var isProject bool
var projectType projecttype.Type
if configuration.SuperprojectTypeFilter() == projecttype.All {
// Project type detection is required.
// The filename provides additional information about the project type. So rather than using isProject(), which doesn't make use this information, use a specialized function that does.
isProject, projectType = isProjectIndicatorFile(targetPath, configuration.SuperprojectTypeFilter())
} else {
// Project was explicitly defined by user.
isProject = true
projectType = configuration.SuperprojectTypeFilter()
}
if isProject {
var projectPath *paths.Path
if projectType == projecttype.PackageIndex {
projectPath = targetPath // With package indexes the project is the file. When the user has provided the full path to the project, that information should be preserved.
} else {
projectPath = targetPath.Parent()
}
foundProject := Type{
Path: projectPath,
ProjectType: projectType,
SuperprojectType: projectType,
}
foundParentProjects = append(foundParentProjects, foundProject)
}
} else {
if configuration.SuperprojectTypeFilter() == projecttype.All || configuration.Recursive() {
// Project discovery and/or type detection is required.
foundParentProjects = findProjectsUnderPath(targetPath, configuration.SuperprojectTypeFilter(), configuration.Recursive(), 0)
} else {
// Project was explicitly defined by user.
foundParentProjects = append(foundParentProjects,
Type{
Path: targetPath,
ProjectType: configuration.SuperprojectTypeFilter(),
SuperprojectType: configuration.SuperprojectTypeFilter(),
},
)
}
}
// Discover subprojects of all found projects.
var foundProjects []Type
for _, foundParentProject := range foundParentProjects {
foundProjects = append(foundProjects, foundParentProject)
foundProjects = append(foundProjects, findSubprojects(foundParentProject, foundParentProject.ProjectType)...)
}
if foundProjects == nil {
return nil, fmt.Errorf("No projects found with project path %s", targetPath)
}
return foundProjects, nil
}
// findProjectsUnderPath finds projects of the given type under the given path. It returns a slice containing the definitions of all found projects.
func findProjectsUnderPath(targetPath *paths.Path, projectTypeFilter projecttype.Type, recursive bool, symlinkDepth int) []Type {
var foundProjects []Type
isProject, foundProjectType := isProject(targetPath, projectTypeFilter)
if isProject {
logrus.Tracef("%s is %s", targetPath, foundProjectType)
foundProject := Type{
Path: targetPath,
ProjectType: foundProjectType,
// findSubprojects() will overwrite this with the correct value when the project is a subproject.
SuperprojectType: foundProjectType,
}
foundProjects = append(foundProjects, foundProject)
// Don't search recursively past a project.
return foundProjects
}
if recursive {
if symlinkDepth > 10 {
panic(fmt.Sprintf("symlink depth exceeded maximum while finding projects under %s", targetPath))
}
// targetPath was not a project, so search the subfolders.
directoryListing, _ := targetPath.ReadDir()
directoryListing.FilterDirs()
for _, potentialProjectDirectory := range directoryListing {
// It is possible for a combination of symlinks to parent paths to cause project discovery to get stuck in
// an endless loop of recursion. This is avoided by keeping count of the depth of symlinks and discontinuing
// recursion when it exceeds reason.
pathStat, err := potentialProjectDirectory.Lstat()
if err != nil {
panic(err)
}
depthDelta := 0
if pathStat.Mode()&os.ModeSymlink != 0 {
depthDelta = 1
}
foundProjects = append(foundProjects, findProjectsUnderPath(potentialProjectDirectory, projectTypeFilter, recursive, symlinkDepth+depthDelta)...)
}
}
return foundProjects
}
// findSubprojects finds subprojects of the given project.
// For example, the subprojects of a library are its example sketches.
func findSubprojects(superproject Type, apexSuperprojectType projecttype.Type) []Type {
subprojectsFolderNames := []string{}
var subProjectType projecttype.Type
var searchPathsRecursively bool
// Determine possible subproject paths
switch superproject.ProjectType {
case projecttype.Sketch:
// Sketches don't have subprojects
return nil
case projecttype.Library:
subprojectsFolderNames = append(subprojectsFolderNames, library.ExamplesFolderSupportedNames()...)
subProjectType = projecttype.Sketch
searchPathsRecursively = true // Examples sketches can be under nested subfolders
case projecttype.Platform:
subprojectsFolderNames = append(subprojectsFolderNames, platform.BundledLibrariesFolderNames()...)
subProjectType = projecttype.Library
searchPathsRecursively = false // Bundled libraries must be in the root of the libraries folder
case projecttype.PackageIndex:
// Platform indexes don't have subprojects
return nil
default:
panic(fmt.Sprintf("Subproject discovery not configured for project type: %s", superproject.ProjectType))
}
// Search the subproject paths for projects.
var immediateSubprojects []Type
for _, subprojectsFolderName := range subprojectsFolderNames {
subprojectsPath := superproject.Path.Join(subprojectsFolderName)
if subprojectsPath.Exist() && subprojectsPath.IsDir() {
directoryListing, err := subprojectsPath.ReadDir()
if err != nil {
panic(err)
}
directoryListing.FilterDirs()
for _, subprojectPath := range directoryListing {
immediateSubprojects = append(immediateSubprojects, findProjectsUnderPath(subprojectPath, subProjectType, searchPathsRecursively, 0)...)
}
}
}
var allSubprojects []Type
// Subprojects may have their own subprojects.
for _, immediateSubproject := range immediateSubprojects {
// Subprojects at all levels should have SuperprojectType set to the top level superproject's type, not the immediate parent's type.
immediateSubproject.SuperprojectType = apexSuperprojectType
// Each parent project should be followed in the list by its subprojects.
allSubprojects = append(allSubprojects, immediateSubproject)
allSubprojects = append(allSubprojects, findSubprojects(immediateSubproject, apexSuperprojectType)...)
}
return allSubprojects
}
// isProject determines if a path contains an Arduino project, and if so which type.
func isProject(potentialProjectPath *paths.Path, projectTypeFilter projecttype.Type) (bool, projecttype.Type) {
logrus.Tracef("Checking if %s is %s", potentialProjectPath, projectTypeFilter)
projectType := projecttype.Not
if projectTypeFilter.Matches(projecttype.Sketch) && isSketch(potentialProjectPath) {
projectType = projecttype.Sketch
} else if projectTypeFilter.Matches(projecttype.Library) && isLibrary(potentialProjectPath) {
projectType = projecttype.Library
} else if projectTypeFilter.Matches(projecttype.Platform) && isPlatform(potentialProjectPath) {
projectType = projecttype.Platform
} else if projectTypeFilter.Matches(projecttype.PackageIndex) && isPackageIndex(potentialProjectPath) {
projectType = projecttype.PackageIndex
}
if projectType == projecttype.Not {
return false, projectType
}
logrus.Tracef("%s is %s", potentialProjectPath, projectType)
return true, projectType
}
// isProjectIndicatorFile determines if a file is the indicator file for an Arduino project, and if so which type.
func isProjectIndicatorFile(potentialProjectFilePath *paths.Path, projectTypeFilter projecttype.Type) (bool, projecttype.Type) {
logrus.Tracef("Checking if %s is %s indicator file", potentialProjectFilePath, projectTypeFilter)
projectType := projecttype.Not
if projectTypeFilter.Matches(projecttype.Sketch) && isSketchIndicatorFile(potentialProjectFilePath) {
projectType = projecttype.Sketch
} else if projectTypeFilter.Matches(projecttype.Library) && isLibraryIndicatorFile(potentialProjectFilePath) {
projectType = projecttype.Library
} else if projectTypeFilter.Matches(projecttype.Platform) && isPlatformIndicatorFile(potentialProjectFilePath) {
projectType = projecttype.Platform
} else if projectTypeFilter.Matches(projecttype.PackageIndex) && isPackageIndexIndicatorFile(potentialProjectFilePath) {
projectType = projecttype.PackageIndex
}
if projectType == projecttype.Not {
logrus.Tracef("%s is not indicator file", potentialProjectFilePath)
return false, projectType
}
logrus.Tracef("%s is %s indicator file", potentialProjectFilePath, projectType)
return true, projectType
}
// isSketch determines whether a path is an Arduino sketch.
// Note: this intentionally does not determine the validity of the sketch, only that the developer's intent was for it to be a sketch.
func isSketch(potentialProjectPath *paths.Path) bool {
directoryListing, _ := potentialProjectPath.ReadDir()
directoryListing.FilterOutDirs()
for _, potentialSketchFile := range directoryListing {
if isSketchIndicatorFile(potentialSketchFile) {
return true
}
}
// No file was found with a valid main sketch file extension.
return false
}
func isSketchIndicatorFile(filePath *paths.Path) bool {
return sketch.HasMainFileValidExtension(filePath)
}
// isLibrary determines if a path is an Arduino library.
// Note: this intentionally does not determine the validity of the library, only that the developer's intent was for it to be a library.
func isLibrary(potentialProjectPath *paths.Path) bool {
// Arduino libraries will always have one of the following files in its root folder:
// - a library.properties metadata file
// - a header file
directoryListing, _ := potentialProjectPath.ReadDir()
directoryListing.FilterOutDirs()
for _, potentialLibraryFile := range directoryListing {
if isLibraryIndicatorFile(potentialLibraryFile) {
return true
}
}
// None of the files required for a valid Arduino library were found.
return false
}
func isLibraryIndicatorFile(filePath *paths.Path) bool {
if library.IsMetadataFile(filePath) {
return true
}
if library.HasHeaderFileValidExtension(filePath) {
return true
}
return false
}
// isPlatform determines if a path is an Arduino boards platform.
// Note: this intentionally does not determine the validity of the platform, only that the developer's intent was for it to be a platform.
func isPlatform(potentialProjectPath *paths.Path) bool {
directoryListing, _ := potentialProjectPath.ReadDir()
directoryListing.FilterOutDirs()
for _, potentialPlatformFile := range directoryListing {
if isStrictPlatformIndicatorFile(potentialPlatformFile) {
return true
}
}
return false
}
func isPlatformIndicatorFile(filePath *paths.Path) bool {
return platform.IsConfigurationFile(filePath)
}
func isStrictPlatformIndicatorFile(filePath *paths.Path) bool {
return platform.IsRequiredConfigurationFile(filePath)
}
// isPackageIndex determines if a path contains an Arduino package index.
// Note: this intentionally does not determine the validity of the package index, only that the developer's intent was for it to be a package index.
func isPackageIndex(potentialProjectPath *paths.Path) bool {
directoryListing, _ := potentialProjectPath.ReadDir()
directoryListing.FilterOutDirs()
for _, potentialPackageIndexFile := range directoryListing {
if isStrictPackageIndexIndicatorFile(potentialPackageIndexFile) {
return true
}
}
return false
}
func isPackageIndexIndicatorFile(filePath *paths.Path) bool {
return packageindex.HasValidExtension(filePath)
}
func isStrictPackageIndexIndicatorFile(filePath *paths.Path) bool {
return packageindex.HasValidFilename(filePath, true)
}