aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/main/java/com/google/devtools/build/lib/rules/cpp/Link.java
blob: a6288e5eed12bdb4d708848a1c5caf64c991a3fa (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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
// 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.cpp;

import com.google.common.collect.AbstractIterator;
import com.google.common.collect.ImmutableList;
import com.google.devtools.build.lib.actions.Artifact;
import com.google.devtools.build.lib.collect.CollectionUtils;
import com.google.devtools.build.lib.collect.nestedset.NestedSet;
import com.google.devtools.build.lib.rules.cpp.LinkerInputs.LibraryToLink;
import com.google.devtools.build.lib.util.FileTypeSet;
import java.util.Iterator;

/**
 * Utility types and methods for generating command lines for the linker, given
 * a CppLinkAction or LinkConfiguration.
 *
 * <p>The linker commands, e.g. "ar", may not be functional, i.e.
 * they may mutate the output file rather than overwriting it.
 * To avoid this, we need to delete the output file before invoking the
 * command.  But that is not done by this class; deleting the output
 * file is the responsibility of the classes implementing CppLinkActionContext.
 */
public abstract class Link {

  /**
   * Categories of link action that must be defined with action_configs in any toolchain. 
   */
  static final Iterable<LinkTargetType> MANDATORY_LINK_TARGET_TYPES =
      ImmutableList.of(
          LinkTargetType.STATIC_LIBRARY,
          LinkTargetType.PIC_STATIC_LIBRARY,
          LinkTargetType.ALWAYS_LINK_STATIC_LIBRARY,
          LinkTargetType.ALWAYS_LINK_PIC_STATIC_LIBRARY,
          LinkTargetType.DYNAMIC_LIBRARY,
          LinkTargetType.EXECUTABLE,
          LinkTargetType.INTERFACE_DYNAMIC_LIBRARY);

  private Link() {} // uninstantiable

  /**
   * These file are supposed to be added using {@code addLibrary()} calls to {@link CppLinkAction}
   * but will never be expanded to their constituent {@code .o} files. {@link CppLinkAction} checks
   * that these files are never added as non-libraries.
   */
  public static final FileTypeSet SHARED_LIBRARY_FILETYPES = FileTypeSet.of(
      CppFileTypes.SHARED_LIBRARY,
      CppFileTypes.VERSIONED_SHARED_LIBRARY,
      CppFileTypes.INTERFACE_SHARED_LIBRARY);

  /**
   * These need special handling when --thin_archive is true. {@link CppLinkAction} checks that
   * these files are never added as non-libraries.
   */
  public static final FileTypeSet ARCHIVE_LIBRARY_FILETYPES = FileTypeSet.of(
      CppFileTypes.ARCHIVE,
      CppFileTypes.PIC_ARCHIVE,
      CppFileTypes.ALWAYS_LINK_LIBRARY,
      CppFileTypes.ALWAYS_LINK_PIC_LIBRARY);

  public static final FileTypeSet ARCHIVE_FILETYPES = FileTypeSet.of(
      CppFileTypes.ARCHIVE,
      CppFileTypes.PIC_ARCHIVE);

  public static final FileTypeSet LINK_LIBRARY_FILETYPES = FileTypeSet.of(
      CppFileTypes.ALWAYS_LINK_LIBRARY,
      CppFileTypes.ALWAYS_LINK_PIC_LIBRARY);


  /** The set of object files */
  public static final FileTypeSet OBJECT_FILETYPES = FileTypeSet.of(
      CppFileTypes.OBJECT_FILE,
      CppFileTypes.PIC_OBJECT_FILE);

  /**
   * Prefix that is prepended to command line entries that refer to the output
   * of cc_fake_binary compile actions. This is a bad hack to signal to the code
   * in {@code CppLinkAction#executeFake(Executor, FileOutErr)} that it needs
   * special handling.
   */
  public static final String FAKE_OBJECT_PREFIX = "fake:";

  /**
   * Whether a particular link target requires PIC code.
   */
  public enum Picness {
    PIC,
    NOPIC
  }

  /**
   * Whether a particular link target linked in statically or dynamically.
   */
  public enum Staticness {
    STATIC,
    DYNAMIC
  }

  /**
   * Types of ELF files that can be created by the linker (.a, .so, .lo,
   * executable).
   */
  public enum LinkTargetType {
    /** A normal static archive. */
    STATIC_LIBRARY(
        ".a",
        Staticness.STATIC,
        "c++-link-static-library",
        Picness.NOPIC,
        ArtifactCategory.STATIC_LIBRARY),
    
    /** An objc static archive. */
    OBJC_ARCHIVE(
        ".a", 
        Staticness.STATIC, 
        "objc-archive", 
        Picness.NOPIC,
        ArtifactCategory.STATIC_LIBRARY),

    /** A static archive with .pic.o object files (compiled with -fPIC). */
    PIC_STATIC_LIBRARY(
        ".pic.a",
        Staticness.STATIC,
        "c++-link-pic-static-library",
        Picness.PIC,
        ArtifactCategory.STATIC_LIBRARY),

    /** An interface dynamic library. */
    INTERFACE_DYNAMIC_LIBRARY(
        ".ifso",
        Staticness.DYNAMIC,
        "c++-link-interface-dynamic-library",
        Picness.NOPIC,  // Actually PIC but it's not indicated in the file name
        ArtifactCategory.INTERFACE_LIBRARY),

    /** A dynamic library. */
    DYNAMIC_LIBRARY(
        ".so",
        Staticness.DYNAMIC,
        "c++-link-dynamic-library",
        Picness.NOPIC,  // Actually PIC but it's not indicated in the file name
        ArtifactCategory.DYNAMIC_LIBRARY),

    /** A static archive without removal of unused object files. */
    ALWAYS_LINK_STATIC_LIBRARY(
        ".lo",
        Staticness.STATIC,
        "c++-link-alwayslink-static-library",
        Picness.NOPIC,
        ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY),

    /** A PIC static archive without removal of unused object files. */
    ALWAYS_LINK_PIC_STATIC_LIBRARY(
        ".pic.lo",
        Staticness.STATIC,
        "c++-link-alwayslink-pic-static-library",
        Picness.PIC,
        ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY),

    /** An executable binary. */
    EXECUTABLE(
        "",
        Staticness.DYNAMIC,
        "c++-link-executable",
        Picness.NOPIC,  // Picness is not indicate in the file name
        ArtifactCategory.EXECUTABLE);

    private final String extension;
    private final Staticness staticness;
    private final String actionName;
    private final ArtifactCategory linkerOutput;
    private final Picness picness;

    LinkTargetType(
        String extension,
        Staticness staticness,
        String actionName,
        Picness picness,
        ArtifactCategory linkerOutput) {
      this.extension = extension;
      this.staticness = staticness;
      this.actionName = actionName;
      this.linkerOutput = linkerOutput;
      this.picness = picness;
    }

    /**
     * Returns whether the name of the output file should denote that the code in the file is PIC.
     */
    public Picness picness() {
      return picness;
    }

    public String getExtension() {
      return extension;
    }

    public Staticness staticness() {
      return staticness;
    }
    
    /** Returns an {@code ArtifactCategory} identifying the artifact type this link action emits. */
    public ArtifactCategory getLinkerOutput() {
      return linkerOutput;
    }

    /**
     * The name of a link action with this LinkTargetType, for the purpose of crosstool feature
     * selection.
     */
    public String getActionName() {
      return actionName;
    }
  }

  /**
   * The degree of "staticness" of symbol resolution during linking.
   */
  public enum LinkStaticness {
    FULLY_STATIC,       // Static binding of all symbols.
    MOSTLY_STATIC,      // Use dynamic binding only for symbols from glibc.
    DYNAMIC,            // Use dynamic binding wherever possible.
  }

  /**
   * Types of archive.
   */
  public enum ArchiveType {
    FAT,            // Regular archive that includes its members.
    THIN,           // Thin archive that just points to its members.
    START_END_LIB   // A --start-lib ... --end-lib group in the command line.
  }

  static boolean useStartEndLib(LinkerInput linkerInput, ArchiveType archiveType) {
    // TODO(bazel-team): Figure out if PicArchives are actually used. For it to be used, both
    // linkingStatically and linkShared must me true, we must be in opt mode and cpu has to be k8.
    return archiveType == ArchiveType.START_END_LIB
        && (linkerInput.getArtifactCategory() == ArtifactCategory.STATIC_LIBRARY
            || linkerInput.getArtifactCategory() == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY)
        && linkerInput.containsObjectFiles();
  }

  /**
   * Replace always used archives with its members. This is used to build the linker cmd line.
   */
  public static Iterable<LinkerInput> mergeInputsCmdLine(NestedSet<LibraryToLink> inputs,
      boolean globalNeedWholeArchive, ArchiveType archiveType) {
    return new FilterMembersForLinkIterable(inputs, globalNeedWholeArchive, archiveType, false);
  }

  /**
   * Add in any object files which are implicitly named as inputs by the linker.
   */
  public static Iterable<LinkerInput> mergeInputsDependencies(NestedSet<LibraryToLink> inputs,
      boolean globalNeedWholeArchive, ArchiveType archiveType) {
    return new FilterMembersForLinkIterable(inputs, globalNeedWholeArchive, archiveType, true);
  }

  /**
   * On the fly implementation to filter the members.
   */
  private static final class FilterMembersForLinkIterable implements Iterable<LinkerInput> {
    private final boolean globalNeedWholeArchive;
    private final ArchiveType archiveType;
    private final boolean deps;

    private final Iterable<LibraryToLink> inputs;

    private FilterMembersForLinkIterable(Iterable<LibraryToLink> inputs,
        boolean globalNeedWholeArchive, ArchiveType archiveType, boolean deps) {
      this.globalNeedWholeArchive = globalNeedWholeArchive;
      this.archiveType = archiveType;
      this.deps = deps;
      this.inputs = CollectionUtils.makeImmutable(inputs);
    }

    @Override
    public Iterator<LinkerInput> iterator() {
      return new FilterMembersForLinkIterator(inputs.iterator(), globalNeedWholeArchive,
          archiveType, deps);
    }
  }

  /**
   * On the fly implementation to filter the members.
   */
  private static final class FilterMembersForLinkIterator extends AbstractIterator<LinkerInput> {
    private final boolean globalNeedWholeArchive;
    private final ArchiveType archiveType;
    private final boolean deps;

    private final Iterator<LibraryToLink> inputs;
    private Iterator<LinkerInput> delayList = ImmutableList.<LinkerInput>of().iterator();

    private FilterMembersForLinkIterator(Iterator<LibraryToLink> inputs,
        boolean globalNeedWholeArchive, ArchiveType archiveType, boolean deps) {
      this.globalNeedWholeArchive = globalNeedWholeArchive;
      this.archiveType = archiveType;
      this.deps = deps;
      this.inputs = inputs;
    }

    @Override
    protected LinkerInput computeNext() {
      if (delayList.hasNext()) {
        return delayList.next();
      }

      while (inputs.hasNext()) {
        LibraryToLink inputLibrary = inputs.next();
        Artifact input = inputLibrary.getArtifact();
        String name = input.getFilename();

        // True if the linker might use the members of this file, i.e., if the file is a thin or
        // start_end_lib archive (aka static library). Also check if the library contains object
        // files - otherwise getObjectFiles returns null, which would lead to an NPE in
        // simpleLinkerInputs.
        boolean needMembersForLink = archiveType != ArchiveType.FAT
            && (inputLibrary.getArtifactCategory() == ArtifactCategory.STATIC_LIBRARY
                || inputLibrary.getArtifactCategory() == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY)
            && inputLibrary.containsObjectFiles();

        // True if we will pass the members instead of the original archive.
        boolean passMembersToLinkCmd = needMembersForLink && (globalNeedWholeArchive
            || inputLibrary.getArtifactCategory() == ArtifactCategory.ALWAYSLINK_STATIC_LIBRARY);

        // If deps is false (when computing the inputs to be passed on the command line), then it's
        // an if-then-else, i.e., the passMembersToLinkCmd flag decides whether to pass the object
        // files or the archive itself. This flag in turn is based on whether the archives are fat
        // or not (thin archives or start_end_lib) - we never expand fat archives, but we do expand
        // non-fat archives if we need whole-archives for the entire link, or for the specific
        // library (i.e., if alwayslink=1).
        //
        // If deps is true (when computing the inputs to be passed to the action as inputs), then it
        // becomes more complicated. We always need to pass the members for thin and start_end_lib
        // archives (needMembersForLink). And we _also_ need to pass the archive file itself unless
        // it's a start_end_lib archive (unless it's an alwayslink library).

        // A note about ordering: the order in which the object files and the library are returned
        // does not currently matter - this code results in the library returned first, and the
        // object files returned after, but only if both are returned, which can only happen if
        // deps is true, in which case this code only computes the list of inputs for the link
        // action (so the order isn't critical).
        if (passMembersToLinkCmd || (deps && needMembersForLink)) {
          delayList = LinkerInputs
              .simpleLinkerInputs(inputLibrary.getObjectFiles(), ArtifactCategory.OBJECT_FILE)
              .iterator();
        }

        if (!(passMembersToLinkCmd || (deps && useStartEndLib(inputLibrary, archiveType)))) {
          return inputLibrary;
        }

        if (delayList.hasNext()) {
          return delayList.next();
        }
      }
      return endOfData();
    }
  }
}