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.sdklib.SdkConstants; 20 import com.android.sdklib.SdkManager; 21 import com.android.sdklib.internal.repository.Archive.Arch; 22 import com.android.sdklib.internal.repository.Archive.Os; 23 24 import org.w3c.dom.Node; 25 26 import java.io.BufferedReader; 27 import java.io.File; 28 import java.io.IOException; 29 import java.io.InputStreamReader; 30 import java.util.Map; 31 import java.util.Properties; 32 33 /** 34 * Represents a tool XML node in an SDK repository. 35 */ 36 public class ToolPackage extends Package { 37 38 /** 39 * Creates a new tool package from the attributes and elements of the given XML node. 40 * <p/> 41 * This constructor should throw an exception if the package cannot be created. 42 */ 43 ToolPackage(RepoSource source, Node packageNode, Map<String,String> licenses) { 44 super(source, packageNode, licenses); 45 } 46 47 /** 48 * Manually create a new package with one archive and the given attributes or properties. 49 * This is used to create packages from local directories in which case there must be 50 * one archive which URL is the actual target location. 51 * <p/> 52 * By design, this creates a package with one and only one archive. 53 */ 54 ToolPackage( 55 RepoSource source, 56 Properties props, 57 int revision, 58 String license, 59 String description, 60 String descUrl, 61 Os archiveOs, 62 Arch archiveArch, 63 String archiveOsPath) { 64 super(source, 65 props, 66 revision, 67 license, 68 description, 69 descUrl, 70 archiveOs, 71 archiveArch, 72 archiveOsPath); 73 } 74 75 /** Returns a short description for an {@link IDescription}. */ 76 @Override 77 public String getShortDescription() { 78 return String.format("Android SDK Tools, revision %1$d%2$s", 79 getRevision(), 80 isObsolete() ? " (Obsolete)" : ""); 81 } 82 83 /** Returns a long description for an {@link IDescription}. */ 84 @Override 85 public String getLongDescription() { 86 String s = getDescription(); 87 if (s == null || s.length() == 0) { 88 s = getShortDescription(); 89 } 90 91 if (s.indexOf("revision") == -1) { 92 s += String.format("\nRevision %1$d%2$s", 93 getRevision(), 94 isObsolete() ? " (Obsolete)" : ""); 95 } 96 97 return s; 98 } 99 100 /** 101 * Computes a potential installation folder if an archive of this package were 102 * to be installed right away in the given SDK root. 103 * <p/> 104 * A "tool" package should always be located in SDK/tools. 105 * 106 * @param osSdkRoot The OS path of the SDK root folder. 107 * @param suggestedDir A suggestion for the installation folder name, based on the root 108 * folder used in the zip archive. 109 * @param sdkManager An existing SDK manager to list current platforms and addons. 110 * @return A new {@link File} corresponding to the directory to use to install this package. 111 */ 112 @Override 113 public File getInstallFolder(String osSdkRoot, String suggestedDir, SdkManager sdkManager) { 114 return new File(osSdkRoot, SdkConstants.FD_TOOLS); 115 } 116 117 @Override 118 public boolean sameItemAs(Package pkg) { 119 // only one tool package so any tool package is the same item. 120 return pkg instanceof ToolPackage; 121 } 122 123 /** 124 * The tool package executes tools/lib/post_tools_install[.bat|.sh] 125 * {@inheritDoc} 126 */ 127 @Override 128 public void postInstallHook(Archive archive, ITaskMonitor monitor, File installFolder) { 129 super.postInstallHook(archive, monitor, installFolder); 130 131 if (installFolder == null) { 132 return; 133 } 134 135 File libDir = new File(installFolder, SdkConstants.FD_LIB); 136 if (!libDir.isDirectory()) { 137 return; 138 } 139 140 String scriptName = "post_tools_install"; //$NON-NLS-1$ 141 String shell = ""; 142 if (SdkConstants.currentPlatform() == SdkConstants.PLATFORM_WINDOWS) { 143 shell = "cmd.exe /c "; 144 scriptName += ".bat"; //$NON-NLS-1$ 145 } else { 146 scriptName += ".sh"; //$NON-NLS-1$ 147 } 148 149 File scriptFile = new File(libDir, scriptName); 150 if (!scriptFile.isFile()) { 151 return; 152 } 153 154 Process proc; 155 int status = -1; 156 157 try { 158 proc = Runtime.getRuntime().exec( 159 shell + scriptName, // command 160 null, // environment 161 libDir); // working dir 162 163 status = grabProcessOutput(proc, monitor, scriptName); 164 165 } catch (Exception e) { 166 monitor.setResult("Exception: %s", e.toString()); 167 } 168 169 if (status != 0) { 170 monitor.setResult("Failed to execute %s", scriptName); 171 return; 172 } 173 } 174 175 /** 176 * Get the stderr/stdout outputs of a process and return when the process is done. 177 * Both <b>must</b> be read or the process will block on windows. 178 * @param process The process to get the ouput from. 179 * @param monitor The monitor where to output errors. 180 * @param scriptName The name of script being executed. 181 * @return the process return code. 182 * @throws InterruptedException 183 */ 184 private int grabProcessOutput(final Process process, 185 final ITaskMonitor monitor, 186 final String scriptName) 187 throws InterruptedException { 188 // read the lines as they come. if null is returned, it's 189 // because the process finished 190 Thread t1 = new Thread("") { //$NON-NLS-1$ 191 @Override 192 public void run() { 193 // create a buffer to read the stderr output 194 InputStreamReader is = new InputStreamReader(process.getErrorStream()); 195 BufferedReader errReader = new BufferedReader(is); 196 197 try { 198 while (true) { 199 String line = errReader.readLine(); 200 if (line != null) { 201 monitor.setResult("[%1$s] Error: %2$s", scriptName, line); 202 } else { 203 break; 204 } 205 } 206 } catch (IOException e) { 207 // do nothing. 208 } 209 } 210 }; 211 212 Thread t2 = new Thread("") { //$NON-NLS-1$ 213 @Override 214 public void run() { 215 InputStreamReader is = new InputStreamReader(process.getInputStream()); 216 BufferedReader outReader = new BufferedReader(is); 217 218 try { 219 while (true) { 220 String line = outReader.readLine(); 221 if (line != null) { 222 monitor.setResult("[%1$s] %2$s", scriptName, line); 223 } else { 224 break; 225 } 226 } 227 } catch (IOException e) { 228 // do nothing. 229 } 230 } 231 }; 232 233 t1.start(); 234 t2.start(); 235 236 // it looks like on windows process#waitFor() can return 237 // before the thread have filled the arrays, so we wait for both threads and the 238 // process itself. 239 /* Disabled since not used. Do we really need this? 240 if (waitforReaders) { 241 try { 242 t1.join(); 243 } catch (InterruptedException e) { 244 } 245 try { 246 t2.join(); 247 } catch (InterruptedException e) { 248 } 249 } 250 */ 251 252 // get the return code from the process 253 return process.waitFor(); 254 } 255 } 256