summaryrefslogtreecommitdiff
path: root/status/boost_check_library.py
blob: e75563936a72fad59ed8cbf0e685260d09ab6c51 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
#!/usr/bin/env python

# Copyright Rene Rivera 2016
#
# Distributed under the Boost Software License, Version 1.0.
# (See accompanying file LICENSE_1_0.txt or copy at
# http://www.boost.org/LICENSE_1_0.txt)

import os
import inspect
import optparse
import sys
import glob
import fnmatch
import json

class check_library():
    '''
    This is a collection of checks for a library to test if a library
    follows the Boost C++ Libraries requirements and guidelines. It also
    checks for possible and likely errors in the library.
    '''
    
    def __init__(self):
        self.main()
    
    def check_organization(self):
        self.run_batch('check_organization_')
    
    def check_organization_build(self):
        if os.path.isdir(os.path.join(self.library_dir, 'build')):
            self.assert_file_exists(os.path.join(self.library_dir, 'build'), self.jamfile,
                '''
                Did not find a Boost Build file in the [project-root]/build directory.
                The library needs to provide a Boost Build project that the user,
                and the top level Boost project, can use to build the library if it
                has sources to build.
                ''',
                'org-build-ok')
        if os.path.isdir(os.path.join(self.library_dir, 'src')):
            self.assert_dir_exists(os.path.join(self.library_dir,'build'),
                '''
                Missing [project-root]/build directory. The [project-root]/build directory
                is required for libraries that have a [project-root]/src directory.
                ''',
                'org-build-src')
    
    def check_organization_doc(self):
        self.assert_file_exists(self.library_dir, ['index.html'],
            '''
            Did not find [project-root]/index.html file.
            
            The file is required for all libraries. Redirection to HTML documentation.
            ''',
            'org-doc-redir')
        self.assert_dir_exists(os.path.join(self.library_dir,'doc'),
            '''
            Missing [project-root]/doc directory. The [project-root]/doc directory
            is required for all libraries.
            
            Sources to build with and built documentation for the library. If the
            library needs to build documentation from non-HTML files this location
            must be buildable with Boost Build.
            ''',
            'org-doc-dir')
    
    def check_organization_include(self):
        if os.path.isdir(os.path.join(self.library_dir,'include','boost',self.library_name)):
            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['*.h*'],
                '''
                Found extra files in [project-root]/include/boost directory.
                ''',
                'org-inc-extra',
                negate = True,
                globs_to_exclude = ['%s.h*'%(self.library_name)])
        else:
            self.warn_file_exists(os.path.join(self.library_dir,'include','boost'), ['%s.h*'%(self.library_name)],
                '''
                Did not find [project-root]/include/boost/[library].h* file.
                
                A single header for the library is suggested at [project-root]/include/boost/[library].h*
                if the library does not have a header directory at [project-root]/include/boost/[library].
                ''',
                'org-inc-one')
    
    def check_organization_meta(self):
        parent_dir = os.path.dirname(self.library_dir)
        # If this is a sublibrary it's possible that the library information is the
        # parent library's meta/libraries.json. Otherwise it's a regular library
        # and structure.
        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
            if self.get_library_meta():
                return
            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
                '''
                Did not find [project-root]/meta/libraries.json file, nor did
                [super-project]/meta/libraries.json contain an entry for the sublibrary.
                
                The file is required for all libraries. And contains information about
                the library used to generate website and documentation for the
                Boost C++ Libraries collection.
                ''',
                'org-meta-libs')
        elif self.assert_dir_exists(os.path.join(self.library_dir,'meta'),
            '''
            Missing [project-root]/meta directory. The [project-root]/meta directory
            is required for all libraries.
            ''',
            'org-meta-dir'):
            self.assert_file_exists(os.path.join(self.library_dir, 'meta'), ['libraries.json'],
                '''
                Did not find [project-root]/meta/libraries.json file.
                
                The file is required for all libraries. And contains information about
                the library used to generate website and documentation for the
                Boost C++ Libraries collection.
                ''',
                'org-meta-libs')
    
    def check_organization_test(self):
        if self.assert_dir_exists(os.path.join(self.library_dir,'test'),
            '''
            Missing [project-root]/test directory. The [project-root]/test directory
            is required for all libraries.
            
            Regression or other test programs or scripts. This is the only location
            considered for automated testing. If you have additional locations that
            need to be part of automated testing it is required that this location
            refer to the additional test locations.
            ''',
            'org-test-dir'):
            self.assert_file_exists(os.path.join(self.library_dir, 'test'), self.jamfile,
                '''
                Did not find a Boost Build file in the [project-root]/test directory.
                ''',
                'org-test-ok')

    def main(self):
        commands = [];
        for method in inspect.getmembers(self, predicate=inspect.ismethod):
            if method[0].startswith('check_'):
                commands.append(method[0][6:].replace('_','-'))
        commands = "commands: %s" % ', '.join(commands)

        opt = optparse.OptionParser(
            usage="%prog [options] [commands]",
            description=commands)
        opt.add_option('--boost-root')
        opt.add_option('--library')
        opt.add_option('--jamfile')
        opt.add_option('--debug', action='store_true')
        self.boost_root = None
        self.library = None
        self.jamfile = None
        self.debug = False
        ( _opt_, self.actions ) = opt.parse_args(None,self)
        
        self.library_dir = os.path.join(self.boost_root, self.library)
        self.error_count = 0;
        self.jamfile = self.jamfile.split(';')
        self.library_name = self.library.split('/',1)[1] #os.path.basename(self.library)
        self.library_key = self.library.split('/',1)[1]

        if self.debug:
            print ">>> cwd: %s"%(os.getcwd())
            print ">>> actions: %s"%(self.actions)
            print ">>> boost_root: %s"%(self.boost_root)
            print ">>> library: %s"%(self.library)
            print ">>> jamfile: %s"%(self.jamfile)

        for action in self.actions:
            action_m = "check_"+action.replace('-','_')
            if hasattr(self,action_m):
                getattr(self,action_m)()
    
    def run_batch(self, action_base, *args, **kargs):
        for method in inspect.getmembers(self, predicate=inspect.ismethod):
            if method[0].startswith(action_base):
                getattr(self,method[0])(*args, **kargs)

    def get_library_meta(self):
        '''
        Fetches the meta data for the current library. The data could be in
        the superlib meta data file. If we can't find the data None is returned.
        '''
        parent_dir = os.path.dirname(self.library_dir)
        if self.test_file_exists(os.path.join(self.library_dir,'meta'),['libraries.json']):
            with open(os.path.join(self.library_dir,'meta','libraries.json'),'r') as f:
                meta_data = json.load(f)
                if isinstance(meta_data,list):
                    for lib in meta_data:
                        if lib['key'] == self.library_key:
                            return lib
                elif 'key' in meta_data and meta_data['key'] == self.library_key:
                    return meta_data
        if not self.test_dir_exists(os.path.join(self.library_dir,'meta')) \
            and self.test_file_exists(os.path.join(parent_dir,'meta'),['libraries.json']):
            with open(os.path.join(parent_dir,'meta','libraries.json'),'r') as f:
                libraries_json = json.load(f)
                if isinstance(libraries_json,list):
                    for lib in libraries_json:
                        if lib['key'] == self.library_key:
                            return lib
        return None

    def error(self, reason, message, key):
        self.error_count += 1
        print("%s: error: %s; %s <<%s>>"%(
            self.library,
            self.clean_message(reason),
            self.clean_message(message),
            key,
            ))
    
    def warn(self, reason, message, key):
        print("%s: warning: %s; %s <<%s>>"%(
            self.library,
            self.clean_message(reason),
            self.clean_message(message),
            key,
            ))
    
    def info(self, message):
        if self.debug:
            print("%s: info: %s"%(self.library, self.clean_message(message)))
    
    def clean_message(self, message):
        return " ".join(message.strip().split())
    
    def assert_dir_exists(self, dir, message, key, negate = False):
        self.info("check directory '%s', negate = %s"%(dir,negate))
        if os.path.isdir(dir):
            if negate:
                self.error("directory found", message, key)
                return False
        else:
            if not negate:
                self.error("directory not found", message, key)
                return False
        return True
    
    def warn_dir_exists(self, dir, message, key, negate = False):
        self.info("check directory '%s', negate = %s"%(dir,negate))
        if os.path.isdir(dir):
            if negate:
                self.warn("directory found", message, key)
                return False
        else:
            if not negate:
                self.warn("directory not found", message, key)
                return False
        return True
    
    def assert_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
        if negate:
            if found:
                self.error("file found", message, key)
                return False
        else:
            if not found:
                self.error("file not found", message, key)
                return False
        return True
    
    def warn_file_exists(self, dir, globs_to_include, message, key, negate = False, globs_to_exclude = []):
        found = self.test_file_exists(dir, globs_to_include = globs_to_include, globs_to_exclude = globs_to_exclude)
        if negate:
            if found:
                self.warn("file found", message, key)
                return False
        else:
            if not found:
                self.warn("file not found", message, key)
                return False
        return True
    
    def test_dir_exists(self, dir):
        return os.path.isdir(dir)
    
    def test_file_exists(self, dir, globs_to_include, globs_to_exclude = []):
        self.info("test file(s) in dir '%s', include = '%s', exclude = %s"%(dir,globs_to_include,globs_to_exclude))
        found = False
        if os.path.isdir(dir):
            for g in globs_to_include:
                for f in glob.iglob(os.path.join(dir,g)):
                    exclude = False
                    for ge in globs_to_exclude:
                        if fnmatch.fnmatch(os.path.basename(f),ge):
                            exclude = True
                    found = not exclude
                if found:
                    break
        return found

if check_library().error_count > 0:
    sys.exit(1)