def test_invoke_compiler_success(): "Test that warnings are propagated" warn_file = Path('tests/pkg1/warn.nim').resolve() out_file = Path('tests/pkg1/warn' + NimCompiler.EXT).resolve() try: # Since we are full-on invoking the compiler for a working build here, # we gotta make sure it has the same flags as normal (for win32, etc.) # This also means Nimpy has to be installed prior to building. NimCompiler.ensure_nimpy() out, err, war, hin = NimCompiler.invoke_compiler([ 'nim', 'c', *NimCompiler.NIM_CLI_ARGS, f'--out:{out_file}', str(warn_file) ]) assert out_file.exists(), err assert any("Warning: imported and not used: 'asyncstreams'" in i for i in war) assert any('Hint' in i for i in hin) finally: if out_file.exists(): out_file.unlink() if sys.platform == 'win32': warn_exp = Path('tests/pkg1/warn.exp') warn_lib = Path('tests/pkg1/warn.lib') if warn_exp.exists(): warn_exp.unlink() if warn_lib.exists(): warn_lib.unlink()
def test_custom_build_switches(): "Test to make sure custom build switches can be used" switch_file = Path('tests/lib2/switches.py') scope = dict( MODULE_PATH=Path('foo/bar/baz.nim'), BUILD_ARTIFACT=Path('foo/bar/__pycache__/baz.' + NimCompiler.EXT), BUILD_DIR=None, IS_LIBRARY=False ) switches = NimCompiler.get_switches(switch_file, **scope) old_import = switches['import'][:] old_bundle = switches['bundle'][:] assert switches assert old_import assert old_bundle # Make sure different platforms are handled correctly old_platform = sys.platform + '' sys.platform = 'darwin' if sys.platform == 'win32' else 'win32' try: switches = NimCompiler.get_switches(switch_file, **scope) assert switches assert old_import assert old_bundle assert old_import != switches['import'] assert old_bundle != switches['bundle'] finally: sys.platform = old_platform
def test_get_import_prefix(): "Make sure that the right namespace is returned for a given module path." with nimporter.cd('tests') as tmpdir: module_path1 = Path('pkg1/mod1.nim') module_path2 = Path('pkg1/pkg2/mod2.nim') gold1 = 'pkg1', 'mod1.nim' gold2 = 'pkg1', 'pkg2', 'mod2.nim' assert NimCompiler.get_import_prefix(module_path1, Path()) == gold1 assert NimCompiler.get_import_prefix(module_path2, Path()) == gold2
def test_build_library(): "Test that a given Nim module can produce a Python extension library." with nimporter.cd('tests'): module = Path('lib1') output = NimCompiler.build_artifact(module) artifact = NimCompiler.compile_nim_code(module, output, library=True) assert artifact.exists() assert artifact.parent == output.parent assert artifact.suffix == output.suffix
def main(args=None): parser = argparse.ArgumentParser(description='Nimporter CLI') subs = parser.add_subparsers(dest='cmd', required=True) subs.add_parser('clean') build = subs.add_parser('build') build.add_argument('source', type=pathlib.Path, help='the Nim module/library to compile') build.add_argument('--dest', type=pathlib.Path, help='the folder to store the build artifact') args = parser.parse_args(args or sys.argv[1:]) if args.cmd == 'clean': cwd = pathlib.Path() print('Cleaning Directory:', cwd.resolve()) clean(cwd) elif args.cmd == 'build': args.source = args.source.absolute() if not args.dest: args.dest = NimCompiler.build_artifact(args.source).parent else: assert args.dest.is_dir(), ( 'Cannot specify output filename since extensions change per ' 'platform. Please specify an output directory such as ".".') args.dest.mkdir(exist_ok=True) module = args.source if args.source.is_dir(): is_library = bool([*module.glob('*.nimble')]) assert is_library, 'Library dir must contain <libname>.nimble file' elif args.source.is_file(): is_library = bool([*module.parent.glob('*.nimble')]) if is_library: module = module.parent temp_build_dir = pathlib.Path('build').absolute() temp_build_dir.mkdir(exist_ok=True) artifact = temp_build_dir / (args.source.stem + NimCompiler.EXT) try: NimCompiler.compile_nim_code(module, artifact, library=is_library) shutil.copy(artifact, args.dest) module_name = args.source.stem + '.nim' Nimporter.update_hash(args.dest.parent / module_name) finally: shutil.rmtree(temp_build_dir)
def test_build_module(): "Test that a given Nim module can produce a Python extension module." with nimporter.cd('tests'): module = Path('mod_a.nim') output = NimCompiler.build_artifact(module) f = io.StringIO() with redirect_stdout(f): artifact = NimCompiler.compile_nim_code(module, output, library=False) assert artifact.exists() assert artifact.parent == output.parent assert 'Warning:' in f.getvalue()
def test_find_nim_std_lib(): "Make sure that Nim's standard library can be found." assert shutil.which('nim'), 'Nim compiler is not installed or not on path' assert NimCompiler.find_nim_std_lib(), ( "Can't find Nim stdlib even though it is installed") # Now test failure condition by making Nim disappear environ = os.environ.copy() try: os.environ['PATH'] = '' assert not shutil.which('nim') assert not NimCompiler.find_nim_std_lib() finally: os.environ.clear() os.environ.update(environ)
def test_build_artifact_location(): "Make sure that the expected location to the build artifact is returned." module_path = Path('tests/pkg1/mod1.nim').absolute() ext = NimCompiler.EXT expected_path = Path('tests/pkg1/__pycache__/mod1' + ext).absolute() assert NimCompiler.build_artifact(module_path).absolute() == expected_path
def test_build_library_fails(): "Test NimInvokeException" # Build library using Nim module try: NimCompiler.compile_nim_code(Path('tests/mod_b.nim'), None, library=True) assert False, 'Should throw exception.' except NimporterException: "Expected result" # Build a library that has an error try: module = Path('tests/lib4') output = NimCompiler.build_artifact(module) NimCompiler.compile_nim_code(module, output, library=True) assert False, 'Should throw exception.' except NimInvokeException as e: assert str(e) assert e.get_output() # Build a library that doesn't have a Nimble file try: NimCompiler.compile_nim_code(Path('tests/lib3'), None, library=True) assert False, 'Should throw exception.' except NimporterException: "Expected result"
def test_build_module_fails(): "Test NimCompileException" # Build nonexistent file try: fake = Path('nonesense.nim') NimCompiler.compile_nim_code(fake, None, library=True) assert False, "Should throw exception. File doesn't exist: " + str( fake) except NimporterException: "Expected result" # Build module using library path try: NimCompiler.compile_nim_code(Path('tests/lib1'), None, library=False) assert False, 'Should throw exception.' except NimporterException: "Expected result" # Build a module that has an error try: module = Path('tests/pkg1/error.nim') output = NimCompiler.build_artifact(module) NimCompiler.compile_nim_code(module, output, library=False) assert False, 'Should throw exception.' except NimCompileException as e: assert str(e)
def test_should_compile(): "Make sure that modules should be (re)compiled or not." filename = Path('tests/pkg4/mod4.nim') assert not Nimporter.is_hashed(filename) assert Nimporter.hash_changed(filename) assert not Nimporter.is_built(filename) assert not Nimporter.is_cache(filename) assert not NimCompiler.pycache_dir(filename).exists() assert not Nimporter.IGNORE_CACHE assert Nimporter.should_compile(filename)
def test_invoke_compiler(): "Make sure that you can invoke the compiler as well as any executable." # NOTE: When ran in PowerShell, echo is a Cmdlet, not an executable ... :| if not shutil.which('echo'): return # Test that any program can be called gold_out = 'Hello World!' + ('\r\n' if sys.platform == 'win32' else '\n') out, err, war, hin = NimCompiler.invoke_compiler(['echo', gold_out.strip()]) assert out == gold_out
def test_compile_switches(): "Make sure that an extension can still be compiled when using a switchfile." ext = NimCompiler.compile_nim_extension(Path('tests/lib2'), Path('tests'), library=True) assert isinstance(ext, Extension) assert ext.name == 'lib2' includes = set(Path(i) for i in ext.include_dirs) for source in ext.sources: src = Path(source).absolute() assert src.suffix == '.c'
def test_compile_nim_configs(lib): """ Make sure that an extension can still be compiled when nim configuration files are detected. """ ext = NimCompiler.compile_nim_extension( Path(f'tests/{lib}'), Path('tests'), library=True ) assert isinstance(ext, Extension) assert ext.name == f'{lib}' for source in ext.sources: src = Path(source).absolute() assert src.suffix == '.c'
def test_invoke_compiler_failure(): "Make sure that the compiler fails on bad input." err_file = Path('tests/pkg1/error.nim').resolve() ext = '.exe' if sys.platform == 'win32' else '' out_file = Path('tests/pkg1/error' + ext).resolve() try: out, err, war, hin = NimCompiler.invoke_compiler( ['nim', 'c', str(err_file)]) assert not out_file.exists() assert any('Error: cannot open file: fallacy' in i for i in err) assert any('Hint: system [Processing]' in i for i in hin) finally: if out_file.exists(): out_file.unlink()
def test_invoke_compiler_success(): "Test that warnings are propagated" warn_file = Path('tests/pkg1/warn.nim').resolve() ext = '.exe' if sys.platform == 'win32' else '' out_file = Path('tests/pkg1/warn' + ext).resolve() try: out, err, war, hin = NimCompiler.invoke_compiler( ['nim', 'c', str(warn_file)]) assert out_file.exists() assert any("Warning: imported and not used: 'tables'" in i for i in war) assert any('Hint: system [Processing]' in i for i in hin) finally: if out_file.exists(): out_file.unlink()
def test_invoke_compiler_failure(): "Make sure that the compiler fails on bad input." err_file = Path('tests/pkg1/error.nim').resolve() ext = '.exe' if sys.platform == 'win32' else '' out_file = Path('tests/pkg1/error' + ext).resolve() try: # Since this will fail at the Nim layer, no need to ensure all the same # flags are set per platform (since it won't ever get that far). out, err, war, hin = NimCompiler.invoke_compiler( ['nim', 'c', *NimCompiler.NIM_CLI_ARGS, str(err_file)]) assert not out_file.exists(), err assert any('Error: cannot open file: fallacy' in i for i in err) assert any('Hint' in i for i in hin) finally: if out_file.exists(): out_file.unlink()
def test_compilation_failures(): "Make sure that all expected errors are thrown." # Doesn't exist try: NimCompiler.compile_nim_extension(Path('bar/baz'), None, library=False) assert False, 'Should throw exception.' except NimporterException: "Expected result" # No Nimble file try: NimCompiler.compile_nim_extension( Path('tests/lib3'), Path('tests'), library=True ) assert False, 'Should throw exception.' except NimporterException: "Expected result" # Errors with Library try: NimCompiler.compile_nim_extension( Path('tests/lib4'), Path('tests'), library=True ) assert False, 'Should throw exception.' except NimInvokeException: "Expected result" # Errors with Module try: NimCompiler.compile_nim_extension( Path('tests/pkg1/error.nim'), Path('tests'), library=False ) assert False, 'Should throw exception.' except NimCompileException: "Expected result"
def test_ensure_nimpy(): "Make sure that the Nimpy library can be installed." assert shutil.which('nim'), 'Nim compiler is not installed or not on path' # Make sure it is not installed out = NimCompiler.invoke_compiler('nimble path nimpy'.split()) # Remove it if out[1]: out = NimCompiler.invoke_compiler( 'nimble uninstall nimpy --accept'.split()) # Install/verify it is already installed NimCompiler.ensure_nimpy() # Check installation code path NimCompiler.ensure_nimpy() # Verify that it is actuall installed according to Nimble out = NimCompiler.invoke_compiler('nimble path nimpy'.split()) assert out[0] and not out[1]
def main(cli_args=None): parser = argparse.ArgumentParser(description='Nimporter CLI') subs = parser.add_subparsers(dest='cmd', required=True) subs.add_parser('clean') build = subs.add_parser('build') build.add_argument( 'source', type=pathlib.Path, help='the Nim module/library to compile' ) build.add_argument( '--dest', type=pathlib.Path, help='the folder to store the build artifact' ) bundle_parser = subs.add_parser('bundle') bundle = bundle_parser.add_subparsers(dest='exp', required=True) bin_ = bundle.add_parser('bin') src = bundle.add_parser('src') args = parser.parse_args(cli_args or sys.argv[1:]) if args.cmd == 'clean': cwd = pathlib.Path() print('Cleaning Directory:', cwd.resolve()) clean(cwd) elif args.cmd == 'build': args.source = args.source.absolute() if not args.dest: args.dest = NimCompiler.build_artifact(args.source).parent else: assert args.dest.is_dir(), ( 'Cannot specify output filename since extensions change per ' 'platform. Please specify an output directory such as ".".' ) args.dest.mkdir(exist_ok=True) module = args.source if args.source.is_dir(): is_library = bool([*module.glob('*.nimble')]) assert is_library, 'Library dir must contain <libname>.nimble file' elif args.source.is_file(): is_library = bool([*module.parent.glob('*.nimble')]) if is_library: module = module.parent temp_build_dir = pathlib.Path('build').absolute() temp_build_dir.mkdir(exist_ok=True) artifact = temp_build_dir / (args.source.stem + NimCompiler.EXT) try: NimCompiler.compile_nim_code( module, artifact, library=is_library ) shutil.copy(artifact, args.dest) module_name = args.source.stem + '.nim' Nimporter.update_hash(args.dest.parent / module_name) finally: shutil.rmtree(temp_build_dir) elif args.cmd == 'bundle': setup = pathlib.Path('setup.py') if not setup.exists(): print('No setup.py found in dir, would you like to generate one?') answer = 'a' while answer not in 'YN': answer = input(' Y/N: ').upper() or 'a' if answer == 'Y': setup.write_text( f'# Setup.py tutorial:\n' f'# https://github.com/navdeep-G/setup.py\n' f'# Edit `packages=` to fit your requirements\n' f'import setuptools, nimporter\n\n' f'setuptools.setup(\n' f' name="{pathlib.Path().absolute().name}",\n' f' packages=[..], # Please read the above tutorial\n' f' ext_modules=nimporter.build_nim_extensions()\n' f')\n' ) print('Generated reference setup.py') print('Modify setup.py to point to your modules/packages.') bundle_type = 'source' if args.exp == 'src' else 'binary' print( f'Once you have finished, run `{" ".join(cli_args)}` again ' f'to create a {bundle_type} distribution package.' ) else: pyexe = 'python' if sys.platform == 'win32' else 'python3' if args.exp == 'bin': subprocess.Popen(f'{pyexe} setup.py bdist_wheel'.split()).wait() elif args.exp == 'src': subprocess.Popen(f'{pyexe} setup.py sdist'.split()).wait() return 0
def main(cli_args=None): parser = argparse.ArgumentParser(description='Nimporter CLI') subs = parser.add_subparsers(dest='cmd', required=True) # Clean command subs.add_parser( 'clean', help=( 'Run in project root to recursively remove all Nimporter-specific ' 'build artifacts and hash files')) # Build command build = subs.add_parser( 'build', help=( 'Builds a Nim module/library into an importable Python extension')) build.add_argument('source', type=pathlib.Path, help='the Nim module/library to compile') build.add_argument('--dest', type=pathlib.Path, help='the folder to store the build artifact') # Bundle command bundle_parser = subs.add_parser( 'bundle', help=( 'Convenience command for running: python setup.py sdist/bdist_wheel' )) bundle = bundle_parser.add_subparsers(dest='exp', required=True) bin_ = bundle.add_parser('bin') src = bundle.add_parser('src') # Compile command compile_ = subs.add_parser( 'compile', help=('Clean project and then recurse through and build all Nim ' 'modules/libraries')) args = parser.parse_args(cli_args or sys.argv[1:]) if args.cmd == 'clean': cwd = pathlib.Path() print('Cleaning Directory:', cwd.resolve()) clean(cwd) elif args.cmd == 'build': args.source = args.source.absolute() if not args.dest: args.dest = NimCompiler.build_artifact(args.source).parent else: assert args.dest.is_dir(), ( 'Cannot specify output filename since extensions change per ' 'platform. Please specify an output directory such as ".".') args.dest.mkdir(exist_ok=True) module = args.source if args.source.is_dir(): is_library = bool([*module.glob('*.nimble')]) assert is_library, 'Library dir must contain <libname>.nimble file' elif args.source.is_file(): is_library = bool([*module.parent.glob('*.nimble')]) if is_library: module = module.parent temp_build_dir = pathlib.Path('build').absolute() temp_build_dir.mkdir(exist_ok=True) artifact = temp_build_dir / (args.source.stem + NimCompiler.EXT) try: NimCompiler.compile_nim_code(module, artifact, library=is_library) shutil.copy(artifact, args.dest) module_name = args.source.stem + '.nim' Nimporter.update_hash(args.dest.parent / module_name) finally: shutil.rmtree(temp_build_dir) elif args.cmd == 'bundle': setup = pathlib.Path('setup.py') if not setup.exists(): print('No setup.py found in dir, would you like to generate one?') answer = 'a' while answer not in 'YN': answer = input(' Y/N: ').upper() or 'a' if answer == 'Y': setup.write_text(SETUPPY_TEMPLATE) print('Generated reference setup.py') print('Modify setup.py to point to your modules/packages.') bundle_type = 'source' if args.exp == 'src' else 'binary' print( f'Once you have finished, run `{" ".join(cli_args)}` again ' f'to create a {bundle_type} distribution package.') else: pyexe = 'python' if sys.platform == 'win32' else 'python3' if args.exp == 'bin': subprocess.Popen( f'{pyexe} setup.py bdist_wheel'.split()).wait() elif args.exp == 'src': subprocess.Popen(f'{pyexe} setup.py sdist'.split()).wait() elif args.cmd == 'compile': clean() CTM = lambda: round(time.time() * 1000) start = CTM() extensions = Nimporter._find_extensions(pathlib.Path()) for extension in extensions: is_lib = extension.is_dir() print(f'Building Extension {"Lib" if is_lib else "Mod"}: ' f'{extension.name}') NimCompiler.compile_nim_code(extension.absolute(), NimCompiler.build_artifact( extension.absolute()), library=is_lib) if is_lib: Nimporter.update_hash(extension / (extension.name + '.nim')) else: Nimporter.update_hash(extension) print('Done.') print(f'Built {len(extensions)} Extensions In ' f'{(CTM() - start) / 1000.0} secs') return 0
def test_pycache_dir(): "Make sure that the correct path to the __pycache__ dir is returned." module_path = Path('tests/pkg1/mod1.nim').absolute() expected_pycache = Path('tests/pkg1/__pycache__').absolute() assert NimCompiler.pycache_dir(module_path).absolute() == expected_pycache