5
5
6
6
This module defines the entry point for command line and programmatic use.
7
7
"""
8
-
9
8
from os import environ
10
9
from pythonforandroid import __version__
11
10
from pythonforandroid .pythonpackage import get_dep_names_of_package
@@ -654,9 +653,12 @@ def add_parser(subparsers, *args, **kwargs):
654
653
"pyproject.toml" ))):
655
654
have_setup_py_or_similar = True
656
655
657
- # Process requirements and put version in environ
658
- if hasattr (args , 'requirements' ):
659
- requirements = []
656
+ # Process requirements and put version in environ:
657
+ if hasattr (args , 'requirements' ) and args .requirements :
658
+ all_recipes = [
659
+ recipe .lower () for recipe in
660
+ set (Recipe .list_recipes (self .ctx ))
661
+ ]
660
662
661
663
# Add dependencies from setup.py, but only if they are recipes
662
664
# (because otherwise, setup.py itself will install them later)
@@ -675,10 +677,6 @@ def add_parser(subparsers, *args, **kwargs):
675
677
)
676
678
]
677
679
info ("Dependencies obtained: " + str (dependencies ))
678
- all_recipes = [
679
- recipe .lower () for recipe in
680
- set (Recipe .list_recipes (self .ctx ))
681
- ]
682
680
dependencies = set (dependencies ).intersection (
683
681
set (all_recipes )
684
682
)
@@ -694,7 +692,126 @@ def add_parser(subparsers, *args, **kwargs):
694
692
"package? Will continue WITHOUT setup.py deps."
695
693
)
696
694
697
- # Parse --requirements argument list:
695
+ non_recipe_requirements = []
696
+ for requirement in args .requirements .split (',' ):
697
+ requirement_name = re .sub (r'==\d+(\.\d+)*' , '' , requirement )
698
+ if requirement_name not in all_recipes :
699
+ non_recipe_requirements .append (requirement )
700
+ args .requirements = re .sub (
701
+ r',?{}' .format (requirement ), '' , args .requirements )
702
+
703
+ # Compile "non-recipe" requirements' dependencies and add to list.
704
+ # Otherwise, only recipe requirements' dependencies get installed.
705
+ # More info https://github.com/kivy/python-for-android/issues/2529
706
+ if non_recipe_requirements :
707
+ info ("Compiling dependencies for: "
708
+ "{}" .format (non_recipe_requirements ))
709
+
710
+ output = shprint (
711
+ sh .bash , '-c' ,
712
+ "echo -e '{}' > requirements.in && "
713
+ "pip-compile -v --dry-run --annotation-style=line && "
714
+ "rm requirements.in" .format (
715
+ '\n ' .join (non_recipe_requirements )))
716
+
717
+ # Parse pip-compile output
718
+ parsed_requirement_info_list = []
719
+ for line in output .splitlines ():
720
+ match_data = re .match (
721
+ r'^([\w.-]+)==(\d+(\.\d+)*).*'
722
+ r'#\s+via\s+([\w\s,.-]+)' , line )
723
+
724
+ if match_data :
725
+ parent_requirements = match_data .group (4 ).split (', ' )
726
+ requirement_name = match_data .group (1 )
727
+ requirement_version = match_data .group (2 )
728
+
729
+ # Requirement is a "non-recipe" one we started with.
730
+ if '-r requirements.in' in parent_requirements :
731
+ parent_requirements .remove ('-r requirements.in' )
732
+
733
+ parsed_requirement_info_list .append ([
734
+ requirement_name ,
735
+ requirement_version ,
736
+ parent_requirements ])
737
+
738
+ info ("Requirements obtained from pip-compile: "
739
+ "{}" .format (["{}=={}" .format (x [0 ], x [1 ])
740
+ for x in parsed_requirement_info_list ]))
741
+
742
+ # Remove indirect requirements ultimately installed by a recipe
743
+ original_parsed_requirement_count = - 1
744
+ while len (parsed_requirement_info_list ) != \
745
+ original_parsed_requirement_count :
746
+
747
+ original_parsed_requirement_count = \
748
+ len (parsed_requirement_info_list )
749
+
750
+ for i , parsed_requirement_info in \
751
+ enumerate (reversed (parsed_requirement_info_list )):
752
+
753
+ index = original_parsed_requirement_count - i - 1
754
+ requirement_name , requirement_version , \
755
+ parent_requirements = parsed_requirement_info
756
+
757
+ # If any parent requirement has a recipe, this
758
+ # requirement ought also to be installed by it.
759
+ # Hence, it's better not to add this requirement the
760
+ # expanded list.
761
+ parent_requirements_with_recipe = list (
762
+ set (parent_requirements ).intersection (
763
+ set (all_recipes )))
764
+
765
+ # Any parent requirement removed for the expanded list
766
+ # implies that it and its own requirements (including
767
+ # this requirement) will be installed by a recipe.
768
+ # Hence, it's better not to add this requirement the
769
+ # expanded list.
770
+ requirement_name_list = \
771
+ [x [0 ] for x in parsed_requirement_info_list ]
772
+ parent_requirements_still_in_list = list (
773
+ set (parent_requirements ).intersection (
774
+ set (requirement_name_list )))
775
+
776
+ is_ultimately_installed_by_a_recipe = \
777
+ len (parent_requirements ) and \
778
+ (parent_requirements_with_recipe or
779
+ len (parent_requirements_still_in_list ) !=
780
+ len (parent_requirements ))
781
+
782
+ if is_ultimately_installed_by_a_recipe :
783
+ info (
784
+ '{} will be installed by a recipe. Removing '
785
+ 'it from requirement list expansion.' .format (
786
+ requirement_name ))
787
+ del parsed_requirement_info_list [index ]
788
+
789
+ for parsed_requirement_info in parsed_requirement_info_list :
790
+ requirement_name , requirement_version , \
791
+ parent_requirements = parsed_requirement_info
792
+
793
+ # If the requirement has a recipe, don't use specific
794
+ # version constraints determined by pip-compile. Some
795
+ # recipes may not support the specified version. Therefor,
796
+ # it's probably safer to just let them use their default
797
+ # version. User can still force the usage of specific
798
+ # version by explicitly declaring it with --requirements.
799
+ requirement_has_recipe = requirement_name in all_recipes
800
+ requirement_str = \
801
+ requirement_name if requirement_has_recipe else \
802
+ '{}=={}' .format (requirement_name , requirement_version )
803
+
804
+ requirement_names_arg = re .sub (
805
+ r'==\d+(\.\d+)*' , '' , args .requirements ).split (',' )
806
+
807
+ # This expansion was carried out based on "non-recipe"
808
+ # requirements. Hence,the counter-part, requirements
809
+ # with a recipe, may already be part of list.
810
+ if requirement_name not in requirement_names_arg :
811
+ args .requirements += ',' + requirement_str
812
+
813
+ # Handle specific version requirement constraints (e.g. foo==x.y)
814
+ requirements = []
698
815
for requirement in split_argument_list (args .requirements ):
699
816
if "==" in requirement :
700
817
requirement , version = requirement .split (u"==" , 1 )
@@ -704,6 +821,9 @@ def add_parser(subparsers, *args, **kwargs):
704
821
requirements .append (requirement )
705
822
args .requirements = u"," .join (requirements )
706
823
824
+ info ('Expanded Requirements List: '
825
+ '{}' .format (args .requirements .split (',' )))
826
+
707
827
self .warn_on_deprecated_args (args )
708
828
709
829
self .storage_dir = args .storage_dir
0 commit comments