Skip to content

[BUG] StaticModule not choosing the Python file encoding #3612

Closed
@JulienPalard

Description

@JulienPalard

setuptools version

setuptools==65.4.0

Python version

3.7, 3.8, 3.9, 3.10, 3.11

OS

Windows mainly, but reproducible on Linux

Additional environment information

No response

Description

The StaticModule opens Python file without looking at -*- encoding, and without specifying the encoding:

    with open(spec.origin) as strm:

On Linux as most Python files are UTF-8 and as Python defaults to UTF-8 to open files, it goes right.

But on Windows, Python will default to CP1252 at file opening, possibly leading to a UnicodeDecodeError.

When the module don't have an import on an external dependency the fallback to evaluate module hides the bug.

Expected behavior

I expect StaticModule to first look at the encoding marker (# -*- coding: encoding -*-) of the file before trying to decode it, and defaulting to UTF-8 if no maker is found.

How to Reproduce

I first found the bug on a Windows CI runner on github so it was reading UTF-8 file as CP1252. I'm on Debian so I'll repro it the other way around: writing a latin1 file and I let setuptools fail to decode it.

mkdir repro
cd repro
echo $'# -*- coding: latin1 -*-\nimport regex\n__version__ = "0.1"\nprint("\xe9")' > repro.py 
cat <<EOF >pyproject.toml 
[build-system]
requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "my_package"
dynamic = ["version"]
dependencies = [
    "regex",
]

[tool.setuptool]
py-modules = ['repro.py']

[tool.setuptools.dynamic.version]
attr = "repro.__version__"
EOF
pip install .

Output

output
Processing /home/mdk/clones/setuptools/repro
  Installing build dependencies ... done
  Getting requirements to build wheel ... error
  error: subprocess-exited-with-error
  
  × Getting requirements to build wheel did not run successfully.
  │ exit code: 1
  ╰─> [58 lines of output]
      /tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py:108: _BetaConfiguration: Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*.
        warnings.warn(msg, _BetaConfiguration)
      Traceback (most recent call last):
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/expand.py", line 191, in read_attr
          return getattr(StaticModule(module_name, spec), attr_name)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/expand.py", line 66, in __init__
          src = strm.read()
        File "/usr/lib/python3.10/codecs.py", line 322, in decode
          (result, consumed) = self._buffer_decode(data, self.errors, final)
      UnicodeDecodeError: 'utf-8' codec can't decode byte 0xe9 in position 65: invalid continuation byte
      
      During handling of the above exception, another exception occurred:
      
      Traceback (most recent call last):
        File "/home/mdk/clones/setuptools/repro/.venv/lib/python3.10/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 363, in <module>
          main()
        File "/home/mdk/clones/setuptools/repro/.venv/lib/python3.10/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 345, in main
          json_out['return_val'] = hook(**hook_input['kwargs'])
        File "/home/mdk/clones/setuptools/repro/.venv/lib/python3.10/site-packages/pip/_vendor/pep517/in_process/_in_process.py", line 130, in get_requires_for_build_wheel
          return hook(config_settings)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/build_meta.py", line 338, in get_requires_for_build_wheel
          return self._get_build_requires(config_settings, requirements=['wheel'])
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/build_meta.py", line 320, in _get_build_requires
          self.run_setup()
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/build_meta.py", line 335, in run_setup
          exec(code, locals())
        File "<string>", line 1, in <module>
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/__init__.py", line 87, in setup
          return distutils.core.setup(**attrs)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/_distutils/core.py", line 159, in setup
          dist.parse_config_files()
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/dist.py", line 868, in parse_config_files
          pyprojecttoml.apply_configuration(self, filename, ignore_option_errors)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 62, in apply_configuration
          config = read_configuration(filepath, True, ignore_option_errors, dist)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 140, in read_configuration
          return expand_configuration(asdict, root_dir, ignore_option_errors, dist)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 195, in expand_configuration
          return _ConfigExpander(config, root_dir, ignore_option_errors, dist).expand()
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 242, in expand
          self._expand_all_dynamic(dist, package_dir)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 286, in _expand_all_dynamic
          version=self._obtain_version(dist, package_dir),
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 332, in _obtain_version
          return _expand.version(self._obtain(dist, "version", package_dir))
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 321, in _obtain
          return self._expand_directive(
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py", line 315, in _expand_directive
          return _expand.read_attr(directive["attr"], package_dir, root_dir)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/expand.py", line 194, in read_attr
          module = _load_spec(spec, module_name)
        File "/tmp/pip-build-env-by2xrl8v/overlay/lib/python3.10/site-packages/setuptools/config/expand.py", line 214, in _load_spec
          spec.loader.exec_module(module)  # type: ignore
        File "<frozen importlib._bootstrap_external>", line 883, in exec_module
        File "<frozen importlib._bootstrap>", line 241, in _call_with_frames_removed
        File "/home/mdk/clones/setuptools/repro/repro.py", line 2, in <module>
          import regex
      ModuleNotFoundError: No module named 'regex'
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.
│ exit code: 1
╰─> See above for output.

note: This error originates from a subprocess, and is likely not a problem with pip.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Needs TriageIssues that need to be evaluated for severity and status.bug

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions