blob: c9f7ff999b75b6dfa4c8f95975b0814bbb601c87 [file] [log] [blame]
Kuang-che Wu708310b2018-03-28 17:24:34 +08001# Copyright 2018 The Chromium OS 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"""Android utility.
5
6Terminology used in this module:
7 "product-variant" is sometimes called "target" and sometimes "flavor".
8 I prefer to use "flavor" in the code because
9 - "target" is too general
10 - sometimes, it is not in the form of "product-variant", for example,
11 "cts_arm_64"
12"""
13
14from __future__ import print_function
15import json
16import logging
17import os
18
19from bisect_kit import cli
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080020from bisect_kit import repo_util
Kuang-che Wu708310b2018-03-28 17:24:34 +080021from bisect_kit import util
22
23logger = logging.getLogger(__name__)
24
25ANDROID_URL_BASE = ('https://android-build.googleplex.com/'
26 'builds/branch/{branch}')
27BUILD_IDS_BETWEEN_URL_TEMPLATE = (
28 ANDROID_URL_BASE + '/build-ids/between/{end}/{start}')
29BUILD_INFO_URL_TEMPLATE = ANDROID_URL_BASE + '/builds?id={build_id}'
30
31
32def is_android_build_id(s):
33 """Is an Android build id."""
34 # Build ID is always a number
35 return s.isdigit()
36
37
38def argtype_android_build_id(s):
39 if not is_android_build_id(s):
40 msg = 'invalid android build id (%s)' % s
41 raise cli.ArgTypeError(msg, '9876543')
42 return s
43
44
45def fetch_android_build_data(url):
46 """Fetches file from android build server.
47
48 Args:
49 url: URL to fetch
50
51 Returns:
52 file content (str). None if failed.
53 """
Kuang-che Wuacb6efd2018-04-25 18:52:58 +080054 # Fetching android build data directly will fail without authentication.
Kuang-che Wu708310b2018-03-28 17:24:34 +080055 # Following code is just serving as demo purpose. You should modify or hook
56 # it with your own implementation.
57 logger.warn('fetch_android_build_data need to be hooked')
58 import urllib2
59 try:
60 return urllib2.urlopen(url).read()
61 except urllib2.URLError:
62 logger.exception('failed to fetch "%s"', url)
63 raise
64
65
66def is_good_build(branch, flavor, build_id):
67 """Determine a build_id was succeeded.
68
69 Args:
70 branch: The Android branch from which to retrieve the builds.
71 flavor: Target name of the Android image in question.
72 E.g. "cheets_x86-userdebug" or "cheets_arm-user".
73 build_id: Android build id
74
75 Returns:
76 True if the given build was successful.
77 """
78
79 url = BUILD_INFO_URL_TEMPLATE.format(branch=branch, build_id=build_id)
80 build = json.loads(fetch_android_build_data(url).decode('utf-8'))
81 for target in build[0]['targets']:
82 if target['target']['name'] == flavor and target.get('successful'):
83 return True
84 return False
85
86
87def get_build_ids_between(branch, start, end):
88 """Returns a list of build IDs.
89
90 Args:
91 branch: The Android branch from which to retrieve the builds.
92 start: The starting point build ID. (inclusive)
93 end: The ending point build ID. (inclusive)
94
95 Returns:
96 A list of build IDs. (str)
97 """
98 # TODO(kcwu): remove pagination hack after b/68239878 fixed
99 build_id_set = set()
100 tmp_end = end
101 while True:
102 url = BUILD_IDS_BETWEEN_URL_TEMPLATE.format(
103 branch=branch, start=start, end=tmp_end)
104 query_result = json.loads(fetch_android_build_data(url).decode('utf-8'))
105 found_new = set(map(int, query_result['ids'])) - build_id_set
106 if not found_new:
107 break
108 build_id_set.update(found_new)
109 tmp_end = min(build_id_set)
110
111 logger.debug('Found %d builds in the range.', len(build_id_set))
112
113 return map(str, sorted(build_id_set))
114
115
116def lunch(android_root, flavor, *args, **kwargs):
117 """Helper to run commands with Android lunch env.
118
119 Args:
120 android_root: root path of Android tree
121 flavor: lunch flavor
122 args: command to run
123 kwargs: extra arguments passed to util.Popen
124 """
125 util.check_call('./android_lunch_helper.sh', android_root, flavor, *args,
126 **kwargs)
127
128
129def fetch_artifact(flavor, build_id, filename, dest):
130 """Fetches Android build artifact.
131
132 Args:
133 flavor: Android build flavor
134 build_id: Android build id
135 filename: artifact name
136 dest: local path to store the fetched artifact
137 """
138 util.check_call('/google/data/ro/projects/android/fetch_artifact', '--target',
139 flavor, '--bid', build_id, filename, dest)
140
141
142def fetch_manifest(android_root, flavor, build_id):
143 """Fetches Android repo manifest of given build.
144
145 Args:
146 android_root: root path of Android tree. Fetched manifest file will be
147 stored inside.
148 flavor: Android build flavor
149 build_id: Android build id
150
151 Returns:
152 the local filename of manifest (relative to android_root/.repo/manifests/)
153 """
154 # Assume manifest is flavor independent, thus not encoded into the file name.
155 manifest_name = 'manifest_%s.xml' % build_id
156 manifest_path = os.path.join(android_root, '.repo', 'manifests',
157 manifest_name)
158 if not os.path.exists(manifest_path):
159 fetch_artifact(flavor, build_id, manifest_name, manifest_path)
160 return manifest_name
Kuang-che Wuacb6efd2018-04-25 18:52:58 +0800161
162
163class AndroidManifestManager(repo_util.ManifestManager):
164 """Manifest operations for Android repo"""
165
166 def __init__(self, config):
167 self.config = config
168 self.flavor = config['flavor']
169
170 def sync_disk_state(self, rev):
171 manifest_name = self.fetch_manifest(rev)
172 repo_util.sync(
173 self.config['android_root'],
174 manifest_name=manifest_name,
175 current_branch=True)
176
177 def fetch_git_repos(self, rev):
178 # TODO(kcwu): fetch git history but don't switch current disk state.
179 self.sync_disk_state(rev)
180
181 def enumerate_manifest(self, old, new):
182 revlist = get_build_ids_between(self.config['branch'], int(old), int(new))
183
184 assert revlist[0] == old
185 assert revlist[-1] == new
186 return revlist
187
188 def fetch_manifest(self, rev):
189 return fetch_manifest(self.config['android_root'], self.flavor, rev)