• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5# Creates a group() that lists Python sources as |data|.
6# Having such targets serves two purposes:
7# 1) Causes files to be included in runtime_deps, so that they are uploaded to
8#    swarming when running tests remotely.
9# 2) Causes "gn analyze" to know about all Python inputs so that tests will be
10#    re-run when relevant Python files change.
11#
12# All non-trivial Python scripts should use a "pydeps" file to track their
13# sources. To create a .pydep file for a target in //example:
14#
15#   build/print_python_deps.py \
16#       --root example \
17#       --output example/$target_name.pydeps \
18#       path/to/your/script.py
19#
20# Keep the .pydep file up-to-date by adding to //PRESUBMIT.py under one of:
21#     _GENERIC_PYDEPS_FILES
22#
23# Variables
24#   pydeps_file: Path to .pydeps file to read sources from (optional).
25#   data: Additional files to include in data. E.g. non-.py files needed by the
26#         library, or .py files that are conditionally / lazily imported.
27#
28# Example
29#   python_library("my_library_py") {
30#      pydeps_file = "my_library.pydeps"
31#      data = [ "foo.dat" ]
32#   }
33template("python_library") {
34  group(target_name) {
35    forward_variables_from(invoker,
36                           [
37                             "data_deps",
38                             "deps",
39                             "testonly",
40                             "visibility",
41                           ])
42
43    if (defined(invoker.pydeps_file)) {
44      _py_files = read_file(invoker.pydeps_file, "list lines")
45
46      # Filter out comments.
47      set_sources_assignment_filter([ "#*" ])
48      sources = _py_files
49
50      # Even though the .pydep file is not used at runtime, it must be added
51      # so that "gn analyze" will mark the target as changed when .py files
52      # are removed but none are added or modified.
53      data = sources + [ invoker.pydeps_file ]
54    } else {
55      data = []
56    }
57    if (defined(invoker.data)) {
58      data += invoker.data
59    }
60  }
61}
62
63# A template used for actions that execute a Python script, which has an
64# associated .pydeps file. In other words:
65#
66# - This is very similar to just an action(), except that |script| must point
67#   to a Python script (e.g. "//build/.../foo.py") that has a corresponding
68#   .pydeps file in the source tree (e.g. "//build/.../foo.pydeps").
69#
70# - The .pydeps file contains a list of python dependencies (imports really)
71#   and is generated _manually_ by using a command like:
72#
73#     build/print_python_deps.py --inplace build/gyp/foo.py
74#
75template("action_with_pydeps") {
76  # Read the .pydeps file now. Note that this is done every time this
77  # template is called, but benchmarking doesn't show any impact on overall
78  # 'gn gen' speed anyway.
79  _pydeps_file = invoker.script + "deps"
80  _pydeps_raw = read_file(_pydeps_file, "list lines")
81
82  # Filter out comments.
83  # This is a bit convoluted to preserve the value of sources if defined.
84  _old_sources = []
85  if (defined(sources)) {
86    _old_sources = sources
87  }
88  set_sources_assignment_filter([ "#*" ])
89  sources = _pydeps_raw
90  _pydeps = sources
91  set_sources_assignment_filter([])
92  sources = _old_sources
93
94  action(target_name) {
95    # Forward all variables. Ensure that testonly and visibility are forwarded
96    # explicitly, since this performs recursive scope lookups, which is
97    # required to ensure their definition from scopes above the caller are
98    # properly handled. All other variables are forwarded with "*", which
99    # doesn't perform recursive lookups at all. See https://crbug.com/862232
100    forward_variables_from(invoker,
101                           [
102                             "testonly",
103                             "visibility",
104                           ])
105    forward_variables_from(invoker,
106                           "*",
107                           [
108                             "testonly",
109                             "visibility",
110                           ])
111
112    if (!defined(inputs)) {
113      inputs = []
114    }
115
116    # Dependencies are listed relative to the script directory, but inputs
117    # expects paths that are relative to the current BUILD.gn
118    _script_dir = get_path_info(script, "dir")
119    inputs += rebase_path(_pydeps, ".", _script_dir)
120  }
121}
122
123template("action_foreach_with_pydeps") {
124  _pydeps_file = invoker.script + "deps"
125  _pydeps_raw = read_file(_pydeps_file, "list lines")
126
127  # Filter out comments.
128  # This is a bit convoluted to preserve the value of sources if defined.
129  _old_sources = []
130  if (defined(sources)) {
131    _old_sources = sources
132  }
133  set_sources_assignment_filter([ "#*" ])
134  sources = _pydeps_raw
135  _pydeps = sources
136  set_sources_assignment_filter([])
137  sources = _old_sources
138
139  action_foreach(target_name) {
140    # Forward all variables. Ensure that testonly and visibility are forwarded
141    # explicitly, since this performs recursive scope lookups, which is
142    # required to ensure their definition from scopes above the caller are
143    # properly handled. All other variables are forwarded with "*", which
144    # doesn't perform recursive lookups at all. See https://crbug.com/862232
145    forward_variables_from(invoker,
146                           [
147                             "testonly",
148                             "visibility",
149                           ])
150    forward_variables_from(invoker,
151                           "*",
152                           [
153                             "testonly",
154                             "visibility",
155                           ])
156
157    if (!defined(inputs)) {
158      inputs = []
159    }
160
161    # Dependencies are listed relative to the script directory, but inputs
162    # expects paths that are relative to the current BUILD.gn
163    _script_dir = get_path_info(script, "dir")
164    inputs += rebase_path(_pydeps, ".", _script_dir)
165  }
166}
167