#!/usr/bin/python # -*- coding: utf-8 -*- import argparse import errno import os import subprocess import sys import tempfile import time 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 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_javac(encoding, source, target, bootcp, cp, files, output, args, sourcefile, outdir): """Execute javac with the given options.""" command = ['javac'] if encoding: command.extend(['-encoding', encoding]) if source: command.extend(['-source', source]) if target: command.extend(['-target', target]) if bootcp: command.extend(['-bootclasspath', bootcp]) 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) if outdir: 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.samefile(needle, files[i]): return files[0:i] + files[i + 1:] return None def figure_out_java_compilation(sessiondir, sourcefile, tempfile, checkstyle, force=False): """Get options for Java compilation from gradle project and run javac.""" (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, 'java_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_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: cmd.extend(['-q', 'flycheckAndroidJava']) output = subprocess.check_output(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 first = None ret = 0 compiled = False for line in output: if line == '***': if data: if not first: first = data files = file_in_list(sourcefile, data['files']) if files: ret = run_javac(data['encoding'], data['source'], data['target'], data['bootcp'], data['cp'], files, data['output'], data['args'], tempfile, outdir) compiled = True break data = {} elif data != None: if line.startswith('args='): data['args'] = line[6:-1].split(', ') elif line.startswith('encoding='): data['encoding'] = line[9:] elif line.startswith('bootcp='): data['bootcp'] = line[7:] elif line.startswith('cp='): data['cp'] = line[3:].split(':') elif line.startswith('source='): data['source'] = line[7:] elif line.startswith('target='): data['target'] = line[7:] elif line.startswith('files='): data['files'] = line[6:].split(':') elif line.startswith('output='): data['output'] = line[7:] if not compiled and data: if not first: first = data files = file_in_list(sourcefile, data['files']) if files: ret = run_javac(data['encoding'], data['source'], data['target'], data['bootcp'], data['cp'], files, data['output'], data['args'], tempfile, outdir) compiled = True if not compiled: # Probably need to rerun gradle to find group for sourcefile if cached and os.path.exists(sourcefile): return figure_out_java_compilation(sessiondir, sourcefile, tempfile, checkstyle, force=True) # OK, perhaps file doesn't exist yet or not yet added to gradle, # whatever, assume the first group is good enough if first: ret = run_javac(first['encoding'], first['source'], first['target'], first['bootcp'], first['cp'], first['files'], first['output'], first['args'], tempfile, outdir) compiled = True if not ret and checkstyle: cmd = ['java', '-jar', os.path.abspath(os.path.join(projectdir, checkstyle['jar']))] if 'config' in checkstyle: cmd.extend(['-c', checkstyle['config']]) if 'properties' in checkstyle: cmd.extend(['-p', checkstyle['properties']]) cmd.append(tempfile) if checkstyle['path']: cwd = os.path.join(projectdir, checkstyle['path']) elif 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('--checkstyle-jar') parser.add_argument('--checkstyle-path') parser.add_argument('--checkstyle-config') parser.add_argument('--checkstyle-properties') 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 checkstyle = None if args.checkstyle_jar: checkstyle = {'jar': args.checkstyle_jar, 'path': args.checkstyle_path, 'config': args.checkstyle_config, 'properties': args.checkstyle_properties} sourcefile = os.path.abspath(sourcefile) return figure_out_java_compilation(args.sessiondir, sourcefile, tempfile, checkstyle) sys.exit(main(sys.argv))