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 sources = _py_files 48 sources = filter_exclude(sources, [ "#*" ]) 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 sources = filter_exclude(_pydeps_raw, [ "#*" ]) 89 _pydeps = sources 90 sources = _old_sources 91 92 action(target_name) { 93 # Forward all variables. Ensure that testonly and visibility are forwarded 94 # explicitly, since this performs recursive scope lookups, which is 95 # required to ensure their definition from scopes above the caller are 96 # properly handled. All other variables are forwarded with "*", which 97 # doesn't perform recursive lookups at all. See https://crbug.com/862232 98 forward_variables_from(invoker, 99 [ 100 "testonly", 101 "visibility", 102 ]) 103 forward_variables_from(invoker, 104 "*", 105 [ 106 "testonly", 107 "visibility", 108 ]) 109 110 if (!defined(inputs)) { 111 inputs = [] 112 } 113 114 # Dependencies are listed relative to the script directory, but inputs 115 # expects paths that are relative to the current BUILD.gn 116 _script_dir = get_path_info(script, "dir") 117 inputs += rebase_path(_pydeps, ".", _script_dir) 118 } 119} 120 121template("action_foreach_with_pydeps") { 122 _pydeps_file = invoker.script + "deps" 123 _pydeps_raw = read_file(_pydeps_file, "list lines") 124 125 # Filter out comments. 126 # This is a bit convoluted to preserve the value of sources if defined. 127 _old_sources = [] 128 if (defined(sources)) { 129 _old_sources = sources 130 } 131 sources = filter_exclude(_pydeps_raw, [ "#*" ]) 132 _pydeps = sources 133 sources = _old_sources 134 135 action_foreach(target_name) { 136 # Forward all variables. Ensure that testonly and visibility are forwarded 137 # explicitly, since this performs recursive scope lookups, which is 138 # required to ensure their definition from scopes above the caller are 139 # properly handled. All other variables are forwarded with "*", which 140 # doesn't perform recursive lookups at all. See https://crbug.com/862232 141 forward_variables_from(invoker, 142 [ 143 "testonly", 144 "visibility", 145 ]) 146 forward_variables_from(invoker, 147 "*", 148 [ 149 "testonly", 150 "visibility", 151 ]) 152 153 if (!defined(inputs)) { 154 inputs = [] 155 } 156 157 # Dependencies are listed relative to the script directory, but inputs 158 # expects paths that are relative to the current BUILD.gn 159 _script_dir = get_path_info(script, "dir") 160 inputs += rebase_path(_pydeps, ".", _script_dir) 161 } 162} 163