diff options
author | h-vetinari <h.vetinari@gmx.com> | 2022-07-27 16:59:02 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-07-27 16:59:02 +0200 |
commit | 6cede008009c6a5bf5c1414591ca79ec62f56256 (patch) | |
tree | 25608e314c47ff482361d713add091fe10056969 | |
parent | 34ce43c8f3dafda734d59345731a61b8200c3b94 (diff) | |
download | cython-6cede008009c6a5bf5c1414591ca79ec62f56256.tar.gz |
[0.29] Add --module-name argument to cython command (GH-4906)
Backport of https://github.com/cython/cython/pull/4548
It can be useful to specify the module name for the output file
directly, rather than working it out from the enclosing file tree -
particularly for out of tree build systems, like Meson.
See background in
https://github.com/rgommers/scipy/issues/31#issuecomment-1002662816
-rw-r--r-- | Cython/Compiler/CmdLine.py | 17 | ||||
-rw-r--r-- | Cython/Compiler/Main.py | 9 | ||||
-rw-r--r-- | Cython/Compiler/Tests/TestCmdLine.py | 74 | ||||
-rw-r--r-- | tests/compile/module_name_arg.srctree | 52 |
4 files changed, 138 insertions, 14 deletions
diff --git a/Cython/Compiler/CmdLine.py b/Cython/Compiler/CmdLine.py index e89e45ab4..9e2f8beb0 100644 --- a/Cython/Compiler/CmdLine.py +++ b/Cython/Compiler/CmdLine.py @@ -50,6 +50,9 @@ Options: --warning-extra, -Wextra Enable extra warnings -X, --directive <name>=<value>[,<name=value,...] Overrides a compiler directive -E, --compile-time-env name=value[,<name=value,...] Provides compile time env like DEF would do. + --module-name Fully qualified module name. If not given, it is deduced from the + import path if source file is in a package, or equals the + filename otherwise. """ @@ -190,6 +193,8 @@ def parse_command_line(args): except ValueError as e: sys.stderr.write("Error in compile-time-env: %s\n" % e.args[0]) sys.exit(1) + elif option == "--module-name": + options.module_name = pop_value() elif option.startswith('--debug'): option = option[2:].replace('-', '_') from . import DebugFlags @@ -202,6 +207,7 @@ def parse_command_line(args): sys.stdout.write(usage) sys.exit(0) else: + sys.stderr.write(usage) sys.stderr.write("Unknown compiler flag: %s\n" % option) sys.exit(1) else: @@ -218,7 +224,16 @@ def parse_command_line(args): bad_usage() if Options.embed and len(sources) > 1: sys.stderr.write( - "cython: Only one source file allowed when using -embed\n") + "cython: Only one source file allowed when using --embed\n") sys.exit(1) + if options.module_name: + if options.timestamps: + sys.stderr.write( + "cython: Cannot use --module-name with --timestamps\n") + sys.exit(1) + if len(sources) > 1: + sys.stderr.write( + "cython: Only one source file allowed when using --module-name\n") + sys.exit(1) return options, sources diff --git a/Cython/Compiler/Main.py b/Cython/Compiler/Main.py index dc4add541..128441da6 100644 --- a/Cython/Compiler/Main.py +++ b/Cython/Compiler/Main.py @@ -735,6 +735,9 @@ def compile_multiple(sources, options): a CompilationResultSet. Performs timestamp checking and/or recursion if these are specified in the options. """ + if options.module_name and len(sources) > 1: + raise RuntimeError('Full module name can only be set ' + 'for single source compilation') # run_pipeline creates the context # context = options.create_context() sources = [os.path.abspath(source) for source in sources] @@ -753,8 +756,9 @@ def compile_multiple(sources, options): if (not timestamps) or out_of_date: if verbose: sys.stderr.write("Compiling %s\n" % source) - - result = run_pipeline(source, options, context=context) + result = run_pipeline(source, options, + full_module_name=options.module_name, + context=context) results.add(source, result) # Compiling multiple sources in one context doesn't quite # work properly yet. @@ -900,5 +904,6 @@ default_options = dict( build_dir=None, cache=None, create_extension=None, + module_name=None, np_pythran=False ) diff --git a/Cython/Compiler/Tests/TestCmdLine.py b/Cython/Compiler/Tests/TestCmdLine.py index abc7c0a89..bd31da000 100644 --- a/Cython/Compiler/Tests/TestCmdLine.py +++ b/Cython/Compiler/Tests/TestCmdLine.py @@ -1,5 +1,6 @@ import sys +import re from unittest import TestCase try: from StringIO import StringIO @@ -10,6 +11,18 @@ from .. import Options from ..CmdLine import parse_command_line +def check_global_options(expected_options, white_list=[]): + """ + returns error message of "" if check Ok + """ + no_value = object() + for name, orig_value in expected_options.items(): + if name not in white_list: + if getattr(Options, name, no_value) != orig_value: + return "error in option " + name + return "" + + class CmdLineParserTest(TestCase): def setUp(self): backup = {} @@ -23,6 +36,17 @@ class CmdLineParserTest(TestCase): if getattr(Options, name, no_value) != orig_value: setattr(Options, name, orig_value) + def check_default_global_options(self, white_list=[]): + self.assertEqual(check_global_options(self._options_backup, white_list), "") + + def check_default_options(self, options, white_list=[]): + from ..Main import CompilationOptions, default_options + default_options = CompilationOptions(default_options) + no_value = object() + for name in default_options.__dict__.keys(): + if name not in white_list: + self.assertEqual(getattr(options, name, no_value), getattr(default_options, name), msg="error in option " + name) + def test_short_options(self): options, sources = parse_command_line([ '-V', '-l', '-+', '-t', '-v', '-v', '-v', '-p', '-D', '-a', '-3', @@ -98,21 +122,49 @@ class CmdLineParserTest(TestCase): self.assertTrue(options.gdb_debug) self.assertEqual(options.output_dir, '/gdb/outdir') + def test_module_name(self): + options, sources = parse_command_line([ + 'source.pyx' + ]) + self.assertEqual(options.module_name, None) + self.check_default_global_options() + self.check_default_options(options) + options, sources = parse_command_line([ + '--module-name', 'foo.bar', + 'source.pyx' + ]) + self.assertEqual(options.module_name, 'foo.bar') + self.check_default_global_options() + self.check_default_options(options, ['module_name']) + def test_errors(self): - def error(*args): + def error(args, regex=None): old_stderr = sys.stderr stderr = sys.stderr = StringIO() try: self.assertRaises(SystemExit, parse_command_line, list(args)) finally: sys.stderr = old_stderr - self.assertTrue(stderr.getvalue()) - - error('-1') - error('-I') - error('--version=-a') - error('--version=--annotate=true') - error('--working') - error('--verbose=1') - error('--verbose=1') - error('--cleanup') + msg = stderr.getvalue().strip() + self.assertTrue(msg) + if regex: + self.assertTrue(re.search(regex, msg), + '"%s" does not match search "%s"' % + (msg, regex)) + + error(['-1'], + 'Unknown compiler flag: -1') + error(['-I']) + error(['--version=-a']) + error(['--version=--annotate=true']) + error(['--working']) + error(['--verbose=1']) + error(['--cleanup']) + error(['--debug-disposal-code-wrong-name', 'file3.pyx'], + "Unknown debug flag: debug_disposal_code_wrong_name") + error(['--module-name', 'foo.pyx']) + error(['--module-name', 'foo.bar']) + error(['--module-name', 'foo.bar', 'foo.pyx', 'bar.pyx'], + "Only one source file allowed when using --module-name") + error(['--module-name', 'foo.bar', '--timestamps', 'foo.pyx'], + "Cannot use --module-name with --timestamps") diff --git a/tests/compile/module_name_arg.srctree b/tests/compile/module_name_arg.srctree new file mode 100644 index 000000000..81e75b008 --- /dev/null +++ b/tests/compile/module_name_arg.srctree @@ -0,0 +1,52 @@ +# Test that we can set module name with --module-name arg to cython +CYTHON a.pyx +CYTHON --module-name w b.pyx +CYTHON --module-name my_module.submod.x c.pyx +PYTHON setup.py build_ext --inplace +PYTHON checks.py + +######## checks.py ######## + +from importlib import import_module + +try: + exc = ModuleNotFoundError +except NameError: + exc = ImportError + +for module_name, should_import in ( + ('a', True), + ('b', False), + ('w', True), + ('my_module.submod.x', True), + ('c', False), + ): + try: + import_module(module_name) + except exc: + if should_import: + assert False, "Cannot import module " + module_name + else: + if not should_import: + assert False, ("Can import module " + module_name + + " but import should not be possible") + + +######## setup.py ######## + +from distutils.core import setup +from distutils.extension import Extension + +setup( + ext_modules = [ + Extension("a", ["a.c"]), + Extension("w", ["b.c"]), + Extension("my_module.submod.x", ["c.c"]), + ], +) + +######## a.pyx ######## +######## b.pyx ######## +######## c.pyx ######## +######## my_module/__init__.py ######## +######## my_module/submod/__init__.py ######## |