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 | 97ff586 | 2018-08-09 14:29:04 +0200 | [diff] [blame^] | 41 | default_rev = "android-9.0" |
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) |
| 55 | matches = filter(lambda x: re.match(re_match, x), git_data) |
| 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) |
| 72 | except urllib.request.HTTPError as e: |
| 73 | print("There was an issue connecting to gerrit." |
| 74 | " Please try again in a minute") |
| 75 | except urllib.request.URLError as e: |
| 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() |
| 109 | except IOError, ES.ParseError: |
| 110 | print("WARNING: error while parsing %s" % file) |
| 111 | else: |
| 112 | for project in man.findall("project"): |
| 113 | yield project |
| 114 | |
| 115 | |
| 116 | def check_project_exists(url, revision, path): |
| 117 | for project in iterate_manifests(): |
| 118 | if project.get("name") == url and project.get("revision") == revision and project.get("path") == path: |
| 119 | return True |
| 120 | return False |
| 121 | |
| 122 | |
| 123 | def check_target_exists(directory): |
| 124 | return os.path.isdir(directory) |
| 125 | |
| 126 | |
| 127 | # Use the indent function from http://stackoverflow.com/a/4590052 |
| 128 | def indent(elem, level=0): |
| 129 | i = ''.join(["\n", level*" "]) |
| 130 | if len(elem): |
| 131 | if not elem.text or not elem.text.strip(): |
| 132 | elem.text = ''.join([i, " "]) |
| 133 | if not elem.tail or not elem.tail.strip(): |
| 134 | elem.tail = i |
| 135 | for elem in elem: |
| 136 | indent(elem, level+1) |
| 137 | if not elem.tail or not elem.tail.strip(): |
| 138 | elem.tail = i |
| 139 | else: |
| 140 | if level and (not elem.tail or not elem.tail.strip()): |
| 141 | elem.tail = i |
| 142 | |
| 143 | |
| 144 | def create_manifest_project(url, directory, |
| 145 | remote=default_rem, |
| 146 | revision=default_rev): |
| 147 | project_exists = check_project_exists(url, revision, directory) |
| 148 | |
| 149 | if project_exists: |
| 150 | return None |
| 151 | |
| 152 | project = ES.Element("project", |
| 153 | attrib={ |
| 154 | "path": directory, |
| 155 | "name": url, |
| 156 | "remote": remote, |
| 157 | "revision": revision |
| 158 | }) |
| 159 | return project |
| 160 | |
| 161 | |
| 162 | def append_to_manifest(project): |
| 163 | try: |
| 164 | lm = ES.parse('/'.join([local_manifest_dir, "roomservice.xml"])) |
| 165 | lm = lm.getroot() |
| 166 | except IOError, ES.ParseError: |
| 167 | lm = ES.Element("manifest") |
| 168 | lm.append(project) |
| 169 | return lm |
| 170 | |
| 171 | |
| 172 | def write_to_manifest(manifest): |
| 173 | indent(manifest) |
| 174 | raw_xml = ES.tostring(manifest).decode() |
| 175 | raw_xml = ''.join(['<?xml version="1.0" encoding="UTF-8"?>\n' |
| 176 | '<!--Please do not manually edit this file-->\n', |
| 177 | raw_xml]) |
| 178 | |
| 179 | with open('/'.join([local_manifest_dir, "roomservice.xml"]), 'w') as f: |
| 180 | f.write(raw_xml) |
| 181 | print("wrote the new roomservice manifest") |
| 182 | |
| 183 | |
| 184 | def parse_device_from_manifest(device): |
| 185 | for project in iterate_manifests(): |
| 186 | name = project.get('name') |
| 187 | if name.startswith("android_device_") and name.endswith(device): |
| 188 | return project.get('path') |
| 189 | return None |
| 190 | |
| 191 | |
| 192 | def parse_device_from_folder(device): |
| 193 | search = [] |
maxwen | c8a6b6c | 2017-12-05 01:37:48 +0100 | [diff] [blame] | 194 | if not os.path.isdir("device"): |
| 195 | os.mkdir("device") |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 196 | for sub_folder in os.listdir("device"): |
| 197 | if os.path.isdir("device/%s/%s" % (sub_folder, device)): |
| 198 | search.append("device/%s/%s" % (sub_folder, device)) |
| 199 | if len(search) > 1: |
| 200 | print("multiple devices under the name %s. " |
| 201 | "defaulting to checking the manifest" % device) |
| 202 | location = parse_device_from_manifest(device) |
| 203 | elif len(search) == 1: |
| 204 | location = search[0] |
| 205 | else: |
| 206 | print("Your device can't be found in device sources..") |
| 207 | location = parse_device_from_manifest(device) |
| 208 | return location |
| 209 | |
| 210 | |
| 211 | def parse_dependency_file(location): |
| 212 | dep_file = "omni.dependencies" |
| 213 | dep_location = '/'.join([location, dep_file]) |
| 214 | if not os.path.isfile(dep_location): |
| 215 | print("WARNING: %s file not found" % dep_location) |
| 216 | sys.exit() |
| 217 | try: |
| 218 | with open(dep_location, 'r') as f: |
| 219 | dependencies = json.loads(f.read()) |
| 220 | except ValueError: |
| 221 | raise Exception("ERROR: malformed dependency file") |
| 222 | return dependencies |
| 223 | |
| 224 | # if there is any conflict with existing and new |
| 225 | # delete the roomservice.xml file and create new |
| 226 | def check_manifest_problems(dependencies): |
| 227 | for dependency in dependencies: |
| 228 | repository = dependency.get("repository") |
| 229 | target_path = dependency.get("target_path") |
| 230 | revision = dependency.get("revision", default_rev) |
| 231 | remote = dependency.get("remote", default_rem) |
| 232 | |
| 233 | # check for existing projects |
| 234 | for project in iterate_manifests(): |
Humberto Borba | e1aa9ba | 2017-09-08 14:10:47 -0300 | [diff] [blame] | 235 | if project.get("revision") is not None and project.get("path") is not None: |
| 236 | if project.get("path") == target_path and project.get("revision") != revision: |
| 237 | print("WARNING: detected conflict in revisions for repository ", repository) |
| 238 | current_dependency = str(project.get(repository)) |
| 239 | file = ES.parse('/'.join([local_manifest_dir, "roomservice.xml"])) |
| 240 | file_root = file.getroot() |
| 241 | for current_project in file_root.findall('project'): |
| 242 | new_dependency = str(current_project.find('revision')) |
| 243 | if new_dependency == current_dependency: |
| 244 | file_root.remove(current_project) |
| 245 | file.write('/'.join([local_manifest_dir, "roomservice.xml"])) |
| 246 | return |
cybojenix | 3e87340 | 2013-10-17 03:34:57 +0400 | [diff] [blame] | 247 | |
| 248 | def create_dependency_manifest(dependencies): |
| 249 | projects = [] |
| 250 | for dependency in dependencies: |
| 251 | repository = dependency.get("repository") |
| 252 | target_path = dependency.get("target_path") |
| 253 | revision = dependency.get("revision", default_rev) |
| 254 | remote = dependency.get("remote", default_rem) |
| 255 | |
| 256 | # not adding an organization should default to android_team |
| 257 | # only apply this to github |
| 258 | if remote == "github": |
| 259 | if "/" not in repository: |
| 260 | repository = '/'.join([android_team, repository]) |
| 261 | project = create_manifest_project(repository, |
| 262 | target_path, |
| 263 | remote=remote, |
| 264 | revision=revision) |
| 265 | if project is not None: |
| 266 | manifest = append_to_manifest(project) |
| 267 | write_to_manifest(manifest) |
| 268 | projects.append(target_path) |
| 269 | if len(projects) > 0: |
| 270 | os.system("repo sync -f --no-clone-bundle %s" % " ".join(projects)) |
| 271 | |
| 272 | |
| 273 | def create_common_dependencies_manifest(dependencies): |
| 274 | dep_file = "omni.dependencies" |
| 275 | common_list = [] |
| 276 | if dependencies is not None: |
| 277 | for dependency in dependencies: |
| 278 | try: |
| 279 | index = common_list.index(dependency['target_path']) |
| 280 | except ValueError: |
| 281 | index = None |
| 282 | if index is None: |
| 283 | common_list.append(dependency['target_path']) |
| 284 | dep_location = '/'.join([dependency['target_path'], dep_file]) |
| 285 | if not os.path.isfile(dep_location): |
| 286 | sys.exit() |
| 287 | else: |
| 288 | try: |
| 289 | with open(dep_location, 'r') as f: |
| 290 | common_deps = json.loads(f.read()) |
| 291 | except ValueError: |
| 292 | raise Exception("ERROR: malformed dependency file") |
| 293 | |
| 294 | if common_deps is not None: |
| 295 | print("Looking for dependencies on: ", |
| 296 | dependency['target_path']) |
| 297 | check_manifest_problems(common_deps) |
| 298 | create_dependency_manifest(common_deps) |
| 299 | create_common_dependencies_manifest(common_deps) |
| 300 | |
| 301 | |
| 302 | def fetch_dependencies(device): |
| 303 | location = parse_device_from_folder(device) |
| 304 | if location is None or not os.path.isdir(location): |
| 305 | raise Exception("ERROR: could not find your device " |
| 306 | "folder location, bailing out") |
| 307 | dependencies = parse_dependency_file(location) |
| 308 | check_manifest_problems(dependencies) |
| 309 | create_dependency_manifest(dependencies) |
| 310 | create_common_dependencies_manifest(dependencies) |
| 311 | fetch_device(device) |
| 312 | |
| 313 | def check_device_exists(device): |
| 314 | location = parse_device_from_folder(device) |
| 315 | if location is None: |
| 316 | return False |
| 317 | return os.path.isdir(location) |
| 318 | |
| 319 | |
| 320 | def fetch_device(device): |
| 321 | if check_device_exists(device): |
| 322 | print("WARNING: Trying to fetch a device that's already there") |
| 323 | git_data = search_gerrit_for_device(device) |
| 324 | if git_data is not None: |
| 325 | device_url = git_data['id'] |
| 326 | device_dir = parse_device_directory(device_url, device) |
| 327 | project = create_manifest_project(device_url, |
| 328 | device_dir, |
| 329 | remote=default_team_rem) |
| 330 | if project is not None: |
| 331 | manifest = append_to_manifest(project) |
| 332 | write_to_manifest(manifest) |
| 333 | # In case a project was written to manifest, but never synced |
| 334 | if project is not None or not check_target_exists(device_dir): |
| 335 | print("syncing the device config") |
| 336 | os.system('repo sync -f --no-clone-bundle %s' % device_dir) |
| 337 | |
| 338 | |
| 339 | if __name__ == '__main__': |
| 340 | if not os.path.isdir(local_manifest_dir): |
| 341 | os.mkdir(local_manifest_dir) |
| 342 | |
| 343 | product = sys.argv[1] |
| 344 | try: |
| 345 | device = product[product.index("_") + 1:] |
| 346 | except ValueError: |
| 347 | device = product |
| 348 | |
| 349 | if len(sys.argv) > 2: |
| 350 | deps_only = sys.argv[2] |
| 351 | else: |
| 352 | deps_only = False |
| 353 | |
| 354 | if not deps_only: |
| 355 | fetch_device(device) |
| 356 | fetch_dependencies(device) |