1// Copyright 2017 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//     http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package blueprint
16
17import (
18	"fmt"
19	"sort"
20	"strings"
21)
22
23// This file exposes the logic of locating a module via a query string, to enable
24// other projects to override it if desired.
25// The default name resolution implementation, SimpleNameInterface,
26// just treats the query string as a module name, and does a simple map lookup.
27
28// A ModuleGroup just points to a moduleGroup to allow external packages to refer
29// to a moduleGroup but not use it
30type ModuleGroup struct {
31	*moduleGroup
32}
33
34func (h *ModuleGroup) String() string {
35	return h.moduleGroup.name
36}
37
38// The Namespace interface is just a marker interface for usage by the NameInterface,
39// to allow a NameInterface to specify that a certain parameter should be a Namespace.
40// In practice, a specific NameInterface will expect to only give and receive structs of
41// the same concrete type, but because Go doesn't support generics, we use a marker interface
42// for a little bit of clarity, and expect implementers to do typecasting instead.
43type Namespace interface {
44	namespace(Namespace)
45}
46type NamespaceMarker struct {
47}
48
49func (m *NamespaceMarker) namespace(Namespace) {
50}
51
52// A NameInterface tells how to locate modules by name.
53// There should only be one name interface per Context, but potentially many namespaces
54type NameInterface interface {
55	// Gets called when a new module is created
56	NewModule(ctx NamespaceContext, group ModuleGroup, module Module) (namespace Namespace, err []error)
57
58	// Gets called when a module was pruned from the build tree by SourceRootDirs
59	NewSkippedModule(ctx NamespaceContext, name string, skipInfo SkippedModuleInfo)
60
61	// Finds the module with the given name
62	ModuleFromName(moduleName string, namespace Namespace) (group ModuleGroup, found bool)
63
64	// Finds if the module with the given name was skipped
65	SkippedModuleFromName(moduleName string, namespace Namespace) (skipInfos []SkippedModuleInfo, skipped bool)
66
67	// Returns an error indicating that the given module could not be found.
68	// The error contains some diagnostic information about where the dependency can be found.
69	MissingDependencyError(depender string, dependerNamespace Namespace, depName string, guess []string) (err error)
70
71	// Rename
72	Rename(oldName string, newName string, namespace Namespace) []error
73
74	// Returns all modules in a deterministic order.
75	AllModules() []ModuleGroup
76
77	// gets the namespace for a given path
78	GetNamespace(ctx NamespaceContext) (namespace Namespace)
79
80	// returns a deterministic, unique, arbitrary string for the given name in the given namespace
81	UniqueName(ctx NamespaceContext, name string) (unique string)
82}
83
84// A NamespaceContext stores the information given to a NameInterface to enable the NameInterface
85// to choose the namespace for any given module
86type NamespaceContext interface {
87	ModulePath() string
88}
89
90type namespaceContextImpl struct {
91	modulePath string
92}
93
94func newNamespaceContext(moduleInfo *moduleInfo) (ctx NamespaceContext) {
95	return &namespaceContextImpl{moduleInfo.pos.Filename}
96}
97
98func newNamespaceContextFromFilename(filename string) NamespaceContext {
99	return &namespaceContextImpl{filename}
100}
101
102func (ctx *namespaceContextImpl) ModulePath() string {
103	return ctx.modulePath
104}
105
106type SkippedModuleInfo struct {
107	filename string
108	reason   string
109}
110
111// a SimpleNameInterface just stores all modules in a map based on name
112type SimpleNameInterface struct {
113	modules        map[string]ModuleGroup
114	skippedModules map[string][]SkippedModuleInfo
115}
116
117func NewSimpleNameInterface() *SimpleNameInterface {
118	return &SimpleNameInterface{
119		modules:        make(map[string]ModuleGroup),
120		skippedModules: make(map[string][]SkippedModuleInfo),
121	}
122}
123
124func (s *SimpleNameInterface) NewModule(ctx NamespaceContext, group ModuleGroup, module Module) (namespace Namespace, err []error) {
125	name := group.name
126	if group, present := s.modules[name]; present {
127		return nil, []error{
128			// seven characters at the start of the second line to align with the string "error: "
129			fmt.Errorf("module %q already defined\n"+
130				"       %s <-- previous definition here", name, group.modules.firstModule().pos),
131		}
132	}
133
134	s.modules[name] = group
135
136	return nil, []error{}
137}
138
139func (s *SimpleNameInterface) NewSkippedModule(ctx NamespaceContext, name string, info SkippedModuleInfo) {
140	if name == "" {
141		return
142	}
143	s.skippedModules[name] = append(s.skippedModules[name], info)
144}
145
146func (s *SimpleNameInterface) ModuleFromName(moduleName string, namespace Namespace) (group ModuleGroup, found bool) {
147	group, found = s.modules[moduleName]
148	return group, found
149}
150
151func (s *SimpleNameInterface) SkippedModuleFromName(moduleName string, namespace Namespace) (skipInfos []SkippedModuleInfo, skipped bool) {
152	skipInfos, skipped = s.skippedModules[moduleName]
153	return
154}
155
156func (s *SimpleNameInterface) Rename(oldName string, newName string, namespace Namespace) (errs []error) {
157	existingGroup, exists := s.modules[newName]
158	if exists {
159		return []error{
160			// seven characters at the start of the second line to align with the string "error: "
161			fmt.Errorf("renaming module %q to %q conflicts with existing module\n"+
162				"       %s <-- existing module defined here",
163				oldName, newName, existingGroup.modules.firstModule().pos),
164		}
165	}
166
167	group, exists := s.modules[oldName]
168	if !exists {
169		return []error{fmt.Errorf("module %q to renamed to %q doesn't exist", oldName, newName)}
170	}
171	s.modules[newName] = group
172	delete(s.modules, group.name)
173	group.name = newName
174	return nil
175}
176
177func (s *SimpleNameInterface) AllModules() []ModuleGroup {
178	groups := make([]ModuleGroup, 0, len(s.modules))
179	for _, group := range s.modules {
180		groups = append(groups, group)
181	}
182
183	duplicateName := ""
184	less := func(i, j int) bool {
185		if groups[i].name == groups[j].name {
186			duplicateName = groups[i].name
187		}
188		return groups[i].name < groups[j].name
189	}
190	sort.Slice(groups, less)
191	if duplicateName != "" {
192		// It is permitted to have two moduleGroup's with the same name, but not within the same
193		// Namespace. The SimpleNameInterface should catch this in NewModule, however, so this
194		// should never happen.
195		panic(fmt.Sprintf("Duplicate moduleGroup name %q", duplicateName))
196	}
197	return groups
198}
199
200func (s *SimpleNameInterface) MissingDependencyError(depender string, dependerNamespace Namespace, dependency string, guess []string) (err error) {
201	skipInfos, skipped := s.SkippedModuleFromName(dependency, dependerNamespace)
202	if skipped {
203		filesFound := make([]string, 0, len(skipInfos))
204		reasons := make([]string, 0, len(skipInfos))
205		for _, info := range skipInfos {
206			filesFound = append(filesFound, info.filename)
207			reasons = append(reasons, info.reason)
208		}
209		return fmt.Errorf(
210			"module %q depends on skipped module %q; %q was defined in files(s) [%v], but was skipped for reason(s) [%v]",
211			depender,
212			dependency,
213			dependency,
214			strings.Join(filesFound, ", "),
215			strings.Join(reasons, "; "),
216		)
217	}
218
219	guessString := ""
220	if len(guess) > 0 {
221		guessString = fmt.Sprintf(" Did you mean %q?", guess)
222	}
223	return fmt.Errorf("%q depends on undefined module %q.%s", depender, dependency, guessString)
224}
225
226func (s *SimpleNameInterface) GetNamespace(ctx NamespaceContext) Namespace {
227	return nil
228}
229
230func (s *SimpleNameInterface) UniqueName(ctx NamespaceContext, name string) (unique string) {
231	return name
232}
233