cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 1 | #!/usr/bin/env python |
| 2 | |
| 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 | |
| 19 | from __future__ import print_function |
| 20 | import json |
| 21 | import sys |
| 22 | import os |
| 23 | import os.path |
| 24 | import re |
| 25 | from xml.etree import ElementTree as ES |
| 26 | # Use the urllib importer from the Cyanogenmod roomservice |
| 27 | try: |
| 28 | # For python3 |
| 29 | import urllib.request |
| 30 | except ImportError: |
| 31 | # For python2 |
| 32 | import imp |
| 33 | import urllib2 |
| 34 | urllib = imp.new_module('urllib') |
| 35 | urllib.request = urllib2 |
| 36 | |
| 37 | # Config |
| 38 | # set this to the default remote to use in repo |
| 39 | default_rem = "omnirom" |
| 40 | # set this to the default revision to use (branch/tag name) |
Marko Man | 3f42acf | 2019-09-11 22:34:33 +0200 | [diff] [blame^] | 41 | default_rev = "android-10" |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 42 | # set this to the remote that you use for projects from your team repos |
| 43 | # example fetch="https://github.com/omnirom" |
| 44 | default_team_rem = "omnirom" |
| 45 | # this shouldn't change unless google makes changes |
| 46 | local_manifest_dir = ".repo/local_manifests" |
| 47 | # change this to your name on github (or equivalent hosting) |
| 48 | android_team = "omnirom" |
| 49 | # url to gerrit repository |
| 50 | gerrit_url = "gerrit.omnirom.org" |
| 51 | |
| 52 | |
| 53 | def check_repo_exists(git_data, device): |
| 54 | re_match = "^android_device_.*_{device}$".format(device=device) |
Felix Elsner | 67fdf89 | 2018-12-04 21:45:49 +0100 | [diff] [blame] | 55 | matches = list(filter(lambda x: re.match(re_match, x), git_data)) |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 56 | if len(matches) != 1: |
| 57 | raise Exception("{device} not found," |
| 58 | "exiting roomservice".format(device=device)) |
| 59 | |
| 60 | return git_data[matches[0]] |
| 61 | |
| 62 | |
| 63 | def search_gerrit_for_device(device): |
| 64 | # TODO: In next gerrit release regex search with r= should be supported! |
| 65 | git_search_url = "https://{gerrit_url}/projects/?m={device}".format( |
| 66 | gerrit_url=gerrit_url, |
| 67 | device=device |
| 68 | ) |
| 69 | git_req = urllib.request.Request(git_search_url) |
| 70 | try: |
| 71 | response = urllib.request.urlopen(git_req) |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 72 | except urllib.request.HTTPError: |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 73 | print("There was an issue connecting to gerrit." |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 74 | " Please try again in a minute") |
| 75 | except urllib.request.URLError: |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 76 | print("WARNING: No network connection available.") |
| 77 | else: |
| 78 | # Skip silly gerrit "header" |
| 79 | response.readline() |
| 80 | git_data = json.load(response) |
| 81 | device_data = check_repo_exists(git_data, device) |
| 82 | print("found the {} device repo".format(device)) |
| 83 | return device_data |
| 84 | |
| 85 | |
| 86 | def parse_device_directory(device_url, device): |
| 87 | pattern = "^android_device_(?P<vendor>.+)_{}$".format(device) |
| 88 | match = re.match(pattern, device_url) |
| 89 | |
| 90 | if match is None: |
| 91 | raise Exception("Invalid project name {}".format(device_url)) |
| 92 | return "device/{vendor}/{device}".format( |
| 93 | vendor=match.group('vendor'), |
| 94 | device=device, |
| 95 | ) |
| 96 | |
| 97 | |
| 98 | # Thank you RaYmAn |
| 99 | def iterate_manifests(): |
| 100 | files = [] |
| 101 | for file in os.listdir(local_manifest_dir): |
| 102 | if file.endswith(".xml"): |
| 103 | files.append(os.path.join(local_manifest_dir, file)) |
| 104 | files.append('.repo/manifest.xml') |
| 105 | for file in files: |
| 106 | try: |
| 107 | man = ES.parse(file) |
| 108 | man = man.getroot() |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 109 | except (IOError, ES.ParseError): |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 110 | print("WARNING: error while parsing %s" % file) |
| 111 | else: |
| 112 | for project in man.findall("project"): |
| 113 | yield project |
| 114 | |
| 115 | |
maxwen | 52025a8 | 2019-05-08 17:04:42 +0200 | [diff] [blame] | 116 | def iterate_manifests_remove_project(): |
| 117 | files = [] |
| 118 | for file in os.listdir(local_manifest_dir): |
| 119 | if file.endswith(".xml"): |
| 120 | files.append(os.path.join(local_manifest_dir, file)) |
| 121 | files.append('.repo/manifest.xml') |
| 122 | for file in files: |
| 123 | try: |
| 124 | man = ES.parse(file) |
| 125 | man = man.getroot() |
| 126 | except (IOError, ES.ParseError): |
| 127 | print("WARNING: error while parsing %s" % file) |
| 128 | else: |
| 129 | for project in man.findall("remove-project"): |
| 130 | yield project |
| 131 | |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 132 | def check_project_exists(url, revision, path): |
| 133 | for project in iterate_manifests(): |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 134 | if project.get("name") == url \ |
| 135 | and project.get("revision") == revision \ |
| 136 | and project.get("path") == path: |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 137 | return True |
| 138 | return False |
| 139 | |
| 140 | |
maxwen | 52025a8 | 2019-05-08 17:04:42 +0200 | [diff] [blame] | 141 | def check_remove_project_exists(url): |
| 142 | for project in iterate_manifests_remove_project(): |
| 143 | if project.get("name") == url: |
| 144 | return True |
| 145 | return False |
| 146 | |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 147 | def check_target_exists(directory): |
| 148 | return os.path.isdir(directory) |
| 149 | |
| 150 | |
| 151 | # Use the indent function from http://stackoverflow.com/a/4590052 |
| 152 | def indent(elem, level=0): |
| 153 | i = ''.join(["\n", level*" "]) |
| 154 | if len(elem): |
| 155 | if not elem.text or not elem.text.strip(): |
| 156 | elem.text = ''.join([i, " "]) |
| 157 | if not elem.tail or not elem.tail.strip(): |
| 158 | elem.tail = i |
| 159 | for elem in elem: |
| 160 | indent(elem, level+1) |
| 161 | if not elem.tail or not elem.tail.strip(): |
| 162 | elem.tail = i |
| 163 | else: |
| 164 | if level and (not elem.tail or not elem.tail.strip()): |
| 165 | elem.tail = i |
| 166 | |
| 167 | |
| 168 | def create_manifest_project(url, directory, |
| 169 | remote=default_rem, |
| 170 | revision=default_rev): |
| 171 | project_exists = check_project_exists(url, revision, directory) |
| 172 | |
| 173 | if project_exists: |
| 174 | return None |
| 175 | |
| 176 | project = ES.Element("project", |
| 177 | attrib={ |
| 178 | "path": directory, |
| 179 | "name": url, |
| 180 | "remote": remote, |
| 181 | "revision": revision |
| 182 | }) |
| 183 | return project |
| 184 | |
maxwen | 52025a8 | 2019-05-08 17:04:42 +0200 | [diff] [blame] | 185 | def create_remove_project(url): |
| 186 | remove_project_exists = check_remove_project_exists(url) |
| 187 | |
| 188 | if remove_project_exists: |
| 189 | return None |
| 190 | |
| 191 | project = ES.Element("remove-project", |
| 192 | attrib={ |
| 193 | "name": url |
| 194 | }) |
| 195 | return project |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 196 | |
| 197 | def append_to_manifest(project): |
| 198 | try: |
| 199 | lm = ES.parse('/'.join([local_manifest_dir, "roomservice.xml"])) |
| 200 | lm = lm.getroot() |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 201 | except (IOError, ES.ParseError): |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 202 | lm = ES.Element("manifest") |
| 203 | lm.append(project) |
| 204 | return lm |
| 205 | |
| 206 | |
| 207 | def write_to_manifest(manifest): |
| 208 | indent(manifest) |
| 209 | raw_xml = ES.tostring(manifest).decode() |
| 210 | raw_xml = ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n' |
| 211 | '<!--Please do not manually edit this file-->\n', |
| 212 | raw_xml]) |
| 213 | |
| 214 | with open('/'.join([local_manifest_dir, "roomservice.xml"]), 'w') as f: |
| 215 | f.write(raw_xml) |
| 216 | print("wrote the new roomservice manifest") |
| 217 | |
| 218 | |
| 219 | def parse_device_from_manifest(device): |
| 220 | for project in iterate_manifests(): |
| 221 | name = project.get('name') |
| 222 | if name.startswith("android_device_") and name.endswith(device): |
| 223 | return project.get('path') |
| 224 | return None |
| 225 | |
| 226 | |
| 227 | def parse_device_from_folder(device): |
| 228 | search = [] |
maxwen | c8a6b6c | 2017-12-05 01:37:48 +0100 | [diff] [blame] | 229 | if not os.path.isdir("device"): |
| 230 | os.mkdir("device") |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 231 | for sub_folder in os.listdir("device"): |
| 232 | if os.path.isdir("device/%s/%s" % (sub_folder, device)): |
| 233 | search.append("device/%s/%s" % (sub_folder, device)) |
| 234 | if len(search) > 1: |
| 235 | print("multiple devices under the name %s. " |
| 236 | "defaulting to checking the manifest" % device) |
| 237 | location = parse_device_from_manifest(device) |
| 238 | elif len(search) == 1: |
| 239 | location = search[0] |
| 240 | else: |
| 241 | print("Your device can't be found in device sources..") |
| 242 | location = parse_device_from_manifest(device) |
| 243 | return location |
| 244 | |
| 245 | |
| 246 | def parse_dependency_file(location): |
| 247 | dep_file = "omni.dependencies" |
| 248 | dep_location = '/'.join([location, dep_file]) |
| 249 | if not os.path.isfile(dep_location): |
| 250 | print("WARNING: %s file not found" % dep_location) |
| 251 | sys.exit() |
| 252 | try: |
| 253 | with open(dep_location, 'r') as f: |
| 254 | dependencies = json.loads(f.read()) |
| 255 | except ValueError: |
| 256 | raise Exception("ERROR: malformed dependency file") |
| 257 | return dependencies |
| 258 | |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 259 | |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 260 | # if there is any conflict with existing and new |
| 261 | # delete the roomservice.xml file and create new |
| 262 | def check_manifest_problems(dependencies): |
| 263 | for dependency in dependencies: |
| 264 | repository = dependency.get("repository") |
| 265 | target_path = dependency.get("target_path") |
| 266 | revision = dependency.get("revision", default_rev) |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 267 | |
| 268 | # check for existing projects |
| 269 | for project in iterate_manifests(): |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 270 | if project.get("revision") is not None \ |
| 271 | and project.get("path") is not None \ |
| 272 | and project.get("path") == target_path \ |
| 273 | and project.get("revision") != revision: |
| 274 | print("WARNING: detected conflict in revisions for repository ", |
| 275 | repository) |
| 276 | current_dependency = str(project.get(repository)) |
| 277 | file = ES.parse('/'.join([local_manifest_dir, |
| 278 | "roomservice.xml"])) |
| 279 | file_root = file.getroot() |
| 280 | for current_project in file_root.findall('project'): |
| 281 | new_dependency = str(current_project.find('revision')) |
| 282 | if new_dependency == current_dependency: |
| 283 | file_root.remove(current_project) |
| 284 | file.write('/'.join([local_manifest_dir, "roomservice.xml"])) |
| 285 | return |
| 286 | |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 287 | |
| 288 | def create_dependency_manifest(dependencies): |
| 289 | projects = [] |
| 290 | for dependency in dependencies: |
| 291 | repository = dependency.get("repository") |
| 292 | target_path = dependency.get("target_path") |
| 293 | revision = dependency.get("revision", default_rev) |
| 294 | remote = dependency.get("remote", default_rem) |
maxwen | 52025a8 | 2019-05-08 17:04:42 +0200 | [diff] [blame] | 295 | override = dependency.get("override", None) |
Marko Man | 718f4c2 | 2019-07-14 12:12:25 +0100 | [diff] [blame] | 296 | |
maxwen | 52025a8 | 2019-05-08 17:04:42 +0200 | [diff] [blame] | 297 | if override is not None: |
| 298 | #print("found override in ", repository) |
| 299 | project = create_remove_project(repository) |
| 300 | if project is not None: |
| 301 | manifest = append_to_manifest(project) |
| 302 | #print(ES.tostring(manifest).decode()) |
| 303 | write_to_manifest(manifest) |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 304 | |
| 305 | # not adding an organization should default to android_team |
| 306 | # only apply this to github |
| 307 | if remote == "github": |
| 308 | if "/" not in repository: |
| 309 | repository = '/'.join([android_team, repository]) |
| 310 | project = create_manifest_project(repository, |
| 311 | target_path, |
| 312 | remote=remote, |
| 313 | revision=revision) |
| 314 | if project is not None: |
| 315 | manifest = append_to_manifest(project) |
| 316 | write_to_manifest(manifest) |
| 317 | projects.append(target_path) |
| 318 | if len(projects) > 0: |
| 319 | os.system("repo sync -f --no-clone-bundle %s" % " ".join(projects)) |
| 320 | |
| 321 | |
| 322 | def create_common_dependencies_manifest(dependencies): |
| 323 | dep_file = "omni.dependencies" |
| 324 | common_list = [] |
| 325 | if dependencies is not None: |
| 326 | for dependency in dependencies: |
| 327 | try: |
| 328 | index = common_list.index(dependency['target_path']) |
| 329 | except ValueError: |
| 330 | index = None |
| 331 | if index is None: |
| 332 | common_list.append(dependency['target_path']) |
| 333 | dep_location = '/'.join([dependency['target_path'], dep_file]) |
| 334 | if not os.path.isfile(dep_location): |
| 335 | sys.exit() |
| 336 | else: |
| 337 | try: |
| 338 | with open(dep_location, 'r') as f: |
| 339 | common_deps = json.loads(f.read()) |
| 340 | except ValueError: |
| 341 | raise Exception("ERROR: malformed dependency file") |
| 342 | |
| 343 | if common_deps is not None: |
| 344 | print("Looking for dependencies on: ", |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 345 | dependency['target_path']) |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 346 | check_manifest_problems(common_deps) |
| 347 | create_dependency_manifest(common_deps) |
| 348 | create_common_dependencies_manifest(common_deps) |
| 349 | |
| 350 | |
| 351 | def fetch_dependencies(device): |
| 352 | location = parse_device_from_folder(device) |
| 353 | if location is None or not os.path.isdir(location): |
| 354 | raise Exception("ERROR: could not find your device " |
| 355 | "folder location, bailing out") |
| 356 | dependencies = parse_dependency_file(location) |
| 357 | check_manifest_problems(dependencies) |
| 358 | create_dependency_manifest(dependencies) |
| 359 | create_common_dependencies_manifest(dependencies) |
| 360 | fetch_device(device) |
| 361 | |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 362 | |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 363 | def check_device_exists(device): |
| 364 | location = parse_device_from_folder(device) |
| 365 | if location is None: |
| 366 | return False |
| 367 | return os.path.isdir(location) |
| 368 | |
| 369 | |
| 370 | def fetch_device(device): |
| 371 | if check_device_exists(device): |
Marko Man | 718f4c2 | 2019-07-14 12:12:25 +0100 | [diff] [blame] | 372 | print("WARNING: Trying to fetch a device that's already there") |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 373 | git_data = search_gerrit_for_device(device) |
| 374 | if git_data is not None: |
| 375 | device_url = git_data['id'] |
| 376 | device_dir = parse_device_directory(device_url, device) |
| 377 | project = create_manifest_project(device_url, |
Felix Elsner | c1c2d75 | 2018-11-25 12:46:13 +0100 | [diff] [blame] | 378 | device_dir, |
| 379 | remote=default_team_rem) |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 380 | if project is not None: |
| 381 | manifest = append_to_manifest(project) |
| 382 | write_to_manifest(manifest) |
| 383 | # In case a project was written to manifest, but never synced |
| 384 | if project is not None or not check_target_exists(device_dir): |
| 385 | print("syncing the device config") |
| 386 | os.system('repo sync -f --no-clone-bundle %s' % device_dir) |
| 387 | |
| 388 | |
| 389 | if __name__ == '__main__': |
| 390 | if not os.path.isdir(local_manifest_dir): |
| 391 | os.mkdir(local_manifest_dir) |
| 392 | |
| 393 | product = sys.argv[1] |
| 394 | try: |
| 395 | device = product[product.index("_") + 1:] |
| 396 | except ValueError: |
| 397 | device = product |
| 398 | |
| 399 | if len(sys.argv) > 2: |
| 400 | deps_only = sys.argv[2] |
| 401 | else: |
| 402 | deps_only = False |
| 403 | |
| 404 | if not deps_only: |
| 405 | fetch_device(device) |
| 406 | fetch_dependencies(device) |