summaryrefslogtreecommitdiffstats
path: root/build/compiler_version.py
blob: 05faf54454df5db32de33fddf0bf5723b297ebd8 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#!/usr/bin/env python
# Copyright (c) 2012 The Chromium Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

"""Compiler version checking tool for gcc

Print gcc version as XY if you are running gcc X.Y.*.
This is used to tweak build flags for gcc 4.4.
"""

import os
import re
import subprocess
import sys


compiler_version_cache = {}  # Map from (compiler, tool) -> version.


def Usage(program_name):
  print '%s MODE TOOL' % os.path.basename(program_name)
  print 'MODE: host or target.'
  print 'TOOL: assembler or compiler or linker.'
  return 1


def ParseArgs(args):
  if len(args) != 2:
    raise Exception('Invalid number of arguments')
  mode = args[0]
  tool = args[1]
  if mode not in ('host', 'target'):
    raise Exception('Invalid mode: %s' % mode)
  if tool not in ('assembler', 'compiler', 'linker'):
    raise Exception('Invalid tool: %s' % tool)
  return mode, tool


def GetEnvironFallback(var_list, default):
  """Look up an environment variable from a possible list of variable names."""
  for var in var_list:
    if var in os.environ:
      return os.environ[var]
  return default


def GetVersion(compiler, tool):
  tool_output = tool_error = None
  cache_key = (compiler, tool)
  cached_version = compiler_version_cache.get(cache_key)
  if cached_version:
    return cached_version
  try:
    # Note that compiler could be something tricky like "distcc g++".
    if tool == "compiler":
      compiler = compiler + " -dumpversion"
      # 4.6
      version_re = re.compile(r"(\d+)\.(\d+)")
    elif tool == "assembler":
      compiler = compiler + " -Xassembler --version -x assembler -c /dev/null"
      # Unmodified: GNU assembler (GNU Binutils) 2.24
      # Ubuntu: GNU assembler (GNU Binutils for Ubuntu) 2.22
      # Fedora: GNU assembler version 2.23.2
      version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
    elif tool == "linker":
      compiler = compiler + " -Xlinker --version"
      # Using BFD linker
      # Unmodified: GNU ld (GNU Binutils) 2.24
      # Ubuntu: GNU ld (GNU Binutils for Ubuntu) 2.22
      # Fedora: GNU ld version 2.23.2
      # Using Gold linker
      # Unmodified: GNU gold (GNU Binutils 2.24) 1.11
      # Ubuntu: GNU gold (GNU Binutils for Ubuntu 2.22) 1.11
      # Fedora: GNU gold (version 2.23.2) 1.11
      version_re = re.compile(r"^GNU [^ ]+ .* (\d+).(\d+).*?$", re.M)
    else:
      raise Exception("Unknown tool %s" % tool)

    # Force the locale to C otherwise the version string could be localized
    # making regex matching fail.
    env = os.environ.copy()
    env["LC_ALL"] = "C"
    pipe = subprocess.Popen(compiler, shell=True, env=env,
                            stdout=subprocess.PIPE, stderr=subprocess.PIPE)
    tool_output, tool_error = pipe.communicate()
    if pipe.returncode:
      raise subprocess.CalledProcessError(pipe.returncode, compiler)

    parsed_output = version_re.match(tool_output)
    result = parsed_output.group(1) + parsed_output.group(2)
    compiler_version_cache[cache_key] = result
    return result
  except Exception, e:
    if tool_error:
      sys.stderr.write(tool_error)
    print >> sys.stderr, "compiler_version.py failed to execute:", compiler
    print >> sys.stderr, e
    return ""


def main(args):
  try:
    (mode, tool) = ParseArgs(args[1:])
  except Exception, e:
    sys.stderr.write(e.message + '\n\n')
    return Usage(args[0])

  ret_code, result = ExtractVersion(mode, tool)
  if ret_code == 0:
    print result
  return ret_code


def DoMain(args):
  """Hook to be called from gyp without starting a separate python
  interpreter."""
  (mode, tool) = ParseArgs(args)
  ret_code, result = ExtractVersion(mode, tool)
  if ret_code == 0:
    return result
  raise Exception("Failed to extract compiler version for args: %s" % args)


def ExtractVersion(mode, tool):
  # Check if various CXX environment variables exist and use them if they
  # exist. The preferences and fallback order is a close approximation of
  # GenerateOutputForConfig() in GYP's ninja generator.
  # The main difference being not supporting GYP's make_global_settings.
  environments = ['CXX_target', 'CXX']
  if mode == 'host':
    environments = ['CXX_host'] + environments;
  compiler = GetEnvironFallback(environments, 'c++')

  if compiler:
    compiler_version = GetVersion(compiler, tool)
    if compiler_version != "":
      return (0, compiler_version)
  return (1, None)


if __name__ == "__main__":
  sys.exit(main(sys.argv))