Home | History | Annotate | Download | only in targetprep
      1 /*
      2  * Copyright (C) 2010 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.tradefed.targetprep;
     18 
     19 import com.android.tradefed.command.remote.DeviceDescriptor;
     20 import com.android.tradefed.util.MultiMap;
     21 
     22 import java.io.BufferedReader;
     23 import java.io.File;
     24 import java.io.IOException;
     25 import java.io.InputStreamReader;
     26 import java.util.ArrayList;
     27 import java.util.Collection;
     28 import java.util.HashMap;
     29 import java.util.Map;
     30 import java.util.regex.Matcher;
     31 import java.util.regex.Pattern;
     32 import java.util.zip.ZipEntry;
     33 import java.util.zip.ZipException;
     34 import java.util.zip.ZipFile;
     35 
     36 /**
     37  * A class that parses out required versions of auxiliary image files needed to flash a device.
     38  * (e.g. bootloader, baseband, etc)
     39  */
     40 public class FlashingResourcesParser implements IFlashingResourcesParser {
     41     /**
     42      * A filtering interface, intended to allow {@link FlashingResourcesParser} to ignore some
     43      * resources that it otherwise might use
     44      */
     45     public static interface Constraint {
     46         /**
     47          * Check if the provided {@code item} passes the constraint.
     48          * @return {@code true} for accept, {@code false} for reject
     49          */
     50         public boolean shouldAccept(String item);
     51     }
     52 
     53     private static final String ANDROID_INFO_FILE_NAME = "android-info.txt";
     54     /**
     55      * Some resource files use "require-foo=bar", others use "foo=bar". This expression handles
     56      * both.
     57      */
     58     private static final Pattern REQUIRE_PATTERN = Pattern.compile("(?:require\\s)?(.*?)=(.*)");
     59     /**
     60      * Some resource files have special product-specific requirements, for instance:
     61      * {@code require-for-product:product1 version-bootloader=xyz} would only require bootloader
     62      * {@code xyz} for device {@code product1}.  This pattern matches the require-for-product line
     63      */
     64     private static final Pattern PRODUCT_REQUIRE_PATTERN =
     65             Pattern.compile("require-for-product:(\\S+) +(.*?)=(.*)");
     66 
     67     // expected keys
     68     public static final String PRODUCT_KEY = "product";
     69     public static final String BOARD_KEY = "board";
     70     public static final String BOOTLOADER_VERSION_KEY = "version-bootloader";
     71     public static final String BASEBAND_VERSION_KEY = "version-baseband";
     72 
     73     // key-value pairs of build requirements
     74     private AndroidInfo mReqs;
     75 
     76     /**
     77      * A typedef for {@code Map<String, MultiMap<String, String>>}.  Useful parsed
     78      * format for storing the data encoded in ANDROID_INFO_FILE_NAME
     79      */
     80     @SuppressWarnings("serial")
     81     public static class AndroidInfo extends HashMap<String, MultiMap<String, String>> {}
     82 
     83     /**
     84      * Create a {@link FlashingResourcesParser} and have it parse the specified device image for
     85      * flashing requirements.  Flashing requirements must pass the appropriate constraint (if one
     86      * exists) before being added.  Rejected requirements will be dropped silently.
     87      *
     88      * @param deviceImgZipFile The {@code updater.zip} file to be flashed
     89      * @param c A map from key name to {@link Constraint}.  Image names will be checked against
     90      *        the appropriate constraint (if any) as a prereq for being added.  May be null to
     91      *        disable filtering.
     92      */
     93     public FlashingResourcesParser(File deviceImgZipFile, Map<String, Constraint> c)
     94             throws TargetSetupError {
     95         mReqs = getBuildRequirements(deviceImgZipFile, c);
     96     }
     97 
     98     /**
     99      * Create a {@link FlashingResourcesParser} and have it parse the specified device image for
    100      * flashing requirements.
    101      *
    102      * @param deviceImgZipFile The {@code updater.zip} file to be flashed
    103      */
    104     public FlashingResourcesParser(File deviceImgZipFile) throws TargetSetupError {
    105         this(deviceImgZipFile, null);
    106     }
    107 
    108     /**
    109      * Constructs a FlashingResourcesParser with the supplied AndroidInfo Reader
    110      * <p/>
    111      * Exposed for unit testing
    112      *
    113      * @param infoReader a {@link BufferedReader} containing the equivalent of android-info.txt to
    114      *        parse
    115      * @param c A map from key name to {@link Constraint}.  Image names will be checked against
    116      *        the appropriate constraint (if any) as a prereq for being added.  May be null to
    117      *        disable filtering.
    118      */
    119     public FlashingResourcesParser(BufferedReader infoReader, Map<String, Constraint> c)
    120             throws IOException {
    121         mReqs = parseAndroidInfo(infoReader, c);
    122     }
    123 
    124     /**
    125      * Constructs a FlashingResourcesParser with the supplied AndroidInfo Reader
    126      * <p/>
    127      * Exposed for unit testing
    128      *
    129      * @param infoReader a {@link BufferedReader} containing the equivalent of android-info.txt to
    130      *        parse
    131      */
    132     public FlashingResourcesParser(BufferedReader infoReader) throws IOException {
    133         this(infoReader, null);
    134     }
    135 
    136     /**
    137      * {@inheritDoc}
    138      * <p/>
    139      * If multiple versions are listed, get the latest with the assumption that versions sort from
    140      * oldest to newest alphabetically.
    141      */
    142     @Override
    143     public String getRequiredBootloaderVersion() {
    144         return getRequiredImageVersion(BOOTLOADER_VERSION_KEY);
    145     }
    146 
    147     /**
    148      * {@inheritDoc}
    149      * <p/>
    150      * If multiple versions are listed, get the latest with the assumption that versions sort from
    151      * oldest to newest alphabetically.
    152      */
    153     @Override
    154     public String getRequiredBasebandVersion() {
    155         return getRequiredImageVersion(BASEBAND_VERSION_KEY);
    156     }
    157 
    158     /**
    159      * {@inheritDoc}
    160      * <p/>
    161      * If multiple versions are listed, get the latest with the assumption that versions sort from
    162      * oldest to newest alphabetically.
    163      */
    164     @Override
    165     public String getRequiredImageVersion(String imageVersionKey) {
    166         // Use null to designate the global product requirements
    167         return getRequiredImageVersion(imageVersionKey, null);
    168     }
    169 
    170     /**
    171      * {@inheritDoc}
    172      * <p/>
    173      * If multiple versions are listed, get the latest with the assumption that versions sort from
    174      * oldest to newest alphabetically.
    175      */
    176     @Override
    177     public String getRequiredImageVersion(String imageVersionKey, String productName) {
    178         MultiMap<String, String> productReqs = mReqs.get(productName);
    179 
    180         if (productReqs == null && productName != null) {
    181             // There aren't any product-specific requirements for productName.  Fall back to global
    182             // requirements.
    183             return getRequiredImageVersion(imageVersionKey, null);
    184         }
    185 
    186         // Get the latest version assuming versions are sorted alphabetically.
    187         String result = getNewest(productReqs.get(imageVersionKey));
    188 
    189         if (result != null) {
    190             // If there's a result, return it
    191             return result;
    192         }
    193         if (result == null && productName != null) {
    194             // There aren't any product-specific requirements for this particular imageVersionKey
    195             // for productName.  Fall back to global requirements.
    196             return getRequiredImageVersion(imageVersionKey, null);
    197         }
    198 
    199         // Neither a specific nor a global result exists; return null
    200         return null;
    201     }
    202 
    203     /**
    204      * {@inheritDoc}
    205      */
    206     @Override
    207     public Collection<String> getRequiredBoards() {
    208         Collection<String> all = new ArrayList<String>();
    209         MultiMap<String, String> boardReqs = mReqs.get(null);
    210         if (boardReqs == null) {
    211             return null;
    212         }
    213 
    214         Collection<String> board = boardReqs.get(BOARD_KEY);
    215         Collection<String> product = boardReqs.get(PRODUCT_KEY);
    216 
    217         // board overrides product here
    218         if (board != null) {
    219             all.addAll(board);
    220         } else if (product != null) {
    221             all.addAll(product);
    222         } else {
    223             return null;
    224         }
    225 
    226         return all;
    227     }
    228 
    229     /**
    230      * Gets the newest element in the given {@link Collection} or <code>null</code> with the
    231      * assumption that newer elements follow older elements when sorted alphabetically.
    232      */
    233     private static String getNewest(Collection<String> values) {
    234         if (values == null || values.isEmpty()) {
    235             return null;
    236         }
    237         String newest = null;
    238         for (String element : values) {
    239             if (newest == null || element.compareTo(newest) > 0) {
    240                 newest = element;
    241             }
    242         }
    243         return newest;
    244     }
    245 
    246     /**
    247      * This parses android-info.txt from system image zip and returns key value pairs of required
    248      * image files.
    249      * <p/>
    250      * Expects the following syntax:
    251      * <p/>
    252      * <i>[require] key=value1[|value2]</i>
    253      *
    254      * @return a {@link Map} of parsed key value pairs, or <code>null</code> if data could not be
    255      * parsed
    256      */
    257     static AndroidInfo getBuildRequirements(File deviceImgZipFile,
    258             Map<String, Constraint> constraints) throws TargetSetupError {
    259         ZipFile deviceZip = null;
    260         BufferedReader infoReader = null;
    261         try {
    262             deviceZip = new ZipFile(deviceImgZipFile);
    263             ZipEntry androidInfoEntry = deviceZip.getEntry(ANDROID_INFO_FILE_NAME);
    264             if (androidInfoEntry == null) {
    265                 DeviceDescriptor nullDescriptor = null;
    266                 throw new TargetSetupError(String.format("Could not find %s in device image zip %s",
    267                         ANDROID_INFO_FILE_NAME, deviceImgZipFile.getName()), nullDescriptor);
    268             }
    269             infoReader = new BufferedReader(new InputStreamReader(
    270                     deviceZip.getInputStream(androidInfoEntry)));
    271 
    272             return parseAndroidInfo(infoReader, constraints);
    273         } catch (ZipException e) {
    274             throw new TargetSetupError(String.format("Could not read device image zip %s",
    275                     deviceImgZipFile.getName()), e, null);
    276         } catch (IOException e) {
    277             throw new TargetSetupError(String.format("Could not read device image zip %s",
    278                     deviceImgZipFile.getName()), e, null);
    279         } finally {
    280             if (deviceZip != null) {
    281                 try {
    282                     deviceZip.close();
    283                 } catch (IOException e) {
    284                     // ignore
    285                 }
    286             }
    287             if (infoReader != null) {
    288                 try {
    289                     infoReader.close();
    290                 } catch (IOException e) {
    291                     // ignore
    292                 }
    293             }
    294         }
    295     }
    296 
    297     /**
    298      * Returns the current value for the provided key if one exists, or creates and returns a new
    299      * value if one does not exist.
    300      */
    301     private static MultiMap<String, String> getOrCreateEntry(AndroidInfo map, String key) {
    302         if (map.containsKey(key)) {
    303             return map.get(key);
    304         } else {
    305             MultiMap<String, String> value = new MultiMap<String, String>();
    306             map.put(key, value);
    307             return value;
    308         }
    309     }
    310 
    311     /**
    312      * Parses the required build attributes from an android-info data source.
    313      * <p/>
    314      * Exposed as package-private for unit testing.
    315      *
    316      * @param infoReader the {@link BufferedReader} to read android-info text data from
    317      * @return a Map of parsed attribute name-value pairs
    318      * @throws IOException
    319      */
    320     static AndroidInfo parseAndroidInfo(BufferedReader infoReader,
    321             Map<String, Constraint> constraints) throws IOException {
    322         AndroidInfo requiredImageMap = new AndroidInfo();
    323 
    324         boolean eof = false;
    325         while (!eof) {
    326             String line = infoReader.readLine();
    327             if (line != null) {
    328                 Matcher matcher = PRODUCT_REQUIRE_PATTERN.matcher(line);
    329                 if (matcher.matches()) {
    330                     String product = matcher.group(1);
    331                     String key = matcher.group(2);
    332                     String values = matcher.group(3);
    333                     // Requirements specific to product {@code product}
    334                     MultiMap<String, String> reqs = getOrCreateEntry(requiredImageMap, product);
    335                     for (String value : values.split("\\|")) {
    336                         reqs.put(key, value);
    337                     }
    338                 } else {
    339                     matcher = REQUIRE_PATTERN.matcher(line);
    340                     if (matcher.matches()) {
    341                         String key = matcher.group(1);
    342                         String values = matcher.group(2);
    343                         Constraint c = null;
    344                         if (constraints != null) {
    345                             c = constraints.get(key);
    346                         }
    347 
    348                         // Use a null product identifier to designate requirements for all products
    349                         MultiMap<String, String> reqs = getOrCreateEntry(requiredImageMap, null);
    350                         for (String value : values.split("\\|")) {
    351                             if ((c == null) || c.shouldAccept(value)) {
    352                                 reqs.put(key, value);
    353                             }
    354                         }
    355                     }
    356                 }
    357             } else {
    358                 eof = true;
    359             }
    360         }
    361         return requiredImageMap;
    362     }
    363 }
    364