summaryrefslogtreecommitdiff
path: root/bin/flycheck-android-kotlin.py
diff options
context:
space:
mode:
Diffstat (limited to 'bin/flycheck-android-kotlin.py')
-rw-r--r--bin/flycheck-android-kotlin.py296
1 files changed, 296 insertions, 0 deletions
diff --git a/bin/flycheck-android-kotlin.py b/bin/flycheck-android-kotlin.py
new file mode 100644
index 0000000..ad1d88f
--- /dev/null
+++ b/bin/flycheck-android-kotlin.py
@@ -0,0 +1,296 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import argparse
+import errno
+import os
+import subprocess
+import sys
+import tempfile
+
+def get_gradle_command_and_project_mtime(dir):
+ """Find top-project gradlew or fallback to using gradle on sub-project dir.
+While iterating the paths, also check mtime on build.gradle files"""
+ topdir = dir
+ build_gradle = None
+ gradlew = None
+ mtime = -1
+ while True:
+ test = os.path.join(topdir, 'build.gradle')
+ if os.path.exists(test):
+ if not build_gradle:
+ build_gradle = test
+
+ try:
+ m = os.path.getmtime(test)
+ if m > mtime:
+ mtime = m
+ except OSError:
+ pass
+
+ test = os.path.join(topdir, 'gradlew')
+ if os.path.exists(test):
+ gradlew = test
+ break
+
+ test = os.path.dirname(topdir)
+ if test == topdir:
+ break
+ topdir = test
+
+ if gradlew:
+ cmd = [gradlew, '-p', topdir]
+ elif build_gradle:
+ topdir = os.path.dirname(build_gradle)
+ cmd = ['gradle', '-p', topdir]
+ else:
+ topdir = ''
+ cmd = ['gradle']
+ return (cmd, mtime, topdir)
+
+def filter_source_files(files):
+ """Remove any item not looking like a kotlin or java source file."""
+ return [f for f in files if f.endswith('.kt') or f.endswith('.java')]
+
+def filter_java_source_files(files):
+ """Remove any item not looking like a java source file."""
+ return [f for f in files if f.endswith('.java')]
+
+def cleanup_output(tmp, real):
+ """Remove any file in tmp that is older than the one in real"""
+ offset = len(tmp)
+ if tmp[-1] != '/':
+ offset = offset + 1
+ for (dirpath, _, files) in os.walk(tmp):
+ realpath = os.path.join(real, dirpath[offset:])
+ for filename in files:
+ try:
+ mtime = os.path.getmtime(os.path.join(realpath, filename))
+ tmp = os.path.join(dirpath, filename)
+ if os.path.getmtime(tmp) <= mtime:
+ os.remove(tmp)
+ except OSError:
+ pass
+
+def run_kotlinc(cp, files, output, args, sourcefile, outdir):
+ """Execute kotlinc with the given options."""
+ command = ['kotlinc']
+ if outdir:
+ command.extend(['-d', outdir])
+ tmp = [outdir]
+ if output:
+ tmp.append(output)
+ if cp:
+ tmp.extend(cp)
+ if tmp:
+ command.extend(['-cp', ':'.join(tmp)])
+
+ if os.fork() == 0:
+ cleanup_output(outdir, output)
+ sys.exit(0)
+ else:
+ if cp:
+ command.extend(['-cp', ':'.join(cp)])
+ if output:
+ command.extend(['-d', output])
+ command.extend(args)
+ command.append(sourcefile)
+ print(sourcefile)
+ if outdir:
+ with tempfile.NamedTemporaryFile(mode='w') as f:
+ command.append('@' + f.name)
+ for source in filter_java_source_files(files):
+ f.write(source)
+ f.write('\n')
+ f.flush()
+ return subprocess.call(command)
+ else:
+ with tempfile.NamedTemporaryFile(mode='w') as f:
+ command.append('@' + f.name)
+ for source in filter_source_files(files):
+ f.write(source)
+ f.write('\n')
+ f.flush()
+ return subprocess.call(command)
+
+def file_in_list(needle, files):
+ """Find needle in files and if so, return the list without needle.
+Otherwise return None."""
+ if os.path.exists(needle):
+ for i in range(0, len(files) - 1):
+ if os.path.exists(files[i]) and os.path.samefile(needle, files[i]):
+ return files[0:i] + files[i + 1:]
+ return None
+
+def figure_out_kotlin_compilation(sessiondir, sourcefile, tempfile, detekt,
+ variant, run_gen, force=False):
+ """Get options for Kotlin compilation from gradle project and run kotlinc."""
+ (cmd, mtime, projectdir) = get_gradle_command_and_project_mtime(
+ os.path.dirname(sourcefile))
+ output = None
+ cached = False
+ if sessiondir != None:
+ outdir = os.path.join(sessiondir, 'kotlin_output')
+ try:
+ os.mkdir(outdir)
+ except OSError as exc:
+ if exc.errno != errno.EEXIST or not os.path.isdir(outdir):
+ outdir = None
+ pass
+ cachefile = os.path.join(sessiondir, 'gradle_kotlin_output')
+ try:
+ if not force and os.path.getmtime(cachefile) >= mtime:
+ with open(cachefile, 'r') as f:
+ output = f.read()
+ cached = True
+ except (OSError, IOError):
+ pass
+ else:
+ outdir = None
+
+ if not output:
+ flycheck_cmd = cmd + ['-q', 'flycheckAndroidKotlin']
+ output = subprocess.check_output(flycheck_cmd, universal_newlines=True)
+ if sessiondir != None:
+ try:
+ with open(cachefile, 'w') as f:
+ f.write(output)
+ except IOError:
+ pass
+
+ output = output.split('\n')
+
+ data = None
+ data_type = None
+ variants = []
+ gen = []
+ ret = 0
+ compiled = False
+ for line in output:
+ if line == '***' or line == '!!!':
+ if data_type == '*':
+ variants.append({'data': data, 'gen': len(gen)})
+ elif data_type == '!' and variants:
+ gen.append(data)
+ data_type = line[0]
+ if data_type == '*':
+ data = {}
+ else:
+ data = []
+ elif data_type == '*':
+ if line.startswith('variant='):
+ data['variant'] = line[8:]
+ if line.startswith('args='):
+ data['args'] = line[6:-1].split(', ')
+ elif line.startswith('cp='):
+ data['cp'] = line[3:].split(':')
+ elif line.startswith('files='):
+ data['files'] = line[6:].split(':')
+ elif line.startswith('output='):
+ data['output'] = line[7:]
+ elif data_type == '!':
+ data.append(line)
+
+ if data_type == '*':
+ variants.append({'data': data, 'gen': len(gen)})
+ elif data_type == '!' and variants:
+ gen.append(data)
+
+ fallback = None
+
+ if variant:
+ for v in variants:
+ data = v['data']
+ if 'variant' in data and data['variant'] == variant:
+ if not fallback:
+ fallback = data
+ files = file_in_list(sourcefile, data['files'])
+ if files:
+ if run_gen and v['gen'] < len(gen):
+ subprocess.call(cmd + ['-q'] + gen[v['gen']])
+
+ ret = run_kotlinc(data['cp'], files, data['output'],
+ data['args'], tempfile, outdir)
+ compiled = True
+ break
+
+ if not compiled and variants:
+ data = variants[0]['data']
+ first = data['variant'] if 'variant' in data else None
+ for v in variants:
+ data = v['data']
+ if first and 'variant' in data and data['variant'] == first:
+ if not fallback:
+ fallback = data
+ files = file_in_list(sourcefile, data['files'])
+ if files:
+ if run_gen and v['gen'] < len(gen):
+ subprocess.call(cmd + ['-q'] + gen[v['gen']])
+
+ ret = run_kotlinc(data['cp'], files, data['output'],
+ data['args'], tempfile, outdir)
+ compiled = True
+ break
+
+ if not compiled:
+ # Probably need to rerun gradle to find group for sourcefile
+ if cached and os.path.exists(sourcefile):
+ return figure_out_kotlin_compilation(sessiondir, sourcefile,
+ tempfile, detekt, variant,
+ run_gen, force=True)
+ # OK, perhaps file doesn't exist yet or not yet added to gradle,
+ # whatever, assume the first group is good enough
+ if fallback:
+ ret = run_kotlin(fallback['cp'], fallback['files'],
+ fallback['output'], fallback['args'],
+ tempfile, outdir)
+ compiled = True
+
+ if not ret and detekt:
+ cmd = [detekt['cli']]
+
+ if 'config' in detekt:
+ if detekt['config']:
+ cmd.extend(['-c', detekt['config']])
+ else:
+ cmd.append('--build-upon-default-config')
+
+ cmd.append('-i')
+ cmd.append(tempfile)
+ if len(projectdir):
+ cwd = projectdir
+ else:
+ cwd = None
+ ret = subprocess.call(cmd, cwd=cwd)
+
+ if not ret and not compiled:
+ print("Source file not in project and project seems empty!")
+ ret = -1
+
+ return ret
+
+def main(argv):
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--detekt-cli')
+ parser.add_argument('--detekt-config')
+ parser.add_argument('--variant')
+ parser.add_argument('--skip-gen', dest='gen', action='store_const',
+ const=False, default=True)
+ parser.add_argument('sessiondir', nargs='?')
+ parser.add_argument('sourcefile')
+ parser.add_argument('tempfile', nargs='?')
+ args = parser.parse_args(argv[1:])
+
+ sourcefile = args.sourcefile
+ tempfile = args.tempfile or sourcefile
+
+ detekt = None
+ if args.detekt_cli:
+ detekt = {'cli': args.detekt_cli,
+ 'config': args.detekt_config}
+
+ sourcefile = os.path.abspath(sourcefile)
+ return figure_out_kotlin_compilation(args.sessiondir, sourcefile, tempfile,
+ detekt, args.variant, args.gen)
+
+sys.exit(main(sys.argv))