1beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org# Copyright (c) 2012 Google Inc. All rights reserved.
2df8224662e615bd36cf8bebae8e58c017201f998sgk@chromium.org# Use of this source code is governed by a BSD-style license that can be
3df8224662e615bd36cf8bebae8e58c017201f998sgk@chromium.org# found in the LICENSE file.
4df8224662e615bd36cf8bebae8e58c017201f998sgk@chromium.org
542d9b6828d9442eedd144ae9b1937be280b19e01thakis@chromium.orgfrom __future__ import with_statement
642d9b6828d9442eedd144ae9b1937be280b19e01thakis@chromium.org
79f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.orgimport collections
8c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.comimport errno
9c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.comimport filecmp
10f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.orgimport os.path
110be4796de4a43311feb4cca55725f685f5182581mark@chromium.orgimport re
12c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.comimport tempfile
130954e99802e6c9c911b87c862c7ca179e4a4a114bradnelson@google.comimport sys
14f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
15f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org
16f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org# A minimal memoizing decorator. It'll blow up if the args aren't immutable,
17f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org# among other "problems".
18f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.orgclass memoize(object):
19f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org  def __init__(self, func):
20f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org    self.func = func
21f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org    self.cache = {}
22f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org  def __call__(self, *args):
23f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org    try:
24f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org      return self.cache[args]
25f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org    except KeyError:
26f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org      result = self.func(*args)
27f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org      self.cache[args] = result
28f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org      return result
29f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org
30f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org
31d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.orgclass GypError(Exception):
32d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org  """Error class representing an error, which is to be presented
33d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org  to the user.  The main entry point will catch and display this.
34d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org  """
35d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org  pass
36d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org
37d3d94fcdfb9745d6784065cade320c2462924df9sbc@chromium.org
38e162e112f7db7dca52aa668996e828315777bbaagspencer@google.comdef ExceptionAppend(e, msg):
39e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com  """Append a message to the given exception's message."""
40e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com  if not e.args:
41e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com    e.args = (msg,)
42e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com  elif len(e.args) == 1:
43e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com    e.args = (str(e.args[0]) + ' ' + msg,)
44e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com  else:
45e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com    e.args = (str(e.args[0]) + ' ' + msg,) + e.args[1:]
46e162e112f7db7dca52aa668996e828315777bbaagspencer@google.com
47f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
48beca12a56c0772fc8263ad7ca12ca022b8671c1csbaigdef FindQualifiedTargets(target, qualified_list):
49beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig  """
50beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig  Given a list of qualified targets, return the qualified targets for the
51beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig  specified |target|.
52beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig  """
53beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig  return [t for t in qualified_list if ParseQualifiedTarget(t)[1] == target]
54beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig
55beca12a56c0772fc8263ad7ca12ca022b8671c1csbaig
56821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.comdef ParseQualifiedTarget(target):
57821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # Splits a qualified target into a build file, target name and toolset.
58f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
593adee9349f3d710dd2159907072bc20413e82b46bradnelson@google.com  # NOTE: rsplit is used to disambiguate the Windows drive letter separator.
603adee9349f3d710dd2159907072bc20413e82b46bradnelson@google.com  target_split = target.rsplit(':', 1)
61f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  if len(target_split) == 2:
62821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    [build_file, target] = target_split
63821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  else:
64821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    build_file = None
65821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
66821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  target_split = target.rsplit('#', 1)
67821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  if len(target_split) == 2:
68821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    [target, toolset] = target_split
69821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  else:
70821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    toolset = None
71821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
72821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  return [build_file, target, toolset]
73821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
74821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
75821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.comdef ResolveTarget(build_file, target, toolset):
76821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # This function resolves a target into a canonical form:
77821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # - a fully defined build file, either absolute or relative to the current
78821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # directory
79821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # - a target name
80821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # - a toolset
81821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  #
82821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # build_file is the file relative to which 'target' is defined.
83821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # target is the qualified target.
84821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # toolset is the default toolset for that target.
85821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  [parsed_build_file, target, parsed_toolset] = ParseQualifiedTarget(target)
86821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
87821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  if parsed_build_file:
88821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    if build_file:
89821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # If a relative path, parsed_build_file is relative to the directory
90821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # containing build_file.  If build_file is not in the current directory,
91821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # parsed_build_file is not a usable path as-is.  Resolve it by
92821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # interpreting it as relative to build_file.  If parsed_build_file is
93821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # absolute, it is usable as a path regardless of the current directory,
94821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      # and os.path.join will return it as-is.
95821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      build_file = os.path.normpath(os.path.join(os.path.dirname(build_file),
96821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com                                                 parsed_build_file))
97b7b7f31931e287d42e767840249194570d07f975bradnelson@google.com      # Further (to handle cases like ../cwd), make it relative to cwd)
9827aaada3227da7f17b70ae773808dc149da70cc4bradnelson@google.com      if not os.path.isabs(build_file):
9927aaada3227da7f17b70ae773808dc149da70cc4bradnelson@google.com        build_file = RelativePath(build_file, '.')
100821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    else:
101821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com      build_file = parsed_build_file
102821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
103821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  if parsed_toolset:
104821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    toolset = parsed_toolset
105821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com
106821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  return [build_file, target, toolset]
107f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
108f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
109821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.comdef BuildFile(fully_qualified_target):
110821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # Extracts the build file from the fully qualified target.
111821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  return ParseQualifiedTarget(fully_qualified_target)[0]
112f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
113f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
11454d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.orgdef GetEnvironFallback(var_list, default):
11554d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org  """Look up a key in the environment, with fallback to secondary keys
11654d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org  and finally falling back to a default value."""
11754d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org  for var in var_list:
11854d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org    if var in os.environ:
11954d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org      return os.environ[var]
12054d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org  return default
12154d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org
12254d2f6fe6d8a7b9d9786bd1f8540df6b4f46b83fsbc@chromium.org
123821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.comdef QualifiedTarget(build_file, target, toolset):
124f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # "Qualified" means the file that a target was defined in and the target
125821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # name, separated by a colon, suffixed by a # and the toolset name:
126821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  # /path/to/file.gyp:target_name#toolset
127821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  fully_qualified = build_file + ':' + target
128821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  if toolset:
129821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com    fully_qualified = fully_qualified + '#' + toolset
130821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  return fully_qualified
131f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
132f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
133f5a52009064941d0b668717e5cd92945643acb95thakis@chromium.org@memoize
134f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.orgdef RelativePath(path, relative_to):
135f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # Assuming both |path| and |relative_to| are relative to the current
136f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # directory, returns a relative path that identifies path relative to
137f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # relative_to.
138f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
1392724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  # Convert to normalized (and therefore absolute paths).
1402724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  path = os.path.realpath(path)
1412724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  relative_to = os.path.realpath(relative_to)
142f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
143ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org  # On Windows, we can't create a relative path to a different drive, so just
144ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org  # use the absolute path.
145ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org  if sys.platform == 'win32':
146ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org    if (os.path.splitdrive(path)[0].lower() !=
147ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org        os.path.splitdrive(relative_to)[0].lower()):
148ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org      return path
149ecf073cd05739a0848ad6b0928b48b4776ef1ff2scottmg@chromium.org
15011b2df7f913292655872b5fda2044ee225780118mmoss@chromium.org  # Split the paths into components.
15111b2df7f913292655872b5fda2044ee225780118mmoss@chromium.org  path_split = path.split(os.path.sep)
15211b2df7f913292655872b5fda2044ee225780118mmoss@chromium.org  relative_to_split = relative_to.split(os.path.sep)
153f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
154f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # Determine how much of the prefix the two paths share.
155f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  prefix_len = len(os.path.commonprefix([path_split, relative_to_split]))
156f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
157f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # Put enough ".." components to back up out of relative_to to the common
158f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # prefix, and then append the part of path_split after the common prefix.
159f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  relative_split = [os.path.pardir] * (len(relative_to_split) - prefix_len) + \
160f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org                   path_split[prefix_len:]
161f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org
162eeccd9e5d515b046419a3e90115ba4ecafad632fmark@chromium.org  if len(relative_split) == 0:
163eeccd9e5d515b046419a3e90115ba4ecafad632fmark@chromium.org    # The paths were the same.
164eeccd9e5d515b046419a3e90115ba4ecafad632fmark@chromium.org    return ''
165eeccd9e5d515b046419a3e90115ba4ecafad632fmark@chromium.org
166f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  # Turn it back into a string and we're done.
167f6d65a99233197a7b2e194d19be63ec85ff635d2mark@chromium.org  return os.path.join(*relative_split)
1680be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
1690be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
1702724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org@memoize
1712724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.orgdef InvertRelativePath(path, toplevel_dir=None):
1722724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  """Given a path like foo/bar that is relative to toplevel_dir, return
1732724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  the inverse relative path back to the toplevel_dir.
1742724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org
1752724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
1762724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  should always produce the empty string, unless the path contains symlinks.
1772724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  """
1782724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  if not path:
1792724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org    return path
1802724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  toplevel_dir = '.' if toplevel_dir is None else toplevel_dir
1812724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org  return RelativePath(toplevel_dir, os.path.join(toplevel_dir, path))
1822724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org
1832724da9e5f57957af502ff40ed586fd6351a5f71thakis@chromium.org
184c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.orgdef FixIfRelativePath(path, relative_to):
185c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  # Like RelativePath but returns |path| unchanged if it is absolute.
186c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  if os.path.isabs(path):
187c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org    return path
188c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  return RelativePath(path, relative_to)
189c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org
190c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org
191c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.orgdef UnrelativePath(path, relative_to):
192c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  # Assuming that |relative_to| is relative to the current directory, and |path|
193c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  # is a path relative to the dirname of |relative_to|, returns a path that
194c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  # identifies |path| relative to the current directory.
195c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  rel_dir = os.path.dirname(relative_to)
196c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org  return os.path.normpath(os.path.join(rel_dir, path))
197c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org
198c01356ff413b71e18e80d081b33e9cc1cb923440evan@chromium.org
199e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# re objects used by EncodePOSIXShellArgument.  See IEEE 1003.1 XCU.2.2 at
200e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_02
201e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# and the documentation for various shells.
2020be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2030be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# _quote is a pattern that should match any argument that needs to be quoted
2040be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# with double-quotes by EncodePOSIXShellArgument.  It matches the following
2050be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# characters appearing anywhere in an argument:
206e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   \t, \n, space  parameter separators
207e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   #              comments
208e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   $              expansions (quoted to always expand within one argument)
209ada32f03ce7c5fdd53398cc0fb112049d252c5edmark@chromium.org#   %              called out by IEEE 1003.1 XCU.2.2
210e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   &              job control
211e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   '              quoting
212e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   (, )           subshell execution
213ada32f03ce7c5fdd53398cc0fb112049d252c5edmark@chromium.org#   *, ?, [        pathname expansion
214e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   ;              command delimiter
215e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   <, >, |        redirection
216ada32f03ce7c5fdd53398cc0fb112049d252c5edmark@chromium.org#   =              assignment
217ada32f03ce7c5fdd53398cc0fb112049d252c5edmark@chromium.org#   {, }           brace expansion (bash)
218e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#   ~              tilde expansion
2190be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# It also matches the empty string, because "" (or '') is the only way to
2200be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# represent an empty string literal argument to a POSIX shell.
221e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#
222e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# This does not match the characters in _escape, because those need to be
223e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# backslash-escaped regardless of whether they appear in a double-quoted
224e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# string.
225ada32f03ce7c5fdd53398cc0fb112049d252c5edmark@chromium.org_quote = re.compile('[\t\n #$%&\'()*;<=>?[{|}~]|^$')
2260be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2270be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# _escape is a pattern that should match any character that needs to be
2280be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# escaped with a backslash, whether or not the argument matched the _quote
229e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# pattern.  _escape is used with re.sub to backslash anything in _escape's
230e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# first match group, hence the (parentheses) in the regular expression.
231e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org#
232e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org# _escape matches the following characters appearing anywhere in an argument:
2330be4796de4a43311feb4cca55725f685f5182581mark@chromium.org#   "  to prevent POSIX shells from interpreting this character for quoting
2340be4796de4a43311feb4cca55725f685f5182581mark@chromium.org#   \  to prevent POSIX shells from interpreting this character for escaping
2350be4796de4a43311feb4cca55725f685f5182581mark@chromium.org#   `  to prevent POSIX shells from interpreting this character for command
2360be4796de4a43311feb4cca55725f685f5182581mark@chromium.org#      substitution
2370be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# Missing from this list is $, because the desired behavior of
2380be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# EncodePOSIXShellArgument is to permit parameter (variable) expansion.
2390be4796de4a43311feb4cca55725f685f5182581mark@chromium.org#
2400be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# Also missing from this list is !, which bash will interpret as the history
2410be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# expansion character when history is enabled.  bash does not enable history
2420be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# by default in non-interactive shells, so this is not thought to be a problem.
2430be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# ! was omitted from this list because bash interprets "\!" as a literal string
2440be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# including the backslash character (avoiding history expansion but retaining
2450be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# the backslash), which would not be correct for argument encoding.  Handling
2460be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# this case properly would also be problematic because bash allows the history
2470be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# character to be changed with the histchars shell variable.  Fortunately,
2480be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# as history is not enabled in non-interactive shells and
2490be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# EncodePOSIXShellArgument is only expected to encode for non-interactive
2500be4796de4a43311feb4cca55725f685f5182581mark@chromium.org# shells, there is no room for error here by ignoring !.
251e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org_escape = re.compile(r'(["\\`])')
2520be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2530be4796de4a43311feb4cca55725f685f5182581mark@chromium.orgdef EncodePOSIXShellArgument(argument):
2540be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  """Encodes |argument| suitably for consumption by POSIX shells.
2550be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2560be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  argument may be quoted and escaped as necessary to ensure that POSIX shells
2570be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  treat the returned value as a literal representing the argument passed to
2580be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  this function.  Parameter (variable) expansions beginning with $ are allowed
2590be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  to remain intact without escaping the $, to allow the argument to contain
2600be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  references to variables to be expanded by the shell.
2610be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  """
2620be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
263955725e2ea029ef03db7fdefff8a3ac0d09c7f6dmark@chromium.org  if not isinstance(argument, str):
264955725e2ea029ef03db7fdefff8a3ac0d09c7f6dmark@chromium.org    argument = str(argument)
265955725e2ea029ef03db7fdefff8a3ac0d09c7f6dmark@chromium.org
2660be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  if _quote.search(argument):
2670be4796de4a43311feb4cca55725f685f5182581mark@chromium.org    quote = '"'
2680be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  else:
2690be4796de4a43311feb4cca55725f685f5182581mark@chromium.org    quote = ''
2700be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
271e757c99b5ca060877a8b00f0d3e7723e6462e71dmark@chromium.org  encoded = quote + re.sub(_escape, r'\\\1', argument) + quote
2720be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2730be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  return encoded
2740be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2750be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2760be4796de4a43311feb4cca55725f685f5182581mark@chromium.orgdef EncodePOSIXShellList(list):
2770be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  """Encodes |list| suitably for consumption by POSIX shells.
2780be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2790be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  Returns EncodePOSIXShellArgument for each item in list, and joins them
2800be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  together using the space character as an argument separator.
2810be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  """
2820be4796de4a43311feb4cca55725f685f5182581mark@chromium.org
2830be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  encoded_arguments = []
2840be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  for argument in list:
2850be4796de4a43311feb4cca55725f685f5182581mark@chromium.org    encoded_arguments.append(EncodePOSIXShellArgument(argument))
2860be4796de4a43311feb4cca55725f685f5182581mark@chromium.org  return ' '.join(encoded_arguments)
2876f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
2886f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
2896f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.orgdef DeepDependencyTargets(target_dicts, roots):
290714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com  """Returns the recursive list of target dependencies."""
2916f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  dependencies = set()
292714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com  pending = set(roots)
293714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com  while pending:
294714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    # Pluck out one.
295714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    r = pending.pop()
296714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    # Skip if visited already.
297714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    if r in dependencies:
298714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com      continue
299714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    # Add it.
300714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    dependencies.add(r)
301714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    # Add its children.
3026f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org    spec = target_dicts[r]
303714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    pending.update(set(spec.get('dependencies', [])))
304714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com    pending.update(set(spec.get('dependencies_original', [])))
305714c0d76150ae9ba7ab68cd8d9cf0dc1423186a9bradnelson@google.com  return list(dependencies - set(roots))
3066f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
3076f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
3086f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.orgdef BuildFileTargets(target_list, build_file):
3096f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  """From a target_list, returns the subset from the specified build_file.
3106f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  """
311821df584822a40838f71013666f9a9ccd3fa8fcfbradnelson@google.com  return [p for p in target_list if BuildFile(p) == build_file]
3126f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
3136f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org
3146f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.orgdef AllTargets(target_list, target_dicts, build_file):
3156f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  """Returns all targets (direct and dependencies) for the specified build_file.
3166f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  """
3176f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  bftargets = BuildFileTargets(target_list, build_file)
3186f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  deptargets = DeepDependencyTargets(target_dicts, bftargets)
3196f97116adcdccf08c5fcd90ecd035023fffcc069sgk@chromium.org  return bftargets + deptargets
320c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
321c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
322c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.comdef WriteOnDiff(filename):
323c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  """Write to a file only if the new contents differ.
324c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
325c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  Arguments:
326c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    filename: name of the file to potentially write to.
327c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  Returns:
328c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    A file like object which will write to temporary file and only overwrite
329c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    the target if it differs (on close).
330c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  """
331c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
332c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  class Writer:
333c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    """Wrapper around file which only covers the target if it differs."""
334c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    def __init__(self):
335c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      # Pick temporary file.
336c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      tmp_fd, self.tmp_path = tempfile.mkstemp(
337c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          suffix='.tmp',
338c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          prefix=os.path.split(filename)[1] + '.gyp.',
339c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          dir=os.path.split(filename)[0])
340c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      try:
341c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        self.tmp_file = os.fdopen(tmp_fd, 'wb')
342c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      except Exception:
343c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        # Don't leave turds behind.
344c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        os.unlink(self.tmp_path)
345c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        raise
346c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
347c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    def __getattr__(self, attrname):
348c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      # Delegate everything else to self.tmp_file
349c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      return getattr(self.tmp_file, attrname)
350c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
351c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com    def close(self):
352c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      try:
353c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        # Close tmp file.
354c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        self.tmp_file.close()
355c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        # Determine if different.
356c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        same = False
357c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        try:
358c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          same = filecmp.cmp(self.tmp_path, filename, False)
359c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        except OSError, e:
360c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          if e.errno != errno.ENOENT:
361c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com            raise
362c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
363c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        if same:
364c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # The new file is identical to the old one, just get rid of the new
365c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # one.
366c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          os.unlink(self.tmp_path)
367c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        else:
368c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # The new file is different from the old one, or there is no old one.
369c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # Rename the new file to the permanent name.
370c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          #
371c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # tempfile.mkstemp uses an overly restrictive mode, resulting in a
372c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # file that can only be read by the owner, regardless of the umask.
373c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # There's no reason to not respect the umask here, which means that
374c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # an extra hoop is required to fetch it and reset the new file's mode.
375c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          #
376c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # No way to get the umask without setting a new one?  Set a safe one
377c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          # and then set it back to the old value.
378c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          umask = os.umask(077)
379c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          os.umask(umask)
380c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          os.chmod(self.tmp_path, 0666 & ~umask)
381ca2d05c4a124c6685179af154f770397ca593bc9sgk@chromium.org          if sys.platform == 'win32' and os.path.exists(filename):
3820954e99802e6c9c911b87c862c7ca179e4a4a114bradnelson@google.com            # NOTE: on windows (but not cygwin) rename will not replace an
3830954e99802e6c9c911b87c862c7ca179e4a4a114bradnelson@google.com            # existing file, so it must be preceded with a remove. Sadly there
3840954e99802e6c9c911b87c862c7ca179e4a4a114bradnelson@google.com            # is no way to make the switch atomic.
3850954e99802e6c9c911b87c862c7ca179e4a4a114bradnelson@google.com            os.remove(filename)
386c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com          os.rename(self.tmp_path, filename)
387c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com      except Exception:
388c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        # Don't leave turds behind.
389c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        os.unlink(self.tmp_path)
390c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com        raise
391c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com
392c376dab1c44c36bf607de14b8204c27bd6e6ce0bbradnelson@google.com  return Writer()
393b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org
394b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org
39578d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.orgdef EnsureDirExists(path):
39678d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org  """Make sure the directory for |path| exists."""
39778d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org  try:
39878d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org    os.makedirs(os.path.dirname(path))
39978d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org  except OSError:
40078d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org    pass
40178d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org
40278d5366f3040ddfd2fcdcde6e2e4dde87c102850scottmg@chromium.org
4031911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.orgdef GetFlavor(params):
4041911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org  """Returns |params.flavor| if it's set, the system's default flavor else."""
4051911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org  flavors = {
406315c6d71c86e47204686753a91c10693b0e17d9cthakis@chromium.org    'cygwin': 'win',
407315c6d71c86e47204686753a91c10693b0e17d9cthakis@chromium.org    'win32': 'win',
4081911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org    'darwin': 'mac',
4091911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org  }
4104a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org
4114a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org  if 'flavor' in params:
4124a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org    return params['flavor']
4134a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org  if sys.platform in flavors:
4144a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org    return flavors[sys.platform]
4154a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org  if sys.platform.startswith('sunos'):
4164a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org    return 'solaris'
4174a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org  if sys.platform.startswith('freebsd'):
4184a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org    return 'freebsd'
419a0d04986c479a6b48f496d2b377ea2b657ab89fdthakis@chromium.org  if sys.platform.startswith('openbsd'):
420a0d04986c479a6b48f496d2b377ea2b657ab89fdthakis@chromium.org    return 'openbsd'
4212324c0e93231f814f0022e55bf7e25a8433eb8e9thakis@chromium.org  if sys.platform.startswith('aix'):
4222324c0e93231f814f0022e55bf7e25a8433eb8e9thakis@chromium.org    return 'aix'
4234a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org
4244a9683bce5c77825c369f12f537d6503399cc9e0thakis@chromium.org  return 'linux'
4251911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org
4261911d51eee5277f2e1ba9e13a6dbad77b544283cthakis@chromium.org
427beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.orgdef CopyTool(flavor, out_path):
428b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org  """Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
429beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  to |out_path|."""
430b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org  # aix and solaris just need flock emulation. mac and win use more complicated
431b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org  # support scripts.
432b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org  prefix = {
433b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org      'aix': 'flock',
434b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org      'solaris': 'flock',
435b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org      'mac': 'mac',
436b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org      'win': 'win'
437b8e58959f8767948aecbc3d8235927401cf3f93dthakis@chromium.org      }.get(flavor, None)
438beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  if not prefix:
439beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org    return
440beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org
441beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  # Slurp input file.
442beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  source_path = os.path.join(
443beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org      os.path.dirname(os.path.abspath(__file__)), '%s_tool.py' % prefix)
444beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  with open(source_path) as source_file:
445beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org    source = source_file.readlines()
446beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org
447beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  # Add header and write it out.
448beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix)
449beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  with open(tool_path, 'w') as tool_file:
450beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org    tool_file.write(
451beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org        ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:]))
452beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org
453beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  # Make file executable.
454beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org  os.chmod(tool_path, 0755)
455beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org
456beab9a2ff9fcbd27d4b1c951d3fc6e6f09230223thakis@chromium.org
457b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org# From Alex Martelli,
458b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
459b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org# ASPN: Python Cookbook: Remove duplicates from a sequence
460b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org# First comment, dated 2001/10/13.
461b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org# (Also in the printed Python Cookbook.)
462b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org
463b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.orgdef uniquer(seq, idfun=None):
464b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org    if idfun is None:
46569c5522a3dc3d0906ab836350f53dae34a9897d3bradnelson@google.com        idfun = lambda x: x
466b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org    seen = {}
467b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org    result = []
468b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org    for item in seq:
469b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org        marker = idfun(item)
470b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org        if marker in seen: continue
471b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org        seen[marker] = 1
472b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org        result.append(item)
473b06ffce92c3e2a37af13f1c35cb098b735c0f1f7sgk@chromium.org    return result
4745c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com
4755c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com
4769f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org# Based on http://code.activestate.com/recipes/576694/.
4779f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.orgclass OrderedSet(collections.MutableSet):
4789f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __init__(self, iterable=None):
4799f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    self.end = end = []
4809f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    end += [None, end, end]         # sentinel node for doubly linked list
4819f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    self.map = {}                   # key --> [key, prev, next]
4829f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if iterable is not None:
4839f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      self |= iterable
4849f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
4859f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __len__(self):
4869f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    return len(self.map)
4879f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
4889f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __contains__(self, key):
4899f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    return key in self.map
4909f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
4919f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def add(self, key):
4929f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if key not in self.map:
4939f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      end = self.end
4949f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      curr = end[1]
4959f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      curr[2] = end[1] = self.map[key] = [key, curr, end]
4969f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
4979f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def discard(self, key):
4989f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if key in self.map:
49963de31e166cc41c3a5d8b9ee4fde53585f5e8d80mark@chromium.org      key, prev_item, next_item = self.map.pop(key)
50063de31e166cc41c3a5d8b9ee4fde53585f5e8d80mark@chromium.org      prev_item[2] = next_item
50163de31e166cc41c3a5d8b9ee4fde53585f5e8d80mark@chromium.org      next_item[1] = prev_item
5029f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5039f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __iter__(self):
5049f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    end = self.end
5059f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    curr = end[2]
5069f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    while curr is not end:
5079f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      yield curr[0]
5089f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      curr = curr[2]
5099f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5109f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __reversed__(self):
5119f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    end = self.end
5129f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    curr = end[1]
5139f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    while curr is not end:
5149f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      yield curr[0]
5159f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      curr = curr[1]
5169f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
51763de31e166cc41c3a5d8b9ee4fde53585f5e8d80mark@chromium.org  # The second argument is an addition that causes a pylint warning.
51863de31e166cc41c3a5d8b9ee4fde53585f5e8d80mark@chromium.org  def pop(self, last=True):  # pylint: disable=W0221
5199f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if not self:
5209f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      raise KeyError('set is empty')
5219f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    key = self.end[1][0] if last else self.end[2][0]
5229f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    self.discard(key)
5239f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    return key
5249f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5259f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __repr__(self):
5269f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if not self:
5279f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      return '%s()' % (self.__class__.__name__,)
5289f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    return '%s(%r)' % (self.__class__.__name__, list(self))
5299f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5309f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def __eq__(self, other):
5319f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    if isinstance(other, OrderedSet):
5329f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      return len(self) == len(other) and list(self) == list(other)
5339f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    return set(self) == set(other)
5349f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5359f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  # Extensions to the recipe.
5369f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org  def update(self, iterable):
5379f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org    for i in iterable:
5389f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org      if i not in self:
5399f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org        self.add(i)
5409f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5419f14141afe735cf6ae93532ddd97f98fa9ea2821thakis@chromium.org
5425c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.comclass CycleError(Exception):
5435c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  """An exception raised when an unexpected cycle is detected."""
5445c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  def __init__(self, nodes):
5455c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    self.nodes = nodes
5465c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  def __str__(self):
5475c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    return 'CycleError: cycle involving: ' + str(self.nodes)
5485c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com
5495c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com
5505c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.comdef TopologicallySorted(graph, get_edges):
5515c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  """Topologically sort based on a user provided edge definition.
5525c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com
5535c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  Args:
5545c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    graph: A list of node names.
5555c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    get_edges: A function mapping from node name to a hashable collection
5565c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com               of node names which this node has outgoing edges to.
5575c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  Returns:
5585c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    A list containing all of the node in graph in topological order.
5595c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    It is assumed that calling get_edges once for each node and caching is
5605c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    cheaper than repeatedly calling get_edges.
5615c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  Raises:
5625c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    CycleError in the event of a cycle.
5635c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  Example:
5645c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
5655c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    def GetEdges(node):
5665c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com      return re.findall(r'\$\(([^))]\)', graph[node])
5675c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    print TopologicallySorted(graph.keys(), GetEdges)
5685c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    ==>
5695c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    ['a', 'c', b']
5705c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  """
5715c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  get_edges = memoize(get_edges)
5725c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  visited = set()
5735c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  visiting = set()
5745c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  ordered_nodes = []
5755c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  def Visit(node):
5765c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    if node in visiting:
5775c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com      raise CycleError(visiting)
5785c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    if node in visited:
5795c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com      return
5805c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    visited.add(node)
5815c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    visiting.add(node)
5825c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    for neighbor in get_edges(node):
5835c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com      Visit(neighbor)
5845c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    visiting.remove(node)
5855c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    ordered_nodes.insert(0, node)
5865c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  for node in sorted(graph):
5875c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com    Visit(node)
5885c770284c79f04c043b706f14e464d5ccf74569fbradnelson@google.com  return ordered_nodes
589