1#!/usr/bin/env python3 2 3# 4# Copyright 2022, The Android Open Source Project 5# 6# Licensed under the Apache License, Version 2.0 (the "License"); 7# you may not use this file except in compliance with the License. 8# You may obtain a copy of the License at 9# 10# http://www.apache.org/licenses/LICENSE-2.0 11# 12# Unless required by applicable law or agreed to in writing, software 13# distributed under the License is distributed on an "AS IS" BASIS, 14# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15# See the License for the specific language governing permissions and 16# limitations under the License. 17# 18"""Script to format or check Kotlin files.""" 19 20import argparse 21import os 22import subprocess 23import sys 24 25 26def main(): 27 parser = argparse.ArgumentParser( 28 'Format Kotlin files or check that they are correctly formatted.') 29 parser.add_argument( 30 '--check', 31 '-c', 32 action='store_true', 33 default=False, 34 help='Perform a format check instead of formatting.') 35 parser.add_argument( 36 '--includes_file', 37 '-i', 38 default='', 39 help='The file containing the Kotlin files and directories that should be included/excluded, generated using generate_includes_file.py.' 40 ) 41 parser.add_argument( 42 '--jar', 43 default='', 44 help='The path to the ktfmt jar.' 45 ) 46 parser.add_argument( 47 'files', 48 nargs='*', 49 help='The files to format or check. If --include_file is specified, only the files at their intersection will be formatted/checked.' 50 ) 51 args = parser.parse_args() 52 53 ktfmt_args = ['--kotlinlang-style'] 54 55 check = args.check 56 if check: 57 ktfmt_args += ['--set-exit-if-changed', '--dry-run'] 58 59 kt_files = [] 60 for file in args.files: 61 if os.path.isdir(file): 62 for root, dirs, files in os.walk(file): 63 for f in files: 64 if is_kotlin_file(f): 65 kt_files += [os.path.join(root, f)] 66 67 if is_kotlin_file(file): 68 kt_files += [file] 69 70 # Only format/check files from the includes list. 71 includes_file = args.includes_file 72 if kt_files and includes_file: 73 f = open(includes_file, 'r') 74 75 lines = f.read().splitlines() 76 included = [line[1:] for line in lines if line.startswith('+')] 77 included_files = set() 78 included_dirs = [] 79 for line in included: 80 if is_kotlin_file(line): 81 included_files.add(line) 82 else: 83 included_dirs += [line] 84 85 excluded_files = [line[1:] for line in lines if line.startswith('-')] 86 87 kt_files = [ 88 kt_file for kt_file in kt_files if kt_file not in excluded_files and 89 (kt_file in included_files or is_included(kt_file, included_dirs)) 90 ] 91 92 # No need to start ktfmt if there are no files to check/format. 93 if not kt_files: 94 sys.exit(0) 95 96 ktfmt_args += kt_files 97 98 dir = os.path.normpath(os.path.dirname(__file__)) 99 ktfmt_jar = args.jar if args.jar else os.path.join(dir, 'ktfmt.jar') 100 101 ktlint_env = os.environ.copy() 102 ktlint_env['JAVA_CMD'] = 'java' 103 try: 104 process = subprocess.Popen( 105 ['java', '-jar', ktfmt_jar] + ktfmt_args, 106 stdout=subprocess.PIPE, 107 env=ktlint_env) 108 stdout, _ = process.communicate() 109 code = process.returncode 110 if check and code != 0: 111 print( 112 '**********************************************************************' 113 ) 114 print( 115 'Some Kotlin files are not properly formatted. Run the command below to format them.\n' 116 'Note: If you are using the Android Studio ktfmt plugin, make sure to select the ' 117 'Kotlinlang style in \'Editor > ktfmt Settings\'.\n') 118 script_path = os.path.normpath(__file__) 119 incorrect_files = [ 120 os.path.abspath(file) for file in stdout.decode('utf-8').splitlines() 121 ] 122 print('$ ' + script_path + ' ' + ' '.join(incorrect_files) + '\n') 123 print( 124 '**********************************************************************' 125 ) 126 sys.exit(code) 127 else: 128 sys.exit(0) 129 except OSError as e: 130 print('Error running ktfmt') 131 sys.exit(1) 132 133 134def is_kotlin_file(name): 135 return name.endswith('.kt') or name.endswith('.kts') 136 137 138def is_included(file, dirs): 139 for dir in dirs: 140 if file.startswith(dir): 141 return True 142 143 144if __name__ == '__main__': 145 main() 146