aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/objc_tools/bundlemerge
diff options
context:
space:
mode:
authorGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
committerGravatar Han-Wen Nienhuys <hanwen@google.com>2015-02-25 16:45:20 +0100
commitd08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch)
tree5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/objc_tools/bundlemerge
Update from Google.
-- MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/objc_tools/bundlemerge')
-rw-r--r--src/objc_tools/bundlemerge/README2
-rw-r--r--src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerge.java54
-rw-r--r--src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java216
3 files changed, 272 insertions, 0 deletions
diff --git a/src/objc_tools/bundlemerge/README b/src/objc_tools/bundlemerge/README
new file mode 100644
index 0000000000..1701f8197f
--- /dev/null
+++ b/src/objc_tools/bundlemerge/README
@@ -0,0 +1,2 @@
+bundlemerge merges the assorted files making up an iOS application into a final
+IPA bundle.
diff --git a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerge.java b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerge.java
new file mode 100644
index 0000000000..daa93a44ae
--- /dev/null
+++ b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerge.java
@@ -0,0 +1,54 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.google.devtools.build.xcode.bundlemerge;
+
+import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.Control;
+import com.google.devtools.build.xcode.plmerge.PlistMerging.ValidationException;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.FileSystems;
+import java.nio.file.Files;
+
+/**
+ * Command-line entry point for {@link BundleMerging}. functionality. The only argument passed to
+ * this command-line utility should be a control file which contains a binary serialization of the
+ * {@link Control} protocol buffer.
+ */
+public class BundleMerge {
+ private BundleMerge() {
+ throw new UnsupportedOperationException("static-only");
+ }
+
+ public static void main(String[] args) throws IOException {
+ if (args.length != 1) {
+ System.err.println("Expect exactly one argument: path to control file");
+ System.exit(1);
+ }
+ FileSystem fileSystem = FileSystems.getDefault();
+ Control control;
+ try (InputStream in = Files.newInputStream(fileSystem.getPath(args[0]))) {
+ control = Control.parseFrom(in);
+ }
+ try {
+ BundleMerging.merge(fileSystem, control);
+ } catch (ValidationException e) {
+ // Don't print stack traces for validation errors.
+ System.err.print("\nBundle merge failed: " + e.getMessage() + "\n");
+ System.exit(1);
+ }
+ }
+}
diff --git a/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java
new file mode 100644
index 0000000000..3a061db304
--- /dev/null
+++ b/src/objc_tools/bundlemerge/java/com/google/devtools/build/xcode/bundlemerge/BundleMerging.java
@@ -0,0 +1,216 @@
+// Copyright 2014 Google Inc. All rights reserved.
+//
+// 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.google.devtools.build.xcode.bundlemerge;
+
+import static com.google.devtools.build.singlejar.ZipCombiner.DOS_EPOCH;
+import static com.google.devtools.build.singlejar.ZipCombiner.OutputMode.FORCE_DEFLATE;
+
+import com.google.common.annotations.VisibleForTesting;
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import com.google.devtools.build.singlejar.ZipCombiner;
+import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile;
+import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.Control;
+import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.MergeZip;
+import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.VariableSubstitution;
+import com.google.devtools.build.xcode.common.Platform;
+import com.google.devtools.build.xcode.common.TargetDeviceFamily;
+import com.google.devtools.build.xcode.plmerge.KeysToRemoveIfEmptyString;
+import com.google.devtools.build.xcode.plmerge.PlistMerging;
+import com.google.devtools.build.xcode.zip.ZipInputEntry;
+
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.file.FileSystem;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipInputStream;
+
+import javax.annotation.CheckReturnValue;
+
+/**
+ * Implementation of the final steps to create an iOS application bundle.
+ *
+ * TODO(bazel-team): Add asset catalog compilation and bundling to this logic.
+ */
+public final class BundleMerging {
+ @VisibleForTesting final FileSystem fileSystem;
+ @VisibleForTesting final Path outputZip;
+ @VisibleForTesting final ImmutableList<ZipInputEntry> inputs;
+ @VisibleForTesting final ImmutableList<MergeZip> mergeZips;
+
+ /**
+ * We can instantiate this class for testing purposes. For typical uses, just use
+ * {@link #merge(FileSystem, Control)}.
+ */
+ private BundleMerging(FileSystem fileSystem,
+ Path outputZip, ImmutableList<ZipInputEntry> inputs, ImmutableList<MergeZip> mergeZips) {
+ this.fileSystem = Preconditions.checkNotNull(fileSystem);
+ this.outputZip = Preconditions.checkNotNull(outputZip);
+ this.inputs = Preconditions.checkNotNull(inputs);
+ this.mergeZips = Preconditions.checkNotNull(mergeZips);
+ }
+
+ /**
+ * Joins two paths to be used in a zip file. The {@code right} part of the path must be relative.
+ * The {@code left} part could or could not have a trailing slash. These paths are used in .ipa
+ * (.zip) files, which must use forward slashes, so they are hard-coded here.
+ * <p>
+ * TODO(bazel-team): This is messy. See if we can use some common joining function that handles
+ * empty paths and doesn't automatically inherit the path conventions of the host platform.
+ */
+ private static String joinPath(String left, String right) {
+ Preconditions.checkArgument(!right.startsWith("/"), "'right' must be relative: %s", right);
+ if (left.isEmpty() || right.isEmpty() || left.endsWith("/")) {
+ return left + right;
+ } else {
+ return left + "/" + right;
+ }
+ }
+
+ private static final String INFOPLIST_FILENAME = "Info.plist";
+ private static final String PKGINFO_FILENAME = "PkgInfo";
+
+ /**
+ * Adds merge artifacts from the given {@code control} into builders that collect merge zips and
+ * individual files. {@code bundleRoot} is prepended to each path, except the paths in the merge
+ * zips.
+ */
+ private static void mergeInto(
+ Path tempDir, FileSystem fileSystem, Control control, String bundleRoot,
+ ImmutableList.Builder<ZipInputEntry> packagedFilesBuilder,
+ ImmutableList.Builder<MergeZip> mergeZipsBuilder, boolean includePkgInfo) throws IOException {
+ Path tempMergedPlist = Files.createTempFile(tempDir, null, INFOPLIST_FILENAME);
+ Path tempPkgInfo = Files.createTempFile(tempDir, null, PKGINFO_FILENAME);
+
+ // Generate the Info.plist and PkgInfo files to include in the app bundle.
+ ImmutableList.Builder<Path> sourcePlistFilesBuilder = new ImmutableList.Builder<>();
+ for (String sourcePlist : control.getSourcePlistFileList()) {
+ sourcePlistFilesBuilder.add(fileSystem.getPath(sourcePlist));
+ }
+ ImmutableList<Path> sourcePlistFiles = sourcePlistFilesBuilder.build();
+ ImmutableMap.Builder<String, String> substitutionMap = ImmutableMap.builder();
+ for (VariableSubstitution substitution : control.getVariableSubstitutionList()) {
+ substitutionMap.put(substitution.getName(), substitution.getValue());
+ }
+ PlistMerging plistMerging = PlistMerging.from(
+ sourcePlistFiles,
+ PlistMerging.automaticEntries(
+ TargetDeviceFamily.fromBundleMergeNames(control.getTargetDeviceFamilyList()),
+ Platform.valueOf(control.getPlatform()),
+ control.getSdkVersion(),
+ control.getMinimumOsVersion()),
+ substitutionMap.build(),
+ new KeysToRemoveIfEmptyString("CFBundleIconFile", "NSPrincipalClass"));
+ if (control.hasExecutableName()) {
+ plistMerging.setExecutableName(control.getExecutableName());
+ }
+ plistMerging.write(tempMergedPlist, tempPkgInfo);
+
+
+ bundleRoot = joinPath(bundleRoot, control.getBundleRoot());
+
+ // Add files to zip configuration which creates the final application bundle.
+ packagedFilesBuilder
+ .add(new ZipInputEntry(tempMergedPlist, joinPath(bundleRoot, INFOPLIST_FILENAME)));
+ if (includePkgInfo) {
+ packagedFilesBuilder
+ .add(new ZipInputEntry(tempPkgInfo, joinPath(bundleRoot, PKGINFO_FILENAME)));
+ }
+ for (BundleFile bundleFile : control.getBundleFileList()) {
+ int externalFileAttribute = bundleFile.hasExternalFileAttribute()
+ ? bundleFile.getExternalFileAttribute() : ZipInputEntry.DEFAULT_EXTERNAL_FILE_ATTRIBUTE;
+ packagedFilesBuilder.add(
+ new ZipInputEntry(
+ fileSystem.getPath(bundleFile.getSourceFile()),
+ joinPath(bundleRoot, bundleFile.getBundlePath()),
+ externalFileAttribute));
+ }
+
+ for (String mergeZip : control.getMergeWithoutNamePrefixZipList()) {
+ mergeZipsBuilder.add(MergeZip.newBuilder()
+ .setSourcePath(mergeZip)
+ .build());
+ }
+ mergeZipsBuilder.addAll(control.getMergeZipList());
+
+ for (Control nestedControl : control.getNestedBundleList()) {
+ mergeInto(tempDir, fileSystem, nestedControl, bundleRoot, packagedFilesBuilder,
+ mergeZipsBuilder, /*includePkgInfo=*/false);
+ }
+ }
+
+ /**
+ * Returns a zipper configuration that can be executed to create the application bundle.
+ */
+ @CheckReturnValue
+ @VisibleForTesting
+ static BundleMerging merging(Path tempDir, FileSystem fileSystem, Control control)
+ throws IOException {
+ ImmutableList.Builder<MergeZip> mergeZipsBuilder = new ImmutableList.Builder<>();
+ ImmutableList.Builder<ZipInputEntry> packagedFilesBuilder =
+ new ImmutableList.Builder<ZipInputEntry>();
+
+ mergeInto(tempDir, fileSystem, control, /*bundleRoot=*/"", packagedFilesBuilder,
+ mergeZipsBuilder, /*includePkgInfo=*/true);
+
+ return new BundleMerging(fileSystem, fileSystem.getPath(control.getOutFile()),
+ packagedFilesBuilder.build(), mergeZipsBuilder.build());
+ }
+
+ /**
+ * Copies all entries from the source zip into a destination zip using the given combiner. The
+ * contents of the source zip can appear to be in a sub-directory of the destination zip by
+ * passing a non-empty string for the entry names prefix with a trailing '/'.
+ */
+ private void addEntriesFromOtherZip(ZipCombiner combiner, Path sourceZip, String entryNamesPrefix)
+ throws IOException {
+ try (ZipInputStream zipIn = new ZipInputStream(Files.newInputStream(sourceZip))) {
+ while (true) {
+ ZipEntry zipInEntry = zipIn.getNextEntry();
+ if (zipInEntry == null) {
+ break;
+ }
+ // TODO(bazel-team): preserve the external file attribute field in the source zip entry.
+ combiner.addFile(entryNamesPrefix + zipInEntry.getName(), DOS_EPOCH, zipIn,
+ ZipInputEntry.DEFAULT_DIRECTORY_ENTRY_INFO);
+ }
+ }
+ }
+
+ @VisibleForTesting
+ void execute() throws IOException {
+ try (OutputStream out = Files.newOutputStream(outputZip);
+ ZipCombiner combiner = new ZipCombiner(FORCE_DEFLATE, out)) {
+ ZipInputEntry.addAll(combiner, inputs);
+ for (MergeZip mergeZip : mergeZips) {
+ addEntriesFromOtherZip(
+ combiner, fileSystem.getPath(mergeZip.getSourcePath()), mergeZip.getEntryNamePrefix());
+ }
+ }
+ }
+
+ /**
+ * Creates an {@code .ipa} file for an iOS application.
+ * @param fileSystem used to resolve paths specified in {@code control}
+ * @param control specifies the locations of input and output files and other parameters used to
+ * create the final {@code .ipa} file.
+ */
+ public static void merge(FileSystem fileSystem, Control control) throws IOException {
+ merging(Files.createTempDirectory("mergebundle"), fileSystem, control).execute();
+ }
+}