summaryrefslogtreecommitdiffstats
path: root/tools/findit/chromium_deps.py
blob: 46436cbc7e008dae02351bbfb35cfa5cf2fdff01 (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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# Copyright (c) 2014 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.

import base64
import json
import os
import re
import time
import urllib2

from common import utils


_THIS_DIR = os.path.abspath(os.path.dirname(__file__))
CONFIG = json.loads(open(os.path.join(_THIS_DIR,
                                      'deps_config.json'), 'r').read())
OLD_GIT_URL_PATTERN = re.compile(r'https?://git.chromium.org/(.*)')


class _VarImpl(object):

  def __init__(self, local_scope):
    self._local_scope = local_scope

  def Lookup(self, var_name):
    if var_name in self._local_scope.get('vars', {}):
      return self._local_scope['vars'][var_name]
    raise Exception('Var is not defined: %s' % var_name)


def _ParseDEPS(content):
  """Parse the DEPS file of chromium."""
  local_scope = {}
  var = _VarImpl(local_scope)
  global_scope = {
      'Var': var.Lookup,
      'deps': {},
      'deps_os': {},
      'include_rules': [],
      'skip_child_includes': [],
      'hooks': [],
  }
  exec(content, global_scope, local_scope)

  local_scope.setdefault('deps', {})
  local_scope.setdefault('deps_os', {})

  return (local_scope['deps'], local_scope['deps_os'])


def _GetComponentName(path, host_dirs):
  """Return the component name of a path."""
  components_renamed = {
      'webkit': 'blink',
  }

  for host_dir in host_dirs:
    if path.startswith(host_dir):
      path = path[len(host_dir):]
      name = path.split('/')[0].lower()
      if name in components_renamed:
        return components_renamed[name].lower()
      else:
        return name.lower()

  # Unknown path, return the whole path as component name.
  return '_'.join(path.split('/'))


def _GetContentOfDEPS(revision):
  chromium_git_file_url_template = CONFIG['chromium_git_file_url']

  # Try .DEPS.git first, because before migration from SVN to GIT, the .DEPS.git
  # has the dependency in GIT repo while DEPS has dependency in SVN repo.
  url = chromium_git_file_url_template % (revision, '.DEPS.git')
  http_status_code, content = utils.GetHttpClient().Get(
      url, retries=5, retry_if_not=404)

  # If .DEPS.git is not found, use DEPS, assuming it is a commit after migration
  # from SVN to GIT.
  if http_status_code == 404:
    url = chromium_git_file_url_template % (revision, 'DEPS')
    http_status_code, content = utils.GetHttpClient().Get(url, retries=5)

  if http_status_code == 200:
    return base64.b64decode(content)
  else:
    return ''


def GetChromiumComponents(chromium_revision,
                          os_platform='unix',
                          deps_file_downloader=_GetContentOfDEPS):
  """Return a list of components used by Chrome of the given revision.

  Args:
    chromium_revision: Revision of the Chrome build: svn revision, or git hash.
    os_platform: The target platform of the Chrome build, eg. win, mac, etc.
    deps_file_downloader: A function that takes the chromium_revision as input,
                          and returns the content of the DEPS file. The returned
                          content is assumed to be trusted input and will be
                          evaluated as python code.

  Returns:
    A map from component path to parsed component name, repository URL,
    repository type and revision.
    Return None if an error occurs.
  """
  if os_platform.lower() == 'linux':
    os_platform = 'unix'

  chromium_git_base_url = CONFIG['chromium_git_base_url']

  if not utils.IsGitHash(chromium_revision):
    # Convert svn revision or commit position to Git hash.
    cr_rev_url_template = CONFIG['cr_rev_url']
    url = cr_rev_url_template % chromium_revision
    status_code, content = utils.GetHttpClient().Get(
        url, timeout=120, retries=5, retry_if_not=404)
    if status_code != 200 or not content:
      if status_code == 404:
        print 'Chromium commit position %s is not found.' % chromium_revision
      return None

    cr_rev_data = json.loads(content)
    if 'git_sha' not in cr_rev_data:
      return None

    if 'repo' not in cr_rev_data or cr_rev_data['repo'] != 'chromium/src':
      print ('%s seems like a commit position of "%s", but not "chromium/src".'
             % (chromium_revision, cr_rev_data['repo']))
      return None

    chromium_revision = cr_rev_data.get('git_sha')
    if not chromium_revision:
      return None

  # Download the content of DEPS file in chromium.
  deps_content = deps_file_downloader(chromium_revision)
  if not deps_content:
    return None

  all_deps = {}

  # Parse the content of DEPS file.
  deps, deps_os = _ParseDEPS(deps_content)
  all_deps.update(deps)
  if os_platform is not None:
    all_deps.update(deps_os.get(os_platform, {}))

  # Figure out components based on the dependencies.
  components = {}
  host_dirs = CONFIG['host_directories']
  for component_path, component_repo_url in all_deps.iteritems():
    if component_repo_url is None:
      # For some platform like iso, some component is ignored.
      continue

    name = _GetComponentName(component_path, host_dirs)
    repository, revision = component_repo_url.split('@')
    match = OLD_GIT_URL_PATTERN.match(repository)
    if match:
      repository = 'https://chromium.googlesource.com/%s' % match.group(1)
    is_git_hash = utils.IsGitHash(revision)
    if is_git_hash:
      repository_type = 'git'
    else:
      repository_type = 'svn'
    if not component_path.endswith('/'):
      component_path += '/'
    components[component_path] = {
        'path': component_path,
        'name': name,
        'repository': repository,
        'repository_type': repository_type,
        'revision': revision
    }

  # Add chromium as a component.
  components['src/'] = {
      'path': 'src/',
      'name': 'chromium',
      'repository': chromium_git_base_url,
      'repository_type': 'git',
      'revision': chromium_revision
  }

  return components


def GetChromiumComponentRange(old_revision,
                              new_revision,
                              os_platform='unix',
                              deps_file_downloader=_GetContentOfDEPS):
  """Return a list of components with their revision ranges.

  Args:
    old_revision: The old revision of a Chrome build.
    new_revision: The new revision of a Chrome build.
    os_platform: The target platform of the Chrome build, eg. win, mac, etc.
    deps_file_downloader: A function that takes the chromium_revision as input,
                          and returns the content of the DEPS file. The returned
                          content is assumed to be trusted input and will be
                          evaluated as python code.

  Returns:
    A map from component path to its parsed regression and other information.
    Return None if an error occurs.
  """
  old_components = GetChromiumComponents(old_revision, os_platform,
                                         deps_file_downloader)
  if not old_components:
    return None

  new_components = GetChromiumComponents(new_revision, os_platform,
                                         deps_file_downloader)
  if not new_components:
    return None

  components = {}
  for path in new_components:
    new_component = new_components[path]
    old_revision = None

    if path in old_components:
      old_component = old_components[path]
      old_revision = old_component['revision']

    components[path] = {
        'path': path,
        'rolled': new_component['revision'] != old_revision,
        'name': new_component['name'],
        'old_revision': old_revision,
        'new_revision': new_component['revision'],
        'repository': new_component['repository'],
        'repository_type': new_component['repository_type']
    }

  return components