availability_finder.py revision 424c4d7b64af9d0d8fd9624f381f469654d5e3d2
1eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Copyright 2013 The Chromium Authors. All rights reserved.
2eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# Use of this source code is governed by a BSD-style license that can be
3eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch# found in the LICENSE file.
4eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
5eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochimport collections
6eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochimport os
7eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
8424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)import svn_constants
9eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom branch_utility import BranchUtility
10eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom compiled_file_system import CompiledFileSystem
11eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom file_system import FileNotFoundError
12424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler import json_parse
13eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochfrom third_party.json_schema_compiler.memoize import memoize
14424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)from third_party.json_schema_compiler.model import UnixName
15eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
16eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
17eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromFeatures(api_name, file_system, path):
18eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''Finds API channel information within _features.json files at the given
19eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  |path| for the given |file_system|. Returns None if channel information for
20eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  the API cannot be located.
21eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
22eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  feature = file_system.GetFromFile(path).get(api_name)
23eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
24eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if feature is None:
25eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return None
26eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  if isinstance(feature, collections.Mapping):
27ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    # The channel information exists as a solitary dict.
28eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return feature.get('channel')
29ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  # The channel information dict is nested within a list for whitelisting
30ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  # purposes. Take the newest channel out of all of the entries.
31ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  return BranchUtility.NewestChannel(entry.get('channel') for entry in feature)
32eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
33424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
34eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromApiFeatures(api_name, file_system):
35424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return _GetChannelFromFeatures(
36424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      api_name,
37424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      file_system,
38424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      '%s/_api_features.json' % svn_constants.API_PATH)
39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _GetChannelFromManifestFeatures(api_name, file_system):
42424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return _GetChannelFromFeatures(
43424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      UnixName(api_name), #_manifest_features uses unix_style API names
44424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      file_system,
45424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      '%s/_manifest_features.json' % svn_constants.API_PATH)
46424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
47424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
48424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)def _GetChannelFromPermissionFeatures(api_name, file_system):
49424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  return _GetChannelFromFeatures(
50424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      api_name,
51424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      file_system,
52424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      '%s/_permission_features.json' % svn_constants.API_PATH)
53eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
55eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochdef _ExistsInExtensionApi(api_name, file_system):
56eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''Parses the api/extension_api.json file (available in Chrome versions
57eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  before 18) for an API namespace. If this is successfully found, then the API
58eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  is considered to have been 'stable' for the given version.
59eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
60eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  try:
61424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    extension_api_json = file_system.GetFromFile(
62424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        '%s/extension_api.json' % svn_constants.API_PATH)
63eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    api_rows = [row.get('namespace') for row in extension_api_json
64eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                if 'namespace' in row]
65424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return api_name in api_rows
66ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch  except FileNotFoundError:
67eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    # This should only happen on preview.py since extension_api.json is no
68eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    # longer present in trunk.
69eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return False
70eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
71424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
72eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochclass AvailabilityFinder(object):
73424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  '''Generates availability information for APIs by looking at API schemas and
74424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  _features files over multiple release versions of Chrome.
75eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  '''
76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
77eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def __init__(self,
78424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)               file_system_iterator,
79eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch               object_store_creator,
80424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)               branch_utility):
81424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    self._file_system_iterator = file_system_iterator
82eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._object_store_creator = object_store_creator
83424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    self._object_store = self._object_store_creator.Create(AvailabilityFinder)
84eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._branch_utility = branch_utility
85eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
86424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def _ExistsInFileSystem(self, api_name, file_system):
87424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Checks for existence of |api_name| within the list of api files in the
88424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    api/ directory found using the given |file_system|.
89424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
90424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    file_names = file_system.ReadSingle('%s/' % svn_constants.API_PATH)
91424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    api_names = tuple(os.path.splitext(name)[0] for name in file_names
92424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                      if os.path.splitext(name)[1][1:] in ['json', 'idl'])
93424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
94424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # API file names in api/ are unix_name at every version except for versions
95424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # 18, 19, and 20. Since unix_name is the more common format, check it first.
96424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return (UnixName(api_name) in api_names) or (api_name in api_names)
97424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
98424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def _CheckStableAvailability(self, api_name, file_system, version):
99424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Checks for availability of an API, |api_name|, on the stable channel.
100424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Considers several _features.json files, file system existence, and
101424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    extension_api.json depending on the given |version|.
102eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
103424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if version < 5:
104424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # SVN data isn't available below version 5.
105424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return False
106424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    available_channel = None
107424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    fs_factory = CompiledFileSystem.Factory(file_system,
108424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                            self._object_store_creator)
109eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json),
110eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                    AvailabilityFinder,
111eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch                                    category='features')
112424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if version >= 28:
113424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _api_features.json file first appears in version 28 and should be
114424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # the most reliable for finding API availability.
115424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      available_channel = _GetChannelFromApiFeatures(api_name, features_fs)
116424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if version >= 20:
117424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The _permission_features.json and _manifest_features.json files are
118424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # present in Chrome 20 and onwards. Use these if no information could be
119424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # found using _api_features.json.
120424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      available_channel = available_channel or (
121424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          _GetChannelFromPermissionFeatures(api_name, features_fs)
122424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)          or _GetChannelFromManifestFeatures(api_name, features_fs))
123424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      if available_channel is not None:
124424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        return available_channel == 'stable'
125424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if version >= 18:
126424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # Fall back to a check for file system existence if the API is not
127424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # stable in any of the _features.json files, OR if we're dealing with
128424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # version 18 or 19, which don't contain relevant _features information.
129424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self._ExistsInFileSystem(api_name, file_system)
130424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if version >= 5:
131424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # Versions 17 down to 5 have an extension_api.json file which
132424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # contains namespaces for each API that was available at the time.
133424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return _ExistsInExtensionApi(api_name, features_fs)
134424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
135424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def _CheckChannelAvailability(self, api_name, file_system, channel_name):
136424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Searches through the _features files in a given |file_system| and
137424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    determines whether or not an API is available on the given channel,
138424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    |channel_name|.
139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
140424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    fs_factory = CompiledFileSystem.Factory(file_system,
141424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                            self._object_store_creator)
142424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    features_fs = fs_factory.Create(lambda _, json: json_parse.Parse(json),
143424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                    AvailabilityFinder,
144424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                    category='features')
145ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch    available_channel = (_GetChannelFromApiFeatures(api_name, features_fs)
146ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        or _GetChannelFromPermissionFeatures(api_name, features_fs)
147ca12bfac764ba476d6cd062bf1dde12cc64c3f40Ben Murdoch        or _GetChannelFromManifestFeatures(api_name, features_fs))
148424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if (available_channel is None and
149424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._ExistsInFileSystem(api_name, file_system)):
150eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # If an API is not represented in any of the _features files, but exists
151eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # in the filesystem, then assume it is available in this version.
152eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      # The windows API is an example of this.
153424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      available_channel = channel_name
154424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # If the channel we're checking is the same as or newer than the
155424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    # |available_channel| then the API is available at this channel.
156424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return (available_channel is not None and
157424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)            BranchUtility.NewestChannel((available_channel, channel_name))
158424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                == channel_name)
159424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)
160424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)  def _CheckApiAvailability(self, api_name, file_system, channel_info):
161424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Determines the availability for an API at a certain version of Chrome.
162424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    Two branches of logic are used depending on whether or not the API is
163424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    determined to be 'stable' at the given version.
164424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''
165424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    if channel_info.channel == 'stable':
166424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self._CheckStableAvailability(api_name,
167424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           file_system,
168424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                           channel_info.version)
169424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    return self._CheckChannelAvailability(api_name,
170424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                          file_system,
171424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)                                          channel_info.channel)
172eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
173eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch  def GetApiAvailability(self, api_name):
174424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    '''Performs a search for an API's top-level availability by using a
175424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    HostFileSystemIterator instance to traverse multiple version of the
176424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    SVN filesystem.
177eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    '''
178eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    availability = self._object_store.Get(api_name).Get()
179eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    if availability is not None:
180eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch      return availability
181eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
182424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    def check_api_availability(file_system, channel_info):
183424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      return self._CheckApiAvailability(api_name, file_system, channel_info)
184eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch
185424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)    availability = self._file_system_iterator.Descending(
186424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        self._branch_utility.GetChannelInfo('dev'),
187424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)        check_api_availability)
1883551c9c881056c480085172ff9840cab31610854Torne (Richard Coles)    if availability is None:
189424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      # The API wasn't available on 'dev', so it must be a 'trunk'-only API.
190424c4d7b64af9d0d8fd9624f381f469654d5e3d2Torne (Richard Coles)      availability = self._branch_utility.GetChannelInfo('trunk')
191eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    self._object_store.Set(api_name, availability)
192eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch    return availability
193