Example #1
0
// 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
}
Example #2
0
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")
}
Example #3
0
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
}
Example #4
0
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
}
Example #5
0
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
}
Example #6
0
// 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
}
Example #7
0
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)
}
Example #8
0
// 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
}
Example #9
0
// 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
}
Example #10
0
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
}
Example #11
0
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
}
Example #12
0
// 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
}
Example #13
0
// 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
}
Example #14
0
// 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
}