1#!/usr/bin/env python
2# Copyright (c) 2012 The Chromium Authors. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6"""Compiler version checking tool for gcc
7
8Print gcc version as XY if you are running gcc X.Y.*.
9This is used to tweak build flags for gcc 4.4.
10"""
11
12import os
13import re
14import subprocess
15import sys
16
17
18compiler_version_cache = {}  # Map from (compiler, tool) -> version.
19
20
21def Usage(program_name):
22  print '%s MODE TOOL' % os.path.basename(program_name)
23  print 'MODE: host or target.'
24  print 'TOOL: assembler or compiler or linker.'
25  return 1
26
27
28def ParseArgs(args):
29  if len(args) != 2:
30    raise Exception('Invalid number of arguments')
31  mode = args[0]
32  tool = args[1]
33  if mode not in ('host', 'target'):
34    raise Exception('Invalid mode: %s' % mode)
35  if tool not in ('assembler', 'compiler', 'linker'):
36    raise Exception('Invalid tool: %s' % tool)
37  return mode, tool
38
39
40def GetEnvironFallback(var_list, default):
41  """Look up an environment variable from a possible list of variable names."""
42  for var in var_list:
43    if var in os.environ:
44      return os.environ[var]
45  return default
46
47
48def GetVersion(compiler, tool):
49  tool_output = tool_error = None
50  cache_key = (compiler, tool)
51  cached_version = compiler_version_cache.get(cache_key)
52  if cached_version:
53    return cached_version
54  try:
55    # Note that compiler could be something tricky like "distcc g++".
56    if tool == "compiler":
57      compiler = compiler + " -dumpversion"
58      # 4.6
59      version_re = re.compile(r"(\d+)\.(\d+)")
60    elif tool == "assembler":
61      compiler = compiler + " -Xassembler --version -x assembler -c /dev/null"
62      # Unmodified: GNU assembler (GNU Binutils) 2.24
63      # Ubuntu: GNU assembler (GNU Binutils for Ubuntu) 2.22
64      # Fedora: GNU assembler version 2.23.2
65      version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
66    elif tool == "linker":
67      compiler = compiler + " -Xlinker --version"
68      # Using BFD linker
69      # Unmodified: GNU ld (GNU Binutils) 2.24
70      # Ubuntu: GNU ld (GNU Binutils for Ubuntu) 2.22
71      # Fedora: GNU ld version 2.23.2
72      # Using Gold linker
73      # Unmodified: GNU gold (GNU Binutils 2.24) 1.11
74      # Ubuntu: GNU gold (GNU Binutils for Ubuntu 2.22) 1.11
75      # Fedora: GNU gold (version 2.23.2) 1.11
76      version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
77    else:
78      raise Exception("Unknown tool %s" % tool)
79
80    # Force the locale to C otherwise the version string could be localized
81    # making regex matching fail.
82    env = os.environ.copy()
83    env["LC_ALL"] = "C"
84    pipe = subprocess.Popen(compiler, shell=True, env=env,
85                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
86    tool_output, tool_error = pipe.communicate()
87    if pipe.returncode:
88      raise subprocess.CalledProcessError(pipe.returncode, compiler)
89
90    parsed_output = version_re.match(tool_output)
91    result = parsed_output.group(1) + parsed_output.group(2)
92    compiler_version_cache[cache_key] = result
93    return result
94  except Exception, e:
95    if tool_error:
96      sys.stderr.write(tool_error)
97    print >> sys.stderr, "compiler_version.py failed to execute:", compiler
98    print >> sys.stderr, e
99    return ""
100
101
102def main(args):
103  try:
104    (mode, tool) = ParseArgs(args[1:])
105  except Exception, e:
106    sys.stderr.write(e.message + '\n\n')
107    return Usage(args[0])
108
109  ret_code, result = ExtractVersion(mode, tool)
110  if ret_code == 0:
111    print result
112  return ret_code
113
114
115def DoMain(args):
116  """Hook to be called from gyp without starting a separate python
117  interpreter."""
118  (mode, tool) = ParseArgs(args)
119  ret_code, result = ExtractVersion(mode, tool)
120  if ret_code == 0:
121    return result
122  raise Exception("Failed to extract compiler version for args: %s" % args)
123
124
125def ExtractVersion(mode, tool):
126  # Check if various CXX environment variables exist and use them if they
127  # exist. The preferences and fallback order is a close approximation of
128  # GenerateOutputForConfig() in GYP's ninja generator.
129  # The main difference being not supporting GYP's make_global_settings.
130  environments = ['CXX_target', 'CXX']
131  if mode == 'host':
132    environments = ['CXX_host'] + environments;
133  compiler = GetEnvironFallback(environments, 'c++')
134
135  if compiler:
136    compiler_version = GetVersion(compiler, tool)
137    if compiler_version != "":
138      return (0, compiler_version)
139  return (1, None)
140
141
142if __name__ == "__main__":
143  sys.exit(main(sys.argv))
144