1 /* 2 * Copyright (C) 2009 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.repository; 18 19 import com.android.annotations.VisibleForTesting; 20 import com.android.annotations.VisibleForTesting.Visibility; 21 import com.android.sdklib.SdkConstants; 22 import com.android.sdklib.SdkManager; 23 import com.android.sdklib.internal.repository.Archive.Arch; 24 import com.android.sdklib.internal.repository.Archive.Os; 25 import com.android.sdklib.repository.SdkRepoConstants; 26 27 import org.w3c.dom.Node; 28 29 import java.io.BufferedReader; 30 import java.io.File; 31 import java.io.IOException; 32 import java.io.InputStreamReader; 33 import java.util.Map; 34 import java.util.Properties; 35 import java.util.regex.Matcher; 36 import java.util.regex.Pattern; 37 38 /** 39 * Represents a tool XML node in an SDK repository. 40 */ 41 public class ToolPackage extends Package implements IMinPlatformToolsDependency { 42 43 /** The value returned by {@link ToolPackage#installId()}. */ 44 public static final String INSTALL_ID = "tools"; //$NON-NLS-1$ 45 46 protected static final String PROP_MIN_PLATFORM_TOOLS_REV = 47 "Platform.MinPlatformToolsRev"; //$NON-NLS-1$ 48 49 /** 50 * The minimal revision of the platform-tools package required by this package 51 * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing. 52 */ 53 private final int mMinPlatformToolsRevision; 54 55 /** 56 * Creates a new tool package from the attributes and elements of the given XML node. 57 * This constructor should throw an exception if the package cannot be created. 58 * 59 * @param source The {@link SdkSource} where this is loaded from. 60 * @param packageNode The XML element being parsed. 61 * @param nsUri The namespace URI of the originating XML document, to be able to deal with 62 * parameters that vary according to the originating XML schema. 63 * @param licenses The licenses loaded from the XML originating document. 64 */ 65 ToolPackage(SdkSource source, Node packageNode, String nsUri, Map<String,String> licenses) { 66 super(source, packageNode, nsUri, licenses); 67 68 mMinPlatformToolsRevision = XmlParserUtils.getXmlInt( 69 packageNode, 70 SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV, 71 MIN_PLATFORM_TOOLS_REV_INVALID); 72 if (mMinPlatformToolsRevision == MIN_PLATFORM_TOOLS_REV_INVALID) { 73 // This revision number is mandatory starting with sdk-repository-3.xsd 74 // and did not exist before. Complain if the URI has level >= 3. 75 76 boolean needRevision = false; 77 78 Pattern nsPattern = Pattern.compile(SdkRepoConstants.NS_PATTERN); 79 Matcher m = nsPattern.matcher(nsUri); 80 if (m.matches()) { 81 String version = m.group(1); 82 try { 83 needRevision = Integer.parseInt(version) >= 3; 84 } catch (NumberFormatException e) { 85 // ignore. needRevision defaults to false 86 } 87 } 88 89 if (needRevision) { 90 throw new IllegalArgumentException( 91 String.format("Missing %1$s element in %2$s package", 92 SdkRepoConstants.NODE_MIN_PLATFORM_TOOLS_REV, 93 SdkRepoConstants.NODE_PLATFORM_TOOL)); 94 } 95 } 96 } 97 98 /** 99 * Manually create a new package with one archive and the given attributes or properties. 100 * This is used to create packages from local directories in which case there must be 101 * one archive which URL is the actual target location. 102 * <p/> 103 * By design, this creates a package with one and only one archive. 104 */ 105 static Package create( 106 SdkSource source, 107 Properties props, 108 int revision, 109 String license, 110 String description, 111 String descUrl, 112 Os archiveOs, 113 Arch archiveArch, 114 String archiveOsPath) { 115 return new ToolPackage(source, props, revision, license, description, 116 descUrl, archiveOs, archiveArch, archiveOsPath); 117 } 118 119 @VisibleForTesting(visibility=Visibility.PRIVATE) 120 protected ToolPackage( 121 SdkSource source, 122 Properties props, 123 int revision, 124 String license, 125 String description, 126 String descUrl, 127 Os archiveOs, 128 Arch archiveArch, 129 String archiveOsPath) { 130 super(source, 131 props, 132 revision, 133 license, 134 description, 135 descUrl, 136 archiveOs, 137 archiveArch, 138 archiveOsPath); 139 140 mMinPlatformToolsRevision = Integer.parseInt( 141 getProperty( 142 props, 143 PROP_MIN_PLATFORM_TOOLS_REV, 144 Integer.toString(MIN_PLATFORM_TOOLS_REV_INVALID))); 145 } 146 147 /** 148 * The minimal revision of the tools package required by this package if > 0, 149 * or {@link #MIN_PLATFORM_TOOLS_REV_INVALID} if the value was missing. 150 * <p/> 151 * This attribute is mandatory and should not be normally missing. 152 */ 153 public int getMinPlatformToolsRevision() { 154 return mMinPlatformToolsRevision; 155 } 156 157 /** 158 * Returns a string identifier to install this package from the command line. 159 * For tools, we use "tools" since this package is unique. 160 * <p/> 161 * {@inheritDoc} 162 */ 163 @Override 164 public String installId() { 165 return INSTALL_ID; 166 } 167 168 /** 169 * Returns a description of this package that is suitable for a list display. 170 * <p/> 171 * {@inheritDoc} 172 */ 173 @Override 174 public String getListDescription() { 175 return String.format("Android SDK Tools%1$s", 176 isObsolete() ? " (Obsolete)" : ""); 177 } 178 179 /** 180 * Returns a short description for an {@link IDescription}. 181 */ 182 @Override 183 public String getShortDescription() { 184 return String.format("Android SDK Tools, revision %1$d%2$s", 185 getRevision(), 186 isObsolete() ? " (Obsolete)" : ""); 187 } 188 189 /** Returns a long description for an {@link IDescription}. */ 190 @Override 191 public String getLongDescription() { 192 String s = getDescription(); 193 if (s == null || s.length() == 0) { 194 s = getShortDescription(); 195 } 196 197 if (s.indexOf("revision") == -1) { 198 s += String.format("\nRevision %1$d%2$s", 199 getRevision(), 200 isObsolete() ? " (Obsolete)" : ""); 201 } 202 203 return s; 204 } 205 206 /** 207 * Computes a potential installation folder if an archive of this package were 208 * to be installed right away in the given SDK root. 209 * <p/> 210 * A "tool" package should always be located in SDK/tools. 211 * 212 * @param osSdkRoot The OS path of the SDK root folder. 213 * @param sdkManager An existing SDK manager to list current platforms and addons. 214 * @return A new {@link File} corresponding to the directory to use to install this package. 215 */ 216 @Override 217 public File getInstallFolder(String osSdkRoot, SdkManager sdkManager) { 218 return new File(osSdkRoot, SdkConstants.FD_TOOLS); 219 } 220 221 @Override 222 public boolean sameItemAs(Package pkg) { 223 // only one tool package so any tool package is the same item. 224 return pkg instanceof ToolPackage; 225 } 226 227 @Override 228 void saveProperties(Properties props) { 229 super.saveProperties(props); 230 231 if (getMinPlatformToolsRevision() != MIN_PLATFORM_TOOLS_REV_INVALID) { 232 props.setProperty(PROP_MIN_PLATFORM_TOOLS_REV, 233 Integer.toString(getMinPlatformToolsRevision())); 234 } 235 } 236 237 /** 238 * The tool package executes tools/lib/post_tools_install[.bat|.sh] 239 * {@inheritDoc} 240 */ 241 @Override 242 public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) { 243 super.postInstallHook(archive, monitor, installFolder); 244 245 if (installFolder == null) { 246 return; 247 } 248 249 File libDir = new File(installFolder, SdkConstants.FD_LIB); 250 if (!libDir.isDirectory()) { 251 return; 252 } 253 254 String scriptName = "post_tools_install"; //$NON-NLS-1$ 255 String shell = ""; //$NON-NLS-1$ 256 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) { 257 shell = "cmd.exe /c "; //$NON-NLS-1$ 258 scriptName += ".bat"; //$NON-NLS-1$ 259 } else { 260 scriptName += ".sh"; //$NON-NLS-1$ 261 } 262 263 File scriptFile = new File(libDir, scriptName); 264 if (!scriptFile.isFile()) { 265 return; 266 } 267 268 Process proc; 269 int status = -1; 270 271 try { 272 proc = Runtime.getRuntime().exec( 273 shell + scriptName, // command 274 null, // environment 275 libDir); // working dir 276 277 status = grabProcessOutput(proc, monitor, scriptName); 278 279 } catch (Exception e) { 280 monitor.logError("Exception: %s", e.toString()); 281 } 282 283 if (status != 0) { 284 monitor.logError("Failed to execute %s", scriptName); 285 return; 286 } 287 } 288 289 /** 290 * Gets the stderr/stdout outputs of a process and returns when the process is done. 291 * Both <b>must</b> be read or the process will block on windows. 292 * @param process The process to get the ouput from. 293 * @param monitor The monitor where to output errors. 294 * @param scriptName The name of script being executed. 295 * @return the process return code. 296 * @throws InterruptedException 297 */ 298 private int grabProcessOutput(final Process process, 299 final ITaskMonitor monitor, 300 final String scriptName) 301 throws InterruptedException { 302 // read the lines as they come. if null is returned, it's 303 // because the process finished 304 Thread t1 = new Thread("") { //$NON-NLS-1$ 305 @Override 306 public void run() { 307 // create a buffer to read the stderr output 308 InputStreamReader is = new InputStreamReader(process.getErrorStream()); 309 BufferedReader errReader = new BufferedReader(is); 310 311 try { 312 while (true) { 313 String line = errReader.readLine(); 314 if (line != null) { 315 monitor.logError("[%1$s] Error: %2$s", scriptName, line); 316 } else { 317 break; 318 } 319 } 320 } catch (IOException e) { 321 // do nothing. 322 } 323 } 324 }; 325 326 Thread t2 = new Thread("") { //$NON-NLS-1$ 327 @Override 328 public void run() { 329 InputStreamReader is = new InputStreamReader(process.getInputStream()); 330 BufferedReader outReader = new BufferedReader(is); 331 332 try { 333 while (true) { 334 String line = outReader.readLine(); 335 if (line != null) { 336 monitor.log("[%1$s] %2$s", scriptName, line); 337 } else { 338 break; 339 } 340 } 341 } catch (IOException e) { 342 // do nothing. 343 } 344 } 345 }; 346 347 t1.start(); 348 t2.start(); 349 350 // it looks like on windows process#waitFor() can return 351 // before the thread have filled the arrays, so we wait for both threads and the 352 // process itself. 353 /* Disabled since not used. Do we really need this? 354 if (waitforReaders) { 355 try { 356 t1.join(); 357 } catch (InterruptedException e) { 358 } 359 try { 360 t2.join(); 361 } catch (InterruptedException e) { 362 } 363 } 364 */ 365 366 // get the return code from the process 367 return process.waitFor(); 368 } 369 370 @Override 371 public int hashCode() { 372 final int prime = 31; 373 int result = super.hashCode(); 374 result = prime * result + mMinPlatformToolsRevision; 375 return result; 376 } 377 378 @Override 379 public boolean equals(Object obj) { 380 if (this == obj) { 381 return true; 382 } 383 if (!super.equals(obj)) { 384 return false; 385 } 386 if (!(obj instanceof ToolPackage)) { 387 return false; 388 } 389 ToolPackage other = (ToolPackage) obj; 390 if (mMinPlatformToolsRevision != other.mMinPlatformToolsRevision) { 391 return false; 392 } 393 return true; 394 } 395 } 396