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