Home | History | Annotate | Download | only in repository
      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