• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python
2
3# Copyright Rene Rivera 2016
4#
5# Distributed under the Boost Software License, Version 1.0.
6# (See accompanying file LICENSE_1_0.txt or copy at
7# http://www.boost.org/LICENSE_1_0.txt)
8
9import os
10import inspect
11import optparse
12import sys
13import glob
14import fnmatch
15import json
16
17class check_library():
18    '''
19    This is a collection of checks for a library to test if a library
20    follows the Boost C++ Libraries requirements and guidelines. It also
21    checks for possible and likely errors in the library.
22    '''
23
24    def __init__(self):
25        self.main()
26
27    def check_organization(self):
28        self.run_batch('check_organization_')
29
30    def check_organization_build(self):
31        if os.path.isdir(os.path.join(self.library_dir, 'build')):
32            self.assert_file_exists(os.path.join(self.library_dir, 'build'), self.jamfile,
33                '''
34                Did not find a Boost Build file in the [project-root]/build directory.
35                The library needs to provide a Boost Build project that the user,
36                and the top level Boost project, can use to build the library if it
37                has sources to build.
38                ''',
39                'org-build-ok')
40        if os.path.isdir(os.path.join(self.library_dir, 'src')):
41            self.assert_dir_exists(os.path.join(self.library_dir,'build'),
42                '''
43                Missing [project-root]/build directory. The [project-root]/build directory
44                is required for libraries that have a [project-root]/src directory.
45                ''',
46                'org-build-src')
47
48    def check_organization_doc(self):
49        self.assert_file_exists(self.library_dir, ['index.html'],
50            '''
51            Did not find [project-root]/index.html file.
52
53            The file is required for all libraries. Redirection to HTML documentation.
54            ''',
55            'org-doc-redir')
56        self.assert_dir_exists(os.path.join(self.library_dir,'doc'),
57            '''
58            Missing [project-root]/doc directory. The [project-root]/doc directory
59            is required for all libraries.
60
61            Sources to build with and built documentation for the library. If the
62            library needs to build documentation from non-HTML files this location
63            must be buildable with Boost Build.
64            ''',
65            'org-doc-dir')
66
67    def check_organization_include(self):
68        if os.path.isdir(os.path.join(self.library_dir,'include','boost',self.library_name)):
69            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['*.h*'],
70                '''
71                Found extra files in [project-root]/include/boost directory.
72                ''',
73                'org-inc-extra',
74                negate = True,
75                globs_to_exclude = ['%s.h*'%(self.library_name)])
76        else:
77            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['%s.h*'%(self.library_name)],
78                '''
79                Did not find [project-root]/include/boost/[library].h* file.
80
81                A single header for the library is suggested at [project-root]/include/boost/[library].h*
82                if the library does not have a header directory at [project-root]/include/boost/[library].
83                ''',
84                'org-inc-one')
85
86    def check_organization_meta(self):
87        parent_dir = os.path.dirname(self.library_dir)
88        # If this is a sublibrary it's possible that the library information is the
89        # parent library's meta/libraries.json. Otherwise it's a regular library
90        # and structure.
91        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
92            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
93            if self.get_library_meta():
94                return
95            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
96                '''
97                Did not find [project-root]/meta/libraries.json file, nor did
98                [super-project]/meta/libraries.json contain an entry for the sublibrary.
99
100                The file is required for all libraries. And contains information about
101                the library used to generate website and documentation for the
102                Boost C++ Libraries collection.
103                ''',
104                'org-meta-libs')
105        elif self.assert_dir_exists(os.path.join(self.library_dir,'meta'),
106            '''
107            Missing [project-root]/meta directory. The [project-root]/meta directory
108            is required for all libraries.
109            ''',
110            'org-meta-dir'):
111            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
112                '''
113                Did not find [project-root]/meta/libraries.json file.
114
115                The file is required for all libraries. And contains information about
116                the library used to generate website and documentation for the
117                Boost C++ Libraries collection.
118                ''',
119                'org-meta-libs')
120
121    def check_organization_test(self):
122        if self.assert_dir_exists(os.path.join(self.library_dir,'test'),
123            '''
124            Missing [project-root]/test directory. The [project-root]/test directory
125            is required for all libraries.
126
127            Regression or other test programs or scripts. This is the only location
128            considered for automated testing. If you have additional locations that
129            need to be part of automated testing it is required that this location
130            refer to the additional test locations.
131            ''',
132            'org-test-dir'):
133            self.assert_file_exists(os.path.join(self.library_dir, 'test'), self.jamfile,
134                '''
135                Did not find a Boost Build file in the [project-root]/test directory.
136                ''',
137                'org-test-ok')
138
139    def main(self):
140        commands = [];
141        for method in inspect.getmembers(self, predicate=inspect.ismethod):
142            if method[0].startswith('check_'):
143                commands.append(method[0][6:].replace('_','-'))
144        commands = "commands: %s" % ', '.join(commands)
145
146        opt = optparse.OptionParser(
147            usage="%prog [options] [commands]",
148            description=commands)
149        opt.add_option('--boost-root')
150        opt.add_option('--library')
151        opt.add_option('--jamfile')
152        opt.add_option('--debug', action='store_true')
153        self.boost_root = None
154        self.library = None
155        self.jamfile = None
156        self.debug = False
157        ( _opt_, self.actions ) = opt.parse_args(None,self)
158
159        self.library_dir = os.path.join(self.boost_root, self.library)
160        self.error_count = 0;
161        self.jamfile = self.jamfile.split(';')
162        self.library_name = self.library.split('/',1)[1] #os.path.basename(self.library)
163        self.library_key = self.library.split('/',1)[1]
164
165        if self.debug:
166            print(">>> cwd: %s"%(os.getcwd()))
167            print(">>> actions: %s"%(self.actions))
168            print(">>> boost_root: %s"%(self.boost_root))
169            print(">>> library: %s"%(self.library))
170            print(">>> jamfile: %s"%(self.jamfile))
171
172        for action in self.actions:
173            action_m = "check_"+action.replace('-','_')
174            if hasattr(self,action_m):
175                getattr(self,action_m)()
176
177    def run_batch(self, action_base, *args, **kargs):
178        for method in inspect.getmembers(self, predicate=inspect.ismethod):
179            if method[0].startswith(action_base):
180                getattr(self,method[0])(*args, **kargs)
181
182    def get_library_meta(self):
183        '''
184        Fetches the meta data for the current library. The data could be in
185        the superlib meta data file. If we can't find the data None is returned.
186        '''
187        parent_dir = os.path.dirname(self.library_dir)
188        if self.test_file_exists(os.path.join(self.library_dir,'meta'),['libraries.json']):
189            with open(os.path.join(self.library_dir,'meta','libraries.json'),'r') as f:
190                meta_data = json.load(f)
191                if isinstance(meta_data,list):
192                    for lib in meta_data:
193                        if lib['key'] == self.library_key:
194                            return lib
195                elif 'key' in meta_data and meta_data['key'] == self.library_key:
196                    return meta_data
197        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
198            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
199            with open(os.path.join(parent_dir,'meta','libraries.json'),'r') as f:
200                libraries_json = json.load(f)
201                if isinstance(libraries_json,list):
202                    for lib in libraries_json:
203                        if lib['key'] == self.library_key:
204                            return lib
205        return None
206
207    def error(self, reason, message, key):
208        self.error_count += 1
209        print("%s: error: %s; %s <<%s>>"%(
210            self.library,
211            self.clean_message(reason),
212            self.clean_message(message),
213            key,
214            ))
215
216    def warn(self, reason, message, key):
217        print("%s: warning: %s; %s <<%s>>"%(
218            self.library,
219            self.clean_message(reason),
220            self.clean_message(message),
221            key,
222            ))
223
224    def info(self, message):
225        if self.debug:
226            print("%s: info: %s"%(self.library, self.clean_message(message)))
227
228    def clean_message(self, message):
229        return " ".join(message.strip().split())
230
231    def assert_dir_exists(self, dir, message, key, negate = False):
232        self.info("check directory '%s', negate = %s"%(dir,negate))
233        if os.path.isdir(dir):
234            if negate:
235                self.error("directory found", message, key)
236                return False
237        else:
238            if not negate:
239                self.error("directory not found", message, key)
240                return False
241        return True
242
243    def warn_dir_exists(self, dir, message, key, negate = False):
244        self.info("check directory '%s', negate = %s"%(dir,negate))
245        if os.path.isdir(dir):
246            if negate:
247                self.warn("directory found", message, key)
248                return False
249        else:
250            if not negate:
251                self.warn("directory not found", message, key)
252                return False
253        return True
254
255    def assert_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
256        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
257        if negate:
258            if found:
259                self.error("file found", message, key)
260                return False
261        else:
262            if not found:
263                self.error("file not found", message, key)
264                return False
265        return True
266
267    def warn_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
268        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
269        if negate:
270            if found:
271                self.warn("file found", message, key)
272                return False
273        else:
274            if not found:
275                self.warn("file not found", message, key)
276                return False
277        return True
278
279    def test_dir_exists(self, dir):
280        return os.path.isdir(dir)
281
282    def test_file_exists(self, dir, globs_to_include, globs_to_exclude = []):
283        self.info("test file(s) in dir '%s', include = '%s', exclude = %s"%(dir,globs_to_include,globs_to_exclude))
284        found = False
285        if os.path.isdir(dir):
286            for g in globs_to_include:
287                for f in glob.iglob(os.path.join(dir,g)):
288                    exclude = False
289                    for ge in globs_to_exclude:
290                        if fnmatch.fnmatch(os.path.basename(f),ge):
291                            exclude = True
292                    found = not exclude
293                if found:
294                    break
295        return found
296
297if check_library().error_count > 0:
298    sys.exit(1)
299
300