8000 BUG: Disallow shadowed modulenames by HaoZeke · Pull Request #25181 · numpy/numpy · GitHub
[go: up one dir, main page]

Skip to content

BUG: Disallow shadowed modulenames #25181

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 8 commits into from
Nov 19, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions doc/release/upcoming_changes/25181.compatibility.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
``f2py`` will no longer accept ambiguous ``-m`` and ``.pyf`` CLI combinations.
When more than one ``.pyf`` file is passed, an error is raised. When both ``-m``
and a ``.pyf`` is passed, a warning is emitted and the ``-m`` provided name is
ignored.
7 changes: 4 additions & 3 deletions doc/source/f2py/usage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -239,10 +239,11 @@ Other options
``-m <modulename>``
Name of an extension module. Default is ``untitled``.

.. warning:: Don't use this option if a signature file (``*.pyf``) is used.
.. warning::
Don't use this option if a signature file (``*.pyf``) is used.

.. versionchanged:: 2.0.0
Will ignore ``-m`` if a ``pyf`` file is provided.
.. versionchanged:: 1.26.3
Will ignore ``-m`` if a ``pyf`` file is provided.

``--[no-]lower``
Do [not] lower the cases in ``<fortran files>``. By default, ``--lower`` is
Expand Down
62 changes: 40 additions & 22 deletions numpy/f2py/f2py2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -450,6 +450,16 @@ def run_main(comline_list):
f2pydir = os.path.dirname(os.path.abspath(cfuncs.__file__))
fobjhsrc = os.path.join(f2pydir, 'src', 'fortranobject.h')
fobjcsrc = os.path.join(f2pydir, 'src', 'fortranobject.c')
# gh-22819 -- begin
parser = make_f2py_parser()
args, comline_list = parser.parse_known_args(comline_list)
pyf_files, _ = filter_files("", "[.]pyf([.]src|)", comline_list)
# Checks that no existing modulename is defined in a pyf file
# TODO: Remove all this when scaninputline is replaced
if "-h" not in comline_list and args.module_name: # Can't check what doesn't exist yet, -h creates the pyf
modname = validate_modulename(pyf_files, args.module_name)
comline_list += ['-m', modname] # needed for the rest of scaninputline
# gh-22819 -- end
Comment on lines +453 to +462
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is implemented this way for BC reasons. Since there are global variables in f2py2e it makes sense to only incrementally make changes, #24552 details why and #25179 has some more information about a possible cleanup.

files, options = scaninputline(comline_list)
auxfuncs.options = options
capi_maps.load_f2cmap_file(options['f2cmap_file'])
Expand Down Expand Up @@ -516,24 +526,30 @@ def get_prefix(module):
p = os.path.dirname(os.path.dirname(module.__file__))
return p

def preparse_sysargv():
# To keep backwards bug compatibility, newer flags are handled by argparse,
# and `sys.argv` is passed to the rest of `f2py` as is.
def make_f2py_parser():
parser = argparse.ArgumentParser(add_help=False)
parser.add_argument("--dep", action="append", dest="dependencies")
parser.add_argument("--backend", choices=['meson', 'distutils'], default='distutils')
parser.add_argument("-m", dest="module_name")
return parser

def preparse_sysargv():
# To keep backwards bug compatibility, newer flags are handled by argparse,
# and `sys.argv` is passed to the rest of `f2py` as is.
parser = make_f2py_parser()

args, remaining_argv = parser.parse_known_args()
sys.argv = [sys.argv[0]] + remaining_argv

backend_key = args.backend
if sys.version_info >= (3, 12) and backend_key == 'distutils':
outmess('Cannot use distutils backend with Python 3.12, using meson backend instead.')
outmess("Cannot use distutils backend with Python 3.12, using meson backend instead.\n")
backend_key = 'meson'

return {
"dependencies": args.dependencies or [],
"backend": backend_key
"backend": backend_key,
"modulename": args.module_name,
}

def run_compile():
Expand All @@ -544,11 +560,11 @@ def run_compile():

# Collect dependency flags, preprocess sys.argv
argy = preparse_sysargv()
modulename = argy["modulename"]
dependencies = argy["dependencies"]
backend_key = argy["backend"]
build_backend = f2py_build_generator(backend_key)


i = sys.argv.index('-c')
del sys.argv[i]

Expand Down Expand Up @@ -628,30 +644,17 @@ def run_compile():
if '--quiet' in f2py_flags:
setup_flags.append('--quiet')

modulename = 'untitled'
sources = sys.argv[1:]

for optname in ['--include_paths', '--include-paths', '--f2cmap']:
if optname in sys.argv:
i = sys.argv.index(optname)
f2py_flags.extend(sys.argv[i:i + 2])
del sys.argv[i + 1], sys.argv[i]
sources = sys.argv[1:]

pyf_files = []
if '-m' in sys.argv:
i = sys.argv.index('-m')
modulename = sys.argv[i + 1]
del sys.argv[i + 1], sys.argv[i]
sources = sys.argv[1:]
else:
pyf_files, _sources = filter_files('', '[.]pyf([.]src|)', sources)
sources = pyf_files + _sources
for f in pyf_files:
modulename = auxfuncs.get_f2py_modulename(f)
if modulename:
break

pyf_files, _sources = filter_files("", "[.]pyf([.]src|)", sources)
sources = pyf_files + _sources
modulename = validate_modulename(pyf_files, modulename)
extra_objects, sources = filter_files('', '[.](o|a|so|dylib)', sources)
include_dirs, sources = filter_files('-I', '', sources, remove_prefix=1)
library_dirs, sources = filter_files('-L', '', sources, remove_prefix=1)
Expand Down Expand Up @@ -698,6 +701,21 @@ def run_compile():

builder.compile()


def validate_modulename(pyf_files, modulename='untitled'):
if len(pyf_files) > 1:
raise ValueError("Only one .pyf file per call")
if pyf_files:
pyff = pyf_files[0]
pyf_modname = auxfuncs.get_f2py_modulename(pyff)
if modulename != pyf_modname:
outmess(
f"Ignoring -m {modulename}.\n"
f"{pyff} defines {pyf_modname} to be the modulename.\n"
)
modulename = pyf_modname
return modulename

def main():
if '--help-link' in sys.argv[1:]:
sys.argv.remove('--help-link')
Expand Down
6 changes: 6 additions & 0 deletions numpy/f2py/tests/src/cli/gh_22819.pyf
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
python module test_22819
interface
subroutine hello()
end subroutine hello
end interface
end python module test_22819
41 changes: 41 additions & 0 deletions numpy/f2py/tests/test_f2py2e.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,15 @@ def gh23598_warn(tmpdir_factory):
return fn


@pytest.fixture(scope="session")
def gh22819_cli(tmpdir_factory):
"""F90 file for testing disallowed CLI arguments in ghff819"""
fdat = util.getpath("tests", "src", "cli", "gh_22819.pyf").read_text()
fn = tmpdir_factory.getbasetemp() / "gh_22819.pyf"
fn.write_text(fdat, encoding="ascii")
return fn


@pytest.fixture(scope="session")
def hello_world_f77(tmpdir_factory):
"""Generates a single f77 file for testing"""
Expand Down Expand Up @@ -100,6 +109,38 @@ def f2cmap_f90(tmpdir_factory):
return fn


def test_gh22819_cli(capfd, gh22819_cli, monkeypatch):
"""Check that module names are handled correctly
gh-22819
Essentially, the -m name cannot be used to import the module, so the module
named in the .pyf needs to be used instead

CLI :: -m and a .pyf file
"""
ipath = Path(gh22819_cli)
monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath}".split())
with util.switchdir(ipath.parent):
f2pycli()
gen_paths = [item.name for item in ipath.parent.rglob("*") if item.is_file()]
assert "blahmodule.c" not in gen_paths # shouldn't be generated
assert "blah-f2pywrappers.f" not in gen_paths
assert "test_22819-f2pywrappers.f" in gen_paths
assert "test_22819module.c" in gen_paths
assert "Ignoring blah"


def test_gh22819_many_pyf(capfd, gh22819_cli, monkeypatch):
"""Only one .pyf file allowed
gh-22819
CLI :: .pyf files
"""
ipath = Path(gh22819_cli)
monkeypatch.setattr(sys, "argv", f"f2py -m blah {ipath} hello.pyf".split())
with util.switchdir(ipath.parent):
with pytest.raises(ValueError, match="Only one .pyf file per call"):
f2pycli()


def test_gh23598_warn(capfd, gh23598_warn, monkeypatch):
foutl = get_io_paths(gh23598_warn, mname="test")
ipath = foutl.f90inp
Expand Down
0