• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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