js_binary.py 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. # Copyright 2017 The Chromium Authors. All rights reserved.
  2. # Use of this source code is governed by a BSD-style license that can be
  3. # found in the LICENSE file.
  4. """Used by a js_binary action to compile javascript files.
  5. This script takes in a list of sources and dependencies and compiles them all
  6. together into a single compiled .js file. The dependencies are ordered in a
  7. post-order, left-to-right traversal order. If multiple instances of the same
  8. source file are read, only the first is kept. The script can also take in
  9. optional --flags argument which will add custom flags to the compiler. Any
  10. extern files can also be passed in using the --extern flag.
  11. """
  12. from __future__ import print_function
  13. import argparse
  14. import os
  15. import sys
  16. import compiler
  17. def ParseDepList(dep):
  18. """Parses a dependency list, returns |sources, deps, externs|."""
  19. assert os.path.isfile(dep), (dep +
  20. ' is not a js_library target')
  21. with open(dep, 'r') as dep_list:
  22. lines = dep_list.read().splitlines()
  23. assert 'deps:' in lines, dep + ' is not formated correctly, missing "deps:"'
  24. deps_start = lines.index('deps:')
  25. assert 'externs:' in lines, dep + ' is not formated correctly, missing "externs:"'
  26. externs_start = lines.index('externs:')
  27. return (lines[1:deps_start],
  28. lines[deps_start+1:externs_start],
  29. lines[externs_start+1:])
  30. # Cache, to avoid reading the same file twice in the dependency tree and
  31. # processing its dependencies again.
  32. depcache = {}
  33. def AppendUnique(items, new_items):
  34. """Append items in |new_items| to |items|, avoiding duplicates."""
  35. # Note this is O(n*n), and assumes |new_items| is already unique, but this is
  36. # not a bottleneck overall.
  37. items += [i for i in new_items if i not in items]
  38. def CrawlDepsTree(deps):
  39. """Parses the dependency tree creating a post-order listing of sources."""
  40. global depcache
  41. if len(deps) == 0:
  42. return ([], [])
  43. new_sources = []
  44. new_externs = []
  45. for dep in deps:
  46. if dep in depcache:
  47. cur_sources, cur_externs = depcache[dep]
  48. else:
  49. dep_sources, dep_deps, dep_externs = ParseDepList(dep)
  50. cur_sources, cur_externs = CrawlDepsTree(dep_deps)
  51. # Add child dependencies of this node before the current node, then cache.
  52. AppendUnique(cur_sources, dep_sources)
  53. AppendUnique(cur_externs, dep_externs)
  54. depcache[dep] = (cur_sources, cur_externs)
  55. # Add the current node's sources and dedupe.
  56. AppendUnique(new_sources, cur_sources)
  57. AppendUnique(new_externs, cur_externs)
  58. return new_sources, new_externs
  59. def CrawlRootDepsTree(deps, target_sources, target_externs):
  60. """Parses the dependency tree and adds target sources."""
  61. sources, externs = CrawlDepsTree(deps)
  62. AppendUnique(sources, target_sources)
  63. AppendUnique(externs, target_externs)
  64. return sources, externs
  65. def main():
  66. parser = argparse.ArgumentParser()
  67. parser.add_argument('-c', '--compiler', required=True,
  68. help='Path to compiler')
  69. parser.add_argument('-s', '--sources', nargs='*', default=[],
  70. help='List of js source files')
  71. parser.add_argument('-o', '--output', required=False,
  72. help='Compile to output')
  73. parser.add_argument('--chunks', action='store_true',
  74. help='Compile each source to its own chunk')
  75. parser.add_argument('--chunk_suffix', required=False,
  76. help='String appended to the source when naming a chunk')
  77. parser.add_argument('-d', '--deps', nargs='*', default=[],
  78. help='List of js_libarary dependencies')
  79. parser.add_argument('-b', '--bootstrap',
  80. help='A file to include before all others')
  81. parser.add_argument('-cf', '--config', nargs='*', default=[],
  82. help='A list of files to include after bootstrap and '
  83. 'before all others')
  84. parser.add_argument('-f', '--flags', nargs='*', default=[],
  85. help='A list of custom flags to pass to the compiler. '
  86. 'Do not include leading dashes')
  87. parser.add_argument('-e', '--externs', nargs='*', default=[],
  88. help='A list of extern files to pass to the compiler')
  89. parser.add_argument('-co', '--checks-only', action='store_true',
  90. help='Only performs checks and writes an empty output')
  91. args = parser.parse_args()
  92. # If --chunks is used, args.sources will be added later
  93. sources, externs = CrawlRootDepsTree(args.deps, [] if args.chunks else args.sources, args.externs)
  94. compiler_args = ['--%s' % flag for flag in args.flags]
  95. compiler_args += ['--externs=%s' % e for e in externs]
  96. if not args.chunks:
  97. compiler_args += [
  98. '--js_output_file',
  99. args.output,
  100. ]
  101. compiler_args += [
  102. '--js',
  103. ]
  104. if args.bootstrap:
  105. compiler_args += [args.bootstrap]
  106. compiler_args += args.config
  107. compiler_args += sources
  108. if args.chunks:
  109. chunk_suffix = args.chunk_suffix
  110. common_chunk_name = 'common' + chunk_suffix
  111. compiler_args += [
  112. '--chunk_output_path_prefix {}'.format(args.output),
  113. '--chunk {}:auto'.format(common_chunk_name)
  114. ]
  115. for s in args.sources:
  116. # '//path/to/target.js' becomes 'target'
  117. chunk_name = '{}{}'.format(s.split('/')[-1].split('.')[0], chunk_suffix)
  118. compiler_args += [
  119. '--chunk {}:1:{}: {}'.format(chunk_name, common_chunk_name, s)
  120. ]
  121. if args.checks_only:
  122. compiler_args += ['--checks-only']
  123. open(args.output, 'w').close()
  124. returncode, errors = compiler.Compiler().run_jar(args.compiler, compiler_args)
  125. if returncode != 0:
  126. print(args.compiler, ' '.join(compiler_args))
  127. print(errors)
  128. return returncode
  129. if __name__ == '__main__':
  130. sys.exit(main())