• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2018 The Chromium Authors
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#     _ANDROID_SPECIFIC_PYDEPS_FILES, _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      # Read and filter out comments.
45      _pydeps_lines = read_file(invoker.pydeps_file, "list lines")
46      _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])
47
48      # Dependencies are listed relative to the pydeps file directory, but data
49      # parameter expects paths that are relative to the current BUILD.gn
50      _script_dir = get_path_info(invoker.pydeps_file, "dir")
51      _rebased_pydeps_entries = rebase_path(_pydeps_entries, ".", _script_dir)
52
53      # Even though the .pydep file is not used at runtime, it must be added
54      # so that "gn analyze" will mark the target as changed when .py files
55      # are removed but none are added or modified.
56      data = _rebased_pydeps_entries + [ invoker.pydeps_file ]
57    } else {
58      data = []
59    }
60    if (defined(invoker.data)) {
61      data += invoker.data
62    }
63  }
64}
65
66# A template used for actions that execute a Python script, which has an
67# associated .pydeps file. In other words:
68#
69# - This is very similar to just an action(), except that |script| must point
70#   to a Python script (e.g. "//build/.../foo.py") that has a corresponding
71#   .pydeps file in the source tree (e.g. "//build/.../foo.pydeps").
72#
73# - The .pydeps file contains a list of python dependencies (imports really)
74#   and is generated _manually_ by using a command like:
75#
76#     build/print_python_deps.py --inplace build/android/gyp/foo.py
77#
78# Example
79#   action_with_pydeps("create_foo") {
80#     script = "myscript.py"
81#     args = [...]
82#   }
83template("action_with_pydeps") {
84  action(target_name) {
85    # Ensure that testonly and visibility are forwarded
86    # explicitly, since this performs recursive scope lookups, which is
87    # required to ensure their definition from scopes above the caller are
88    # properly handled. All other variables are forwarded with "*", which
89    # doesn't perform recursive lookups at all. See https://crbug.com/862232
90    forward_variables_from(invoker,
91                           [
92                             "testonly",
93                             "visibility",
94                           ])
95    forward_variables_from(invoker,
96                           "*",
97                           [
98                             "testonly",
99                             "visibility",
100                           ])
101
102    # Read and filter out comments.
103    # Happens every time the template is instantiated, but benchmarking shows no
104    # perceivable impact on overall 'gn gen' speed.
105    _pydeps_file = invoker.script + "deps"
106
107    _pydeps_lines =
108        read_file(_pydeps_file, "list lines")  # https://crbug.com/1102058
109    _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])
110
111    if (!defined(inputs)) {
112      inputs = []
113    }
114
115    # Dependencies are listed relative to the script directory, but inputs
116    # expects paths that are relative to the current BUILD.gn
117    _script_dir = get_path_info(_pydeps_file, "dir")
118    inputs += rebase_path(_pydeps_entries, ".", _script_dir)
119  }
120}
121
122template("action_foreach_with_pydeps") {
123  action_foreach(target_name) {
124    # Ensure that testonly and visibility are forwarded
125    # explicitly, since this performs recursive scope lookups, which is
126    # required to ensure their definition from scopes above the caller are
127    # properly handled. All other variables are forwarded with "*", which
128    # doesn't perform recursive lookups at all. See https://crbug.com/862232
129    forward_variables_from(invoker,
130                           [
131                             "testonly",
132                             "visibility",
133                           ])
134    forward_variables_from(invoker,
135                           "*",
136                           [
137                             "testonly",
138                             "visibility",
139                           ])
140
141    # Read and filter out comments.
142    # Happens every time the template is instantiated, but benchmarking shows no
143    # perceivable impact on overall 'gn gen' speed.
144    if (defined(invoker.deps_file)) {
145      _pydeps_file = invoker.deps_file
146    } else {
147      _pydeps_file = invoker.script + "deps"
148    }
149    _pydeps_lines = read_file(_pydeps_file, "list lines")
150    _pydeps_entries = filter_exclude(_pydeps_lines, [ "#*" ])
151
152    if (!defined(inputs)) {
153      inputs = []
154    }
155
156    # Dependencies are listed relative to the script directory, but inputs
157    # expects paths that are relative to the current BUILD.gn
158    _script_dir = get_path_info(script, "dir")
159    inputs += rebase_path(_pydeps_entries, ".", _script_dir)
160  }
161}
162