Home | History | Annotate | Download | only in snapshot
      1 #!/usr/bin/env python
      2 #
      3 # Copyright (C) 2017 The Android Open Source Project
      4 #
      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 #
     17 
     18 import argparse
     19 import glob
     20 import os
     21 import shutil
     22 import subprocess
     23 import tempfile
     24 import xml.etree.ElementTree as xml_tree
     25 
     26 import utils
     27 
     28 
     29 class GPLChecker(object):
     30     """Checks that all GPL projects in a VNDK snapshot have released sources.
     31 
     32     Makes sure that the current source tree have the sources for all GPL
     33     prebuilt libraries in a specified VNDK snapshot version.
     34     """
     35     MANIFEST_XML = utils.MANIFEST_FILE_NAME
     36     MODULE_PATHS_TXT = utils.MODULE_PATHS_FILE_NAME
     37 
     38     def __init__(self, install_dir, android_build_top, temp_artifact_dir):
     39         """GPLChecker constructor.
     40 
     41         Args:
     42           install_dir: string, absolute path to the prebuilts/vndk/v{version}
     43             directory where the build files will be generated.
     44           android_build_top: string, absolute path to ANDROID_BUILD_TOP
     45         """
     46         self._android_build_top = android_build_top
     47         self._install_dir = install_dir
     48         self._manifest_file = os.path.join(temp_artifact_dir,
     49                                            self.MANIFEST_XML)
     50         self._notice_files_dir = os.path.join(install_dir,
     51                                               utils.NOTICE_FILES_DIR_PATH)
     52 
     53         if not os.path.isfile(self._manifest_file):
     54             raise RuntimeError(
     55                 '{manifest} not found at {manifest_file}'.format(
     56                     manifest=self.MANIFEST_XML,
     57                     manifest_file=self._manifest_file))
     58 
     59     def _parse_module_paths(self):
     60         """Parses the module_path.txt files into a dictionary,
     61 
     62         Returns:
     63           module_paths: dict, e.g. {libfoo.so: some/path/here}
     64         """
     65         module_paths = dict()
     66         for file in utils.find(self._install_dir, [self.MODULE_PATHS_TXT]):
     67             file_path = os.path.join(self._install_dir, file)
     68             with open(file_path, 'r') as f:
     69                 for line in f.read().strip().split('\n'):
     70                     paths = line.split(' ')
     71                     if len(paths) > 1:
     72                         if paths[0] not in module_paths:
     73                             module_paths[paths[0]] = paths[1]
     74         return module_paths
     75 
     76     def _parse_manifest(self):
     77         """Parses manifest.xml file and returns list of 'project' tags."""
     78 
     79         root = xml_tree.parse(self._manifest_file).getroot()
     80         return root.findall('project')
     81 
     82     def _get_revision(self, module_path, manifest_projects):
     83         """Returns revision value recorded in manifest.xml for given project.
     84 
     85         Args:
     86           module_path: string, project path relative to ANDROID_BUILD_TOP
     87           manifest_projects: list of xml_tree.Element, list of 'project' tags
     88         """
     89         revision = None
     90         for project in manifest_projects:
     91             path = project.get('path')
     92             if module_path.startswith(path):
     93                 revision = project.get('revision')
     94                 break
     95         return revision
     96 
     97     def _check_revision_exists(self, revision, git_project_path):
     98         """Checks whether a revision is found in a git project of current tree.
     99 
    100         Args:
    101           revision: string, revision value recorded in manifest.xml
    102           git_project_path: string, path relative to ANDROID_BUILD_TOP
    103         """
    104         path = utils.join_realpath(self._android_build_top, git_project_path)
    105         try:
    106             subprocess.check_call(
    107                 ['git', '-C', path, 'rev-list', 'HEAD..{}'.format(revision)])
    108             return True
    109         except subprocess.CalledProcessError:
    110             return False
    111 
    112     def check_gpl_projects(self):
    113         """Checks that all GPL projects have released sources.
    114 
    115         Raises:
    116           ValueError: There are GPL projects with unreleased sources.
    117         """
    118         print 'Starting license check for GPL projects...'
    119 
    120         notice_files = glob.glob('{}/*'.format(self._notice_files_dir))
    121         if len(notice_files) == 0:
    122             raise RuntimeError('No license files found in {}'.format(
    123                 self._notice_files_dir))
    124 
    125         gpl_projects = []
    126         pattern = 'GENERAL PUBLIC LICENSE'
    127         for notice_file_path in notice_files:
    128             with open(notice_file_path, 'r') as notice_file:
    129                 if pattern in notice_file.read():
    130                     lib_name = os.path.splitext(
    131                         os.path.basename(notice_file_path))[0]
    132                     gpl_projects.append(lib_name)
    133 
    134         if not gpl_projects:
    135             print 'No GPL projects found.'
    136             return
    137 
    138         print 'GPL projects found:', ', '.join(gpl_projects)
    139 
    140         module_paths = self._parse_module_paths()
    141         manifest_projects = self._parse_manifest()
    142         released_projects = []
    143         unreleased_projects = []
    144 
    145         for lib in gpl_projects:
    146             if lib in module_paths:
    147                 module_path = module_paths[lib]
    148                 revision = self._get_revision(module_path, manifest_projects)
    149                 if not revision:
    150                     raise RuntimeError(
    151                         'No project found for {path} in {manifest}'.format(
    152                             path=module_path, manifest=self.MANIFEST_XML))
    153                 revision_exists = self._check_revision_exists(
    154                     revision, module_path)
    155                 if not revision_exists:
    156                     unreleased_projects.append((lib, module_path))
    157                 else:
    158                     released_projects.append((lib, module_path))
    159             else:
    160                 raise RuntimeError(
    161                     'No module path was found for {lib} in {module_paths}'.
    162                     format(lib=lib, module_paths=self.MODULE_PATHS_TXT))
    163 
    164         if released_projects:
    165             print 'Released GPL projects:', released_projects
    166 
    167         if unreleased_projects:
    168             raise ValueError(
    169                 ('FAIL: The following GPL projects have NOT been released in '
    170                  'current tree: {}'.format(unreleased_projects)))
    171 
    172         print 'PASS: All GPL projects have source in current tree.'
    173 
    174 
    175 def get_args():
    176     parser = argparse.ArgumentParser()
    177     parser.add_argument(
    178         'vndk_version', type=int,
    179         help='VNDK snapshot version to check, e.g. "27".')
    180     parser.add_argument('-b', '--branch', help='Branch to pull manifest from.')
    181     parser.add_argument('--build', help='Build number to pull manifest from.')
    182     return parser.parse_args()
    183 
    184 
    185 def main():
    186     """For local testing purposes.
    187 
    188     Note: VNDK snapshot must be already installed under
    189       prebuilts/vndk/v{version}.
    190     """
    191     ANDROID_BUILD_TOP = utils.get_android_build_top()
    192     PREBUILTS_VNDK_DIR = utils.join_realpath(ANDROID_BUILD_TOP,
    193                                              'prebuilts/vndk')
    194 
    195     args = get_args()
    196     vndk_version = args.vndk_version
    197     install_dir = os.path.join(PREBUILTS_VNDK_DIR, 'v{}'.format(vndk_version))
    198     if not os.path.isdir(install_dir):
    199         raise ValueError(
    200             'Please provide valid VNDK version. {} does not exist.'
    201             .format(install_dir))
    202 
    203     temp_artifact_dir = tempfile.mkdtemp()
    204     os.chdir(temp_artifact_dir)
    205     manifest_pattern = 'manifest_{}.xml'.format(args.build)
    206     print 'Fetching {file} from {branch} (bid: {build})'.format(
    207         file=manifest_pattern, branch=args.branch, build=args.build)
    208     manifest_dest = os.path.join(temp_artifact_dir, utils.MANIFEST_FILE_NAME)
    209     utils.fetch_artifact(args.branch, args.build, manifest_pattern,
    210                          manifest_dest)
    211 
    212     license_checker = GPLChecker(install_dir, ANDROID_BUILD_TOP,
    213                                  temp_artifact_dir)
    214     try:
    215         license_checker.check_gpl_projects()
    216     except ValueError as error:
    217         print error
    218         raise
    219     finally:
    220         print 'Deleting temp_artifact_dir: {}'.format(temp_artifact_dir)
    221         shutil.rmtree(temp_artifact_dir)
    222 
    223 
    224 if __name__ == '__main__':
    225     main()
    226