text: #!/usr/bin/env python
  # Copyright 2018 The Chromium Authors. All rights reserved.
  # Use of this source code is governed by a BSD-style license that can be
  # found in the LICENSE file.
  
  """Logic for reading .a files."""
  
  from __future__ import print_function
  
  import argparse
  import logging
  import os
  
  
  def IsThinArchive(path):
    """Returns whether the given .a is a thin archive."""
    with open(path, 'rb') as f:
      return f.read(8) == '!<thin>\n'
  
  
  def CreateThinObjectPath(archive_path, subpath):
    """Given the .a path and .o subpath, returns the .o path."""
    # .o subpaths in thin archives are relative to the directory of the .a.
    parent_path = os.path.dirname(archive_path)
    return os.path.normpath(os.path.join(parent_path, subpath))
  
  
  def IterArchiveChunks(path):
    """For each .o embedded in the given .a file, yields (foo.o, foo_contents)."""
    # File format reference:
    # https://github.com/pathscale/binutils/blob/master/gold/archive.cc
    with open(path, 'rb') as f:
      header = f.read(8)
      is_thin = header == '!<thin>\n'
      if not is_thin and header != '!<arch>\n':
        raise Exception('Invalid .a: ' + path)
  
      def read_payload(size):
        ret = f.read(size)
        # Entries are 2-byte aligned.
        if size & 1 != 0:
          f.read(1)
        return ret
  
      while True:
        entry = f.read(60)
        if not entry:
          return
        entry_name = entry[:16].rstrip()
        entry_size = int(entry[48:58].rstrip())
  
        if entry_name in ('', '/', '//', '/SYM64/'):
          payload = read_payload(entry_size)
          # Metadata sections we don't care about.
          if entry_name == '//':
            name_list = payload
          continue
  
        if entry_name[0] == '/':
          # Name is specified as location in name table.
          # E.g.: /123
          name_offset = int(entry_name[1:])
          # String table enties are delimited by \n (e.g. "browser.o/\n").
          end_idx = name_list.index('\n', name_offset)
          entry_name = name_list[name_offset:end_idx]
        else:
          # Name specified inline with spaces for padding (e.g. "browser.o/    ").
          entry_name = entry_name.rstrip()
  
        # Although entry_size exists for thin archives, no data exists in the .a.
        entry_data = None if is_thin else read_payload(entry_size)
        yield entry_name.rstrip('/'), entry_data
  
  
  def ExpandThinArchives(paths, output_directory):
    """Expands all thin archives found in |paths| into .o paths.
  
    Args:
      paths: List of paths relative to |output_directory|.
      output_directory: Output directory.
  
    Returns:
      * A new list of paths with all thin archives replaced by .o paths.
      * A set of all .a paths that were thin archives.
    """
    expanded_paths = []
    thin_paths = set()
    num_archives = 0
    for path in paths:
      if not path.endswith('.a'):
        expanded_paths.append(path)
        continue
      num_archives += 1
      abs_path = os.path.join(output_directory, path)
      if not IsThinArchive(abs_path):
        expanded_paths.append(path)
        continue
  
      thin_paths.add(path)
      # Thin archive.
      for subpath, _ in IterArchiveChunks(abs_path):
        expanded_paths.append(CreateThinObjectPath(path, subpath))
  
    logging.info('%d of %d .a files were thin archives',
                 len(thin_paths), num_archives)
    return expanded_paths, thin_paths
  
  
  def CreateThinObjectPath(archive_path, subpath):
    # |subpath| is path complete under Gold, and incomplete under LLD. Check its
    # prefix to test completeness, and if not, use |archive_path| to supply the
    # required prefix.
    if subpath.startswith('obj/'):
      return subpath
    parent_path = os.path.dirname(archive_path)
    return os.path.normpath(os.path.join(parent_path, subpath))
  
  
  def main():
    parser = argparse.ArgumentParser()
    parser.add_argument('ar_path')
    parser.add_argument('--expand-thin', action='store_true')
    parser.add_argument('--output-directory', default='.')
    args = parser.parse_args()
  
    if args.expand_thin:
      expanded = ExpandThinArchives([args.ar_path], args.output_directory)[0]
      print('\n'.join(expanded))
    else:
      for name, payload in IterArchiveChunks(args.ar_path):
        print('{}: size={}'.format(name, len(payload) if payload else '<thin>'))
  
  
  if __name__ == '__main__':
    main()
  