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
5 changes: 5 additions & 0 deletions .changes/next-release/bugfix-installer-40023.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"type": "bugfix",
"category": "installer",
"description": "Remove unnecessary .dist-info directories from executable"
}
9 changes: 9 additions & 0 deletions backends/build_system/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@
DISTRIBUTION_SOURCE_EXE = "source-exe"
DISTRIBUTION_SOURCE_SANDBOX = "source-sandbox"

# List of .dist-info directories to include in the CLI distribution.
# See also hook-awscli.py in the pyinstaller directory.
#
# prompt_toolkit uses its own metadata to determine
# its version. So we need to bundle the package
# metadata to avoid runtime errors.
# https://github.com/aws/aws-cli/issues/9453
DIST_INFO_DIRECTORIES_TO_KEEP = ['prompt_toolkit']


class ArtifactType(Enum):
PORTABLE_EXE = "portable-exe"
Expand Down
13 changes: 12 additions & 1 deletion backends/build_system/exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@

from awscli_venv import AwsCliVenv
from constants import (
DIST_INFO_DIRECTORIES_TO_KEEP,
DISTRIBUTION_SOURCE_EXE,
EXE_ASSETS_DIR,
PYINSTALLER_DIR,
Expand Down Expand Up @@ -56,6 +57,16 @@ def _update_metadata(self):
self._final_dist_dir,
distribution_source=DISTRIBUTION_SOURCE_EXE,
)
for distinfo in self._utils.glob(
'**/*.dist-info', root=self._final_dist_dir
):
if not any(
package_name in distinfo
for package_name in DIST_INFO_DIRECTORIES_TO_KEEP
):
self._utils.rmtree(
os.path.join(self._final_dist_dir, distinfo)
)

def _ensure_no_existing_build_dir(self):
if self._utils.isdir(self._dist_dir):
Expand Down Expand Up @@ -99,4 +110,4 @@ def _cleanup(self):
]
for location in locations:
self._utils.rmtree(location)
print("Deleted build directory: %s" % location)
print(f"Deleted build directory: {location}")
21 changes: 20 additions & 1 deletion scripts/installers/make-exe
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,13 @@ from distutils.dir_util import copy_tree
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))

from install_deps import install_packages
from utils import run, save_to_zip, tmp_dir, update_metadata
from utils import (
remove_dist_info_directories,
run,
save_to_zip,
tmp_dir,
update_metadata,
)

ROOT = os.path.dirname(
os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
Expand All @@ -27,6 +33,16 @@ DEFAULT_OUTPUT_ZIP = 'awscli-exe.zip'
AC_INDEX_INTERNAL_PATH = os.path.join('awscli', 'data', 'ac.index')
DISTRIBUTION_SOURCE = 'exe'

# List of .dist-info directories to include in the CLI distribution.
# See also hook-awscli.py in the pyinstaller directory,
# and constants.py in the custom build backend
#
# prompt_toolkit uses its own metadata to determine
# its version. So we need to bundle the package
# metadata to avoid runtime errors.
# https://github.com/aws/aws-cli/issues/9453
DIST_INFO_DIRECTORIES_TO_KEEP = ['prompt_toolkit']


def make_exe(exe_zipfile, cleanup=True, ac_index=None):
with tmp_dir() as workdir:
Expand All @@ -53,6 +69,9 @@ def do_make_exe(workdir, exe_zipfile, ac_index):
)
copy_directory_contents_into(aws_complete_exe_build, output_exe_dist_dir)
copy_directory_contents_into(ASSETS_DIR, exe_dir)
remove_dist_info_directories(
output_exe_dist_dir, DIST_INFO_DIRECTORIES_TO_KEEP
)
save_to_zip(workdir, exe_zipfile)


Expand Down
12 changes: 12 additions & 0 deletions scripts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,3 +99,15 @@ def save_to_zip(dirname, zipfile_name):
if zipfile_name.endswith('.zip'):
zipfile_name = zipfile_name[:-4]
shutil.make_archive(zipfile_name, 'zip', dirname)


def remove_dist_info_directories(dirname, keep_packages):
"""Remove .dist-info directories except for specified packages.
Generally these are not needed in the final CLI executable,
unless specified via keep_packages.
"""
for item in os.listdir(dirname):
if item.endswith('.dist-info') and not any(
package in item for package in keep_packages
):
shutil.rmtree(os.path.join(dirname, item))
15 changes: 13 additions & 2 deletions tests/backends/build_system/integration/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@
import sys
import venv
from pathlib import Path
from typing import Dict

from constants import DIST_INFO_DIRECTORIES_TO_KEEP

IS_WINDOWS = sys.platform == "win32"
BIN_DIRNAME = "Scripts" if IS_WINDOWS else "bin"
Expand Down Expand Up @@ -102,6 +103,16 @@ def assert_built_exe_is_correct(self, root_dir):
"dist",
"install",
}
dist_dir = aws_dir / "dist"
dist_info_files = set(
f
for f in os.listdir(dist_dir)
if '.dist-info' in f
and not any(keep in f for keep in DIST_INFO_DIRECTORIES_TO_KEEP)
)
assert (
dist_info_files == set()
), f"Expected no dist-info files (except {DIST_INFO_DIRECTORIES_TO_KEEP}), found: {dist_info_files}"

aws_exe = aws_dir / "dist" / "aws"
self.assert_version_string_is_correct(aws_exe, "exe")
Expand Down Expand Up @@ -171,7 +182,7 @@ def build_path(self):
def python_exe(self):
return self.venv_path / BIN_DIRNAME / PYTHON_EXE_NAME

def env(self, overrides: Dict[str, str] = None):
def env(self, overrides: dict[str, str] = None):
env = os.environ.copy()
if overrides:
env.update(overrides)
Expand Down
6 changes: 6 additions & 0 deletions tests/backends/build_system/unit/test_exe.py
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,12 @@ def _expected_build_tasks(self):
os.path.join("workspace", "aws", "dist"),
{"distribution_source": "source-exe"},
),
(
"glob",
"**/*.dist-info",
os.path.join("workspace", "aws", "dist"),
True,
),
]

def test_build(self, fake_aws_cli_venv):
Expand Down