import threading
import magic
import logging
+from distutils.version import LooseVersion
import metadata
def get_default_config():
return {
'sdk_path': os.getenv("ANDROID_HOME"),
- 'ndk_path': "$ANDROID_NDK",
- 'build_tools': "19.0.3",
+ 'ndk_path': os.getenv("ANDROID_NDK"),
+ 'build_tools': "19.1.0",
'ant': "ant",
'mvn3': "mvn",
'gradle': 'gradle',
'stats_to_carbon': False,
'repo_maxage': 0,
'build_server_always': False,
- 'keystore': '$HOME/.local/share/fdroidserver/keystore.jks',
+ 'keystore': os.path.join(os.getenv("HOME"), '.local', 'share', 'fdroidserver', 'keystore.jks'),
'smartcardoptions': [],
'char_limits': {
'Summary': 50,
'sun.security.pkcs11.SunPKCS11',
'-providerArg', 'opensc-fdroid.cfg']
+ if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
+ st = os.stat(config_file)
+ if st.st_mode & stat.S_IRWXG or st.st_mode & stat.S_IRWXO:
+ logging.warn("unsafe permissions on {0} (should be 0600)!".format(config_file))
+
defconfig = get_default_config()
for k, v in defconfig.items():
if k not in config:
if not test_sdk_exists(config):
sys.exit(3)
- if any(k in config for k in ["keystore", "keystorepass", "keypass"]):
- st = os.stat(config_file)
- if st.st_mode & stat.S_IRWXG or st.st_mode & stat.S_IRWXO:
- logging.warn("unsafe permissions on {0} (should be 0600)!".format(config_file))
-
for k in ["keystorepass", "keypass"]:
if k in config:
write_password_file(k)
if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools')):
logging.critical('Android SDK path "' + c['sdk_path'] + '" does not contain "build-tools/"!')
return False
+ if not os.path.isdir(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'])):
+ logging.critical('Configured build-tools version "' + c['build_tools'] + '" not found in the SDK!')
+ return False
+ return True
+
+
+def test_build_tools_exists(c):
+ if not test_sdk_exists(c):
+ return False
+ build_tools = os.path.join(c['sdk_path'], 'build-tools')
+ versioned_build_tools = os.path.join(build_tools, c['build_tools'])
+ if not os.path.isdir(versioned_build_tools):
+ logging.critical('Android Build Tools path "'
+ + versioned_build_tools + '" does not exist!')
+ return False
+ if not os.path.exists(os.path.join(c['sdk_path'], 'build-tools', c['build_tools'], 'aapt')):
+ logging.critical('Android Build Tools "'
+ + versioned_build_tools
+ + '" does not contain "aapt"!')
+ return False
return True
def getsrclibvcs(name):
- srclib_path = os.path.join('srclibs', name + ".txt")
- if not os.path.exists(srclib_path):
+ if name not in metadata.srclibs:
raise VCSException("Missing srclib " + name)
- return metadata.parse_srclib(srclib_path)['Repo Type']
+ return metadata.srclibs[name]['Repo Type']
class vcs:
def latesttags(self, alltags, number):
self.checkrepo()
- p = SilentPopen(['echo "'+'\n'.join(alltags)+'" | \
+ p = SilentPopen(['echo "' + '\n'.join(alltags) + '" | \
xargs -I@ git log --format=format:"%at @%n" -1 @ | \
sort -n | awk \'{print $2}\''],
cwd=self.local, shell=True)
p = SilentPopen(['hg', 'purge', '--all'], cwd=self.local)
# Also delete untracked files, we have to enable purge extension for that:
if "'purge' is provided by the following extension" in p.stdout:
- with open(self.local+"/.hg/hgrc", "a") as myfile:
+ with open(self.local + "/.hg/hgrc", "a") as myfile:
myfile.write("\n[extensions]\nhgext.purge=\n")
p = SilentPopen(['hg', 'purge', '--all'], cwd=self.local)
if p.returncode != 0:
string_search = None
if string.startswith('@string/'):
- string_search = re.compile(r'.*"'+string[8:]+'".*?>([^<]+?)<.*').search
+ string_search = re.compile(r'.*name="' + string[8:] + '".*?>([^<]+?)<.*').search
elif string.startswith('&') and string.endswith(';'):
- string_search = re.compile(r'.*<!ENTITY.*'+string[1:-1]+'.*?"([^"]+?)".*>').search
+ string_search = re.compile(r'.*<!ENTITY.*' + string[1:-1] + '.*?"([^"]+?)".*>').search
if string_search is not None:
for xmlfile in xmlfiles:
# Extract some information from the AndroidManifest.xml at the given path.
# Returns (version, vercode, package), any or all of which might be None.
# All values returned are strings.
-def parse_androidmanifests(paths):
+def parse_androidmanifests(paths, ignoreversions=None):
if not paths:
return (None, None, None)
vnsearch_g = re.compile(r'.*versionName *=* *(["\'])((?:(?=(\\?))\3.)*?)\1.*').search
psearch_g = re.compile(r'.*packageName *=* *["\']([^"]+)["\'].*').search
+ ignoresearch = re.compile(ignoreversions).search if ignoreversions else None
+
max_version = None
max_vercode = None
max_package = None
if matches:
vercode = matches.group(1)
- # Better some package name than nothing
- if max_package is None:
+ # Always grab the package name and version name in case they are not
+ # together with the highest version code
+ if max_package is None and package is not None:
max_package = package
+ if max_version is None and version is not None:
+ max_version = version
if max_vercode is None or (vercode is not None and vercode > max_vercode):
- max_version = version
- max_vercode = vercode
- max_package = package
+ if not ignoresearch or not ignoresearch(version):
+ if version is not None:
+ max_version = version
+ if vercode is not None:
+ max_vercode = vercode
+ if package is not None:
+ max_package = package
+ else:
+ max_version = "Ignore"
if max_version is None:
max_version = "Unknown"
return ret
def __str__(self):
- ret = repr(self.value)
+ ret = self.value
if self.detail:
ret += "\n==== detail begin ====\n%s\n==== detail end ====" % self.detail.strip()
return ret
self.value = value
def __str__(self):
- return repr(self.value)
+ return self.value
# Get the specified source library.
if '/' in name:
name, subdir = name.split('/', 1)
- srclib_path = os.path.join('srclibs', name + ".txt")
-
- if not os.path.exists(srclib_path):
+ if name not in metadata.srclibs:
raise BuildException('srclib ' + name + ' not found.')
- srclib = metadata.parse_srclib(srclib_path)
+ srclib = metadata.srclibs[name]
sdir = os.path.join(srclib_dir, name)
def prepare_source(vcs, app, build, build_dir, srclib_dir, extlib_dir, onserver=False):
# Optionally, the actual app source can be in a subdirectory
- if 'subdir' in build:
+ if build['subdir']:
root_dir = os.path.join(build_dir, build['subdir'])
else:
root_dir = build_dir
raise BuildException('Missing subdir ' + root_dir)
# Run an init command if one is required
- if 'init' in build:
+ if build['init']:
cmd = replace_config_vars(build['init'])
logging.info("Running 'init' commands in %s" % root_dir)
(app['id'], build['version']), p.stdout)
# Apply patches if any
- if 'patch' in build:
+ if build['patch']:
+ logging.info("Applying patches")
for patch in build['patch']:
patch = patch.strip()
logging.info("Applying " + patch)
# Get required source libraries
srclibpaths = []
- if 'srclibs' in build:
+ if build['srclibs']:
logging.info("Collecting source libraries")
for lib in build['srclibs']:
srclibpaths.append(getsrclib(lib, srclib_dir, srclibpaths,
# Update the local.properties file
localprops = [os.path.join(build_dir, 'local.properties')]
- if 'subdir' in build:
+ if build['subdir']:
localprops += [os.path.join(root_dir, 'local.properties')]
for path in localprops:
if not os.path.isfile(path):
props += "ndk.dir=%s\n" % config['ndk_path']
props += "ndk-location=%s\n" % config['ndk_path']
# Add java.encoding if necessary
- if 'encoding' in build:
+ if build['encoding']:
props += "java.encoding=%s\n" % build['encoding']
f = open(path, 'w')
f.write(props)
if flavour in ['main', 'yes', '']:
flavour = None
- if 'target' in build:
+ version_regex = re.compile(r".*'com\.android\.tools\.build:gradle:([^\.]+\.[^\.]+).*'.*")
+ gradlepluginver = None
+
+ gradle_files = [os.path.join(root_dir, 'build.gradle')]
+
+ # Parent dir build.gradle
+ parent_dir = os.path.normpath(os.path.join(root_dir, '..'))
+ if parent_dir.startswith(build_dir):
+ gradle_files.append(os.path.join(parent_dir, 'build.gradle'))
+
+ # Gradle execution dir build.gradle
+ if '@' in build['gradle']:
+ gradle_file = os.path.join(root_dir, build['gradle'].split('@', 1)[1], 'build.gradle')
+ gradle_file = os.path.normpath(gradle_file)
+ if gradle_file not in gradle_files:
+ gradle_files.append(gradle_file)
+
+ for path in gradle_files:
+ if gradlepluginver:
+ break
+ if not os.path.isfile(path):
+ continue
+ with open(path) as f:
+ for line in f:
+ match = version_regex.match(line)
+ if match:
+ gradlepluginver = match.group(1)
+ break
+
+ if gradlepluginver:
+ build['gradlepluginver'] = LooseVersion(gradlepluginver)
+ else:
+ logging.warn("Could not fetch the gradle plugin version, defaulting to 0.11")
+ build['gradlepluginver'] = LooseVersion('0.11')
+
+ if build['target']:
n = build["target"].split('-')[1]
FDroidPopen(['sed', '-i',
- 's@compileSdkVersion *[0-9]*@compileSdkVersion '+n+'@g',
+ 's@compileSdkVersion *[0-9]*@compileSdkVersion ' + n + '@g',
'build.gradle'],
cwd=root_dir)
if '@' in build['gradle']:
gradle_dir = os.path.join(root_dir, build['gradle'].split('@', 1)[1])
gradle_dir = os.path.normpath(gradle_dir)
FDroidPopen(['sed', '-i',
- 's@compileSdkVersion *[0-9]*@compileSdkVersion '+n+'@g',
+ 's@compileSdkVersion *[0-9]*@compileSdkVersion ' + n + '@g',
'build.gradle'],
cwd=gradle_dir)
raise BuildException("Failed to amend build.gradle")
# Delete unwanted files
- if 'rm' in build:
+ if build['rm']:
+ logging.info("Removing specified files")
for part in getpaths(build_dir, build, 'rm'):
dest = os.path.join(build_dir, part)
logging.info("Removing {0}".format(part))
remove_signing_keys(build_dir)
# Add required external libraries
- if 'extlibs' in build:
+ if build['extlibs']:
logging.info("Collecting prebuilt libraries")
libsdir = os.path.join(root_dir, 'libs')
if not os.path.exists(libsdir):
shutil.copyfile(libsrc, os.path.join(libsdir, libf))
# Run a pre-build command if one is required
- if 'prebuild' in build:
+ if build['prebuild']:
+ logging.info("Running 'prebuild' commands in %s" % root_dir)
+
cmd = replace_config_vars(build['prebuild'])
# Substitute source library paths into prebuild commands
libpath = os.path.relpath(libpath, root_dir)
cmd = cmd.replace('$$' + name + '$$', libpath)
- logging.info("Running 'prebuild' commands in %s" % root_dir)
-
p = FDroidPopen(['bash', '-x', '-c', cmd], cwd=root_dir)
if p.returncode != 0:
raise BuildException("Error running prebuild command for %s:%s" %
(app['id'], build['version']), p.stdout)
- updatemode = build.get('update', ['auto'])
# Generate (or update) the ant build file, build.xml...
- if updatemode != ['no'] and build['type'] == 'ant':
+ if build['update'] and build['update'] != ['no'] and build['type'] == 'ant':
parms = [os.path.join(config['sdk_path'], 'tools', 'android'), 'update']
lparms = parms + ['lib-project']
parms = parms + ['project']
- if 'target' in build and build['target']:
+ if build['target']:
parms += ['-t', build['target']]
lparms += ['-t', build['target']]
- if updatemode == ['auto']:
+ if build['update'] == ['auto']:
update_dirs = ant_subprojects(root_dir) + ['.']
else:
- update_dirs = updatemode
+ update_dirs = build['update']
for d in update_dirs:
subdir = os.path.join(root_dir, d)
# Split and extend via globbing the paths from a field
def getpaths(build_dir, build, field):
paths = []
- if field not in build:
- return paths
for p in build[field]:
p = p.strip()
full_path = os.path.join(build_dir, p)
full_path = os.path.normpath(full_path)
- paths += [r[len(build_dir)+1:] for r in glob.glob(full_path)]
+ paths += [r[len(build_dir) + 1:] for r in glob.glob(full_path)]
return paths
re.compile(r'bugsense', re.IGNORECASE),
re.compile(r'crashlytics', re.IGNORECASE),
re.compile(r'ouya.*sdk', re.IGNORECASE),
+ re.compile(r'libspen23', re.IGNORECASE),
]
scanignore = getpaths(build_dir, thisbuild, 'scanignore')
# Path (relative) to the file
fp = os.path.join(r, curfile)
- fd = fp[len(build_dir)+1:]
+ fd = fp[len(build_dir) + 1:]
# Check if this file has been explicitly excluded from scanning
if toignore(fd):
# indicate a problem (if it's not a problem, explicitly use
# buildjni=no to bypass this check)
if (os.path.exists(os.path.join(root_dir, 'jni')) and
- thisbuild.get('buildjni') is None):
- logging.warn('Found jni directory, but buildjni is not enabled')
+ not thisbuild['buildjni']):
+ logging.error('Found jni directory, but buildjni is not enabled')
count += 1
return count
# Record an apk (if it's new, otherwise does nothing)
# Returns the date it was added.
def recordapk(self, apk, app):
- if not apk in self.apks:
+ if apk not in self.apks:
self.apks[apk] = (app, time.gmtime(time.time()))
self.changed = True
_, added = self.apks[apk]
while not stdout_reader.eof():
while not stdout_queue.empty():
line = stdout_queue.get()
- if output and options.verbose:
+ if output:
# Output directly to console
sys.stdout.write(line)
sys.stdout.flush()
signing_configs = re.compile(r'^[\t ]*signingConfigs[ \t]*{[ \t]*$')
line_matches = [
re.compile(r'^[\t ]*signingConfig [^ ]*$'),
- re.compile(r'.*android\.signingConfigs\..*'),
+ re.compile(r'.*android\.signingConfigs\.[^{]*$'),
re.compile(r'.*variant\.outputFile = .*'),
re.compile(r'.*\.readLine\(.*'),
]
with open(path, "r") as o:
lines = o.readlines()
+ changed = False
+
opened = 0
with open(path, "w") as o:
for line in lines:
continue
if signing_configs.match(line):
+ changed = True
opened += 1
continue
if any(s.match(line) for s in line_matches):
+ changed = True
continue
if opened == 0:
o.write(line)
- logging.info("Cleaned build.gradle of keysigning configs at %s" % path)
+ if changed:
+ logging.info("Cleaned build.gradle of keysigning configs at %s" % path)
for propfile in [
'project.properties',
with open(path, "r") as o:
lines = o.readlines()
+ changed = False
+
with open(path, "w") as o:
for line in lines:
- if line.startswith('key.store'):
- continue
- if line.startswith('key.alias'):
+ if any(line.startswith(s) for s in ('key.store', 'key.alias')):
+ changed = True
continue
+
o.write(line)
- logging.info("Cleaned %s of keysigning configs at %s" % (propfile, path))
+ if changed:
+ logging.info("Cleaned %s of keysigning configs at %s" % (propfile, path))
def replace_config_vars(cmd):