1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 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.sdklib.internal.project; 18 19 import static com.android.sdklib.SdkConstants.FD_PROGUARD; 20 import static com.android.sdklib.SdkConstants.FD_TOOLS; 21 import static com.android.sdklib.SdkConstants.FN_ANDROID_PROGUARD_FILE; 22 import static com.android.sdklib.SdkConstants.FN_PROJECT_PROGUARD_FILE; 23 24 import com.android.io.FolderWrapper; 25 import com.android.io.IAbstractFile; 26 import com.android.io.IAbstractFolder; 27 import com.android.io.StreamException; 28 import com.android.sdklib.ISdkLog; 29 import com.android.sdklib.SdkConstants; 30 31 import java.io.BufferedReader; 32 import java.io.FileNotFoundException; 33 import java.io.IOException; 34 import java.io.InputStreamReader; 35 import java.util.Arrays; 36 import java.util.Collections; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.regex.Matcher; 42 import java.util.regex.Pattern; 43 44 /** 45 * Class representing project properties for both ADT and Ant-based build. 46 * <p/>The class is associated to a {@link PropertyType} that indicate which of the project 47 * property file is represented. 48 * <p/>To load an existing file, use {@link #load(IAbstractFolder, PropertyType)}. 49 * <p/>The class is meant to be always in sync (or at least not newer) than the file it represents. 50 * Once created, it can only be updated through {@link #reload()} 51 * 52 * <p/>The make modification or make new file, use a {@link ProjectPropertiesWorkingCopy} instance, 53 * either through {@link #create(IAbstractFolder, PropertyType)} or through 54 * {@link #makeWorkingCopy()}. 55 * 56 */ 57 public class ProjectProperties implements IPropertySource { 58 protected final static Pattern PATTERN_PROP = Pattern.compile( 59 "^([a-zA-Z0-9._-]+)\\s*=\\s*(.*)\\s*$"); 60 61 /** The property name for the project target */ 62 public final static String PROPERTY_TARGET = "target"; 63 64 public final static String PROPERTY_LIBRARY = "android.library"; 65 public final static String PROPERTY_LIB_REF = "android.library.reference."; 66 private final static String PROPERTY_LIB_REF_REGEX = "android.library.reference.\\d+"; 67 68 public final static String PROPERTY_PROGUARD_CONFIG = "proguard.config"; 69 public final static String PROPERTY_RULES_PATH = "layoutrules.jars"; 70 71 public final static String PROPERTY_SDK = "sdk.dir"; 72 // LEGACY - Kept so that we can actually remove it from local.properties. 73 private final static String PROPERTY_SDK_LEGACY = "sdk-location"; 74 75 public final static String PROPERTY_SPLIT_BY_DENSITY = "split.density"; 76 public final static String PROPERTY_SPLIT_BY_ABI = "split.abi"; 77 public final static String PROPERTY_SPLIT_BY_LOCALE = "split.locale"; 78 79 public final static String PROPERTY_TESTED_PROJECT = "tested.project.dir"; 80 81 public final static String PROPERTY_BUILD_SOURCE_DIR = "source.dir"; 82 public final static String PROPERTY_BUILD_OUT_DIR = "out.dir"; 83 84 public final static String PROPERTY_PACKAGE = "package"; 85 public final static String PROPERTY_VERSIONCODE = "versionCode"; 86 public final static String PROPERTY_PROJECTS = "projects"; 87 public final static String PROPERTY_KEY_STORE = "key.store"; 88 public final static String PROPERTY_KEY_ALIAS = "key.alias"; 89 90 public static enum PropertyType { 91 ANT(SdkConstants.FN_ANT_PROPERTIES, BUILD_HEADER, new String[] { 92 PROPERTY_BUILD_SOURCE_DIR, PROPERTY_BUILD_OUT_DIR 93 }, null), 94 PROJECT(SdkConstants.FN_PROJECT_PROPERTIES, DEFAULT_HEADER, new String[] { 95 PROPERTY_TARGET, PROPERTY_LIBRARY, PROPERTY_LIB_REF_REGEX, 96 PROPERTY_KEY_STORE, PROPERTY_KEY_ALIAS, PROPERTY_PROGUARD_CONFIG, 97 PROPERTY_RULES_PATH 98 }, null), 99 LOCAL(SdkConstants.FN_LOCAL_PROPERTIES, LOCAL_HEADER, new String[] { 100 PROPERTY_SDK 101 }, 102 new String[] { PROPERTY_SDK_LEGACY }), 103 @Deprecated 104 LEGACY_DEFAULT("default.properties", null, null, null), 105 @Deprecated 106 LEGACY_BUILD("build.properties", null, null, null); 107 108 109 private final String mFilename; 110 private final String mHeader; 111 private final Set<String> mKnownProps; 112 private final Set<String> mRemovedProps; 113 114 PropertyType(String filename, String header, String[] validProps, String[] removedProps) { 115 mFilename = filename; 116 mHeader = header; 117 HashSet<String> s = new HashSet<String>(); 118 if (validProps != null) { 119 s.addAll(Arrays.asList(validProps)); 120 } 121 mKnownProps = Collections.unmodifiableSet(s); 122 123 s = new HashSet<String>(); 124 if (removedProps != null) { 125 s.addAll(Arrays.asList(removedProps)); 126 } 127 mRemovedProps = Collections.unmodifiableSet(s); 128 129 } 130 131 public String getFilename() { 132 return mFilename; 133 } 134 135 public String getHeader() { 136 return mHeader; 137 } 138 139 /** 140 * Returns whether a given property is known for the property type. 141 */ 142 public boolean isKnownProperty(String name) { 143 for (String propRegex : mKnownProps) { 144 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) { 145 return true; 146 } 147 } 148 149 return false; 150 } 151 152 /** 153 * Returns whether a given property should be removed for the property type. 154 */ 155 public boolean isRemovedProperty(String name) { 156 for (String propRegex : mRemovedProps) { 157 if (propRegex.equals(name) || Pattern.matches(propRegex, name)) { 158 return true; 159 } 160 } 161 162 return false; 163 } 164 } 165 166 private final static String LOCAL_HEADER = 167 // 1-------10--------20--------30--------40--------50--------60--------70--------80 168 "# This file is automatically generated by Android Tools.\n" + 169 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 170 "#\n" + 171 "# This file must *NOT* be checked into Version Control Systems,\n" + 172 "# as it contains information specific to your local configuration.\n" + 173 "\n"; 174 175 private final static String DEFAULT_HEADER = 176 // 1-------10--------20--------30--------40--------50--------60--------70--------80 177 "# This file is automatically generated by Android Tools.\n" + 178 "# Do not modify this file -- YOUR CHANGES WILL BE ERASED!\n" + 179 "#\n" + 180 "# This file must be checked in Version Control Systems.\n" + 181 "#\n" + 182 "# To customize properties used by the Ant build system edit\n" + 183 "# \"ant.properties\", and override values to adapt the script to your\n" + 184 "# project structure.\n" + 185 "#\n" + 186 "# To enable ProGuard to shrink and obfuscate your code, uncomment this " 187 + "(available properties: sdk.dir, user.home):\n" + 188 // Note: always use / separators in the properties paths. Both Ant and 189 // our ExportHelper will convert them properly according to the platform. 190 "#" + PROPERTY_PROGUARD_CONFIG + "=${" + PROPERTY_SDK +"}/" 191 + FD_TOOLS + '/' + FD_PROGUARD + '/' 192 + FN_ANDROID_PROGUARD_FILE + ':' + FN_PROJECT_PROGUARD_FILE +'\n' + 193 "\n"; 194 195 private final static String BUILD_HEADER = 196 // 1-------10--------20--------30--------40--------50--------60--------70--------80 197 "# This file is used to override default values used by the Ant build system.\n" + 198 "#\n" + 199 "# This file must be checked into Version Control Systems, as it is\n" + 200 "# integral to the build system of your project.\n" + 201 "\n" + 202 "# This file is only used by the Ant script.\n" + 203 "\n" + 204 "# You can use this to override default values such as\n" + 205 "# 'source.dir' for the location of your java source folder and\n" + 206 "# 'out.dir' for the location of your output folder.\n" + 207 "\n" + 208 "# You can also use it define how the release builds are signed by declaring\n" + 209 "# the following properties:\n" + 210 "# 'key.store' for the location of your keystore and\n" + 211 "# 'key.alias' for the name of the key to use.\n" + 212 "# The password will be asked during the build when you use the 'release' target.\n" + 213 "\n"; 214 215 protected final IAbstractFolder mProjectFolder; 216 protected final Map<String, String> mProperties; 217 protected final PropertyType mType; 218 219 /** 220 * Loads a project properties file and return a {@link ProjectProperties} object 221 * containing the properties. 222 * 223 * @param projectFolderOsPath the project folder. 224 * @param type One the possible {@link PropertyType}s. 225 */ 226 public static ProjectProperties load(String projectFolderOsPath, PropertyType type) { 227 IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath); 228 return load(wrapper, type); 229 } 230 231 /** 232 * Loads a project properties file and return a {@link ProjectProperties} object 233 * containing the properties. 234 * 235 * @param projectFolder the project folder. 236 * @param type One the possible {@link PropertyType}s. 237 */ 238 public static ProjectProperties load(IAbstractFolder projectFolder, PropertyType type) { 239 if (projectFolder.exists()) { 240 IAbstractFile propFile = projectFolder.getFile(type.mFilename); 241 if (propFile.exists()) { 242 Map<String, String> map = parsePropertyFile(propFile, null /* log */); 243 if (map != null) { 244 return new ProjectProperties(projectFolder, map, type); 245 } 246 } 247 } 248 return null; 249 } 250 251 /** 252 * Deletes a project properties file. 253 * 254 * @param projectFolder the project folder. 255 * @param type One the possible {@link PropertyType}s. 256 * @return true if success. 257 */ 258 public static boolean delete(IAbstractFolder projectFolder, PropertyType type) { 259 if (projectFolder.exists()) { 260 IAbstractFile propFile = projectFolder.getFile(type.mFilename); 261 if (propFile.exists()) { 262 return propFile.delete(); 263 } 264 } 265 266 return false; 267 } 268 269 /** 270 * Deletes a project properties file. 271 * 272 * @param projectFolderOsPath the project folder. 273 * @param type One the possible {@link PropertyType}s. 274 * @return true if success. 275 */ 276 public static boolean delete(String projectFolderOsPath, PropertyType type) { 277 IAbstractFolder wrapper = new FolderWrapper(projectFolderOsPath); 278 return delete(wrapper, type); 279 } 280 281 282 /** 283 * Creates a new project properties object, with no properties. 284 * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called. 285 * @param projectFolderOsPath the project folder. 286 * @param type the type of property file to create 287 */ 288 public static ProjectPropertiesWorkingCopy create(String projectFolderOsPath, 289 PropertyType type) { 290 // create and return a ProjectProperties with an empty map. 291 IAbstractFolder folder = new FolderWrapper(projectFolderOsPath); 292 return create(folder, type); 293 } 294 295 /** 296 * Creates a new project properties object, with no properties. 297 * <p/>The file is not created until {@link ProjectPropertiesWorkingCopy#save()} is called. 298 * @param projectFolder the project folder. 299 * @param type the type of property file to create 300 */ 301 public static ProjectPropertiesWorkingCopy create(IAbstractFolder projectFolder, 302 PropertyType type) { 303 // create and return a ProjectProperties with an empty map. 304 return new ProjectPropertiesWorkingCopy(projectFolder, new HashMap<String, String>(), type); 305 } 306 307 /** 308 * Creates and returns a copy of the current properties as a 309 * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. 310 * @return a new instance of {@link ProjectPropertiesWorkingCopy} 311 */ 312 public ProjectPropertiesWorkingCopy makeWorkingCopy() { 313 return makeWorkingCopy(mType); 314 } 315 316 /** 317 * Creates and returns a copy of the current properties as a 318 * {@link ProjectPropertiesWorkingCopy} that can be modified and saved. This also allows 319 * converting to a new type, by specifying a different {@link PropertyType}. 320 * 321 * @param type the {@link PropertyType} of the prop file to save. 322 * 323 * @return a new instance of {@link ProjectPropertiesWorkingCopy} 324 */ 325 public ProjectPropertiesWorkingCopy makeWorkingCopy(PropertyType type) { 326 // copy the current properties in a new map 327 HashMap<String, String> propList = new HashMap<String, String>(); 328 propList.putAll(mProperties); 329 330 return new ProjectPropertiesWorkingCopy(mProjectFolder, propList, type); 331 } 332 333 /** 334 * Returns the type of the property file. 335 * 336 * @see PropertyType 337 */ 338 public PropertyType getType() { 339 return mType; 340 } 341 342 /** 343 * Returns the value of a property. 344 * @param name the name of the property. 345 * @return the property value or null if the property is not set. 346 */ 347 @Override 348 public synchronized String getProperty(String name) { 349 return mProperties.get(name); 350 } 351 352 /** 353 * Returns a set of the property keys. Unlike {@link Map#keySet()} this is not a view of the 354 * map keys. Modifying the returned {@link Set} will not impact the underlying {@link Map}. 355 */ 356 public synchronized Set<String> keySet() { 357 return new HashSet<String>(mProperties.keySet()); 358 } 359 360 /** 361 * Reloads the properties from the underlying file. 362 */ 363 public synchronized void reload() { 364 if (mProjectFolder.exists()) { 365 IAbstractFile propFile = mProjectFolder.getFile(mType.mFilename); 366 if (propFile.exists()) { 367 Map<String, String> map = parsePropertyFile(propFile, null /* log */); 368 if (map != null) { 369 mProperties.clear(); 370 mProperties.putAll(map); 371 } 372 } 373 } 374 } 375 376 /** 377 * Parses a property file (using UTF-8 encoding) and returns a map of the content. 378 * <p/>If the file is not present, null is returned with no error messages sent to the log. 379 * 380 * @param propFile the property file to parse 381 * @param log the ISdkLog object receiving warning/error from the parsing. Cannot be null. 382 * @return the map of (key,value) pairs, or null if the parsing failed. 383 */ 384 public static Map<String, String> parsePropertyFile(IAbstractFile propFile, ISdkLog log) { 385 BufferedReader reader = null; 386 try { 387 reader = new BufferedReader(new InputStreamReader(propFile.getContents(), 388 SdkConstants.INI_CHARSET)); 389 390 String line = null; 391 Map<String, String> map = new HashMap<String, String>(); 392 while ((line = reader.readLine()) != null) { 393 if (line.length() > 0 && line.charAt(0) != '#') { 394 395 Matcher m = PATTERN_PROP.matcher(line); 396 if (m.matches()) { 397 map.put(m.group(1), unescape(m.group(2))); 398 } else { 399 log.warning("Error parsing '%1$s': \"%2$s\" is not a valid syntax", 400 propFile.getOsLocation(), 401 line); 402 return null; 403 } 404 } 405 } 406 407 return map; 408 } catch (FileNotFoundException e) { 409 // this should not happen since we usually test the file existence before 410 // calling the method. 411 // Return null below. 412 } catch (IOException e) { 413 log.warning("Error parsing '%1$s': %2$s.", 414 propFile.getOsLocation(), 415 e.getMessage()); 416 } catch (StreamException e) { 417 log.warning("Error parsing '%1$s': %2$s.", 418 propFile.getOsLocation(), 419 e.getMessage()); 420 } finally { 421 if (reader != null) { 422 try { 423 reader.close(); 424 } catch (IOException e) { 425 // pass 426 } 427 } 428 } 429 430 return null; 431 } 432 433 /** 434 * Private constructor. 435 * <p/> 436 * Use {@link #load(String, PropertyType)} or {@link #create(String, PropertyType)} 437 * to instantiate. 438 */ 439 protected ProjectProperties(IAbstractFolder projectFolder, Map<String, String> map, 440 PropertyType type) { 441 mProjectFolder = projectFolder; 442 mProperties = map; 443 mType = type; 444 } 445 446 private static String unescape(String value) { 447 return value.replaceAll("\\\\\\\\", "\\\\"); 448 } 449 450 protected static String escape(String value) { 451 return value.replaceAll("\\\\", "\\\\\\\\"); 452 } 453 } 454