1#!/usr/bin/env bash 2 3# Copyright (C) 2020 The Android Open Source Project 4# 5# Licensed under the Apache License, Version 2.0 (the "License"); 6# you may not use this file except in compliance with the License. 7# You may obtain a copy of the License at 8# 9# http://www.apache.org/licenses/LICENSE-2.0 10# 11# Unless required by applicable law or agreed to in writing, software 12# distributed under the License is distributed on an "AS IS" BASIS, 13# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14# See the License for the specific language governing permissions and 15# limitations under the License. 16 17set -e 18 19clang_format=clang-format 20 21# future considerations: 22# - could we make this work with git-clang-format instead? 23# - should we have our own formatter? 24 25function _aidl-format() ( 26 # Find .aidl-format file to use. The file is located in one of the parent 27 # directories of the source file 28 function find-aidl-format-style() { 29 local path="$1" 30 while [[ "$path" != / ]]; 31 do 32 if find "$path" -maxdepth 1 -mindepth 1 -name .aidl-format | grep "."; then 33 return 34 fi 35 path="$(readlink -f "$path"/..)" 36 done 37 } 38 39 # Do a "reversible" conversion of the input file so that it is more friendly 40 # to clang-format. For example 'oneway interface Foo{}' is not recognized as 41 # an interface. Convert it to 'interface __aidl_oneway__ Foo{}'. 42 function prepare() { 43 # oneway interface Foo {} is not correctly recognized as an interface by 44 # clang-format. Change it to interface __aidl_oneway__ Foo {}. 45 sed -i -E 's/oneway[[:space:]]+interface/interface\ __aidl_oneway__/g' "$1" 46 47 # When a declaration becomes too long, clang-format splits the declaration 48 # into multiple lines. In doing so, annotations that are at the front of 49 # the declaration are always split. i.e. 50 # 51 # @utf8InCpp @nullable void foo(int looooooo....ong, int looo....ong); 52 # 53 # becomes 54 # 55 # @utf8InCpp 56 # @nullable 57 # void foo(int loooooo...ong, 58 # int looo.....ong); 59 # 60 # This isn't desirable for utf8InCpp and nullable annotations which are 61 # semantically tagged to the type, not the member (field/method). We want 62 # to have the annotations in the same line as the type that they actually 63 # annotate. i.e. 64 # 65 # @utf8InCpp @nullable void foo(int looo....ong, 66 # int looo.....ong); 67 # 68 # To do so, the annotations are temporarily replaced with tokens that are 69 # not annotations. 70 sed -i -E 's/@utf8InCpp/__aidl_utf8inCpp__/g' "$1" 71 sed -i -E 's/@nullable/__aidl_nullable__/g' "$1" 72 } 73 74 function apply-clang-format() { 75 local input="$1" 76 local style="$2" 77 local temp="$(mktemp)" 78 local styletext="$([ -f "$style" ] && cat "$style" | tr '\n' ',' 2> /dev/null)" 79 cat "$input" | $clang_format \ 80 --style='{BasedOnStyle: Google, 81 ColumnLimit: 100, 82 IndentWidth: 4, 83 ContinuationIndentWidth: 8, '"${styletext}"'}' \ 84 --assume-filename=${input%.*}.java \ 85 > "$temp" 86 mv "$temp" "$input" 87 } 88 89 # clang-format is good, but doesn't perfectly fit to our needs. Fix the 90 # minor mismatches manually. 91 function fixup() { 92 # Revert the changes done during the prepare call. Notice that the 93 # original tokens (@utf8InCpp, etc.) are shorter than the temporary tokens 94 # (__aidl_utf8InCpp, etc.). This can make the output text length shorter 95 # than the specified column limit. We can try to reduce the undesirable 96 # effect by keeping the tokens to have similar lengths, but that seems to 97 # be an overkill at this moment. We can revisit this when this becomes a 98 # real problem. 99 sed -i -E 's/interface\ __aidl_oneway__/oneway\ interface/g' "$1" 100 sed -i -E 's/__aidl_utf8inCpp__/@utf8InCpp/g' "$1" 101 sed -i -E 's/__aidl_nullable__/@nullable/g' "$1" 102 103 # clang-format adds space around "=" in annotation parameters. e.g. 104 # @Anno(a = 100). The following awk script removes the spaces back. 105 # @Anno(a = 1, b = 2) @Anno(c = 3, d = 4) int foo = 3; becomes 106 # @Anno(a=1, b=2) @Anno(c=3, d=4) int foo = 3; 107 # [^@,=] ensures that the match doesn't cross the characters, otherwise 108 # "a = 1, b = 2" would match only once and will become "a = 1, b=2". 109 awk -i inplace \ 110 '/@[^@]+\(.*=.*\)/ { # matches a line having @anno(param = val) \ 111 print(gensub(/([^@,=]+) = ([^@,=]+|"[^"]*")/, "\\1=\\2", "g", $0)); \ 112 done=1;\ 113 } \ 114 {if (!done) {print($0);} done=0;}' "$1" 115 } 116 117 function format-one() { 118 local mode="$1" 119 local input="$2" 120 local style="$3" 121 local output="$(mktemp)" 122 123 cp "$input" "$output" 124 prepare "$output" 125 apply-clang-format "$output" "$style" 126 fixup "$output" 127 128 if [ $mode = "diff" ]; then 129 diff "$input" "$output" || ( 130 echo "You can try to fix this by running:" 131 echo "$0 -w <file>" 132 echo "" 133 ) 134 rm "$output" 135 elif [ $mode = "write" ]; then 136 if diff -q "$output" "$input" >/dev/null; then 137 rm "$output" 138 else 139 mv "$output" "$input" 140 fi 141 elif [ $mode = "print" ]; then 142 cat "$output" 143 rm "$output" 144 fi 145 } 146 147 function show-help-and-exit() { 148 echo "Usage: $0 [options] [path...]" 149 echo " -d: display diff instead of the formatted result" 150 echo " -w: rewrite the result back to the source file, instead of stdout" 151 echo " -h: show this help message" 152 echo " --clang-format-path <PATH>: set the path to the clang-format to <PATH>" 153 echo " [path...]: source files. if none, input is read from stdin" 154 exit 1 155 } 156 157 local mode=print 158 while [ $# -gt 0 ]; do 159 case "$1" in 160 -d) mode=diff; shift;; 161 -w) mode=write; shift;; 162 -h) show-help-and-exit;; 163 --clang-format-path) clang_format="$2"; shift 2;; 164 *) break;; 165 esac 166 done 167 168 if [ $# -lt 1 ]; then 169 if [ $mode = "write" ]; then 170 echo "-w not supported when input is stdin" 171 exit 1 172 fi 173 local input="$(mktemp)" 174 cat /dev/stdin > "$input" 175 local style="$(pwd)/.aidl-format" 176 format-one $mode "$input" "$style" 177 rm "$input" 178 else 179 for file in "$@" 180 do 181 if [ ! -f "$file" ]; then 182 echo "$file": no such file 183 exit 1 184 fi 185 local style="$(find-aidl-format-style $(dirname "$filename"))" 186 format-one $mode "$file" "$style" 187 done 188 fi 189) 190 191 192_aidl-format "$@" 193