blob: 21436a543a8b67bf40f8b7ff0ed72a1844f98cc0 [file] [log] [blame]
// 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();
}
}