• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.sdklib.internal.export;
18 
19 import com.android.sdklib.SdkConstants;
20 import com.android.sdklib.io.FileWrapper;
21 import com.android.sdklib.io.IAbstractFile;
22 import com.android.sdklib.io.StreamException;
23 import com.android.sdklib.xml.AndroidManifestParser;
24 import com.android.sdklib.xml.ManifestData;
25 import com.android.sdklib.xml.ManifestData.SupportsScreens;
26 
27 import org.xml.sax.SAXException;
28 
29 import java.io.BufferedReader;
30 import java.io.File;
31 import java.io.FileInputStream;
32 import java.io.FileOutputStream;
33 import java.io.IOException;
34 import java.io.InputStreamReader;
35 import java.io.OutputStreamWriter;
36 import java.io.PrintStream;
37 import java.util.ArrayList;
38 import java.util.Calendar;
39 import java.util.Collections;
40 import java.util.Formatter;
41 import java.util.HashMap;
42 import java.util.List;
43 import java.util.Map;
44 
45 import javax.xml.parsers.ParserConfigurationException;
46 
47 /**
48  * Helper to export multiple APKs from 1 or or more projects.
49  * <strong>This class is not meant to be accessed from multiple threads</strong>
50  */
51 public class MultiApkExportHelper {
52 
53     private final static String PROP_VERSIONCODE = "versionCode";
54     private final static String PROP_PACKAGE = "package";
55 
56     private final String mExportProjectRoot;
57     private final String mAppPackage;
58     private final int mVersionCode;
59     private final Target mTarget;
60 
61     private ArrayList<ProjectConfig> mProjectList;
62     private ArrayList<ApkData> mApkDataList;
63 
64     final static int MAX_MINOR = 100;
65     final static int MAX_BUILDINFO = 100;
66     final static int OFFSET_BUILD_INFO = MAX_MINOR;
67     final static int OFFSET_VERSION_CODE = OFFSET_BUILD_INFO * MAX_BUILDINFO;
68 
69     private final static String FILE_CONFIG = "projects.config";
70     private final static String FILE_MINOR_CODE = "minor.codes";
71     private final static String FOLDER_LOG = "logs";
72     private final PrintStream mStdio;
73 
74     public static final class ExportException extends Exception {
75         private static final long serialVersionUID = 1L;
76 
ExportException(String message)77         public ExportException(String message) {
78             super(message);
79         }
80 
ExportException(String format, Object... args)81         public ExportException(String format, Object... args) {
82             super(String.format(format, args));
83         }
84 
ExportException(Throwable cause, String format, Object... args)85         public ExportException(Throwable cause, String format, Object... args) {
86             super(String.format(format, args), cause);
87         }
88 
ExportException(String message, Throwable cause)89         public ExportException(String message, Throwable cause) {
90             super(message, cause);
91         }
92     }
93 
94     public static enum Target {
95         RELEASE("release"), CLEAN("clean");
96 
97         private final String mName;
98 
Target(String name)99         Target(String name) {
100             mName = name;
101         }
102 
getTarget()103         public String getTarget() {
104             return mName;
105         }
106 
getTarget(String value)107         public static Target getTarget(String value) {
108             for (Target t : values()) {
109                 if (t.mName.equals(value)) {
110                     return t;
111                 }
112 
113             }
114 
115             return null;
116         }
117     }
118 
MultiApkExportHelper(String exportProjectRoot, String appPackage, int versionCode, Target target, PrintStream stdio)119     public MultiApkExportHelper(String exportProjectRoot, String appPackage,
120             int versionCode, Target target, PrintStream stdio) {
121         mExportProjectRoot = exportProjectRoot;
122         mAppPackage = appPackage;
123         mVersionCode = versionCode;
124         mTarget = target;
125         mStdio = stdio;
126     }
127 
getApkData(String projectList)128     public List<ApkData> getApkData(String projectList) throws ExportException {
129         if (mTarget != Target.RELEASE) {
130             throw new IllegalArgumentException("getApkData must only be called for Target.RELEASE");
131         }
132 
133         // get the list of apk to export and their configuration.
134         List<ProjectConfig> projects = getProjects(projectList);
135 
136         // look to see if there's a config file from a previous export
137         File configProp = new File(mExportProjectRoot, FILE_CONFIG);
138         if (configProp.isFile()) {
139             compareProjectsToConfigFile(projects, configProp);
140         }
141 
142         // look to see if there's a minor properties file
143         File minorCodeProp = new File(mExportProjectRoot, FILE_MINOR_CODE);
144         Map<Integer, Integer> minorCodeMap = null;
145         if (minorCodeProp.isFile()) {
146             minorCodeMap = getMinorCodeMap(minorCodeProp);
147         }
148 
149         // get the apk from the projects.
150         return getApkData(projects, minorCodeMap);
151     }
152 
153     /**
154      * Returns the list of projects defined by the <var>projectList</var> string.
155      * The projects are checked to be valid Android project and to represent a valid set
156      * of projects for multi-apk export.
157      * If a project does not exist or is not valid, the method will throw a {@link BuildException}.
158      * The string must be a list of paths, relative to the export project path (given to
159      * {@link #MultiApkExportHelper(String, String, int, Target)}), separated by the colon (':')
160      * character. The path separator is expected to be forward-slash ('/') on all platforms.
161      * @param projects the string containing all the relative paths to the projects. This is
162      * usually read from export.properties.
163      * @throws ExportException
164      */
getProjects(String projectList)165     public List<ProjectConfig> getProjects(String projectList) throws ExportException {
166         String[] paths = projectList.split("\\:");
167 
168         mProjectList = new ArrayList<ProjectConfig>();
169 
170         for (String path : paths) {
171             path = path.replaceAll("\\/", File.separator);
172             processProject(path, mProjectList);
173         }
174 
175         return mProjectList;
176     }
177 
178     /**
179      * Writes post-export logs and other files.
180      * @throws ExportException if writing the files failed.
181      */
writeLogs()182     public void writeLogs() throws ExportException {
183         writeConfigProperties();
184         writeMinorVersionProperties();
185         writeApkLog();
186     }
187 
writeConfigProperties()188     private void writeConfigProperties() throws ExportException {
189         OutputStreamWriter writer = null;
190         try {
191             writer = new OutputStreamWriter(
192                     new FileOutputStream(new File(mExportProjectRoot, FILE_CONFIG)));
193 
194             writer.append("# PROJECT CONFIG -- DO NOT DELETE.\n");
195             writeValue(writer, PROP_VERSIONCODE, mVersionCode);
196 
197             for (ProjectConfig project : mProjectList) {
198                 writeValue(writer,project.getRelativePath(),
199                         project.getConfigString(false /*onlyManifestData*/));
200             }
201 
202             writer.flush();
203         } catch (Exception e) {
204             throw new ExportException("Failed to write config log", e);
205         } finally {
206             try {
207                 if (writer != null) {
208                     writer.close();
209                 }
210             } catch (IOException e) {
211                 throw new ExportException("Failed to write config log", e);
212             }
213         }
214     }
215 
writeMinorVersionProperties()216     private void writeMinorVersionProperties() throws ExportException {
217         OutputStreamWriter writer = null;
218         try {
219             writer = new OutputStreamWriter(
220                     new FileOutputStream(new File(mExportProjectRoot, FILE_MINOR_CODE)));
221 
222             writer.append(
223                     "# Minor version codes.\n" +
224                     "# To create update to select APKs without updating the main versionCode\n" +
225                     "# edit this file and manually increase the minor version for the select\n" +
226                     "# build info.\n" +
227                     "# Format of the file is <buildinfo>:<minor>\n");
228             writeValue(writer, PROP_VERSIONCODE, mVersionCode);
229 
230             for (ApkData apk : mApkDataList) {
231                 writeValue(writer, Integer.toString(apk.getBuildInfo()), apk.getMinorCode());
232             }
233 
234             writer.flush();
235         } catch (Exception e) {
236             throw new ExportException("Failed to write minor log", e);
237         } finally {
238             try {
239                 if (writer != null) {
240                     writer.close();
241                 }
242             } catch (IOException e) {
243                 throw new ExportException("Failed to write minor log", e);
244             }
245         }
246     }
247 
writeApkLog()248     private void writeApkLog() throws ExportException {
249         OutputStreamWriter writer = null;
250         try {
251             File logFolder = new File(mExportProjectRoot, FOLDER_LOG);
252             if (logFolder.isFile()) {
253                 throw new ExportException("Cannot create folder '%1$s', file is in the way!",
254                         FOLDER_LOG);
255             } else if (logFolder.exists() == false) {
256                 logFolder.mkdir();
257             }
258 
259             Formatter formatter = new Formatter();
260             formatter.format("%1$s.%2$d-%3$tY%3$tm%3$td-%3$tH%3$tM.log",
261                     mAppPackage, mVersionCode,
262                     Calendar.getInstance().getTime());
263 
264             writer = new OutputStreamWriter(
265                     new FileOutputStream(new File(logFolder, formatter.toString())));
266 
267             writer.append("# Multi-APK BUILD LOG.\n");
268             writeValue(writer, PROP_PACKAGE, mAppPackage);
269             writeValue(writer, PROP_VERSIONCODE, mVersionCode);
270 
271             for (ApkData apk : mApkDataList) {
272                 // if there are soft variant, do not display the main log line, as it's not actually
273                 // exported.
274                 Map<String, String> softVariants = apk.getSoftVariantMap();
275                 if (softVariants.size() > 0) {
276                     for (String softVariant : softVariants.keySet()) {
277                         writer.append(apk.getLogLine(softVariant));
278                         writer.append('\n');
279                     }
280                 } else {
281                     writer.append(apk.getLogLine(null));
282                     writer.append('\n');
283                 }
284             }
285 
286             writer.flush();
287         } catch (Exception e) {
288             throw new ExportException("Failed to write build log", e);
289         } finally {
290             try {
291                 if (writer != null) {
292                     writer.close();
293                 }
294             } catch (IOException e) {
295                 throw new ExportException("Failed to write build log", e);
296             }
297         }
298     }
299 
writeValue(OutputStreamWriter writer, String name, String value)300     private void writeValue(OutputStreamWriter writer, String name, String value)
301             throws IOException {
302         writer.append(name).append(':').append(value).append('\n');
303     }
304 
writeValue(OutputStreamWriter writer, String name, int value)305     private void writeValue(OutputStreamWriter writer, String name, int value) throws IOException {
306         writeValue(writer, name, Integer.toString(value));
307     }
308 
getApkData(List<ProjectConfig> projects, Map<Integer, Integer> minorCodes)309     private List<ApkData> getApkData(List<ProjectConfig> projects,
310             Map<Integer, Integer> minorCodes) {
311         mApkDataList = new ArrayList<ApkData>();
312 
313         // get all the apkdata from all the projects
314         for (ProjectConfig config : projects) {
315             mApkDataList.addAll(config.getApkDataList());
316         }
317 
318         // sort the projects and assign buildInfo
319         Collections.sort(mApkDataList);
320         int buildInfo = 0;
321         for (ApkData data : mApkDataList) {
322             data.setBuildInfo(buildInfo);
323             if (minorCodes != null) {
324                 Integer minorCode = minorCodes.get(buildInfo);
325                 if (minorCode != null) {
326                     data.setMinorCode(minorCode);
327                 }
328             }
329 
330             buildInfo++;
331         }
332 
333         return mApkDataList;
334     }
335 
336     /**
337      * Checks a project for inclusion in the list of exported APK.
338      * <p/>This performs a check on the manifest, as well as gathers more information about
339      * mutli-apk from the project's default.properties file.
340      * If the manifest is correct, a list of apk to export is created and returned.
341      *
342      * @param projectFolder the folder of the project to check
343      * @param projects the list of project to file with the project if it passes validation.
344      * @throws ExportException in case of error.
345      */
processProject(String relativePath, ArrayList<ProjectConfig> projects)346     private void processProject(String relativePath,
347             ArrayList<ProjectConfig> projects) throws ExportException {
348 
349         // resolve the relative path
350         File projectFolder;
351         try {
352             File path = new File(mExportProjectRoot, relativePath);
353 
354             projectFolder = path.getCanonicalFile();
355 
356             // project folder must exist and be a directory
357             if (projectFolder.isDirectory() == false) {
358                 throw new ExportException(
359                         "Project folder '%1$s' is not a valid directory.",
360                         projectFolder.getAbsolutePath());
361             }
362         } catch (IOException e) {
363             throw new ExportException(
364                     e, "Failed to resolve path %1$s", relativePath);
365         }
366 
367         try {
368             // Check AndroidManifest.xml is present
369             IAbstractFile androidManifest = new FileWrapper(projectFolder,
370                     SdkConstants.FN_ANDROID_MANIFEST_XML);
371 
372             if (androidManifest.exists() == false) {
373                 throw new ExportException(String.format(
374                         "%1$s is not a valid project (%2$s not found).",
375                         relativePath, androidManifest.getOsLocation()));
376             }
377 
378             // output the relative path resolution.
379             mStdio.println(String.format("%1$s => %2$s", relativePath,
380                     projectFolder.getAbsolutePath()));
381 
382             // parse the manifest of the project.
383             ManifestData manifestData = AndroidManifestParser.parse(androidManifest);
384 
385             // validate the application package name
386             String manifestPackage = manifestData.getPackage();
387             if (mAppPackage.equals(manifestPackage) == false) {
388                 throw new ExportException(
389                         "%1$s package value is not valid. Found '%2$s', expected '%3$s'.",
390                         androidManifest.getOsLocation(), manifestPackage, mAppPackage);
391             }
392 
393             // validate that the manifest has no versionCode set.
394             if (manifestData.getVersionCode() != null) {
395                 throw new ExportException(
396                         "%1$s is not valid: versionCode must not be set for multi-apk export.",
397                         androidManifest.getOsLocation());
398             }
399 
400             // validate that the minSdkVersion is not a codename
401             int minSdkVersion = manifestData.getMinSdkVersion();
402             if (minSdkVersion == ManifestData.MIN_SDK_CODENAME) {
403                 throw new ExportException(
404                         "Codename in minSdkVersion is not supported by multi-apk export.");
405             }
406 
407             // compare to other projects already processed to make sure that they are not
408             // identical.
409             for (ProjectConfig otherProject : projects) {
410                 // Multiple apk export support difference in:
411                 // - min SDK Version
412                 // - Screen version
413                 // - GL version
414                 // - ABI (not managed at the Manifest level).
415                 // if those values are the same between 2 manifest, then it's an error.
416 
417 
418                 // first the minSdkVersion.
419                 if (minSdkVersion == otherProject.getMinSdkVersion()) {
420                     // if it's the same compare the rest.
421                     SupportsScreens currentSS = manifestData.getSupportsScreensValues();
422                     SupportsScreens previousSS = otherProject.getSupportsScreens();
423                     boolean sameSupportsScreens = currentSS.hasSameScreenSupportAs(previousSS);
424 
425                     // if it's the same, then it's an error. Can't export 2 projects that have the
426                     // same approved (for multi-apk export) hard-properties.
427                     if (manifestData.getGlEsVersion() == otherProject.getGlEsVersion() &&
428                             sameSupportsScreens) {
429 
430                         throw new ExportException(
431                                 "Android manifests must differ in at least one of the following values:\n" +
432                                 "- minSdkVersion\n" +
433                                 "- SupportsScreen (screen sizes only)\n" +
434                                 "- GL ES version.\n" +
435                                 "%1$s and %2$s are considered identical for multi-apk export.",
436                                 relativePath,
437                                 otherProject.getRelativePath());
438                     }
439 
440                     // At this point, either supports-screens or GL are different.
441                     // Because supports-screens is the highest priority properties to be
442                     // (potentially) different, we must do some extra checks on it.
443                     // It must either be the same in both projects (difference is only on GL value),
444                     // or follow theses rules:
445                     // - Property in each projects must be strictly different, ie both projects
446                     //   cannot support the same screen size(s).
447                     // - Property in each projects cannot overlap, ie a projects cannot support
448                     //   both a lower and a higher screen size than the other project.
449                     //   (ie APK1 supports small/large and APK2 supports normal).
450                     if (sameSupportsScreens == false) {
451                         if (currentSS.hasStrictlyDifferentScreenSupportAs(previousSS) == false) {
452                             throw new ExportException(
453                                     "APK differentiation by Supports-Screens cannot support different APKs supporting the same screen size.\n" +
454                                     "%1$s supports %2$s\n" +
455                                     "%3$s supports %4$s\n",
456                                     relativePath, currentSS.toString(),
457                                     otherProject.getRelativePath(), previousSS.toString());
458                         }
459 
460                         if (currentSS.overlapWith(previousSS)) {
461                             throw new ExportException(
462                                     "Unable to compute APK priority due to incompatible difference in Supports-Screens values.\n" +
463                                     "%1$s supports %2$s\n" +
464                                     "%3$s supports %4$s\n",
465                                     relativePath, currentSS.toString(),
466                                     otherProject.getRelativePath(), previousSS.toString());
467                         }
468                     }
469                 }
470             }
471 
472             // project passes first validation. Attempt to create a ProjectConfig object.
473 
474             ProjectConfig config = ProjectConfig.create(projectFolder, relativePath, manifestData);
475             projects.add(config);
476         } catch (SAXException e) {
477             throw new ExportException(e, "Failed to validate %1$s", relativePath);
478         } catch (IOException e) {
479             throw new ExportException(e, "Failed to validate %1$s", relativePath);
480         } catch (StreamException e) {
481             throw new ExportException(e, "Failed to validate %1$s", relativePath);
482         } catch (ParserConfigurationException e) {
483             throw new ExportException(e, "Failed to validate %1$s", relativePath);
484         }
485     }
486 
487     /**
488      * Checks an existing list of {@link ProjectConfig} versus a config file.
489      * @param projects the list of projects to check
490      * @param configProp the config file (must have been generated from a previous export)
491      * @return true if the projects and config file match
492      * @throws ExportException in case of error
493      */
compareProjectsToConfigFile(List<ProjectConfig> projects, File configProp)494     private void compareProjectsToConfigFile(List<ProjectConfig> projects, File configProp)
495             throws ExportException {
496         InputStreamReader reader = null;
497         BufferedReader bufferedReader = null;
498         try {
499             reader = new InputStreamReader(new FileInputStream(configProp));
500             bufferedReader = new BufferedReader(reader);
501             String line;
502 
503             // List of the ProjectConfig that need to be checked. This is to detect
504             // new Projects added to the setup.
505             // removed projects are detected when an entry in the config file doesn't match
506             // any ProjectConfig in the list.
507             ArrayList<ProjectConfig> projectsToCheck = new ArrayList<ProjectConfig>();
508             projectsToCheck.addAll(projects);
509 
510             // store the project that doesn't match.
511             ProjectConfig badMatch = null;
512             String errorMsg = null;
513 
514             // recorded whether we checked the version code. this is for when we compare
515             // a project config
516             boolean checkedVersion = false;
517 
518             int lineNumber = 0;
519             while ((line = bufferedReader.readLine()) != null) {
520                 lineNumber++;
521                 line = line.trim();
522                 if (line.length() == 0 || line.startsWith("#")) {
523                     continue;
524                 }
525 
526                 // read the name of the property
527                 int colonPos = line.indexOf(':');
528                 if (colonPos == -1) {
529                     // looks like there's an invalid line!
530                     throw new ExportException(
531                             "Failed to read existing build log. Line %d is not a property line.",
532                             lineNumber);
533                 }
534 
535                 String name = line.substring(0, colonPos);
536                 String value = line.substring(colonPos + 1);
537 
538                 if (PROP_VERSIONCODE.equals(name)) {
539                     try {
540                         int versionCode = Integer.parseInt(value);
541                         if (versionCode < mVersionCode) {
542                             // this means this config file is obsolete and we can ignore it.
543                             return;
544                         } else if (versionCode > mVersionCode) {
545                             // we're exporting at a lower versionCode level than the config file?
546                             throw new ExportException(
547                                     "Incompatible versionCode: Exporting at versionCode %1$d but %2$s file indicate previous export with versionCode %3$d.",
548                                     mVersionCode, FILE_CONFIG, versionCode);
549                         } else if (badMatch != null) {
550                             // looks like versionCode is a match, but a project
551                             // isn't compatible.
552                             break;
553                         } else {
554                             // record that we did check the versionCode
555                             checkedVersion = true;
556                         }
557                     } catch (NumberFormatException e) {
558                         throw new ExportException(
559                                 "Failed to read integer property %1$s at line %2$d.",
560                                 PROP_VERSIONCODE, lineNumber);
561                     }
562                 } else {
563                     // looks like this is (or should be) a project line.
564                     // name of the property is the relative path.
565                     // look for a matching project.
566                     ProjectConfig found = null;
567                     for (int i = 0 ; i < projectsToCheck.size() ; i++) {
568                         ProjectConfig p = projectsToCheck.get(i);
569                         if (p.getRelativePath().equals(name)) {
570                             found = p;
571                             projectsToCheck.remove(i);
572                             break;
573                         }
574                     }
575 
576                     if (found == null) {
577                         // deleted project!
578                         throw new ExportException(
579                                 "Project %1$s has been removed from the list of projects to export.\n" +
580                                 "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
581                                 name);
582                     } else {
583                         // make a map of properties
584                         HashMap<String, String> map = new HashMap<String, String>();
585                         String[] properties = value.split(";");
586                         for (String prop : properties) {
587                             int equalPos = prop.indexOf('=');
588                             map.put(prop.substring(0, equalPos), prop.substring(equalPos + 1));
589                         }
590 
591                         errorMsg = found.compareToProperties(map);
592                         if (errorMsg != null) {
593                             // bad project config, record the project
594                             badMatch = found;
595 
596                             // if we've already checked that the versionCode didn't already change
597                             // we stop right away.
598                             if (checkedVersion) {
599                                 break;
600                             }
601                         }
602                     }
603 
604                 }
605 
606             }
607 
608             if (badMatch != null) {
609                 throw new ExportException(
610                         "Config for project %1$s has changed from previous export with versionCode %2$d:\n" +
611                         "\t%3$s\n" +
612                         "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
613                         badMatch.getRelativePath(), mVersionCode, errorMsg);
614             } else if (projectsToCheck.size() > 0) {
615                 throw new ExportException(
616                         "Project %1$s was not part of the previous export with versionCode %2$d.\n" +
617                         "Any change in the multi-apk configuration requires an increment of the versionCode in export.properties.",
618                         projectsToCheck.get(0).getRelativePath(), mVersionCode);
619             }
620 
621         } catch (IOException e) {
622             throw new ExportException(e, "Failed to read existing config log: %s", FILE_CONFIG);
623         } finally {
624             try {
625                 if (reader != null) {
626                     reader.close();
627                 }
628             } catch (IOException e) {
629                 throw new ExportException(e, "Failed to read existing config log: %s", FILE_CONFIG);
630             }
631         }
632     }
633 
getMinorCodeMap(File minorProp)634     private Map<Integer, Integer> getMinorCodeMap(File minorProp) throws ExportException {
635         InputStreamReader reader = null;
636         BufferedReader bufferedReader = null;
637         try {
638             reader = new InputStreamReader(new FileInputStream(minorProp));
639             bufferedReader = new BufferedReader(reader);
640             String line;
641 
642             boolean foundVersionCode = false;
643             Map<Integer, Integer> map = new HashMap<Integer, Integer>();
644 
645             int lineNumber = 0;
646             while ((line = bufferedReader.readLine()) != null) {
647                 lineNumber++;
648                 line = line.trim();
649                 if (line.length() == 0 || line.startsWith("#")) {
650                     continue;
651                 }
652 
653                 // read the name of the property
654                 int colonPos = line.indexOf(':');
655                 if (colonPos == -1) {
656                     // looks like there's an invalid line!
657                     throw new ExportException(
658                             "Failed to read existing build log. Line %d is not a property line.",
659                             lineNumber);
660                 }
661 
662                 String name = line.substring(0, colonPos);
663                 String value = line.substring(colonPos + 1);
664 
665                 if (PROP_VERSIONCODE.equals(name)) {
666                     try {
667                         int versionCode = Integer.parseInt(value);
668                         if (versionCode < mVersionCode) {
669                             // this means this minor file is obsolete and we can ignore it.
670                             return null;
671                         } else if (versionCode > mVersionCode) {
672                             // we're exporting at a lower versionCode level than the minor file?
673                             throw new ExportException(
674                                     "Incompatible versionCode: Exporting at versionCode %1$d but %2$s file indicate previous export with versionCode %3$d.",
675                                     mVersionCode, FILE_MINOR_CODE, versionCode);
676                         }
677                         foundVersionCode = true;
678                     } catch (NumberFormatException e) {
679                         throw new ExportException(
680                                 "Failed to read integer property %1$s at line %2$d.",
681                                 PROP_VERSIONCODE, lineNumber);
682                     }
683                 } else {
684                     try {
685                         map.put(Integer.valueOf(name), Integer.valueOf(value));
686                     } catch (NumberFormatException e) {
687                         throw new ExportException(
688                                 "Failed to read buildInfo property '%1$s' at line %2$d.",
689                                 line, lineNumber);
690                     }
691                 }
692             }
693 
694             // if there was no versionCode found, we can't garantee that the minor version
695             // found are for this versionCode
696             if (foundVersionCode == false) {
697                 throw new ExportException(
698                         "%1$s property missing from file %2$s.", PROP_VERSIONCODE, FILE_MINOR_CODE);
699             }
700 
701             return map;
702         } catch (IOException e) {
703             throw new ExportException(e, "Failed to read existing minor log: %s", FILE_MINOR_CODE);
704         } finally {
705             try {
706                 if (reader != null) {
707                     reader.close();
708                 }
709             } catch (IOException e) {
710                 throw new ExportException(e, "Failed to read existing minor log: %s",
711                         FILE_MINOR_CODE);
712             }
713         }
714     }
715 }
716