# Copyright 2021 The Pigweed Authors # # Licensed under the Apache License, Version 2.0 (the "License"); you may not # use this file except in compliance with the License. You may obtain a copy of # the License at # # https://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations under # the License. import("//build_overrides/pigweed.gni") import("$dir_pw_build/python.gni") import("$dir_pw_build/python_action.gni") import("$dir_pw_build/zip.gni") # Builds a directory containing a collection of Python wheels. # # Given one or more pw_python_package targets, this target will build their # .wheel sub-targets along with the .wheel sub-targets of all dependencies, # direct and indirect, as understood by GN. The resulting .whl files will be # collected into a single directory called 'python_wheels'. # # Args: # packages: A list of pw_python_package targets whose wheels should be # included; their dependencies will be pulled in as wheels also. # directory: output directory for the wheels; defaults to # $target_out_dir/$target_name # deps: additional dependencies # template("pw_python_wheels") { _wheel_paths_path = "${target_gen_dir}/${target_name}_wheel_paths.txt" _deps = [] if (defined(invoker.deps)) { _deps = invoker.deps } if (defined(invoker.directory)) { _directory = invoker.directory } else { _directory = "$target_out_dir/$target_name" } _packages = [] foreach(_pkg, invoker.packages) { _pkg_name = get_label_info(_pkg, "label_no_toolchain") _pkg_toolchain = get_label_info(_pkg, "toolchain") _packages += [ "${_pkg_name}.wheel(${_pkg_toolchain})" ] } # Build a list of relative paths containing all the wheels we depend on. generated_file("${target_name}._wheel_paths") { data_keys = [ "pw_python_package_wheels" ] rebase = root_build_dir deps = _packages outputs = [ _wheel_paths_path ] } pw_python_action(target_name) { forward_variables_from(invoker, [ "public_deps" ]) deps = _deps + [ ":$target_name._wheel_paths" ] module = "pw_build.collect_wheels" args = [ "--prefix", rebase_path(root_build_dir, root_build_dir), "--suffix", rebase_path(_wheel_paths_path, root_build_dir), "--out_dir", rebase_path(_directory, root_build_dir), ] stamp = true } } # Builds a .zip containing Python wheels and setup scripts. # # The resulting .zip archive will contain a directory with Python wheels for # all pw_python_package targets listed in 'packages', plus wheels for any # pw_python_package targets those packages depend on, directly or indirectly, # as understood by GN. # # In addition to Python wheels, the resulting .zip will also contain simple # setup scripts for Linux, MacOS, and Windows that take care of creating a # Python venv and installing all the included wheels into it, and a README.md # file with setup and usage instructions. # # Args: # packages: A list of pw_python_package targets whose wheels should be # included; their dependencies will be pulled in as wheels also. # inputs: An optional list of extra files to include in the generated .zip, # formatted the same was as the 'inputs' argument to pw_zip targets. # dirs: An optional list of directories to include in the generated .zip, # formatted the same way as the 'dirs' argument to pw_zip targets. template("pw_python_zip_with_setup") { _outer_name = target_name _zip_path = "${target_out_dir}/${target_name}.zip" _inputs = [] if (defined(invoker.inputs)) { _inputs = invoker.inputs } _dirs = [] if (defined(invoker.dirs)) { _dirs = invoker.dirs } _public_deps = [] if (defined(invoker.public_deps)) { _public_deps = invoker.public_deps } pw_python_wheels("$target_name.wheels") { packages = invoker.packages forward_variables_from(invoker, [ "deps" ]) } pw_zip(target_name) { forward_variables_from(invoker, [ "deps" ]) inputs = _inputs + [ "$dir_pw_build/python_dist/setup.bat > /${target_name}/", "$dir_pw_build/python_dist/setup.sh > /${target_name}/", ] dirs = _dirs + [ "$target_out_dir/$target_name.wheels/ > /$target_name/python_wheels/" ] output = _zip_path # TODO(pwbug/634): Remove the plumbing-through of invoker's public_deps. public_deps = _public_deps + [ ":${_outer_name}.wheels" ] } } # Generates a directory of Python packages from source files suitable for # deployment outside of the project developer environment. # # The resulting directory contains only files mentioned in each package's # setup.cfg file. This is useful for bundling multiple Python packages up # into a single package for distribution to other locations like # http://pypi.org. # # Args: # packages: A list of pw_python_package targets to be installed into the build # directory. Their dependencies will be pulled in as wheels also. # # include_tests: If true, copy Python package tests to a `tests` subdir. # # extra_files: A list of extra files that should be included in the output. The # format of each item in this list follows this convention: # //some/nested/source_file > nested/destination_file template("pw_create_python_source_tree") { _output_dir = "${target_out_dir}/${target_name}/" _metadata_json_file_list = "${target_gen_dir}/${target_name}_metadata_path_list.txt" # If generating a setup.cfg file a common base file must be provided. if (defined(invoker.generate_setup_cfg)) { generate_setup_cfg = invoker.generate_setup_cfg assert(defined(generate_setup_cfg.common_config_file), "'common_config_file' is required in generate_setup_cfg") } _extra_file_inputs = [] _extra_file_args = [] # Convert extra_file strings to input, outputs and create_python_tree.py args. if (defined(invoker.extra_files)) { _delimiter = ">" _extra_file_outputs = [] foreach(input, invoker.extra_files) { # Remove spaces before and after the delimiter input = string_replace(input, " $_delimiter", _delimiter) input = string_replace(input, "$_delimiter ", _delimiter) input_list = [] input_list = string_split(input, _delimiter) # Save the input file _extra_file_inputs += [ input_list[0] ] # Save the output file _this_output = _output_dir + "/" + input_list[1] _extra_file_outputs += [ _this_output ] # Compose an arg for passing to create_python_tree.py with properly # rebased paths. _extra_file_args += [ string_join(" $_delimiter ", [ rebase_path(input_list[0], root_build_dir), rebase_path(_this_output, root_build_dir), ]) ] } } _include_tests = defined(invoker.include_tests) && invoker.include_tests # Build a list of relative paths containing all the python # package_metadata.json files we depend on. generated_file("${target_name}._metadata_path_list.txt") { data_keys = [ "pw_python_package_metadata_json" ] rebase = root_build_dir deps = invoker.packages outputs = [ _metadata_json_file_list ] } # Run the python action on the metadata_path_list.txt file pw_python_action(target_name) { deps = invoker.packages + [ ":${invoker.target_name}._metadata_path_list.txt" ] script = "$dir_pw_build/py/pw_build/create_python_tree.py" inputs = _extra_file_inputs args = [ "--tree-destination-dir", rebase_path(_output_dir, root_build_dir), "--input-list-files", rebase_path(_metadata_json_file_list, root_build_dir), ] # Add required setup.cfg args if we are generating a merged config. if (defined(generate_setup_cfg)) { if (defined(generate_setup_cfg.common_config_file)) { args += [ "--setupcfg-common-file", rebase_path(generate_setup_cfg.common_config_file, root_build_dir), ] } if (defined(generate_setup_cfg.append_git_sha_to_version)) { args += [ "--setupcfg-version-append-git-sha" ] } if (defined(generate_setup_cfg.append_date_to_version)) { args += [ "--setupcfg-version-append-date" ] } } if (_extra_file_args == []) { # No known output files - stamp instead. stamp = true } else { args += [ "--extra-files" ] args += _extra_file_args # Include extra_files as outputs outputs = _extra_file_outputs } if (_include_tests) { args += [ "--include-tests" ] } } }