"Fossies" - the Fresh Open Source Software Archive

Member "Atom/resources/app/apm/node_modules/node-gyp/gyp/pylib/gyp/common.py" (8 Mar 2017, 20063 Bytes) of archive /windows/misc/atom-windows.zip:


As a special service "Fossies" has tried to format the requested source page into HTML format using (guessed) Python source code syntax highlighting (style: standard) with prefixed line numbers. Alternatively you can here view or download the uninterpreted source code file.

    1 # Copyright (c) 2012 Google Inc. 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 
    5 from __future__ import with_statement
    6 
    7 import collections
    8 import errno
    9 import filecmp
   10 import os.path
   11 import re
   12 import tempfile
   13 import sys
   14 
   15 
   16 # A minimal memoizing decorator. It'll blow up if the args aren't immutable,
   17 # among other "problems".
   18 class memoize(object):
   19   def __init__(self, func):
   20     self.func = func
   21     self.cache = {}
   22   def __call__(self, *args):
   23     try:
   24       return self.cache[args]
   25     except KeyError:
   26       result = self.func(*args)
   27       self.cache[args] = result
   28       return result
   29 
   30 
   31 class GypError(Exception):
   32   """Error class representing an error, which is to be presented
   33   to the user.  The main entry point will catch and display this.
   34   """
   35   pass
   36 
   37 
   38 def ExceptionAppend(e, msg):
   39   """Append a message to the given exception's message."""
   40   if not e.args:
   41     e.args = (msg,)
   42   elif len(e.args) == 1:
   43     e.args = (str(e.args[0]) + ' ' + msg,)
   44   else:
   45     e.args = (str(e.args[0]) + ' ' + msg,) + e.args[1:]
   46 
   47 
   48 def FindQualifiedTargets(target, qualified_list):
   49   """
   50   Given a list of qualified targets, return the qualified targets for the
   51   specified |target|.
   52   """
   53   return [t for t in qualified_list if ParseQualifiedTarget(t)[1] == target]
   54 
   55 
   56 def ParseQualifiedTarget(target):
   57   # Splits a qualified target into a build file, target name and toolset.
   58 
   59   # NOTE: rsplit is used to disambiguate the Windows drive letter separator.
   60   target_split = target.rsplit(':', 1)
   61   if len(target_split) == 2:
   62     [build_file, target] = target_split
   63   else:
   64     build_file = None
   65 
   66   target_split = target.rsplit('#', 1)
   67   if len(target_split) == 2:
   68     [target, toolset] = target_split
   69   else:
   70     toolset = None
   71 
   72   return [build_file, target, toolset]
   73 
   74 
   75 def ResolveTarget(build_file, target, toolset):
   76   # This function resolves a target into a canonical form:
   77   # - a fully defined build file, either absolute or relative to the current
   78   # directory
   79   # - a target name
   80   # - a toolset
   81   #
   82   # build_file is the file relative to which 'target' is defined.
   83   # target is the qualified target.
   84   # toolset is the default toolset for that target.
   85   [parsed_build_file, target, parsed_toolset] = ParseQualifiedTarget(target)
   86 
   87   if parsed_build_file:
   88     if build_file:
   89       # If a relative path, parsed_build_file is relative to the directory
   90       # containing build_file.  If build_file is not in the current directory,
   91       # parsed_build_file is not a usable path as-is.  Resolve it by
   92       # interpreting it as relative to build_file.  If parsed_build_file is
   93       # absolute, it is usable as a path regardless of the current directory,
   94       # and os.path.join will return it as-is.
   95       build_file = os.path.normpath(os.path.join(os.path.dirname(build_file),
   96                                                  parsed_build_file))
   97       # Further (to handle cases like ../cwd), make it relative to cwd)
   98       if not os.path.isabs(build_file):
   99         build_file = RelativePath(build_file, '.')
  100     else:
  101       build_file = parsed_build_file
  102 
  103   if parsed_toolset:
  104     toolset = parsed_toolset
  105 
  106   return [build_file, target, toolset]
  107 
  108 
  109 def BuildFile(fully_qualified_target):
  110   # Extracts the build file from the fully qualified target.
  111   return ParseQualifiedTarget(fully_qualified_target)[0]
  112 
  113 
  114 def GetEnvironFallback(var_list, default):
  115   """Look up a key in the environment, with fallback to secondary keys
  116   and finally falling back to a default value."""
  117   for var in var_list:
  118     if var in os.environ:
  119       return os.environ[var]
  120   return default
  121 
  122 
  123 def QualifiedTarget(build_file, target, toolset):
  124   # "Qualified" means the file that a target was defined in and the target
  125   # name, separated by a colon, suffixed by a # and the toolset name:
  126   # /path/to/file.gyp:target_name#toolset
  127   fully_qualified = build_file + ':' + target
  128   if toolset:
  129     fully_qualified = fully_qualified + '#' + toolset
  130   return fully_qualified
  131 
  132 
  133 @memoize
  134 def RelativePath(path, relative_to, follow_path_symlink=True):
  135   # Assuming both |path| and |relative_to| are relative to the current
  136   # directory, returns a relative path that identifies path relative to
  137   # relative_to.
  138   # If |follow_symlink_path| is true (default) and |path| is a symlink, then
  139   # this method returns a path to the real file represented by |path|. If it is
  140   # false, this method returns a path to the symlink. If |path| is not a
  141   # symlink, this option has no effect.
  142 
  143   # Convert to normalized (and therefore absolute paths).
  144   if follow_path_symlink:
  145     path = os.path.realpath(path)
  146   else:
  147     path = os.path.abspath(path)
  148   relative_to = os.path.realpath(relative_to)
  149 
  150   # On Windows, we can't create a relative path to a different drive, so just
  151   # use the absolute path.
  152   if sys.platform == 'win32':
  153     if (os.path.splitdrive(path)[0].lower() !=
  154         os.path.splitdrive(relative_to)[0].lower()):
  155       return path
  156 
  157   # Split the paths into components.
  158   path_split = path.split(os.path.sep)
  159   relative_to_split = relative_to.split(os.path.sep)
  160 
  161   # Determine how much of the prefix the two paths share.
  162   prefix_len = len(os.path.commonprefix([path_split, relative_to_split]))
  163 
  164   # Put enough ".." components to back up out of relative_to to the common
  165   # prefix, and then append the part of path_split after the common prefix.
  166   relative_split = [os.path.pardir] * (len(relative_to_split) - prefix_len) + \
  167                    path_split[prefix_len:]
  168 
  169   if len(relative_split) == 0:
  170     # The paths were the same.
  171     return ''
  172 
  173   # Turn it back into a string and we're done.
  174   return os.path.join(*relative_split)
  175 
  176 
  177 @memoize
  178 def InvertRelativePath(path, toplevel_dir=None):
  179   """Given a path like foo/bar that is relative to toplevel_dir, return
  180   the inverse relative path back to the toplevel_dir.
  181 
  182   E.g. os.path.normpath(os.path.join(path, InvertRelativePath(path)))
  183   should always produce the empty string, unless the path contains symlinks.
  184   """
  185   if not path:
  186     return path
  187   toplevel_dir = '.' if toplevel_dir is None else toplevel_dir
  188   return RelativePath(toplevel_dir, os.path.join(toplevel_dir, path))
  189 
  190 
  191 def FixIfRelativePath(path, relative_to):
  192   # Like RelativePath but returns |path| unchanged if it is absolute.
  193   if os.path.isabs(path):
  194     return path
  195   return RelativePath(path, relative_to)
  196 
  197 
  198 def UnrelativePath(path, relative_to):
  199   # Assuming that |relative_to| is relative to the current directory, and |path|
  200   # is a path relative to the dirname of |relative_to|, returns a path that
  201   # identifies |path| relative to the current directory.
  202   rel_dir = os.path.dirname(relative_to)
  203   return os.path.normpath(os.path.join(rel_dir, path))
  204 
  205 
  206 # re objects used by EncodePOSIXShellArgument.  See IEEE 1003.1 XCU.2.2 at
  207 # http://www.opengroup.org/onlinepubs/009695399/utilities/xcu_chap02.html#tag_02_02
  208 # and the documentation for various shells.
  209 
  210 # _quote is a pattern that should match any argument that needs to be quoted
  211 # with double-quotes by EncodePOSIXShellArgument.  It matches the following
  212 # characters appearing anywhere in an argument:
  213 #   \t, \n, space  parameter separators
  214 #   #              comments
  215 #   $              expansions (quoted to always expand within one argument)
  216 #   %              called out by IEEE 1003.1 XCU.2.2
  217 #   &              job control
  218 #   '              quoting
  219 #   (, )           subshell execution
  220 #   *, ?, [        pathname expansion
  221 #   ;              command delimiter
  222 #   <, >, |        redirection
  223 #   =              assignment
  224 #   {, }           brace expansion (bash)
  225 #   ~              tilde expansion
  226 # It also matches the empty string, because "" (or '') is the only way to
  227 # represent an empty string literal argument to a POSIX shell.
  228 #
  229 # This does not match the characters in _escape, because those need to be
  230 # backslash-escaped regardless of whether they appear in a double-quoted
  231 # string.
  232 _quote = re.compile('[\t\n #$%&\'()*;<=>?[{|}~]|^$')
  233 
  234 # _escape is a pattern that should match any character that needs to be
  235 # escaped with a backslash, whether or not the argument matched the _quote
  236 # pattern.  _escape is used with re.sub to backslash anything in _escape's
  237 # first match group, hence the (parentheses) in the regular expression.
  238 #
  239 # _escape matches the following characters appearing anywhere in an argument:
  240 #   "  to prevent POSIX shells from interpreting this character for quoting
  241 #   \  to prevent POSIX shells from interpreting this character for escaping
  242 #   `  to prevent POSIX shells from interpreting this character for command
  243 #      substitution
  244 # Missing from this list is $, because the desired behavior of
  245 # EncodePOSIXShellArgument is to permit parameter (variable) expansion.
  246 #
  247 # Also missing from this list is !, which bash will interpret as the history
  248 # expansion character when history is enabled.  bash does not enable history
  249 # by default in non-interactive shells, so this is not thought to be a problem.
  250 # ! was omitted from this list because bash interprets "\!" as a literal string
  251 # including the backslash character (avoiding history expansion but retaining
  252 # the backslash), which would not be correct for argument encoding.  Handling
  253 # this case properly would also be problematic because bash allows the history
  254 # character to be changed with the histchars shell variable.  Fortunately,
  255 # as history is not enabled in non-interactive shells and
  256 # EncodePOSIXShellArgument is only expected to encode for non-interactive
  257 # shells, there is no room for error here by ignoring !.
  258 _escape = re.compile(r'(["\\`])')
  259 
  260 def EncodePOSIXShellArgument(argument):
  261   """Encodes |argument| suitably for consumption by POSIX shells.
  262 
  263   argument may be quoted and escaped as necessary to ensure that POSIX shells
  264   treat the returned value as a literal representing the argument passed to
  265   this function.  Parameter (variable) expansions beginning with $ are allowed
  266   to remain intact without escaping the $, to allow the argument to contain
  267   references to variables to be expanded by the shell.
  268   """
  269 
  270   if not isinstance(argument, str):
  271     argument = str(argument)
  272 
  273   if _quote.search(argument):
  274     quote = '"'
  275   else:
  276     quote = ''
  277 
  278   encoded = quote + re.sub(_escape, r'\\\1', argument) + quote
  279 
  280   return encoded
  281 
  282 
  283 def EncodePOSIXShellList(list):
  284   """Encodes |list| suitably for consumption by POSIX shells.
  285 
  286   Returns EncodePOSIXShellArgument for each item in list, and joins them
  287   together using the space character as an argument separator.
  288   """
  289 
  290   encoded_arguments = []
  291   for argument in list:
  292     encoded_arguments.append(EncodePOSIXShellArgument(argument))
  293   return ' '.join(encoded_arguments)
  294 
  295 
  296 def DeepDependencyTargets(target_dicts, roots):
  297   """Returns the recursive list of target dependencies."""
  298   dependencies = set()
  299   pending = set(roots)
  300   while pending:
  301     # Pluck out one.
  302     r = pending.pop()
  303     # Skip if visited already.
  304     if r in dependencies:
  305       continue
  306     # Add it.
  307     dependencies.add(r)
  308     # Add its children.
  309     spec = target_dicts[r]
  310     pending.update(set(spec.get('dependencies', [])))
  311     pending.update(set(spec.get('dependencies_original', [])))
  312   return list(dependencies - set(roots))
  313 
  314 
  315 def BuildFileTargets(target_list, build_file):
  316   """From a target_list, returns the subset from the specified build_file.
  317   """
  318   return [p for p in target_list if BuildFile(p) == build_file]
  319 
  320 
  321 def AllTargets(target_list, target_dicts, build_file):
  322   """Returns all targets (direct and dependencies) for the specified build_file.
  323   """
  324   bftargets = BuildFileTargets(target_list, build_file)
  325   deptargets = DeepDependencyTargets(target_dicts, bftargets)
  326   return bftargets + deptargets
  327 
  328 
  329 def WriteOnDiff(filename):
  330   """Write to a file only if the new contents differ.
  331 
  332   Arguments:
  333     filename: name of the file to potentially write to.
  334   Returns:
  335     A file like object which will write to temporary file and only overwrite
  336     the target if it differs (on close).
  337   """
  338 
  339   class Writer(object):
  340     """Wrapper around file which only covers the target if it differs."""
  341     def __init__(self):
  342       # Pick temporary file.
  343       tmp_fd, self.tmp_path = tempfile.mkstemp(
  344           suffix='.tmp',
  345           prefix=os.path.split(filename)[1] + '.gyp.',
  346           dir=os.path.split(filename)[0])
  347       try:
  348         self.tmp_file = os.fdopen(tmp_fd, 'wb')
  349       except Exception:
  350         # Don't leave turds behind.
  351         os.unlink(self.tmp_path)
  352         raise
  353 
  354     def __getattr__(self, attrname):
  355       # Delegate everything else to self.tmp_file
  356       return getattr(self.tmp_file, attrname)
  357 
  358     def close(self):
  359       try:
  360         # Close tmp file.
  361         self.tmp_file.close()
  362         # Determine if different.
  363         same = False
  364         try:
  365           same = filecmp.cmp(self.tmp_path, filename, False)
  366         except OSError, e:
  367           if e.errno != errno.ENOENT:
  368             raise
  369 
  370         if same:
  371           # The new file is identical to the old one, just get rid of the new
  372           # one.
  373           os.unlink(self.tmp_path)
  374         else:
  375           # The new file is different from the old one, or there is no old one.
  376           # Rename the new file to the permanent name.
  377           #
  378           # tempfile.mkstemp uses an overly restrictive mode, resulting in a
  379           # file that can only be read by the owner, regardless of the umask.
  380           # There's no reason to not respect the umask here, which means that
  381           # an extra hoop is required to fetch it and reset the new file's mode.
  382           #
  383           # No way to get the umask without setting a new one?  Set a safe one
  384           # and then set it back to the old value.
  385           umask = os.umask(077)
  386           os.umask(umask)
  387           os.chmod(self.tmp_path, 0666 & ~umask)
  388           if sys.platform == 'win32' and os.path.exists(filename):
  389             # NOTE: on windows (but not cygwin) rename will not replace an
  390             # existing file, so it must be preceded with a remove. Sadly there
  391             # is no way to make the switch atomic.
  392             os.remove(filename)
  393           os.rename(self.tmp_path, filename)
  394       except Exception:
  395         # Don't leave turds behind.
  396         os.unlink(self.tmp_path)
  397         raise
  398 
  399   return Writer()
  400 
  401 
  402 def EnsureDirExists(path):
  403   """Make sure the directory for |path| exists."""
  404   try:
  405     os.makedirs(os.path.dirname(path))
  406   except OSError:
  407     pass
  408 
  409 
  410 def GetFlavor(params):
  411   """Returns |params.flavor| if it's set, the system's default flavor else."""
  412   flavors = {
  413     'cygwin': 'win',
  414     'win32': 'win',
  415     'darwin': 'mac',
  416   }
  417 
  418   if 'flavor' in params:
  419     return params['flavor']
  420   if sys.platform in flavors:
  421     return flavors[sys.platform]
  422   if sys.platform.startswith('sunos'):
  423     return 'solaris'
  424   if sys.platform.startswith('freebsd'):
  425     return 'freebsd'
  426   if sys.platform.startswith('openbsd'):
  427     return 'openbsd'
  428   if sys.platform.startswith('netbsd'):
  429     return 'netbsd'
  430   if sys.platform.startswith('aix'):
  431     return 'aix'
  432 
  433   return 'linux'
  434 
  435 
  436 def CopyTool(flavor, out_path):
  437   """Finds (flock|mac|win)_tool.gyp in the gyp directory and copies it
  438   to |out_path|."""
  439   # aix and solaris just need flock emulation. mac and win use more complicated
  440   # support scripts.
  441   prefix = {
  442       'aix': 'flock',
  443       'solaris': 'flock',
  444       'mac': 'mac',
  445       'win': 'win'
  446       }.get(flavor, None)
  447   if not prefix:
  448     return
  449 
  450   # Slurp input file.
  451   source_path = os.path.join(
  452       os.path.dirname(os.path.abspath(__file__)), '%s_tool.py' % prefix)
  453   with open(source_path) as source_file:
  454     source = source_file.readlines()
  455 
  456   # Add header and write it out.
  457   tool_path = os.path.join(out_path, 'gyp-%s-tool' % prefix)
  458   with open(tool_path, 'w') as tool_file:
  459     tool_file.write(
  460         ''.join([source[0], '# Generated by gyp. Do not edit.\n'] + source[1:]))
  461 
  462   # Make file executable.
  463   os.chmod(tool_path, 0755)
  464 
  465 
  466 # From Alex Martelli,
  467 # http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/52560
  468 # ASPN: Python Cookbook: Remove duplicates from a sequence
  469 # First comment, dated 2001/10/13.
  470 # (Also in the printed Python Cookbook.)
  471 
  472 def uniquer(seq, idfun=None):
  473     if idfun is None:
  474         idfun = lambda x: x
  475     seen = {}
  476     result = []
  477     for item in seq:
  478         marker = idfun(item)
  479         if marker in seen: continue
  480         seen[marker] = 1
  481         result.append(item)
  482     return result
  483 
  484 
  485 # Based on http://code.activestate.com/recipes/576694/.
  486 class OrderedSet(collections.MutableSet):
  487   def __init__(self, iterable=None):
  488     self.end = end = []
  489     end += [None, end, end]         # sentinel node for doubly linked list
  490     self.map = {}                   # key --> [key, prev, next]
  491     if iterable is not None:
  492       self |= iterable
  493 
  494   def __len__(self):
  495     return len(self.map)
  496 
  497   def __contains__(self, key):
  498     return key in self.map
  499 
  500   def add(self, key):
  501     if key not in self.map:
  502       end = self.end
  503       curr = end[1]
  504       curr[2] = end[1] = self.map[key] = [key, curr, end]
  505 
  506   def discard(self, key):
  507     if key in self.map:
  508       key, prev_item, next_item = self.map.pop(key)
  509       prev_item[2] = next_item
  510       next_item[1] = prev_item
  511 
  512   def __iter__(self):
  513     end = self.end
  514     curr = end[2]
  515     while curr is not end:
  516       yield curr[0]
  517       curr = curr[2]
  518 
  519   def __reversed__(self):
  520     end = self.end
  521     curr = end[1]
  522     while curr is not end:
  523       yield curr[0]
  524       curr = curr[1]
  525 
  526   # The second argument is an addition that causes a pylint warning.
  527   def pop(self, last=True):  # pylint: disable=W0221
  528     if not self:
  529       raise KeyError('set is empty')
  530     key = self.end[1][0] if last else self.end[2][0]
  531     self.discard(key)
  532     return key
  533 
  534   def __repr__(self):
  535     if not self:
  536       return '%s()' % (self.__class__.__name__,)
  537     return '%s(%r)' % (self.__class__.__name__, list(self))
  538 
  539   def __eq__(self, other):
  540     if isinstance(other, OrderedSet):
  541       return len(self) == len(other) and list(self) == list(other)
  542     return set(self) == set(other)
  543 
  544   # Extensions to the recipe.
  545   def update(self, iterable):
  546     for i in iterable:
  547       if i not in self:
  548         self.add(i)
  549 
  550 
  551 class CycleError(Exception):
  552   """An exception raised when an unexpected cycle is detected."""
  553   def __init__(self, nodes):
  554     self.nodes = nodes
  555   def __str__(self):
  556     return 'CycleError: cycle involving: ' + str(self.nodes)
  557 
  558 
  559 def TopologicallySorted(graph, get_edges):
  560   r"""Topologically sort based on a user provided edge definition.
  561 
  562   Args:
  563     graph: A list of node names.
  564     get_edges: A function mapping from node name to a hashable collection
  565                of node names which this node has outgoing edges to.
  566   Returns:
  567     A list containing all of the node in graph in topological order.
  568     It is assumed that calling get_edges once for each node and caching is
  569     cheaper than repeatedly calling get_edges.
  570   Raises:
  571     CycleError in the event of a cycle.
  572   Example:
  573     graph = {'a': '$(b) $(c)', 'b': 'hi', 'c': '$(b)'}
  574     def GetEdges(node):
  575       return re.findall(r'\$\(([^))]\)', graph[node])
  576     print TopologicallySorted(graph.keys(), GetEdges)
  577     ==>
  578     ['a', 'c', b']
  579   """
  580   get_edges = memoize(get_edges)
  581   visited = set()
  582   visiting = set()
  583   ordered_nodes = []
  584   def Visit(node):
  585     if node in visiting:
  586       raise CycleError(visiting)
  587     if node in visited:
  588       return
  589     visited.add(node)
  590     visiting.add(node)
  591     for neighbor in get_edges(node):
  592       Visit(neighbor)
  593     visiting.remove(node)
  594     ordered_nodes.insert(0, node)
  595   for node in sorted(graph):
  596     Visit(node)
  597   return ordered_nodes
  598 
  599 def CrossCompileRequested():
  600   # TODO: figure out how to not build extra host objects in the
  601   # non-cross-compile case when this is enabled, and enable unconditionally.
  602   return (os.environ.get('GYP_CROSSCOMPILE') or
  603           os.environ.get('AR_host') or
  604           os.environ.get('CC_host') or
  605           os.environ.get('CXX_host') or
  606           os.environ.get('AR_target') or
  607           os.environ.get('CC_target') or
  608           os.environ.get('CXX_target'))