// Copyright 2014 The Bazel Authors. 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.lib.rules.objc; import static com.google.devtools.build.lib.rules.objc.ArtifactListAttribute.BUNDLE_IMPORTS; import static com.google.devtools.build.lib.rules.objc.ObjcCommon.BUNDLE_CONTAINER_TYPE; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.devtools.build.lib.actions.Artifact; import com.google.devtools.build.lib.analysis.RuleContext; import com.google.devtools.build.lib.vfs.PathFragment; import com.google.devtools.build.xcode.bundlemerge.proto.BundleMergeProtos.BundleFile; /** * Represents a file which is processed to another file and bundled. It contains the * {@code Artifact} corresponding to the original file as well as the {@code Artifact} for the file * converted to its bundled form. Examples of files that fit this pattern are .strings and .xib * files. */ public final class BundleableFile extends Value<BundleableFile> { static final int EXECUTABLE_EXTERNAL_FILE_ATTRIBUTE = 0100755 << 16; static final int DEFAULT_EXTERNAL_FILE_ATTRIBUTE = 0100644 << 16; /** The field in the Skylark struct that holds the {@code bundled} artifact. */ static final String BUNDLED_FIELD = "file"; /** The field in the Skylark struct that holds the {@code bundlePath} string. */ static final String BUNDLE_PATH_FIELD = "bundle_path"; private final Artifact bundled; private final String bundlePath; private final int zipExternalFileAttribute; /** * Creates an instance whose {@code zipExternalFileAttribute} value is * {@link #DEFAULT_EXTERNAL_FILE_ATTRIBUTE}. */ BundleableFile(Artifact bundled, String bundlePath) { this(bundled, bundlePath, DEFAULT_EXTERNAL_FILE_ATTRIBUTE); } /** * @param bundled the {@link Artifact} whose data is placed in the bundle * @param bundlePath the path of the file in the bundle * @param zipExternalFileAttribute external file attribute of the file in the central directory of * the bundle (zip file). The lower 16 bits contain the MS-DOS file attributes. The upper 16 * bits contain the Unix file attributes, for instance 0100755 (octal) for a regular file with * permissions {@code rwxr-xr-x}. */ BundleableFile(Artifact bundled, String bundlePath, int zipExternalFileAttribute) { super(new ImmutableMap.Builder<String, Object>() .put("bundled", bundled) .put("bundlePath", bundlePath) .put("zipExternalFileAttribute", zipExternalFileAttribute) .build()); this.bundled = bundled; this.bundlePath = bundlePath; this.zipExternalFileAttribute = zipExternalFileAttribute; } static String flatBundlePath(PathFragment path) { String containingDir = path.getParentDirectory().getBaseName(); return (containingDir.endsWith(".lproj") ? (containingDir + "/") : "") + path.getBaseName(); } /** * Given a sequence of non-compiled resource files, returns a sequence of the same length of * instances of this class with the resource paths flattened (resources are put in the bundle * root) or, if the source file is in a directory ending in {@code .lproj}, in a directory of that * name directly under the bundle root. * * <p>Non-compiled resource files are resources which are not processed before placing them in the * final bundle. This is different from (for example) {@code .strings} and {@code .xib} files, * which must be converted to binary plist form or compiled. * * @param files a sequence of artifacts corresponding to non-compiled resource files */ public static Iterable<BundleableFile> flattenedRawResourceFiles(Iterable<Artifact> files) { ImmutableList.Builder<BundleableFile> result = new ImmutableList.Builder<>(); for (Artifact file : files) { result.add(new BundleableFile(file, flatBundlePath(file.getExecPath()))); } return result.build(); } /** * Given a sequence of non-compiled resource files, returns a sequence of the same length of * instances of this class with the resource paths copied as-is into the bundle root. * * <p>Non-compiled resource files are resources which are not processed before placing them in the * final bundle. This is different from (for example) {@code .strings} and {@code .xib} files, * which must be converted to binary plist form or compiled. * * @param files a sequence of artifacts corresponding to non-compiled resource files */ public static Iterable<BundleableFile> structuredRawResourceFiles(Iterable<Artifact> files) { ImmutableList.Builder<BundleableFile> result = new ImmutableList.Builder<>(); for (Artifact file : files) { result.add(new BundleableFile(file, ownerBundlePath(file))); } return result.build(); } private static String ownerBundlePath(Artifact file) { PathFragment packageFragment = file.getArtifactOwner().getLabel().getPackageIdentifier().getSourceRoot(); return file.getRootRelativePath().relativeTo(packageFragment).toString(); } /** * Returns an instance for every file in a bundle directory. * <p> * This uses the parent-most container matching {@code *.bundle} as the bundle root. * TODO(bazel-team): add something like an import_root attribute to specify this explicitly, which * will be helpful if a bundle that appears to be nested needs to be imported alone. */ public static Iterable<BundleableFile> bundleImportsFromRule(RuleContext context) { ImmutableList.Builder<BundleableFile> result = new ImmutableList.Builder<>(); for (Artifact artifact : BUNDLE_IMPORTS.get(context)) { for (PathFragment container : ObjcCommon.farthestContainerMatching(BUNDLE_CONTAINER_TYPE, artifact).asSet()) { // TODO(bazel-team): Figure out if we need to remove symbols of architectures we aren't // building for from the binary in the bundle. result.add(new BundleableFile( artifact, // The path from the artifact's container (including the container), to the artifact // itself. For instance, if artifact is foo/bar.bundle/baz, then this value // is bar.bundle/baz. artifact.getExecPath().relativeTo(container.getParentDirectory()).getSafePathString())); } } return result.build(); } /** * Returns the location into which this file should be put in, relative to the bundle root. */ public String getBundlePath() { return bundlePath; } /** * Returns the artifact representing the source for this bundleable file. */ public Artifact getBundled() { return bundled; } /** * Returns bundle files for each given strings file. These are used to merge the strings files to * the final application bundle. */ public static Iterable<BundleFile> toBundleFiles(Iterable<BundleableFile> files) { ImmutableList.Builder<BundleFile> result = new ImmutableList.Builder<>(); for (BundleableFile file : files) { result.add(BundleFile.newBuilder() .setBundlePath(file.bundlePath) .setSourceFile(file.bundled.getExecPathString()) .setExternalFileAttribute(file.zipExternalFileAttribute) .build()); } return result.build(); } /** * Returns the artifacts for the bundled files. These can be used, for instance, as the input * files to add to the bundlemerge action for a bundle that contains all the given files. */ public static Iterable<Artifact> toArtifacts(Iterable<BundleableFile> files) { ImmutableList.Builder<Artifact> result = new ImmutableList.Builder<>(); for (BundleableFile file : files) { result.add(file.bundled); } return result.build(); } }