aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/objc/BundleableFile.java
blob: a6dfeb33fb002f0341e9338737b05d477c200f4f (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// 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;

  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().getPackageFragment();
    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();
  }
}