availability_finder.py revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
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
5import collections
6import os
7
8import svn_constants
9from branch_utility import BranchUtility
10from compiled_file_system import CompiledFileSystem
11from file_system import FileNotFoundError
12from third_party.json_schema_compiler import json_parse
13from third_party.json_schema_compiler.memoize import memoize
14from third_party.json_schema_compiler.model import UnixName
15
16
17def _GetChannelFromFeatures(api_name, file_system, path):
18  '''Finds API channel information within _features.json files at the given
19  |path| for the given |file_system|. Returns None if channel information for
20  the API cannot be located.
21  '''
22  feature = file_system.GetFromFile(path).get(api_name)
23
24  if feature is None:
25    return None
26  if isinstance(feature, collections.Mapping):
27    # The channel information exists as a solitary dict.
28    return feature.get('channel')
29  # The channel information dict is nested within a list for whitelisting
30  # purposes. Take the newest channel out of all of the entries.
31  return BranchUtility.NewestChannel(entry.get('channel') for entry in feature)
32
33
34def _GetChannelFromApiFeatures(api_name, file_system):
35  return _GetChannelFromFeatures(
36      api_name,
37      file_system,
38      '%s/_api_features.json' % svn_constants.API_PATH)
39
40
41def _GetChannelFromManifestFeatures(api_name, file_system):
42  return _GetChannelFromFeatures(
43      UnixName(api_name), #_manifest_features uses unix_style API names
44      file_system,
45      '%s/_manifest_features.json' % svn_constants.API_PATH)
46
47
48def _GetChannelFromPermissionFeatures(api_name, file_system):
49  return _GetChannelFromFeatures(
50      api_name,
51      file_system,
52      '%s/_permission_features.json' % svn_constants.API_PATH)
53
54
55def _ExistsInExtensionApi(api_name, file_system):
56  '''Parses the api/extension_api.json file (available in Chrome versions
57  before 18) for an API namespace. If this is successfully found, then the API
58  is considered to have been 'stable' for the given version.
59  '''
60  try:
61    extension_api_json = file_system.GetFromFile(
62        '%s/extension_api.json' % svn_constants.API_PATH)
63    api_rows = [row.get('namespace') for row in extension_api_json
64                if 'namespace' in row]
65    return api_name in api_rows
66  except FileNotFoundError:
67    # This should only happen on preview.py since extension_api.json is no
68    # longer present in trunk.
69    return False
70
71
72class AvailabilityFinder(object):
73  '''Generates availability information for APIs by looking at API schemas and
74  _features files over multiple release versions of Chrome.
75  '''
76
77  def __init__(self,
78               file_system_iterator,
79               object_store_creator,
80               branch_utility):
81    self._file_system_iterator = file_system_iterator
82    self._object_store_creator = object_store_creator
83    self._object_store = self._object_store_creator.Create(AvailabilityFinder)
84    self._branch_utility = branch_utility
85
86  def _ExistsInFileSystem(self, api_name, file_system):
87    '''Checks for existence of |api_name| within the list of api files in the
88    api/ directory found using the given |file_system|.
89    '''
90    file_names = file_system.ReadSingle('%s/' % svn_constants.API_PATH)
91    api_names = tuple(os.path.splitext(name)[0] for name in file_names
92                      if os.path.splitext(name)[1][1:] in ['json', 'idl'])
93
94    # API file names in api/ are unix_name at every version except for versions
95    # 18, 19, and 20. Since unix_name is the more common format, check it first.
96    return (UnixName(api_name) in api_names) or (api_name in api_names)
97
98  def _CheckStableAvailability(self, api_name, file_system, version):
99    '''Checks for availability of an API, |api_name|, on the stable channel.
100    Considers several _features.json files, file system existence, and
101    extension_api.json depending on the given |version|.
102    '''
103    if version < 5:
104      # SVN data isn't available below version 5.
105      return False
106    available_channel = None
107    fs_factory = CompiledFileSystem.Factory(file_system,
108                                            self._object_store_creator)
109    features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json),
110                                    AvailabilityFinder,
111                                    category='features')
112    if version >= 28:
113      # The _api_features.json file first appears in version 28 and should be
114      # the most reliable for finding API availability.
115      available_channel = _GetChannelFromApiFeatures(api_name, features_fs)
116    if version >= 20:
117      # The _permission_features.json and _manifest_features.json files are
118      # present in Chrome 20 and onwards. Use these if no information could be
119      # found using _api_features.json.
120      available_channel = available_channel or (
121          _GetChannelFromPermissionFeatures(api_name, features_fs)
122          or _GetChannelFromManifestFeatures(api_name, features_fs))
123      if available_channel is not None:
124        return available_channel == 'stable'
125    if version >= 18:
126      # Fall back to a check for file system existence if the API is not
127      # stable in any of the _features.json files, OR if we're dealing with
128      # version 18 or 19, which don't contain relevant _features information.
129      return self._ExistsInFileSystem(api_name, file_system)
130    if version >= 5:
131      # Versions 17 down to 5 have an extension_api.json file which
132      # contains namespaces for each API that was available at the time.
133      return _ExistsInExtensionApi(api_name, features_fs)
134
135  def _CheckChannelAvailability(self, api_name, file_system, channel_name):
136    '''Searches through the _features files in a given |file_system| and
137    determines whether or not an API is available on the given channel,
138    |channel_name|.
139    '''
140    fs_factory = CompiledFileSystem.Factory(file_system,
141                                            self._object_store_creator)
142    features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json),
143                                    AvailabilityFinder,
144                                    category='features')
145    available_channel = (_GetChannelFromApiFeatures(api_name, features_fs)
146        or _GetChannelFromPermissionFeatures(api_name, features_fs)
147        or _GetChannelFromManifestFeatures(api_name, features_fs))
148    if (available_channel is None and
149        self._ExistsInFileSystem(api_name, file_system)):
150      # If an API is not represented in any of the _features files, but exists
151      # in the filesystem, then assume it is available in this version.
152      # The windows API is an example of this.
153      available_channel = channel_name
154    # If the channel we're checking is the same as or newer than the
155    # |available_channel| then the API is available at this channel.
156    return (available_channel is not None and
157            BranchUtility.NewestChannel((available_channel, channel_name))
158                == channel_name)
159
160  def _CheckApiAvailability(self, api_name, file_system, channel_info):
161    '''Determines the availability for an API at a certain version of Chrome.
162    Two branches of logic are used depending on whether or not the API is
163    determined to be 'stable' at the given version.
164    '''
165    if channel_info.channel == 'stable':
166      return self._CheckStableAvailability(api_name,
167                                           file_system,
168                                           channel_info.version)
169    return self._CheckChannelAvailability(api_name,
170                                          file_system,
171                                          channel_info.channel)
172
173  def GetApiAvailability(self, api_name):
174    '''Performs a search for an API's top-level availability by using a
175    HostFileSystemIterator instance to traverse multiple version of the
176    SVN filesystem.
177    '''
178    availability = self._object_store.Get(api_name).Get()
179    if availability is not None:
180      return availability
181
182    def check_api_availability(file_system, channel_info):
183      return self._CheckApiAvailability(api_name, file_system, channel_info)
184
185    availability = self._file_system_iterator.Descending(
186        self._branch_utility.GetChannelInfo('dev'),
187        check_api_availability)
188    if availability is None:
189      # The API wasn't available on 'dev', so it must be a 'trunk'-only API.
190      availability = self._branch_utility.GetChannelInfo('trunk')
191    self._object_store.Set(api_name, availability)
192    return availability
193