Skip to content
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
9 changes: 8 additions & 1 deletion python/rpdk/python/codegen.py
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,7 @@ def _make_pip_command(base_path):
str(base_path / "requirements.txt"),
"--target",
str(base_path / "build"),
"setuptools<82",
]

@staticmethod
Expand Down Expand Up @@ -399,9 +400,15 @@ def _pip_build(cls, base_path):
LOG.warning("Starting pip build.")
try:
# On windows run pip command through the default shell (CMD)
# Build a quoted string so CMD doesn't misinterpret '<' or '>'
# in version specifiers (e.g. 'setuptools<82') as redirection.
if os.name == "nt":
cmd_str = " ".join(
f'"{a}"' if any(c in a for c in ("<", ">", "&", "|")) else a
for a in command
)
completed_proc = subprocess_run( # nosec
command,
cmd_str,
stdout=PIPE,
stderr=PIPE,
cwd=base_path,
Expand Down
1 change: 1 addition & 0 deletions python/rpdk/python/templates/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
{{ support_lib_name }}>=2.1.9
setuptools<82
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Without this change, a brand new project created with cfn init would get a requirements.txt containing only:

cloudformation-cli-python-lib>=2.1.9

You would still need to pin <82 locally to get cfn init working based on my testing on a fresh instalation without pinning <82 when you run cfn init you will get the following error "ModuleNotFoundError: No module named 'pkg_resources' "

2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ def find_version(*file_paths):
install_requires=[
"cloudformation-cli>=0.2.26",
"types-dataclasses>=0.1.5",
"setuptools",
"setuptools<82",
Copy link
Copy Markdown
Contributor Author

@tagaws tagaws Apr 15, 2026

Choose a reason for hiding this comment

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

Protects the dev env (venv) from pinning incorrect setuptools version still safe to add a <82 here, although codegen.py is what compiles it into the build: - If someone uses a different build tool or manually runs pip against requirements.txt

],
entry_points={
"rpdk.v1.languages": [
Expand Down
37 changes: 36 additions & 1 deletion tests/plugin/codegen_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,27 @@ def test__pip_build_called_process_error(tmp_path):
assert isinstance(excinfo.value.__cause__, (FileNotFoundError, CalledProcessError))


def test__pip_build_windows_quotes_version_specifiers(tmp_path):
"""On Windows, args with '<' must be quoted so CMD doesn't misinterpret them."""
command = ["pip", "install", "setuptools<82"]
patch_cmd = patch.object(
PythonLanguagePlugin, "_make_pip_command", return_value=command
)
patch_os = patch("rpdk.python.codegen.os.name", "nt")
patch_run = patch("rpdk.python.codegen.subprocess_run", autospec=True)

with patch_cmd, patch_os, patch_run as mock_run:
PythonLanguagePlugin._pip_build(tmp_path)

call_args = mock_run.call_args
cmd_arg = call_args[0][0]
assert isinstance(
cmd_arg, str
), "Windows branch must pass a string to subprocess_run"
assert "setuptools<82" in cmd_arg
assert call_args[1]["shell"] is True


def test__build_pip(plugin):
plugin._use_docker = False
plugin._no_docker = True
Expand All @@ -553,6 +574,17 @@ def test__build_pip(plugin):
mock_pip.assert_called_once_with(sentinel.base_path)


def test__make_pip_command_pins_setuptools_lt_82(tmp_path):
"""setuptools<82 must be in the pip command to prevent pkg_resources removal."""
cmd = PythonLanguagePlugin._make_pip_command(tmp_path)
assert "setuptools<82" in cmd, (
"setuptools<82 must be pinned in pip command — setuptools 82+ removes "
"pkg_resources which breaks cloudformation-cli-python-lib at runtime"
)
assert str(tmp_path / "requirements.txt") in cmd
assert str(tmp_path / "build") in cmd


def test__build_pip_posix(plugin):
patch_os_name = patch("rpdk.python.codegen.os.name", "posix")
patch_subproc = patch("rpdk.python.codegen.subprocess_run")
Expand Down Expand Up @@ -581,7 +613,10 @@ def test__build_pip_windows(plugin):
plugin._pip_build(temppath)

mock_subproc.assert_called_once_with(
plugin._make_pip_command(temppath),
" ".join(
f'"{a}"' if any(c in a for c in ("<", ">", "&", "|")) else a
for a in plugin._make_pip_command(temppath)
),
stdout=ANY,
stderr=ANY,
cwd=temppath,
Expand Down
Loading