Skip to content

Commit

Permalink
Add --module-name argument to cython command (cythonGH-4548)
Browse files Browse the repository at this point in the history
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
rgommers/scipy#31 (comment)

Backported-By: H. Vetinari <h.vetinari@gmx.com>
  • Loading branch information
matthew-brett authored and h-vetinari committed Jul 19, 2022
1 parent 4d626ca commit ce9a543
Show file tree
Hide file tree
Showing 4 changed files with 124 additions and 14 deletions.
16 changes: 14 additions & 2 deletions Cython/Compiler/CmdLine.py
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,8 @@ def get_param(option):
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
Expand All @@ -216,9 +218,19 @@ def get_param(option):
sys.exit(1)
if len(sources) == 0 and not options.show_version:
bad_usage()
if Options.embed and len(sources) > 1:
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)
parser.error("")
return options, sources

8 changes: 6 additions & 2 deletions Cython/Compiler/Main.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 len(sources) > 1 and options.module_name:
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]
Expand All @@ -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.
Expand Down
62 changes: 52 additions & 10 deletions Cython/Compiler/Tests/TestCmdLine.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@

import sys
import re
from unittest import TestCase
try:
from StringIO import StringIO
Expand Down Expand Up @@ -98,21 +99,62 @@ def test_options_with_values(self):
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())
msg = stderr.getvalue()
err_msg = 'Message "{}"'.format(msg.strip())
self.assertTrue(msg.startswith('usage: '),
'%s does not start with "usage :"' % err_msg)
self.assertTrue(': error: ' in msg,
'%s does not contain ": error :"' % err_msg)
if regex:
self.assertTrue(re.search(regex, msg),
'%s does not match search "%s"' %
(err_msg, regex))

error('-1')
error('-I')
error('--version=-a')
error('--version=--annotate=true')
error('--working')
error('--verbose=1')
error('--verbose=1')
error('--cleanup')
error(['-1'],
'unknown option -1')
error(['-I'],
'argument -I/--include-dir: expected one argument')
error(['--version=-a'],
"argument -V/--version: ignored explicit argument '-a'")
error(['--version=--annotate=true'],
"argument -V/--version: ignored explicit argument "
"'--annotate=true'")
error(['--working'],
"argument -w/--working: expected one argument")
error(['--verbose=1'],
"argument -v/--verbose: ignored explicit argument '1'")
error(['--cleanup'],
"argument --cleanup: expected one argument")
error(['--debug-disposal-code-wrong-name', 'file3.pyx'],
"unknown option --debug-disposal-code-wrong-name")
error(['--module-name', 'foo.pyx'],
"Need at least one source file")
error(['--module-name', 'foo.bar'],
"Need at least one source file")
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")
52 changes: 52 additions & 0 deletions tests/compile/module_name_arg.srctree
Original file line number Diff line number Diff line change
@@ -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 ########

0 comments on commit ce9a543

Please sign in to comment.