diff options
author | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
---|---|---|
committer | Han-Wen Nienhuys <hanwen@google.com> | 2015-02-25 16:45:20 +0100 |
commit | d08b27fa9701fecfdb69e1b0d1ac2459efc2129b (patch) | |
tree | 5d50963026239ca5aebfb47ea5b8db7e814e57c8 /src/tools/xcode-common |
Update from Google.
--
MOE_MIGRATED_REVID=85702957
Diffstat (limited to 'src/tools/xcode-common')
20 files changed, 1275 insertions, 0 deletions
diff --git a/src/tools/xcode-common/BUILD b/src/tools/xcode-common/BUILD new file mode 100644 index 0000000000..f6c45f3cda --- /dev/null +++ b/src/tools/xcode-common/BUILD @@ -0,0 +1,14 @@ +package(default_visibility = ["//src/main/java:__subpackages__"]) + +# TODO(bazel-team): Split this into multiple rules. +java_library( + name = "xcode-common", + srcs = glob([ + "java/com/google/devtools/build/xcode/util/*.java", + "java/com/google/devtools/build/xcode/common/*.java", + ]), + deps = [ + "//third_party:guava", + "//third_party:jsr305", + ], +) diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/BuildOptionsUtil.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/BuildOptionsUtil.java new file mode 100644 index 0000000000..9f1a96768e --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/BuildOptionsUtil.java @@ -0,0 +1,25 @@ +// 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.common; + +/** + * Utility code related to build settings (referred to as <em>build configuration</em> within + * Xcode). + */ +public class BuildOptionsUtil { + private BuildOptionsUtil() {} + + public static final String DEFAULT_OPTIONS_NAME = "Bazel"; +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/InvalidFamilyNameException.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/InvalidFamilyNameException.java new file mode 100644 index 0000000000..930b5047c9 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/InvalidFamilyNameException.java @@ -0,0 +1,24 @@ +// 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.common; + +/** + * An exception that indicates the name of a device family was not recognized or is somehow invalid. + */ +public class InvalidFamilyNameException extends IllegalArgumentException { + public InvalidFamilyNameException(String message) { + super(message); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/PathTransformer.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/PathTransformer.java new file mode 100644 index 0000000000..08b3f6d024 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/PathTransformer.java @@ -0,0 +1,46 @@ +// 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.common; + +import java.nio.file.Path; + +/** Defines operations common to file paths. */ +public interface PathTransformer<P> { + /** Returns the containing directory of the given path. */ + P parent(P path); + + /** Returns the result of joining a path component string to the given path. */ + P join(P path, String segment); + + /** Returns the name of the file at the given path, i.e. the last path component. */ + String name(P path); + + static final PathTransformer<Path> FOR_JAVA_PATH = new PathTransformer<Path>() { + @Override + public Path parent(Path path) { + return path.getParent(); + } + + @Override + public Path join(Path path, String segment) { + return path.resolve(segment); + } + + @Override + public String name(Path path) { + return path.getFileName().toString(); + } + }; +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/Platform.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/Platform.java new file mode 100644 index 0000000000..6e7a059be4 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/Platform.java @@ -0,0 +1,59 @@ +// 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.common; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.devtools.build.xcode.util.Containing; + +import java.util.Locale; +import java.util.Set; + +/** + * An enum that can be used to distinguish between an iOS simulator and device. + */ +public enum Platform { + DEVICE("iPhoneOS"), SIMULATOR("iPhoneSimulator"); + + private static final Set<String> SIMULATOR_ARCHS = ImmutableSet.of("i386", "x86_64"); + + private final String nameInPlist; + + Platform(String nameInPlist) { + this.nameInPlist = Preconditions.checkNotNull(nameInPlist); + } + + /** + * Returns the name of the "platform" as it appears in the CFBundleSupportedPlatforms plist + * setting. + */ + public String getNameInPlist() { + return nameInPlist; + } + + /** + * Returns the name of the "platform" as it appears in the plist when it appears in all-lowercase. + */ + public String getLowerCaseNameInPlist() { + return nameInPlist.toLowerCase(Locale.US); + } + + /** + * Returns the platform for the arch. + */ + public static Platform forArch(String arch) { + return Containing.item(SIMULATOR_ARCHS, arch) ? SIMULATOR : DEVICE; + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/RepeatedFamilyNameException.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/RepeatedFamilyNameException.java new file mode 100644 index 0000000000..39be4e65cd --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/RepeatedFamilyNameException.java @@ -0,0 +1,24 @@ +// 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.common; + +/** + * An exception that indicates a family name appeared twice in a sequence when only one is expected. + */ +public class RepeatedFamilyNameException extends IllegalArgumentException { + public RepeatedFamilyNameException(String message) { + super(message); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/TargetDeviceFamily.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/TargetDeviceFamily.java new file mode 100644 index 0000000000..3d369a15d1 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/TargetDeviceFamily.java @@ -0,0 +1,132 @@ +// 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.common; + +import com.google.common.base.Splitter; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; + +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** + * Possible values in the {@code TARGETED_DEVICE_FAMILY} build setting. + */ +public enum TargetDeviceFamily { + IPAD, IPHONE; + + /** + * Contains the values of the UIDeviceFamily plist info setting for each valid set of + * TargetDeviceFamilies. + */ + public static final Map<Set<TargetDeviceFamily>, List<Integer>> UI_DEVICE_FAMILY_VALUES = + ImmutableMap.<Set<TargetDeviceFamily>, List<Integer>>builder() + .put(ImmutableSet.of(TargetDeviceFamily.IPHONE), ImmutableList.of(1)) + .put(ImmutableSet.of(TargetDeviceFamily.IPAD), ImmutableList.of(2)) + .put(ImmutableSet.of(TargetDeviceFamily.IPHONE, TargetDeviceFamily.IPAD), + ImmutableList.of(1, 2)) + .build(); + + /** + * Returns the name of the family as it appears in build rules. + */ + public String getNameInRule() { + return BY_NAME_IN_RULE.get(this); + } + + /** + * Returns the name of family which should be used in the bundlemerge control proto. + */ + public String getBundleMergeName() { + return BY_BUNDLE_MERGE_NAME.get(this); + } + + private static final ImmutableBiMap<TargetDeviceFamily, String> BY_NAME_IN_RULE = + ImmutableBiMap.<TargetDeviceFamily, String>of(IPAD, "ipad", IPHONE, "iphone"); + + private static final ImmutableBiMap<TargetDeviceFamily, String> BY_BUNDLE_MERGE_NAME = + ImmutableBiMap.<TargetDeviceFamily, String>of(IPAD, "IPAD", IPHONE, "IPHONE"); + + private static Set<TargetDeviceFamily> fromNames( + Iterable<String> names, Map<String, TargetDeviceFamily> mapping) { + Set<TargetDeviceFamily> families = EnumSet.noneOf(TargetDeviceFamily.class); + for (String name : names) { + TargetDeviceFamily family = mapping.get(name); + if (family == null) { + throw new InvalidFamilyNameException(name); + } + if (!families.add(family)) { + throw new RepeatedFamilyNameException(name); + } + } + return families; + } + + /** + * Converts a sequence containing the strings returned by {@link #getBundleMergeName()} to a set + * of instances of this enum. + * + * <p>If there are multiple items in the returned set, they are in enumeration order. + * + * @param names the names of the families + * @throws InvalidFamilyNameException if some family name in the sequence was not recognized + * @throws RepeatedFamilyNameException if some family name appeared in the sequence twice + */ + public static Set<TargetDeviceFamily> fromBundleMergeNames(Iterable<String> names) { + return fromNames(names, BY_BUNDLE_MERGE_NAME.inverse()); + } + + /** + * Converts a sequence containing the strings returned by {@link #getNameInRule()} to a set of + * instances of this enum. + * + * <p>If there are multiple items in the returned set, they are in enumeration order. + * + * @param names the names of the families + * @throws InvalidFamilyNameException if some family name in the sequence was not recognized + * @throws RepeatedFamilyNameException if some family name appeared in the sequence twice + */ + public static Set<TargetDeviceFamily> fromNamesInRule(Iterable<String> names) { + return fromNames(names, BY_NAME_IN_RULE.inverse()); + } + + /** + * Converts the {@code TARGETED_DEVICE_FAMILY} setting in build settings to a set of + * {@code TargetedDevice}s. + */ + public static Set<TargetDeviceFamily> fromBuildSetting(String targetedDevice) { + ImmutableSet.Builder<TargetDeviceFamily> result = ImmutableSet.builder(); + for (String numericSetting : Splitter.on(",").split(targetedDevice)) { + numericSetting = numericSetting.trim(); + switch (numericSetting) { + case "1": + result.add(IPHONE); + break; + case "2": + result.add(IPAD); + break; + default: + throw new IllegalArgumentException( + "Expect comma-separated list containing only '1' and/or '2' for " + + "TARGETED_DEVICE_FAMILY: " + targetedDevice); + } + } + return result.build(); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/XcodeprojPath.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/XcodeprojPath.java new file mode 100644 index 0000000000..eedf326d13 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/common/XcodeprojPath.java @@ -0,0 +1,121 @@ +// 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.common; + +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.xcode.util.Equaling; +import com.google.devtools.build.xcode.util.Value; + +import java.nio.file.Path; +import java.util.List; + +/** + * Represents the path to an xcodeproj directory. Contains utilities for getting related + * information, including the project.pbxproj file and the project <em>name</em>, which is the + * .xcodeproj directory name without the ".xcodeproj" extension. + * + * @param <T> The type of the backing path, such as {@link java.nio.file.Path}. + */ +public class XcodeprojPath<T extends Comparable<T>> + extends Value<XcodeprojPath<T>> + implements Comparable<XcodeprojPath<T>> { + public static final String PBXPROJ_FILE_NAME = "project.pbxproj"; + public static final String XCODEPROJ_DIRECTORY_SUFFIX = ".xcodeproj"; + + /** + * An object that knows how to create {@code XcodeprojPath}s from paths of some other type. + */ + public static class Converter<T extends Comparable<T>> { + private final PathTransformer<T> transformer; + + public Converter(PathTransformer<T> transformer) { + this.transformer = checkNotNull(transformer); + } + + /** + * Converts a path to an XcodeprojPath. The given path may point to the {@code project.pbxproj} + * file or the {@code *.xcodeproj} directory. + */ + public XcodeprojPath<T> fromPath(T path) { + if (Equaling.of(PBXPROJ_FILE_NAME, transformer.name(path))) { + path = transformer.parent(path); + } + return new XcodeprojPath<T>(path, transformer); + } + + /** + * Converts normal paths to {@code ProjectFilePath}s using {@link #fromPath(Comparable)}. + */ + public List<XcodeprojPath<T>> fromPaths(Iterable<? extends T> pbxprojFiles) { + ImmutableList.Builder<XcodeprojPath<T>> result = new ImmutableList.Builder<>(); + for (T pbxprojFile : pbxprojFiles) { + result.add(fromPath(pbxprojFile)); + } + return result.build(); + } + } + + private final T xcodeprojDirectory; + private final PathTransformer<T> transformer; + + public XcodeprojPath(T xcodeprojDirectory, PathTransformer<T> transformer) { + super(xcodeprojDirectory); + checkArgument(transformer.name(xcodeprojDirectory).endsWith(XCODEPROJ_DIRECTORY_SUFFIX), + "xcodeprojDirectory should end with %s, but it is '%s'", XCODEPROJ_DIRECTORY_SUFFIX, + xcodeprojDirectory); + this.xcodeprojDirectory = xcodeprojDirectory; + this.transformer = transformer; + } + + /** Returns a converter which works for the Java {@code Path} class. */ + public static Converter<Path> converter() { + return new Converter<>(PathTransformer.FOR_JAVA_PATH); + } + + public final T getXcodeprojDirectory() { + return xcodeprojDirectory; + } + + public final T getPbxprojFile() { + return transformer.join(xcodeprojDirectory, PBXPROJ_FILE_NAME); + } + + /** + * Returns the package or directory in which the project is located. For instance, if the project + * file is {@code /foo/bar/App.xcodeproj/project.pbxproj}, then this method returns + * {@code /foo/bar}. + */ + public final T getXcodeprojContainerDir() { + return transformer.parent(xcodeprojDirectory); + } + + /** + * Returns the name of the xcodeproj directory without the {@code .xcodeproj} extension or the + * containing directory. For instance, for an xcodeproj directory of + * {@code /client/foo.xcodeproj}, this method returns {@code "foo"}. + */ + public final String getProjectName() { + String pathStr = transformer.name(xcodeprojDirectory); + return pathStr.substring(0, pathStr.length() - XCODEPROJ_DIRECTORY_SUFFIX.length()); + } + + @Override + public final int compareTo(XcodeprojPath<T> o) { + return getXcodeprojDirectory().compareTo(o.getXcodeprojDirectory()); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Containing.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Containing.java new file mode 100644 index 0000000000..8f2434d28f --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Containing.java @@ -0,0 +1,71 @@ +// 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.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.collect.Multimap; + +import java.util.Collection; +import java.util.Map; + +/** + * Provides methods that make checking for the presence of an item in a collection type-safe. For + * instance, in {@code foo.containsKey(bar)}, where {@code foo} is a {@code Map<K, V>}, {@code bar} + * can be a type other than {@code K} and may be {@code null}, in which case the method will just + * return false (collections that allow null references may of course return true in the latter + * case). + * <p> + * The methods in this class, such as {@link #key(Map, Object)}, will cause a compiler error if you + * use the wrong type and throw a {@link NullPointerException} if you pass {@code null} for the + * object whose presence to check. In the case where you want to check for {@code null} in a + * collection, add a new method to this class, use the methods in the plain Collections API + * (such as {@link Collection#contains(Object)}), or use the {@code Optional} type as the element of + * the collection. + * <p> + * TODO(bazel-team): This class should either be simplified or eliminated when the + * CollectionIncompatibleType feature is available: + * https://code.google.com/p/error-prone/wiki/CollectionIncompatibleType + */ +public class Containing { + private Containing() { + throw new UnsupportedOperationException("static-only"); + } + + public static <K> boolean key(Map<K, ?> map, K key) { + checkNotNull(key); + return map.containsKey(key); + } + + public static <K> boolean key(Multimap<K, ?> map, K key) { + checkNotNull(key); + return map.containsKey(key); + } + + public static <E> boolean item(Collection<E> collection, E item) { + checkNotNull(item); + return collection.contains(item); + } + + public static <V> boolean value(Map<?, V> map, V value) { + checkNotNull(value); + return map.containsValue(value); + } + + public static <V> boolean value(Multimap<?, V> map, V value) { + checkNotNull(value); + return map.containsValue(value); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Difference.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Difference.java new file mode 100644 index 0000000000..54bd567f28 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Difference.java @@ -0,0 +1,43 @@ +// 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.util; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * Provides utility methods that make difference operations type safe. + * + * {@link Sets#difference(Set, Set)} requires no type bound on the second set, which has led to + * calls which can never subtract any elements because the set being subtracted cannot contain any + * elements which may exist in the first set. + */ +public class Difference { + private Difference() { + throw new UnsupportedOperationException("static-only"); + } + + /** + * Returns the elements in set1 which are not in set2. set2 may contain extra elements which will + * be ignored. + * + * @param set1 Set whose elements to return + * @param set2 Set whose elements are to be subtracted + */ + public static <T> Set<T> of(Set<T> set1, Set<T> set2) { + return Sets.difference(set1, set2); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Equaling.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Equaling.java new file mode 100644 index 0000000000..c359e57056 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Equaling.java @@ -0,0 +1,80 @@ +// 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.util; + +import static com.google.common.base.Preconditions.checkNotNull; + +import com.google.common.base.Optional; + +import java.io.File; +import java.nio.file.Path; +import java.util.List; +import java.util.Set; + +/** + * Provides utility methods that make equality comparison type safe. The + * {@link Object#equals(Object)} method usually returns false when the other object is {@code null} + * or a different runtime class. These utility methods try to force each object to be of the same + * class (with the method signatures) and non-null (and throwing a {@link NullPointerException} if + * either one is null. + */ +public class Equaling { + private Equaling() { + throw new UnsupportedOperationException("static-only"); + } + + // Note that we always checkNotNull(b) on a separate line from a.equals(b). This is to make it so + // the stack trace will tell you exactly which reference is null. + + public static <T extends Value<T>> boolean of(Value<T> a, Value<T> b) { + checkNotNull(b); + return a.equals(b); + } + + public static boolean of(String a, String b) { + checkNotNull(b); + return a.equals(b); + } + + public static <T> boolean of(Optional<T> a, Optional<T> b) { + checkNotNull(b); + return a.equals(b); + } + + public static <T> boolean of(Set<T> a, Set<T> b) { + checkNotNull(b); + return a.equals(b); + } + + public static boolean of(File a, File b) { + checkNotNull(b); + return a.equals(b); + } + + public static boolean of(Path a, Path b) { + checkNotNull(b); + return a.equals(b); + } + + public static <T> boolean of(List<T> a, List<T> b) { + checkNotNull(b); + return a.equals(b); + } + + public static <T extends Enum<T>> boolean of(Enum<T> a, Enum<T> b) { + checkNotNull(b); + return a.equals(b); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Intersection.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Intersection.java new file mode 100644 index 0000000000..7171cdeb65 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Intersection.java @@ -0,0 +1,38 @@ +// 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.util; + +import com.google.common.collect.Sets; + +import java.util.Set; + +/** + * Provides utility methods that make intersections operations type safe. + * + * {@link Sets#intersection(Set, Set)} requires no type bound on the second set, which could lead to + * calls which always return an empty set. + */ +public class Intersection { + private Intersection() { + throw new UnsupportedOperationException("static-only"); + } + + /** + * Returns the intersection of two sets. + */ + public static <T> Set<T> of(Set<T> set1, Set<T> set2) { + return Sets.intersection(set1, set2); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Interspersing.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Interspersing.java new file mode 100644 index 0000000000..89bf487221 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Interspersing.java @@ -0,0 +1,71 @@ +// 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.util; + +import com.google.common.base.Function; +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterables; + +/** + * Utility code for interspersing items into sequences. + */ +public class Interspersing { + private Interspersing() {} + + /** + * Inserts {@code what} before each item in {@code sequence}, returning a lazy sequence of twice + * the length. + */ + public static <E> Iterable<E> beforeEach(final E what, Iterable<E> sequence) { + Preconditions.checkNotNull(what); + return Iterables.concat( + Iterables.transform( + sequence, + new Function<E, Iterable<E>>() { + @Override + public Iterable<E> apply(E element) { + return ImmutableList.of(what, element); + } + } + )); + } + + /** + * Prepends {@code what} to each string in {@code sequence}, returning a lazy sequence of the + * same length. + */ + public static Iterable<String> + prependEach(final String what, Iterable<String> sequence) { + Preconditions.checkNotNull(what); + return Iterables.transform( + sequence, + new Function<String, String>() { + @Override + public String apply(String input) { + return what + input; + } + }); + } + + /** + * Similar to {@link #prependEach(String, Iterable)}, but also converts each item in the sequence + * to a string. + */ + public static <E> Iterable<String> + prependEach(String what, Iterable<E> sequence, Function<? super E, String> toString) { + return prependEach(what, Iterables.transform(sequence, toString)); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Mapping.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Mapping.java new file mode 100644 index 0000000000..0e5ef53f16 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Mapping.java @@ -0,0 +1,39 @@ +// 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.util; + +import com.google.common.base.Optional; +import com.google.common.base.Preconditions; + +import java.util.Map; + +/** + * Provides utility methods that make map lookup safe. + */ +public class Mapping { + private Mapping() { + throw new UnsupportedOperationException("static-only"); + } + + /** + * Returns the value mapped to the given key for a map. If the mapping is not present, an absent + * {@code Optional} is returned. + * @throws NullPointerException if the map or key argument is null + */ + public static <K, V> Optional<V> of(Map<K, V> map, K key) { + Preconditions.checkNotNull(key); + return Optional.fromNullable(map.get(key)); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Value.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Value.java new file mode 100644 index 0000000000..6363af19a3 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/util/Value.java @@ -0,0 +1,69 @@ +// 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.util; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableList; + +/** + * Represents a type whose equality, hash code, and string representation are defined by a single + * immutable array. This class is designed to be extended by a final class, and to pass the member + * data to this class's constructor. + * + * @param <V> the base class that extends {@code Value} + */ +public class Value<V extends Value<V>> { + private final Object memberData; + + /** + * Constructs a new instance with the given member data. Generally, all member data should be + * reflected in final fields in the child class. + * @throws NullPointerException if any element in {@code memberData} is null + */ + public Value(Object... memberData) { + Preconditions.checkArgument(memberData.length > 0); + this.memberData = (memberData.length == 1) + ? Preconditions.checkNotNull(memberData[0]) : ImmutableList.copyOf(memberData); + } + + /** + * A type-safe alternative to calling {@code a.equals(b)}. When using {@code a.equals(b)}, + * {@code b} may accidentally be a different class from {@code a}, in which case there will be no + * compiler warning and the result will always be false. This method requires both values to have + * compatible types and to be non-null. + */ + public boolean equalsOther(V other) { + return equals(Preconditions.checkNotNull(other)); + } + + @Override + public boolean equals(Object o) { + if ((o == null) || (o.getClass() != getClass())) { + return false; + } + Value<?> other = (Value<?>) o; + return memberData.equals(other.memberData); + } + + @Override + public int hashCode() { + return memberData.hashCode(); + } + + @Override + public String toString() { + return getClass().getSimpleName() + ":" + memberData.toString(); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipInputEntry.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipInputEntry.java new file mode 100644 index 0000000000..01aaad0373 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zip/ZipInputEntry.java @@ -0,0 +1,141 @@ +// 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.zip; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.singlejar.ZipCombiner; +import com.google.devtools.build.xcode.util.Value; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +/** + * Describes an entry in a zip file when the zip file is being created. + */ +public class ZipInputEntry extends Value<ZipInputEntry> { + /** + * The external file attribute used for files by default. This indicates a non-executable regular + * file that is readable by group and world. + */ + public static final int DEFAULT_EXTERNAL_FILE_ATTRIBUTE = (0100644 << 16); + + /** + * An external file attribute that indicates an executable regular file that is readable and + * executable by group and world. + */ + public static final int EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE = (0100755 << 16); + + /** + * The central directory record information that is used when adding a plain, non-executable file. + */ + public static final ZipCombiner.DirectoryEntryInfo DEFAULT_DIRECTORY_ENTRY_INFO = + ZipCombiner.DEFAULT_DIRECTORY_ENTRY_INFO + // This is what .ipa files built by Xcode are set to. Upper byte indicates Unix host. + // Lower byte indicates version of encoding software + // (note that 0x1e = 30 = (3.0 * 10), so 0x1e translates to 3.0). + // The Unix host value in the upper byte is what causes the external file attribute to be + // interpreted as POSIX permission and file type bits. + .withMadeByVersion((short) 0x031e) + .withExternalFileAttribute(DEFAULT_EXTERNAL_FILE_ATTRIBUTE); + + private final Path source; + private final String zipPath; + private final int externalFileAttribute; + + public ZipInputEntry(Path source, String zipPath) { + this(source, zipPath, DEFAULT_EXTERNAL_FILE_ATTRIBUTE); + } + + public ZipInputEntry(Path source, String zipPath, int externalFileAttribute) { + super(source, zipPath, externalFileAttribute); + this.source = source; + this.zipPath = zipPath; + this.externalFileAttribute = externalFileAttribute; + } + + /** + * The location of the source file to place in the zip. + */ + public Path getSource() { + return source; + } + + /** + * The full path of the item in the zip. + */ + public String getZipPath() { + return zipPath; + } + + /** + * The external file attribute field of the zip entry in the central directory record. On + * Unix-originated .zips, this corresponds to the permission bits (e.g. 0755 for an excutable + * file). + */ + public int getExternalFileAttribute() { + return externalFileAttribute; + } + + /** + * Adds this entry to a zip using the given {@code ZipCombiner}. + */ + public void add(ZipCombiner combiner) throws IOException { + try (InputStream inputStream = Files.newInputStream(source)) { + combiner.addFile(zipPath, ZipCombiner.DOS_EPOCH, inputStream, + DEFAULT_DIRECTORY_ENTRY_INFO.withExternalFileAttribute(externalFileAttribute)); + } + } + + public static void addAll(ZipCombiner combiner, Iterable<ZipInputEntry> inputs) + throws IOException { + for (ZipInputEntry input : inputs) { + input.add(combiner); + } + } + + /** + * Returns the entries to place in a zip file as if the zip file mirrors some directory structure. + * For instance, if {@code rootDirectory} is /tmp/foo, and the following files exist: + * <ul> + * <li>/tmp/foo/a + * <li>/tmp/foo/bar/c + * <li>/tmp/foo/baz/d + * </ul> + * This function will return entries which point to these files and have in-zip paths of: + * <ul> + * <li>a + * <li>bar/c + * <li>baz/d + * </ul> + */ + public static Iterable<ZipInputEntry> fromDirectory(final Path rootDirectory) throws IOException { + final ImmutableList.Builder<ZipInputEntry> zipInputs = new ImmutableList.Builder<>(); + Files.walkFileTree(rootDirectory, new SimpleFileVisitor<Path>() { + @Override + public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { + // TODO(bazel-team): Set the external file attribute based on the attribute of the + // permissions of the file on-disk. + zipInputs.add(new ZipInputEntry(file, rootDirectory.relativize(file).toString())); + return FileVisitResult.CONTINUE; + } + }); + return zipInputs.build(); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Arguments.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Arguments.java new file mode 100644 index 0000000000..e6bea8cce8 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Arguments.java @@ -0,0 +1,57 @@ +// 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.zippingoutput; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.xcode.util.Value; + +/** + * Arguments that have been parsed from a do-something-and-zip-output wrapper. + */ +public class Arguments extends Value<Arguments> { + + private final String outputZip; + private final String bundleRoot; + private final String subtoolCmd; + private final ImmutableList<String> subtoolExtraArgs; + + Arguments( + String outputZip, + String bundleRoot, + String subtoolCmd, + ImmutableList<String> subtoolExtraArgs) { + super(outputZip, bundleRoot, subtoolCmd, subtoolExtraArgs); + this.outputZip = outputZip; + this.bundleRoot = bundleRoot; + this.subtoolCmd = subtoolCmd; + this.subtoolExtraArgs = subtoolExtraArgs; + } + + public String outputZip() { + return outputZip; + } + + public String bundleRoot() { + return bundleRoot; + } + + public String subtoolCmd() { + return subtoolCmd; + } + + public ImmutableList<String> subtoolExtraArgs() { + return subtoolExtraArgs; + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/ArgumentsParsing.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/ArgumentsParsing.java new file mode 100644 index 0000000000..06e0ae62aa --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/ArgumentsParsing.java @@ -0,0 +1,99 @@ +// 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.zippingoutput; + +import com.google.common.base.Optional; +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.xcode.util.Value; + +import java.nio.file.FileSystem; +import java.nio.file.Files; +import java.util.Locale; + +/** + * Arguments passed to the do-something-then-zip wrapper tool. + */ +class ArgumentsParsing extends Value<ArgumentsParsing> { + + private final Optional<String> error; + private final Optional<Arguments> arguments; + + private ArgumentsParsing(Optional<String> error, Optional<Arguments> arguments) { + super(error, arguments); + this.error = error; + this.arguments = arguments; + } + + public Optional<String> error() { + return error; + } + + public Optional<Arguments> arguments() { + return arguments; + } + + private static final int MIN_ARGS = 3; + + /** + * @param args raw arguments passed to wrapper tool through the {@code main} method + * @param subtoolName name of the subtool, such as "actool" + * @return an instance based on results of parsing the given arguments. + */ + public static ArgumentsParsing parse(FileSystem fileSystem, String[] args, String wrapperName, + String subtoolName) { + String capitalizedSubtool = subtoolName.toUpperCase(Locale.US); + if (args.length < MIN_ARGS) { + return new ArgumentsParsing(Optional.of(String.format( + "Expected at least %1$d args.\n" + + "Usage: java %2$s OUTZIP ARCHIVEROOT (%3$s_CMD %3$s ARGS)\n" + + "Runs %4$s and zips the results.\n" + + "OUTZIP - the path to place the output zip file.\n" + + "ARCHIVEROOT - the path in the zip to place the output, or an empty\n" + + " string for the root of the zip. e.g. 'Payload/foo.app'. If\n" + + " this tool outputs a single file, ARCHIVEROOT is the name of\n" + + " the only file in the zip file.\n" + + "%3$s_CMD - path to the subtool.\n" + + " e.g. /Applications/Xcode.app/Contents/Developer/usr/bin/actool\n" + + "%3$s ARGS - the arguments to pass to %4$s besides the\n" + + " one that specifies the output directory.\n", + MIN_ARGS, wrapperName, capitalizedSubtool, subtoolName)), + Optional.<Arguments>absent()); + } + String outputZip = args[0]; + String archiveRoot = args[1]; + String subtoolCmd = args[2]; + if (archiveRoot.startsWith("/")) { + return new ArgumentsParsing( + Optional.of(String.format("Archive root cannot start with /: '%s'\n", archiveRoot)), + Optional.<Arguments>absent()); + } + + // TODO(bazel-team): Remove this hack when the released version of Bazel uses the correct momc + // path for device builds. + subtoolCmd = subtoolCmd.replace("/iPhoneOS.platform/", "/iPhoneSimulator.platform/"); + if (!Files.isRegularFile(fileSystem.getPath(subtoolCmd))) { + return new ArgumentsParsing( + Optional.of(String.format( + "The given %s_CMD does not exist: '%s'\n", capitalizedSubtool, subtoolCmd)), + Optional.<Arguments>absent()); + } + return new ArgumentsParsing( + Optional.<String>absent(), + Optional.<Arguments>of( + new Arguments( + outputZip, archiveRoot, subtoolCmd, + ImmutableList.copyOf(args).subList(MIN_ARGS, args.length)))); + } +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrapper.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrapper.java new file mode 100644 index 0000000000..975cf7214e --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrapper.java @@ -0,0 +1,55 @@ +// 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.zippingoutput; + +/** + * Defines a zipping wrapper for a tool. A wrapper is a tool that runs some subcommand that writes + * its output (usually multiple files in a non-trivial directory structure) to some directory, and + * then zips the output of that subtool into a timestamp-less zip file whose location is specified + * on the command line. + * <p> + * The arguments passed are passed in this form: + * <pre> + * java {wrapper_name} OUTZIP ARCHIVEROOT (SUBTOOL_CMD SUBTOOL ARGS) + * </pre> + * Where SUBTOOL ARGS are the arguments to pass to SUBTOOL_CMD unchanged, except the argument that + * specifies the output directory. Note that the arguments are positional and do not use flags in + * the form of -f or --foo, except for those flags passed directly to and interpreted by the + * subtool itself. + * <p> + * A wrapper has some simple metadata the name of the wrapped tool + * and how to transform {@link Arguments} passed to the wrapper tool into arguments for the subtool. + */ +public interface Wrapper { + /** The name of the wrapper, such as {@code ActoolZip}. */ + String name(); + + /** The subtool name, such as {@code actool}. */ + String subtoolName(); + + /** + * Returns the command (i.e. argv), including the executable file, to be executed. + * @param arguments the parsed arguments passed to the wrapper tool + * @param outputDirectory the output directory which the subtool should write tool + */ + Iterable<String> subCommand(Arguments arguments, String outputDirectory); + + /** + * Indicates whether the output directory must exist before invoking the wrapped tool, this being + * dependent on the nature of the tool only. Note that the directory immediately containing the + * output directory is guaranteed to always exist, regardless of this method's return value. + */ + boolean outputDirectoryMustExist(); +} diff --git a/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrappers.java b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrappers.java new file mode 100644 index 0000000000..b4b3203386 --- /dev/null +++ b/src/tools/xcode-common/java/com/google/devtools/build/xcode/zippingoutput/Wrappers.java @@ -0,0 +1,67 @@ +// 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.zippingoutput; + +import com.google.common.collect.ImmutableList; +import com.google.devtools.build.singlejar.ZipCombiner; +import com.google.devtools.build.xcode.zip.ZipInputEntry; + +import java.io.IOException; +import java.io.OutputStream; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +/** Utility code for working with {@link Wrapper}s. */ +public class Wrappers { + private Wrappers() { + throw new UnsupportedOperationException("static-only"); + } + + /** + * Runs the given wrapper using command-line arguments passed to the {@code main} method. Calling + * this method should be the last thing you do in {@code main}, because it may exit prematurely + * with {@link System#exit(int)}. + */ + public static void execute(String[] argsArray, Wrapper wrapper) + throws IOException, InterruptedException { + ArgumentsParsing argsParsing = ArgumentsParsing.parse( + FileSystems.getDefault(), argsArray, wrapper.name(), wrapper.subtoolName()); + for (String error : argsParsing.error().asSet()) { + System.err.printf(error); + System.exit(1); + } + for (Arguments args : argsParsing.arguments().asSet()) { + Path outputDir = Files.createTempDirectory("ZippingOutput"); + Path rootedOutputDir = outputDir.resolve(args.bundleRoot()); + Files.createDirectories( + wrapper.outputDirectoryMustExist() ? rootedOutputDir : rootedOutputDir.getParent()); + + ImmutableList<String> subCommandArguments = + ImmutableList.copyOf(wrapper.subCommand(args, rootedOutputDir.toString())); + Process subProcess = new ProcessBuilder(subCommandArguments).inheritIO().start(); + int exit = subProcess.waitFor(); + if (exit != 0) { + System.exit(exit); + } + + try (OutputStream out = Files.newOutputStream(Paths.get(args.outputZip())); + ZipCombiner combiner = new ZipCombiner(out)) { + ZipInputEntry.addAll(combiner, ZipInputEntry.fromDirectory(outputDir)); + } + } + } +} |