Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision

Target

Select target project
  • fabcat/vmap
1 result
Select Git revision
Show changes
Showing
with 1299 additions and 2 deletions
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
File mode changed from 100644 to 100755
No preview for this file type
No preview for this file type
{
"name": "Vitis",
"version": "0.1.0"
"version": "0.1.0",
"dependencies": {
"google-closure-compiler": "^20160911.0.0",
"google-closure-library": "^20160911.0.0",
"grunt": "^1.0.4",
"grunt-closure-tools": "^1.0.0"
}
}
Subproject commit d9f2f97e212dadeff0df8288b4f829e9b2a75845
Google closure exceptions
\ No newline at end of file
#!/usr/bin/env python
#
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility for Closure Library dependency calculation.
ClosureBuilder scans source files to build dependency info. From the
dependencies, the script can produce a manifest in dependency order,
a concatenated script, or compiled output from the Closure Compiler.
Paths to files can be expressed as individual arguments to the tool (intended
for use with find and xargs). As a convenience, --root can be used to specify
all JS files below a directory.
usage: %prog [options] [file1.js file2.js ...]
"""
__author__ = 'nnaze@google.com (Nathan Naze)'
import io
import logging
import optparse
import os
import sys
import depstree
import jscompiler
import source
import treescan
def _GetOptionsParser():
"""Get the options parser."""
parser = optparse.OptionParser(__doc__)
parser.add_option('-i',
'--input',
dest='inputs',
action='append',
default=[],
help='One or more input files to calculate dependencies '
'for. The namespaces in this file will be combined with '
'those given with the -n flag to form the set of '
'namespaces to find dependencies for.')
parser.add_option('-n',
'--namespace',
dest='namespaces',
action='append',
default=[],
help='One or more namespaces to calculate dependencies '
'for. These namespaces will be combined with those given '
'with the -i flag to form the set of namespaces to find '
'dependencies for. A Closure namespace is a '
'dot-delimited path expression declared with a call to '
'goog.provide() (e.g. "goog.array" or "foo.bar").')
parser.add_option('--root',
dest='roots',
action='append',
default=[],
help='The paths that should be traversed to build the '
'dependencies.')
parser.add_option('-o',
'--output_mode',
dest='output_mode',
type='choice',
action='store',
choices=['list', 'script', 'compiled'],
default='list',
help='The type of output to generate from this script. '
'Options are "list" for a list of filenames, "script" '
'for a single script containing the contents of all the '
'files, or "compiled" to produce compiled output with '
'the Closure Compiler. Default is "list".')
parser.add_option('-c',
'--compiler_jar',
dest='compiler_jar',
action='store',
help='The location of the Closure compiler .jar file.')
parser.add_option('-f',
'--compiler_flags',
dest='compiler_flags',
default=[],
action='append',
help='Additional flags to pass to the Closure compiler. '
'To pass multiple flags, --compiler_flags has to be '
'specified multiple times.')
parser.add_option('-j',
'--jvm_flags',
dest='jvm_flags',
default=[],
action='append',
help='Additional flags to pass to the JVM compiler. '
'To pass multiple flags, --jvm_flags has to be '
'specified multiple times.')
parser.add_option('--output_file',
dest='output_file',
action='store',
help=('If specified, write output to this path instead of '
'writing to standard output.'))
return parser
def _GetInputByPath(path, sources):
"""Get the source identified by a path.
Args:
path: str, A path to a file that identifies a source.
sources: An iterable collection of source objects.
Returns:
The source from sources identified by path, if found. Converts to
real paths for comparison.
"""
for js_source in sources:
# Convert both to real paths for comparison.
if os.path.realpath(path) == os.path.realpath(js_source.GetPath()):
return js_source
def _GetClosureBaseFile(sources):
"""Given a set of sources, returns the one base.js file.
Note that if zero or two or more base.js files are found, an error message
will be written and the program will be exited.
Args:
sources: An iterable of _PathSource objects.
Returns:
The _PathSource representing the base Closure file.
"""
base_files = [
js_source for js_source in sources if _IsClosureBaseFile(js_source)
]
if not base_files:
logging.error('No Closure base.js file found.')
sys.exit(1)
if len(base_files) > 1:
logging.error('More than one Closure base.js files found at these paths:')
for base_file in base_files:
logging.error(base_file.GetPath())
sys.exit(1)
return base_files[0]
def _IsClosureBaseFile(js_source):
"""Returns true if the given _PathSource is the Closure base.js source."""
return (os.path.basename(js_source.GetPath()) == 'base.js' and
js_source.provides == set(['goog']))
class _PathSource(source.Source):
"""Source file subclass that remembers its file path."""
def __init__(self, path):
"""Initialize a source.
Args:
path: str, Path to a JavaScript file. The source string will be read
from this file.
"""
super(_PathSource, self).__init__(source.GetFileContents(path))
self._path = path
def __str__(self):
return 'PathSource %s' % self._path
def GetPath(self):
"""Returns the path."""
return self._path
def _WrapGoogModuleSource(src):
return ('goog.loadModule(function(exports) {{'
'"use strict";'
'{0}'
'\n' # terminate any trailing single line comment.
';return exports'
'}});\n').format(src)
def main():
logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
level=logging.INFO)
options, args = _GetOptionsParser().parse_args()
# Make our output pipe.
if options.output_file:
out = io.open(options.output_file, 'wb')
else:
version = sys.version_info[:2]
if version >= (3, 0):
# Write bytes to stdout
out = sys.stdout.buffer
else:
out = sys.stdout
sources = set()
logging.info('Scanning paths...')
for path in options.roots:
for js_path in treescan.ScanTreeForJsFiles(path):
sources.add(_PathSource(js_path))
# Add scripts specified on the command line.
for js_path in args:
sources.add(_PathSource(js_path))
logging.info('%s sources scanned.', len(sources))
# Though deps output doesn't need to query the tree, we still build it
# to validate dependencies.
logging.info('Building dependency tree..')
tree = depstree.DepsTree(sources)
input_namespaces = set()
inputs = options.inputs or []
for input_path in inputs:
js_input = _GetInputByPath(input_path, sources)
if not js_input:
logging.error('No source matched input %s', input_path)
sys.exit(1)
input_namespaces.update(js_input.provides)
input_namespaces.update(options.namespaces)
if not input_namespaces:
logging.error('No namespaces found. At least one namespace must be '
'specified with the --namespace or --input flags.')
sys.exit(2)
# The Closure Library base file must go first.
base = _GetClosureBaseFile(sources)
deps = [base] + tree.GetDependencies(input_namespaces)
output_mode = options.output_mode
if output_mode == 'list':
out.writelines([js_source.GetPath() + '\n' for js_source in deps])
elif output_mode == 'script':
for js_source in deps:
src = js_source.GetSource()
if js_source.is_goog_module:
src = _WrapGoogModuleSource(src)
out.write(src.encode('utf-8') + b'\n')
elif output_mode == 'compiled':
logging.warning("""\
Closure Compiler now natively understands and orders Closure dependencies and
is prefererred over using this script for performing JavaScript compilation.
Please migrate your codebase.
See:
https://github.com/google/closure-compiler/wiki/Managing-Dependencies
""")
# Make sure a .jar is specified.
if not options.compiler_jar:
logging.error('--compiler_jar flag must be specified if --output is '
'"compiled"')
sys.exit(2)
# Will throw an error if the compilation fails.
compiled_source = jscompiler.Compile(options.compiler_jar,
[js_source.GetPath()
for js_source in deps],
jvm_flags=options.jvm_flags,
compiler_flags=options.compiler_flags)
logging.info('JavaScript compilation succeeded.')
out.write(compiled_source.encode('utf-8'))
else:
logging.error('Invalid value for --output flag.')
sys.exit(2)
if __name__ == '__main__':
main()
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Class to represent a full Closure Library dependency tree.
Offers a queryable tree of dependencies of a given set of sources. The tree
will also do logical validation to prevent duplicate provides and circular
dependencies.
"""
__author__ = 'nnaze@google.com (Nathan Naze)'
class DepsTree(object):
"""Represents the set of dependencies between source files."""
def __init__(self, sources):
"""Initializes the tree with a set of sources.
Args:
sources: A set of JavaScript sources.
Raises:
MultipleProvideError: A namespace is provided by muplitple sources.
NamespaceNotFoundError: A namespace is required but never provided.
"""
self._sources = sources
self._provides_map = dict()
# Ensure nothing was provided twice.
for source in sources:
for provide in source.provides:
if provide in self._provides_map:
raise MultipleProvideError(
provide, [self._provides_map[provide], source])
self._provides_map[provide] = source
# Check that all required namespaces are provided.
for source in sources:
for require in source.requires:
if require not in self._provides_map:
raise NamespaceNotFoundError(require, source)
def GetDependencies(self, required_namespaces):
"""Get source dependencies, in order, for the given namespaces.
Args:
required_namespaces: A string (for one) or list (for one or more) of
namespaces.
Returns:
A list of source objects that provide those namespaces and all
requirements, in dependency order.
Raises:
NamespaceNotFoundError: A namespace is requested but doesn't exist.
CircularDependencyError: A cycle is detected in the dependency tree.
"""
if isinstance(required_namespaces, str):
required_namespaces = [required_namespaces]
deps_sources = []
for namespace in required_namespaces:
for source in DepsTree._ResolveDependencies(
namespace, [], self._provides_map, []):
if source not in deps_sources:
deps_sources.append(source)
return deps_sources
@staticmethod
def _ResolveDependencies(required_namespace, deps_list, provides_map,
traversal_path):
"""Resolve dependencies for Closure source files.
Follows the dependency tree down and builds a list of sources in dependency
order. This function will recursively call itself to fill all dependencies
below the requested namespaces, and then append its sources at the end of
the list.
Args:
required_namespace: String of required namespace.
deps_list: List of sources in dependency order. This function will append
the required source once all of its dependencies are satisfied.
provides_map: Map from namespace to source that provides it.
traversal_path: List of namespaces of our path from the root down the
dependency/recursion tree. Used to identify cyclical dependencies.
This is a list used as a stack -- when the function is entered, the
current namespace is pushed and popped right before returning.
Each recursive call will check that the current namespace does not
appear in the list, throwing a CircularDependencyError if it does.
Returns:
The given deps_list object filled with sources in dependency order.
Raises:
NamespaceNotFoundError: A namespace is requested but doesn't exist.
CircularDependencyError: A cycle is detected in the dependency tree.
"""
source = provides_map.get(required_namespace)
if not source:
raise NamespaceNotFoundError(required_namespace)
if required_namespace in traversal_path:
traversal_path.append(required_namespace) # do this *after* the test
# This must be a cycle.
raise CircularDependencyError(traversal_path)
# If we don't have the source yet, we'll have to visit this namespace and
# add the required dependencies to deps_list.
if source not in deps_list:
traversal_path.append(required_namespace)
for require in source.requires:
# Append all other dependencies before we append our own.
DepsTree._ResolveDependencies(require, deps_list, provides_map,
traversal_path)
deps_list.append(source)
traversal_path.pop()
return deps_list
class BaseDepsTreeError(Exception):
"""Base DepsTree error."""
def __init__(self):
Exception.__init__(self)
class CircularDependencyError(BaseDepsTreeError):
"""Raised when a dependency cycle is encountered."""
def __init__(self, dependency_list):
BaseDepsTreeError.__init__(self)
self._dependency_list = dependency_list
def __str__(self):
return ('Encountered circular dependency:\n%s\n' %
'\n'.join(self._dependency_list))
class MultipleProvideError(BaseDepsTreeError):
"""Raised when a namespace is provided more than once."""
def __init__(self, namespace, sources):
BaseDepsTreeError.__init__(self)
self._namespace = namespace
self._sources = sources
def __str__(self):
source_strs = map(str, self._sources)
return ('Namespace "%s" provided more than once in sources:\n%s\n' %
(self._namespace, '\n'.join(source_strs)))
class NamespaceNotFoundError(BaseDepsTreeError):
"""Raised when a namespace is requested but not provided."""
def __init__(self, namespace, source=None):
BaseDepsTreeError.__init__(self)
self._namespace = namespace
self._source = source
def __str__(self):
msg = 'Namespace "%s" never provided.' % self._namespace
if self._source:
msg += ' Required in %s' % self._source
return msg
#!/usr/bin/env python
#
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit test for depstree."""
__author__ = 'nnaze@google.com (Nathan Naze)'
import unittest
import depstree
def _GetProvides(sources):
"""Get all namespaces provided by a collection of sources."""
provides = set()
for source in sources:
provides.update(source.provides)
return provides
class MockSource(object):
"""Mock Source file."""
def __init__(self, provides, requires):
self.provides = set(provides)
self.requires = set(requires)
def __repr__(self):
return 'MockSource %s' % self.provides
class DepsTreeTestCase(unittest.TestCase):
"""Unit test for DepsTree. Tests several common situations and errors."""
def AssertValidDependencies(self, deps_list):
"""Validates a dependency list.
Asserts that a dependency list is valid: For every source in the list,
ensure that every require is provided by a source earlier in the list.
Args:
deps_list: A list of sources that should be in dependency order.
"""
for i in range(len(deps_list)):
source = deps_list[i]
previous_provides = _GetProvides(deps_list[:i])
for require in source.requires:
self.assertTrue(
require in previous_provides,
'Namespace "%s" not provided before required by %s' % (
require, source))
def testSimpleDepsTree(self):
a = MockSource(['A'], ['B', 'C'])
b = MockSource(['B'], [])
c = MockSource(['C'], ['D'])
d = MockSource(['D'], ['E'])
e = MockSource(['E'], [])
tree = depstree.DepsTree([a, b, c, d, e])
self.AssertValidDependencies(tree.GetDependencies('A'))
self.AssertValidDependencies(tree.GetDependencies('B'))
self.AssertValidDependencies(tree.GetDependencies('C'))
self.AssertValidDependencies(tree.GetDependencies('D'))
self.AssertValidDependencies(tree.GetDependencies('E'))
def testCircularDependency(self):
# Circular deps
a = MockSource(['A'], ['B'])
b = MockSource(['B'], ['C'])
c = MockSource(['C'], ['A'])
tree = depstree.DepsTree([a, b, c])
self.assertRaises(depstree.CircularDependencyError,
tree.GetDependencies, 'A')
def testRequiresUndefinedNamespace(self):
a = MockSource(['A'], ['B'])
b = MockSource(['B'], ['C'])
c = MockSource(['C'], ['D']) # But there is no D.
def MakeDepsTree():
return depstree.DepsTree([a, b, c])
self.assertRaises(depstree.NamespaceNotFoundError, MakeDepsTree)
def testDepsForMissingNamespace(self):
a = MockSource(['A'], ['B'])
b = MockSource(['B'], [])
tree = depstree.DepsTree([a, b])
# There is no C.
self.assertRaises(depstree.NamespaceNotFoundError,
tree.GetDependencies, 'C')
def testMultipleRequires(self):
a = MockSource(['A'], ['B'])
b = MockSource(['B'], ['C'])
c = MockSource(['C'], [])
d = MockSource(['D'], ['B'])
tree = depstree.DepsTree([a, b, c, d])
self.AssertValidDependencies(tree.GetDependencies(['D', 'A']))
if __name__ == '__main__':
unittest.main()
#!/usr/bin/env python
#
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Generates out a Closure deps.js file given a list of JavaScript sources.
Paths can be specified as arguments or (more commonly) specifying trees
with the flags (call with --help for descriptions).
Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...]
"""
import json
import logging
import optparse
import os
import posixpath
import shlex
import sys
import source
import treescan
__author__ = 'nnaze@google.com (Nathan Naze)'
def MakeDepsFile(source_map):
"""Make a generated deps file.
Args:
source_map: A dict map of the source path to source.Source object.
Returns:
str, A generated deps file source.
"""
# Write in path alphabetical order
paths = sorted(source_map.keys())
lines = []
for path in paths:
js_source = source_map[path]
# We don't need to add entries that don't provide anything.
if js_source.provides:
lines.append(_GetDepsLine(path, js_source))
return ''.join(lines)
def _GetDepsLine(path, js_source):
"""Get a deps.js file string for a source."""
provides = _ToJsSrc(sorted(js_source.provides))
requires = _ToJsSrc(sorted(js_source.requires))
module = 'true' if js_source.is_goog_module else 'false'
return 'goog.addDependency(\'%s?_\' + sessionStorage[\'build\'], %s, %s, %s);\n' % (
path, provides, requires, module)
def _ToJsSrc(arr):
"""Convert a python arr to a js source string."""
return json.dumps(arr).replace('"', '\'')
def _GetOptionsParser():
"""Get the options parser."""
parser = optparse.OptionParser(__doc__)
parser.add_option('--output_file',
dest='output_file',
action='store',
help=('If specified, write output to this path instead of '
'writing to standard output.'))
parser.add_option('--root',
dest='roots',
default=[],
action='append',
help='A root directory to scan for JS source files. '
'Paths of JS files in generated deps file will be '
'relative to this path. This flag may be specified '
'multiple times.')
parser.add_option('--root_with_prefix',
dest='roots_with_prefix',
default=[],
action='append',
help='A root directory to scan for JS source files, plus '
'a prefix (if either contains a space, surround with '
'quotes). Paths in generated deps file will be relative '
'to the root, but preceded by the prefix. This flag '
'may be specified multiple times.')
parser.add_option('--path_with_depspath',
dest='paths_with_depspath',
default=[],
action='append',
help='A path to a source file and an alternate path to '
'the file in the generated deps file (if either contains '
'a space, surround with whitespace). This flag may be '
'specified multiple times.')
return parser
def _NormalizePathSeparators(path):
"""Replaces OS-specific path separators with POSIX-style slashes.
Args:
path: str, A file path.
Returns:
str, The path with any OS-specific path separators (such as backslash on
Windows) replaced with URL-compatible forward slashes. A no-op on systems
that use POSIX paths.
"""
return path.replace(os.sep, posixpath.sep)
def _GetRelativePathToSourceDict(root, prefix=''):
"""Scans a top root directory for .js sources.
Args:
root: str, Root directory.
prefix: str, Prefix for returned paths.
Returns:
dict, A map of relative paths (with prefix, if given), to source.Source
objects.
"""
# Remember and restore the cwd when we're done. We work from the root so
# that paths are relative from the root.
start_wd = os.getcwd()
os.chdir(root)
path_to_source = {}
for path in treescan.ScanTreeForJsFiles('.'):
prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path))
path_to_source[prefixed_path] = source.Source(source.GetFileContents(path))
os.chdir(start_wd)
return path_to_source
def _GetPair(s):
"""Return a string as a shell-parsed tuple. Two values expected."""
try:
# shlex uses '\' as an escape character, so they must be escaped.
s = s.replace('\\', '\\\\')
first, second = shlex.split(s)
return (first, second)
except:
raise Exception('Unable to parse input line as a pair: %s' % s)
def main():
"""CLI frontend to MakeDepsFile."""
logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
level=logging.INFO)
options, args = _GetOptionsParser().parse_args()
path_to_source = {}
# Roots without prefixes
for root in options.roots:
path_to_source.update(_GetRelativePathToSourceDict(root))
# Roots with prefixes
for root_and_prefix in options.roots_with_prefix:
root, prefix = _GetPair(root_and_prefix)
path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix))
# Source paths
for path in args:
path_to_source[path] = source.Source(source.GetFileContents(path))
# Source paths with alternate deps paths
for path_with_depspath in options.paths_with_depspath:
srcpath, depspath = _GetPair(path_with_depspath)
path_to_source[depspath] = source.Source(source.GetFileContents(srcpath))
# Make our output pipe.
if options.output_file:
out = open(options.output_file, 'w')
else:
out = sys.stdout
out.write(('// This file was autogenerated by %s.\n' %
os.path.basename(__file__)))
out.write('// Please do not edit.\n')
out.write(MakeDepsFile(path_to_source))
if __name__ == '__main__':
main()
#!/usr/bin/env python
#
# Copyright 2010 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit test for depswriter."""
__author__ = 'johnlenz@google.com (John Lenz)'
import unittest
import depswriter
class MockSource(object):
"""Mock Source file."""
def __init__(self, provides, requires):
self.provides = set(provides)
self.requires = set(requires)
self.is_goog_module = False
def __repr__(self):
return 'MockSource %s' % self.provides
class DepsWriterTestCase(unittest.TestCase):
"""Unit test for depswriter."""
def testMakeDepsFile(self):
sources = {}
sources['test.js'] = MockSource(['A'], ['B', 'C'])
deps = depswriter.MakeDepsFile(sources)
self.assertEqual(
'goog.addDependency(\'test.js\', [\'A\'], [\'B\', \'C\'], false);\n',
deps)
def testMakeDepsFileUnicode(self):
sources = {}
sources['test.js'] = MockSource([u'A'], [u'B', u'C'])
deps = depswriter.MakeDepsFile(sources)
self.assertEqual(
'goog.addDependency(\'test.js\', [\'A\'], [\'B\', \'C\'], false);\n',
deps)
if __name__ == '__main__':
unittest.main()
# Copyright 2010 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Utility to use the Closure Compiler CLI from Python."""
import logging
import os
import re
import subprocess
import tempfile
# Pulls just the major and minor version numbers from the first line of
# 'java -version'. Versions are in the format of [0-9]+\.[0-9]+\..* See:
# http://www.oracle.com/technetwork/java/javase/versioning-naming-139433.html
_VERSION_REGEX = re.compile(r'"([0-9]+)\.([0-9]+)')
class JsCompilerError(Exception):
"""Raised if there's an error in calling the compiler."""
pass
def _GetJavaVersionString():
"""Get the version string from the Java VM."""
return subprocess.check_output(['java', '-version'], stderr=subprocess.STDOUT)
def _ParseJavaVersion(version_string):
"""Returns a 2-tuple for the current version of Java installed.
Args:
version_string: String of the Java version (e.g. '1.7.2-ea').
Returns:
The major and minor versions, as a 2-tuple (e.g. (1, 7)).
"""
match = _VERSION_REGEX.search(version_string)
if match:
version = tuple(int(x, 10) for x in match.groups())
assert len(version) == 2
return version
def _JavaSupports32BitMode():
"""Determines whether the JVM supports 32-bit mode on the platform."""
# Suppresses process output to stderr and stdout from showing up in the
# console as we're only trying to determine 32-bit JVM support.
supported = False
try:
devnull = open(os.devnull, 'wb')
return subprocess.call(['java', '-d32', '-version'],
stdout=devnull,
stderr=devnull) == 0
except IOError:
pass
else:
devnull.close()
return supported
def _GetJsCompilerArgs(compiler_jar_path, java_version, jvm_flags):
"""Assembles arguments for call to JsCompiler."""
if java_version < (1, 7):
raise JsCompilerError('Closure Compiler requires Java 1.7 or higher. '
'Please visit http://www.java.com/getjava')
args = ['java']
# Add JVM flags we believe will produce the best performance. See
# https://groups.google.com/forum/#!topic/closure-library-discuss/7w_O9-vzlj4
# Attempt 32-bit mode if available (Java 7 on Mac OS X does not support 32-bit
# mode, for example).
if _JavaSupports32BitMode():
args += ['-d32']
# Prefer the "client" VM.
args += ['-client']
# Add JVM flags, if any
if jvm_flags:
args += jvm_flags
# Add the application JAR.
args += ['-jar', compiler_jar_path]
return args
def _GetFlagFile(source_paths, compiler_flags):
"""Writes given source paths and compiler flags to a --flagfile.
The given source_paths will be written as '--js' flags and the compiler_flags
are written as-is.
Args:
source_paths: List of string js source paths.
compiler_flags: List of string compiler flags.
Returns:
The file to which the flags were written.
"""
args = []
for path in source_paths:
args += ['--js', path]
# Add compiler flags, if any.
if compiler_flags:
args += compiler_flags
flags_file = tempfile.NamedTemporaryFile(delete=False)
flags_file.write(' '.join(args))
flags_file.close()
return flags_file
def Compile(compiler_jar_path,
source_paths,
jvm_flags=None,
compiler_flags=None):
"""Prepares command-line call to Closure Compiler.
Args:
compiler_jar_path: Path to the Closure compiler .jar file.
source_paths: Source paths to build, in order.
jvm_flags: A list of additional flags to pass on to JVM.
compiler_flags: A list of additional flags to pass on to Closure Compiler.
Returns:
The compiled source, as a string, or None if compilation failed.
"""
java_version = _ParseJavaVersion(str(_GetJavaVersionString()))
args = _GetJsCompilerArgs(compiler_jar_path, java_version, jvm_flags)
# Write source path arguments to flag file for avoiding "The filename or
# extension is too long" error in big projects. See
# https://github.com/google/closure-library/pull/678
flags_file = _GetFlagFile(source_paths, compiler_flags)
args += ['--flagfile', flags_file.name]
logging.info('Compiling with the following command: %s', ' '.join(args))
try:
return subprocess.check_output(args)
except subprocess.CalledProcessError:
raise JsCompilerError('JavaScript compilation failed.')
finally:
os.remove(flags_file.name)
#!/usr/bin/env python
#
# Copyright 2013 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Unit test for depstree."""
__author__ = 'nnaze@google.com (Nathan Naze)'
import os
import unittest
import jscompiler
class JsCompilerTestCase(unittest.TestCase):
"""Unit tests for jscompiler module."""
def testGetFlagFile(self):
flags_file = jscompiler._GetFlagFile(
['path/to/src1.js', 'path/to/src2.js'], ['--test_compiler_flag'])
def file_get_contents(filename):
with open(filename) as f:
content = f.read()
f.close()
return content
flags_file_content = file_get_contents(flags_file.name)
os.remove(flags_file.name)
self.assertEqual(
'--js path/to/src1.js --js path/to/src2.js --test_compiler_flag',
flags_file_content)
def testGetJsCompilerArgs(self):
original_check = jscompiler._JavaSupports32BitMode
jscompiler._JavaSupports32BitMode = lambda: False
args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7),
['--test_jvm_flag'])
self.assertEqual(
['java', '-client', '--test_jvm_flag', '-jar',
'path/to/jscompiler.jar'], args)
def CheckJava15RaisesError():
jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 5),
['--test_jvm_flag'])
self.assertRaises(jscompiler.JsCompilerError, CheckJava15RaisesError)
jscompiler._JavaSupports32BitMode = original_check
def testGetJsCompilerArgs32BitJava(self):
original_check = jscompiler._JavaSupports32BitMode
# Should include the -d32 flag only if 32-bit Java is supported by the
# system.
jscompiler._JavaSupports32BitMode = lambda: True
args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7),
['--test_jvm_flag'])
self.assertEqual(
['java', '-d32', '-client', '--test_jvm_flag', '-jar',
'path/to/jscompiler.jar'], args)
# Should exclude the -d32 flag if 32-bit Java is not supported by the
# system.
jscompiler._JavaSupports32BitMode = lambda: False
args = jscompiler._GetJsCompilerArgs('path/to/jscompiler.jar', (1, 7),
['--test_jvm_flag'])
self.assertEqual(
['java', '-client', '--test_jvm_flag', '-jar',
'path/to/jscompiler.jar'], args)
jscompiler._JavaSupports32BitMode = original_check
def testGetJavaVersion(self):
def assertVersion(expected, version_string):
self.assertEquals(expected, jscompiler._ParseJavaVersion(version_string))
assertVersion((1, 7), _TEST_JAVA_VERSION_STRING)
assertVersion((1, 6), _TEST_JAVA_NESTED_VERSION_STRING)
assertVersion((1, 4), 'java version "1.4.0_03-ea"')
_TEST_JAVA_VERSION_STRING = """\
openjdk version "1.7.0-google-v5"
OpenJDK Runtime Environment (build 1.7.0-google-v5-64327-39803485)
OpenJDK Server VM (build 22.0-b10, mixed mode)
"""
_TEST_JAVA_NESTED_VERSION_STRING = """\
Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8
java version "1.6.0_35"
Java(TM) SE Runtime Environment (build 1.6.0_35-b10-428-11M3811)
Java HotSpot(TM) Client VM (build 20.10-b01-428, mixed mode)
"""
if __name__ == '__main__':
unittest.main()
# Copyright 2009 The Closure Library Authors. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS-IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Scans a source JS file for its provided and required namespaces.
Simple class to scan a JavaScript file and express its dependencies.
"""
__author__ = 'nnaze@google.com'
import codecs
import re
_BASE_REGEX_STRING = r'^\s*goog\.%s\(\s*[\'"](.+)[\'"]\s*\)'
_MODULE_REGEX = re.compile(_BASE_REGEX_STRING % 'module')
_PROVIDE_REGEX = re.compile(_BASE_REGEX_STRING % 'provide')
_REQUIRE_REGEX_STRING = (r'^\s*(?:(?:var|let|const)\s+[a-zA-Z_$][a-zA-Z0-9$_]*'
r'\s*=\s*)?goog\.require\(\s*[\'"](.+)[\'"]\s*\)')
_REQUIRES_REGEX = re.compile(_REQUIRE_REGEX_STRING)
class Source(object):
"""Scans a JavaScript source for its provided and required namespaces."""
# Matches a "/* ... */" comment.
# Note: We can't definitively distinguish a "/*" in a string literal without a
# state machine tokenizer. We'll assume that a line starting with whitespace
# and "/*" is a comment.
_COMMENT_REGEX = re.compile(
r"""
^\s* # Start of a new line and whitespace
/\* # Opening "/*"
.*? # Non greedy match of any characters (including newlines)
\*/ # Closing "*/""",
re.MULTILINE | re.DOTALL | re.VERBOSE)
def __init__(self, source):
"""Initialize a source.
Args:
source: str, The JavaScript source.
"""
self.provides = set()
self.requires = set()
self.is_goog_module = False
self._source = source
self._ScanSource()
def GetSource(self):
"""Get the source as a string."""
return self._source
@classmethod
def _StripComments(cls, source):
return cls._COMMENT_REGEX.sub('', source)
@classmethod
def _HasProvideGoogFlag(cls, source):
"""Determines whether the @provideGoog flag is in a comment."""
for comment_content in cls._COMMENT_REGEX.findall(source):
if '@provideGoog' in comment_content:
return True
return False
def _ScanSource(self):
"""Fill in provides and requires by scanning the source."""
stripped_source = self._StripComments(self.GetSource())
source_lines = stripped_source.splitlines()
for line in source_lines:
match = _PROVIDE_REGEX.match(line)
if match:
self.provides.add(match.group(1))
match = _MODULE_REGEX.match(line)
if match:
self.provides.add(match.group(1))
self.is_goog_module = True
match = _REQUIRES_REGEX.match(line)
if match:
self.requires.add(match.group(1))
# Closure's base file implicitly provides 'goog'.
# This is indicated with the @provideGoog flag.
if self._HasProvideGoogFlag(self.GetSource()):
if len(self.provides) or len(self.requires):
raise Exception(
'Base file should not provide or require namespaces.')
self.provides.add('goog')
def GetFileContents(path):
"""Get a file's contents as a string.
Args:
path: str, Path to file.
Returns:
str, Contents of file.
Raises:
IOError: An error occurred opening or reading the file.
"""
fileobj = None
try:
fileobj = codecs.open(path, encoding='utf-8-sig')
return fileobj.read()
except IOError as error:
raise IOError('An error occurred opening or reading the file: %s. %s'
% (path, error))
finally:
if fileobj is not None:
fileobj.close()