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