8888# One of the choices for user to use for requirements cache during staging
8989SKIP_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