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