1# Copyright 2013 The Chromium Authors. 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"""A module to add ninja support to cr."""
6
7import multiprocessing
8import os
9
10import cr
11
12_PHONY_SUFFIX = ': phony'
13_LINK_SUFFIX = ': link'
14
15
16DEFAULT = cr.Config.From(
17    GOMA_DIR=os.path.expanduser('~/goma'),
18)
19
20class NinjaBuilder(cr.Builder):
21  """An implementation of Builder that uses ninja to do the actual build."""
22
23  # Some basic configuration installed if we are enabled.
24  EXTRA_FOR_IO_BOUND_JOBS = 2
25  ENABLED = cr.Config.From(
26      NINJA_BINARY=os.path.join('{DEPOT_TOOLS}', 'ninja'),
27      NINJA_JOBS=multiprocessing.cpu_count() + EXTRA_FOR_IO_BOUND_JOBS,
28      NINJA_PROCESSORS=multiprocessing.cpu_count(),
29      NINJA_BUILD_FILE=os.path.join('{CR_BUILD_DIR}', 'build.ninja'),
30      # Don't rename to GOMA_* or Goma will complain: "unkown GOMA_ parameter".
31      NINJA_GOMA_LINE='cc = {CR_GOMA_CC} $',
32  )
33  # A config block only included if goma is detected.
34  GOMA = cr.Config.From(
35      CR_GOMA_CC=os.path.join('{GOMA_DIR}', 'gomacc'),
36      CR_GOMA_CTL=os.path.join('{GOMA_DIR}', 'goma_ctl.py'),
37      GOMA_DIR='{CR_GOMA_DIR}',
38      GYP_DEF_gomadir='{CR_GOMA_DIR}',
39      GYP_DEF_use_goma=1,
40      NINJA_JOBS=multiprocessing.cpu_count() * 10,
41  )
42  # A placeholder for the system detected configuration
43  DETECTED = cr.Config('DETECTED')
44
45  def __init__(self):
46    super(NinjaBuilder, self).__init__()
47    self._targets = []
48
49  def Build(self, targets, arguments):
50    # Make sure Goma is started if Ninja is set to use it.
51    # This may be redundant, but it currently improves reliability.
52    try:
53      with open(cr.context.Get('NINJA_BUILD_FILE'), 'r') as f:
54        if f.readline().rstrip('\n') == cr.context.Get('NINJA_GOMA_LINE'):
55          # Goma is active, so make sure it's started.
56          cr.Host.ExecuteSilently(
57              '{CR_GOMA_CTL}',
58              'ensure_start'
59          )
60    except IOError:
61      pass
62
63    build_arguments = [target.build_target for target in targets]
64    build_arguments.extend(arguments)
65    cr.Host.Execute(
66        '{NINJA_BINARY}',
67        '-C{CR_BUILD_DIR}',
68        '-j{NINJA_JOBS}',
69        '-l{NINJA_PROCESSORS}',
70        *build_arguments
71    )
72
73  def Clean(self, targets, arguments):
74    build_arguments = [target.build_target for target in targets]
75    build_arguments.extend(arguments)
76    cr.Host.Execute(
77        '{NINJA_BINARY}',
78        '-C{CR_BUILD_DIR}',
79        '-tclean',
80        *build_arguments
81    )
82
83  def GetTargets(self):
84    """Overridden from Builder.GetTargets."""
85    if not self._targets:
86      try:
87        cr.context.Get('CR_BUILD_DIR', raise_errors=True)
88      except KeyError:
89        return self._targets
90      output = cr.Host.Capture(
91          '{NINJA_BINARY}',
92          '-C{CR_BUILD_DIR}',
93          '-ttargets',
94          'all'
95      )
96      for line in output.split('\n'):
97        line = line.strip()
98        if line.endswith(_PHONY_SUFFIX):
99          target = line[:-len(_PHONY_SUFFIX)].strip()
100          self._targets.append(target)
101        elif line.endswith(_LINK_SUFFIX):
102          target = line[:-len(_LINK_SUFFIX)].strip()
103          self._targets.append(target)
104    return self._targets
105
106  @classmethod
107  def ClassInit(cls):
108    # TODO(iancottrell): If we can't detect ninja, we should be disabled.
109    ninja_binaries = cr.Host.SearchPath('ninja')
110    if ninja_binaries:
111      cls.DETECTED.Set(NINJA_BINARY=ninja_binaries[0])
112
113    goma_binaries = cr.Host.SearchPath('gomacc', [
114      '{GOMA_DIR}',
115      '/usr/local/google/code/goma',
116      os.path.expanduser('~/goma')
117    ])
118    if goma_binaries:
119      cls.DETECTED.Set(CR_GOMA_DIR=os.path.dirname(goma_binaries[0]))
120      cls.DETECTED.AddChildren(cls.GOMA)
121