func copyCssFile() error { conf := config.Current() target := conf.Gss.CurTarget() if conf.Gss == nil { return nil } srcName := filepath.Join(conf.Build, config.CSS_NAME) filename := target.Output if strings.Contains(filename, "{sha1}") { sha1, err := calcFileSha1(srcName) if err != nil { return err } filename = strings.Replace(filename, "{sha1}", sha1, -1) } mapping[config.SelectedTarget+"-css"] = filename if err := copyFile(srcName, filename); err != nil { return err } return nil }
// Scans folder recursively search for files with the ext // extension and returns the whole list. func Do(folder string, ext string) ([]string, error) { conf := config.Current() var library bool if conf.Library != nil { library = strings.Contains(folder, conf.Library.Root) } if library { r, ok := libraryCache[folder] if ok { return r, nil } } v := &visitor{[]string{}} if err := v.scan(folder, ext); err != nil { return nil, err } if library { libraryCache[folder] = v.results } return v.results, nil }
// Load the caches from a file. func Load() error { conf := config.Current() filename := filepath.Join(conf.Build, CACHE_FILENAME) if config.NoCache { return nil } f, err := os.Open(filename) if err != nil && os.IsNotExist(err) { return nil } else if err != nil { return app.Error(err) } defer f.Close() log.Println("Reading cache:", filename) d := gob.NewDecoder(f) if err := d.Decode(&modificationCache); err != nil { return app.Error(err) } if err := d.Decode(&dataCache); err != nil { return app.Error(err) } log.Println("Read", len(modificationCache), "modifications and", len(dataCache), "datas!") return nil }
func (s *DepsSuite) BenchmarkGeneration(c *C) { conf := config.Current() for i := 0; i < c.N; i++ { depstree, err := NewDepsTree("compile") if err != nil { c.Error(err) return } namespaces := []string{} for _, input := range conf.Js.Inputs { if strings.Contains(input.File, "_test") { continue } ns, err := depstree.GetProvides(input.File) if err != nil { c.Error(err) return } namespaces = append(namespaces, ns...) } if _, err := depstree.GetDependencies(namespaces); err != nil { c.Error(err) return } } }
func copyJsFile() error { conf := config.Current() target := conf.Js.CurTarget() if conf.Js == nil { return nil } srcName := filepath.Join(conf.Build, config.JS_NAME) filename := filepath.Join(conf.Js.Root, target.Output) if strings.Contains(filename, "{sha1}") { sha1, err := calcFileSha1(srcName) if err != nil { return err } filename = strings.Replace(filename, "{sha1}", sha1, -1) } mapping[config.SelectedTarget+"-js"] = filename files := []string{} for _, n := range conf.Js.Prepends { files = append(files, filepath.Join(conf.Js.Root, n.File)) } files = append(files, srcName) if err := copyFiles(files, filename); err != nil { return err } 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 }
func RawOutput(r *app.Request) error { if err := hooks.PreCompile(); err != nil { return err } if err := gss.Compile(); err != nil { return err } if err := soy.Compile(); err != nil { return err } _, namespaces, err := js.GenerateDeps("input-production") if err != nil { return err } log.Println("Output RAW mode") conf := config.Current() content := bytes.NewBuffer(nil) base := path.Join(conf.Library.Root, "closure", "goog", "base.js") if err := addFile(content, base); err != nil { return err } if err := addFile(content, path.Join(conf.Build, config.RENAMING_MAP_NAME)); err != nil { return err } if err := addFile(content, path.Join(conf.Build, config.DEPS_NAME)); err != nil { return err } if err := hooks.PostCompile(); err != nil { return err } css := make([]byte, 0) if conf.Gss != nil { css, err = ioutil.ReadFile(filepath.Join(conf.Build, config.CSS_NAME)) if err != nil { return app.Error(err) } } data := map[string]interface{}{ "Content": template.HTML(string(content.Bytes())), "Port": config.Port, "LT": template.HTML("<"), "Namespaces": template.HTML("'" + strings.Join(namespaces, "', '") + "'"), "Css": template.HTML(template.JSEscapeString(string(css))), } r.W.Header().Set("Content-Type", "text/javascript") return r.ExecuteTemplate([]string{"raw"}, data) }
func compile(r *app.Request) error { conf := config.Current() target := conf.Js.CurTarget() if target == nil || target.Mode == "RAW" { return RawOutput(r) } return js.CompiledJs(r) }
// Returns true if the directory name is worth scanning. // It checks too the list of ignored files. func (v *visitor) validDir(path, name string) bool { conf := config.Current() if conf.Ignores != nil && path != "" { for _, ignore := range conf.Ignores { if strings.HasPrefix(path, ignore.Path) { return false } } } return name != ".svn" && name != ".hg" && name != ".git" }
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) }
func cleanRenamingMap() error { conf := config.Current() // Create/Clean the renaming map file to avoid compilation errors (the JS // compiler assumes there's a file with this name there). f, err := os.Create(path.Join(conf.Build, config.RENAMING_MAP_NAME)) if err != nil { return app.Error(err) } f.Close() 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 }
// Called before each compilation task. It load the caches // and reload the confs if needed. func PreCompile() error { if err := config.Load(); err != nil { return err } conf := config.Current() if err := os.MkdirAll(conf.Build, 0755); err != nil { return app.Error(err) } var err error loadCacheOnce.Do(func() { err = cache.Load() }) return err }
func outputMap() error { conf := config.Current() if conf.Map == nil { return nil } f, err := os.Create(conf.Map.File) if err != nil { return app.Error(err) } defer f.Close() fmt.Fprintf(f, "var mapping = ") if err := json.NewEncoder(f).Encode(&mapping); err != nil { return app.Error(err) } return nil }
// Search for "_test.js" files and relativize them to // the root directory. It replaces the .js ext with .html. func scanTests() ([]string, error) { conf := config.Current() tests, err := scan.Do(conf.Js.Root, "_test.js") if err != nil { return nil, err } for i, test := range tests { // Relativize the path adding .html instead of .js p, err := filepath.Rel(conf.Js.Root, test[:len(test)-2]+"html") if err != nil { return nil, app.Error(err) } tests[i] = p } return tests, nil }
func CompiledJs(r *app.Request) error { r.W.Header().Set("Content-Type", "text/javascript") conf := config.Current() if err := FullCompile(); err != nil { return err } f, err := os.Open(path.Join(conf.Build, config.JS_NAME)) if err != nil { return app.Error(err) } defer f.Close() if _, err := io.Copy(r.W, f); err != nil { return app.Error(err) } return nil }
// Save the caches to a file. func Dump() error { conf := config.Current() f, err := os.Create(filepath.Join(conf.Build, CACHE_FILENAME)) if err != nil { return app.Error(err) } defer f.Close() log.Println("Write", len(modificationCache), "modifications and", len(dataCache), "datas!") e := gob.NewEncoder(f) if err := e.Encode(&modificationCache); err != nil { return app.Error(err) } if err := e.Encode(&dataCache); err != nil { return app.Error(err) } return nil }
// Build a dependency tree that allows the client to know the order of // compilation // Dest will be "compile" or "input" depending on the use. func NewDepsTree(dest string) (*DepsTree, error) { conf := config.Current() // Initialize the tree depstree := &DepsTree{ sources: map[string]*domain.Source{}, provides: map[string]*domain.Source{}, dest: dest, } if conf.Library != nil { depstree.basePath = path.Join(conf.Library.Root, "closure", "goog", "base.js") } // Build the deps tree scanning each root directory recursively roots := BaseJSPaths() for _, root := range roots { // Scan the sources src, err := Do(root, ".js") if err != nil { return nil, err } // Add them to the tree for _, s := range src { if err := depstree.AddSource(s); err != nil { return nil, err } } } // Check the integrity of the tree if err := depstree.Check(); err != nil { return nil, err } return depstree, nil }
// Base paths, all routes to a JS must start from one // of these ones. // The order is important, the paths will be scanned as // they've been written. func BaseJSPaths() []string { conf := config.Current() p := []string{} if conf.Library != nil { p = append(p, path.Join(conf.Library.Root, "closure", "goog")) p = append(p, conf.Library.Root) } if conf.Js != nil { p = append(p, conf.Js.Root) } if conf.Soy != nil { path.Join(conf.Soy.Compiler, "javascript") if conf.Soy.Root != "" { p = append(p, path.Join(conf.Build, "templates")) } } return p }
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 }
// 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 }
// 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 }