android_vendor_aospa/build/tools/roomservice.py

247 lines
10 KiB
Python
Executable File

#!/usr/bin/env python3
# roomservice: Android device repository management utility.
# Copyright (C) 2013 Cybojenix <anthonydking@gmail.com>
# Copyright (C) 2013 The OmniROM Project
# Copyright (C) 2015-2019 ParanoidAndroid Project
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import json
import os
import sys
from xml.etree import ElementTree as ET
extra_manifests_dir = '.repo/manifests/'
upstream_manifest_path = '.repo/manifest.xml'
local_manifests_dir = '.repo/local_manifests'
roomservice_manifest_path = local_manifests_dir + '/roomservice.xml'
dependencies_json_path = 'vendor/aospa/products/%s/aospa.dependencies'
# Indenting code from https://stackoverflow.com/a/4590052
def indent(elem, level=0):
i = "\n" + level * " "
if len(elem):
if not elem.text or not elem.text.strip():
elem.text = i + " "
if not elem.tail or not elem.tail.strip():
elem.tail = i
for elem in elem:
indent(elem, level + 1)
if not elem.tail or not elem.tail.strip():
elem.tail = i
else:
if level and (not elem.tail or not elem.tail.strip()):
elem.tail = i
def recurse_include(manifest):
includes = manifest.findall('include')
if includes is not None:
for file in includes:
extra_manifest = ET.parse(extra_manifests_dir + file.get('name')).getroot()
for elem in extra_manifest:
manifest.append(elem)
for elem in recurse_include(extra_manifest):
manifest.append(elem)
return manifest
if __name__ == '__main__':
if not os.path.isdir(local_manifests_dir):
os.mkdir(local_manifests_dir)
if len(sys.argv) <= 1:
raise ValueError('The first argument must be the product.')
product = sys.argv[1]
try:
device = product[product.index('_') + 1:]
except ValueError:
device = product
dependencies_json_path %= device
if not os.path.isfile(dependencies_json_path):
raise ValueError('No dependencies file could be found for the device (%s).' % device)
dependencies = json.loads(open(dependencies_json_path, 'r').read())
try:
upstream_manifest = ET.parse(upstream_manifest_path).getroot()
except (IOError, ET.ParseError):
upstream_manifest = ET.Element('manifest')
recurse_include(upstream_manifest)
try:
roomservice_manifest = ET.parse(roomservice_manifest_path).getroot()
except (IOError, ET.ParseError):
roomservice_manifest = ET.Element('manifest')
syncable_projects = []
mentioned_projects = []
# Clean up all the <remove-project> elements.
for removable_project in roomservice_manifest.findall('remove-project'):
name = removable_project.get('name')
path = None
for project in upstream_manifest.findall('project'):
if project.get('name') == name:
path = project.get('path')
break
if path is None:
# The upstream manifest doesn't know this project, so drop it.
roomservice_manifest.remove(removable_project)
continue
found_in_dependencies = False
for dependency in dependencies:
if dependency.get('target_path') == path:
found_in_dependencies = True
break
if not found_in_dependencies:
# We don't need special dependencies for this project, so drop it and sync it up.
roomservice_manifest.remove(removable_project)
syncable_projects.append(path)
for project in roomservice_manifest.findall('project'):
if project.get('path') == path:
roomservice_manifest.remove(project)
break
# Make sure our <project> elements are set.
for dependency in dependencies:
path = dependency.get('target_path')
name = dependency.get('repository')
remote = dependency.get('remote')
revision = dependency.get('revision')
clone_depth = dependency.get('clone-depth')
# Store path of every repositories mentioned in dependencies.
mentioned_projects.append(path)
# Make sure the required remote exists in the upstream manifest.
found_remote = False
for known_remote in upstream_manifest.findall('remote'):
if known_remote.get('name') == remote:
found_remote = True
break
if not found_remote:
raise ValueError('No remote declaration could be found for the %s project. (%s)' % (name, remote))
modified_project = False
found_in_roomservice = False
# In case the project was already added, update it.
for project in roomservice_manifest.findall('project'):
if project.get('name') == name or project.get('path') == path:
if found_in_roomservice:
roomservice_manifest.remove(project)
else:
found_in_roomservice = True
msg = ''
if project.get('path') != path:
modified_project = True
project.set('path', path)
msg += f'--> Path : Updated {project.get("path")} to {path}\n'
if project.get('remote') != remote:
modified_project = True
project.set('remote', remote)
msg += f'--> Remote : Updated {project.get("remote")} to {remote}\n'
if project.get('revision') != revision:
modified_project = True
project.set('revision', revision)
msg += f'--> Revision : Updated {project.get("revision")} to {revision}\n'
if project.get('clone-depth') != clone_depth:
modified_project = True
project.set('clone-depth', clone_depth)
msg += f'--> Clone depth : Updated {project.get("clone-depth")} to {clone_depth}\n'
if project.get('name') != name:
modified_project = True
project.set('name', name)
msg += f'--> Repository : Updated {project.get("name")} to {name}\n'
if modified_project:
print(f'{name} changed:\n{msg}\n')
# In case the project was not already added, create it.
if not found_in_roomservice:
print('Adding dependency:')
print(f'--> Repository : {name}')
print(f'--> Path : {path}')
print(f'--> Revision : {revision}')
print(f'--> Remote : {remote}')
found_in_roomservice = True
modified_project = True
attributes = {
'path': path,
'name': name,
'remote': remote,
'revision': revision,
}
if clone_depth is not None:
attributes['clone-depth'] = clone_depth
print(f'--> Clone depth : {clone_depth}')
print('\n')
roomservice_manifest.append(
ET.Element('project', attrib=attributes)
)
# In case the project also exists in the main manifest, instruct Repo to ignore that one.
for project in upstream_manifest.findall('project'):
if project.get('path') == path:
upstream_name = project.get('name')
found_remove_element = False
for removable_project in roomservice_manifest.findall('remove-project'):
if removable_project.get('name') == upstream_name:
found_remove_element = True
break
for removable_project in upstream_manifest.findall('remove-project'):
if removable_project.get('name') == upstream_name:
found_remove_element = True
break
if not found_remove_element:
modified_project = True
roomservice_manifest.insert(0, ET.Element('remove-project', attrib = {
'name': upstream_name
}))
# In case anything has changed, set the project as syncable.
if modified_project:
syncable_projects.append(path)
# Output our manifest.
indent(roomservice_manifest)
open(roomservice_manifest_path, 'w').write('\n'.join([
'<?xml version="1.0" encoding="UTF-8"?>',
'<!-- You should probably let Roomservice deal with this unless you know what you are doing. -->',
ET.tostring(roomservice_manifest).decode()
]))
# If roomservice manifest is perfectly fine, check if there are missing repos to be resynced.
if len(syncable_projects) == 0:
for path in mentioned_projects:
if not os.path.exists(path):
print('Dependency to be resynced:')
print(f'--> Repository Path : {path}\n')
syncable_projects.append(path)
# Sync the project that have changed and should be synced.
if len(syncable_projects) > 0:
print('Syncing the dependencies.')
if os.system('repo sync --force-sync --quiet --no-clone-bundle --no-tags %s' % ' '.join(syncable_projects)) != 0:
raise ValueError('Got an unexpected exit status from the sync process.')