depswriter.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212
  1. #!/usr/bin/env python
  2. #
  3. # Copyright 2009 The Closure Library Authors. All Rights Reserved.
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS-IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. """Generates out a Closure deps.js file given a list of JavaScript sources.
  17. Paths can be specified as arguments or (more commonly) specifying trees
  18. with the flags (call with --help for descriptions).
  19. Usage: depswriter.py [path/to/js1.js [path/to/js2.js] ...]
  20. """
  21. import json
  22. import logging
  23. import optparse
  24. import os
  25. import posixpath
  26. import shlex
  27. import sys
  28. import source
  29. import treescan
  30. __author__ = 'nnaze@google.com (Nathan Naze)'
  31. def MakeDepsFile(source_map):
  32. """Make a generated deps file.
  33. Args:
  34. source_map: A dict map of the source path to source.Source object.
  35. Returns:
  36. str, A generated deps file source.
  37. """
  38. # Write in path alphabetical order
  39. paths = sorted(source_map.keys())
  40. lines = []
  41. for path in paths:
  42. js_source = source_map[path]
  43. # We don't need to add entries that don't provide anything.
  44. if js_source.provides:
  45. lines.append(_GetDepsLine(path, js_source))
  46. return ''.join(lines)
  47. def _GetDepsLine(path, js_source):
  48. """Get a deps.js file string for a source."""
  49. provides = _ToJsSrc(sorted(js_source.provides))
  50. requires = _ToJsSrc(sorted(js_source.requires))
  51. module = 'true' if js_source.is_goog_module else 'false'
  52. return 'goog.addDependency(\'%s\', %s, %s, %s);\n' % (
  53. path, provides, requires, module)
  54. def _ToJsSrc(arr):
  55. """Convert a python arr to a js source string."""
  56. return json.dumps(arr).replace('"', '\'')
  57. def _GetOptionsParser():
  58. """Get the options parser."""
  59. parser = optparse.OptionParser(__doc__)
  60. parser.add_option('--output_file',
  61. dest='output_file',
  62. action='store',
  63. help=('If specified, write output to this path instead of '
  64. 'writing to standard output.'))
  65. parser.add_option('--root',
  66. dest='roots',
  67. default=[],
  68. action='append',
  69. help='A root directory to scan for JS source files. '
  70. 'Paths of JS files in generated deps file will be '
  71. 'relative to this path. This flag may be specified '
  72. 'multiple times.')
  73. parser.add_option('--root_with_prefix',
  74. dest='roots_with_prefix',
  75. default=[],
  76. action='append',
  77. help='A root directory to scan for JS source files, plus '
  78. 'a prefix (if either contains a space, surround with '
  79. 'quotes). Paths in generated deps file will be relative '
  80. 'to the root, but preceded by the prefix. This flag '
  81. 'may be specified multiple times.')
  82. parser.add_option('--path_with_depspath',
  83. dest='paths_with_depspath',
  84. default=[],
  85. action='append',
  86. help='A path to a source file and an alternate path to '
  87. 'the file in the generated deps file (if either contains '
  88. 'a space, surround with whitespace). This flag may be '
  89. 'specified multiple times.')
  90. return parser
  91. def _NormalizePathSeparators(path):
  92. """Replaces OS-specific path separators with POSIX-style slashes.
  93. Args:
  94. path: str, A file path.
  95. Returns:
  96. str, The path with any OS-specific path separators (such as backslash on
  97. Windows) replaced with URL-compatible forward slashes. A no-op on systems
  98. that use POSIX paths.
  99. """
  100. return path.replace(os.sep, posixpath.sep)
  101. def _GetRelativePathToSourceDict(root, prefix=''):
  102. """Scans a top root directory for .js sources.
  103. Args:
  104. root: str, Root directory.
  105. prefix: str, Prefix for returned paths.
  106. Returns:
  107. dict, A map of relative paths (with prefix, if given), to source.Source
  108. objects.
  109. """
  110. # Remember and restore the cwd when we're done. We work from the root so
  111. # that paths are relative from the root.
  112. start_wd = os.getcwd()
  113. os.chdir(root)
  114. path_to_source = {}
  115. for path in treescan.ScanTreeForJsFiles('.'):
  116. prefixed_path = _NormalizePathSeparators(os.path.join(prefix, path))
  117. path_to_source[prefixed_path] = source.Source(source.GetFileContents(path))
  118. os.chdir(start_wd)
  119. return path_to_source
  120. def _GetPair(s):
  121. """Return a string as a shell-parsed tuple. Two values expected."""
  122. try:
  123. # shlex uses '\' as an escape character, so they must be escaped.
  124. s = s.replace('\\', '\\\\')
  125. first, second = shlex.split(s)
  126. return (first, second)
  127. except:
  128. raise Exception('Unable to parse input line as a pair: %s' % s)
  129. def main():
  130. """CLI frontend to MakeDepsFile."""
  131. logging.basicConfig(format=(sys.argv[0] + ': %(message)s'),
  132. level=logging.INFO)
  133. options, args = _GetOptionsParser().parse_args()
  134. path_to_source = {}
  135. # Roots without prefixes
  136. for root in options.roots:
  137. path_to_source.update(_GetRelativePathToSourceDict(root))
  138. # Roots with prefixes
  139. for root_and_prefix in options.roots_with_prefix:
  140. root, prefix = _GetPair(root_and_prefix)
  141. path_to_source.update(_GetRelativePathToSourceDict(root, prefix=prefix))
  142. # Source paths
  143. for path in args:
  144. path_to_source[path] = source.Source(source.GetFileContents(path))
  145. # Source paths with alternate deps paths
  146. for path_with_depspath in options.paths_with_depspath:
  147. srcpath, depspath = _GetPair(path_with_depspath)
  148. path_to_source[depspath] = source.Source(source.GetFileContents(srcpath))
  149. # Make our output pipe.
  150. if options.output_file:
  151. out = open(options.output_file, 'w')
  152. else:
  153. out = sys.stdout
  154. out.write(('// This file was autogenerated by %s.\n' %
  155. os.path.basename(__file__)))
  156. out.write('// Please do not edit.\n')
  157. out.write(MakeDepsFile(path_to_source))
  158. if __name__ == '__main__':
  159. main()