blob: bf502a531f60bd9bf25ab3bf5fe153b9cce865a0 [file] [log] [blame]
Michael Bestas3952f6c2016-08-26 01:12:08 +03001#!/usr/bin/env python
2# Copyright (C) 2012-2013, The CyanogenMod Project
Dan Pasanen03447712016-12-19 11:22:55 -06003# (C) 2017, The LineageOS Project
Michael Bestas3952f6c2016-08-26 01:12:08 +03004#
5# Licensed under the Apache License, Version 2.0 (the "License");
6# you may not use this file except in compliance with the License.
7# You may obtain a copy of the License at
8#
9# http://www.apache.org/licenses/LICENSE-2.0
10#
11# Unless required by applicable law or agreed to in writing, software
12# distributed under the License is distributed on an "AS IS" BASIS,
13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14# See the License for the specific language governing permissions and
15# limitations under the License.
16
17from __future__ import print_function
18
19import base64
20import json
21import netrc
22import os
23import re
24import sys
25try:
26 # For python3
27 import urllib.error
28 import urllib.parse
29 import urllib.request
30except ImportError:
31 # For python2
32 import imp
33 import urllib2
34 import urlparse
35 urllib = imp.new_module('urllib')
36 urllib.error = urllib2
37 urllib.parse = urlparse
38 urllib.request = urllib2
39
40from xml.etree import ElementTree
41
42product = sys.argv[1]
43
44if len(sys.argv) > 2:
45 depsonly = sys.argv[2]
46else:
47 depsonly = None
48
49try:
50 device = product[product.index("_") + 1:]
51except:
52 device = product
53
54if not depsonly:
Dan Pasanen03447712016-12-19 11:22:55 -060055 print("Device %s not found. Attempting to retrieve device repository from LineageOS Github (http://github.com/LineageOS)." % device)
Michael Bestas3952f6c2016-08-26 01:12:08 +030056
57repositories = []
58
59try:
60 authtuple = netrc.netrc().authenticators("api.github.com")
61
62 if authtuple:
63 auth_string = ('%s:%s' % (authtuple[0], authtuple[2])).encode()
64 githubauth = base64.encodestring(auth_string).decode().replace('\n', '')
65 else:
66 githubauth = None
67except:
68 githubauth = None
69
70def add_auth(githubreq):
71 if githubauth:
72 githubreq.add_header("Authorization","Basic %s" % githubauth)
73
74if not depsonly:
Dan Pasanen03447712016-12-19 11:22:55 -060075 githubreq = urllib.request.Request("https://api.github.com/search/repositories?q=%s+user:LineageOS+in:name+fork:true" % device)
Michael Bestas3952f6c2016-08-26 01:12:08 +030076 add_auth(githubreq)
77 try:
78 result = json.loads(urllib.request.urlopen(githubreq).read().decode())
79 except urllib.error.URLError:
80 print("Failed to search GitHub")
81 sys.exit()
82 except ValueError:
83 print("Failed to parse return data from GitHub")
84 sys.exit()
85 for res in result.get('items', []):
86 repositories.append(res)
87
88local_manifests = r'.repo/local_manifests'
89if not os.path.exists(local_manifests): os.makedirs(local_manifests)
90
91def exists_in_tree(lm, path):
92 for child in lm.getchildren():
93 if child.attrib['path'] == path:
94 return True
95 return False
96
97# in-place prettyprint formatter
98def indent(elem, level=0):
99 i = "\n" + level*" "
100 if len(elem):
101 if not elem.text or not elem.text.strip():
102 elem.text = i + " "
103 if not elem.tail or not elem.tail.strip():
104 elem.tail = i
105 for elem in elem:
106 indent(elem, level+1)
107 if not elem.tail or not elem.tail.strip():
108 elem.tail = i
109 else:
110 if level and (not elem.tail or not elem.tail.strip()):
111 elem.tail = i
112
Tom Powellf8adf062020-02-25 20:45:43 -0800113def get_manifest_path():
114 '''Find the current manifest path
115 In old versions of repo this is at .repo/manifest.xml
116 In new versions, .repo/manifest.xml includes an include
117 to some arbitrary file in .repo/manifests'''
118
Michael Bestas3952f6c2016-08-26 01:12:08 +0300119 m = ElementTree.parse(".repo/manifest.xml")
Tom Powellf8adf062020-02-25 20:45:43 -0800120 try:
121 m.findall('default')[0]
122 return '.repo/manifest.xml'
123 except IndexError:
124 return ".repo/manifests/{}".format(m.find("include").get("name"))
125
126def get_default_revision():
127 m = ElementTree.parse(get_manifest_path())
Michael Bestas3952f6c2016-08-26 01:12:08 +0300128 d = m.findall('default')[0]
129 r = d.get('revision')
130 return r.replace('refs/heads/', '').replace('refs/tags/', '')
131
132def get_from_manifest(devicename):
133 try:
134 lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
135 lm = lm.getroot()
136 except:
137 lm = ElementTree.Element("manifest")
138
139 for localpath in lm.findall("project"):
140 if re.search("android_device_.*_%s$" % device, localpath.get("name")):
141 return localpath.get("path")
142
Michael Bestas3952f6c2016-08-26 01:12:08 +0300143 return None
144
145def is_in_manifest(projectpath):
146 try:
147 lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
148 lm = lm.getroot()
149 except:
150 lm = ElementTree.Element("manifest")
151
152 for localpath in lm.findall("project"):
153 if localpath.get("path") == projectpath:
154 return True
155
Michael Bestase5969e22017-06-17 20:01:23 +0300156 # Search in main manifest, too
Michael Bestas3952f6c2016-08-26 01:12:08 +0300157 try:
Tom Powellf8adf062020-02-25 20:45:43 -0800158 lm = ElementTree.parse(get_manifest_path())
Michael Bestas3952f6c2016-08-26 01:12:08 +0300159 lm = lm.getroot()
160 except:
161 lm = ElementTree.Element("manifest")
162
163 for localpath in lm.findall("project"):
164 if localpath.get("path") == projectpath:
165 return True
166
Michael Bestase5969e22017-06-17 20:01:23 +0300167 # ... and don't forget the lineage snippet
168 try:
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200169 lm = ElementTree.parse(".repo/manifests/snippets/lineage.xml")
Michael Bestase5969e22017-06-17 20:01:23 +0300170 lm = lm.getroot()
171 except:
172 lm = ElementTree.Element("manifest")
173
174 for localpath in lm.findall("project"):
175 if localpath.get("path") == projectpath:
176 return True
177
Michael Bestas3952f6c2016-08-26 01:12:08 +0300178 return False
179
180def add_to_manifest(repositories, fallback_branch = None):
181 try:
182 lm = ElementTree.parse(".repo/local_manifests/roomservice.xml")
183 lm = lm.getroot()
184 except:
185 lm = ElementTree.Element("manifest")
186
187 for repository in repositories:
188 repo_name = repository['repository']
189 repo_target = repository['target_path']
190 print('Checking if %s is fetched from %s' % (repo_target, repo_name))
191 if is_in_manifest(repo_target):
Dan Pasanen03447712016-12-19 11:22:55 -0600192 print('LineageOS/%s already fetched to %s' % (repo_name, repo_target))
Michael Bestas3952f6c2016-08-26 01:12:08 +0300193 continue
194
Dan Pasanen03447712016-12-19 11:22:55 -0600195 print('Adding dependency: LineageOS/%s -> %s' % (repo_name, repo_target))
Michael Bestas3952f6c2016-08-26 01:12:08 +0300196 project = ElementTree.Element("project", attrib = { "path": repo_target,
Dan Pasanen03447712016-12-19 11:22:55 -0600197 "remote": "github", "name": "LineageOS/%s" % repo_name })
Michael Bestas3952f6c2016-08-26 01:12:08 +0300198
199 if 'branch' in repository:
200 project.set('revision',repository['branch'])
201 elif fallback_branch:
202 print("Using fallback branch %s for %s" % (fallback_branch, repo_name))
203 project.set('revision', fallback_branch)
204 else:
205 print("Using default branch for %s" % repo_name)
206
207 lm.append(project)
208
209 indent(lm, 0)
210 raw_xml = ElementTree.tostring(lm).decode()
211 raw_xml = '<?xml version="1.0" encoding="UTF-8"?>\n' + raw_xml
212
213 f = open('.repo/local_manifests/roomservice.xml', 'w')
214 f.write(raw_xml)
215 f.close()
216
217def fetch_dependencies(repo_path, fallback_branch = None):
Adrian DC01bdd552016-11-29 00:24:26 +0100218 print('Looking for dependencies in %s' % repo_path)
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200219 dependencies_path = repo_path + '/lineage.dependencies'
Michael Bestas3952f6c2016-08-26 01:12:08 +0300220 syncable_repos = []
Adrian DC01bdd552016-11-29 00:24:26 +0100221 verify_repos = []
Michael Bestas3952f6c2016-08-26 01:12:08 +0300222
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200223 if os.path.exists(dependencies_path):
224 dependencies_file = open(dependencies_path, 'r')
225 dependencies = json.loads(dependencies_file.read())
226 fetch_list = []
Michael Bestas3952f6c2016-08-26 01:12:08 +0300227
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200228 for dependency in dependencies:
229 if not is_in_manifest(dependency['target_path']):
230 fetch_list.append(dependency)
231 syncable_repos.append(dependency['target_path'])
232 verify_repos.append(dependency['target_path'])
LuK1337e67b6cb2017-12-28 12:46:16 +0100233 else:
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200234 verify_repos.append(dependency['target_path'])
Michael Bestas3952f6c2016-08-26 01:12:08 +0300235
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200236 dependencies_file.close()
Michael Bestas3952f6c2016-08-26 01:12:08 +0300237
Luca Stefani5c60e4f2017-08-17 19:28:48 +0200238 if len(fetch_list) > 0:
239 print('Adding dependencies to manifest')
240 add_to_manifest(fetch_list, fallback_branch)
241 else:
LuK1337f017e362018-01-26 15:00:52 +0100242 print('%s has no additional dependencies.' % repo_path)
Michael Bestas3952f6c2016-08-26 01:12:08 +0300243
244 if len(syncable_repos) > 0:
245 print('Syncing dependencies')
246 os.system('repo sync --force-sync %s' % ' '.join(syncable_repos))
247
Adrian DC01bdd552016-11-29 00:24:26 +0100248 for deprepo in verify_repos:
Michael Bestas3952f6c2016-08-26 01:12:08 +0300249 fetch_dependencies(deprepo)
250
251def has_branch(branches, revision):
252 return revision in [branch['name'] for branch in branches]
253
254if depsonly:
255 repo_path = get_from_manifest(device)
256 if repo_path:
257 fetch_dependencies(repo_path)
258 else:
259 print("Trying dependencies-only mode on a non-existing device tree?")
260
261 sys.exit()
262
263else:
264 for repository in repositories:
265 repo_name = repository['name']
Michael Gernoth29f3b572017-03-26 14:33:05 +0200266 if re.match(r"^android_device_[^_]*_" + device + "$", repo_name):
Michael Bestas3952f6c2016-08-26 01:12:08 +0300267 print("Found repository: %s" % repository['name'])
268
269 manufacturer = repo_name.replace("android_device_", "").replace("_" + device, "")
270
271 default_revision = get_default_revision()
272 print("Default revision: %s" % default_revision)
273 print("Checking branch info")
274 githubreq = urllib.request.Request(repository['branches_url'].replace('{/branch}', ''))
275 add_auth(githubreq)
276 result = json.loads(urllib.request.urlopen(githubreq).read().decode())
277
278 ## Try tags, too, since that's what releases use
279 if not has_branch(result, default_revision):
280 githubreq = urllib.request.Request(repository['tags_url'].replace('{/tag}', ''))
281 add_auth(githubreq)
282 result.extend (json.loads(urllib.request.urlopen(githubreq).read().decode()))
283
284 repo_path = "device/%s/%s" % (manufacturer, device)
285 adding = {'repository':repo_name,'target_path':repo_path}
286
287 fallback_branch = None
288 if not has_branch(result, default_revision):
289 if os.getenv('ROOMSERVICE_BRANCHES'):
290 fallbacks = list(filter(bool, os.getenv('ROOMSERVICE_BRANCHES').split(' ')))
291 for fallback in fallbacks:
292 if has_branch(result, fallback):
293 print("Using fallback branch: %s" % fallback)
294 fallback_branch = fallback
295 break
296
297 if not fallback_branch:
298 print("Default revision %s not found in %s. Bailing." % (default_revision, repo_name))
299 print("Branches found:")
300 for branch in [branch['name'] for branch in result]:
301 print(branch)
302 print("Use the ROOMSERVICE_BRANCHES environment variable to specify a list of fallback branches.")
303 sys.exit()
304
305 add_to_manifest([adding], fallback_branch)
306
307 print("Syncing repository to retrieve project.")
308 os.system('repo sync --force-sync %s' % repo_path)
309 print("Repository synced!")
310
311 fetch_dependencies(repo_path, fallback_branch)
312 print("Done")
313 sys.exit()
314
Dan Pasanen03447712016-12-19 11:22:55 -0600315print("Repository for %s not found in the LineageOS Github repository list. If this is in error, you may need to manually add it to your local_manifests/roomservice.xml." % device)