8000 Add automatic expansion of --requirement list · kivy/python-for-android@8fa1a30 · GitHub
[go: up one dir, main page]

Skip to content

Commit 8fa1a30

Browse files
committed
Add automatic expansion of --requirement list
Related to issue #2529
1 parent 4563f3e commit 8fa1a30

File tree

2 files changed

+122
-10
lines changed

2 files changed

+122
-10
lines changed

pythonforandroid/toolchain.py

Lines changed: 121 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
66
This module defines the entry point for command line and programmatic use.
77
"""
8-
98
from os import environ
109
from pythonforandroid import __version__
1110
from pythonforandroid.pythonpackage import get_dep_names_of_package
@@ -651,9 +650,12 @@ def add_parser(subparsers, *args, **kwargs):
651650
"pyproject.toml"))):
652651
have_setup_py_or_similar = True
653652

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+
]
657659

658660
# Add dependencies from setup.py, but only if they are recipes
659661
# (because otherwise, setup.py itself will install them later)
@@ -672,10 +674,6 @@ def add_parser(subparsers, *args, **kwargs):
672674
)
673675
]
674676
info("Dependencies obtained: " + str(dependencies))
675-
all_recipes = [
676-
recipe.lower() for recipe in
677-
set(Recipe.list_recipes(self.ctx))
678-
]
679677
dependencies = set(dependencies).intersection(
680678
set(all_recipes)
681679
)
@@ -691,7 +689,118 @@ def add_parser(subparsers, *args, **kwargs):
691689
"package? Will continue WITHOUT setup.py deps."
692690
)
693691

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 = []
695804
for requirement in split_argument_list(args.requirements):
696805
if "==" in requirement:
697806
requirement, version = requirement.split(u"==", 1)
@@ -701,6 +810,9 @@ def add_parser(subparsers, *args, **kwargs):
701810
requirements.append(requirement)
702811
args.requirements = u",".join(requirements)
703812

813+
info('Expanded Requirements List: '
814+
'{}'.format(args.requirements.split(',')))
815+
704816
self.warn_on_deprecated_args(args)
705817

706818
self.storage_dir = args.storage_dir

setup.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222
install_reqs = [
2323
'appdirs', 'colorama>=0.3.3', 'jinja2', 'six',
2424
'enum34; python_version<"3.4"', 'sh>=1.10; sys_platform!="nt"',
25-
'pep517<0.7.0', 'toml',
25+
'pep517<0.7.0', 'toml', 'pip-tools'
2626
]
2727
# (pep517 and toml are used by pythonpackage.py)
2828

0 commit comments

Comments
 (0)
0