Skip to content

Enhance distribution reuse: android_api #2031

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: develop
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion pythonforandroid/bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -160,7 +160,7 @@ def prepare_dist_dir(self):
ensure_dir(self.dist_dir)

def run_distribute(self):
self.distribution.save_info(self.dist_dir)
self.distribution.save_info()

@classmethod
def all_bootstraps(cls):
Expand Down
141 changes: 107 additions & 34 deletions pythonforandroid/distribution.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from os.path import exists, join
import glob
import json
from pathlib import Path

from pythonforandroid.logger import (info, info_notify, warning, Err_Style, Err_Fore)
from pythonforandroid.util import current_directory, BuildInterruptingException
from pythonforandroid.util import BuildInterruptingException
from shutil import rmtree


Expand All @@ -19,9 +20,11 @@ class Distribution(object):

name = None # A name identifying the dist. May not be None.
needs_build = False # Whether the dist needs compiling
needs_android_api_update = False # Whether the dist needs api update
url = None
dist_dir = None # Where the dist dir ultimately is. Should not be None.
ndk_api = None
android_api = None

archs = []
'''The names of the arch targets that the dist is built for.'''
Expand Down Expand Up @@ -183,6 +186,7 @@ def get_distribution(
)
dist.recipes = recipes
dist.ndk_api = ctx.ndk_api
dist.android_api = ctx.android_api
dist.archs = [arch_name]

return dist
Expand Down Expand Up @@ -210,44 +214,113 @@ def get_distributions(cls, ctx, extra_dist_dirs=[]):
if exists(join(folder, 'dist_info.json')):
with open(join(folder, 'dist_info.json')) as fileh:
dist_info = json.load(fileh)
dist = cls(ctx)
dist.name = dist_info['dist_name']
dist.dist_dir = folder
dist.needs_build = False
dist.recipes = dist_info['recipes']
if 'archs' in dist_info:
dist.archs = dist_info['archs']
if 'ndk_api' in dist_info:
dist.ndk_api = dist_info['ndk_api']
else:
dist.ndk_api = None
warning(
"Distribution {distname}: ({distdir}) has been "
"built with an unknown api target, ignoring it, "
"you might want to delete it".format(
distname=dist.name,
distdir=dist.dist_dir
)
)
dists.append(dist)
dists.append(
cls.get_dist_from_dist_info(ctx, dist_info, folder)
)
return dists

def save_info(self, dirn):
@classmethod
def get_dist_from_dist_info(cls, ctx, dist_info, dist_dir):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that you split concerns making a dedicated method for this

"""Initializes a distribution based on ``dist_info.json` info."""
dist = cls(ctx)
dist.needs_build = False
dist.dist_dir = dist_dir

dist.name = dist_info['dist_name']
dist.recipes = dist_info['recipes']
if 'archs' in dist_info:
dist.archs = dist_info['archs']
if 'ndk_api' in dist_info:
dist.ndk_api = dist_info['ndk_api']
else:
dist.ndk_api = None
warning(
f"Distribution {dist.name}: ({dist.dist_dir}) has been "
f"built with an unknown api target, ignoring it, "
f"you might want to delete it"
)

dist.android_api = dist_info.get('android_api', None)
if dist.android_api != ctx.android_api:
dist.needs_android_api_update = True

return dist

@property
def dist_info_file(self):
"""Return the path of the distribution `dist_info.json` file."""
return str(Path(self.dist_dir, 'dist_info.json'))

@property
def dist_info(self):
"""Load the Distribution's `dist_info.json` file."""
with open(self.dist_info_file, 'r') as file_opened:
dist_info = json.load(file_opened)
return dist_info

def save_info(self):
'''
Save information about the distribution in its dist_dir.
'''
with current_directory(dirn):
info('Saving distribution info')
with open('dist_info.json', 'w') as fileh:
json.dump({'dist_name': self.name,
'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
'ndk_api': self.ctx.ndk_api,
'use_setup_py': self.ctx.use_setup_py,
'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
'hostpython': self.ctx.hostpython,
'python_version': self.ctx.python_recipe.major_minor_version_string},
fileh)
info('Saving distribution info')
with open(self.dist_info_file, 'w') as file_opened:
json.dump(
{'dist_name': self.name,
'bootstrap': self.ctx.bootstrap.name,
'archs': [arch.arch for arch in self.ctx.archs],
'ndk_api': self.ctx.ndk_api,
'android_api': self.ctx.android_api,
'use_setup_py': self.ctx.use_setup_py,
'recipes':
self.ctx.recipe_build_order + self.ctx.python_modules,
'hostpython': self.ctx.hostpython,
'python_version':
self.ctx.python_recipe.major_minor_version_string,
},
file_opened,
indent=4,
sort_keys=True,
)

def update_dist_info(self, key, value):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it feels like this method could share some code with save_info(), could it?

"""Update `dist_info.json` file values."""
new_dist_info = self.dist_info.copy()

new_dist_info[key] = value
with open(self.dist_info_file, 'w') as file_opened:
json.dump(new_dist_info, file_opened, indent=4, sort_keys=True)

def update_dist_project_properties_android_api(self, new_android_api):
"""Update `project.properties` with a new android api."""
project_props_file = str(Path(self.dist_dir, 'project.properties'))
with open(project_props_file, 'w') as file_opened:
file_opened.write(f'target=android-{new_android_api}')

def update_dist_android_api(self, new_android_api):
"""Update distribution files with a new android api."""
info(f'Update distribution files for android api {new_android_api}')

# 1) Update hardcoded android api at build.gradle
gradle_file = str(Path(self.dist_dir, 'build.gradle'))
old_android_api = self.android_api
# Read in the file
with open(gradle_file, 'r') as f_gradle:
file_data = f_gradle.read()
# Replace the android API at proper keys
for keyword in {'compileSdkVersion', 'targetSdkVersion'}:
file_data = file_data.replace(
f'{keyword} {old_android_api}', f'{keyword} {new_android_api}'
)
# Write the file out again with the new content
with open(gradle_file, 'w') as new_f_gradle:
new_f_gradle.write(file_data)

# 2) Update hardcoded android api at project.properties
self.update_dist_project_properties_android_api(new_android_api)

# 3) Update cls attributes and `dist_info.json` file
self.android_api = new_android_api
self.update_dist_info('android_api', new_android_api)


def pretty_log_dists(dists, log_func=info):
Expand Down
5 changes: 5 additions & 0 deletions pythonforandroid/toolchain.py
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,11 @@ def wrapper_func(self, args):
info_notify('No dist exists that meets your requirements, '
'so one will be built.')
build_dist_from_args(ctx, dist, args)
if dist.needs_android_api_update:
info_notify(
f'Updating dist with new android api {self.android_api}...'
)
dist.update_dist_android_api(self.android_api)
func(self, args)
return wrapper_func

Expand Down
9 changes: 5 additions & 4 deletions tests/test_bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -370,7 +370,6 @@ def bootstrap_name(self):
@mock.patch("pythonforandroid.bootstraps.service_only.open", create=True)
@mock.patch("pythonforandroid.bootstraps.webview.open", create=True)
@mock.patch("pythonforandroid.bootstraps.sdl2.open", create=True)
@mock.patch("pythonforandroid.distribution.open", create=True)
@mock.patch(
"pythonforandroid.python.GuestPythonRecipe.create_python_bundle"
)
Expand All @@ -389,7 +388,6 @@ def test_run_distribute(
mock_ensure_dir,
mock_strip_libraries,
mock_create_python_bundle,
mock_open_dist_files,
mock_open_sdl2_files,
mock_open_webview_files,
mock_open_service_only_files,
Expand Down Expand Up @@ -423,9 +421,12 @@ def test_run_distribute(
self.ctx.python_modules = ["requests"]
self.ctx.archs = [ArchARMv7_a(self.ctx)]

bs.run_distribute()
with mock.patch(
'pythonforandroid.distribution.Distribution.save_info',
) as mock_open_save_info:
bs.run_distribute()

mock_open_dist_files.assert_called_once_with("dist_info.json", "w")
mock_open_save_info.assert_called()
mock_open_bootstraps = {
"sdl2": mock_open_sdl2_files,
"webview": mock_open_webview_files,
Expand Down
Loading