ninja: Don't rerun actions/rules based on where gyp was invoked

Right now, ninja generates unique names for actions/rules by combining the
action/rule name with a hash on the qualified_target.  However, the path of the
qualified_target is relative to where gyp was invoked from.  So, the following
gyp command:

  gyp --depth=. foo/bar.gyp

will generate a different hash than:

  cd foo
  gyp --depth=../ bar.gyp

Since the hash is different, this causes actions/rules to get rerun based on
where gyp happened to be invoked from.

This commit fixes this issue by using a qualified_target relative to the
toplevel_dir, rather than the current working directory.  Also, the
self.qualified_target in NinjaWriter is only used for the hash, so we can
remove it and replace it with a precomputed hash for all actions/rules.

BUG=
R=scottmg@chromium.org

Review URL: https://codereview.chromium.org/607083002

git-svn-id: http://gyp.googlecode.com/svn/trunk@1987 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index 7b7f8f2..677ce3c 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -213,7 +213,7 @@
 #   to the input file name as well as the output target name.
 
 class NinjaWriter:
-  def __init__(self, qualified_target, target_outputs, base_dir, build_dir,
+  def __init__(self, hash_for_rules, target_outputs, base_dir, build_dir,
                output_file, toplevel_build, output_file_name, flavor,
                toplevel_dir=None):
     """
@@ -223,7 +223,7 @@
     toplevel_dir: path to the toplevel directory
     """
 
-    self.qualified_target = qualified_target
+    self.hash_for_rules = hash_for_rules
     self.target_outputs = target_outputs
     self.base_dir = base_dir
     self.build_dir = build_dir
@@ -591,8 +591,7 @@
     all_outputs = []
     for action in actions:
       # First write out a rule for the action.
-      name = '%s_%s' % (action['action_name'],
-                        hashlib.md5(self.qualified_target).hexdigest())
+      name = '%s_%s' % (action['action_name'], self.hash_for_rules)
       description = self.GenerateDescription('ACTION',
                                              action.get('message', None),
                                              name)
@@ -629,8 +628,7 @@
         continue
 
       # First write out a rule for the rule action.
-      name = '%s_%s' % (rule['rule_name'],
-                        hashlib.md5(self.qualified_target).hexdigest())
+      name = '%s_%s' % (rule['rule_name'], self.hash_for_rules)
 
       args = rule['action']
       description = self.GenerateDescription(
@@ -2183,6 +2181,10 @@
 
     build_file = gyp.common.RelativePath(build_file, options.toplevel_dir)
 
+    qualified_target_for_hash = gyp.common.QualifiedTarget(build_file, name,
+                                                           toolset)
+    hash_for_rules = hashlib.md5(qualified_target_for_hash).hexdigest()
+
     base_path = os.path.dirname(build_file)
     obj = 'obj'
     if toolset != 'target':
@@ -2190,7 +2192,7 @@
     output_file = os.path.join(obj, base_path, name + '.ninja')
 
     ninja_output = StringIO()
-    writer = NinjaWriter(qualified_target, target_outputs, base_path, build_dir,
+    writer = NinjaWriter(hash_for_rules, target_outputs, base_path, build_dir,
                          ninja_output,
                          toplevel_build, output_file,
                          flavor, toplevel_dir=options.toplevel_dir)
diff --git a/test/ninja/action-rule-hash/gyptest-action-rule-hash.py b/test/ninja/action-rule-hash/gyptest-action-rule-hash.py
new file mode 100644
index 0000000..631e176
--- /dev/null
+++ b/test/ninja/action-rule-hash/gyptest-action-rule-hash.py
@@ -0,0 +1,27 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+"""
+Verify that running gyp in a different directory does not cause actions and
+rules to rerun.
+"""
+
+import os
+import sys
+import TestGyp
+
+test = TestGyp.TestGyp(formats=['ninja'])
+
+test.run_gyp('subdir/action-rule-hash.gyp')
+test.build('subdir/action-rule-hash.gyp', test.ALL)
+test.up_to_date('subdir/action-rule-hash.gyp')
+
+# Verify that everything is still up-to-date when we re-invoke gyp from a
+# different directory.
+test.run_gyp('action-rule-hash.gyp', '--depth=../', chdir='subdir')
+test.up_to_date('subdir/action-rule-hash.gyp')
+
+test.pass_test()
diff --git a/test/ninja/action-rule-hash/subdir/action-rule-hash.gyp b/test/ninja/action-rule-hash/subdir/action-rule-hash.gyp
new file mode 100644
index 0000000..0e88a30
--- /dev/null
+++ b/test/ninja/action-rule-hash/subdir/action-rule-hash.gyp
@@ -0,0 +1,29 @@
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+{
+  'targets': [
+    {
+      'target_name': 'program',
+      'type': 'executable',
+      'sources': [
+        '<(INTERMEDIATE_DIR)/main.cc',
+      ],
+      'actions': [
+        {
+          'action_name': 'emit_main_cc',
+          'inputs': ['emit.py'],
+          'outputs': ['<(INTERMEDIATE_DIR)/main.cc'],
+          'action': [
+            'python',
+            'emit.py',
+            '<(INTERMEDIATE_DIR)/main.cc',
+          ],
+          # Allows the test to run without hermetic cygwin on windows.
+          'msvs_cygwin_shell': 0,
+        },
+      ],
+    },
+  ],
+}
diff --git a/test/ninja/action-rule-hash/subdir/emit.py b/test/ninja/action-rule-hash/subdir/emit.py
new file mode 100644
index 0000000..fcb715a
--- /dev/null
+++ b/test/ninja/action-rule-hash/subdir/emit.py
@@ -0,0 +1,13 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2014 Google Inc. All rights reserved.
+# Use of this source code is governed by a BSD-style license that can be
+# found in the LICENSE file.
+
+import sys
+
+f = open(sys.argv[1], 'wb')
+f.write('int main() {\n')
+f.write('  return 0;\n')
+f.write('}\n')
+f.close()