1#!/usr/bin/env python3 2# 3# Copyright (C) 2019 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 17"""This module implements an Android.mk rewriter which fixes errors that are 18caught by prebuilt ELF checker.""" 19 20from __future__ import print_function 21 22import os.path 23import re 24import sys 25 26from .android import find_android_build_top 27from .readobj import readobj 28 29 30def _report_error(line, fmt, *args): 31 """This function prints an error message.""" 32 fmt = '{}: error: ' + fmt 33 print(fmt.format(line, *args), file=sys.stderr) 34 35 36class Variable(object): 37 """This class represents the value and locations of a variable.""" 38 39 def __init__(self, value, locs): 40 self.value = value 41 self.locs = list(locs) 42 43 44 def __repr__(self): 45 return repr(self.value) 46 47 48 def append(self, value, locs): 49 """Append a value to this variable.""" 50 self.value += value 51 self.locs.extend(locs) 52 53 54class StashedLines(object): 55 """This class stashes lines and rewrites them before flushing them.""" 56 57 _KEEP = 1 58 _DELETE = 2 59 60 61 def __init__(self): 62 self._lines = [] 63 64 65 def __len__(self): 66 return len(self._lines) 67 68 69 def append(self, line): 70 """This function appends a line to stashed lines.""" 71 self._lines.append((self._KEEP, line)) 72 73 74 def extend(self, lines): 75 """This function appends multiple lines to stashed lines.""" 76 for line in lines: 77 self.append(line) 78 79 80 def replace(self, locs, line): 81 """This function replaces `locs[0]` with `line` and marks the rest of 82 line numbers in `locs` as deleted.""" 83 locs = iter(locs) 84 self._lines[next(locs)] = (self._KEEP, line) 85 for loc in locs: 86 self._lines[loc] = (self._DELETE, None) 87 88 89 def flush(self, out_file): 90 """This function prints the stashed lines to `out_file` and resets the 91 stashed lines.""" 92 for action, line in self._lines: 93 if action == self._KEEP: 94 print(line, file=out_file) 95 self._lines = [] 96 97 98 99class Rewriter(object): # pylint: disable=too-few-public-methods 100 """This class rewrites the input Android.mk file and adds missing 101 LOCAL_SHARED_LIBRARIES, LOCAL_MULTILIB, or LOCAL_CHECK_ELF_FILES.""" 102 103 104 _INCLUDE = re.compile('\\s*include\\s+\\$\\(([A-Za-z0-9_]*)\\)') 105 _VAR = re.compile('([A-Za-z_][A-Za-z0-9_-]*)\\s*([:+]?=)\\s*(.*)$') 106 107 108 def __init__(self, mk_path, variables=None, android_build_top=None): 109 self._mk_path = mk_path 110 self._mk_dirname = os.path.dirname(mk_path) 111 112 self._variables = {} 113 if variables: 114 for key, value in variables.items(): 115 self._add_var(key, value) 116 117 if android_build_top is None: 118 self._android_build_top = find_android_build_top(mk_path) 119 else: 120 self._android_build_top = android_build_top 121 122 123 def _read_prebuilt_file_path(self): 124 file_var = self._variables.get('LOCAL_SRC_FILES') 125 if file_var is not None: 126 return os.path.join(self._mk_dirname, file_var.value) 127 128 file_var = self._variables.get('LOCAL_PREBUILT_MODULE_FILE') 129 if file_var is not None: 130 return os.path.join(self._android_build_top, file_var.value) 131 132 return None 133 134 135 @staticmethod 136 def _get_module_name_for_dt_needed(dt_needed): 137 """Convert a DT_NEEDED name to the build system module name.""" 138 return re.sub('\\.so$', '', dt_needed) 139 140 141 def _get_module_names_for_dt_needed_entries(self, dt_needed_entries): 142 """Convert DT_NEEDED names into build system module names.""" 143 return set(self._get_module_name_for_dt_needed(dt_needed) 144 for dt_needed in dt_needed_entries) 145 146 147 def _rewrite_build_prebuilt(self, stashed_lines, line_no): 148 check_elf_files_var = self._variables.get('LOCAL_CHECK_ELF_FILES') 149 if check_elf_files_var is not None and \ 150 check_elf_files_var.value == 'false': 151 return 152 153 # Read the prebuilt ELF file 154 prebuilt_file = self._read_prebuilt_file_path() 155 if prebuilt_file is None: 156 _report_error(line_no, 157 'LOCAL_SRC_FILES and LOCAL_PREBUILT_MODULE_FILE are ' 158 'not defined') 159 return 160 161 if not os.path.exists(prebuilt_file): 162 _report_error(line_no, 'Prebuilt file does not exist: "{}"', 163 prebuilt_file) 164 165 is_32bit, dt_soname, dt_needed = readobj(prebuilt_file) 166 167 # Check whether LOCAL_MULTILIB is missing for 32-bit executables 168 multilib_var = self._variables.get('LOCAL_MULTILIB') 169 if not multilib_var and is_32bit: 170 stashed_lines.append('LOCAL_MULTILIB := 32') 171 172 # Check whether DT_SONAME matches with the file name 173 filename = os.path.basename(prebuilt_file) 174 if dt_soname and dt_soname != filename: 175 stashed_lines.extend([ 176 '# Bypass prebuilt ELF check due to mismatched DT_SONAME', 177 'LOCAL_CHECK_ELF_FILES := false',]) 178 return 179 180 # Check LOCAL_SHARED_LIBRARIES 181 shared_libs = self._get_module_names_for_dt_needed_entries(dt_needed) 182 shared_libs_var = self._variables.get('LOCAL_SHARED_LIBRARIES') 183 184 if not shared_libs_var: 185 if shared_libs: 186 stashed_lines.append('LOCAL_SHARED_LIBRARIES := ' + 187 ' '.join(sorted(shared_libs))) 188 return 189 190 shared_libs_specified = set(re.split('[ \t\n]', shared_libs_var.value)) 191 if shared_libs != shared_libs_specified: 192 # Replace LOCAL_SHARED_LIBRARIES 193 stashed_lines.replace(shared_libs_var.locs, 194 'LOCAL_SHARED_LIBRARIES := ' + 195 ' '.join(sorted(shared_libs))) 196 197 198 def _add_var(self, key, value, locs=tuple(), is_append=False): 199 value = self._expand_vars(value) 200 201 if is_append and key in self._variables: 202 self._variables[key].append(value, locs) 203 else: 204 self._variables[key] = Variable(value, locs) 205 206 207 def _expand_vars(self, string): 208 def _lookup_variable(match): 209 key = match.group(1) 210 try: 211 return self._variables[key].value 212 except KeyError: 213 # If we cannot find the variable, leave it as-is. 214 return match.group(0) 215 216 old_string = None 217 while old_string != string: 218 old_string = string 219 string = re.sub('\\$\\(([A-Za-z][A-Za-z0-9_-]*)\\)', 220 _lookup_variable, old_string) 221 return string 222 223 224 def _clear_vars(self): 225 self._variables = {key: value 226 for key, value in self._variables.items() 227 if not key.startswith('LOCAL_')} 228 229 230 def _rewrite_lines(self, lines, out_file): 231 stashed_lines = StashedLines() 232 233 line_iter = enumerate(lines) 234 for line_no, line in line_iter: 235 match = self._INCLUDE.match(line) 236 if match: 237 command = match.group(1) 238 if command == 'CLEAR_VARS': 239 self._clear_vars() 240 elif command == 'BUILD_PREBUILT': 241 self._rewrite_build_prebuilt(stashed_lines, line_no) 242 stashed_lines.append(line) 243 stashed_lines.flush(out_file) 244 continue 245 246 match = self._VAR.match(line) 247 if match: 248 start = len(stashed_lines) 249 stashed_lines.append(line) 250 251 key = match.group(1).strip() 252 assign_op = match.group(2).strip() 253 value = match.group(3).strip() 254 255 while value.endswith('\\'): 256 line_no, line = next(line_iter, (-1, None)) 257 if line is None: 258 value = value[:-1] 259 break 260 stashed_lines.append(line) 261 value = value[:-1] + line.strip() 262 end = len(stashed_lines) 263 locs = range(start, end) 264 265 self._add_var(key, value, locs, assign_op == '+=') 266 continue 267 268 stashed_lines.append(line) 269 270 stashed_lines.flush(out_file) 271 272 273 def rewrite(self, out_file=sys.stdout): 274 """This function reads the content of `self._mk_path`, rewrites build 275 rules, and prints the rewritten build rules to `out_file`.""" 276 277 with open(self._mk_path, 'r') as input_file: 278 lines = input_file.read().splitlines() 279 280 self._rewrite_lines(lines, out_file) 281