1#!/usr/bin/env python 2 3# depinst.py - installs the dependencies needed to test 4# a Boost library 5# 6# Copyright 2016-2020 Peter Dimov 7# 8# Distributed under the Boost Software License, Version 1.0. 9# See accompanying file LICENSE_1_0.txt or copy at 10# http://www.boost.org/LICENSE_1_0.txt 11 12from __future__ import print_function 13 14import re 15import sys 16import os 17import argparse 18 19verbose = 0 20 21def vprint( level, *args ): 22 23 if verbose >= level: 24 print( *args ) 25 26 27def is_module( m, gm ): 28 29 return ( 'libs/' + m ) in gm 30 31 32def module_for_header( h, x, gm ): 33 34 if h in x: 35 36 return x[ h ] 37 38 else: 39 40 # boost/function.hpp 41 m = re.match( 'boost/([^\\./]*)\\.h[a-z]*$', h ) 42 43 if m and is_module( m.group( 1 ), gm ): 44 45 return m.group( 1 ) 46 47 # boost/numeric/conversion.hpp 48 m = re.match( 'boost/([^/]*/[^\\./]*)\\.h[a-z]*$', h ) 49 50 if m and is_module( m.group( 1 ), gm ): 51 52 return m.group( 1 ) 53 54 # boost/numeric/conversion/header.hpp 55 m = re.match( 'boost/([^/]*/[^/]*)/', h ) 56 57 if m and is_module( m.group( 1 ), gm ): 58 59 return m.group( 1 ) 60 61 # boost/function/header.hpp 62 m = re.match( 'boost/([^/]*)/', h ) 63 64 if m and is_module( m.group( 1 ), gm ): 65 66 return m.group( 1 ) 67 68 vprint( 0, 'Cannot determine module for header', h ) 69 70 return None 71 72 73def scan_header_dependencies( f, x, gm, deps ): 74 75 for line in f: 76 77 m = re.match( '[ \t]*#[ \t]*include[ \t]*["<](boost/[^">]*)[">]', line ) 78 79 if m: 80 81 h = m.group( 1 ) 82 83 mod = module_for_header( h, x, gm ) 84 85 if mod: 86 87 if not mod in deps: 88 89 vprint( 1, 'Adding dependency', mod ) 90 deps[ mod ] = 0 91 92 93def scan_directory( d, x, gm, deps ): 94 95 vprint( 1, 'Scanning directory', d ) 96 97 if os.name == 'nt' and sys.version_info[0] < 3: 98 d = unicode( d ) 99 100 for root, dirs, files in os.walk( d ): 101 102 for file in files: 103 104 fn = os.path.join( root, file ) 105 106 vprint( 2, 'Scanning file', fn ) 107 108 if sys.version_info[0] < 3: 109 110 with open( fn, 'r' ) as f: 111 scan_header_dependencies( f, x, gm, deps ) 112 113 else: 114 115 with open( fn, 'r', encoding='latin-1' ) as f: 116 scan_header_dependencies( f, x, gm, deps ) 117 118 119def scan_module_dependencies( m, x, gm, deps, dirs ): 120 121 vprint( 1, 'Scanning module', m ) 122 123 for dir in dirs: 124 scan_directory( os.path.join( 'libs', m, dir ), x, gm, deps ) 125 126 127def read_exceptions(): 128 129 # exceptions.txt is the output of "boostdep --list-exceptions" 130 131 vprint( 1, 'Reading exceptions.txt' ) 132 133 x = {} 134 135 module = None 136 137 with open( os.path.join( os.path.dirname( sys.argv[0] ), 'exceptions.txt' ), 'r' ) as f: 138 139 for line in f: 140 141 line = line.rstrip() 142 143 m = re.match( '(.*):$', line ) 144 145 if m: 146 147 module = m.group( 1 ).replace( '~', '/' ) 148 149 else: 150 151 header = line.lstrip() 152 x[ header ] = module 153 154 return x 155 156 157def read_gitmodules(): 158 159 vprint( 1, 'Reading .gitmodules' ) 160 161 gm = [] 162 163 with open( '.gitmodules', 'r' ) as f: 164 165 for line in f: 166 167 line = line.strip() 168 169 m = re.match( 'path[ \t]*=[ \t]*(.*)$', line ) 170 171 if m: 172 173 gm.append( m.group( 1 ) ) 174 175 return gm 176 177def install_modules( modules, git_args ): 178 179 if len( modules ) == 0: 180 return 181 182 vprint( 0, 'Installing:', ', '.join(modules) ) 183 184 modules = [ 'libs/' + m for m in modules ] 185 186 command = 'git submodule' 187 188 if verbose <= 0: 189 command += ' -q' 190 191 command += ' update --init ' + git_args + ' ' + ' '.join( modules ) 192 193 vprint( 1, 'Executing:', command ) 194 r = os.system( command ); 195 196 if r != 0: 197 raise Exception( "The command '%s' failed with exit code %d" % (command, r) ) 198 199 200def install_module_dependencies( deps, x, gm, git_args ): 201 202 modules = [] 203 204 for m, i in deps.items(): 205 206 if not i: 207 208 modules += [ m ] 209 deps[ m ] = 1 # mark as installed 210 211 if len( modules ) == 0: 212 return 0 213 214 install_modules( modules, git_args ) 215 216 for m in modules: 217 scan_module_dependencies( m, x, gm, deps, [ 'include', 'src' ] ) 218 219 return len( modules ) 220 221 222if( __name__ == "__main__" ): 223 224 parser = argparse.ArgumentParser( description='Installs the dependencies needed to test a Boost library.' ) 225 226 parser.add_argument( '-v', '--verbose', help='enable verbose output', action='count', default=0 ) 227 parser.add_argument( '-q', '--quiet', help='quiet output (opposite of -v)', action='count', default=0 ) 228 parser.add_argument( '-X', '--exclude', help="exclude a default subdirectory ('include', 'src', or 'test') from scan; can be repeated", metavar='DIR', action='append', default=[] ) 229 parser.add_argument( '-N', '--ignore', help="exclude top-level dependency even when found in scan; can be repeated", metavar='LIB', action='append', default=[] ) 230 parser.add_argument( '-I', '--include', help="additional subdirectory to scan; can be repeated", metavar='DIR', action='append', default=[] ) 231 parser.add_argument( '-g', '--git_args', help="additional arguments to `git submodule update`", default='', action='store' ) 232 parser.add_argument( '-u', '--update', help='update <library> before scanning', action='store_true' ) 233 parser.add_argument( 'library', help="name of library to scan ('libs/' will be prepended)" ) 234 235 args = parser.parse_args() 236 237 verbose = args.verbose - args.quiet 238 239 vprint( 2, '-X:', args.exclude ) 240 vprint( 2, '-I:', args.include ) 241 vprint( 2, '-N:', args.ignore ) 242 243 x = read_exceptions() 244 vprint( 2, 'Exceptions:', x ) 245 246 gm = read_gitmodules() 247 vprint( 2, '.gitmodules:', gm ) 248 249 essentials = [ 'config', 'headers', '../tools/boost_install', '../tools/build', '../tools/cmake' ] 250 251 essentials = [ e for e in essentials if os.path.exists( 'libs/' + e ) ] 252 253 if args.update: 254 essentials.append( args.library ) 255 256 install_modules( essentials, args.git_args ) 257 258 m = args.library 259 260 deps = { m : 1 } 261 262 dirs = [ 'include', 'src', 'test' ] 263 264 for dir in args.exclude: 265 dirs.remove( dir ) 266 267 for dir in args.include: 268 dirs.append( dir ) 269 270 vprint( 1, 'Directories to scan:', *dirs ) 271 272 scan_module_dependencies( m, x, gm, deps, dirs ) 273 274 for dep in args.ignore: 275 if dep in deps: 276 vprint( 1, 'Ignoring dependency', dep ) 277 del deps[dep] 278 279 vprint( 2, 'Dependencies:', deps ) 280 281 while install_module_dependencies( deps, x, gm, args.git_args ): 282 pass 283