2017-08-13 03:24:00 +02:00
|
|
|
from os.path import exists, join
|
|
|
|
import glob
|
|
|
|
import json
|
|
|
|
|
2019-03-30 21:58:45 +01:00
|
|
|
from pythonforandroid.logger import (info, info_notify, warning, Err_Style, Err_Fore)
|
|
|
|
from pythonforandroid.util import current_directory, BuildInterruptingException
|
|
|
|
from shutil import rmtree
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
|
2022-12-02 21:15:34 +01:00
|
|
|
class Distribution:
|
2017-08-13 03:24:00 +02:00
|
|
|
'''State container for information about a distribution (i.e. an
|
|
|
|
Android project).
|
|
|
|
|
|
|
|
This is separate from a Bootstrap because the Bootstrap is
|
|
|
|
concerned with building and populating the dist directory, whereas
|
|
|
|
the dist itself could also come from e.g. a binary download.
|
|
|
|
'''
|
|
|
|
ctx = None
|
|
|
|
|
|
|
|
name = None # A name identifying the dist. May not be None.
|
|
|
|
needs_build = False # Whether the dist needs compiling
|
|
|
|
url = None
|
|
|
|
dist_dir = None # Where the dist dir ultimately is. Should not be None.
|
2019-03-30 21:58:45 +01:00
|
|
|
ndk_api = None
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
archs = []
|
2022-12-02 21:15:34 +01:00
|
|
|
'''The names of the arch targets that the dist is built for.'''
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
recipes = []
|
|
|
|
|
|
|
|
description = '' # A long description
|
|
|
|
|
|
|
|
def __init__(self, ctx):
|
|
|
|
self.ctx = ctx
|
|
|
|
|
|
|
|
def __str__(self):
|
|
|
|
return '<Distribution: name {} with recipes ({})>'.format(
|
|
|
|
# self.name, ', '.join([recipe.name for recipe in self.recipes]))
|
|
|
|
self.name, ', '.join(self.recipes))
|
|
|
|
|
|
|
|
def __repr__(self):
|
|
|
|
return str(self)
|
|
|
|
|
|
|
|
@classmethod
|
2022-12-02 21:15:34 +01:00
|
|
|
def get_distribution(
|
|
|
|
cls,
|
|
|
|
ctx,
|
|
|
|
*,
|
|
|
|
archs, # required keyword argument: there is no sensible default
|
|
|
|
name=None,
|
|
|
|
recipes=[],
|
|
|
|
ndk_api=None,
|
|
|
|
force_build=False,
|
|
|
|
extra_dist_dirs=[],
|
|
|
|
require_perfect_match=False,
|
|
|
|
allow_replace_dist=True
|
|
|
|
):
|
2017-08-13 03:24:00 +02:00
|
|
|
'''Takes information about the distribution, and decides what kind of
|
|
|
|
distribution it will be.
|
|
|
|
|
|
|
|
If parameters conflict (e.g. a dist with that name already
|
|
|
|
exists, but doesn't have the right set of recipes),
|
|
|
|
an error is thrown.
|
|
|
|
|
|
|
|
Parameters
|
|
|
|
----------
|
|
|
|
name : str
|
|
|
|
The name of the distribution. If a dist with this name already '
|
|
|
|
exists, it will be used.
|
2022-12-02 21:15:34 +01:00
|
|
|
ndk_api : int
|
|
|
|
The NDK API to compile against, included in the dist because it cannot
|
|
|
|
be changed later during APK packaging.
|
|
|
|
archs : list
|
|
|
|
The target architectures list to compile against, included in the dist because
|
|
|
|
it cannot be changed later during APK packaging.
|
2017-08-13 03:24:00 +02:00
|
|
|
recipes : list
|
|
|
|
The recipes that the distribution must contain.
|
|
|
|
force_download: bool
|
|
|
|
If True, only downloaded dists are considered.
|
|
|
|
force_build : bool
|
|
|
|
If True, the dist is forced to be built locally.
|
|
|
|
extra_dist_dirs : list
|
|
|
|
Any extra directories in which to search for dists.
|
|
|
|
require_perfect_match : bool
|
|
|
|
If True, will only match distributions with precisely the
|
|
|
|
correct set of recipes.
|
2019-03-30 21:58:45 +01:00
|
|
|
allow_replace_dist : bool
|
|
|
|
If True, will allow an existing dist with the specified
|
|
|
|
name but incompatible requirements to be overwritten by
|
|
|
|
a new one with the current requirements.
|
2017-08-13 03:24:00 +02:00
|
|
|
'''
|
|
|
|
|
2022-12-02 21:15:34 +01:00
|
|
|
possible_dists = Distribution.get_distributions(ctx)
|
2017-08-13 03:24:00 +02:00
|
|
|
|
2022-12-02 21:15:34 +01:00
|
|
|
# Will hold dists that would be built in the same folder as an existing dist
|
|
|
|
folder_match_dist = None
|
2017-08-13 03:24:00 +02:00
|
|
|
|
2022-12-02 21:15:34 +01:00
|
|
|
# 0) Check if a dist with that name and architecture already exists
|
2017-08-13 03:24:00 +02:00
|
|
|
if name is not None and name:
|
2022-12-02 21:15:34 +01:00
|
|
|
possible_dists = [
|
|
|
|
d for d in possible_dists if
|
|
|
|
(d.name == name) and all(arch_name in d.archs for arch_name in archs)]
|
|
|
|
|
2019-03-30 21:58:45 +01:00
|
|
|
if possible_dists:
|
2022-12-02 21:15:34 +01:00
|
|
|
# There should only be one folder with a given dist name *and* arch.
|
|
|
|
# We could check that here, but for compatibility let's let it slide
|
|
|
|
# and just record the details of one of them. We only use this data to
|
|
|
|
# possibly fail the build later, so it doesn't really matter if there
|
|
|
|
# was more than one clash.
|
|
|
|
folder_match_dist = possible_dists[0]
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
# 1) Check if any existing dists meet the requirements
|
|
|
|
_possible_dists = []
|
|
|
|
for dist in possible_dists:
|
2019-03-30 21:58:45 +01:00
|
|
|
if (
|
|
|
|
ndk_api is not None and dist.ndk_api != ndk_api
|
|
|
|
) or dist.ndk_api is None:
|
|
|
|
continue
|
2017-08-13 03:24:00 +02:00
|
|
|
for recipe in recipes:
|
|
|
|
if recipe not in dist.recipes:
|
|
|
|
break
|
|
|
|
else:
|
|
|
|
_possible_dists.append(dist)
|
|
|
|
possible_dists = _possible_dists
|
|
|
|
|
|
|
|
if possible_dists:
|
|
|
|
info('Of the existing distributions, the following meet '
|
|
|
|
'the given requirements:')
|
|
|
|
pretty_log_dists(possible_dists)
|
|
|
|
else:
|
|
|
|
info('No existing dists meet the given requirements!')
|
|
|
|
|
2022-12-02 21:15:34 +01:00
|
|
|
# If any dist has perfect recipes, arch and NDK API, return it
|
2017-08-13 03:24:00 +02:00
|
|
|
for dist in possible_dists:
|
|
|
|
if force_build:
|
|
|
|
continue
|
2019-03-30 21:58:45 +01:00
|
|
|
if ndk_api is not None and dist.ndk_api != ndk_api:
|
|
|
|
continue
|
2022-12-02 21:15:34 +01:00
|
|
|
if not all(arch_name in dist.archs for arch_name in archs):
|
|
|
|
continue
|
2017-08-13 03:24:00 +02:00
|
|
|
if (set(dist.recipes) == set(recipes) or
|
|
|
|
(set(recipes).issubset(set(dist.recipes)) and
|
|
|
|
not require_perfect_match)):
|
|
|
|
info_notify('{} has compatible recipes, using this one'
|
|
|
|
.format(dist.name))
|
|
|
|
return dist
|
|
|
|
|
2019-03-30 21:58:45 +01:00
|
|
|
# If there was a name match but we didn't already choose it,
|
|
|
|
# then the existing dist is incompatible with the requested
|
|
|
|
# configuration and the build cannot continue
|
2022-12-02 21:15:34 +01:00
|
|
|
if folder_match_dist is not None and not allow_replace_dist:
|
2019-03-30 21:58:45 +01:00
|
|
|
raise BuildInterruptingException(
|
|
|
|
'Asked for dist with name {name} with recipes ({req_recipes}) and '
|
|
|
|
'NDK API {req_ndk_api}, but a dist '
|
|
|
|
'with this name already exists and has either incompatible recipes '
|
|
|
|
'({dist_recipes}) or NDK API {dist_ndk_api}'.format(
|
|
|
|
name=name,
|
|
|
|
req_ndk_api=ndk_api,
|
2022-12-02 21:15:34 +01:00
|
|
|
dist_ndk_api=folder_match_dist.ndk_api,
|
2019-03-30 21:58:45 +01:00
|
|
|
req_recipes=', '.join(recipes),
|
2022-12-02 21:15:34 +01:00
|
|
|
dist_recipes=', '.join(folder_match_dist.recipes)))
|
|
|
|
|
|
|
|
assert len(possible_dists) < 2
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
# If we got this far, we need to build a new dist
|
|
|
|
dist = Distribution(ctx)
|
|
|
|
dist.needs_build = True
|
|
|
|
|
|
|
|
if not name:
|
|
|
|
filen = 'unnamed_dist_{}'
|
|
|
|
i = 1
|
|
|
|
while exists(join(ctx.dist_dir, filen.format(i))):
|
|
|
|
i += 1
|
|
|
|
name = filen.format(i)
|
|
|
|
|
|
|
|
dist.name = name
|
2022-12-02 21:15:34 +01:00
|
|
|
dist.dist_dir = join(
|
|
|
|
ctx.dist_dir,
|
|
|
|
name)
|
2017-08-13 03:24:00 +02:00
|
|
|
dist.recipes = recipes
|
2019-03-30 21:58:45 +01:00
|
|
|
dist.ndk_api = ctx.ndk_api
|
2022-12-02 21:15:34 +01:00
|
|
|
dist.archs = archs
|
2017-08-13 03:24:00 +02:00
|
|
|
|
|
|
|
return dist
|
|
|
|
|
2019-03-30 21:58:45 +01:00
|
|
|
def folder_exists(self):
|
|
|
|
return exists(self.dist_dir)
|
|
|
|
|
|
|
|
def delete(self):
|
|
|
|
rmtree(self.dist_dir)
|
|
|
|
|
2017-08-13 03:24:00 +02:00
|
|
|
@classmethod
|
|
|
|
def get_distributions(cls, ctx, extra_dist_dirs=[]):
|
|
|
|
'''Returns all the distributions found locally.'''
|
|
|
|
if extra_dist_dirs:
|
2019-03-30 21:58:45 +01:00
|
|
|
raise BuildInterruptingException(
|
|
|
|
'extra_dist_dirs argument to get_distributions '
|
|
|
|
'is not yet implemented')
|
2017-08-13 03:24:00 +02:00
|
|
|
dist_dir = ctx.dist_dir
|
|
|
|
folders = glob.glob(join(dist_dir, '*'))
|
|
|
|
for dir in extra_dist_dirs:
|
|
|
|
folders.extend(glob.glob(join(dir, '*')))
|
|
|
|
|
|
|
|
dists = []
|
|
|
|
for folder in folders:
|
|
|
|
if exists(join(folder, 'dist_info.json')):
|
|
|
|
with open(join(folder, 'dist_info.json')) as fileh:
|
|
|
|
dist_info = json.load(fileh)
|
|
|
|
dist = cls(ctx)
|
2022-12-02 21:15:34 +01:00
|
|
|
dist.name = dist_info['dist_name']
|
2017-08-13 03:24:00 +02:00
|
|
|
dist.dist_dir = folder
|
|
|
|
dist.needs_build = False
|
|
|
|
dist.recipes = dist_info['recipes']
|
|
|
|
if 'archs' in dist_info:
|
|
|
|
dist.archs = dist_info['archs']
|
2019-03-30 21:58:45 +01:00
|
|
|
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
|
|
|
|
)
|
|
|
|
)
|
2017-08-13 03:24:00 +02:00
|
|
|
dists.append(dist)
|
|
|
|
return dists
|
|
|
|
|
2019-03-30 21:58:45 +01:00
|
|
|
def save_info(self, dirn):
|
2017-08-13 03:24:00 +02:00
|
|
|
'''
|
|
|
|
Save information about the distribution in its dist_dir.
|
|
|
|
'''
|
2019-03-30 21:58:45 +01:00
|
|
|
with current_directory(dirn):
|
2017-08-13 03:24:00 +02:00
|
|
|
info('Saving distribution info')
|
|
|
|
with open('dist_info.json', 'w') as fileh:
|
2022-12-02 21:15:34 +01:00
|
|
|
json.dump({'dist_name': self.name,
|
2019-03-30 21:58:45 +01:00
|
|
|
'bootstrap': self.ctx.bootstrap.name,
|
2017-08-13 03:24:00 +02:00
|
|
|
'archs': [arch.arch for arch in self.ctx.archs],
|
2019-03-30 21:58:45 +01:00
|
|
|
'ndk_api': self.ctx.ndk_api,
|
2022-12-02 21:15:34 +01:00
|
|
|
'use_setup_py': self.ctx.use_setup_py,
|
2019-03-30 21:58:45 +01:00
|
|
|
'recipes': self.ctx.recipe_build_order + self.ctx.python_modules,
|
|
|
|
'hostpython': self.ctx.hostpython,
|
|
|
|
'python_version': self.ctx.python_recipe.major_minor_version_string},
|
2017-08-13 03:24:00 +02:00
|
|
|
fileh)
|
|
|
|
|
|
|
|
|
|
|
|
def pretty_log_dists(dists, log_func=info):
|
|
|
|
infos = []
|
|
|
|
for dist in dists:
|
2019-03-30 21:58:45 +01:00
|
|
|
ndk_api = 'unknown' if dist.ndk_api is None else dist.ndk_api
|
|
|
|
infos.append('{Fore.GREEN}{Style.BRIGHT}{name}{Style.RESET_ALL}: min API {ndk_api}, '
|
2017-08-13 03:24:00 +02:00
|
|
|
'includes recipes ({Fore.GREEN}{recipes}'
|
|
|
|
'{Style.RESET_ALL}), built for archs ({Fore.BLUE}'
|
|
|
|
'{archs}{Style.RESET_ALL})'.format(
|
2019-03-30 21:58:45 +01:00
|
|
|
ndk_api=ndk_api,
|
2017-08-13 03:24:00 +02:00
|
|
|
name=dist.name, recipes=', '.join(dist.recipes),
|
|
|
|
archs=', '.join(dist.archs) if dist.archs else 'UNKNOWN',
|
|
|
|
Fore=Err_Fore, Style=Err_Style))
|
|
|
|
|
|
|
|
for line in infos:
|
|
|
|
log_func('\t' + line)
|