Skip to content

Commit d2a3cee

Browse files
authored
Enforce binary-only constraints during early requirements cache attempts (#38688)
* Add fallback platform for stager. * Force wheel if we have other platform to try * More comments. * Trigger PostCommit Python * Refactor code. * Refactor more. * pip download dep in requirement one by one.
1 parent 852d072 commit d2a3cee

2 files changed

Lines changed: 95 additions & 58 deletions

File tree

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
22
"comment": "Modify this file in a trivial way to cause this test suite to run.",
33
"pr": "38069",
4-
"modification": 41
4+
"modification": 42
55
}

sdks/python/apache_beam/runners/portability/stager.py

Lines changed: 94 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,16 @@
8888
# One of the choices for user to use for requirements cache during staging
8989
SKIP_REQUIREMENTS_CACHE = 'skip'
9090

91+
# Ordered list of manylinux tags from newest (strictest) to oldest (most compatible)
92+
# paired with the minimum pip version required to support the tag.
93+
# See https://github.com/pypa/manylinux.
94+
_MANYLINUX_PLATFORMS = [
95+
('manylinux_2_28_x86_64', '20.3'),
96+
('manylinux2014_x86_64', '19.3'), # equivalent to manylinux_2_17
97+
('manylinux2010_x86_64',
98+
'0.0'), # equivalent to manylinux_2_12, the fallback if pip is too old
99+
]
100+
91101
_LOGGER = logging.getLogger(__name__)
92102

93103

@@ -717,30 +727,6 @@ def _extract_local_packages(requirements_file):
717727
else:
718728
return [], requirements_file
719729

720-
@staticmethod
721-
def _get_platform_for_default_sdk_container():
722-
"""
723-
Get the platform for apache beam SDK container based on Pip version.
724-
725-
Note: pip is still expected to download compatible wheel of a package
726-
with platform tag manylinux1 if the package on PyPI doesn't
727-
have (manylinux2014) or (manylinux2010) wheels.
728-
Reference: https://www.python.org/dev/peps/pep-0599/#id21
729-
"""
730-
731-
# TODO(anandinguva): When https://github.com/pypa/pip/issues/10760 is
732-
# addressed, download wheel based on glibc version in Beam's Python
733-
# Base image
734-
pip_version = distribution('pip').version
735-
# See more information about manylinux at
736-
# https://github.com/pypa/manylinux
737-
if version.parse(pip_version) >= version.parse('20.3'):
738-
return 'manylinux_2_28_x86_64'
739-
elif version.parse(pip_version) >= version.parse('19.3'):
740-
return 'manylinux2014_x86_64'
741-
else:
742-
return 'manylinux2010_x86_64'
743-
744730
@staticmethod
745731
@retry.with_exponential_backoff(
746732
num_retries=4, retry_filter=retry_on_non_zero_exit)
@@ -764,40 +750,44 @@ def _populate_requirements_cache(
764750
# requirements file.
765751
download_dir = tempfile.mkdtemp(dir=temp_directory)
766752

767-
cmd_args = [
768-
Stager._get_python_executable(),
769-
'-m',
770-
'pip',
771-
'download',
772-
'--dest',
773-
download_dir,
774-
'--find-links',
775-
cache_dir,
776-
'-r',
777-
tmp_requirements_filepath,
778-
'--exists-action',
779-
'i',
780-
'--no-deps'
781-
]
753+
# Read packages from the requirements file
754+
requirements = []
755+
with open(tmp_requirements_filepath, 'r') as f:
756+
for line in f:
757+
line = line.strip()
758+
if line and not line.startswith('#'):
759+
requirements.append(line)
760+
761+
for req in requirements:
762+
cmd_args = [
763+
Stager._get_python_executable(),
764+
'-m',
765+
'pip',
766+
'download',
767+
'--find-links',
768+
cache_dir,
769+
req,
770+
'--exists-action',
771+
'i',
772+
'--no-deps'
773+
]
774+
775+
if populate_cache_with_sdists:
776+
attempt_download_dir = tempfile.mkdtemp(dir=temp_directory)
777+
cmd_args.extend(
778+
['--dest', attempt_download_dir, '--no-binary', ':all:'])
779+
_LOGGER.info('Executing command: %s', cmd_args)
780+
processes.check_output(cmd_args, stderr=processes.STDOUT)
781+
else:
782+
attempt_download_dir = Stager._download_pypi_packages(
783+
cmd_args, temp_directory)
782784

783-
if populate_cache_with_sdists:
784-
cmd_args.extend(['--no-binary', ':all:'])
785-
else:
786-
language_implementation_tag = 'cp'
787-
abi_suffix = 'm' if sys.version_info < (3, 8) else ''
788-
abi_tag = 'cp%d%d%s' % (
789-
sys.version_info[0], sys.version_info[1], abi_suffix)
790-
platform_tag = Stager._get_platform_for_default_sdk_container()
791-
cmd_args.extend([
792-
'--implementation',
793-
language_implementation_tag,
794-
'--abi',
795-
abi_tag,
796-
'--platform',
797-
platform_tag
798-
])
799-
_LOGGER.info('Executing command: %s', cmd_args)
800-
processes.check_output(cmd_args, stderr=processes.STDOUT)
785+
# Move downloaded packages to our common download directory
786+
for pkg_file in os.listdir(attempt_download_dir):
787+
src_path = os.path.join(attempt_download_dir, pkg_file)
788+
dest_path = os.path.join(download_dir, pkg_file)
789+
if not os.path.exists(dest_path):
790+
shutil.move(src_path, dest_path)
801791

802792
# Get list of downloaded packages and copy them to the cache
803793
downloaded_packages = set()
@@ -811,6 +801,53 @@ def _populate_requirements_cache(
811801

812802
return downloaded_packages
813803

804+
@staticmethod
805+
def _download_pypi_packages(cmd_args, temp_directory):
806+
language_implementation_tag = 'cp'
807+
abi_suffix = 'm' if sys.version_info < (3, 8) else ''
808+
abi_tag = 'cp%d%d%s' % (
809+
sys.version_info[0], sys.version_info[1], abi_suffix)
810+
pip_version = distribution('pip').version
811+
platforms = [
812+
platform for platform, min_pip_version in _MANYLINUX_PLATFORMS
813+
if version.parse(pip_version) >= version.parse(min_pip_version)
814+
]
815+
816+
last_exception = None
817+
for idx, platform in enumerate(platforms):
818+
attempt_download_dir = tempfile.mkdtemp(dir=temp_directory)
819+
attempt_cmd_args = cmd_args + [
820+
'--dest',
821+
attempt_download_dir,
822+
'--implementation',
823+
language_implementation_tag,
824+
'--abi',
825+
abi_tag,
826+
'--platform',
827+
platform
828+
]
829+
# Force binary wheel only if we have more platform fallbacks to try.
830+
# For the last platform, we omit this flag so it can natively fall back
831+
# to downloading a source distribution (sdist) if no matching wheel is found.
832+
if idx < len(platforms) - 1:
833+
attempt_cmd_args.extend(['--only-binary', ':all:'])
834+
835+
_LOGGER.info('Executing command: %s', attempt_cmd_args)
836+
try:
837+
processes.check_output(attempt_cmd_args, stderr=processes.STDOUT)
838+
last_exception = None
839+
return attempt_download_dir
840+
except Exception as e:
841+
_LOGGER.warning(
842+
'Pip download failed with platform %s, trying fallback: %s',
843+
platform,
844+
e)
845+
shutil.rmtree(attempt_download_dir)
846+
last_exception = e
847+
848+
if last_exception:
849+
raise last_exception
850+
814851
@staticmethod
815852
def _build_setup_package(
816853
setup_file: str,

0 commit comments

Comments
 (0)