chromium/build/config/python.gni

# Copyright 2018 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

# Creates a group() that lists Python sources as |data|.
# Having such targets serves two purposes:
# 1) Causes files to be included in runtime_deps, so that they are uploaded to
#    swarming when running tests remotely.
# 2) Causes "gn analyze" to know about all Python inputs so that tests will be
#    re-run when relevant Python files change.
#
# All non-trivial Python scripts should use a "pydeps" file to track their
# sources. To create a .pydep file for a target in //example:
#
#   build/print_python_deps.py \
#       --root example \
#       --output example/$target_name.pydeps \
#       path/to/your/script.py
#
# Keep the .pydep file up-to-date by adding to //PRESUBMIT.py under one of:
#     _ANDROID_SPECIFIC_PYDEPS_FILES, _GENERIC_PYDEPS_FILES
#
# Variables
#   pydeps_file: Path to .pydeps file to read sources from (optional).
#   data: Additional files to include in data. E.g. non-.py files needed by the
#         library, or .py files that are conditionally / lazily imported.
#
# Example
#   python_library("my_library_py") {
#      pydeps_file = "my_library.pydeps"
#      data = [ "foo.dat" ]
#   }
template("python_library") {
  group(target_name) {
    forward_variables_from(invoker,
                           [
                             "data_deps",
                             "deps",
                             "testonly",
                             "visibility",
                           ])

    if (defined(invoker.pydeps_file)) {
      # Read and filter out comments.
      _pydeps_lines = read_file(invoker.pydeps_file, "list lines")
      _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])

      # Dependencies are listed relative to the pydeps file directory, but data
      # parameter expects paths that are relative to the current BUILD.gn
      _script_dir = get_path_info(invoker.pydeps_file, "dir")
      _rebased_pydeps_entries = rebase_path(_pydeps_entries, ".", _script_dir)

      # Even though the .pydep file is not used at runtime, it must be added
      # so that "gn analyze" will mark the target as changed when .py files
      # are removed but none are added or modified.
      data = _rebased_pydeps_entries + [ invoker.pydeps_file ]
    } else {
      data = []
    }
    if (defined(invoker.data)) {
      data += invoker.data
    }
  }
}

# A template used for actions that execute a Python script, which has an
# associated .pydeps file. In other words:
#
# - This is very similar to just an action(), except that |script| must point
#   to a Python script (e.g. "//build/.../foo.py") that has a corresponding
#   .pydeps file in the source tree (e.g. "//build/.../foo.pydeps").
#
# - The .pydeps file contains a list of python dependencies (imports really)
#   and is generated _manually_ by using a command like:
#
#     build/print_python_deps.py --inplace build/android/gyp/foo.py
#
# Example
#   action_with_pydeps("create_foo") {
#     script = "myscript.py"
#     args = [...]
#   }
template("action_with_pydeps") {
  action(target_name) {
    # Ensure that testonly and visibility are forwarded
    # explicitly, since this performs recursive scope lookups, which is
    # required to ensure their definition from scopes above the caller are
    # properly handled. All other variables are forwarded with "*", which
    # doesn't perform recursive lookups at all. See https://crbug.com/862232
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])

    # Read and filter out comments.
    # Happens every time the template is instantiated, but benchmarking shows no
    # perceivable impact on overall 'gn gen' speed.
    _pydeps_file = invoker.script + "deps"

    _pydeps_lines =
        read_file(_pydeps_file, "list lines")  # https://crbug.com/1102058
    _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])

    if (!defined(inputs)) {
      inputs = []
    }

    # Dependencies are listed relative to the script directory, but inputs
    # expects paths that are relative to the current BUILD.gn
    _script_dir = get_path_info(_pydeps_file, "dir")
    inputs += rebase_path(_pydeps_entries, ".", _script_dir)
  }
}

template("action_foreach_with_pydeps") {
  action_foreach(target_name) {
    # Ensure that testonly and visibility are forwarded
    # explicitly, since this performs recursive scope lookups, which is
    # required to ensure their definition from scopes above the caller are
    # properly handled. All other variables are forwarded with "*", which
    # doesn't perform recursive lookups at all. See https://crbug.com/862232
    forward_variables_from(invoker,
                           [
                             "testonly",
                             "visibility",
                           ])
    forward_variables_from(invoker,
                           "*",
                           [
                             "testonly",
                             "visibility",
                           ])

    # Read and filter out comments.
    # Happens every time the template is instantiated, but benchmarking shows no
    # perceivable impact on overall 'gn gen' speed.
    if (defined(invoker.deps_file)) {
      _pydeps_file = invoker.deps_file
    } else {
      _pydeps_file = invoker.script + "deps"
    }
    _pydeps_lines = read_file(_pydeps_file, "list lines")
    _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])

    if (!defined(inputs)) {
      inputs = []
    }

    # Dependencies are listed relative to the script directory, but inputs
    # expects paths that are relative to the current BUILD.gn
    _script_dir = get_path_info(script, "dir")
    inputs += rebase_path(_pydeps_entries, ".", _script_dir)
  }
}