提交 7425c0d1 编写于 作者: P Pankaj Kumar

Prepare for release 1.1.0.

上级 279962ef
......@@ -26,4 +26,4 @@ https://developer.android.com/studio/command-line/bundletool
## Releases
Latest release: [1.0.0](https://github.com/google/bundletool/releases)
Latest release: [1.1.0](https://github.com/google/bundletool/releases)
......@@ -43,7 +43,8 @@ configurations {
// The repackaging rules are defined in the "shadowJar" task below.
dependencies {
compile "com.android.tools:r8:2.0.23"
compile "com.android.tools.build:apkzlib:3.4.0-beta01"
compile "com.android.tools.build:apkzlib:4.0.0"
compile "com.android.tools.build:apksig:4.0.0"
compile "com.android.tools.ddms:ddmlib:26.2.0"
shadow "com.android.tools.build:aapt2-proto:4.1.0-alpha01-6193524"
......@@ -53,6 +54,9 @@ dependencies {
shadow "com.google.guava:guava:27.0.1-jre"
shadow "com.google.protobuf:protobuf-java:3.4.0"
shadow "com.google.protobuf:protobuf-java-util:3.4.0"
shadow "com.google.dagger:dagger:2.28.3"
annotationProcessor "com.google.dagger:dagger-compiler:2.28.3"
shadow "javax.inject:javax.inject:1"
compileWindows "com.android.tools.build:aapt2:4.1.0-alpha01-6193524:windows"
compileMacOs "com.android.tools.build:aapt2:4.1.0-alpha01-6193524:osx"
......@@ -74,6 +78,9 @@ dependencies {
testCompile "org.junit.vintage:junit-vintage-engine:5.2.0"
testRuntime "org.junit.jupiter:junit-jupiter-engine:5.2.0"
testCompile "org.junit.platform:junit-platform-runner:1.2.0"
testCompile "com.google.dagger:dagger:2.28.3"
testAnnotationProcessor "com.google.dagger:dagger-compiler:2.28.3"
testCompile "javax.inject:javax.inject:1"
testCompile("org.smali:dexlib2:2.3.4") {
exclude group: "com.google.guava", module: "guava"
}
......
release_version = 1.0.0
release_version = 1.1.0
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.tools.build.bundletool.commands;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.bundle.Config.BundleConfig;
import com.android.tools.build.bundletool.model.AppBundle;
import com.android.tools.build.bundletool.model.BundleMetadata;
import dagger.Module;
import dagger.Provides;
import java.lang.annotation.Retention;
import java.util.zip.ZipFile;
import javax.inject.Qualifier;
/** Dagger module for components that manipulate an App Bundle. */
@Module
public final class AppBundleModule {
@CommandScoped
@Provides
static AppBundle provideAppBundle(@AppBundleZip ZipFile zip) {
return AppBundle.buildFromZip(zip);
}
@CommandScoped
@Provides
static BundleConfig provideBundleConfig(AppBundle appBundle) {
return appBundle.getBundleConfig();
}
@CommandScoped
@Provides
static BundleMetadata provideBundleMetadata(AppBundle appBundle) {
return appBundle.getBundleMetadata();
}
private AppBundleModule() {}
/** Qualifying annotation for the ZipFile corresponding to the App Bundle. */
@Retention(RUNTIME)
@Qualifier
public @interface AppBundleZip {}
}
......@@ -23,8 +23,15 @@ import static com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBu
import static com.android.tools.build.bundletool.commands.CommandUtils.ANDROID_SERIAL_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.ANDROID_HOME_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.SdkToolsLocator.SYSTEM_PATH_VARIABLE;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileDoesNotExist;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndExecutable;
import static com.android.tools.build.bundletool.model.utils.files.FilePreconditions.checkFileExistsAndReadable;
import static com.google.common.base.Preconditions.checkArgument;
import com.android.apksig.SigningCertificateLineage;
import com.android.apksig.apk.ApkFormatException;
import com.android.apksig.util.DataSource;
import com.android.apksig.util.DataSources;
import com.android.bundle.Devices.DeviceSpec;
import com.android.tools.build.bundletool.commands.CommandHelp.CommandDescription;
import com.android.tools.build.bundletool.commands.CommandHelp.FlagDescription;
......@@ -36,13 +43,18 @@ import com.android.tools.build.bundletool.io.TempDirectory;
import com.android.tools.build.bundletool.model.Aapt2Command;
import com.android.tools.build.bundletool.model.ApkListener;
import com.android.tools.build.bundletool.model.ApkModifier;
import com.android.tools.build.bundletool.model.KeystoreProperties;
import com.android.tools.build.bundletool.model.OptimizationDimension;
import com.android.tools.build.bundletool.model.Password;
import com.android.tools.build.bundletool.model.SignerConfig;
import com.android.tools.build.bundletool.model.SigningConfiguration;
import com.android.tools.build.bundletool.model.SourceStamp;
import com.android.tools.build.bundletool.model.exceptions.CommandExecutionException;
import com.android.tools.build.bundletool.model.exceptions.InvalidBundleException;
import com.android.tools.build.bundletool.model.exceptions.InvalidCommandException;
import com.android.tools.build.bundletool.model.utils.DefaultSystemEnvironmentProvider;
import com.android.tools.build.bundletool.model.utils.SystemEnvironmentProvider;
import com.android.tools.build.bundletool.model.utils.files.FileUtils;
import com.android.tools.build.bundletool.splitters.DexCompressionSplitter;
import com.android.tools.build.bundletool.splitters.NativeLibrariesCompressionSplitter;
import com.android.tools.build.bundletool.validation.SubValidator;
......@@ -54,12 +66,21 @@ import com.google.common.collect.ImmutableSet;
import com.google.common.io.MoreFiles;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.io.RandomAccessFile;
import java.io.UncheckedIOException;
import java.nio.ByteOrder;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Arrays;
import java.util.Optional;
import java.util.concurrent.Executors;
import java.util.logging.Logger;
import java.util.stream.Collectors;
import java.util.zip.ZipException;
import java.util.zip.ZipFile;
/** Command to generate APKs from an Android App Bundle. */
@AutoValue
......@@ -69,6 +90,8 @@ public abstract class BuildApksCommand {
public static final String COMMAND_NAME = "build-apks";
private static final Logger logger = Logger.getLogger(BuildApksCommand.class.getName());
/** Modes to run {@link BuildApksCommand} against to generate APKs. */
public enum ApkBuildMode {
/** DEFAULT mode generates split, standalone and instant APKs. */
......@@ -132,11 +155,15 @@ public abstract class BuildApksCommand {
private static final Flag<Password> STAMP_KEY_PASSWORD_FLAG = Flag.password("stamp-key-pass");
private static final Flag<String> STAMP_SOURCE_FLAG = Flag.string("stamp-source");
private static final String APK_SET_ARCHIVE_EXTENSION = "apks";
private static final SystemEnvironmentProvider DEFAULT_PROVIDER =
new DefaultSystemEnvironmentProvider();
// Number embedded at the beginning of a zip file to indicate its file format.
private static final int ZIP_MAGIC = 0x04034b50;
public abstract Path getBundlePath();
public abstract Path getOutputFile();
......@@ -179,6 +206,7 @@ public abstract class BuildApksCommand {
public abstract boolean getCreateApkSetArchive();
public abstract Optional<ApkListener> getApkListener();
public abstract Optional<ApkModifier> getApkModifier();
......@@ -318,6 +346,7 @@ public abstract class BuildApksCommand {
*/
public abstract Builder setCreateApkSetArchive(boolean value);
/**
* Provides an {@link ApkListener} that will be notified at defined stages of APK creation.
*
......@@ -525,9 +554,7 @@ public abstract class BuildApksCommand {
.getValue(flags)
.map(deviceSpecParser)
.ifPresent(buildApksCommand::setDeviceSpec);
MODULES_FLAG.getValue(flags).ifPresent(buildApksCommand::setModules);
VERBOSE_FLAG.getValue(flags).ifPresent(buildApksCommand::setVerbose);
flags.checkNoUnknownFlags();
......@@ -536,10 +563,55 @@ public abstract class BuildApksCommand {
}
public Path execute() {
try (TempDirectory tempDir = new TempDirectory()) {
Aapt2Command aapt2 =
getAapt2Command().orElseGet(() -> CommandUtils.extractAapt2FromJar(tempDir.getPath()));
return new BuildApksManager(this, aapt2, tempDir.getPath()).execute();
validateInput();
Path outputDirectory = getCreateApkSetArchive() ? getOutputFile().getParent() : getOutputFile();
if (outputDirectory != null && Files.notExists(outputDirectory)) {
logger.info("Output directory '" + outputDirectory + "' does not exist, creating it.");
FileUtils.createDirectories(outputDirectory);
}
try (TempDirectory tempDir = new TempDirectory();
ZipFile bundleZip = new ZipFile(getBundlePath().toFile())) {
BuildApksManager buildApksManager =
DaggerBuildApksManagerComponent.builder()
.setBuildApksCommand(this)
.setTempDirectory(tempDir)
.setBundleZip(bundleZip)
.build()
.create();
buildApksManager.execute();
} catch (ZipException e) {
throw InvalidBundleException.builder()
.withCause(e)
.withUserMessage("The App Bundle is not a valid zip file.")
.build();
} catch (IOException e) {
throw new UncheckedIOException("An error occurred when processing the App Bundle.", e);
} finally {
if (isExecutorServiceCreatedByBundleTool()) {
getExecutorService().shutdown();
}
}
return getOutputFile();
}
private void validateInput() {
checkFileExistsAndReadable(getBundlePath());
if (getCreateApkSetArchive() && !getOverwriteOutput()) {
checkFileDoesNotExist(getOutputFile());
}
if (getGenerateOnlyForConnectedDevice()) {
checkArgument(
getAdbServer().isPresent(),
"Property 'adbServer' is required when 'generateOnlyForConnectedDevice' is true.");
checkArgument(
getAdbPath().isPresent(),
"Property 'adbPath' is required when 'generateOnlyForConnectedDevice' is true.");
checkFileExistsAndExecutable(getAdbPath().get());
}
}
......@@ -747,8 +819,8 @@ public abstract class BuildApksCommand {
.setFlagName(VERBOSE_FLAG.getName())
.setOptional(true)
.setDescription(
"If set, prints extra information about the command execution "
+ "in the standard output.")
"If set, prints extra information about the command execution in the standard"
+ " output.")
.build())
.addFlag(
FlagDescription.builder()
......@@ -788,9 +860,12 @@ public abstract class BuildApksCommand {
Optional<Password> keyPassword = KEY_PASSWORD_FLAG.getValue(flags);
if (keystorePath.isPresent() && keyAlias.isPresent()) {
buildApksCommand.setSigningConfiguration(
SigningConfiguration.extractFromKeystore(
keystorePath.get(), keyAlias.get(), keystorePassword, keyPassword));
SignerConfig signerConfig =
SignerConfig.extractFromKeystore(
keystorePath.get(), keyAlias.get(), keystorePassword, keyPassword);
SigningConfiguration.Builder builder =
SigningConfiguration.builder().setSignerConfig(signerConfig);
buildApksCommand.setSigningConfiguration(builder.build());
} else if (keystorePath.isPresent() && !keyAlias.isPresent()) {
throw InvalidCommandException.builder()
.withInternalMessage("Flag --ks-key-alias is required when --ks is set.")
......@@ -816,6 +891,7 @@ public abstract class BuildApksCommand {
}
}
private static void populateSourceStampFromFlags(
Builder buildApksCommand,
ParsedFlags flags,
......
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.tools.build.bundletool.commands;
import com.android.tools.build.bundletool.commands.AppBundleModule.AppBundleZip;
import com.android.tools.build.bundletool.io.TempDirectory;
import dagger.BindsInstance;
import dagger.Component;
import java.util.zip.ZipFile;
/** Dagger component to create a {@link BuildApksManager}. */
@CommandScoped
@Component(modules = BuildApksModule.class)
public interface BuildApksManagerComponent {
BuildApksManager create();
/** Builder for the {@link BuildApksManagerComponent}. */
@Component.Builder
interface Builder {
BuildApksManagerComponent build();
@BindsInstance
Builder setTempDirectory(TempDirectory tempDirectory);
@BindsInstance
Builder setBuildApksCommand(BuildApksCommand command);
@BindsInstance
Builder setBundleZip(@AppBundleZip ZipFile bundleZip);
}
}
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.tools.build.bundletool.commands;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
import com.android.bundle.Config.BundleConfig;
import com.android.bundle.Devices.DeviceSpec;
import com.android.tools.build.bundletool.commands.BuildApksCommand.ApkBuildMode;
import com.android.tools.build.bundletool.device.AdbServer;
import com.android.tools.build.bundletool.device.DeviceAnalyzer;
import com.android.tools.build.bundletool.model.ApkListener;
import com.android.tools.build.bundletool.model.ApkModifier;
import com.android.tools.build.bundletool.model.SigningConfiguration;
import com.android.tools.build.bundletool.model.SourceStamp;
import com.android.tools.build.bundletool.optimizations.ApkOptimizations;
import com.android.tools.build.bundletool.optimizations.OptimizationsMerger;
import com.google.common.util.concurrent.ListeningExecutorService;
import dagger.Module;
import dagger.Provides;
import java.lang.annotation.Retention;
import java.util.Optional;
import javax.inject.Qualifier;
/** Dagger module for the build-apks command. */
@Module(includes = {BundleConfigModule.class, BundletoolModule.class, AppBundleModule.class})
public final class BuildApksModule {
@CommandScoped
@Provides
@ApkSigningConfig
static Optional<SigningConfiguration> provideApkSigningConfiguration(BuildApksCommand command) {
return command.getSigningConfiguration();
}
@CommandScoped
@Provides
@StampSigningConfig
static Optional<SigningConfiguration> provideStampSigningConfiguration(BuildApksCommand command) {
return command.getSourceStamp().map(SourceStamp::getSigningConfiguration);
}
@CommandScoped
@Provides
static Optional<SourceStamp> provideStampSource(BuildApksCommand command) {
return command.getSourceStamp();
}
@CommandScoped
@Provides
static ListeningExecutorService provideExecutorService(BuildApksCommand command) {
return command.getExecutorService();
}
@CommandScoped
@Provides
static Optional<ApkListener> provideApkListener(BuildApksCommand command) {
return command.getApkListener();
}
@CommandScoped
@Provides
static Optional<ApkModifier> provideApkModifier(BuildApksCommand command) {
return command.getApkModifier();
}
@CommandScoped
@Provides
static ApkOptimizations provideApkOptimizations(
BundleConfig bundleConfig,
BuildApksCommand command,
OptimizationsMerger optimizationsMerger) {
return optimizationsMerger.mergeWithDefaults(bundleConfig, command.getOptimizationDimensions());
}
@CommandScoped
@Provides
static ApkBuildMode provideApkBuildMode(BuildApksCommand command) {
return command.getApkBuildMode();
}
@CommandScoped
@FirstVariantNumber
@Provides
static Optional<Integer> provideFirstVariantNumber(BuildApksCommand command) {
return command.getFirstVariantNumber();
}
@CommandScoped
@Provides
static Optional<DeviceSpec> provideDeviceSpec(BuildApksCommand command) {
if (command.getGenerateOnlyForConnectedDevice()) {
AdbServer adbServer = command.getAdbServer().get();
adbServer.init(command.getAdbPath().get());
DeviceSpec connectedDeviceSpec =
new DeviceAnalyzer(adbServer).getDeviceSpec(command.getDeviceId());
return Optional.of(connectedDeviceSpec);
}
return command.getDeviceSpec();
}
@CommandScoped
@Provides
@VerboseLogs
static boolean provideVerbose(BuildApksCommand command) {
return command.getVerbose();
}
/**
* Qualifying annotation of an {@code Optional<Integer>} for the first variant number to use when
* numbering the generated variants.
*/
@Qualifier
@Retention(RUNTIME)
public @interface FirstVariantNumber {}
/** Qualifying annotation of a {@code boolean} on whether to be verbose with logs. */
@Qualifier
@Retention(RUNTIME)
public @interface VerboseLogs {}
/** Qualifying annotation of a {@code SigningConfiguration} for the APK signing configuration. */
@Qualifier
@Retention(RUNTIME)
public @interface ApkSigningConfig {}
/**
* Qualifying annotation of a {@code SigningConfiguration} for the Stamp signing configuration.
*/
@Qualifier
@Retention(RUNTIME)
public @interface StampSigningConfig {}
private BuildApksModule() {}
}
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.tools.build.bundletool.commands;
import com.android.bundle.Config.BundleConfig;
import com.android.bundle.Config.SuffixStripping;
import com.android.tools.build.bundletool.model.OptimizationDimension;
import com.android.tools.build.bundletool.model.version.Version;
import com.android.tools.build.bundletool.optimizations.OptimizationsMerger;
import com.google.common.collect.ImmutableMap;
import dagger.Module;
import dagger.Provides;
/** Dagger module exposing the BundleConfig. */
@Module
public final class BundleConfigModule {
@CommandScoped
@Provides
static Version provideBundletoolVersion(BundleConfig bundleConfig) {
return Version.of(bundleConfig.getBundletool().getVersion());
}
@CommandScoped
@Provides
static ImmutableMap<OptimizationDimension, SuffixStripping>
provideSuffixStrippingPerOptimizationDimension(BundleConfig bundleConfig) {
return OptimizationsMerger.getSuffixStrippings(
bundleConfig.getOptimizations().getSplitsConfig().getSplitDimensionList());
}
private BundleConfigModule() {}
}
/*
* Copyright (C) 2020 The Android Open Source Project
*
* 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.tools.build.bundletool.commands;