• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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