github.com/ernestokarim/closurer@v0.0.0-20130119214741-f245d086c750/scan/deps.go (about) 1 package scan 2 3 import ( 4 "fmt" 5 "io" 6 "path" 7 "path/filepath" 8 "strings" 9 10 "github.com/ernestokarim/closurer/app" 11 "github.com/ernestokarim/closurer/config" 12 "github.com/ernestokarim/closurer/domain" 13 ) 14 15 // Store the info of a dependencies tree 16 type DepsTree struct { 17 MustCompile bool 18 19 sources map[string]*domain.Source 20 provides map[string]*domain.Source 21 base *domain.Source 22 basePath string 23 dest string 24 } 25 26 // Build a dependency tree that allows the client to know the order of 27 // compilation 28 // Dest will be "compile" or "input" depending on the use. 29 func NewDepsTree(dest string) (*DepsTree, error) { 30 conf := config.Current() 31 32 // Initialize the tree 33 depstree := &DepsTree{ 34 sources: map[string]*domain.Source{}, 35 provides: map[string]*domain.Source{}, 36 dest: dest, 37 } 38 39 if conf.Library != nil { 40 depstree.basePath = path.Join(conf.Library.Root, "closure", "goog", "base.js") 41 } 42 43 // Build the deps tree scanning each root directory recursively 44 roots := BaseJSPaths() 45 for _, root := range roots { 46 // Scan the sources 47 src, err := Do(root, ".js") 48 if err != nil { 49 return nil, err 50 } 51 52 // Add them to the tree 53 for _, s := range src { 54 if err := depstree.AddSource(s); err != nil { 55 return nil, err 56 } 57 } 58 } 59 60 // Check the integrity of the tree 61 if err := depstree.Check(); err != nil { 62 return nil, err 63 } 64 65 return depstree, nil 66 } 67 68 // Adds a new JS source file to the tree 69 func (tree *DepsTree) AddSource(filename string) error { 70 // Build the source 71 src, cached, err := domain.NewSource(tree.dest, filename, tree.basePath) 72 if err != nil { 73 return err 74 } 75 76 // If it's the base file, save it 77 if src.Base { 78 tree.base = src 79 } 80 81 conf := config.Current() 82 83 // Scan all the previous sources searching for repeated 84 // namespaces. We ignore closure library files because they're 85 // supposed to be correct and tested by other methods 86 if conf.Library == nil || !strings.HasPrefix(filename, conf.Library.Root) { 87 for k, source := range tree.sources { 88 for _, provide := range source.Provides { 89 if In(src.Provides, provide) { 90 return app.Errorf("multiple provide %s: %s and %s", provide, k, filename) 91 } 92 } 93 } 94 } 95 96 // Files without the goog.provide directive 97 // use a trick to provide its own name. It fullfills the need 98 // to compile things apart from the Closure style (Angular, ...). 99 if len(src.Provides) == 0 { 100 src.Provides = []string{filename} 101 } 102 103 // Add all the provides to the map 104 for _, provide := range src.Provides { 105 tree.provides[provide] = src 106 } 107 108 // Save the source 109 tree.sources[filename] = src 110 111 // Update the MustCompile flag 112 tree.MustCompile = tree.MustCompile || !cached 113 114 return nil 115 } 116 117 // Check if all required namespaces are provided by the 118 // scanned files 119 func (tree *DepsTree) Check() error { 120 for k, source := range tree.sources { 121 for _, require := range source.Requires { 122 _, ok := tree.provides[require] 123 if !ok { 124 return app.Errorf("namespace not found %s: %s", require, k) 125 } 126 } 127 } 128 129 return nil 130 } 131 132 // Returns the provides list of a source file, or an error if it hasn't been 133 // scanned previously into the tree 134 func (tree *DepsTree) GetProvides(filename string) ([]string, error) { 135 src, ok := tree.sources[filename] 136 if !ok { 137 return nil, app.Errorf("input not present in the sources: %s", filename) 138 } 139 140 return src.Provides, nil 141 } 142 143 // Return the list of namespaces need to include the test files too 144 func (tree *DepsTree) GetTestingNamespaces() []string { 145 ns := make([]string, 0) 146 for _, src := range tree.sources { 147 if strings.Contains(src.Filename, "_test.js") { 148 ns = append(ns, src.Provides...) 149 } 150 } 151 return ns 152 } 153 154 // Struct to store the info of a dependencies tree traversal 155 type TraversalInfo struct { 156 deps []*domain.Source 157 traversal []string 158 } 159 160 // Returns the list of files (in order) that must be compiled to finally 161 // obtain all namespaces, including the base one. 162 func (tree *DepsTree) GetDependencies(namespaces []string) ([]*domain.Source, error) { 163 // Prepare the info 164 info := &TraversalInfo{ 165 deps: []*domain.Source{}, 166 traversal: []string{}, 167 } 168 169 for _, ns := range namespaces { 170 // Resolve all the needed dependencies 171 if err := tree.ResolveDependencies(ns, info); err != nil { 172 return nil, err 173 } 174 } 175 176 return info.deps, nil 177 } 178 179 // Adds to the traversal info the list of dependencies recursively. 180 func (tree *DepsTree) ResolveDependencies(ns string, info *TraversalInfo) error { 181 // Check that the namespace is correct 182 src, ok := tree.provides[ns] 183 if !ok { 184 return app.Errorf("namespace not found: %s", ns) 185 } 186 187 // Detects circular deps 188 if In(info.traversal, ns) { 189 info.traversal = append(info.traversal, ns) 190 return app.Errorf("circular dependency detected: %v", info.traversal) 191 } 192 193 // Memoize results, don't recalculate old depencies 194 if !InSource(info.deps, src) { 195 // Add a new namespace to the traversal 196 info.traversal = append(info.traversal, ns) 197 198 // Compile first all dependencies 199 for _, require := range src.Requires { 200 tree.ResolveDependencies(require, info) 201 } 202 203 // Add ourselves to the list of files 204 info.deps = append(info.deps, src) 205 206 // Remove the namespace from the traversal 207 info.traversal = info.traversal[:len(info.traversal)-1] 208 } 209 210 return nil 211 } 212 213 func WriteDeps(f io.Writer, deps []*domain.Source) error { 214 paths := BaseJSPaths() 215 for _, src := range deps { 216 // Accumulates the provides & requires of the source 217 provides := "'" + strings.Join(src.Provides, "', '") + "'" 218 requires := "'" + strings.Join(src.Requires, "', '") + "'" 219 220 // Search the base path to the file, and put the path 221 // relative to it 222 var n string 223 for _, p := range paths { 224 tn, err := filepath.Rel(p, src.Filename) 225 if err == nil && !strings.Contains(tn, "..") { 226 n = tn 227 break 228 } 229 } 230 if n == "" { 231 return app.Errorf("cannot generate the relative filename for %s", src.Filename) 232 } 233 234 // Write the line to the output of the deps.js file request 235 fmt.Fprintf(f, "goog.addDependency('%s', [%s], [%s]);\n", n, provides, requires) 236 } 237 238 return nil 239 } 240 241 // Base paths, all routes to a JS must start from one 242 // of these ones. 243 // The order is important, the paths will be scanned as 244 // they've been written. 245 func BaseJSPaths() []string { 246 conf := config.Current() 247 248 p := []string{} 249 250 if conf.Library != nil { 251 p = append(p, path.Join(conf.Library.Root, "closure", "goog")) 252 p = append(p, conf.Library.Root) 253 } 254 255 if conf.Js != nil { 256 p = append(p, conf.Js.Root) 257 } 258 259 if conf.Soy != nil { 260 path.Join(conf.Soy.Compiler, "javascript") 261 if conf.Soy.Root != "" { 262 p = append(p, path.Join(conf.Build, "templates")) 263 } 264 } 265 266 return p 267 }