Blame status/boost_check_library.py

Packit 58578d
#!/usr/bin/env python
Packit 58578d
Packit 58578d
# Copyright Rene Rivera 2016
Packit 58578d
#
Packit 58578d
# Distributed under the Boost Software License, Version 1.0.
Packit 58578d
# (See accompanying file LICENSE_1_0.txt or copy at
Packit 58578d
# http://www.boost.org/LICENSE_1_0.txt)
Packit 58578d
Packit 58578d
import os
Packit 58578d
import inspect
Packit 58578d
import optparse
Packit 58578d
import sys
Packit 58578d
import glob
Packit 58578d
import fnmatch
Packit 58578d
import json
Packit 58578d
Packit 58578d
class check_library():
Packit 58578d
    '''
Packit 58578d
    This is a collection of checks for a library to test if a library
Packit 58578d
    follows the Boost C++ Libraries requirements and guidelines. It also
Packit 58578d
    checks for possible and likely errors in the library.
Packit 58578d
    '''
Packit 58578d
    
Packit 58578d
    def __init__(self):
Packit 58578d
        self.main()
Packit 58578d
    
Packit 58578d
    def check_organization(self):
Packit 58578d
        self.run_batch('check_organization_')
Packit 58578d
    
Packit 58578d
    def check_organization_build(self):
Packit 58578d
        if os.path.isdir(os.path.join(self.library_dir, 'build')):
Packit 58578d
            self.assert_file_exists(os.path.join(self.library_dir, 'build'), self.jamfile,
Packit 58578d
                '''
Packit 58578d
                Did not find a Boost Build file in the [project-root]/build directory.
Packit 58578d
                The library needs to provide a Boost Build project that the user,
Packit 58578d
                and the top level Boost project, can use to build the library if it
Packit 58578d
                has sources to build.
Packit 58578d
                ''',
Packit 58578d
                'org-build-ok')
Packit 58578d
        if os.path.isdir(os.path.join(self.library_dir, 'src')):
Packit 58578d
            self.assert_dir_exists(os.path.join(self.library_dir,'build'),
Packit 58578d
                '''
Packit 58578d
                Missing [project-root]/build directory. The [project-root]/build directory
Packit 58578d
                is required for libraries that have a [project-root]/src directory.
Packit 58578d
                ''',
Packit 58578d
                'org-build-src')
Packit 58578d
    
Packit 58578d
    def check_organization_doc(self):
Packit 58578d
        self.assert_file_exists(self.library_dir, ['index.html'],
Packit 58578d
            '''
Packit 58578d
            Did not find [project-root]/index.html file.
Packit 58578d
            
Packit 58578d
            The file is required for all libraries. Redirection to HTML documentation.
Packit 58578d
            ''',
Packit 58578d
            'org-doc-redir')
Packit 58578d
        self.assert_dir_exists(os.path.join(self.library_dir,'doc'),
Packit 58578d
            '''
Packit 58578d
            Missing [project-root]/doc directory. The [project-root]/doc directory
Packit 58578d
            is required for all libraries.
Packit 58578d
            
Packit 58578d
            Sources to build with and built documentation for the library. If the
Packit 58578d
            library needs to build documentation from non-HTML files this location
Packit 58578d
            must be buildable with Boost Build.
Packit 58578d
            ''',
Packit 58578d
            'org-doc-dir')
Packit 58578d
    
Packit 58578d
    def check_organization_include(self):
Packit 58578d
        if os.path.isdir(os.path.join(self.library_dir,'include','boost',self.library_name)):
Packit 58578d
            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['*.h*'],
Packit 58578d
                '''
Packit 58578d
                Found extra files in [project-root]/include/boost directory.
Packit 58578d
                ''',
Packit 58578d
                'org-inc-extra',
Packit 58578d
                negate = True,
Packit 58578d
                globs_to_exclude = ['%s.h*'%(self.library_name)])
Packit 58578d
        else:
Packit 58578d
            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['%s.h*'%(self.library_name)],
Packit 58578d
                '''
Packit 58578d
                Did not find [project-root]/include/boost/[library].h* file.
Packit 58578d
                
Packit 58578d
                A single header for the library is suggested at [project-root]/include/boost/[library].h*
Packit 58578d
                if the library does not have a header directory at [project-root]/include/boost/[library].
Packit 58578d
                ''',
Packit 58578d
                'org-inc-one')
Packit 58578d
    
Packit 58578d
    def check_organization_meta(self):
Packit 58578d
        parent_dir = os.path.dirname(self.library_dir)
Packit 58578d
        # If this is a sublibrary it's possible that the library information is the
Packit 58578d
        # parent library's meta/libraries.json. Otherwise it's a regular library
Packit 58578d
        # and structure.
Packit 58578d
        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
Packit 58578d
            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
Packit 58578d
            if self.get_library_meta():
Packit 58578d
                return
Packit 58578d
            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
Packit 58578d
                '''
Packit 58578d
                Did not find [project-root]/meta/libraries.json file, nor did
Packit 58578d
                [super-project]/meta/libraries.json contain an entry for the sublibrary.
Packit 58578d
                
Packit 58578d
                The file is required for all libraries. And contains information about
Packit 58578d
                the library used to generate website and documentation for the
Packit 58578d
                Boost C++ Libraries collection.
Packit 58578d
                ''',
Packit 58578d
                'org-meta-libs')
Packit 58578d
        elif self.assert_dir_exists(os.path.join(self.library_dir,'meta'),
Packit 58578d
            '''
Packit 58578d
            Missing [project-root]/meta directory. The [project-root]/meta directory
Packit 58578d
            is required for all libraries.
Packit 58578d
            ''',
Packit 58578d
            'org-meta-dir'):
Packit 58578d
            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
Packit 58578d
                '''
Packit 58578d
                Did not find [project-root]/meta/libraries.json file.
Packit 58578d
                
Packit 58578d
                The file is required for all libraries. And contains information about
Packit 58578d
                the library used to generate website and documentation for the
Packit 58578d
                Boost C++ Libraries collection.
Packit 58578d
                ''',
Packit 58578d
                'org-meta-libs')
Packit 58578d
    
Packit 58578d
    def check_organization_test(self):
Packit 58578d
        if self.assert_dir_exists(os.path.join(self.library_dir,'test'),
Packit 58578d
            '''
Packit 58578d
            Missing [project-root]/test directory. The [project-root]/test directory
Packit 58578d
            is required for all libraries.
Packit 58578d
            
Packit 58578d
            Regression or other test programs or scripts. This is the only location
Packit 58578d
            considered for automated testing. If you have additional locations that
Packit 58578d
            need to be part of automated testing it is required that this location
Packit 58578d
            refer to the additional test locations.
Packit 58578d
            ''',
Packit 58578d
            'org-test-dir'):
Packit 58578d
            self.assert_file_exists(os.path.join(self.library_dir, 'test'), self.jamfile,
Packit 58578d
                '''
Packit 58578d
                Did not find a Boost Build file in the [project-root]/test directory.
Packit 58578d
                ''',
Packit 58578d
                'org-test-ok')
Packit 58578d
Packit 58578d
    def main(self):
Packit 58578d
        commands = [];
Packit 58578d
        for method in inspect.getmembers(self, predicate=inspect.ismethod):
Packit 58578d
            if method[0].startswith('check_'):
Packit 58578d
                commands.append(method[0][6:].replace('_','-'))
Packit 58578d
        commands = "commands: %s" % ', '.join(commands)
Packit 58578d
Packit 58578d
        opt = optparse.OptionParser(
Packit 58578d
            usage="%prog [options] [commands]",
Packit 58578d
            description=commands)
Packit 58578d
        opt.add_option('--boost-root')
Packit 58578d
        opt.add_option('--library')
Packit 58578d
        opt.add_option('--jamfile')
Packit 58578d
        opt.add_option('--debug', action='store_true')
Packit 58578d
        self.boost_root = None
Packit 58578d
        self.library = None
Packit 58578d
        self.jamfile = None
Packit 58578d
        self.debug = False
Packit 58578d
        ( _opt_, self.actions ) = opt.parse_args(None,self)
Packit 58578d
        
Packit 58578d
        self.library_dir = os.path.join(self.boost_root, self.library)
Packit 58578d
        self.error_count = 0;
Packit 58578d
        self.jamfile = self.jamfile.split(';')
Packit 58578d
        self.library_name = self.library.split('/',1)[1] #os.path.basename(self.library)
Packit 58578d
        self.library_key = self.library.split('/',1)[1]
Packit 58578d
Packit 58578d
        if self.debug:
Packit 58578d
            print ">>> cwd: %s"%(os.getcwd())
Packit 58578d
            print ">>> actions: %s"%(self.actions)
Packit 58578d
            print ">>> boost_root: %s"%(self.boost_root)
Packit 58578d
            print ">>> library: %s"%(self.library)
Packit 58578d
            print ">>> jamfile: %s"%(self.jamfile)
Packit 58578d
Packit 58578d
        for action in self.actions:
Packit 58578d
            action_m = "check_"+action.replace('-','_')
Packit 58578d
            if hasattr(self,action_m):
Packit 58578d
                getattr(self,action_m)()
Packit 58578d
    
Packit 58578d
    def run_batch(self, action_base, *args, **kargs):
Packit 58578d
        for method in inspect.getmembers(self, predicate=inspect.ismethod):
Packit 58578d
            if method[0].startswith(action_base):
Packit 58578d
                getattr(self,method[0])(*args, **kargs)
Packit 58578d
Packit 58578d
    def get_library_meta(self):
Packit 58578d
        '''
Packit 58578d
        Fetches the meta data for the current library. The data could be in
Packit 58578d
        the superlib meta data file. If we can't find the data None is returned.
Packit 58578d
        '''
Packit 58578d
        parent_dir = os.path.dirname(self.library_dir)
Packit 58578d
        if self.test_file_exists(os.path.join(self.library_dir,'meta'),['libraries.json']):
Packit 58578d
            with open(os.path.join(self.library_dir,'meta','libraries.json'),'r') as f:
Packit 58578d
                meta_data = json.load(f)
Packit 58578d
                if isinstance(meta_data,list):
Packit 58578d
                    for lib in meta_data:
Packit 58578d
                        if lib['key'] == self.library_key:
Packit 58578d
                            return lib
Packit 58578d
                elif 'key' in meta_data and meta_data['key'] == self.library_key:
Packit 58578d
                    return meta_data
Packit 58578d
        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
Packit 58578d
            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
Packit 58578d
            with open(os.path.join(parent_dir,'meta','libraries.json'),'r') as f:
Packit 58578d
                libraries_json = json.load(f)
Packit 58578d
                if isinstance(libraries_json,list):
Packit 58578d
                    for lib in libraries_json:
Packit 58578d
                        if lib['key'] == self.library_key:
Packit 58578d
                            return lib
Packit 58578d
        return None
Packit 58578d
Packit 58578d
    def error(self, reason, message, key):
Packit 58578d
        self.error_count += 1
Packit 58578d
        print("%s: error: %s; %s <<%s>>"%(
Packit 58578d
            self.library,
Packit 58578d
            self.clean_message(reason),
Packit 58578d
            self.clean_message(message),
Packit 58578d
            key,
Packit 58578d
            ))
Packit 58578d
    
Packit 58578d
    def warn(self, reason, message, key):
Packit 58578d
        print("%s: warning: %s; %s <<%s>>"%(
Packit 58578d
            self.library,
Packit 58578d
            self.clean_message(reason),
Packit 58578d
            self.clean_message(message),
Packit 58578d
            key,
Packit 58578d
            ))
Packit 58578d
    
Packit 58578d
    def info(self, message):
Packit 58578d
        if self.debug:
Packit 58578d
            print("%s: info: %s"%(self.library, self.clean_message(message)))
Packit 58578d
    
Packit 58578d
    def clean_message(self, message):
Packit 58578d
        return " ".join(message.strip().split())
Packit 58578d
    
Packit 58578d
    def assert_dir_exists(self, dir, message, key, negate = False):
Packit 58578d
        self.info("check directory '%s', negate = %s"%(dir,negate))
Packit 58578d
        if os.path.isdir(dir):
Packit 58578d
            if negate:
Packit 58578d
                self.error("directory found", message, key)
Packit 58578d
                return False
Packit 58578d
        else:
Packit 58578d
            if not negate:
Packit 58578d
                self.error("directory not found", message, key)
Packit 58578d
                return False
Packit 58578d
        return True
Packit 58578d
    
Packit 58578d
    def warn_dir_exists(self, dir, message, key, negate = False):
Packit 58578d
        self.info("check directory '%s', negate = %s"%(dir,negate))
Packit 58578d
        if os.path.isdir(dir):
Packit 58578d
            if negate:
Packit 58578d
                self.warn("directory found", message, key)
Packit 58578d
                return False
Packit 58578d
        else:
Packit 58578d
            if not negate:
Packit 58578d
                self.warn("directory not found", message, key)
Packit 58578d
                return False
Packit 58578d
        return True
Packit 58578d
    
Packit 58578d
    def assert_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
Packit 58578d
        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
Packit 58578d
        if negate:
Packit 58578d
            if found:
Packit 58578d
                self.error("file found", message, key)
Packit 58578d
                return False
Packit 58578d
        else:
Packit 58578d
            if not found:
Packit 58578d
                self.error("file not found", message, key)
Packit 58578d
                return False
Packit 58578d
        return True
Packit 58578d
    
Packit 58578d
    def warn_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
Packit 58578d
        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
Packit 58578d
        if negate:
Packit 58578d
            if found:
Packit 58578d
                self.warn("file found", message, key)
Packit 58578d
                return False
Packit 58578d
        else:
Packit 58578d
            if not found:
Packit 58578d
                self.warn("file not found", message, key)
Packit 58578d
                return False
Packit 58578d
        return True
Packit 58578d
    
Packit 58578d
    def test_dir_exists(self, dir):
Packit 58578d
        return os.path.isdir(dir)
Packit 58578d
    
Packit 58578d
    def test_file_exists(self, dir, globs_to_include, globs_to_exclude = []):
Packit 58578d
        self.info("test file(s) in dir '%s', include = '%s', exclude = %s"%(dir,globs_to_include,globs_to_exclude))
Packit 58578d
        found = False
Packit 58578d
        if os.path.isdir(dir):
Packit 58578d
            for g in globs_to_include:
Packit 58578d
                for f in glob.iglob(os.path.join(dir,g)):
Packit 58578d
                    exclude = False
Packit 58578d
                    for ge in globs_to_exclude:
Packit 58578d
                        if fnmatch.fnmatch(os.path.basename(f),ge):
Packit 58578d
                            exclude = True
Packit 58578d
                    found = not exclude
Packit 58578d
                if found:
Packit 58578d
                    break
Packit 58578d
        return found
Packit 58578d
Packit 58578d
if check_library().error_count > 0:
Packit 58578d
    sys.exit(1)
Packit 58578d