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