// Adds to the traversal info the list of dependencies recursively. func (tree *DepsTree) ResolveDependencies(ns string, info *TraversalInfo) error { // Check that the namespace is correct src, ok := tree.provides[ns] if !ok { return app.Errorf("namespace not found: %s", ns) } // Detects circular deps if In(info.traversal, ns) { info.traversal = append(info.traversal, ns) return app.Errorf("circular dependency detected: %v", info.traversal) } // Memoize results, don't recalculate old depencies if !InSource(info.deps, src) { // Add a new namespace to the traversal info.traversal = append(info.traversal, ns) // Compile first all dependencies for _, require := range src.Requires { tree.ResolveDependencies(require, info) } // Add ourselves to the list of files info.deps = append(info.deps, src) // Remove the namespace from the traversal info.traversal = info.traversal[:len(info.traversal)-1] } return nil }
func (t *GssTargetNode) ApplyInherits() error { if t.Name == "" { return app.Errorf("The name of the target is required") } if t.Inherits == "" { return nil } for _, parent := range globalConf.Gss.Targets { if parent.Name == t.Name { return app.Errorf("Inherits should reference a previous target: %s", t.Name) } if parent.Name != t.Inherits { continue } if t.Rename == "" { t.Rename = parent.Rename } if t.Output == "" { t.Output = parent.Output } for _, d := range parent.Defines { if !t.HasDefine(d.Name) { t.Defines = append(t.Defines, d.Clone()) } } return nil } panic("not reached") }
func validChecks(lst []*CheckNode) error { for _, check := range lst { checks := map[string]bool{ "ambiguousFunctionDecl": true, "checkRegExp": true, "checkTypes": true, "checkVars": true, "constantProperty": true, "deprecated": true, "fileoverviewTags": true, "internetExplorerChecks": true, "invalidCasts": true, "missingProperties": true, "nonStandardJsDocs": true, "strictModuleDepCheck": true, "typeInvalidation": true, "undefinedNames": true, "undefinedVars": true, "unknownDefines": true, "uselessCode": true, "globalThis": true, "duplicateMessage": true, } if _, ok := checks[check.Name]; !ok { return app.Errorf("Illegal check: %s", check.Name) } } return nil }
func WriteDeps(f io.Writer, deps []*domain.Source) error { paths := BaseJSPaths() for _, src := range deps { // Accumulates the provides & requires of the source provides := "'" + strings.Join(src.Provides, "', '") + "'" requires := "'" + strings.Join(src.Requires, "', '") + "'" // Search the base path to the file, and put the path // relative to it var n string for _, p := range paths { tn, err := filepath.Rel(p, src.Filename) if err == nil && !strings.Contains(tn, "..") { n = tn break } } if n == "" { return app.Errorf("cannot generate the relative filename for %s", src.Filename) } // Write the line to the output of the deps.js file request fmt.Fprintf(f, "goog.addDependency('%s', [%s], [%s]);\n", n, provides, requires) } return nil }
func GenerateDeps(dest string) ([]*domain.Source, []string, error) { log.Println("Scanning deps...") conf := config.Current() depstree, err := scan.NewDepsTree(dest) if err != nil { return nil, nil, err } namespaces := []string{} if conf.Js != nil { for _, input := range conf.Js.Inputs { if dest != "input" && strings.Contains(input.File, "_test") { continue } file := filepath.Join(conf.Js.Root, input.File) ns, err := depstree.GetProvides(file) if err != nil { return nil, nil, err } namespaces = append(namespaces, ns...) } } if len(namespaces) == 0 { return nil, nil, app.Errorf("no namespaces provided in the input files") } if dest == "input" { // Add the necesary namespaces for the multi-test runner namespaces = append(namespaces, "goog.style") namespaces = append(namespaces, "goog.userAgent.product") namespaces = append(namespaces, "goog.testing.MultiTestRunner") namespaces = append(namespaces, depstree.GetTestingNamespaces()...) } else if dest == "input-production" { namespaces = append(namespaces, "goog.style") } deps, err := depstree.GetDependencies(namespaces) if err != nil { return nil, nil, err } f, err := os.Create(filepath.Join(conf.Build, config.DEPS_NAME)) if err != nil { return nil, nil, app.Error(err) } defer f.Close() if err := scan.WriteDeps(f, deps); err != nil { return nil, nil, err } log.Println("Done scanning deps!") return deps, namespaces, nil }
// Returns the provides list of a source file, or an error if it hasn't been // scanned previously into the tree func (tree *DepsTree) GetProvides(filename string) ([]string, error) { src, ok := tree.sources[filename] if !ok { return nil, app.Errorf("input not present in the sources: %s", filename) } return src.Provides, nil }
func Input(r *app.Request) error { name := mux.Vars(r.Req)["name"] if name == config.DEPS_NAME { if err := hooks.PreCompile(); err != nil { return err } if err := soy.Compile(); err != nil { return err } if _, _, err := js.GenerateDeps("input"); err != nil { return err } conf := config.Current() f, err := os.Open(path.Join(conf.Build, config.DEPS_NAME)) if err != nil { return app.Error(err) } defer f.Close() r.W.Header().Set("Content-Type", "text/javascript") if _, err := io.Copy(r.W, f); err != nil { return app.Error(err) } if err := hooks.PostCompile(); err != nil { return err } return nil } // Otherwise serve the file if it can be found paths := scan.BaseJSPaths() for _, p := range paths { f, err := os.Open(path.Join(p, name)) if err != nil && !os.IsNotExist(err) { return app.Error(err) } else if err == nil { defer f.Close() r.W.Header().Set("Content-Type", "text/javascript") io.Copy(r.W, f) return nil } } return app.Errorf("file not found: %s", name) }
// Check if all required namespaces are provided by the // scanned files func (tree *DepsTree) Check() error { for k, source := range tree.sources { for _, require := range source.Requires { _, ok := tree.provides[require] if !ok { return app.Errorf("namespace not found %s: %s", require, k) } } } return nil }
// Adds a new JS source file to the tree func (tree *DepsTree) AddSource(filename string) error { // Build the source src, cached, err := domain.NewSource(tree.dest, filename, tree.basePath) if err != nil { return err } // If it's the base file, save it if src.Base { tree.base = src } conf := config.Current() // Scan all the previous sources searching for repeated // namespaces. We ignore closure library files because they're // supposed to be correct and tested by other methods if conf.Library == nil || !strings.HasPrefix(filename, conf.Library.Root) { for k, source := range tree.sources { for _, provide := range source.Provides { if In(src.Provides, provide) { return app.Errorf("multiple provide %s: %s and %s", provide, k, filename) } } } } // Files without the goog.provide directive // use a trick to provide its own name. It fullfills the need // to compile things apart from the Closure style (Angular, ...). if len(src.Provides) == 0 { src.Provides = []string{filename} } // Add all the provides to the map for _, provide := range src.Provides { tree.provides[provide] = src } // Save the source tree.sources[filename] = src // Update the MustCompile flag tree.MustCompile = tree.MustCompile || !cached return nil }
func Compile() error { conf := config.Current() target := conf.Js.CurTarget() if conf.Js == nil { return nil } if len(conf.Js.Inputs) == 0 { return nil } deps, _, err := GenerateDeps("compile") if err != nil { return err } args := []string{ "-jar", path.Join(conf.Js.Compiler, "build", "compiler.jar"), "--js_output_file", path.Join(conf.Build, config.JS_NAME), } if conf.Library != nil { args = append(args, "--js", path.Join(conf.Library.Root, "closure", "goog", "base.js"), "--js", path.Join(conf.Library.Root, "closure", "goog", "deps.js"), ) } args = append(args, "--js", filepath.Join(conf.Build, config.DEPS_NAME), "--js", filepath.Join(conf.Build, config.RENAMING_MAP_NAME), ) if conf.Js.SideEffects == "" { args = append(args, "--output_wrapper", `(function(){%output%})();`) } for _, dep := range deps { if !strings.Contains(dep.Filename, "_test.js") { args = append(args, "--js", dep.Filename) } } if target.Defines != nil { for _, define := range target.Defines { // If it's not a boolean, quote it if define.Value != "true" && define.Value != "false" { define.Value = "\"" + define.Value + "\"" } args = append(args, "--define", define.Name+"="+define.Value) } } if target.Mode == "ADVANCED" { args = append(args, "--compilation_level", "ADVANCED_OPTIMIZATIONS") } else if target.Mode == "SIMPLE" { args = append(args, "--compilation_level", "SIMPLE_OPTIMIZATIONS") } else if target.Mode == "WHITESPACE" { args = append(args, "--compilation_level", "WHITESPACE_ONLY") } else { return app.Errorf("RAW mode not allowed while compiling") } args = append(args, "--warning_level", target.Level) if conf.Js.Checks != nil { for _, check := range conf.Js.Checks.Errors { args = append(args, "--jscomp_error", check.Name) } for _, check := range conf.Js.Checks.Warnings { args = append(args, "--jscomp_warning", check.Name) } for _, check := range conf.Js.Checks.Offs { args = append(args, "--jscomp_off", check.Name) } } for _, extern := range conf.Js.Externs { args = append(args, "--externs", extern.File) } if conf.Js.Language != "" { args = append(args, "--language_in", conf.Js.Language) } if conf.Js.Formatting != "" { args = append(args, "--formatting", conf.Js.Formatting) args = append(args, "--debug", "true") } log.Println("Compiling JS:", target.Name) // Prepare the command cmd := exec.Command("java", args...) // Output it if asked to if config.OutputCmd { fmt.Println("java", strings.Join(cmd.Args, " ")) } // Run the JS compiler output, err := cmd.CombinedOutput() if err != nil { if len(output) != 0 { fmt.Println(string(output)) } return app.Errorf("exec error: %s", err) } if len(output) > 0 { log.Println("Output from JS compiler:\n", string(output)) } log.Println("Done compiling JS!") return nil }
func (c *Config) validate() error { // Library & compiler paths if c.Js != nil { if c.Js.Root == "" { return app.Errorf("The JS root folder is required") } if c.Js.Formatting != "" && c.Js.Formatting != "PRETTY_PRINT" { return app.Errorf("formatting mode not allowed: %s", c.Js.Formatting) } if c.Js.SideEffects != "" && c.Js.SideEffects != "true" { return app.Errorf("boolean value not allowed: %s", c.Js.SideEffects) } if c.Js.Language != "" { modes := map[string]bool{ "ECMASCRIPT3": true, "ECMASCRIPT5": true, "ECMASCRIPT5_STRICT": true, } if _, ok := modes[c.Js.Language]; !ok { return app.Errorf("language mode not allowed: %s", c.Js.Language) } } // JS targets and inheritation if len(c.Js.Targets) == 0 { return app.Errorf("No target provided for JS code") } for _, t := range c.Js.Targets { if err := t.ApplyInherits(); err != nil { return err } } // Check compilation mode and warnings level for _, t := range c.Js.Targets { modes := map[string]bool{ "SIMPLE": true, "ADVANCED": true, "WHITESPACE": true, "RAW": true, } if _, ok := modes[t.Mode]; !ok { return app.Errorf("Illegal compilation mode in target %s: %s", t.Name, t.Mode) } levels := map[string]bool{ "QUIET": true, "DEFAULT": true, "VERBOSE": true, } if _, ok := levels[t.Level]; !ok { return app.Errorf("Illegal warning level in target %s: %s", t.Name, t.Level) } } // Check that the command line target is in the config file found := false for _, name := range TargetList() { for _, t := range c.Js.Targets { if t.Name == name { found = true break } } if !found { return app.Errorf("Target %s not found in the config file", name) } } // Validate the compilation checks if c.Js.Checks != nil { validChecks(c.Js.Checks.Errors) validChecks(c.Js.Checks.Warnings) validChecks(c.Js.Checks.Offs) } // Check the prepend files if c.Js.Prepends != nil { for _, prepend := range c.Js.Prepends { if prepend.File == "" { return app.Errorf("prepend file empty") } } } } if c.Build == "" { return app.Errorf("The build folder is required") } if c.Library != nil && c.Library.Root == "" { return app.Errorf("The Closure Library path is required") } if c.Js != nil && c.Js.Compiler == "" { return app.Errorf("The Closure Compiler path is required") } if c.Gss != nil { // GSS compiler if c.Gss.Compiler == "" { return app.Errorf("The Closure Stylesheets path is required") } // GSS targets if len(c.Gss.Targets) == 0 { return app.Errorf("No target provided for GSS code") } // At least one input file should be provided if len(c.Gss.Inputs) == 0 { return app.Errorf("No inputs provided for GSS code") } // Compare JS targets and GSS targets if c.Js != nil { if len(c.Js.Targets) != len(c.Gss.Targets) { return app.Errorf("Different number of targets provided for GSS & JS") } for i, tjs := range c.Js.Targets { tgss := c.Gss.Targets[i] if tjs.Name != tgss.Name { return app.Errorf("Targets with different name or order: %s != %s", tjs.Name, tgss.Name) } // Rename property of the GSS target if tgss.Rename != "true" && tgss.Rename != "false" && tgss.Rename != "" { return app.Errorf("Illegal renaming policy value") } // Apply the inherits option if err := tgss.ApplyInherits(); err != nil { return err } // Check that the GSS defines don't have a value for _, d := range tgss.Defines { if d.Value != "" { return app.Errorf("Define values in GSS should be empty") } } } } } // Soy compiler if c.Soy != nil && c.Soy.Root != "" && c.Soy.Compiler == "" { return app.Errorf("The Closure Templates path is required") } // Current targets in build mode if c.Js != nil && c.Gss != nil { for _, t := range TargetList() { SelectTarget(t) tjs := c.Js.CurTarget() tgss := c.Gss.CurTarget() if tjs == nil || tgss == nil { return app.Errorf("Target not found in the config: %s", t) } if Build && IsTarget(tjs.Name) { if tjs.Output == "" { return app.Errorf("Target to build JS without an output file: %s", tjs.Name) } if tgss != nil && tgss.Output == "" { return app.Errorf("Target to build GSS without an output file: %s", tjs.Name) } } } } // Fix the compilers paths if c.Js != nil { c.Js.Compiler = fixPath(c.Js.Compiler) } if c.Gss != nil { c.Gss.Compiler = fixPath(c.Gss.Compiler) } if c.Soy != nil { c.Soy.Compiler = fixPath(c.Soy.Compiler) } if c.Library != nil { c.Library.Root = fixPath(c.Library.Root) } return nil }
// Compile all modified templates func Compile() error { conf := config.Current() if conf.Soy == nil || conf.Soy.Root == "" { return nil } if err := os.MkdirAll(path.Join(conf.Build, "templates"), 0755); err != nil { return app.Error(err) } buildPrefix := filepath.Join(conf.Build, "templates") oldSoy, err := scan.Do(buildPrefix, ".js") if err != nil { return err } soy, err := scan.Do(conf.Soy.Root, ".soy") if err != nil { return err } indexed := map[string]bool{} for _, f := range soy { f = f[len(conf.Soy.Root):] indexed[f] = true } // Delete compiled templates no longer present in the sources for _, f := range oldSoy { compare := f[len(buildPrefix) : len(f)-3] if _, ok := indexed[compare]; !ok { if err := os.Remove(f); err != nil { return app.Error(err) } } } if len(soy) == 0 { return nil } for _, t := range soy { if modified, err := cache.Modified("compile", t); err != nil { return err } else if !modified { continue } prel, err := filepath.Rel(conf.Soy.Root, t) if err != nil { return app.Error(err) } out := path.Join(conf.Build, "templates", prel+".js") if err := os.MkdirAll(path.Dir(out), 0755); err != nil { return app.Error(err) } log.Println("Compiling template", t, "...") // Run the compiler command cmd := exec.Command( "java", "-jar", path.Join(conf.Soy.Compiler, "build", "SoyToJsSrcCompiler.jar"), "--outputPathFormat", out, "--shouldGenerateJsdoc", "--shouldProvideRequireSoyNamespaces", "--cssHandlingScheme", "goog", t) output, err := cmd.CombinedOutput() if err != nil { return app.Errorf("exec error with %s: %s\n%s", t, err, string(output)) } log.Println("Done compiling template!") } return nil }
// Creates a new source. Returns the source, if it has been // loaded from cache or not, and an error. func NewSource(dest, filename, base string) (*Source, bool, error) { src := cache.ReadData(dest+filename, new(Source)).(*Source) // Return the file from cache if possible if modified, err := cache.Modified(dest, filename); err != nil { return nil, false, err } else if !modified { return src, true, nil } // Reset the source info src.Provides = []string{} src.Requires = []string{} src.Base = (filename == base) src.Filename = filename // Open the file f, err := os.Open(filename) if err != nil { return nil, false, app.Error(err) } defer f.Close() r := bufio.NewReader(f) for { // Read it line by line line, _, err := r.ReadLine() if err != nil { if err == io.EOF { break } return nil, false, err } // Find the goog.provide() calls if strings.Contains(string(line), "goog.provide") { matchs := provideRe.FindSubmatch(line) if matchs != nil { src.Provides = append(src.Provides, string(matchs[1])) continue } } // Find the goog.require() calls if strings.Contains(string(line), "goog.require") { matchs := requiresRe.FindSubmatch(line) if matchs != nil { src.Requires = append(src.Requires, string(matchs[1])) continue } } } // Validates the base file if src.Base { if len(src.Provides) > 0 || len(src.Requires) > 0 { return nil, false, app.Errorf("base files should not provide or require namespaces: %s", filename) } src.Provides = append(src.Provides, "goog") } return src, false, nil }
// Compiles the .gss files func Compile() error { conf := config.Current() target := conf.Gss.CurTarget() // Output early if there's no GSS files. if conf.Gss == nil { if err := cleanRenamingMap(); err != nil { return err } return nil } // Check if the cached version is still ok modified := false for _, input := range conf.Gss.Inputs { if m, err := cache.Modified("compile", input.File); err != nil { return err } else if m { modified = true break } } if !modified { return nil } log.Println("Compiling GSS:", target.Name) if err := cleanRenamingMap(); err != nil { return err } // Prepare the list of non-standard functions. funcs := []string{} for _, f := range conf.Gss.Funcs { funcs = append(funcs, "--allowed-non-standard-function") funcs = append(funcs, f.Name) } // Prepare the renaming map args renaming := []string{} if target.Rename == "true" { renaming = []string{ "--output-renaming-map-format", "CLOSURE_COMPILED", "--rename", "CLOSURE", "--output-renaming-map", path.Join(conf.Build, config.RENAMING_MAP_NAME), } } // Prepare the defines defines := []string{} for _, define := range target.Defines { defines = append(defines, "--define", define.Name) } // Prepare the inputs inputs := []string{} for _, input := range conf.Gss.Inputs { inputs = append(inputs, input.File) } // Prepare the command cmd := exec.Command( "java", "-jar", path.Join(conf.Gss.Compiler, "build", "closure-stylesheets.jar"), "--output-file", filepath.Join(conf.Build, config.CSS_NAME)) cmd.Args = append(cmd.Args, funcs...) cmd.Args = append(cmd.Args, renaming...) cmd.Args = append(cmd.Args, inputs...) cmd.Args = append(cmd.Args, defines...) // Output the command if asked to if config.OutputCmd { fmt.Println("java", strings.Join(cmd.Args, " ")) } // Run the compiler output, err := cmd.CombinedOutput() if err != nil { if len(output) != 0 { fmt.Println(string(output)) } return app.Errorf("exec error: %s", err) } if len(output) > 0 { log.Println("Output from GSS compiler:\n", string(output)) } log.Println("Done compiling GSS!") return nil }