pw_build: Update pw_error template
- Display full target names, including the toolchain, for build errors.
- Print the dependency path that caused the error using gn path.
- Add message_lines argument to simplify multi-line messages.
- Replace null_backend.py with pw_error.
- Ensure that pw_error works without the Pigweed Python packages
installed.
Example error if pw_log_BACKEND is unset:
9:28:27 ERR
9:28:27 ERR Build error for //pw_log:pw_log.NO_BACKEND_SET(//targets/host:host_clang_debug):
9:28:27 ERR
9:28:27 ERR Attempted to build the //pw_log:pw_log facade with no backend.
9:28:27 ERR
9:28:27 ERR If you are using this facade, ensure you have configured a backend
9:28:27 ERR properly. The build arg for the facade must be set to a valid
9:28:27 ERR backend in the toolchain. For example, you may need to add a line
9:28:27 ERR like the following to the toolchain's .gni file:
9:28:27 ERR
9:28:27 ERR pw_log_BACKEND = "//path/to/the:backend"
9:28:27 ERR
9:28:27 ERR If you are NOT using this facade, this error may have been triggered
9:28:27 ERR by trying to build all targets.
9:28:27 ERR
9:28:28 ERR Dependency path to this target:
9:28:28 ERR
9:28:28 ERR gn path out //:default "//pw_log:pw_log.NO_BACKEND_SET(//targets/host:host_clang_debug)"
//:default --[private]-->
//:host --[private]-->
//:pigweed_default(//targets/host:host_clang_debug) --[private]-->
//pw_trace:trace_example_basic(//targets/host:host_clang_debug) --[private]-->
//pw_log:pw_log(//targets/host:host_clang_debug) --[public]-->
//pw_log:pw_log.NO_BACKEND_SET(//targets/host:host_clang_debug)
Showing one of 75 "interesting" non-data paths. 0 of them are public.
Use --all to print all paths.
Fixed: 336
Change-Id: Ida252e1ae9956822ec0efbbfc82bad60f58de5e8
Reviewed-on: https://pigweed-review.googlesource.com/c/pigweed/pigweed/+/36240
Pigweed-Auto-Submit: Wyatt Hepler <hepler@google.com>
Reviewed-by: Keir Mierle <keir@google.com>
Commit-Queue: Auto-Submit <auto-submit@pigweed.google.com.iam.gserviceaccount.com>
diff --git a/pw_build/error.gni b/pw_build/error.gni
index 6e7994a..a7c0e69 100644
--- a/pw_build/error.gni
+++ b/pw_build/error.gni
@@ -14,23 +14,39 @@
import("python_action.gni")
-# Prints an error message and exits the build unsuccessfully.
+# Prints an error message and exits the build unsuccessfully. Either 'message'
+# or 'message_lines' must be specified, but not both.
#
# Args:
-# message: The message to print.
+# message: The message to print. Use \n for newlines.
+# message_lines: List of lines to use for the message.
#
template("pw_error") {
- assert(defined(invoker.message) && invoker.message != "",
- "pw_error requires an error message")
+ assert(
+ defined(invoker.message) != defined(invoker.message_lines),
+ "pw_error requires either a 'message' string or a 'message_lines' list")
- pw_python_action(target_name) {
+ if (defined(invoker.message_lines)) {
+ _message = string_join("\n", invoker.message_lines)
+ } else {
+ _message = invoker.message
+ }
+ assert(_message != "", "The message cannot be empty")
+
+ action(target_name) {
script = "$dir_pw_build/py/pw_build/error.py"
args = [
"--target",
- get_label_info(target_name, "label_no_toolchain"),
+ get_label_info(":$target_name", "label_with_toolchain"),
"--message",
- invoker.message,
+ _message,
+ "--root",
+ rebase_path("//"),
+ "--out",
+ rebase_path(root_build_dir),
]
- stamp = true
+
+ # This output file is never created.
+ outputs = [ "$target_gen_dir/$target_name.build_error" ]
}
}
diff --git a/pw_build/facade.gni b/pw_build/facade.gni
index 4beee92..517762b 100644
--- a/pw_build/facade.gni
+++ b/pw_build/facade.gni
@@ -14,7 +14,7 @@
import("//build_overrides/pigweed.gni")
-import("$dir_pw_build/python_action.gni")
+import("$dir_pw_build/error.gni")
import("$dir_pw_build/target_types.gni")
# Declare a facade.
@@ -100,17 +100,35 @@
}
if (invoker.backend == "") {
+ # Try to guess the name of the facade's backend variable.
+ _dir = get_path_info(get_label_info(":$target_name", "dir"), "name")
+ if (target_name == _dir) {
+ _varname = target_name + "_BACKEND"
+ } else {
+ # There is no way to capitalize this string in GN, so use <FACADE_NAME>
+ # instead of the lowercase target name.
+ _varname = _dir + "_<FACADE_NAME>_BACKEND"
+ }
+
# If backend is not set to anything, create a script that emits an error.
# This will be added as a data dependency to the actual target, so that
# attempting to build the facade without a backend fails with a relevant
# error message.
- _main_target_name = target_name
-
- pw_python_action(_main_target_name + ".NO_BACKEND_SET") {
- stamp = true
- script = "$dir_pw_build/py/pw_build/null_backend.py"
- args = [ _main_target_name ]
- not_needed(invoker, "*")
+ pw_error(target_name + ".NO_BACKEND_SET") {
+ _label = get_label_info(":${invoker.target_name}", "label_no_toolchain")
+ message_lines = [
+ "Attempted to build the $_label facade with no backend.",
+ "",
+ "If you are using this facade, ensure you have configured a backend ",
+ "properly. The build arg for the facade must be set to a valid ",
+ "backend in the toolchain. For example, you may need to add a line ",
+ "like the following to the toolchain's .gni file:",
+ "",
+ " $_varname = \"//path/to/the:backend\"",
+ "",
+ "If you are NOT using this facade, this error may have been triggered ",
+ "by trying to build all targets.",
+ ]
}
}
@@ -129,7 +147,7 @@
public_deps += [ invoker.backend ]
} else {
# If the backend is not set, depend on the *.NO_BACKEND_SET target.
- public_deps += [ ":$_main_target_name" + ".NO_BACKEND_SET" ]
+ public_deps += [ ":$target_name.NO_BACKEND_SET" ]
}
}
}
diff --git a/pw_build/py/BUILD.gn b/pw_build/py/BUILD.gn
index 2355db7..5287fb5 100644
--- a/pw_build/py/BUILD.gn
+++ b/pw_build/py/BUILD.gn
@@ -28,7 +28,6 @@
"pw_build/host_tool.py",
"pw_build/mirror_tree.py",
"pw_build/nop.py",
- "pw_build/null_backend.py",
"pw_build/python_runner.py",
"pw_build/python_wheels.py",
"pw_build/zip.py",
diff --git a/pw_build/py/pw_build/error.py b/pw_build/py/pw_build/error.py
index d98d55d..9d0f41e 100644
--- a/pw_build/py/pw_build/error.py
+++ b/pw_build/py/pw_build/error.py
@@ -1,4 +1,4 @@
-# Copyright 2020 The Pigweed Authors
+# 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
@@ -15,32 +15,70 @@
import argparse
import logging
+import os
+from pathlib import Path
+import subprocess
import sys
-import pw_cli.log
+try:
+ from pw_cli.log import install as setup_logging
+except ImportError:
+ from logging import basicConfig as setup_logging # type: ignore
_LOG = logging.getLogger(__name__)
-def _parse_args():
+def _parse_args() -> argparse.Namespace:
parser = argparse.ArgumentParser(description=__doc__)
- parser.add_argument('--message', help='Error message to print')
- parser.add_argument('--target', help='GN target in which error occurred')
+ parser.add_argument('--message',
+ required=True,
+ help='Error message to print')
+ parser.add_argument('--target',
+ required=True,
+ help='GN target in which the error occurred')
+ parser.add_argument('--root', required=True, type=Path, help='GN root')
+ parser.add_argument('--out', required=True, type=Path, help='GN out dir')
return parser.parse_args()
-def main(message: str, target: str) -> int:
+def main(message: str, target: str, root: Path, out: Path) -> int:
+ """Logs the error message and returns 1."""
+
_LOG.error('')
- _LOG.error('Build error:')
+ _LOG.error('Build error for %s:', target)
_LOG.error('')
+
for line in message.split('\\n'):
_LOG.error(' %s', line)
+
_LOG.error('')
- _LOG.error('(in %s)', target)
- _LOG.error('')
+
+ gn_cmd = subprocess.run(
+ ['gn', 'path', f'--root={root}', out, '//:default', target],
+ capture_output=True)
+ path_info = gn_cmd.stdout.decode(errors='replace').rstrip()
+
+ relative_out = os.path.relpath(out, root)
+
+ if gn_cmd.returncode == 0 and 'No non-data paths found' not in path_info:
+ _LOG.error('Dependency path to this target:')
+ _LOG.error('')
+ _LOG.error(' gn path %s //:default "%s"\n%s', relative_out, target,
+ path_info)
+ _LOG.error('')
+ else:
+ _LOG.error(
+ 'Run this command to see the build dependency path to this target:'
+ )
+ _LOG.error('')
+ _LOG.error(' gn path %s <target> "%s"', relative_out, target)
+ _LOG.error('')
+ _LOG.error('where <target> is the GN target you are building.')
+ _LOG.error('')
+
return 1
if __name__ == '__main__':
- pw_cli.log.install()
+ setup_logging()
sys.exit(main(**vars(_parse_args())))
diff --git a/pw_build/py/pw_build/null_backend.py b/pw_build/py/pw_build/null_backend.py
deleted file mode 100644
index adc61e7..0000000
--- a/pw_build/py/pw_build/null_backend.py
+++ /dev/null
@@ -1,39 +0,0 @@
-# Copyright 2019 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.
-"""Script that emits a helpful error when a facade is used without a backend."""
-
-import argparse
-import sys
-
-
-def parse_args():
- """Parses command-line arguments."""
-
- parser = argparse.ArgumentParser(
- description='Emits an error when a facade has a null backend')
- parser.add_argument('facade_name', help='The facade with a null backend')
- return parser.parse_args()
-
-
-def main():
- args = parse_args()
- print(f'ERROR: {args.facade_name} tried to build without a backend.')
- print('If you are using this module, ensure you have configured a backend '
- 'properly. If you are NOT using this module, this error may have '
- 'been triggered by trying to build all targets.')
- sys.exit(1)
-
-
-if __name__ == '__main__':
- main()