1 /* 2 * Copyright (C) 2010 The Android Open Source Project 3 * 4 * Licensed under the Eclipse Public License, Version 1.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.eclipse.org/org/documents/epl-v10.php 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.ide.eclipse.adt.internal.build; 18 19 import com.android.ide.eclipse.adt.AndroidConstants; 20 import com.android.ide.eclipse.adt.internal.project.BaseProjectHelper; 21 22 import org.eclipse.core.resources.IMarker; 23 import org.eclipse.core.resources.IProject; 24 import org.eclipse.core.resources.IResource; 25 import org.eclipse.core.runtime.CoreException; 26 27 import java.io.File; 28 import java.util.ArrayList; 29 import java.util.regex.Matcher; 30 import java.util.regex.Pattern; 31 32 public class AaptParser { 33 34 // TODO: rename the pattern to something that makes sense + javadoc comments. 35 /** 36 * Single line aapt warning for skipping files.<br> 37 * " (skipping hidden file '<file path>'" 38 */ 39 private final static Pattern sPattern0Line1 = Pattern.compile( 40 "^\\s+\\(skipping hidden file\\s'(.*)'\\)$"); //$NON-NLS-1$ 41 42 /** 43 * First line of dual line aapt error.<br> 44 * "ERROR at line <line>: <error>"<br> 45 * " (Occurred while parsing <path>)" 46 */ 47 private final static Pattern sPattern1Line1 = Pattern.compile( 48 "^ERROR\\s+at\\s+line\\s+(\\d+):\\s+(.*)$"); //$NON-NLS-1$ 49 /** 50 * Second line of dual line aapt error.<br> 51 * "ERROR at line <line>: <error>"<br> 52 * " (Occurred while parsing <path>)"<br> 53 * @see #sPattern1Line1 54 */ 55 private final static Pattern sPattern1Line2 = Pattern.compile( 56 "^\\s+\\(Occurred while parsing\\s+(.*)\\)$"); //$NON-NLS-1$ 57 /** 58 * First line of dual line aapt error.<br> 59 * "ERROR: <error>"<br> 60 * "Defined at file <path> line <line>" 61 */ 62 private final static Pattern sPattern2Line1 = Pattern.compile( 63 "^ERROR:\\s+(.+)$"); //$NON-NLS-1$ 64 /** 65 * Second line of dual line aapt error.<br> 66 * "ERROR: <error>"<br> 67 * "Defined at file <path> line <line>"<br> 68 * @see #sPattern2Line1 69 */ 70 private final static Pattern sPattern2Line2 = Pattern.compile( 71 "Defined\\s+at\\s+file\\s+(.+)\\s+line\\s+(\\d+)"); //$NON-NLS-1$ 72 /** 73 * Single line aapt error<br> 74 * "<path> line <line>: <error>" 75 */ 76 private final static Pattern sPattern3Line1 = Pattern.compile( 77 "^(.+)\\sline\\s(\\d+):\\s(.+)$"); //$NON-NLS-1$ 78 /** 79 * First line of dual line aapt error.<br> 80 * "ERROR parsing XML file <path>"<br> 81 * "<error> at line <line>" 82 */ 83 private final static Pattern sPattern4Line1 = Pattern.compile( 84 "^Error\\s+parsing\\s+XML\\s+file\\s(.+)$"); //$NON-NLS-1$ 85 /** 86 * Second line of dual line aapt error.<br> 87 * "ERROR parsing XML file <path>"<br> 88 * "<error> at line <line>"<br> 89 * @see #sPattern4Line1 90 */ 91 private final static Pattern sPattern4Line2 = Pattern.compile( 92 "^(.+)\\s+at\\s+line\\s+(\\d+)$"); //$NON-NLS-1$ 93 94 /** 95 * Single line aapt warning<br> 96 * "<path>:<line>: <error>" 97 */ 98 private final static Pattern sPattern5Line1 = Pattern.compile( 99 "^(.+?):(\\d+):\\s+WARNING:(.+)$"); //$NON-NLS-1$ 100 101 /** 102 * Single line aapt error<br> 103 * "<path>:<line>: <error>" 104 */ 105 private final static Pattern sPattern6Line1 = Pattern.compile( 106 "^(.+?):(\\d+):\\s+(.+)$"); //$NON-NLS-1$ 107 108 /** 109 * 4 line aapt error<br> 110 * "ERROR: 9-path image <path> malformed"<br> 111 * Line 2 and 3 are taken as-is while line 4 is ignored (it repeats with<br> 112 * 'ERROR: failure processing <path>) 113 */ 114 private final static Pattern sPattern7Line1 = Pattern.compile( 115 "^ERROR:\\s+9-patch\\s+image\\s+(.+)\\s+malformed\\.$"); //$NON-NLS-1$ 116 117 private final static Pattern sPattern8Line1 = Pattern.compile( 118 "^(invalid resource directory name): (.*)$"); //$NON-NLS-1$ 119 120 /** 121 * 2 line aapt error<br> 122 * "ERROR: Invalid configuration: foo"<br> 123 * " ^^^"<br> 124 * There's no need to parse the 2nd line. 125 */ 126 private final static Pattern sPattern9Line1 = Pattern.compile( 127 "^Invalid configuration: (.+)$"); //$NON-NLS-1$ 128 129 130 /** 131 * Parse the output of aapt and mark the incorrect file with error markers 132 * 133 * @param results the output of aapt 134 * @param project the project containing the file to mark 135 * @return true if the parsing failed, false if success. 136 */ parseOutput(ArrayList<String> results, IProject project)137 static final boolean parseOutput(ArrayList<String> results, 138 IProject project) { 139 // nothing to parse? just return false; 140 if (results.size() == 0) { 141 return false; 142 } 143 144 // get the root of the project so that we can make IFile from full 145 // file path 146 String osRoot = project.getLocation().toOSString(); 147 148 Matcher m; 149 150 for (int i = 0; i < results.size(); i++) { 151 String p = results.get(i); 152 153 m = sPattern0Line1.matcher(p); 154 if (m.matches()) { 155 // we ignore those (as this is an ignore message from aapt) 156 continue; 157 } 158 159 m = sPattern1Line1.matcher(p); 160 if (m.matches()) { 161 String lineStr = m.group(1); 162 String msg = m.group(2); 163 164 // get the matcher for the next line. 165 m = getNextLineMatcher(results, ++i, sPattern1Line2); 166 if (m == null) { 167 return true; 168 } 169 170 String location = m.group(1); 171 172 // check the values and attempt to mark the file. 173 if (checkAndMark(location, lineStr, msg, osRoot, project, 174 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 175 return true; 176 } 177 continue; 178 } 179 180 // this needs to be tested before Pattern2 since they both start with 'ERROR:' 181 m = sPattern7Line1.matcher(p); 182 if (m.matches()) { 183 String location = m.group(1); 184 String msg = p; // default msg is the line in case we don't find anything else 185 186 if (++i < results.size()) { 187 msg = results.get(i).trim(); 188 if (++i < results.size()) { 189 msg = msg + " - " + results.get(i).trim(); //$NON-NLS-1$ 190 191 // skip the next line 192 i++; 193 } 194 } 195 196 // display the error 197 if (checkAndMark(location, null, msg, osRoot, project, 198 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 199 return true; 200 } 201 202 // success, go to the next line 203 continue; 204 } 205 206 m = sPattern2Line1.matcher(p); 207 if (m.matches()) { 208 // get the msg 209 String msg = m.group(1); 210 211 // get the matcher for the next line. 212 m = getNextLineMatcher(results, ++i, sPattern2Line2); 213 if (m == null) { 214 return true; 215 } 216 217 String location = m.group(1); 218 String lineStr = m.group(2); 219 220 // check the values and attempt to mark the file. 221 if (checkAndMark(location, lineStr, msg, osRoot, project, 222 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 223 return true; 224 } 225 continue; 226 } 227 228 m = sPattern3Line1.matcher(p); 229 if (m.matches()) { 230 String location = m.group(1); 231 String lineStr = m.group(2); 232 String msg = m.group(3); 233 234 // check the values and attempt to mark the file. 235 if (checkAndMark(location, lineStr, msg, osRoot, project, 236 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 237 return true; 238 } 239 240 // success, go to the next line 241 continue; 242 } 243 244 m = sPattern4Line1.matcher(p); 245 if (m.matches()) { 246 // get the filename. 247 String location = m.group(1); 248 249 // get the matcher for the next line. 250 m = getNextLineMatcher(results, ++i, sPattern4Line2); 251 if (m == null) { 252 return true; 253 } 254 255 String msg = m.group(1); 256 String lineStr = m.group(2); 257 258 // check the values and attempt to mark the file. 259 if (checkAndMark(location, lineStr, msg, osRoot, project, 260 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 261 return true; 262 } 263 264 // success, go to the next line 265 continue; 266 } 267 268 m = sPattern5Line1.matcher(p); 269 if (m.matches()) { 270 String location = m.group(1); 271 String lineStr = m.group(2); 272 String msg = m.group(3); 273 274 // check the values and attempt to mark the file. 275 if (checkAndMark(location, lineStr, msg, osRoot, project, 276 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_WARNING) == false) { 277 return true; 278 } 279 280 // success, go to the next line 281 continue; 282 } 283 284 m = sPattern6Line1.matcher(p); 285 if (m.matches()) { 286 String location = m.group(1); 287 String lineStr = m.group(2); 288 String msg = m.group(3); 289 290 // check the values and attempt to mark the file. 291 if (checkAndMark(location, lineStr, msg, osRoot, project, 292 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 293 return true; 294 } 295 296 // success, go to the next line 297 continue; 298 } 299 300 m = sPattern8Line1.matcher(p); 301 if (m.matches()) { 302 String location = m.group(2); 303 String msg = m.group(1); 304 305 // check the values and attempt to mark the file. 306 if (checkAndMark(location, null, msg, osRoot, project, 307 AndroidConstants.MARKER_AAPT_COMPILE, IMarker.SEVERITY_ERROR) == false) { 308 return true; 309 } 310 311 // success, go to the next line 312 continue; 313 } 314 315 m = sPattern9Line1.matcher(p); 316 if (m.matches()) { 317 String badConfig = m.group(1); 318 String msg = String.format("APK Configuration filter '%1$s' is invalid", badConfig); 319 320 // skip the next line 321 i++; 322 323 // check the values and attempt to mark the file. 324 if (checkAndMark(null /*location*/, null, msg, osRoot, project, 325 AndroidConstants.MARKER_AAPT_PACKAGE, IMarker.SEVERITY_ERROR) == false) { 326 return true; 327 } 328 329 // success, go to the next line 330 continue; 331 } 332 333 // invalid line format, flag as error, and bail 334 return true; 335 } 336 337 return false; 338 } 339 340 /** 341 * Check if the parameters gotten from the error output are valid, and mark 342 * the file with an AAPT marker. 343 * @param location the full OS path of the error file. If null, the project is marked 344 * @param lineStr 345 * @param message 346 * @param root The root directory of the project, in OS specific format. 347 * @param project 348 * @param markerId The marker id to put. 349 * @param severity The severity of the marker to put (IMarker.SEVERITY_*) 350 * @return true if the parameters were valid and the file was marked successfully. 351 * 352 * @see IMarker 353 */ checkAndMark(String location, String lineStr, String message, String root, IProject project, String markerId, int severity)354 private static final boolean checkAndMark(String location, String lineStr, 355 String message, String root, IProject project, String markerId, int severity) { 356 // check this is in fact a file 357 if (location != null) { 358 File f = new File(location); 359 if (f.exists() == false) { 360 return false; 361 } 362 } 363 364 // get the line number 365 int line = -1; // default value for error with no line. 366 367 if (lineStr != null) { 368 try { 369 line = Integer.parseInt(lineStr); 370 } catch (NumberFormatException e) { 371 // looks like the string we extracted wasn't a valid 372 // file number. Parsing failed and we return true 373 return false; 374 } 375 } 376 377 // add the marker 378 IResource f2 = project; 379 if (location != null) { 380 f2 = getResourceFromFullPath(location, root, project); 381 if (f2 == null) { 382 return false; 383 } 384 } 385 386 // check if there's a similar marker already, since aapt is launched twice 387 boolean markerAlreadyExists = false; 388 try { 389 IMarker[] markers = f2.findMarkers(markerId, true, IResource.DEPTH_ZERO); 390 391 for (IMarker marker : markers) { 392 int tmpLine = marker.getAttribute(IMarker.LINE_NUMBER, -1); 393 if (tmpLine != line) { 394 break; 395 } 396 397 int tmpSeverity = marker.getAttribute(IMarker.SEVERITY, -1); 398 if (tmpSeverity != severity) { 399 break; 400 } 401 402 String tmpMsg = marker.getAttribute(IMarker.MESSAGE, null); 403 if (tmpMsg == null || tmpMsg.equals(message) == false) { 404 break; 405 } 406 407 // if we're here, all the marker attributes are equals, we found it 408 // and exit 409 markerAlreadyExists = true; 410 break; 411 } 412 413 } catch (CoreException e) { 414 // if we couldn't get the markers, then we just mark the file again 415 // (since markerAlreadyExists is initialized to false, we do nothing) 416 } 417 418 if (markerAlreadyExists == false) { 419 BaseProjectHelper.markResource(f2, markerId, message, line, severity); 420 } 421 422 return true; 423 } 424 425 /** 426 * Returns a matching matcher for the next line 427 * @param lines The array of lines 428 * @param nextIndex The index of the next line 429 * @param pattern The pattern to match 430 * @return null if error or no match, the matcher otherwise. 431 */ getNextLineMatcher(ArrayList<String> lines, int nextIndex, Pattern pattern)432 private static final Matcher getNextLineMatcher(ArrayList<String> lines, 433 int nextIndex, Pattern pattern) { 434 // unless we can't, because we reached the last line 435 if (nextIndex == lines.size()) { 436 // we expected a 2nd line, so we flag as error 437 // and we bail 438 return null; 439 } 440 441 Matcher m = pattern.matcher(lines.get(nextIndex)); 442 if (m.matches()) { 443 return m; 444 } 445 446 return null; 447 } 448 getResourceFromFullPath(String filename, String root, IProject project)449 private static IResource getResourceFromFullPath(String filename, String root, 450 IProject project) { 451 if (filename.startsWith(root)) { 452 String file = filename.substring(root.length()); 453 454 // get the resource 455 IResource r = project.findMember(file); 456 457 // if the resource is valid, we add the marker 458 if (r.exists()) { 459 return r; 460 } 461 } 462 463 return null; 464 } 465 466 } 467