blob: d677a512ef6d2015cad6558c52e7a84f207605c5 [file] [log] [blame]
Anthony King7472bb72023-01-13 11:04:16 -05001#!/usr/bin/env python
2# Copyright (C) 2012-2013, The CyanogenMod Project
3# Copyright (C) 2012-2015, SlimRoms Project
4# Copyright (C) 2016-2017, AOSiP
Michael Bestas3952f6c2016-08-26 01:12:08 +03005#
Anthony King7472bb72023-01-13 11:04:16 -05006# Licensed under the Apache License, Version 2.0 (the "License");
7# you may not use this file except in compliance with the License.
8# You may obtain a copy of the License at
Michael Bestas3952f6c2016-08-26 01:12:08 +03009#
Anthony King7472bb72023-01-13 11:04:16 -050010# http://www.apache.org/licenses/LICENSE-2.0
Michael Bestas3952f6c2016-08-26 01:12:08 +030011#
Anthony King7472bb72023-01-13 11:04:16 -050012# Unless required by applicable law or agreed to in writing, software
13# distributed under the License is distributed on an "AS IS" BASIS,
14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15# See the License for the specific language governing permissions and
16# limitations under the License.
Michael Bestas3952f6c2016-08-26 01:12:08 +030017
18from __future__ import print_function
Anthony King7472bb72023-01-13 11:04:16 -050019
20import base64
Michael Bestas3952f6c2016-08-26 01:12:08 +030021import json
Anthony King7472bb72023-01-13 11:04:16 -050022import netrc
Michael Bestas3952f6c2016-08-26 01:12:08 +030023import os
Anthony King7472bb72023-01-13 11:04:16 -050024import sys
25
26from xml.etree import ElementTree
27
Michael Bestas3952f6c2016-08-26 01:12:08 +030028try:
Jackeagle881b7722019-10-13 05:33:32 -040029 # For python3
Anthony King7472bb72023-01-13 11:04:16 -050030 import urllib.error
31 import urllib.parse
Jackeagle881b7722019-10-13 05:33:32 -040032 import urllib.request
Michael Bestas3952f6c2016-08-26 01:12:08 +030033except ImportError:
Jackeagle881b7722019-10-13 05:33:32 -040034 # For python2
35 import imp
36 import urllib2
Anthony King7472bb72023-01-13 11:04:16 -050037 import urlparse
Jackeagle881b7722019-10-13 05:33:32 -040038 urllib = imp.new_module('urllib')
Anthony King7472bb72023-01-13 11:04:16 -050039 urllib.error = urllib2
40 urllib.parse = urlparse
Jackeagle881b7722019-10-13 05:33:32 -040041 urllib.request = urllib2
Michael Bestas3952f6c2016-08-26 01:12:08 +030042
Anthony King7472bb72023-01-13 11:04:16 -050043DEBUG = False
44
45custom_local_manifest = ".repo/local_manifests/roomservice.xml"
Jackeaglec439dbc2023-11-10 08:15:24 -050046custom_default_revision = os.getenv('ROOMSERVICE_DEFAULT_BRANCH', 'universe')
Anthony King7472bb72023-01-13 11:04:16 -050047custom_dependencies = "bliss.dependencies"
48org_manifest = "BlissRoms-Devices" # leave empty if org is provided in manifest
49org_display = "BlissRoms-Devices" # needed for displaying
50
51github_auth = None
Michael Bestas3952f6c2016-08-26 01:12:08 +030052
Michael Bestas3952f6c2016-08-26 01:12:08 +030053
Anthony King7472bb72023-01-13 11:04:16 -050054local_manifests = '.repo/local_manifests'
55if not os.path.exists(local_manifests):
56 os.makedirs(local_manifests)
Michael Bestas3952f6c2016-08-26 01:12:08 +030057
Michael Bestas3952f6c2016-08-26 01:12:08 +030058
Anthony King7472bb72023-01-13 11:04:16 -050059def debug(*args, **kwargs):
60 if DEBUG:
61 print(*args, **kwargs)
Michael Bestas3952f6c2016-08-26 01:12:08 +030062
Michael Bestas3952f6c2016-08-26 01:12:08 +030063
Anthony King7472bb72023-01-13 11:04:16 -050064def add_auth(g_req):
65 global github_auth
66 if github_auth is None:
Jackeagle881b7722019-10-13 05:33:32 -040067 try:
Anthony King7472bb72023-01-13 11:04:16 -050068 auth = netrc.netrc().authenticators("api.github.com")
69 except (netrc.NetrcParseError, IOError):
70 auth = None
71 if auth:
72 github_auth = base64.b64encode(
73 ('%s:%s' % (auth[0], auth[2])).encode()
74 )
Jackeagle881b7722019-10-13 05:33:32 -040075 else:
Anthony King7472bb72023-01-13 11:04:16 -050076 github_auth = ""
77 if github_auth:
78 g_req.add_header("Authorization", "Basic %s" % github_auth)
Jackeagle881b7722019-10-13 05:33:32 -040079
80
Michael Bestas3952f6c2016-08-26 01:12:08 +030081def indent(elem, level=0):
Anthony King7472bb72023-01-13 11:04:16 -050082 # in-place prettyprint formatter
83 i = "\n" + " " * level
Michael Bestas3952f6c2016-08-26 01:12:08 +030084 if len(elem):
85 if not elem.text or not elem.text.strip():
Anthony King7472bb72023-01-13 11:04:16 -050086 elem.text = i + " "
Michael Bestas3952f6c2016-08-26 01:12:08 +030087 if not elem.tail or not elem.tail.strip():
88 elem.tail = i
89 for elem in elem:
90 indent(elem, level+1)
91 if not elem.tail or not elem.tail.strip():
92 elem.tail = i
93 else:
94 if level and (not elem.tail or not elem.tail.strip()):
95 elem.tail = i
96
Anthony King7472bb72023-01-13 11:04:16 -050097def load_manifest(manifest):
Tom Powellf8adf062020-02-25 20:45:43 -080098 try:
Anthony King7472bb72023-01-13 11:04:16 -050099 man = ElementTree.parse(manifest).getroot()
100 except (IOError, ElementTree.ParseError):
101 man = ElementTree.Element("manifest")
102 return man
Michael Bestas3952f6c2016-08-26 01:12:08 +0300103
Anthony King7472bb72023-01-13 11:04:16 -0500104def get_from_manifest(device_name):
105 if os.path.exists(custom_local_manifest):
106 man = load_manifest(custom_local_manifest)
107 for local_path in man.findall("project"):
108 lp = local_path.get("path").strip('/')
109 if lp.startswith("device/") and lp.endswith("/" + device_name):
110 return lp
Michael Bestas3952f6c2016-08-26 01:12:08 +0300111 return None
112
Michael Bestas3952f6c2016-08-26 01:12:08 +0300113
Anthony King7472bb72023-01-13 11:04:16 -0500114def is_in_manifest(project_path):
115 man = load_manifest(custom_local_manifest)
116 for local_path in man.findall("project"):
117 if local_path.get("path") == project_path:
118 return True
119 return False
Michael Bestas3952f6c2016-08-26 01:12:08 +0300120
Michael Bestas3952f6c2016-08-26 01:12:08 +0300121
Anthony King7472bb72023-01-13 11:04:16 -0500122def add_to_manifest(repos, fallback_branch=None):
123 lm = load_manifest(custom_local_manifest)
124
125 for repo in repos:
126 repo_name = repo['repository']
127 repo_path = repo['target_path']
128 if 'branch' in repo:
129 repo_branch=repo['branch']
130 else:
131 repo_branch=custom_default_revision
132 if 'remote' in repo:
133 repo_remote=repo['remote']
134 elif "/" not in repo_name:
135 repo_remote=org_manifest
136 elif "/" in repo_name:
137 repo_remote="github"
138
139 if is_in_manifest(repo_path):
140 print('already exists: %s' % repo_path)
141 continue
142
143 print('Adding dependency:\nRepository: %s\nBranch: %s\nRemote: %s\nPath: %s\n' % (repo_name, repo_branch,repo_remote, repo_path))
144
145 project = ElementTree.Element(
146 "project",
147 attrib={"path": repo_path,
148 "remote": repo_remote,
149 "name": "%s" % repo_name}
150 )
151
152 clone_depth = os.getenv('ROOMSERVICE_CLONE_DEPTH')
153 if clone_depth:
154 project.set('clone-depth', clone_depth)
155
156 if repo_branch is not None:
157 project.set('revision', repo_branch)
158 elif fallback_branch:
159 print("Using branch %s for %s" %
160 (fallback_branch, repo_name))
161 project.set('revision', fallback_branch)
162 else:
163 print("Using default branch for %s" % repo_name)
164 if 'clone-depth' in repo:
165 print("Setting clone-depth to %s for %s" % (repo['clone-depth'], repo_name))
166 project.set('clone-depth', repo['clone-depth'])
167 lm.append(project)
168
169 indent(lm)
170 raw_xml = "\n".join(('<?xml version="1.0" encoding="UTF-8"?>',
171 ElementTree.tostring(lm).decode()))
172
173 f = open(custom_local_manifest, 'w')
174 f.write(raw_xml)
175 f.close()
176
177_fetch_dep_cache = []
Michael Bestas3952f6c2016-08-26 01:12:08 +0300178
Michael Bestas3952f6c2016-08-26 01:12:08 +0300179
Anthony King7472bb72023-01-13 11:04:16 -0500180def fetch_dependencies(repo_path, fallback_branch=None):
181 global _fetch_dep_cache
182 if repo_path in _fetch_dep_cache:
Jackeagle881b7722019-10-13 05:33:32 -0400183 return
Anthony King7472bb72023-01-13 11:04:16 -0500184 _fetch_dep_cache.append(repo_path)
185
186 print('Looking for dependencies')
187
188 dep_p = '/'.join((repo_path, custom_dependencies))
189 if os.path.exists(dep_p):
190 with open(dep_p) as dep_f:
191 dependencies = json.load(dep_f)
192 else:
193 dependencies = {}
194 print('%s has no additional dependencies.' % repo_path)
195
196 fetch_list = []
197 syncable_repos = []
198
199 for dependency in dependencies:
200 if not is_in_manifest(dependency['target_path']):
201 if not dependency.get('branch'):
202 dependency['branch'] = custom_default_revision
203
204 fetch_list.append(dependency)
205 syncable_repos.append(dependency['target_path'])
206 else:
207 print("Dependency already present in manifest: %s => %s" % (dependency['repository'], dependency['target_path']))
208
209 if fetch_list:
210 print('Adding dependencies to manifest\n')
211 add_to_manifest(fetch_list, fallback_branch)
212
213 if syncable_repos:
214 print('Syncing dependencies')
215 os.system('repo sync --force-sync --no-tags --current-branch --no-clone-bundle %s' % ' '.join(syncable_repos))
216
217 for deprepo in syncable_repos:
218 fetch_dependencies(deprepo)
Jackeagle881b7722019-10-13 05:33:32 -0400219
220
Anthony King7472bb72023-01-13 11:04:16 -0500221def has_branch(branches, revision):
222 return revision in (branch['name'] for branch in branches)
223
224
225def detect_revision(repo):
226 """
227 returns None if using the default revision, else return
228 the branch name if using a different revision
229 """
230 print("Checking branch info")
231 githubreq = urllib.request.Request(
232 repo['branches_url'].replace('{/branch}', ''))
233 add_auth(githubreq)
234 result = json.loads(urllib.request.urlopen(githubreq).read().decode())
235
236 print("Calculated revision: %s" % custom_default_revision)
237
238 if has_branch(result, custom_default_revision):
239 return custom_default_revision
240
241 print("Branch %s not found" % custom_default_revision)
242 sys.exit()
243
244
245def main():
246 global DEBUG
247 try:
248 depsonly = bool(sys.argv[2] in ['true', 1])
249 except IndexError:
250 depsonly = False
251
252 if os.getenv('ROOMSERVICE_DEBUG'):
253 DEBUG = True
Jackeagle881b7722019-10-13 05:33:32 -0400254
255 product = sys.argv[1]
Anthony King7472bb72023-01-13 11:04:16 -0500256 device = product[product.find("_") + 1:] or product
257
258 if depsonly:
259 repo_path = get_from_manifest(device)
260 if repo_path:
261 fetch_dependencies(repo_path)
262 else:
263 print("Trying dependencies-only mode on a "
264 "non-existing device tree?")
265 sys.exit()
266
267 print("Device {0} not found. Attempting to retrieve device repository from "
268 "{1} Github (http://github.com/{1}).".format(device, org_display))
269
270 githubreq = urllib.request.Request(
271 "https://api.github.com/search/repositories?"
272 "q={0}+user:{1}+in:name+fork:true".format(device, org_display))
273 add_auth(githubreq)
274
275 repositories = []
276
Jackeagle881b7722019-10-13 05:33:32 -0400277 try:
Anthony King7472bb72023-01-13 11:04:16 -0500278 result = json.loads(urllib.request.urlopen(githubreq).read().decode())
279 except urllib.error.URLError:
280 print("Failed to search GitHub")
281 sys.exit(1)
Jackeagle881b7722019-10-13 05:33:32 -0400282 except ValueError:
Anthony King7472bb72023-01-13 11:04:16 -0500283 print("Failed to parse return data from GitHub")
284 sys.exit(1)
285 for res in result.get('items', []):
286 repositories.append(res)
Jackeagle881b7722019-10-13 05:33:32 -0400287
Anthony King7472bb72023-01-13 11:04:16 -0500288 for repository in repositories:
289 repo_name = repository['name']
Michael Bestas3952f6c2016-08-26 01:12:08 +0300290
Jackeagle1a98fe02023-01-13 11:06:47 -0500291 if not (repo_name.startswith("android_device") and
Anthony King7472bb72023-01-13 11:04:16 -0500292 repo_name.endswith("_" + device)):
293 continue
294 print("Found repository: %s" % repository['name'])
295
296 fallback_branch = detect_revision(repository)
Jackeagle1a98fe02023-01-13 11:06:47 -0500297 manufacturer = repo_name[15:-(len(device)+1)]
Anthony King7472bb72023-01-13 11:04:16 -0500298 repo_path = "device/%s/%s" % (manufacturer, device)
299 adding = [{'repository': repo_name, 'target_path': repo_path}]
300
301 add_to_manifest(adding, fallback_branch)
302
303 print("Syncing repository to retrieve project.")
304 os.system('repo sync --force-sync --no-tags --current-branch --no-clone-bundle %s' % repo_path)
305 print("Repository synced!")
306
307 fetch_dependencies(repo_path, fallback_branch)
308 print("Done")
309 sys.exit()
310
311 print("Repository for %s not found in the %s Github repository list."
312 % (device, org_display))
313 print("If this is in error, you may need to manually add it to your "
314 "%s" % custom_local_manifest)
315
316if __name__ == "__main__":
317 main()