1# Copyright 2021 The Pigweed Authors 2# 3# Licensed under the Apache License, Version 2.0 (the "License"); you may not 4# use this file except in compliance with the License. You may obtain a copy of 5# the License at 6# 7# https://www.apache.org/licenses/LICENSE-2.0 8# 9# Unless required by applicable law or agreed to in writing, software 10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12# License for the specific language governing permissions and limitations under 13# the License. 14 15import("//build_overrides/pigweed.gni") 16 17import("$dir_pw_build/python.gni") 18import("$dir_pw_build/python_action.gni") 19import("$dir_pw_build/zip.gni") 20 21# Builds a directory containing a collection of Python wheels. 22# 23# Given one or more pw_python_package targets, this target will build their 24# .wheel sub-targets along with the .wheel sub-targets of all dependencies, 25# direct and indirect, as understood by GN. The resulting .whl files will be 26# collected into a single directory called 'python_wheels'. 27# 28# Args: 29# packages: A list of pw_python_package targets whose wheels should be 30# included; their dependencies will be pulled in as wheels also. 31# directory: output directory for the wheels; defaults to 32# $target_out_dir/$target_name 33# deps: additional dependencies 34# 35template("pw_python_wheels") { 36 _wheel_paths_path = "${target_gen_dir}/${target_name}_wheel_paths.txt" 37 38 _deps = [] 39 if (defined(invoker.deps)) { 40 _deps = invoker.deps 41 } 42 43 if (defined(invoker.directory)) { 44 _directory = invoker.directory 45 } else { 46 _directory = "$target_out_dir/$target_name" 47 } 48 49 _packages = [] 50 foreach(_pkg, invoker.packages) { 51 _pkg_name = get_label_info(_pkg, "label_no_toolchain") 52 _pkg_toolchain = get_label_info(_pkg, "toolchain") 53 _packages += [ "${_pkg_name}.wheel(${_pkg_toolchain})" ] 54 } 55 56 # Build a list of relative paths containing all the wheels we depend on. 57 generated_file("${target_name}._wheel_paths") { 58 data_keys = [ "pw_python_package_wheels" ] 59 rebase = root_build_dir 60 deps = _packages 61 outputs = [ _wheel_paths_path ] 62 } 63 64 pw_python_action(target_name) { 65 forward_variables_from(invoker, [ "public_deps" ]) 66 deps = _deps + [ ":$target_name._wheel_paths" ] 67 module = "pw_build.collect_wheels" 68 69 args = [ 70 "--prefix", 71 rebase_path(root_build_dir, root_build_dir), 72 "--suffix", 73 rebase_path(_wheel_paths_path, root_build_dir), 74 "--out_dir", 75 rebase_path(_directory, root_build_dir), 76 ] 77 78 stamp = true 79 } 80} 81 82# Builds a .zip containing Python wheels and setup scripts. 83# 84# The resulting .zip archive will contain a directory with Python wheels for 85# all pw_python_package targets listed in 'packages', plus wheels for any 86# pw_python_package targets those packages depend on, directly or indirectly, 87# as understood by GN. 88# 89# In addition to Python wheels, the resulting .zip will also contain simple 90# setup scripts for Linux, MacOS, and Windows that take care of creating a 91# Python venv and installing all the included wheels into it, and a README.md 92# file with setup and usage instructions. 93# 94# Args: 95# packages: A list of pw_python_package targets whose wheels should be 96# included; their dependencies will be pulled in as wheels also. 97# inputs: An optional list of extra files to include in the generated .zip, 98# formatted the same was as the 'inputs' argument to pw_zip targets. 99# dirs: An optional list of directories to include in the generated .zip, 100# formatted the same way as the 'dirs' argument to pw_zip targets. 101template("pw_python_zip_with_setup") { 102 _outer_name = target_name 103 _zip_path = "${target_out_dir}/${target_name}.zip" 104 105 _inputs = [] 106 if (defined(invoker.inputs)) { 107 _inputs = invoker.inputs 108 } 109 _dirs = [] 110 if (defined(invoker.dirs)) { 111 _dirs = invoker.dirs 112 } 113 _public_deps = [] 114 if (defined(invoker.public_deps)) { 115 _public_deps = invoker.public_deps 116 } 117 118 pw_python_wheels("$target_name.wheels") { 119 packages = invoker.packages 120 forward_variables_from(invoker, [ "deps" ]) 121 } 122 123 pw_zip(target_name) { 124 forward_variables_from(invoker, [ "deps" ]) 125 inputs = _inputs + [ 126 "$dir_pw_build/python_dist/setup.bat > /${target_name}/", 127 "$dir_pw_build/python_dist/setup.sh > /${target_name}/", 128 ] 129 130 dirs = _dirs + [ "$target_out_dir/$target_name.wheels/ > /$target_name/python_wheels/" ] 131 132 output = _zip_path 133 134 # TODO(pwbug/634): Remove the plumbing-through of invoker's public_deps. 135 public_deps = _public_deps + [ ":${_outer_name}.wheels" ] 136 } 137} 138 139# Generates a directory of Python packages from source files suitable for 140# deployment outside of the project developer environment. 141# 142# The resulting directory contains only files mentioned in each package's 143# setup.cfg file. This is useful for bundling multiple Python packages up 144# into a single package for distribution to other locations like 145# http://pypi.org. 146# 147# Args: 148# packages: A list of pw_python_package targets to be installed into the build 149# directory. Their dependencies will be pulled in as wheels also. 150# 151# include_tests: If true, copy Python package tests to a `tests` subdir. 152# 153# extra_files: A list of extra files that should be included in the output. The 154# format of each item in this list follows this convention: 155# //some/nested/source_file > nested/destination_file 156template("pw_create_python_source_tree") { 157 _output_dir = "${target_out_dir}/${target_name}/" 158 _metadata_json_file_list = 159 "${target_gen_dir}/${target_name}_metadata_path_list.txt" 160 161 # If generating a setup.cfg file a common base file must be provided. 162 if (defined(invoker.generate_setup_cfg)) { 163 generate_setup_cfg = invoker.generate_setup_cfg 164 assert(defined(generate_setup_cfg.common_config_file), 165 "'common_config_file' is required in generate_setup_cfg") 166 } 167 168 _extra_file_inputs = [] 169 _extra_file_args = [] 170 171 # Convert extra_file strings to input, outputs and create_python_tree.py args. 172 if (defined(invoker.extra_files)) { 173 _delimiter = ">" 174 _extra_file_outputs = [] 175 foreach(input, invoker.extra_files) { 176 # Remove spaces before and after the delimiter 177 input = string_replace(input, " $_delimiter", _delimiter) 178 input = string_replace(input, "$_delimiter ", _delimiter) 179 180 input_list = [] 181 input_list = string_split(input, _delimiter) 182 183 # Save the input file 184 _extra_file_inputs += [ input_list[0] ] 185 186 # Save the output file 187 _this_output = _output_dir + "/" + input_list[1] 188 _extra_file_outputs += [ _this_output ] 189 190 # Compose an arg for passing to create_python_tree.py with properly 191 # rebased paths. 192 _extra_file_args += 193 [ string_join(" $_delimiter ", 194 [ 195 rebase_path(input_list[0], root_build_dir), 196 rebase_path(_this_output, root_build_dir), 197 ]) ] 198 } 199 } 200 201 _include_tests = defined(invoker.include_tests) && invoker.include_tests 202 203 # Build a list of relative paths containing all the python 204 # package_metadata.json files we depend on. 205 generated_file("${target_name}._metadata_path_list.txt") { 206 data_keys = [ "pw_python_package_metadata_json" ] 207 rebase = root_build_dir 208 deps = invoker.packages 209 outputs = [ _metadata_json_file_list ] 210 } 211 212 # Run the python action on the metadata_path_list.txt file 213 pw_python_action(target_name) { 214 deps = 215 invoker.packages + [ ":${invoker.target_name}._metadata_path_list.txt" ] 216 script = "$dir_pw_build/py/pw_build/create_python_tree.py" 217 inputs = _extra_file_inputs 218 219 args = [ 220 "--tree-destination-dir", 221 rebase_path(_output_dir, root_build_dir), 222 "--input-list-files", 223 rebase_path(_metadata_json_file_list, root_build_dir), 224 ] 225 226 # Add required setup.cfg args if we are generating a merged config. 227 if (defined(generate_setup_cfg)) { 228 if (defined(generate_setup_cfg.common_config_file)) { 229 args += [ 230 "--setupcfg-common-file", 231 rebase_path(generate_setup_cfg.common_config_file, root_build_dir), 232 ] 233 } 234 if (defined(generate_setup_cfg.append_git_sha_to_version)) { 235 args += [ "--setupcfg-version-append-git-sha" ] 236 } 237 if (defined(generate_setup_cfg.append_date_to_version)) { 238 args += [ "--setupcfg-version-append-date" ] 239 } 240 } 241 242 if (_extra_file_args == []) { 243 # No known output files - stamp instead. 244 stamp = true 245 } else { 246 args += [ "--extra-files" ] 247 args += _extra_file_args 248 249 # Include extra_files as outputs 250 outputs = _extra_file_outputs 251 } 252 253 if (_include_tests) { 254 args += [ "--include-tests" ] 255 } 256 } 257} 258