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