blob: d2c13b027e1536a14a0b9ca11269b5a379afefea [file] [log] [blame]
LuK1337f8a64942024-02-14 09:06:18 +01001#!/usr/bin/env python3
cybojenix3e873402013-10-17 03:34:57 +04002
3# Copyright (C) 2013 Cybojenix <anthonydking@gmail.com>
4# Copyright (C) 2013 The OmniROM Project
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation, either version 3 of the License, or
9# (at your option) any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15#
16# You should have received a copy of the GNU General Public License
17# along with this program. If not, see <http://www.gnu.org/licenses/>.
18
19from __future__ import print_function
20import json
21import sys
22import os
23import os.path
24import re
25from xml.etree import ElementTree as ES
26# Use the urllib importer from the Cyanogenmod roomservice
LuK1337728729b2024-02-14 09:22:34 +010027import urllib.request
cybojenix3e873402013-10-17 03:34:57 +040028
29# Config
30# set this to the default remote to use in repo
Marko Man9401bfe2020-04-15 22:16:16 +020031default_rem = "omnirom2"
cybojenix3e873402013-10-17 03:34:57 +040032# set this to the default revision to use (branch/tag name)
micky387ceda3c12025-06-25 13:48:42 -040033default_rev = "android-16"
cybojenix3e873402013-10-17 03:34:57 +040034# set this to the remote that you use for projects from your team repos
35# example fetch="https://github.com/omnirom"
Marko Manba650f32020-04-17 00:29:51 +020036default_team_rem = "omnirom2"
cybojenix3e873402013-10-17 03:34:57 +040037# this shouldn't change unless google makes changes
38local_manifest_dir = ".repo/local_manifests"
39# change this to your name on github (or equivalent hosting)
40android_team = "omnirom"
41# url to gerrit repository
42gerrit_url = "gerrit.omnirom.org"
maxwencfdb5c22025-05-04 13:30:02 +020043github_url = "api.github.com"
cybojenix3e873402013-10-17 03:34:57 +040044
45
46def check_repo_exists(git_data, device):
Vachounetb99cad92021-02-18 14:44:04 +010047 if device.count("_") < 2:
48 re_match = "^android_device_.*_{device}$".format(device=device)
49 else:
50 re_match = "^android_device.*_{device}$".format(device=device)
51
Felix Elsner67fdf892018-12-04 21:45:49 +010052 matches = list(filter(lambda x: re.match(re_match, x), git_data))
Vachounetb99cad92021-02-18 14:44:04 +010053
cybojenix3e873402013-10-17 03:34:57 +040054 if len(matches) != 1:
55 raise Exception("{device} not found,"
56 "exiting roomservice".format(device=device))
57
58 return git_data[matches[0]]
59
60
61def search_gerrit_for_device(device):
62 # TODO: In next gerrit release regex search with r= should be supported!
63 git_search_url = "https://{gerrit_url}/projects/?m={device}".format(
64 gerrit_url=gerrit_url,
65 device=device
66 )
67 git_req = urllib.request.Request(git_search_url)
68 try:
69 response = urllib.request.urlopen(git_req)
Felix Elsnerc1c2d752018-11-25 12:46:13 +010070 except urllib.request.HTTPError:
cybojenix3e873402013-10-17 03:34:57 +040071 print("There was an issue connecting to gerrit."
Felix Elsnerc1c2d752018-11-25 12:46:13 +010072 " Please try again in a minute")
73 except urllib.request.URLError:
cybojenix3e873402013-10-17 03:34:57 +040074 print("WARNING: No network connection available.")
75 else:
76 # Skip silly gerrit "header"
77 response.readline()
78 git_data = json.load(response)
79 device_data = check_repo_exists(git_data, device)
80 print("found the {} device repo".format(device))
81 return device_data
82
maxwencfdb5c22025-05-04 13:30:02 +020083def check_repo_exists_github(git_data, device):
84 if device.count("_") < 2:
85 re_match = "android_device_.*_{device}".format(device=device)
86 else:
87 re_match = "android_device.*_{device}".format(device=device)
88
89 for r in git_data:
90 if re.match(re_match, r["name"]):
91 return r
92
93 raise Exception("{device} not found,"
94 "exiting roomservice".format(device=device))
95
96def search_github_for_device(device):
97 # TODO: In next gerrit release regex search with r= should be supported!
98 git_search_url = "https://{github_url}/search/repositories?q={device}+owner:omnirom".format(
99 github_url=github_url,
100 device=device
101 )
102 git_req = urllib.request.Request(git_search_url)
103 try:
104 response = urllib.request.urlopen(git_req)
105 except urllib.request.HTTPError:
106 print("There was an issue connecting to gerrit."
107 " Please try again in a minute")
108 except urllib.request.URLError:
109 print("WARNING: No network connection available.")
110 else:
111 git_data = json.load(response)
112 device_data = check_repo_exists_github(git_data["items"], device)
113 print("found the {} device repo".format(device))
114 return {"id":device_data["name"]}
cybojenix3e873402013-10-17 03:34:57 +0400115
116def parse_device_directory(device_url, device):
Vachounetb99cad92021-02-18 14:44:04 +0100117 if device.count("_") < 2:
118 pattern = "^android_device_(?P<vendor>.+)_{}$".format(device)
119 else:
120 pattern = "^android_device_{}$".format(device)
121
cybojenix3e873402013-10-17 03:34:57 +0400122 match = re.match(pattern, device_url)
123
124 if match is None:
125 raise Exception("Invalid project name {}".format(device_url))
Vachounetb99cad92021-02-18 14:44:04 +0100126
127 if device.count('_') < 2:
128 return "device/{vendor}/{device}".format(
129 vendor=match.group('vendor'),
130 device=device,
131 )
132 else:
133 vendor = device.split('_')[0]
134 return "device/{vendor}/{device}".format(
135 vendor=vendor,
136 device=device,
137 )
cybojenix3e873402013-10-17 03:34:57 +0400138
139
140# Thank you RaYmAn
141def iterate_manifests():
142 files = []
143 for file in os.listdir(local_manifest_dir):
144 if file.endswith(".xml"):
145 files.append(os.path.join(local_manifest_dir, file))
146 files.append('.repo/manifest.xml')
147 for file in files:
148 try:
149 man = ES.parse(file)
150 man = man.getroot()
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100151 except (IOError, ES.ParseError):
cybojenix3e873402013-10-17 03:34:57 +0400152 print("WARNING: error while parsing %s" % file)
153 else:
154 for project in man.findall("project"):
155 yield project
156
157
maxwen52025a82019-05-08 17:04:42 +0200158def iterate_manifests_remove_project():
159 files = []
160 for file in os.listdir(local_manifest_dir):
161 if file.endswith(".xml"):
162 files.append(os.path.join(local_manifest_dir, file))
163 files.append('.repo/manifest.xml')
164 for file in files:
165 try:
166 man = ES.parse(file)
167 man = man.getroot()
168 except (IOError, ES.ParseError):
169 print("WARNING: error while parsing %s" % file)
170 else:
171 for project in man.findall("remove-project"):
172 yield project
173
cybojenix3e873402013-10-17 03:34:57 +0400174def check_project_exists(url, revision, path):
175 for project in iterate_manifests():
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100176 if project.get("name") == url \
177 and project.get("revision") == revision \
178 and project.get("path") == path:
cybojenix3e873402013-10-17 03:34:57 +0400179 return True
180 return False
181
182
maxwen52025a82019-05-08 17:04:42 +0200183def check_remove_project_exists(url):
184 for project in iterate_manifests_remove_project():
185 if project.get("name") == url:
186 return True
187 return False
188
cybojenix3e873402013-10-17 03:34:57 +0400189def check_target_exists(directory):
190 return os.path.isdir(directory)
191
192
193# Use the indent function from http://stackoverflow.com/a/4590052
194def indent(elem, level=0):
195 i = ''.join(["\n", level*" "])
196 if len(elem):
197 if not elem.text or not elem.text.strip():
198 elem.text = ''.join([i, " "])
199 if not elem.tail or not elem.tail.strip():
200 elem.tail = i
201 for elem in elem:
202 indent(elem, level+1)
203 if not elem.tail or not elem.tail.strip():
204 elem.tail = i
205 else:
206 if level and (not elem.tail or not elem.tail.strip()):
207 elem.tail = i
208
209
210def create_manifest_project(url, directory,
211 remote=default_rem,
212 revision=default_rev):
213 project_exists = check_project_exists(url, revision, directory)
214
215 if project_exists:
216 return None
217
218 project = ES.Element("project",
219 attrib={
220 "path": directory,
221 "name": url,
222 "remote": remote,
223 "revision": revision
224 })
225 return project
226
maxwen52025a82019-05-08 17:04:42 +0200227def create_remove_project(url):
228 remove_project_exists = check_remove_project_exists(url)
229
230 if remove_project_exists:
231 return None
232
233 project = ES.Element("remove-project",
234 attrib={
235 "name": url
236 })
237 return project
cybojenix3e873402013-10-17 03:34:57 +0400238
239def append_to_manifest(project):
240 try:
241 lm = ES.parse('/'.join([local_manifest_dir, "roomservice.xml"]))
242 lm = lm.getroot()
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100243 except (IOError, ES.ParseError):
cybojenix3e873402013-10-17 03:34:57 +0400244 lm = ES.Element("manifest")
245 lm.append(project)
246 return lm
247
248
249def write_to_manifest(manifest):
250 indent(manifest)
251 raw_xml = ES.tostring(manifest).decode()
252 raw_xml = ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n'
253 '<!--Please do not manually edit this file-->\n',
254 raw_xml])
255
256 with open('/'.join([local_manifest_dir, "roomservice.xml"]), 'w') as f:
257 f.write(raw_xml)
258 print("wrote the new roomservice manifest")
259
260
261def parse_device_from_manifest(device):
262 for project in iterate_manifests():
263 name = project.get('name')
264 if name.startswith("android_device_") and name.endswith(device):
265 return project.get('path')
266 return None
267
268
269def parse_device_from_folder(device):
270 search = []
maxwenc8a6b6c2017-12-05 01:37:48 +0100271 if not os.path.isdir("device"):
272 os.mkdir("device")
cybojenix3e873402013-10-17 03:34:57 +0400273 for sub_folder in os.listdir("device"):
274 if os.path.isdir("device/%s/%s" % (sub_folder, device)):
275 search.append("device/%s/%s" % (sub_folder, device))
276 if len(search) > 1:
277 print("multiple devices under the name %s. "
278 "defaulting to checking the manifest" % device)
279 location = parse_device_from_manifest(device)
280 elif len(search) == 1:
281 location = search[0]
282 else:
283 print("Your device can't be found in device sources..")
284 location = parse_device_from_manifest(device)
285 return location
286
287
288def parse_dependency_file(location):
289 dep_file = "omni.dependencies"
290 dep_location = '/'.join([location, dep_file])
291 if not os.path.isfile(dep_location):
292 print("WARNING: %s file not found" % dep_location)
293 sys.exit()
294 try:
295 with open(dep_location, 'r') as f:
296 dependencies = json.loads(f.read())
297 except ValueError:
298 raise Exception("ERROR: malformed dependency file")
299 return dependencies
300
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100301
cybojenix3e873402013-10-17 03:34:57 +0400302# if there is any conflict with existing and new
303# delete the roomservice.xml file and create new
304def check_manifest_problems(dependencies):
305 for dependency in dependencies:
306 repository = dependency.get("repository")
307 target_path = dependency.get("target_path")
308 revision = dependency.get("revision", default_rev)
cybojenix3e873402013-10-17 03:34:57 +0400309
310 # check for existing projects
311 for project in iterate_manifests():
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100312 if project.get("revision") is not None \
313 and project.get("path") is not None \
314 and project.get("path") == target_path \
315 and project.get("revision") != revision:
316 print("WARNING: detected conflict in revisions for repository ",
317 repository)
318 current_dependency = str(project.get(repository))
319 file = ES.parse('/'.join([local_manifest_dir,
320 "roomservice.xml"]))
321 file_root = file.getroot()
322 for current_project in file_root.findall('project'):
323 new_dependency = str(current_project.find('revision'))
324 if new_dependency == current_dependency:
325 file_root.remove(current_project)
326 file.write('/'.join([local_manifest_dir, "roomservice.xml"]))
327 return
328
cybojenix3e873402013-10-17 03:34:57 +0400329
330def create_dependency_manifest(dependencies):
331 projects = []
332 for dependency in dependencies:
333 repository = dependency.get("repository")
334 target_path = dependency.get("target_path")
335 revision = dependency.get("revision", default_rev)
336 remote = dependency.get("remote", default_rem)
maxwen52025a82019-05-08 17:04:42 +0200337 override = dependency.get("override", None)
maxwen2ef6de32020-02-18 12:35:40 +0100338 remove = dependency.get("remove", None)
339
340 if remove is not None:
341 #print("found remove in ", repository)
342 project = create_remove_project(remove)
maxwen52025a82019-05-08 17:04:42 +0200343 if project is not None:
344 manifest = append_to_manifest(project)
345 #print(ES.tostring(manifest).decode())
346 write_to_manifest(manifest)
maxwen2ef6de32020-02-18 12:35:40 +0100347 else:
348 if override is not None:
349 #print("found override in ", repository)
350 project = create_remove_project(override)
351 if project is not None:
352 manifest = append_to_manifest(project)
353 #print(ES.tostring(manifest).decode())
354 write_to_manifest(manifest)
cybojenix3e873402013-10-17 03:34:57 +0400355
maxwen2ef6de32020-02-18 12:35:40 +0100356 # not adding an organization should default to android_team
357 # only apply this to github
358 if remote == "github":
359 if "/" not in repository:
360 repository = '/'.join([android_team, repository])
361 project = create_manifest_project(repository,
362 target_path,
363 remote=remote,
364 revision=revision)
365 if project is not None:
366 manifest = append_to_manifest(project)
367 write_to_manifest(manifest)
368 projects.append(target_path)
max.weningereec135d2024-01-20 23:29:03 +0000369 if len(projects) > 0:
370 os.system("repo sync --force-sync --no-clone-bundle %s" % " ".join(projects))
cybojenix3e873402013-10-17 03:34:57 +0400371
372
373def create_common_dependencies_manifest(dependencies):
374 dep_file = "omni.dependencies"
375 common_list = []
376 if dependencies is not None:
377 for dependency in dependencies:
378 try:
379 index = common_list.index(dependency['target_path'])
380 except ValueError:
381 index = None
382 if index is None:
383 common_list.append(dependency['target_path'])
384 dep_location = '/'.join([dependency['target_path'], dep_file])
385 if not os.path.isfile(dep_location):
386 sys.exit()
387 else:
388 try:
389 with open(dep_location, 'r') as f:
390 common_deps = json.loads(f.read())
391 except ValueError:
392 raise Exception("ERROR: malformed dependency file")
393
394 if common_deps is not None:
395 print("Looking for dependencies on: ",
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100396 dependency['target_path'])
cybojenix3e873402013-10-17 03:34:57 +0400397 check_manifest_problems(common_deps)
398 create_dependency_manifest(common_deps)
399 create_common_dependencies_manifest(common_deps)
400
401
402def fetch_dependencies(device):
Vachounet9ef51682019-11-23 18:56:32 +0100403 if device == "emulator":
maxwen3a275aa2020-11-27 23:40:31 +0100404 location = "vendor/omni/utils/omni_emulator"
Vachounet9ef51682019-11-23 18:56:32 +0100405 else:
406 location = parse_device_from_folder(device)
cybojenix3e873402013-10-17 03:34:57 +0400407 if location is None or not os.path.isdir(location):
408 raise Exception("ERROR: could not find your device "
409 "folder location, bailing out")
410 dependencies = parse_dependency_file(location)
411 check_manifest_problems(dependencies)
412 create_dependency_manifest(dependencies)
413 create_common_dependencies_manifest(dependencies)
414 fetch_device(device)
415
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100416
cybojenix3e873402013-10-17 03:34:57 +0400417def check_device_exists(device):
418 location = parse_device_from_folder(device)
419 if location is None:
420 return False
421 return os.path.isdir(location)
422
423
424def fetch_device(device):
425 if check_device_exists(device):
Marko Man718f4c22019-07-14 12:12:25 +0100426 print("WARNING: Trying to fetch a device that's already there")
maxwencfdb5c22025-05-04 13:30:02 +0200427 git_data = search_github_for_device(device)
cybojenix3e873402013-10-17 03:34:57 +0400428 if git_data is not None:
429 device_url = git_data['id']
430 device_dir = parse_device_directory(device_url, device)
431 project = create_manifest_project(device_url,
Felix Elsnerc1c2d752018-11-25 12:46:13 +0100432 device_dir,
433 remote=default_team_rem)
cybojenix3e873402013-10-17 03:34:57 +0400434 if project is not None:
435 manifest = append_to_manifest(project)
436 write_to_manifest(manifest)
437 # In case a project was written to manifest, but never synced
max.weningereec135d2024-01-20 23:29:03 +0000438 if project is not None or not check_target_exists(device_dir):
439 print("syncing the device config")
440 os.system('repo sync --force-sync --no-clone-bundle %s' % device_dir)
cybojenix3e873402013-10-17 03:34:57 +0400441
442
443if __name__ == '__main__':
444 if not os.path.isdir(local_manifest_dir):
445 os.mkdir(local_manifest_dir)
446
447 product = sys.argv[1]
448 try:
449 device = product[product.index("_") + 1:]
450 except ValueError:
451 device = product
452
453 if len(sys.argv) > 2:
454 deps_only = sys.argv[2]
455 else:
456 deps_only = False
457
458 if not deps_only:
459 fetch_device(device)
460 fetch_dependencies(device)