1 /*
2  * Copyright (C) 2017 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 package com.android.tools.appbundle.bundletool;
17 
18 import static com.google.common.base.Preconditions.checkArgument;
19 
20 import com.android.tools.appbundle.bundletool.utils.FlagParser;
21 import com.google.auto.value.AutoValue;
22 import java.nio.file.Files;
23 import java.nio.file.Path;
24 import java.nio.file.Paths;
25 import java.util.Optional;
26 
27 /** Command responsible for building an App Bundle module. */
28 @AutoValue
29 public abstract class BuildModuleCommand {
30 
31   public static final String COMMAND_NAME = "build-module";
32 
33   private static final String OUTPUT_FLAG = "output";
34   private static final String MANIFEST_FLAG = "manifest";
35   private static final String MANIFEST_DIR_FLAG = "manifest-dir";
36   private static final String DEX_FLAG = "dex";
37   private static final String DEX_DIR_FLAG = "dex-dir";
38   private static final String RESOURCES_DIR_FLAG = "resources-dir";
39   private static final String ASSETS_DIR_FLAG = "assets-dir";
40   private static final String NATIVE_DIR_FLAG = "native-dir";
41 
getOutputPath()42   abstract Path getOutputPath();
43 
getManifestPath()44   abstract Optional<Path> getManifestPath();
45 
getManifestDirPath()46   abstract Optional<Path> getManifestDirPath();
47 
getDexPath()48   abstract Optional<Path> getDexPath();
49 
getDexDirPath()50   abstract Optional<Path> getDexDirPath();
51 
getResourcesDirPath()52   abstract Optional<Path> getResourcesDirPath();
53 
getAssetsDirPath()54   abstract Optional<Path> getAssetsDirPath();
55 
getNativeDirPath()56   abstract Optional<Path> getNativeDirPath();
57 
builder()58   public static Builder builder() {
59     return new AutoValue_BuildModuleCommand.Builder();
60   }
61 
62   /** Builder for the {@link BuildModuleCommand} */
63   @AutoValue.Builder
64   public abstract static class Builder {
setOutputPath(Path outputPath)65     abstract Builder setOutputPath(Path outputPath);
66 
setManifestPath(Path manifestPath)67     abstract Builder setManifestPath(Path manifestPath);
68 
setManifestDirPath(Path manifestDirPath)69     abstract Builder setManifestDirPath(Path manifestDirPath);
70 
setDexPath(Path dexPath)71     abstract Builder setDexPath(Path dexPath);
72 
setDexDirPath(Path dexDirPath)73     abstract Builder setDexDirPath(Path dexDirPath);
74 
setResourcesDirPath(Path resourcesDirPath)75     abstract Builder setResourcesDirPath(Path resourcesDirPath);
76 
setAssetsDirPath(Path assetsDirPath)77     abstract Builder setAssetsDirPath(Path assetsDirPath);
78 
setNativeDirPath(Path nativeDirPath)79     abstract Builder setNativeDirPath(Path nativeDirPath);
80 
build()81     abstract BuildModuleCommand build();
82   }
83 
fromFlags(FlagParser flagParser)84   static BuildModuleCommand fromFlags(FlagParser flagParser) {
85     Builder builder =
86         builder().setOutputPath(Paths.get(flagParser.getRequiredFlagValue(OUTPUT_FLAG)));
87     flagParser.getFlagValueAsPath(MANIFEST_FLAG).ifPresent(builder::setManifestPath);
88     flagParser.getFlagValueAsPath(MANIFEST_DIR_FLAG).ifPresent(builder::setManifestDirPath);
89     flagParser.getFlagValueAsPath(DEX_FLAG).ifPresent(builder::setDexPath);
90     flagParser.getFlagValueAsPath(DEX_DIR_FLAG).ifPresent(builder::setDexDirPath);
91     flagParser.getFlagValueAsPath(RESOURCES_DIR_FLAG).ifPresent(builder::setResourcesDirPath);
92     flagParser.getFlagValueAsPath(ASSETS_DIR_FLAG).ifPresent(builder::setAssetsDirPath);
93     flagParser.getFlagValueAsPath(NATIVE_DIR_FLAG).ifPresent(builder::setNativeDirPath);
94 
95     return builder.build();
96   }
97 
execute()98   public void execute() {
99     validateInput();
100 
101 
102   }
103 
validateInput()104   private void validateInput() {
105     checkArgument(
106         getManifestPath().isPresent() || getManifestDirPath().isPresent(),
107         "One of --%s or --%s is required.",
108         MANIFEST_FLAG,
109         MANIFEST_DIR_FLAG);
110     checkArgument(
111         !getManifestPath().isPresent() || !getManifestDirPath().isPresent(),
112         "Cannot set both --%s and --%s flags.",
113         MANIFEST_FLAG,
114         MANIFEST_DIR_FLAG);
115     checkArgument(
116         !getDexPath().isPresent() || !getDexDirPath().isPresent(),
117         "Cannot set both --%s and --%s flags.",
118         DEX_FLAG,
119         DEX_DIR_FLAG);
120 
121     checkArgument(!Files.exists(getOutputPath()), "File %s already exists.", getOutputPath());
122     checkFileExistsAndReadable(getManifestPath());
123     checkDirectoryExists(getManifestDirPath());
124     checkFileExistsAndReadable(getDexPath());
125     checkDirectoryExists(getDexDirPath());
126     checkDirectoryExists(getResourcesDirPath());
127     checkDirectoryExists(getAssetsDirPath());
128     checkDirectoryExists(getNativeDirPath());
129   }
130 
checkFileExistsAndReadable(Optional<Path> pathOptional)131   private static void checkFileExistsAndReadable(Optional<Path> pathOptional) {
132     if (pathOptional.isPresent()) {
133       Path path = pathOptional.get();
134       checkArgument(Files.exists(path), "File '%s' was not found.", path);
135       checkArgument(Files.isReadable(path), "File '%s' is not readable.", path);
136     }
137   }
138 
checkDirectoryExists(Optional<Path> pathOptional)139   private static void checkDirectoryExists(Optional<Path> pathOptional) {
140     if (pathOptional.isPresent()) {
141       Path path = pathOptional.get();
142       checkArgument(Files.exists(path), "Directory '%s' was not found.", path);
143       checkArgument(Files.isDirectory(path), "'%s' is not a directory.");
144     }
145   }
146 
help()147   public static void help() {
148     System.out.println(
149         String.format(
150             "bundletool %s --output=<path/to/module.zip> "
151                 + "[--%s=<path/to/AndroidManifest.flat>|--%s=<path/to/manifest-dir/>] "
152                 + "[--%s=<path/to/classes.dex>|--%s=<path/to/dex-dir/>] "
153                 + "[--%s=<path/to/res/>] "
154                 + "[--%s=<path/to/assets/>] "
155                 + "[--%s=<path/to/lib/>] ",
156             COMMAND_NAME,
157             MANIFEST_FLAG,
158             MANIFEST_DIR_FLAG,
159             DEX_FLAG,
160             DEX_DIR_FLAG,
161             RESOURCES_DIR_FLAG,
162             ASSETS_DIR_FLAG,
163             NATIVE_DIR_FLAG));
164     System.out.println();
165     System.out.println(
166         "Builds a module as a zip from an app's project. Note that the resources and the "
167             + "AndroidManifest.xml must already have been compiled with aapt2.");
168     System.out.println();
169     System.out.println("--output: Path to the zip file to build.");
170     System.out.printf(
171         "--%s: Path to the AndroidManifest.flat compiled by aapt2. Use --%s if there "
172             + "are more than one.\n",
173         MANIFEST_FLAG, MANIFEST_DIR_FLAG);
174     System.out.printf(
175         "--%s: Path to the directory containing multiple Android manifests compiled by aapt2. "
176             + "A file named 'manifest-targeting.xml' must be present in the directory "
177             + "describing the targeting of each manifest present.\n",
178         MANIFEST_DIR_FLAG);
179     System.out.printf(
180         "--%s: Path to the dex file. Use --%s if there are more than one.\n",
181         DEX_FLAG, DEX_DIR_FLAG);
182     System.out.printf(
183         "--%s: Path to the directory containing multiple dex files. Unless all dex files must "
184             + "be included in the generated APKs (for MultiDex), a file named "
185             + "'dex-targeting.xml' must be present in the directory describing the targeting "
186             + "of the different dex files.\n",
187         DEX_DIR_FLAG);
188     System.out.printf(
189         "--%s: Path to the directory containing the resources file(s). A file named "
190             + "'resources.flat' must be present in that directory corresponding to the output "
191             + "of the aapt2 compilation of the resources.\n",
192         RESOURCES_DIR_FLAG);
193     System.out.printf("--%s: Path to the directory containing the assets.\n", ASSETS_DIR_FLAG);
194     System.out.printf(
195         "--%s: Path to the directory containing the native libraries.\n", NATIVE_DIR_FLAG);
196   }
197 }
198