/*
*
*
*
* Apache License
* Version 2.0, January 2004
* http://www.apache.org/licenses/
*
* TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
*
* 1. Definitions.
*
* "License" shall mean the terms and conditions for use, reproduction,
* and distribution as defined by Sections 1 through 9 of this document.
*
* "Licensor" shall mean the copyright owner or entity authorized by
* the copyright owner that is granting the License.
*
* "Legal Entity" shall mean the union of the acting entity and all
* other entities that control, are controlled by, or are under common
* control with that entity. For the purposes of this definition,
* "control" means (i) the power, direct or indirect, to cause the
* direction or management of such entity, whether by contract or
* otherwise, or (ii) ownership of fifty percent (50%) or more of the
* outstanding shares, or (iii) beneficial ownership of such entity.
*
* "You" (or "Your") shall mean an individual or Legal Entity
* exercising permissions granted by this License.
*
* "Source" form shall mean the preferred form for making modifications,
* including but not limited to software source code, documentation
* source, and configuration files.
*
* "Object" form shall mean any form resulting from mechanical
* transformation or translation of a Source form, including but
* not limited to compiled object code, generated documentation,
* and conversions to other media types.
*
* "Work" shall mean the work of authorship, whether in Source or
* Object form, made available under the License, as indicated by a
* copyright notice that is included in or attached to the work
* (an example is provided in the Appendix below).
*
* "Derivative Works" shall mean any work, whether in Source or Object
* form, that is based on (or derived from) the Work and for which the
* editorial revisions, annotations, elaborations, or other modifications
* represent, as a whole, an original work of authorship. For the purposes
* of this License, Derivative Works shall not include works that remain
* separable from, or merely link (or bind by name) to the interfaces of,
* the Work and Derivative Works thereof.
*
* "Contribution" shall mean any work of authorship, including
* the original version of the Work and any modifications or additions
* to that Work or Derivative Works thereof, that is intentionally
* submitted to Licensor for inclusion in the Work by the copyright owner
* or by an individual or Legal Entity authorized to submit on behalf of
* the copyright owner. For the purposes of this definition, "submitted"
* means any form of electronic, verbal, or written communication sent
* to the Licensor or its representatives, including but not limited to
* communication on electronic mailing lists, source code control systems,
* and issue tracking systems that are managed by, or on behalf of, the
* Licensor for the purpose of discussing and improving the Work, but
* excluding communication that is conspicuously marked or otherwise
* designated in writing by the copyright owner as "Not a Contribution."
*
* "Contributor" shall mean Licensor and any individual or Legal Entity
* on behalf of whom a Contribution has been received by Licensor and
* subsequently incorporated within the Work.
*
* 2. Grant of Copyright License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* copyright license to reproduce, prepare Derivative Works of,
* publicly display, publicly perform, sublicense, and distribute the
* Work and such Derivative Works in Source or Object form.
*
* 3. Grant of Patent License. Subject to the terms and conditions of
* this License, each Contributor hereby grants to You a perpetual,
* worldwide, non-exclusive, no-charge, royalty-free, irrevocable
* (except as stated in this section) patent license to make, have made,
* use, offer to sell, sell, import, and otherwise transfer the Work,
* where such license applies only to those patent claims licensable
* by such Contributor that are necessarily infringed by their
* Contribution(s) alone or by combination of their Contribution(s)
* with the Work to which such Contribution(s) was submitted. If You
* institute patent litigation against any entity (including a
* cross-claim or counterclaim in a lawsuit) alleging that the Work
* or a Contribution incorporated within the Work constitutes direct
* or contributory patent infringement, then any patent licenses
* granted to You under this License for that Work shall terminate
* as of the date such litigation is filed.
*
* 4. Redistribution. You may reproduce and distribute copies of the
* Work or Derivative Works thereof in any medium, with or without
* modifications, and in Source or Object form, provided that You
* meet the following conditions:
*
* (a) You must give any other recipients of the Work or
* Derivative Works a copy of this License; and
*
* (b) You must cause any modified files to carry prominent notices
* stating that You changed the files; and
*
* (c) You must retain, in the Source form of any Derivative Works
* that You distribute, all copyright, patent, trademark, and
* attribution notices from the Source form of the Work,
* excluding those notices that do not pertain to any part of
* the Derivative Works; and
*
* (d) If the Work includes a "NOTICE" text file as part of its
* distribution, then any Derivative Works that You distribute must
* include a readable copy of the attribution notices contained
* within such NOTICE file, excluding those notices that do not
* pertain to any part of the Derivative Works, in at least one
* of the following places: within a NOTICE text file distributed
* as part of the Derivative Works; within the Source form or
* documentation, if provided along with the Derivative Works; or,
* within a display generated by the Derivative Works, if and
* wherever such third-party notices normally appear. The contents
* of the NOTICE file are for informational purposes only and
* do not modify the License. You may add Your own attribution
* notices within Derivative Works that You distribute, alongside
* or as an addendum to the NOTICE text from the Work, provided
* that such additional attribution notices cannot be construed
* as modifying the License.
*
* You may add Your own copyright statement to Your modifications and
* may provide additional or different license terms and conditions
* for use, reproduction, or distribution of Your modifications, or
* for any such Derivative Works as a whole, provided Your use,
* reproduction, and distribution of the Work otherwise complies with
* the conditions stated in this License.
*
* 5. Submission of Contributions. Unless You explicitly state otherwise,
* any Contribution intentionally submitted for inclusion in the Work
* by You to the Licensor shall be under the terms and conditions of
* this License, without any additional terms or conditions.
* Notwithstanding the above, nothing herein shall supersede or modify
* the terms of any separate license agreement you may have executed
* with Licensor regarding such Contributions.
*
* 6. Trademarks. This License does not grant permission to use the trade
* names, trademarks, service marks, or product names of the Licensor,
* except as required for reasonable and customary use in describing the
* origin of the Work and reproducing the content of the NOTICE file.
*
* 7. Disclaimer of Warranty. Unless required by applicable law or
* agreed to in writing, Licensor provides the Work (and each
* Contributor provides its Contributions) on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
* implied, including, without limitation, any warranties or conditions
* of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
* PARTICULAR PURPOSE. You are solely responsible for determining the
* appropriateness of using or redistributing the Work and assume any
* risks associated with Your exercise of permissions under this License.
*
* 8. Limitation of Liability. In no event and under no legal theory,
* whether in tort (including negligence), contract, or otherwise,
* unless required by applicable law (such as deliberate and grossly
* negligent acts) or agreed to in writing, shall any Contributor be
* liable to You for damages, including any direct, indirect, special,
* incidental, or consequential damages of any character arising as a
* result of this License or out of the use or inability to use the
* Work (including but not limited to damages for loss of goodwill,
* work stoppage, computer failure or malfunction, or any and all
* other commercial damages or losses), even if such Contributor
* has been advised of the possibility of such damages.
*
* 9. Accepting Warranty or Additional Liability. While redistributing
* the Work or Derivative Works thereof, You may choose to offer,
* and charge a fee for, acceptance of support, warranty, indemnity,
* or other liability obligations and/or rights consistent with this
* License. However, in accepting such obligations, You may act only
* on Your own behalf and on Your sole responsibility, not on behalf
* of any other Contributor, and only if You agree to indemnify,
* defend, and hold each Contributor harmless for any liability
* incurred by, or claims asserted against, such Contributor by reason
* of your accepting any such warranty or additional liability.
*
* END OF TERMS AND CONDITIONS
*
* APPENDIX: How to apply the Apache License to your work.
*
* To apply the Apache License to your work, attach the following
* boilerplate notice, with the fields enclosed by brackets "[]"
* replaced with your own identifying information. (Don't include
* the brackets!) The text should be enclosed in the appropriate
* comment syntax for the file format. We also recommend that a
* file or class name and description of purpose be included on the
* same "printed page" as the copyright notice for easier
* identification within third-party archives.
*
* Copyright 2016 Alibaba Group
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*
*/
package com.android.builder.core;
import com.android.SdkConstants;
import com.android.annotations.NonNull;
import com.android.annotations.Nullable;
import com.android.apkzlib.zfile.ApkCreatorFactory;
import com.android.apkzlib.zfile.NativeLibrariesPackagingMode;
import com.android.build.gradle.internal.LoggerWrapper;
import com.android.build.gradle.internal.dsl.AaptOptions;
import com.android.builder.files.NativeLibraryAbiPredicate;
import com.android.builder.files.RelativeFile;
import com.android.builder.files.RelativeFiles;
import com.android.builder.internal.aapt.Aapt;
import com.android.builder.internal.aapt.AaptPackageConfig.Builder;
import com.android.builder.internal.aapt.v1.AaptV1;
import com.android.builder.internal.aapt.v1.AaptV1.PngProcessMode;
import com.android.builder.internal.packaging.OldPackager;
import com.android.builder.model.AndroidLibrary;
import com.android.builder.model.SigningConfig;
import com.android.builder.packaging.PackagerException;
import com.android.builder.packaging.SigningException;
import com.android.builder.sdk.SdkInfo;
import com.android.builder.sdk.TargetInfo;
import com.android.dex.Dex;
import com.android.dx.command.dexer.DxContext;
import com.android.dx.merge.CollisionPolicy;
import com.android.dx.merge.DexMerger;
import com.android.ide.common.process.JavaProcessExecutor;
import com.android.ide.common.process.ProcessException;
import com.android.ide.common.process.ProcessExecutor;
import com.android.ide.common.process.ProcessInfo;
import com.android.ide.common.process.ProcessOutputHandler;
import com.android.ide.common.process.ProcessResult;
import com.android.ide.common.res2.FileStatus;
import com.android.ide.common.signing.KeytoolException;
import com.android.sdklib.BuildToolInfo;
import com.android.sdklib.BuildToolInfo.PathId;
import com.android.sdklib.internal.build.SymbolLoader;
import com.android.sdklib.internal.build.SymbolWriter;
import com.android.utils.ILogger;
import com.google.common.base.Functions;
import com.google.common.base.Predicate;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.Maps;
import com.google.common.collect.Multimap;
import com.taobao.android.builder.AtlasBuildContext;
import com.taobao.android.builder.extension.AtlasExtension;
import com.taobao.android.builder.tools.MD5Util;
import com.taobao.android.builder.tools.manifest.ManifestFileUtils;
import com.taobao.android.builder.tools.zip.ZipUtils;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.reflect.FieldUtils;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.gradle.api.GradleException;
import org.gradle.api.tasks.StopExecutionException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.security.PrivateKey;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
/**
* 1. 自定义的Android Builder工具类,支持自定义的aapt操作
* 2. 对dex做了cache优化 , 调整dexOptions参数
*/
public class AtlasBuilder extends AndroidBuilder {
private static Logger sLogger = LoggerFactory.getLogger(AtlasBuilder.class);
protected AtlasExtension atlasExtension;
protected AndroidBuilder defaultBuilder;
/**
* Creates an AndroidBuilder.
* <p/>
* <var>verboseExec</var> is needed on top of the ILogger due to remote exec tools not being
* able to output info and verbose messages separately.
*
* @param projectId
* @param createdBy the createdBy String for the apk manifest.
* @param processExecutor
* @param javaProcessExecutor
* @param errorReporter
* @param logger the Logger
* @param verboseExec whether external tools are launched in verbose mode
*/
public AtlasBuilder(@NonNull String projectId,
@Nullable String createdBy,
@NonNull ProcessExecutor processExecutor,
@NonNull JavaProcessExecutor javaProcessExecutor,
@NonNull ErrorReporter errorReporter,
@NonNull ILogger logger,
boolean verboseExec) {
super(projectId,
createdBy,
processExecutor,
javaProcessExecutor,
errorReporter,
logger,
verboseExec);
}
public AtlasExtension getAtlasExtension() {
return atlasExtension;
}
public void setAtlasExtension(AtlasExtension atlasExtension) {
this.atlasExtension = atlasExtension;
}
public AndroidBuilder getDefaultBuilder() {
return defaultBuilder;
}
public void setDefaultBuilder(AndroidBuilder defaultBuilder) {
this.defaultBuilder = defaultBuilder;
}
@Override
public void processResources(Aapt aapt,
Builder aaptConfigBuilder,
boolean enforceUniquePackageName)
throws IOException, InterruptedException, ProcessException {
if (aapt instanceof AaptV1) {
try {
getTargetInfo();
AaptOptions aaptOptions = (AaptOptions)FieldUtils.readField(aaptConfigBuilder.build(),
"mAaptOptions",
true);
ProcessExecutor processExecutor = (ProcessExecutor)FieldUtils.readField(aapt,
"mProcessExecutor",
true);
ProcessOutputHandler processOutputHandler = (ProcessOutputHandler)FieldUtils.readField(
aapt,
"mProcessOutputHandler",
true);
BuildToolInfo buildToolInfo = (BuildToolInfo)FieldUtils.readField(aapt,
"mBuildToolInfo",
true);
PngProcessMode processMode = (PngProcessMode)FieldUtils.readField(aapt,
"mProcessMode",
true);
int cruncherProcesses = aaptOptions.getCruncherProcesses();
aapt = new AtlasAapt(processExecutor,
processOutputHandler,
buildToolInfo,
getLogger(),
processMode,
cruncherProcesses);
} catch (Throwable e) {
throw new GradleException("aapt exception", e);
}
}
super.processResources(aapt, aaptConfigBuilder, enforceUniquePackageName);
}
/**
* 对主bundle的资源进行处理
*
* @param aaptCommand
* @param enforceUniquePackageName
* @param processOutputHandler
* @throws IOException
* @throws InterruptedException
* @throws ProcessException
*/
public void processResources(AaptPackageProcessBuilder aaptCommand,
boolean enforceUniquePackageName,
ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
checkState(getTargetInfo() != null,
"Cannot call processResources() before setTargetInfo() is called.");
BuildToolInfo buildToolInfo = getTargetInfo().getBuildTools();
// launch aapt: create the command line
ProcessInfo processInfo = aaptCommand.build(buildToolInfo,
getTargetInfo().getTarget(),
getLogger());
processInfo = new TProcessInfo(processInfo);
// 打印日志
// if (null != getLogger()) {
// getLogger().info("[Aapt]" + processInfo.getExecutable() + " "
// + StringUtils.join(processInfo.getArgs(), " "));
// }
ProcessResult result = getProcessExecutor().execute(processInfo, processOutputHandler);
result.rethrowFailure().assertNormalExitValue();
// If the project has libraries, R needs to be created for each library.
if (aaptCommand.getSourceOutputDir() != null && !aaptCommand.getLibraries().isEmpty()) {
SymbolLoader fullSymbolValues = null;
// First pass processing the libraries, collecting them by packageName,
// and ignoring the ones that have the same package name as the application
// (since that R class was already created).
String appPackageName = aaptCommand.getPackageForR();
if (appPackageName == null) {
appPackageName = ManifestFileUtils.getPackage(aaptCommand.getManifestFile());
}
// list of all the symbol loaders per package names.
Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
for (AndroidLibrary lib : aaptCommand.getLibraries()) {
if (lib.isOptional()) {
continue;
}
String packageName = ManifestFileUtils.getPackage(lib.getManifest());
if (appPackageName == null) {
continue;
}
if (appPackageName.equals(packageName)) {
if (enforceUniquePackageName) {
String msg = String.format(
"Error: A library uses the same package as this project: %s",
packageName);
throw new RuntimeException(msg);
}
// ignore libraries that have the same package name as the app
continue;
}
File rFile = lib.getSymbolFile();
// if the library has no resource, this file won't exist.
if (rFile.isFile()) {
// load the full values if that's not already been done.
// Doing it lazily allow us to support the case where there's no
// resources anywhere.
if (fullSymbolValues == null) {
fullSymbolValues = new SymbolLoader(new File(aaptCommand.getSymbolOutputDir(),
"R.txt"));
fullSymbolValues.load();
}
SymbolLoader libSymbols = new SymbolLoader(rFile);
libSymbols.load();
// store these symbols by associating them with the package name.
libMap.put(packageName, libSymbols);
}
}
// now loop on all the package name, merge all the symbols to write, and write them
for (String packageName : libMap.keySet()) {
Collection<SymbolLoader> symbols = libMap.get(packageName);
if (enforceUniquePackageName && symbols.size() > 1) {
String msg = String.format("Error: more than one library with package name '%s'",
packageName);
throw new RuntimeException(msg);
}
SymbolWriter writer = new SymbolWriter(aaptCommand.getSourceOutputDir(),
packageName,
fullSymbolValues);
for (SymbolLoader symbolLoader : symbols) {
writer.addSymbolsToWrite(symbolLoader);
}
writer.write();
}
}
}
/**
* 处理awb的资源
*
* @param aaptCommand
* @param enforceUniquePackageName
* @param processOutputHandler
* @param mainSymbolFile
*/
public void processAwbResources(AaptPackageProcessBuilder aaptCommand,
boolean enforceUniquePackageName,
ProcessOutputHandler processOutputHandler,
File mainSymbolFile) throws IOException, InterruptedException, ProcessException {
if (!atlasExtension.getTBuildConfig().getUseCustomAapt()) {
throw new StopExecutionException("Must set useCustomAapt value to true for awb build!");
}
checkState(getTargetInfo() != null,
"Cannot call processResources() before setTargetInfo() is called.");
// launch aapt: create the command line
ProcessInfo processInfo = aaptCommand.build(getTargetInfo().getBuildTools(),
getTargetInfo().getTarget(),
getLogger());
processInfo = new TProcessInfo(processInfo, aaptCommand.getSymbolOutputDir());
// 打印日志
// if (null != getLogger()) {
// getLogger().info("[Aapt]" + processInfo.getExecutable() + " "
// + StringUtils.join(processInfo.getArgs(), " "));
// }
ProcessResult result = getProcessExecutor().execute(processInfo, processOutputHandler);
result.rethrowFailure().assertNormalExitValue();
processAwbSymbols(aaptCommand, mainSymbolFile, enforceUniquePackageName);
}
/**
* 处理Awb的资源文件
*
* @param aaptCommand
* @throws IOException
*/
public void processAwbSymbols(AaptPackageProcessBuilder aaptCommand,
File mainSymbolFile,
boolean enforceUniquePackageName) throws IOException {
//1. 首先将主的R.txt和awb生成的R.txt进行merge操作
File awbSymbolFile = new File(aaptCommand.getSymbolOutputDir(), "R.txt");
File mergedSymbolFile = new File(aaptCommand.getSymbolOutputDir(), "R-all.txt");
//合并2个R.txt文件
try {
sLogger.info("mainSymbolFile:" + mainSymbolFile);
if (null != mainSymbolFile && mainSymbolFile.exists()) {
FileUtils.copyFile(mainSymbolFile, mergedSymbolFile);
}
FileUtils.writeLines(mergedSymbolFile, FileUtils.readLines(awbSymbolFile), true);
} catch (IOException e) {
throw new RuntimeException("Could not load file ", e);
}
//生成awb的java文件
SymbolLoader awbSymbols = null;
// First pass processing the libraries, collecting them by packageName,
// and ignoring the ones that have the same package name as the application
// (since that R class was already created).
String appPackageName = aaptCommand.getPackageForR();
if (appPackageName == null) {
appPackageName = ManifestFileUtils.getPackage(aaptCommand.getManifestFile());
}
awbSymbols = new SymbolLoader(mergedSymbolFile);
awbSymbols.load();
SymbolWriter writer = new SymbolWriter(aaptCommand.getSourceOutputDir(),
appPackageName,
awbSymbols);
writer.addSymbolsToWrite(awbSymbols);
sLogger.info("SymbolWriter Package:" +
appPackageName +
" to dir:" +
aaptCommand.getSourceOutputDir());
writer.write();
//再写入各自awb依赖的aar的资源
if (!aaptCommand.getLibraries().isEmpty()) {
// list of all the symbol loaders per package names.
Multimap<String, SymbolLoader> libMap = ArrayListMultimap.create();
for (AndroidLibrary lib : aaptCommand.getLibraries()) {
if (lib.isOptional()) {
continue;
}
String packageName = ManifestFileUtils.getPackage(lib.getManifest());
if (appPackageName == null) {
continue;
}
if (appPackageName.equals(packageName)) {
if (enforceUniquePackageName) {
String msg = String.format(
"Error: A library uses the same package as this project: %s\n" +
"You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
"However, this is temporary and will be enforced in 1.0",
packageName);
throw new RuntimeException(msg);
}
// ignore libraries that have the same package name as the app
continue;
}
File rFile = lib.getSymbolFile();
// if the library has no resource, this file won't exist.
if (rFile.isFile()) {
SymbolLoader libSymbols = new SymbolLoader(rFile);
libSymbols.load();
// store these symbols by associating them with the package name.
libMap.put(packageName, libSymbols);
}
}
// now loop on all the package name, merge all the symbols to write, and write them
for (String packageName : libMap.keySet()) {
Collection<SymbolLoader> symbols = libMap.get(packageName);
if (enforceUniquePackageName && symbols.size() > 1) {
String msg = String.format(
"Error: more than one library with package name '%s'\n" +
"You can temporarily disable this error with android.enforceUniquePackageName=false\n" +
"However, this is temporary and will be enforced in 1.0",
packageName);
throw new RuntimeException(msg);
}
SymbolWriter libWriter = new SymbolWriter(aaptCommand.getSourceOutputDir(),
packageName,
awbSymbols);
for (SymbolLoader symbolLoader : symbols) {
libWriter.addSymbolsToWrite(symbolLoader);
}
sLogger.info("SymbolWriter Package:" +
packageName +
" to dir:" +
aaptCommand.getSourceOutputDir());
libWriter.write();
}
}
}
/**
* 获取自定义的buildToolInfo
*
* @param targetInfo
* @return
*/
private boolean updateAapt;
@Override
public TargetInfo getTargetInfo() {
if (null == defaultBuilder.getTargetInfo()) {
return null;
}
if (!updateAapt && atlasExtension.getTBuildConfig().getUseCustomAapt()) {
super.setTargetInfo(defaultBuilder.getTargetInfo());
BuildToolInfo defaultBuildToolInfo = defaultBuilder.getTargetInfo().getBuildTools();
File customAaptFile = getAapt();
try {
Method method = defaultBuildToolInfo.getClass()
.getDeclaredMethod("add", PathId.class, File.class);
method.setAccessible(true);
method.invoke(defaultBuildToolInfo, PathId.AAPT, customAaptFile);
} catch (Throwable e) {
throw new GradleException(e.getMessage());
}
updateAapt = true;
}
return defaultBuilder.getTargetInfo();
}
static class TProcessInfo implements ProcessInfo {
private ProcessInfo origin;
private String sybolOutputDir;
public TProcessInfo(ProcessInfo origin) {
this.origin = origin;
}
public TProcessInfo(ProcessInfo origin, String sybolOutputDir) {
this.origin = origin;
this.sybolOutputDir = sybolOutputDir;
}
@Override
public String getExecutable() {
return origin.getExecutable();
}
@Override
public List<String> getArgs() {
List<String> args = new ArrayList<String>();
args.addAll(origin.getArgs());
//args.remove("--no-version-vectors");
//加入R.txt文件的生成
if (!args.contains("--output-text-symbols") && null != sybolOutputDir) {
args.add("--output-text-symbols");
args.add(sybolOutputDir);
}
return args;
}
@Override
public Map<String, Object> getEnvironment() {
return origin.getEnvironment();
}
@Override
public String getDescription() {
return origin.getDescription();
}
}
private File getAapt() {
String osName = "mac";
String fileName = "aapt";
if (StringUtils.containsIgnoreCase(System.getProperty("os.name"), "Mac")) {
osName = "mac";
} else if (StringUtils.containsIgnoreCase(System.getProperty("os.name"), "Linux")) {
osName = "linux";
} else if (StringUtils.containsIgnoreCase(System.getProperty("os.name"), "windows")) {
osName = "win";
fileName = "aapt.exe";
}
String aaptPath = "aapt/" + osName + "/" + fileName;
File aaptFile = new File(AtlasBuilder.class.getClassLoader().getResource(aaptPath).getFile());
if (aaptFile.isFile()) {
return aaptFile;
}
String path = AtlasBuilder.class.getProtectionDomain()
.getCodeSource()
.getLocation()
.getFile();
File jarFile = new File(path);
File jarFolder = new File(jarFile.getParentFile(),
FilenameUtils.getBaseName(jarFile.getName()));
jarFolder.mkdirs();
aaptFile = new File(jarFolder, fileName);
if (!aaptFile.exists()) {
aaptFile = ZipUtils.extractZipFileToFolder(jarFile, aaptPath,
aaptFile.getParentFile());
aaptFile.setExecutable(true);
}
return aaptFile;
}
@Override
public void convertByteCode(Collection<File> inputs,
File outDexFolder,
boolean multidex,
File mainDexList,
DexOptions dexOptions,
ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
if (AtlasBuildContext.sBuilderAdapter.fileCache.isCacheEnabled() && inputs.size() > 1) {
List<Dex> dexs = new ArrayList<>();
//做dexMerge
File tmpDir = new File(outDexFolder, "tmp");
tmpDir.mkdirs();
long startTime = System.currentTimeMillis();
for (File input : inputs) {
File dexDir = new File(tmpDir, "dexDir" + dexs.size());
dexDir.mkdirs();
preDexLibrary(input, dexDir, multidex, dexOptions, processOutputHandler);
File dexFile = new File(dexDir, "classes.dex");
if (dexFile.exists() && dexFile.length() > 0) {
dexs.add(new Dex(dexFile));
}
}
long endDexTime = System.currentTimeMillis();
DexMerger dexMerger = new DexMerger(dexs.toArray(new Dex[0]),
CollisionPolicy.KEEP_FIRST,
new DxContext());
Dex dex = dexMerger.merge();
File dexFile = new File(outDexFolder, "classes.dex");
dex.writeTo(dexFile);
FileUtils.deleteDirectory(tmpDir);
long finishTime = System.currentTimeMillis();
sLogger.info("[mtldex] dex consume {} , dexmerge consume {}",
endDexTime - startTime,
finishTime - endDexTime);
} else {
super.convertByteCode(inputs,
outDexFolder,
multidex,
mainDexList,
dexOptions,
processOutputHandler);
}
}
@Override
public void preDexLibrary(@NonNull File inputFile,
@NonNull File outFile,
boolean multiDex,
@NonNull DexOptions dexOptions,
@NonNull ProcessOutputHandler processOutputHandler)
throws IOException, InterruptedException, ProcessException {
if (!AtlasBuildContext.sBuilderAdapter.dexCacheEnabled){
super.preDexLibrary(inputFile,outFile,multiDex,dexOptions,processOutputHandler);
return;
}
String md5 = "";
File dexFile = new File(outFile, "classes.dex");
if (!inputFile.getName().startsWith("combined")) {
if (inputFile.isFile()) {
md5 = MD5Util.getFileMD5(inputFile);
} else if (inputFile.isDirectory()) {
md5 = MD5Util.getFileMd5(FileUtils.listFiles(inputFile,
new String[] {"class"},
true));
}
if (StringUtils.isNotEmpty(md5)) {
AtlasBuildContext.sBuilderAdapter.fileCache.fetchFile(md5, dexFile, "pre-dex");
if (dexFile.exists() && dexFile.length() > 0) {
sLogger.info("[mtldex] cache dex for {} , {}",
inputFile.getAbsolutePath(),
dexFile.getAbsolutePath());
return;
} else {
sLogger.info("[mtldex] discache dex for {}", inputFile.getAbsolutePath());
}
}
}
dexFile.delete();
//todo 设置dexOptions
DefaultDexOptions defaultDexOptions = new DefaultDexOptions();
defaultDexOptions.setDexInProcess(true);
defaultDexOptions.setJumboMode(dexOptions.getJumboMode());
defaultDexOptions.setDexInProcess(true);
//外部已经启动了多线程,尽量少一点
defaultDexOptions.setThreadCount(dexOptions.getThreadCount());
defaultDexOptions.setAdditionalParameters(dexOptions.getAdditionalParameters());
defaultDexOptions.setJumboMode(dexOptions.getJumboMode());
defaultDexOptions.setJavaMaxHeapSize("500m");
sLogger.info("[mtldex] pre dex for {} {}",
inputFile.getAbsolutePath(),
outFile.getAbsolutePath());
super.preDexLibrary(inputFile, outFile, multiDex, defaultDexOptions, processOutputHandler);
if (StringUtils.isNotEmpty(md5)) {
AtlasBuildContext.sBuilderAdapter.fileCache.cacheFile(md5, dexFile, "pre-dex");
}
}
//TODO 可以增量构建,加快速度
@Deprecated
public void oldPackageApk(
@NonNull String androidResPkgLocation,
@NonNull Set<File> dexFolders,
@NonNull Collection<File> javaResourcesLocations,
@NonNull Collection<File> jniLibsLocations,
@Nullable File assetsFolder,
@NonNull Set<String> abiFilters,
boolean jniDebugBuild,
@Nullable SigningConfig signingConfig,
@NonNull File outApkLocation,
int minSdkVersion,
@NonNull Predicate<String> noCompressPredicate)
throws KeytoolException, PackagerException, SigningException, IOException {
checkNotNull(androidResPkgLocation, "androidResPkgLocation cannot be null.");
checkNotNull(outApkLocation, "outApkLocation cannot be null.");
/*
* This is because this method is not supposed be be called in an incremental build. So, if
* an out APK already exists, we delete it.
*/
if (outApkLocation.exists()) {
FileUtils.forceDelete(outApkLocation);
}
Map<RelativeFile, FileStatus> javaResourceMods = Maps.newHashMap();
Map<File, FileStatus> javaResourceArchiveMods = Maps.newHashMap();
for (File resourceLocation : javaResourcesLocations) {
if (resourceLocation.isFile()) {
javaResourceArchiveMods.put(resourceLocation, FileStatus.NEW);
} else {
Set<RelativeFile> files =
RelativeFiles.fromDirectory(resourceLocation, new java.util.function.Predicate<RelativeFile>() {
@Override
public boolean test(RelativeFile relativeFile) {
return relativeFile.getFile().isFile();
}
});
javaResourceMods.putAll(
Maps.asMap(files, Functions.constant(FileStatus.NEW)));
}
}
NativeLibraryAbiPredicate nativeLibraryPredicate =
new NativeLibraryAbiPredicate(abiFilters, jniDebugBuild);
Map<RelativeFile, FileStatus> jniMods = Maps.newHashMap();
Map<File, FileStatus> jniArchiveMods = Maps.newHashMap();
for (File jniLoc : jniLibsLocations) {
if (jniLoc.isFile()) {
jniArchiveMods.put(jniLoc, FileStatus.NEW);
} else {
Set<RelativeFile> files = RelativeFiles.fromDirectory(jniLoc,
RelativeFiles
.fromPathPredicate(nativeLibraryPredicate));
jniMods.putAll(Maps.asMap(files,
Functions.constant(FileStatus.NEW)));
}
}
Set<RelativeFile> assets = assetsFolder == null
? Collections.emptySet()
: RelativeFiles.fromDirectory(assetsFolder, new java.util.function.Predicate<RelativeFile>() {
@Override
public boolean test(RelativeFile relativeFile) {
return relativeFile.getFile().isFile();
}
});
PrivateKey key = null;
X509Certificate certificate = null;
boolean v1SigningEnabled = false;
boolean v2SigningEnabled = false;
ApkCreatorFactory.CreationData creationData =
new ApkCreatorFactory.CreationData(
outApkLocation,
key,
certificate,
v1SigningEnabled,
v2SigningEnabled,
null, // BuiltBy
"atlas",
minSdkVersion,
NativeLibrariesPackagingMode.COMPRESSED,
noCompressPredicate::apply);
try (OldPackager packager = new OldPackager(creationData, androidResPkgLocation,
LoggerWrapper.getLogger(AtlasBuilder.class))) {
// add dex folder to the apk root.
if (!dexFolders.isEmpty()) {
packager.addDexFiles(dexFolders);
}
// add the output of the java resource merger
for (Map.Entry<RelativeFile, FileStatus> resourceUpdate :
javaResourceMods.entrySet()) {
packager.updateResource(resourceUpdate.getKey(), resourceUpdate.getValue());
}
for (Map.Entry<File, FileStatus> resourceArchiveUpdate :
javaResourceArchiveMods.entrySet()) {
packager.updateResourceArchive(resourceArchiveUpdate.getKey(),
resourceArchiveUpdate.getValue(),
new java.util.function.Predicate<String>() {
@Override
public boolean test(String s) {
return false;
}
});
}
for (Map.Entry<RelativeFile, FileStatus> jniLibUpdates : jniMods.entrySet()) {
packager.updateResource(jniLibUpdates.getKey(), jniLibUpdates.getValue());
}
for (Map.Entry<File, FileStatus> resourceArchiveUpdate :
jniArchiveMods.entrySet()) {
packager.updateResourceArchive(resourceArchiveUpdate.getKey(),
resourceArchiveUpdate.getValue(),
new java.util.function.Predicate<String>() {
@Override
public boolean test(String s) {
return nativeLibraryPredicate.test(s);
}
});
}
for (RelativeFile asset : assets) {
packager.addFile(
asset.getFile(),
SdkConstants.FD_ASSETS + "/" + asset.getOsIndependentRelativePath());
}
}
}
@Override
public SdkInfo getSdkInfo() {
return defaultBuilder.getSdkInfo();
}
private static final String MANIFEST_TEMPLATE = "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n"
+ "<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n"
+ " package=\"${packageName}\"\n"
+ " xmlns:tools=\"http://schemas.android.com/tools\"\n"
+ " android:versionCode=\"${versionCode}\"\n"
+ " android:versionName=\"${versionName}\">\n"
+ " <application></application>\n"
+ "</manifest>\n";
public void mergeManifestsForBunlde(File outputFile, String packageName, String versionName, String versionCode)
throws IOException {
String xml = MANIFEST_TEMPLATE.replace("${packageName}", packageName)
.replace("${versionCode}", versionCode).replace("${versionName}", versionName);
outputFile.getParentFile().mkdirs();
outputFile.delete();
FileUtils.write(outputFile, xml);
}
}