ninja/posix: Don't link c-file-only targets to libstdc++.

The most important part of this change is that LINK will now default to what
is set for CXX (in make) or what is set for CC (in ninja). In ninja, there's
now an additional LINKXX that's set to what CXX is set to, and in ninja
LINKXX is only used to link targets that contain at least one C++ source
file.

Also change several make_global_settings behaviors:

1. The ninja generator was using LD and LD.host for make_global_settings, while
   the make generator uses LINK and LINK.host. Chromium's common.gypi only
   sets the latter, so it looks like this code isn't needed. Removed support
   for setting LD and LD.host via make_global_settings in ninja. (In practice,
   this was alway set to the same thing as CC / CXX anyhow.)
2. The same is done for the environment variables in ninja. Again, as the
   linker now just uses what's set via CC / CXX, this shouldn't be an issue
   in practice.
3. Change the make generator to look at the LINK envvar instead of LD,
   for consistency with its make_global_settings key and with what
   `make -p` prints.

(If setting a linker that's different from CC / CXX is needed later on, this
is easy to add back.)

(Make doesn't have explicit support for setting LINK, make_global_settings
allows changing arbitrary make variables there. But due to the defaults change,
setting LINK is no longer required in make either.)

BUG=chromium:280718
R=scottmg@chromium.org

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

git-svn-id: http://gyp.googlecode.com/svn/trunk@1721 78cadc50-ecff-11dd-a971-7dbc132099af
diff --git a/pylib/gyp/generator/make.py b/pylib/gyp/generator/make.py
index ab04721..8cdf718 100644
--- a/pylib/gyp/generator/make.py
+++ b/pylib/gyp/generator/make.py
@@ -246,6 +246,14 @@
 
 %(make_global_settings)s
 
+CC.target ?= %(CC.target)s
+CFLAGS.target ?= $(CFLAGS)
+CXX.target ?= %(CXX.target)s
+CXXFLAGS.target ?= $(CXXFLAGS)
+LINK.target ?= %(LINK.target)s
+LDFLAGS.target ?= $(LDFLAGS)
+AR.target ?= $(AR)
+
 # C++ apps need to be linked with g++.
 #
 # Note: flock is used to seralize linking. Linking is a memory-intensive
@@ -257,14 +265,6 @@
 # This will allow make to invoke N linker processes as specified in -jN.
 LINK ?= %(flock)s $(builddir)/linker.lock $(CXX.target)
 
-CC.target ?= %(CC.target)s
-CFLAGS.target ?= $(CFLAGS)
-CXX.target ?= %(CXX.target)s
-CXXFLAGS.target ?= $(CXXFLAGS)
-LINK.target ?= %(LINK.target)s
-LDFLAGS.target ?= $(LDFLAGS)
-AR.target ?= $(AR)
-
 # TODO(evan): move all cross-compilation logic to gyp-time so we don't need
 # to replicate this environment fallback in make as well.
 CC.host ?= %(CC.host)s
@@ -2000,11 +2000,11 @@
     'CC.target':   GetEnvironFallback(('CC_target', 'CC'), '$(CC)'),
     'AR.target':   GetEnvironFallback(('AR_target', 'AR'), '$(AR)'),
     'CXX.target':  GetEnvironFallback(('CXX_target', 'CXX'), '$(CXX)'),
-    'LINK.target': GetEnvironFallback(('LD_target', 'LD'), '$(LINK)'),
+    'LINK.target': GetEnvironFallback(('LINK_target', 'LINK'), '$(LINK)'),
     'CC.host':     GetEnvironFallback(('CC_host',), 'gcc'),
     'AR.host':     GetEnvironFallback(('AR_host',), 'ar'),
     'CXX.host':    GetEnvironFallback(('CXX_host',), 'g++'),
-    'LINK.host':   GetEnvironFallback(('LD_host',), 'g++'),
+    'LINK.host':   GetEnvironFallback(('LINK_host',), '$(CXX.host)'),
   })
 
   build_file, _, _ = gyp.common.ParseQualifiedTarget(target_list[0])
diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py
index fa857d3..f37d440 100644
--- a/pylib/gyp/generator/ninja.py
+++ b/pylib/gyp/generator/ninja.py
@@ -374,6 +374,9 @@
     self.target = Target(spec['type'])
     self.is_standalone_static_library = bool(
         spec.get('standalone_static_library', 0))
+    # Track if this target contains any C++ files, to decide if gcc or g++
+    # should be used for linking.
+    self.uses_cpp = False
 
     self.is_mac_bundle = gyp.xcode_emulation.IsMacBundle(self.flavor, spec)
     self.xcode_settings = self.msvs_settings = None
@@ -778,6 +781,7 @@
       self.ninja.variable('cc', '$cc_host')
       self.ninja.variable('cxx', '$cxx_host')
       self.ninja.variable('ld', '$ld_host')
+      self.ninja.variable('ldxx', '$ldxx_host')
 
     if self.flavor != 'mac' or len(self.archs) == 1:
       return self.WriteSourcesForArch(
@@ -875,6 +879,7 @@
       obj_ext = self.obj_ext
       if ext in ('cc', 'cpp', 'cxx'):
         command = 'cxx'
+        self.uses_cpp = True
       elif ext == 'c' or (ext == 'S' and self.flavor != 'win'):
         command = 'cc'
       elif ext == 's' and self.flavor != 'win':  # Doesn't generate .o.d files.
@@ -891,6 +896,7 @@
         command = 'objc'
       elif self.flavor == 'mac' and ext == 'mm':
         command = 'objcxx'
+        self.uses_cpp = True
       elif self.flavor == 'win' and ext == 'rc':
         command = 'rc'
         obj_ext = '.res'
@@ -995,6 +1001,9 @@
       link_deps.extend(list(extra_link_deps))
 
     extra_bindings = []
+    if self.uses_cpp and self.flavor != 'win':
+      extra_bindings.append(('ld', '$ldxx'))
+
     output = self.ComputeOutput(spec, arch)
     if arch is None and not self.is_mac_bundle:
       self.AppendPostbuildVariable(extra_bindings, spec, output, output)
@@ -1643,9 +1652,10 @@
   else:
     cc = 'gcc'
     cxx = 'g++'
-    ld = '$cxx'
-    ld_c = '$cc'
-    ld_host = '$cxx_host'
+    ld = '$cc'
+    ldxx = '$cxx'
+    ld_host = '$cc_host'
+    ldxx_host = '$cxx_host'
 
   cc_host = None
   cxx_host = None
@@ -1662,16 +1672,12 @@
       cc = os.path.join(build_to_root, value)
     if key == 'CXX':
       cxx = os.path.join(build_to_root, value)
-    if key == 'LD':
-      ld = os.path.join(build_to_root, value)
     if key == 'CC.host':
       cc_host = os.path.join(build_to_root, value)
       cc_host_global_setting = value
     if key == 'CXX.host':
       cxx_host = os.path.join(build_to_root, value)
       cxx_host_global_setting = value
-    if key == 'LD.host':
-      ld_host = os.path.join(build_to_root, value)
     if key.endswith('_wrapper'):
       wrappers[key[:-len('_wrapper')]] = os.path.join(build_to_root, value)
 
@@ -1694,7 +1700,6 @@
   master_ninja.variable('cc', CommandWithWrapper('CC', wrappers, cc))
   cxx = GetEnvironFallback(['CXX_target', 'CXX'], cxx)
   master_ninja.variable('cxx', CommandWithWrapper('CXX', wrappers, cxx))
-  ld = GetEnvironFallback(['LD_target', 'LD'], ld)
 
   if flavor == 'win':
     master_ninja.variable('ld', ld)
@@ -1705,6 +1710,7 @@
     master_ninja.variable('mt', 'mt.exe')
   else:
     master_ninja.variable('ld', CommandWithWrapper('LINK', wrappers, ld))
+    master_ninja.variable('ldxx', CommandWithWrapper('LINK', wrappers, ldxx))
     master_ninja.variable('ar', GetEnvironFallback(['AR_target', 'AR'], 'ar'))
 
   if generator_supports_multiple_toolsets:
@@ -1716,7 +1722,6 @@
     master_ninja.variable('ar_host', GetEnvironFallback(['AR_host'], 'ar'))
     cc_host = GetEnvironFallback(['CC_host'], cc_host)
     cxx_host = GetEnvironFallback(['CXX_host'], cxx_host)
-    ld_host = GetEnvironFallback(['LD_host'], ld_host)
 
     # The environment variable could be used in 'make_global_settings', like
     # ['CC.host', '$(CC)'] or ['CXX.host', '$(CXX)'], transform them here.
@@ -1733,6 +1738,8 @@
     else:
       master_ninja.variable('ld_host', CommandWithWrapper(
           'LINK', wrappers, ld_host))
+      master_ninja.variable('ldxx_host', CommandWithWrapper(
+          'LINK', wrappers, ldxx_host))
 
   master_ninja.newline()
 
diff --git a/test/compiler-override/gyptest-compiler-env.py b/test/compiler-override/gyptest-compiler-env.py
index 8c77f97..d13d692 100755
--- a/test/compiler-override/gyptest-compiler-env.py
+++ b/test/compiler-override/gyptest-compiler-env.py
@@ -20,7 +20,7 @@
   sys.exit(0)
 
 # Clear any existing compiler related env vars.
-for key in 'CC', 'CXX', 'LD', 'CC_host', 'CXX_host', 'LD_host':
+for key in ['CC', 'CXX', 'LINK', 'CC_host', 'CXX_host', 'LINK_host']:
   if key in os.environ:
     del os.environ[key]
 
@@ -38,15 +38,18 @@
 test = TestGyp.TestGyp(formats=['ninja', 'make'])
 
 def TestTargetOveride():
+  expected = ['my_cc.py', 'my_cxx.py', 'FOO' ]
+  if test.format != 'ninja':  # ninja just uses $CC / $CXX as linker.
+    expected.append('FOO_LINK')
+
   # Check that CC, CXX and LD set target compiler
   oldenv = os.environ.copy()
   try:
     os.environ['CC'] = 'python %s/my_cc.py FOO' % here
     os.environ['CXX'] = 'python %s/my_cxx.py FOO' % here
-    os.environ['LD'] = 'python %s/my_ld.py FOO_LINK' % here
+    os.environ['LINK'] = 'python %s/my_ld.py FOO_LINK' % here
 
-    CheckCompiler(test, 'compiler.gyp',
-                  ['my_cc.py', 'my_cxx.py', 'FOO', 'FOO_LINK'],
+    CheckCompiler(test, 'compiler.gyp', expected,
                   True)
   finally:
     os.environ.clear()
@@ -55,8 +58,7 @@
   # Run the same tests once the eviron has been restored.  The
   # generated should have embedded all the settings in the
   # project files so the results should be the same.
-  CheckCompiler(test, 'compiler.gyp',
-                ['my_cc.py', 'my_cxx.py', 'FOO', 'FOO_LINK'],
+  CheckCompiler(test, 'compiler.gyp', expected,
                 False)
 
 def TestTargetOverideCompilerOnly():
@@ -82,15 +84,17 @@
 
 
 def TestHostOveride():
+  expected = ['my_cc.py', 'my_cxx.py', 'HOST' ]
+  if test.format != 'ninja':  # ninja just uses $CC / $CXX as linker.
+    expected.append('HOST_LINK')
+
   # Check that CC_host sets host compilee
   oldenv = os.environ.copy()
   try:
     os.environ['CC_host'] = 'python %s/my_cc.py HOST' % here
     os.environ['CXX_host'] = 'python %s/my_cxx.py HOST' % here
-    os.environ['LD_host'] = 'python %s/my_ld.py HOST_LINK' % here
-    CheckCompiler(test, 'compiler-host.gyp',
-                  ['my_cc.py', 'my_cxx.py', 'HOST', 'HOST_LINK'],
-                  True)
+    os.environ['LINK_host'] = 'python %s/my_ld.py HOST_LINK' % here
+    CheckCompiler(test, 'compiler-host.gyp', expected, True)
   finally:
     os.environ.clear()
     os.environ.update(oldenv)
@@ -98,9 +102,7 @@
   # Run the same tests once the eviron has been restored.  The
   # generated should have embedded all the settings in the
   # project files so the results should be the same.
-  CheckCompiler(test, 'compiler-host.gyp',
-                ['my_cc.py', 'my_cxx.py', 'HOST', 'HOST_LINK'],
-                False)
+  CheckCompiler(test, 'compiler-host.gyp', expected, False)
 
 
 TestTargetOveride()
diff --git a/test/make_global_settings/basics/gyptest-make_global_settings.py b/test/make_global_settings/basics/gyptest-make_global_settings.py
index dfc2aad..1c1b1fb 100644
--- a/test/make_global_settings/basics/gyptest-make_global_settings.py
+++ b/test/make_global_settings/basics/gyptest-make_global_settings.py
@@ -27,17 +27,17 @@
 """
   if sys.platform == 'linux2':
     link_expected = """
-LINK ?= flock $(builddir)/linker.lock $(abspath clang++)
+LINK ?= flock $(builddir)/linker.lock $(abspath clang)
 """
   elif sys.platform == 'darwin':
     link_expected = """
-LINK ?= ./gyp-mac-tool flock $(builddir)/linker.lock $(abspath clang++)
+LINK ?= ./gyp-mac-tool flock $(builddir)/linker.lock $(abspath clang)
 """
   test.must_contain('Makefile', cc_expected)
   test.must_contain('Makefile', link_expected)
 if test.format == 'ninja':
   cc_expected = 'cc = ' + os.path.join('..', '..', 'clang')
-  ld_expected = 'ld = $cxx'
+  ld_expected = 'ld = $cc'
   if sys.platform == 'win32':
     ld_expected = 'link.exe'
   test.must_contain('out/Default/build.ninja', cc_expected)
diff --git a/test/make_global_settings/basics/make_global_settings.gyp b/test/make_global_settings/basics/make_global_settings.gyp
index 07b8791..47dbc85 100644
--- a/test/make_global_settings/basics/make_global_settings.gyp
+++ b/test/make_global_settings/basics/make_global_settings.gyp
@@ -5,7 +5,7 @@
 {
   'make_global_settings': [
     ['CC', 'clang'],
-    ['LINK', 'clang++'],
+    ['LINK', 'clang'],
   ],
   'targets': [
     {
diff --git a/test/make_global_settings/env-wrapper/gyptest-wrapper.py b/test/make_global_settings/env-wrapper/gyptest-wrapper.py
index 09470e1..70d6906 100644
--- a/test/make_global_settings/env-wrapper/gyptest-wrapper.py
+++ b/test/make_global_settings/env-wrapper/gyptest-wrapper.py
@@ -31,11 +31,16 @@
                  os.path.join('..', '..', 'clang'))
   cc_host_expected = ('cc_host = ' + os.path.join('..', '..', 'ccache') + ' ' +
                       os.path.join('..', '..', 'clang'))
-  ld_expected = 'ld = ../../distlink $cxx'
+  ld_expected = 'ld = ../../distlink $cc'
+  if sys.platform != 'win32':
+    ldxx_expected = 'ldxx = ../../distlink $cxx'
+
   if sys.platform == 'win32':
      ld_expected = 'link.exe'
   test.must_contain('out/Default/build.ninja', cc_expected)
   test.must_contain('out/Default/build.ninja', cc_host_expected)
   test.must_contain('out/Default/build.ninja', ld_expected)
+  if sys.platform != 'win32':
+    test.must_contain('out/Default/build.ninja', ldxx_expected)
 
 test.pass_test()
diff --git a/test/make_global_settings/env-wrapper/wrapper.gyp b/test/make_global_settings/env-wrapper/wrapper.gyp
index c5d46bd..1698d71 100644
--- a/test/make_global_settings/env-wrapper/wrapper.gyp
+++ b/test/make_global_settings/env-wrapper/wrapper.gyp
@@ -5,7 +5,6 @@
 {
   'make_global_settings': [
     ['CC', 'clang'],
-    ['LINK', 'clang++'],
     ['CC.host', 'clang'],
   ],
   'targets': [
diff --git a/test/make_global_settings/wrapper/gyptest-wrapper.py b/test/make_global_settings/wrapper/gyptest-wrapper.py
index 3b391e5..eb1ebfd 100644
--- a/test/make_global_settings/wrapper/gyptest-wrapper.py
+++ b/test/make_global_settings/wrapper/gyptest-wrapper.py
@@ -37,7 +37,7 @@
                  os.path.join('..', '..', 'clang'))
   cc_host_expected = ('cc_host = ' + os.path.join('..', '..', 'ccache') + ' ' +
                       os.path.join('..', '..', 'clang'))
-  ld_expected = 'ld = ../../distlink $cxx'
+  ld_expected = 'ld = ../../distlink $cc'
   if sys.platform == 'win32':
      ld_expected = 'link.exe'
   test.must_contain('out/Default/build.ninja', cc_expected)
diff --git a/test/no-cpp/gyptest-no-cpp.py b/test/no-cpp/gyptest-no-cpp.py
new file mode 100644
index 0000000..97f759e
--- /dev/null
+++ b/test/no-cpp/gyptest-no-cpp.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+# Copyright (c) 2012 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.
+
+"""
+Checks that C-only targets aren't linked against libstdc++.
+"""
+
+import TestGyp
+
+import re
+import subprocess
+import sys
+
+# set |match| to ignore build stderr output.
+test = TestGyp.TestGyp(match = lambda a, b: True)
+if sys.platform != 'win32' and test.format not in ('make', 'android'):
+  # TODO: This doesn't pass with make.
+  # TODO: Does a test like this make sense with Windows? Android?
+
+  CHDIR = 'src'
+  test.run_gyp('test.gyp', chdir=CHDIR)
+  test.build('test.gyp', 'no_cpp', chdir=CHDIR)
+
+  def LinksLibStdCpp(path):
+    path = test.built_file_path(path, chdir=CHDIR)
+    if sys.platform == 'darwin':
+      proc = subprocess.Popen(['otool', '-L', path], stdout=subprocess.PIPE)
+    else:
+      proc = subprocess.Popen(['ldd', path], stdout=subprocess.PIPE)
+    output = proc.communicate()[0]
+    assert not proc.returncode
+    return 'libstdc++' in output or 'libc++' in output
+
+  if LinksLibStdCpp('no_cpp'):
+    test.fail_test()
+
+  build_error_code = {
+    'xcode': [1, 65],  # 1 for xcode 3, 65 for xcode 4 (see `man sysexits`)
+    'make': 2,
+    'ninja': 1,
+  }[test.format]
+
+  test.build('test.gyp', 'no_cpp_dep_on_cc_lib', chdir=CHDIR,
+             status=build_error_code)
+
+  test.pass_test()
diff --git a/test/no-cpp/src/call-f-main.c b/test/no-cpp/src/call-f-main.c
new file mode 100644
index 0000000..8b95c59
--- /dev/null
+++ b/test/no-cpp/src/call-f-main.c
@@ -0,0 +1,2 @@
+void* f();
+int main() { f(); }
diff --git a/test/no-cpp/src/empty-main.c b/test/no-cpp/src/empty-main.c
new file mode 100644
index 0000000..237c8ce
--- /dev/null
+++ b/test/no-cpp/src/empty-main.c
@@ -0,0 +1 @@
+int main() {}
diff --git a/test/no-cpp/src/f.cc b/test/no-cpp/src/f.cc
new file mode 100644
index 0000000..02f50f2
--- /dev/null
+++ b/test/no-cpp/src/f.cc
@@ -0,0 +1,3 @@
+extern "C" { void* f(); }
+
+void* f() { return new int; }
diff --git a/test/no-cpp/src/test.gyp b/test/no-cpp/src/test.gyp
new file mode 100644
index 0000000..417015e
--- /dev/null
+++ b/test/no-cpp/src/test.gyp
@@ -0,0 +1,25 @@
+# Copyright (c) 2013 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': 'no_cpp',
+      'type': 'executable',
+      'sources': [ 'empty-main.c' ],
+    },
+    # A static_library with a cpp file and a linkable with only .c files
+    # depending on it causes a linker error:
+    {
+      'target_name': 'cpp_lib',
+      'type': 'static_library',
+      'sources': [ 'f.cc' ],
+    },
+    {
+      'target_name': 'no_cpp_dep_on_cc_lib',
+      'type': 'executable',
+      'dependencies': [ 'cpp_lib' ],
+      'sources': [ 'call-f-main.c' ],
+    },
+  ],
+}