diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8dacec9..c9be314 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -391,7 +391,7 @@ jobs: - throw_exception - uuid - args: [ '' ] + args: [ '', '-C' ] runs-on: ubuntu-latest diff --git a/depinst/depinst.py b/depinst/depinst.py index 6dfba36..7f759aa 100644 --- a/depinst/depinst.py +++ b/depinst/depinst.py @@ -70,7 +70,7 @@ def module_for_header( h, x, gm ): return None -def scan_header_dependencies( f, x, gm, deps ): +def scan_header_dependencies( f, x, gm, deps, dep_path ): for line in f: @@ -89,8 +89,13 @@ def scan_header_dependencies( f, x, gm, deps ): vprint( 1, 'Adding dependency', mod ) deps[ mod ] = 0 + elif len(dep_path) > 1 and mod == dep_path[0]: -def scan_directory( d, x, gm, deps ): + dep_path = dep_path + [ mod ] + raise DependencyCycle( 'Dependency cycle detected: ' + ' -> '.join(dep_path) ) + + +def scan_directory( d, x, gm, deps, dep_path ): vprint( 1, 'Scanning directory', d ) @@ -108,20 +113,20 @@ def scan_directory( d, x, gm, deps ): if sys.version_info[0] < 3: with open( fn, 'r' ) as f: - scan_header_dependencies( f, x, gm, deps ) + scan_header_dependencies( f, x, gm, deps, dep_path ) else: with open( fn, 'r', encoding='latin-1' ) as f: - scan_header_dependencies( f, x, gm, deps ) + scan_header_dependencies( f, x, gm, deps, dep_path ) -def scan_module_dependencies( m, x, gm, deps, dirs ): +def scan_module_dependencies( m, x, gm, deps, dirs, dep_path ): vprint( 1, 'Scanning module', m ) for dir in dirs: - scan_directory( os.path.join( 'libs', m, dir ), x, gm, deps ) + scan_directory( os.path.join( 'libs', m, dir ), x, gm, deps, dep_path ) def read_exceptions(): @@ -197,7 +202,7 @@ def install_modules( modules, git_args ): raise Exception( "The command '%s' failed with exit code %d" % (command, r) ) -def install_module_dependencies( deps, x, gm, git_args ): +def install_module_dependencies( deps, x, gm, git_args, dep_path ): modules = [] @@ -214,11 +219,20 @@ def install_module_dependencies( deps, x, gm, git_args ): install_modules( modules, git_args ) for m in modules: - scan_module_dependencies( m, x, gm, deps, [ 'include', 'src' ] ) + next_dep_path = dep_path + + if dep_path: + next_dep_path = dep_path + [m] + + scan_module_dependencies( m, x, gm, deps, [ 'include', 'src' ], next_dep_path ) return len( modules ) +class DependencyCycle(Exception): + pass + + if( __name__ == "__main__" ): parser = argparse.ArgumentParser( description='Installs the dependencies needed to test a Boost library.' ) @@ -230,6 +244,7 @@ def install_module_dependencies( deps, x, gm, git_args ): parser.add_argument( '-I', '--include', help="additional subdirectory to scan; can be repeated", metavar='DIR', action='append', default=[] ) parser.add_argument( '-g', '--git_args', help="additional arguments to `git submodule update`", default='', action='store' ) parser.add_argument( '-u', '--update', help='update before scanning', action='store_true' ) + parser.add_argument( '-C', '--reject-cycles', help='abort if has cyclical dependencies', action='store_true' ) parser.add_argument( 'library', help="name of library to scan ('libs/' will be prepended)" ) args = parser.parse_args() @@ -239,6 +254,7 @@ def install_module_dependencies( deps, x, gm, git_args ): vprint( 2, '-X:', args.exclude ) vprint( 2, '-I:', args.include ) vprint( 2, '-N:', args.ignore ) + vprint( 2, '-C:', args.reject_cycles ) x = read_exceptions() vprint( 2, 'Exceptions:', x ) @@ -257,26 +273,57 @@ def install_module_dependencies( deps, x, gm, git_args ): m = args.library - deps = { m : 1 } - - dirs = [ 'include', 'src', 'test' ] + main_dirs = [ 'include', 'src' ] + extra_dirs = [ 'test' ] for dir in args.exclude: - dirs.remove( dir ) + if dir in main_dirs: + main_dirs.remove( dir ) + + if dir in extra_dirs: + extra_dirs.remove( dir ) for dir in args.include: - dirs.append( dir ) + extra_dirs.append( dir ) - vprint( 1, 'Directories to scan:', *dirs ) + vprint( 1, 'Directories to scan:', *(main_dirs + extra_dirs) ) - scan_module_dependencies( m, x, gm, deps, dirs ) + dep_path = [] + if args.reject_cycles: + dep_path.append( m ) + + main_deps = { m : 1 } + scan_module_dependencies( m, x, gm, main_deps, main_dirs, dep_path ) + + extra_deps = { m : 1 } + scan_module_dependencies( m, x, gm, extra_deps, extra_dirs, [] ) for dep in args.ignore: - if dep in deps: + reported = False + + if dep in main_deps: vprint( 1, 'Ignoring dependency', dep ) - del deps[dep] + reported = True + del main_deps[dep] + + if dep in extra_deps: + if not reported: + vprint( 1, 'Ignoring dependency', dep ) + reported = True + del extra_deps[dep] + + all_deps = dict( **main_deps ) + all_deps.update( extra_deps ) + vprint( 2, 'Dependencies:', all_deps ) + + try: + + while install_module_dependencies( main_deps, x, gm, args.git_args, dep_path ): + pass - vprint( 2, 'Dependencies:', deps ) + while install_module_dependencies( extra_deps, x, gm, args.git_args, [] ): + pass - while install_module_dependencies( deps, x, gm, args.git_args ): - pass + except DependencyCycle as e: + print( e, file=sys.stderr ) + sys.exit(1)