diff --git a/doc/rules.md b/doc/rules.md index 8c5b63dce..7540d104f 100644 --- a/doc/rules.md +++ b/doc/rules.md @@ -458,7 +458,8 @@ its transitive dependencies be propagated.
 swift_library(name, deps, srcs, data, always_include_developer_search_paths, alwayslink, copts,
               defines, generated_header_name, generates_header, library_evolution, linkopts,
-              linkstatic, module_name, package_name, plugins, private_deps, swiftc_inputs)
+              linkstatic, module_name, package_name, plugins, private_deps, suppress_warning_groups,
+              swiftc_inputs)
 
Compiles and links Swift code into a static library and Swift module. @@ -485,6 +486,7 @@ Compiles and links Swift code into a static library and Swift module. | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | | private_deps | A list of targets that are implementation-only dependencies of the target being built. Libraries/linker flags from these dependencies will be propagated to dependent for linking, but artifacts/flags required for compilation (such as .swiftmodule files, C headers, and search paths) will not be propagated.

Allowed kinds of dependencies are:

* `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` and `objc_library` (or anything propagating `CcInfo`) | List of labels | optional | `[]` | +| suppress_warning_groups | A list of Swift diagnostic groups to suppress in build output (for example, `DeprecatedDeclaration`). These diagnostics are filtered from the compiler's output by the wrapper and do not change the compiler's behavior. See https://docs.swift.org/compiler/documentation/diagnostics/diagnostic-groups/ for available diagnostic groups. | List of strings | optional | `[]` | | swiftc_inputs | Additional files that are referenced using `$(location ...)` in attributes that support location expansion. | List of labels | optional | `[]` | @@ -618,7 +620,7 @@ remaining modules collected are not present in the `aliases` of the
 swift_overlay(name, deps, srcs, always_include_developer_search_paths, alwayslink, copts, defines,
               library_evolution, linkopts, linkstatic, package_name, plugins, private_deps,
-              swiftc_inputs)
+              suppress_warning_groups, swiftc_inputs)
 
A Swift overlay that sits on top of a C/Objective-C library, allowing an author @@ -709,6 +711,7 @@ almost always an anti-pattern. | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | | private_deps | A list of targets that are implementation-only dependencies of the target being built. Libraries/linker flags from these dependencies will be propagated to dependent for linking, but artifacts/flags required for compilation (such as .swiftmodule files, C headers, and search paths) will not be propagated.

Allowed kinds of dependencies are:

* `swift_library` (or anything propagating `SwiftInfo`)

* `cc_library` and `objc_library` (or anything propagating `CcInfo`) | List of labels | optional | `[]` | +| suppress_warning_groups | A list of Swift diagnostic groups to suppress in build output (for example, `DeprecatedDeclaration`). These diagnostics are filtered from the compiler's output by the wrapper and do not change the compiler's behavior. See https://docs.swift.org/compiler/documentation/diagnostics/diagnostic-groups/ for available diagnostic groups. | List of strings | optional | `[]` | | swiftc_inputs | Additional files that are referenced using `$(location ...)` in attributes that support location expansion. | List of labels | optional | `[]` | @@ -772,7 +775,8 @@ swift_proto_compiler(name, name, deps, srcs, data, additional_compiler_deps, additional_compiler_info, always_include_developer_search_paths, alwayslink, compilers, copts, defines, generated_header_name, generates_header, library_evolution, linkopts, linkstatic, - module_name, package_name, plugins, protos, swiftc_inputs) + module_name, package_name, plugins, protos, suppress_warning_groups, + swiftc_inputs) Generates a Swift static library from one or more targets producing `ProtoInfo`. @@ -837,6 +841,7 @@ swift_proto_library( | package_name | The semantic package of the Swift target being built. Targets with the same package_name can access APIs using the 'package' access control modifier in Swift 5.9+. | String | optional | `""` | | plugins | A list of `swift_compiler_plugin` targets that should be loaded by the compiler when compiling this module and any modules that directly depend on it. | List of labels | optional | `[]` | | protos | A list of `proto_library` targets (or targets producing `ProtoInfo`), from which the Swift source files should be generated. | List of labels | optional | `[]` | +| suppress_warning_groups | A list of Swift diagnostic groups to suppress in build output (for example, `DeprecatedDeclaration`). These diagnostics are filtered from the compiler's output by the wrapper and do not change the compiler's behavior. See https://docs.swift.org/compiler/documentation/diagnostics/diagnostic-groups/ for available diagnostic groups. | List of strings | optional | `[]` | | swiftc_inputs | Additional files that are referenced using `$(location ...)` in attributes that support location expansion. | List of labels | optional | `[]` | diff --git a/examples/xplatform/suppress_warning_groups/BUILD b/examples/xplatform/suppress_warning_groups/BUILD new file mode 100644 index 000000000..bad1a4ae3 --- /dev/null +++ b/examples/xplatform/suppress_warning_groups/BUILD @@ -0,0 +1,14 @@ +load("//swift:swift_library.bzl", "swift_library") + +swift_library( + name = "impl_dep", + srcs = ["ImplDep.swift"], + module_name = "ImplDep", +) + +swift_library( + name = "impl_only_suppressed", + srcs = ["ImplementationOnlySuppressed.swift"], + private_deps = [":impl_dep"], + suppress_warning_groups = ["ImplementationOnlyDeprecated"], +) diff --git a/examples/xplatform/suppress_warning_groups/ImplDep.swift b/examples/xplatform/suppress_warning_groups/ImplDep.swift new file mode 100644 index 000000000..f3b4a05cc --- /dev/null +++ b/examples/xplatform/suppress_warning_groups/ImplDep.swift @@ -0,0 +1,3 @@ +public struct Dep { + public init() {} +} diff --git a/examples/xplatform/suppress_warning_groups/ImplementationOnlySuppressed.swift b/examples/xplatform/suppress_warning_groups/ImplementationOnlySuppressed.swift new file mode 100644 index 000000000..c2ab6d2ba --- /dev/null +++ b/examples/xplatform/suppress_warning_groups/ImplementationOnlySuppressed.swift @@ -0,0 +1,5 @@ +@_implementationOnly import ImplDep + +func useDepSuppressed() { + _ = Dep() +} diff --git a/proto/swift_proto_utils.bzl b/proto/swift_proto_utils.bzl index 551420c7a..e505a7054 100644 --- a/proto/swift_proto_utils.bzl +++ b/proto/swift_proto_utils.bzl @@ -271,6 +271,14 @@ def compile_swift_protos_for_target( copts = expand_make_variables(ctx, copts, "copts") linkopts = expand_locations(ctx, getattr(attr, "linkopts", []), swiftc_inputs) linkopts = expand_make_variables(ctx, linkopts, "linkopts") + suppress_warning_groups = getattr(attr, "suppress_warning_groups", []) + if suppress_warning_groups: + if "-print-diagnostic-groups" not in copts: + copts.append("-print-diagnostic-groups") + for group in suppress_warning_groups: + copts.append( + "-Xwrapped-swift=-suppress-warning-group={}".format(group), + ) # Compile the generated Swift source files as a module: include_dev_srch_paths = include_developer_search_paths(attr) diff --git a/swift/internal/attrs.bzl b/swift/internal/attrs.bzl index 070511b4b..56281eb67 100644 --- a/swift/internal/attrs.bzl +++ b/swift/internal/attrs.bzl @@ -323,6 +323,15 @@ Note that by default, this value will default to True. But if the swift.enable_embedded feature is on, this value will be automatically overridden to False, as the swift features that cause -force_load to be required (such as reflection) are not available in that mode. +""", + ), + "suppress_warning_groups": attr.string_list( + doc = """\ +A list of Swift diagnostic groups to suppress in build output (for example, +`DeprecatedDeclaration`). These diagnostics are filtered from the compiler's +output by the wrapper and do not change the compiler's behavior. See +https://docs.swift.org/compiler/documentation/diagnostics/diagnostic-groups/ +for available diagnostic groups. """, ), "generated_header_name": attr.string( diff --git a/swift/swift_library.bzl b/swift/swift_library.bzl index 24ade7444..c24c96225 100644 --- a/swift/swift_library.bzl +++ b/swift/swift_library.bzl @@ -129,6 +129,15 @@ def _swift_library_impl(ctx): linkopts = expand_locations(ctx, ctx.attr.linkopts, ctx.attr.swiftc_inputs) linkopts = expand_make_variables(ctx, linkopts, "linkopts") srcs = ctx.files.srcs + if ctx.attr.suppress_warning_groups: + if "-print-diagnostic-groups" not in copts: + copts.append("-print-diagnostic-groups") + for group in ctx.attr.suppress_warning_groups: + copts.append( + "-Xwrapped-swift=-suppress-warning-group={}".format( + group, + ), + ) module_copts = additional_per_module_swiftcopts( ctx.label, diff --git a/swift/swift_overlay.bzl b/swift/swift_overlay.bzl index a17e4d529..61cf488ff 100644 --- a/swift/swift_overlay.bzl +++ b/swift/swift_overlay.bzl @@ -47,6 +47,17 @@ def _swift_overlay_impl(ctx): deps = ctx.attr.deps private_deps = ctx.attr.private_deps + copts = list(ctx.attr.copts) + if ctx.attr.suppress_warning_groups: + if "-print-diagnostic-groups" not in copts: + copts.append("-print-diagnostic-groups") + for group in ctx.attr.suppress_warning_groups: + copts.append( + "-Xwrapped-swift=-suppress-warning-group={}".format( + group, + ), + ) + features = list(ctx.features) if ctx.attr.library_evolution: features.append(SWIFT_FEATURE_ENABLE_LIBRARY_EVOLUTION) @@ -59,7 +70,7 @@ def _swift_overlay_impl(ctx): label = ctx.label, srcs = ctx.files.srcs, additional_inputs = ctx.files.swiftc_inputs, - copts = ctx.attr.copts, + copts = copts, defines = ctx.attr.defines, disabled_features = ctx.disabled_features, enabled_features = ctx.features, diff --git a/test/BUILD b/test/BUILD index 5929d0850..d6772c29b 100644 --- a/test/BUILD +++ b/test/BUILD @@ -22,6 +22,7 @@ load(":private_deps_tests.bzl", "private_deps_test_suite") load(":private_swiftinterface_tests.bzl", "private_swiftinterface_test_suite") load(":runtime_deps_tests.bzl", "runtime_deps_test_suite") load(":split_derived_files_tests.bzl", "split_derived_files_test_suite") +load(":suppress_warning_groups_tests.bzl", "suppress_warning_groups_test_suite") load(":swift_binary_linking_tests.bzl", "swift_binary_linking_test_suite") load(":swift_through_non_swift_tests.bzl", "swift_through_non_swift_test_suite") load(":symbol_graphs_tests.bzl", "symbol_graphs_test_suite") @@ -69,6 +70,8 @@ private_deps_test_suite(name = "private_deps") split_derived_files_test_suite(name = "split_derived_files") +suppress_warning_groups_test_suite(name = "suppress_warning_groups") + swift_binary_linking_test_suite(name = "swift_binary_rules") swift_through_non_swift_test_suite(name = "swift_through_non_swift") diff --git a/test/fixtures/suppress_warning_groups/BUILD b/test/fixtures/suppress_warning_groups/BUILD new file mode 100644 index 000000000..c7196e8a8 --- /dev/null +++ b/test/fixtures/suppress_warning_groups/BUILD @@ -0,0 +1,31 @@ +load("//swift:swift_library.bzl", "swift_library") +load("//swift:swift_overlay.bzl", "swift_overlay") +load("//test/fixtures:common.bzl", "FIXTURE_TAGS") + +package( + default_visibility = ["//test:__subpackages__"], +) + +swift_library( + name = "single_category", + srcs = ["Library.swift"], + suppress_warning_groups = ["DeprecatedDeclaration"], + tags = FIXTURE_TAGS, +) + +swift_library( + name = "multiple_categories", + srcs = ["Library.swift"], + suppress_warning_groups = [ + "DeprecatedDeclaration", + "ImplementationOnlyDeprecated", + ], + tags = FIXTURE_TAGS, +) + +swift_overlay( + name = "overlay", + srcs = ["Overlay.swift"], + suppress_warning_groups = ["OverlayDeprecated"], + tags = FIXTURE_TAGS, +) diff --git a/test/fixtures/suppress_warning_groups/Library.swift b/test/fixtures/suppress_warning_groups/Library.swift new file mode 100644 index 000000000..2da93851b --- /dev/null +++ b/test/fixtures/suppress_warning_groups/Library.swift @@ -0,0 +1 @@ +public func libraryFunction() {} diff --git a/test/fixtures/suppress_warning_groups/Overlay.swift b/test/fixtures/suppress_warning_groups/Overlay.swift new file mode 100644 index 000000000..c39879658 --- /dev/null +++ b/test/fixtures/suppress_warning_groups/Overlay.swift @@ -0,0 +1 @@ +public func overlayFunction() {} diff --git a/test/suppress_warning_groups_tests.bzl b/test/suppress_warning_groups_tests.bzl new file mode 100644 index 000000000..9de7343bc --- /dev/null +++ b/test/suppress_warning_groups_tests.bzl @@ -0,0 +1,95 @@ +"""Tests for suppress_warning_groups.""" + +load( + "//test/rules:action_command_line_test.bzl", + "action_command_line_test", +) +load("@bazel_skylib//lib:unittest.bzl", "analysistest", "unittest") +load("//swift/internal:providers.bzl", "SwiftOverlayCompileInfo") + +def suppress_warning_groups_test_suite(name, tags = []): + """Test suite for suppress_warning_groups handling. + + Args: + name: The base name to be used in targets created by this macro. + tags: Additional tags to apply to each test. + """ + all_tags = [name] + tags + + action_command_line_test( + name = "{}_swift_library_single".format(name), + expected_argv = [ + "-Xwrapped-swift=-suppress-warning-group=DeprecatedDeclaration", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/suppress_warning_groups:single_category", + ) + + action_command_line_test( + name = "{}_swift_library_multiple".format(name), + expected_argv = [ + "-Xwrapped-swift=-suppress-warning-group=DeprecatedDeclaration", + "-Xwrapped-swift=-suppress-warning-group=ImplementationOnlyDeprecated", + ], + mnemonic = "SwiftCompile", + tags = all_tags, + target_under_test = "//test/fixtures/suppress_warning_groups:multiple_categories", + ) + + overlay_copts_test( + name = "{}_swift_overlay_copts".format(name), + expected_copt = "-Xwrapped-swift=-suppress-warning-group=OverlayDeprecated", + tags = all_tags, + target_under_test = "//test/fixtures/suppress_warning_groups:overlay", + ) + + native.test_suite( + name = name, + tags = all_tags, + ) + +def _normalize_copt(copt): + if copt.startswith("\"") and copt.endswith("\""): + return copt[1:-1] + return copt + +def _overlay_copts_test_impl(ctx): + env = analysistest.begin(ctx) + target_under_test = analysistest.target_under_test(env) + + if SwiftOverlayCompileInfo not in target_under_test: + unittest.fail( + env, + "Target '{}' did not provide SwiftOverlayCompileInfo.".format( + target_under_test.label, + ), + ) + return analysistest.end(env) + + actual = [ + _normalize_copt(copt) + for copt in target_under_test[SwiftOverlayCompileInfo].copts + ] + expected = ctx.attr.expected_copt + if expected not in actual: + unittest.fail( + env, + ("Expected '{}' to contain '{}' in SwiftOverlayCompileInfo.copts, " + + "but got {}.").format( + target_under_test.label, + expected, + actual, + ), + ) + return analysistest.end(env) + +overlay_copts_test = analysistest.make( + _overlay_copts_test_impl, + attrs = { + "expected_copt": attr.string( + mandatory = True, + doc = "The compiler option expected in SwiftOverlayCompileInfo.copts.", + ), + }, +) diff --git a/tools/worker/swift_runner.cc b/tools/worker/swift_runner.cc index 86e70fc20..a2dc91bf8 100644 --- a/tools/worker/swift_runner.cc +++ b/tools/worker/swift_runner.cc @@ -14,8 +14,11 @@ #include "tools/worker/swift_runner.h" +#include +#include #include #include +#include #include "tools/common/bazel_substitutions.h" #include "tools/common/process.h" @@ -103,6 +106,391 @@ static bool StripPrefix(const std::string &prefix, std::string &str) { return true; } +static std::string NormalizeDiagnosticGroup(std::string group) { + size_t first = group.find_first_not_of(" \t"); + if (first == std::string::npos) { + return ""; + } + size_t last = group.find_last_not_of(" \t"); + group = group.substr(first, last - first + 1); + if (group.rfind("[#", 0) == 0 && group.back() == ']') { + group = group.substr(2, group.size() - 3); + } + if (!group.empty() && group.front() == '#') { + group.erase(0, 1); + } + return group; +} + +static std::string StripAnsiCodes(const std::string &line) { + auto skip_csi_sequence = [&](size_t index) { + size_t pos = index + 2; + while (pos < line.size() && (line[pos] < '@' || line[pos] > '~')) { + ++pos; + } + return pos; + }; + + auto skip_osc_sequence = [&](size_t index) { + size_t pos = index + 2; + while (pos < line.size()) { + if (line[pos] == '\a') { + break; + } + if (line[pos] == '\x1b' && pos + 1 < line.size() && + line[pos + 1] == '\\') { + ++pos; + break; + } + ++pos; + } + return pos; + }; + + auto try_skip_escape = [&](size_t *index) { + if (line[*index] != '\x1b' || *index + 1 >= line.size()) { + return false; + } + if (line[*index + 1] == '[') { + *index = skip_csi_sequence(*index); + return true; + } + if (line[*index + 1] == ']') { + *index = skip_osc_sequence(*index); + return true; + } + return false; + }; + + std::string cleaned; + cleaned.reserve(line.size()); + for (size_t i = 0; i < line.size(); ++i) { + if (try_skip_escape(&i)) { + continue; + } + cleaned.push_back(line[i]); + } + return cleaned; +} + +static bool LineHasSuppressedGroup( + const std::string &line, + const std::vector &suppressed_tokens) { + if (suppressed_tokens.empty()) { + return false; + } + std::string cleaned = StripAnsiCodes(line); + for (const auto &token : suppressed_tokens) { + if (cleaned.find(token) != std::string::npos) { + return true; + } + } + return false; +} + +static bool IsGroupDocLine( + const std::string &line, + std::string *group) { + std::string cleaned = StripAnsiCodes(line); + size_t nonspace = cleaned.find_first_not_of(" \t"); + if (nonspace == std::string::npos) { + return false; + } + if (cleaned.compare(nonspace, 2, "[#") != 0) { + return false; + } + size_t closing = cleaned.find(']', nonspace + 2); + if (closing == std::string::npos) { + return false; + } + if (closing + 1 >= cleaned.size() || cleaned[closing + 1] != ':') { + return false; + } + if (group) { + *group = cleaned.substr(nonspace + 2, closing - (nonspace + 2)); + } + return true; +} + +enum class DiagnosticSeverity { + kUnknown, + kWarning, + kError, +}; + +static bool ConsumeColonNumber(const std::string &line, size_t *pos) { + if (*pos >= line.size() || line[*pos] != ':') { + return false; + } + size_t number_start = *pos + 1; + size_t number_end = number_start; + while (number_end < line.size() && + std::isdigit(static_cast(line[number_end]))) { + ++number_end; + } + if (number_end == number_start || number_end >= line.size() || + line[number_end] != ':') { + return false; + } + *pos = number_end; + return true; +} + +static DiagnosticSeverity IsDiagnosticHeader(const std::string &line) { + std::string cleaned = StripAnsiCodes(line); + size_t nonspace = cleaned.find_first_not_of(" \t"); + if (nonspace != std::string::npos) { + if (cleaned.compare(nonspace, 6, "error:") == 0) { + return DiagnosticSeverity::kError; + } + if (cleaned.compare(nonspace, 8, "warning:") == 0) { + return DiagnosticSeverity::kWarning; + } + } + for (size_t i = 0; i < cleaned.size(); ++i) { + if (cleaned[i] != ':') { + continue; + } + size_t pos = i; + if (!ConsumeColonNumber(cleaned, &pos)) { + continue; + } + if (!ConsumeColonNumber(cleaned, &pos)) { + continue; + } + size_t severity_start = pos + 1; + if (severity_start < cleaned.size() && cleaned[severity_start] == ' ') { + ++severity_start; + } + if (cleaned.compare(severity_start, 8, "warning:") == 0) { + return DiagnosticSeverity::kWarning; + } + if (cleaned.compare(severity_start, 6, "error:") == 0) { + return DiagnosticSeverity::kError; + } + } + return DiagnosticSeverity::kUnknown; +} + +static bool HasLocatedDiagnosticKind( + const std::string &line, + const std::string &kind) { + for (size_t i = 0; i < line.size(); ++i) { + if (line[i] != ':') { + continue; + } + size_t pos = i; + if (!ConsumeColonNumber(line, &pos) || !ConsumeColonNumber(line, &pos)) { + continue; + } + size_t kind_start = pos + 1; + if (kind_start < line.size() && line[kind_start] == ' ') { + ++kind_start; + } + if (line.compare(kind_start, kind.size(), kind) == 0) { + return true; + } + } + return false; +} + +static bool IsDiagnosticContinuationLine( + const std::string &line, + bool is_group_doc_line) { + if (is_group_doc_line) { + return true; + } + + std::string cleaned = StripAnsiCodes(line); + if (cleaned.empty()) { + return true; + } + + size_t nonspace = cleaned.find_first_not_of(" \t"); + if (nonspace == std::string::npos) { + return true; + } + + // Most source snippets, caret/fix-it lines, and wrapped diagnostic text are + // indented under a header line. + if (nonspace != 0) { + return true; + } + + if (cleaned.compare(0, 5, "note:") == 0 || + cleaned.compare(0, 7, "remark:") == 0) { + return true; + } + if (HasLocatedDiagnosticKind(cleaned, "note:") || + HasLocatedDiagnosticKind(cleaned, "remark:")) { + return true; + } + + // Match source-context line-number formatting such as "12 | foo()". + if (std::isdigit(static_cast(cleaned[0]))) { + size_t pos = 1; + while (pos < cleaned.size() && + std::isdigit(static_cast(cleaned[pos]))) { + ++pos; + } + while (pos < cleaned.size() && cleaned[pos] == ' ') { + ++pos; + } + if (pos < cleaned.size() && cleaned[pos] == '|') { + return true; + } + } + + if (cleaned[0] == '|' || cleaned[0] == '^' || cleaned[0] == '~') { + return true; + } + + return false; +} + +class DiagnosticFilteringStreambuf : public std::streambuf { + public: + DiagnosticFilteringStreambuf( + std::ostream *dest, + const std::vector &groups) + : dest_(dest), + groups_(groups), + in_diagnostic_block_(false), + suppress_current_block_(false), + current_severity_(DiagnosticSeverity::kUnknown) { + suppressed_tokens_.reserve(groups_.size()); + for (const auto &group : groups_) { + suppressed_tokens_.push_back("[#" + group + "]"); + } + } + + void FlushRemainder() { + if (!buffer_.empty()) { + ProcessLine(buffer_, /*has_newline=*/false); + buffer_.clear(); + } + FlushDiagnosticBlock(); + dest_->flush(); + } + + protected: + int overflow(int ch) override { + if (ch == traits_type::eof()) { + return traits_type::not_eof(ch); + } + char c = static_cast(ch); + return xsputn(&c, 1) == 1 ? ch : traits_type::eof(); + } + + std::streamsize xsputn(const char *s, std::streamsize n) override { + buffer_.append(s, static_cast(n)); + size_t pos = 0; + while ((pos = buffer_.find('\n')) != std::string::npos) { + std::string line = buffer_.substr(0, pos); + ProcessLine(line, /*has_newline=*/true); + buffer_.erase(0, pos + 1); + } + return n; + } + + private: + struct Line { + std::string text; + bool has_newline; + }; + + void WriteLine(const Line &line) { + dest_->write(line.text.data(), line.text.size()); + if (line.has_newline) { + dest_->put('\n'); + } + } + + bool ShouldSuppressCurrentBlock() const { + return in_diagnostic_block_ && suppress_current_block_ && + current_severity_ == DiagnosticSeverity::kWarning; + } + + void FlushDiagnosticBlock() { + if (!in_diagnostic_block_ || diagnostic_block_.empty()) { + return; + } + if (!ShouldSuppressCurrentBlock()) { + for (const auto &line : diagnostic_block_) { + WriteLine(line); + } + } + diagnostic_block_.clear(); + suppress_current_block_ = false; + in_diagnostic_block_ = false; + current_severity_ = DiagnosticSeverity::kUnknown; + } + + void ProcessLine(const std::string &line, bool has_newline) { + DiagnosticSeverity severity = IsDiagnosticHeader(line); + std::string doc_group; + bool is_group_doc_line = IsGroupDocLine(line, &doc_group); + bool is_doc_for_suppressed_group = false; + if (is_group_doc_line) { + is_doc_for_suppressed_group = + std::find(groups_.begin(), groups_.end(), doc_group) != groups_.end(); + if (is_doc_for_suppressed_group) { + return; + } + } + + if (severity != DiagnosticSeverity::kUnknown) { + FlushDiagnosticBlock(); + in_diagnostic_block_ = true; + current_severity_ = severity; + } + + Line current{line, has_newline}; + if (in_diagnostic_block_) { + if (severity == DiagnosticSeverity::kUnknown && + !IsDiagnosticContinuationLine(line, is_group_doc_line)) { + FlushDiagnosticBlock(); + WriteLine(current); + return; + } + + if (!is_group_doc_line && + LineHasSuppressedGroup(line, suppressed_tokens_)) { + suppress_current_block_ = true; + } + + diagnostic_block_.push_back(current); + } else { + WriteLine(current); + } + } + + std::ostream *dest_; + std::string buffer_; + const std::vector &groups_; + std::vector suppressed_tokens_; + std::vector diagnostic_block_; + bool in_diagnostic_block_; + bool suppress_current_block_; + DiagnosticSeverity current_severity_; +}; + +class DiagnosticFilteringStream : public std::ostream { + public: + DiagnosticFilteringStream( + std::ostream *dest, + const std::vector &groups) + : std::ostream(nullptr), + buffer_(dest, groups) { + rdbuf(&buffer_); + } + + void FlushRemainder() { buffer_.FlushRemainder(); } + + private: + DiagnosticFilteringStreambuf buffer_; +}; + } // namespace SwiftRunner::SwiftRunner(const std::vector &args, @@ -125,11 +513,22 @@ int SwiftRunner::Run(std::ostream *stderr_stream, bool stdout_to_stderr) { std::filesystem::remove(swift_source_info_path_); } - int exit_code = RunSubProcess( - args_, &job_env_, stderr_stream, stdout_to_stderr); - - if (exit_code != 0) { - return exit_code; + int exit_code = 0; + bool should_filter_output = !suppress_warning_groups_.empty(); + if (should_filter_output) { + DiagnosticFilteringStream filtered(stderr_stream, suppress_warning_groups_); + // Preserve stdout unless the caller explicitly requested redirecting it. + exit_code = RunSubProcess( + args_, &job_env_, &filtered, /*stdout_to_stderr=*/stdout_to_stderr); + filtered.FlushRemainder(); + if (exit_code != 0) { + return exit_code; + } + } else { + exit_code = RunSubProcess(args_, &job_env_, stderr_stream, stdout_to_stderr); + if (exit_code != 0) { + return exit_code; + } } if (!generated_header_rewriter_path_.empty()) { @@ -149,6 +548,9 @@ int SwiftRunner::Run(std::ostream *stderr_stream, bool stdout_to_stderr) { exit_code = RunSubProcess( rewriter_args, /*env=*/nullptr, stderr_stream, stdout_to_stderr); + if (exit_code != 0) { + return exit_code; + } } auto enable_global_index_store = global_index_store_import_path_ != ""; @@ -365,6 +767,16 @@ bool SwiftRunner::ProcessArgument( changed = true; } else if (StripPrefix("-global-index-store-import-path=", new_arg)) { changed = true; + } else if (StripPrefix("-suppress-warning-group=", new_arg)) { + std::string group = NormalizeDiagnosticGroup(new_arg); + if (!group.empty() && + std::find( + suppress_warning_groups_.begin(), + suppress_warning_groups_.end(), + group) == suppress_warning_groups_.end()) { + suppress_warning_groups_.push_back(group); + } + changed = true; } else { // TODO(allevato): Report that an unknown wrapper arg was found and give // the caller a way to exit gracefully. diff --git a/tools/worker/swift_runner.h b/tools/worker/swift_runner.h index 74b30b98d..61b294657 100644 --- a/tools/worker/swift_runner.h +++ b/tools/worker/swift_runner.h @@ -59,6 +59,10 @@ extern bool ArgumentEnablesWMO(const std::string &arg); // the directory afterwards. This should resolve issues where the module // cache state is not refreshed correctly in all situations, which // sometimes results in hard-to-diagnose crashes in `swiftc`. +// +// -Xwrapped-swift=-suppress-warning-group= +// When specified, diagnostics that include the given group (for example, +// `DeprecatedDeclaration`) will be filtered from the compiler output. class SwiftRunner { public: // Create a new spawner that launches a Swift tool with the given arguments. @@ -182,6 +186,9 @@ class SwiftRunner { // Whether `.swiftsourceinfo` files are being generated. bool emit_swift_source_info_; + + // Diagnostic groups to suppress from compiler output. + std::vector suppress_warning_groups_; }; #endif // BUILD_BAZEL_RULES_SWIFT_TOOLS_WORKER_SWIFT_RUNNER_H_