diff --git a/delvewheel/__main__.py b/delvewheel/__main__.py index aaa57db..d51bd13 100644 --- a/delvewheel/__main__.py +++ b/delvewheel/__main__.py @@ -42,16 +42,16 @@ def main(): parser_needed = subparsers.add_parser('needed', help=parser_needed_description, description=parser_needed_description) for subparser in (parser_show, parser_repair): subparser.add_argument('wheel', nargs='+', help='wheel(s) to show or repair') - subparser.add_argument('--add-path', default='', metavar='PATHS', help=f'additional path(s) to search for DLLs, {os.pathsep!r}-delimited') - subparser.add_argument('--include', '--add-dll', default='', metavar='DLLS', type=_dll_names, help=f'force inclusion of DLL name(s), {os.pathsep!r}-delimited') - subparser.add_argument('--exclude', '--no-dll', default='', metavar='DLLS', type=_dll_names, help=f'force exclusion of DLL name(s), {os.pathsep!r}-delimited') + subparser.add_argument('--add-path', action='append', default=[], metavar='PATHS', help=f'additional path(s) to search for DLLs, {os.pathsep!r}-delimited') + subparser.add_argument('--include', '--add-dll', action='append', default=[], metavar='DLLS', type=_dll_names, help=f'force inclusion of DLL name(s), {os.pathsep!r}-delimited') + subparser.add_argument('--exclude', '--no-dll', action='append', default=[], metavar='DLLS', type=_dll_names, help=f'force exclusion of DLL name(s), {os.pathsep!r}-delimited') subparser.add_argument('--ignore-existing', '--ignore-in-wheel', action='store_true', help="don't search for or vendor in DLLs that are already in the wheel") subparser.add_argument('--analyze-existing', action='store_true', help='analyze and vendor in dependencies of DLLs that are already in the wheel') subparser.add_argument('-v', action='count', default=0, help='verbosity') subparser.add_argument('--extract-dir', help=argparse.SUPPRESS) subparser.add_argument('--test', default='', help=argparse.SUPPRESS) # comma-separated testing options, internal use only parser_repair.add_argument('-w', '--wheel-dir', dest='target', default='wheelhouse', help='directory to write repaired wheel') - parser_repair.add_argument('--no-mangle', default='', metavar='DLLS', type=_dll_names, help=f'DLL names(s) not to mangle, {os.pathsep!r}-delimited') + parser_repair.add_argument('--no-mangle', action='append', default=[], metavar='DLLS', type=_dll_names, help=f'DLL names(s) not to mangle, {os.pathsep!r}-delimited') parser_repair.add_argument('--no-mangle-all', action='store_true', help="don't mangle any DLL names") parser_repair.add_argument('--strip', action='store_true', help='strip DLLs that contain trailing data when name-mangling') parser_repair.add_argument('-L', '--lib-sdir', default='.libs', type=_subdir_suffix, help='directory suffix in package to store vendored DLLs (default .libs)') @@ -65,9 +65,9 @@ def main(): # handle command if args.command in ('show', 'repair'): - add_paths = dict.fromkeys(os.path.abspath(path) for path in args.add_path.split(os.pathsep) if path) - include = set(dll_name.lower() for dll_name in args.include.split(os.pathsep) if dll_name) - exclude = set(dll_name.lower() for dll_name in args.exclude.split(os.pathsep) if dll_name) + add_paths = dict.fromkeys(os.path.abspath(path) for path in os.pathsep.join(args.add_path).split(os.pathsep) if path) + include = set(dll_name.lower() for dll_name in os.pathsep.join(args.include).split(os.pathsep) if dll_name) + exclude = set(dll_name.lower() for dll_name in os.pathsep.join(args.exclude).split(os.pathsep) if dll_name) intersection = include & exclude if intersection: @@ -81,7 +81,7 @@ def main(): if args.command == 'show': wr.show() else: # args.command == 'repair' - no_mangles = set(dll_name.lower() for dll_name in args.no_mangle.split(os.pathsep) if dll_name) + no_mangles = set(dll_name.lower() for dll_name in os.pathsep.join(args.no_mangle).split(os.pathsep) if dll_name) namespace_pkgs = set(tuple(namespace_pkg.split('.')) for namespace_pkg in args.namespace_pkg.split(os.pathsep) if namespace_pkg) wr.repair(args.target, no_mangles, args.no_mangle_all, args.strip, args.lib_sdir, not args.no_diagnostic and 'SOURCE_DATE_EPOCH' not in os.environ, namespace_pkgs, args.include_symbols, args.include_imports) else: # args.command == 'needed' diff --git a/tests/run_tests.py b/tests/run_tests.py index 3b120af..84759b2 100644 --- a/tests/run_tests.py +++ b/tests/run_tests.py @@ -258,6 +258,19 @@ def test_no_mangle_2(self): self.assertTrue(is_mangled(path.name), f'{path.name} is mangled') self.assertTrue(import_iknowpy_successful()) + def test_no_mangle_3(self): + """--no-mangle for 2 DLLs, flag specified twice""" + check_call(['delvewheel', 'repair', '--add-path', 'iknowpy', '--no-mangle', 'iKnowEngine.dll', '--no-mangle', 'iKnowBase.dll', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) + with zipfile.ZipFile('wheelhouse/iknowpy-1.5.3-cp312-cp312-win_amd64.whl') as wheel: + for path in zipfile.Path(wheel, 'iknowpy.libs/').iterdir(): + if path.name in ('.load-order-iknowpy-1.5.3',): + continue + if path.name.startswith('iKnowEngine') or path.name.startswith('iKnowBase'): + self.assertFalse(is_mangled(path.name), f'{path.name} is not mangled') + else: + self.assertTrue(is_mangled(path.name), f'{path.name} is mangled') + self.assertTrue(import_iknowpy_successful()) + def test_no_mangle_all(self): """--no-mangle for all DLLs""" check_call(['delvewheel', 'repair', '--add-path', 'iknowpy', '--no-mangle-all', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) @@ -278,6 +291,13 @@ def test_strip_1(self): check_call(['delvewheel', 'repair', '--add-path', 'iknowpy/trailing_data_1;iknowpy', '--strip', '--test', 'not_enough_padding', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) self.assertTrue(import_iknowpy_successful()) + def test_add_path_2(self): + """--add-path specified twice""" + with self.assertRaises(subprocess.CalledProcessError): + check_call(['delvewheel', 'repair', '--add-path', 'iknowpy/trailing_data_1', '--add-path', 'iknowpy', '--test', 'not_enough_padding', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) + check_call(['delvewheel', 'repair', '--add-path', 'iknowpy/trailing_data_1;iknowpy', '--strip', '--test', 'not_enough_padding', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) + self.assertTrue(import_iknowpy_successful()) + def test_strip_2(self): """--strip needed for 2 DLLs""" with self.assertRaises(subprocess.CalledProcessError): @@ -364,6 +384,27 @@ def test_include_2(self): self.assertTrue(kernelbase_found, 'kernelbase.dll found') self.assertTrue(import_iknowpy_successful()) + def test_include_3(self): + """--include for 2 DLLs, flag specified twice""" + check_call(['delvewheel', 'repair', '--add-path', 'iknowpy', '--include', 'kernel32.dll', '--include', 'kernelbase.dll', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl']) + kernel32_found = False + kernelbase_found = False + with zipfile.ZipFile('wheelhouse/iknowpy-1.5.3-cp312-cp312-win_amd64.whl') as wheel: + for path in zipfile.Path(wheel, 'iknowpy.libs/').iterdir(): + if path.name in ('.load-order-iknowpy-1.5.3',): + continue + if path.name.lower().startswith('kernel32'): + self.assertFalse(is_mangled(path.name), f'{path.name} is not mangled') + kernel32_found = True + elif path.name.lower().startswith('kernelbase'): + self.assertFalse(is_mangled(path.name), f'{path.name} is not mangled') + kernelbase_found = True + else: + self.assertTrue(is_mangled(path.name), f'{path.name} is mangled') + self.assertTrue(kernel32_found, 'kernel32.dll found') + self.assertTrue(kernelbase_found, 'kernelbase.dll found') + self.assertTrue(import_iknowpy_successful()) + def test_include_exclude_overlap(self): """overlap between --include and --exclude generates an error""" with self.assertRaises(subprocess.CalledProcessError): @@ -424,6 +465,14 @@ def test_exclude_all(self): finally: remove('wheelhouse/iknowpy-1.5.3-cp312-cp312-win_amd64.whl') + def test_exclude_all_2(self): + """--exclude that removes all DLLs, flag specified twice""" + try: + output = subprocess.check_output(['delvewheel', 'repair', '--add-path', 'iknowpy', '--exclude', 'iKnowEngine.dll', '--exclude', 'msvcp140.dll', '--no-mangle-all', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl'], text=True) + self.assertIn('no external dependencies are needed', output) + finally: + remove('wheelhouse/iknowpy-1.5.3-cp312-cp312-win_amd64.whl') + def test_ignore_existing_irrelevant(self): """--ignore-existing when no DLLs are in the wheel""" check_call(['delvewheel', 'repair', '--add-path', 'iknowpy', '--ignore-existing', '--no-mangle-all', 'iknowpy/iknowpy-1.5.3-cp312-cp312-win_amd64.whl'])