<reponame>allansrc/fuchsia
#!/usr/bin/env python3.8
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Determines the files that needed to be rebuilt by fx ninja.

The primary use of this script is to find which build targets were changed
by a particular CL or patch for use by the clang static analyzer.

The input will most likely be the compile_commands.json generated by either
an `fx set` or `fx gn gen --export-compile-commands=default <OUT_DIR>`.

The output is a json file containing the translation units (TUs) taken
from the input compile_commands.json of which the build targets were
modified by a fx ninja call.

The expectated workflow is:
  1. Check out the base fuchsia commit against which to apply the patch.
  1. Run `fx build`.
  1. Apply the patch.
  1. Run this script.
  1. Run analyze-build against the output of this script.
"""
import argparse
import json
import os
import subprocess
import sys
import tempfile
import time

import helper as color


def main(input_args):
  parser = argparse.ArgumentParser(
      description=
      'Runs `fx ninja build` on a set of targets for compile translation units '
      'and determines which files were modified since that change.')
  parser.add_argument(
      '-i',
      '--input',
      required=True,
      help='The path of a compile_commands.json generated '
      'by either `fx set` or `fx gn gen '
      '--export-compile-commands=default <OUT_DIR>`')
  parser.add_argument(
      '-o',
      '--output',
      required=True,
      help=
      'The path to write the output file of an array of paths for modified files in JSON format.'
  )
  parser.add_argument(
      '-n',
      '--ninja',
      required=True,
      help=
      'Path to the prebuilt ninja compiler.'
  )
  args = parser.parse_args(input_args)

  with open(args.input) as compile_commands:
    res = ninja_build_tu(json.load(compile_commands), args.ninja)

    if not res:
      print(color.white('Did not find any changed files.'))

    with open(args.output, 'w') as out:
      json.dump(res, out, indent=2)

    print(color.white('Wrote to output file'), color.yellow(args.output))
  return 0


def ninja_build_tu(compdb, ninja_path):
  """Find build targets that were modified since last build.

  Attemps to build all build targets specified, returning the subset
  of those that require a rebuild since the last build. Leverages `fx
  ninja` in order to determine which build targets needed rebuilding.

  Args:
      compdb: A json-style list containing dictionary-like entries
              for each translation unit.
      ninja_path: Path to the ninja executable.

  Returns:
      A subset of the input compdb, only keeping the translation units
      that correspond to build targets that had been modified by the
      `ninja` call.
  """
  files = {}
  tus = {}
  # Find all TU targets
  for tu in compdb:
    directory = tu['directory']
    command = tu['command']
    command_entries = command.split()

    # Gather all targets in this TU
    for i in range(0, len(command_entries) - 1):
      if command_entries[i] != '-o':
        continue

      target = command_entries[i + 1]

      if directory not in files:
        files[directory] = []

      files[directory].append(target)

      # Store TU for reverse lookup later
      target_path = os.path.join(directory, target)
      if target_path not in tus:
        tus[target_path] = []
      tus[target_path].append(tu)

  modified_files = [process_tu_dir_xargs(directory, files[directory],
                                         ninja_path) for directory in files]

  # Find all TUs that match modified files
  out_tus = []
  for files in modified_files:
    for target in files:
      if target in tus:
        out_tus.extend(tus[target])
      else:
        sys.exit('Expected %s to exist in reverse lookup dict.'%(target))
  return out_tus


def process_tu_dir_xargs(directory, build_targets, ninja_path):
  """Find build targets that were rebuilt.

  Takes an input directory (corresponding to the -C option in fx ninja) and
  a list of build targets. Based on system modified time, returns the list
  of build targets that have a new modified time.

  Args:
      directory: The string path name correpsonding to the build_targets.
      build_targets: A list of build targets to attempt to build.
      ninja_path: Path to the ninja executable.

  Returns:
      The subset of build_targets which were modified by `ninja`.
  """
  # Write targets to a temp file
  with tempfile.NamedTemporaryFile(mode='w') as fp:
    for f in build_targets:
      fp.write('%s\n' % (f))
    fp.flush()
    filename = os.path.realpath(fp.name)

    # The argument list may exceed the input buffer, so we use xargs to
    # call fx ninja multiple times, each with a subset of the arguments
    # commands = ['xargs', '-a', filename, 'fx', 'ninja', '-C', directory]
    commands = ['xargs', '-a', filename, ninja_path, '-C', directory]

    # Record the time before running fx ninja
    before = time.time()
    print(color.white('Running command'), color.yellow(' '.join(commands)))

    try:
      subprocess.run(commands, capture_output=True, check=True)
    except subprocess.CalledProcessError as err:
      print(color.red('Failed with exit code'), color.white(err.returncode))
      print(color.red(err.stderr))
      sys.exit('Error in fx ninja.')

  # Find all files that have a newer modified time than `before`
  modified_targets = []
  for file in build_targets:
    file_path = os.path.join(directory, file)
    if not os.path.isfile(file_path):
      continue
    mtime = os.path.getmtime(file_path)
    if mtime > before:
      modified_targets.append(file_path)

  return modified_targets


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))

#!/usr/bin/env python3.8
# Copyright 2021 The Fuchsia Authors. All rights reserved.
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
"""Determines the files that needed to be rebuilt by fx ninja.

The primary use of this script is to find which build targets were changed
by a particular CL or patch for use by the clang static analyzer.

The input will most likely be the compile_commands.json generated by either
an `fx set` or `fx gn gen --export-compile-commands=default <OUT_DIR>`.

The output is a json file containing the translation units (TUs) taken
from the input compile_commands.json of which the build targets were
modified by a fx ninja call.

The expectated workflow is:
  1. Check out the base fuchsia commit against which to apply the patch.
  1. Run `fx build`.
  1. Apply the patch.
  1. Run this script.
  1. Run analyze-build against the output of this script.
"""
import argparse
import json
import os
import subprocess
import sys
import tempfile
import time

import helper as color


def main(input_args):
  parser = argparse.ArgumentParser(
      description=
      'Runs `fx ninja build` on a set of targets for compile translation units '
      'and determines which files were modified since that change.')
  parser.add_argument(
      '-i',
      '--input',
      required=True,
      help='The path of a compile_commands.json generated '
      'by either `fx set` or `fx gn gen '
      '--export-compile-commands=default <OUT_DIR>`')
  parser.add_argument(
      '-o',
      '--output',
      required=True,
      help=
      'The path to write the output file of an array of paths for modified files in JSON format.'
  )
  parser.add_argument(
      '-n',
      '--ninja',
      required=True,
      help=
      'Path to the prebuilt ninja compiler.'
  )
  args = parser.parse_args(input_args)

  with open(args.input) as compile_commands:
    res = ninja_build_tu(json.load(compile_commands), args.ninja)

    if not res:
      print(color.white('Did not find any changed files.'))

    with open(args.output, 'w') as out:
      json.dump(res, out, indent=2)

    print(color.white('Wrote to output file'), color.yellow(args.output))
  return 0


def ninja_build_tu(compdb, ninja_path):
  """Find build targets that were modified since last build.

  Attemps to build all build targets specified, returning the subset
  of those that require a rebuild since the last build. Leverages `fx
  ninja` in order to determine which build targets needed rebuilding.

  Args:
      compdb: A json-style list containing dictionary-like entries
              for each translation unit.
      ninja_path: Path to the ninja executable.

  Returns:
      A subset of the input compdb, only keeping the translation units
      that correspond to build targets that had been modified by the
      `ninja` call.
  """
  files = {}
  tus = {}
  # Find all TU targets
  for tu in compdb:
    directory = tu['directory']
    command = tu['command']
    command_entries = command.split()

    # Gather all targets in this TU
    for i in range(0, len(command_entries) - 1):
      if command_entries[i] != '-o':
        continue

      target = command_entries[i + 1]

      if directory not in files:
        files[directory] = []

      files[directory].append(target)

      # Store TU for reverse lookup later
      target_path = os.path.join(directory, target)
      if target_path not in tus:
        tus[target_path] = []
      tus[target_path].append(tu)

  modified_files = [process_tu_dir_xargs(directory, files[directory],
                                         ninja_path) for directory in files]

  # Find all TUs that match modified files
  out_tus = []
  for files in modified_files:
    for target in files:
      if target in tus:
        out_tus.extend(tus[target])
      else:
        sys.exit('Expected %s to exist in reverse lookup dict.'%(target))
  return out_tus


def process_tu_dir_xargs(directory, build_targets, ninja_path):
  """Find build targets that were rebuilt.

  Takes an input directory (corresponding to the -C option in fx ninja) and
  a list of build targets. Based on system modified time, returns the list
  of build targets that have a new modified time.

  Args:
      directory: The string path name correpsonding to the build_targets.
      build_targets: A list of build targets to attempt to build.
      ninja_path: Path to the ninja executable.

  Returns:
      The subset of build_targets which were modified by `ninja`.
  """
  # Write targets to a temp file
  with tempfile.NamedTemporaryFile(mode='w') as fp:
    for f in build_targets:
      fp.write('%s\n' % (f))
    fp.flush()
    filename = os.path.realpath(fp.name)

    # The argument list may exceed the input buffer, so we use xargs to
    # call fx ninja multiple times, each with a subset of the arguments
    # commands = ['xargs', '-a', filename, 'fx', 'ninja', '-C', directory]
    commands = ['xargs', '-a', filename, ninja_path, '-C', directory]

    # Record the time before running fx ninja
    before = time.time()
    print(color.white('Running command'), color.yellow(' '.join(commands)))

    try:
      subprocess.run(commands, capture_output=True, check=True)
    except subprocess.CalledProcessError as err:
      print(color.red('Failed with exit code'), color.white(err.returncode))
      print(color.red(err.stderr))
      sys.exit('Error in fx ninja.')

  # Find all files that have a newer modified time than `before`
  modified_targets = []
  for file in build_targets:
    file_path = os.path.join(directory, file)
    if not os.path.isfile(file_path):
      continue
    mtime = os.path.getmtime(file_path)
    if mtime > before:
      modified_targets.append(file_path)

  return modified_targets


if __name__ == '__main__':
  sys.exit(main(sys.argv[1:]))
#!/usr/bin/env python3.8 # Copyright 2021 The Fuchsia Authors. All rights reserved. # Use of this source code is governed by a BSD-style license that can be # found in the LICENSE file. Determines the files that needed to be rebuilt by fx ninja.  The primary use of this script is to find which build targets were changed by a particular CL or patch for use by the clang static analyzer.  The input will most likely be the compile_commands.json generated by either an `fx set` or `fx gn gen --export-compile-commands=default <OUT_DIR>`.  The output is a json file containing the translation units (TUs) taken from the input compile_commands.json of which the build targets were modified by a fx ninja call.  The expectated workflow is:   1. Check out the base fuchsia commit against which to apply the patch.   1. Run `fx build`.   1. Apply the patch.   1. Run this script.   1. Run analyze-build against the output of this script. Find build targets that were modified since last build.    Attemps to build all build targets specified, returning the subset   of those that require a rebuild since the last build. Leverages `fx   ninja` in order to determine which build targets needed rebuilding.    Args:       compdb: A json-style list containing dictionary-like entries               for each translation unit.       ninja_path: Path to the ninja executable.    Returns:       A subset of the input compdb, only keeping the translation units       that correspond to build targets that had been modified by the       `ninja` call. # Find all TU targets # Gather all targets in this TU # Store TU for reverse lookup later # Find all TUs that match modified files Find build targets that were rebuilt.    Takes an input directory (corresponding to the -C option in fx ninja) and   a list of build targets. Based on system modified time, returns the list   of build targets that have a new modified time.    Args:       directory: The string path name correpsonding to the build_targets.       build_targets: A list of build targets to attempt to build.       ninja_path: Path to the ninja executable.    Returns:       The subset of build_targets which were modified by `ninja`. # Write targets to a temp file # The argument list may exceed the input buffer, so we use xargs to # call fx ninja multiple times, each with a subset of the arguments # commands = ['xargs', '-a', filename, 'fx', 'ninja', '-C', directory] # Record the time before running fx ninja # Find all files that have a newer modified time than `before`