| # Copyright 2026 The Fuchsia Authors. All rights reserved. | |
| # Use of this source code is governed by a BSD-style license that can be | |
| # found in the LICENSE file. | |
| """Defines SHAC checks for FIDL library migration from GN to Bazel. | |
| These checks are only needed until the migration from GN to Bazel is complete. | |
| """ | |
| load("./common.star", "os_exec") | |
| # FIDL library migration checks: | |
| # 1. All the attributes in BUILD.bazel should have a mapped attribute in BUILD.gn. | |
| # 2. All the attributes in BUILD.gn should be mapped to attributes in BUILD.bazel. | |
| # 3. The copyright years in new created BUILD.bazel should be current year. | |
| # 4. Migrated targets should be added to the targets_lists in `//sdk/fidl/category_lists.bzl`, according to its `category` and `stable` attributes. | |
| # 5. The "//build/tools/bazel2gn/bazel_migration.gni" should be imported in the BUILD.gn of migrated libraries. | |
| # 6. In the BUILD.bazel, the "fidl_library" should be loaded from "//build/bazel/rules/fidl:fidl_library.bzl". | |
| # 7. visibility attribute should exist in both BUILD.bazel and BUILD.gn. | |
| # 8. target name in BUILD.bazel should be the same as in BUILD.gn. | |
| # 9. Attributes in BUILD.bazel and BUILD.gn should have the same values. | |
| # Verified by changing copyright year to 2025. | |
| # The Gerrit SHAC does report the expected warning. It confirms: | |
| # 1. Current year can be obtained correctly. | |
| # 2. The BUILD.bazel file is read correctly. | |
| # 3. The copyright year checking works as expected. | |
| def _check_copyright_year(ctx, path, content): | |
| """Checks if the copyright year of a newly created BUILD.bazel file is current year.""" | |
| # Get current year. | |
| date_res = os_exec(ctx, ["/bin/date", "+%Y %m"]).wait() | |
| date_parts = date_res.stdout.strip().split() | |
| current_year = date_parts[0] | |
| # Check Item 3 | |
| # The first line should start with "# Copyright". | |
| lines = str(content).splitlines() | |
| if len(lines) > 0: | |
| line = lines[0] | |
| if not line.startswith("# Copyright"): | |
| ctx.emit.finding( | |
| message = "First line of BUILD.bazel must be a copyright header.", | |
| filepath = path, | |
| level = "error", | |
| line = 1, | |
| ) | |
| elif not line.startswith("# Copyright " + current_year): | |
| ctx.emit.finding( | |
| message = "Copyright year should be '%s'. Please double check if the copyright year is correct." % current_year, | |
| filepath = path, | |
| level = "warning", | |
| line = 1, | |
| ) | |
| else: | |
| ctx.emit.finding( | |
| message = "BUILD.bazel file is empty or could not be read.", | |
| filepath = path, | |
| level = "error", | |
| line = 1, | |
| ) | |
| # Verified by changing a different target name in BUILD.gn. | |
| # Errors are reported as expected. This confirms: | |
| # 1. Both BUILD.bazel and BUILD.gn can be read correctly. | |
| # 2. Target names are retrieved correctly. | |
| # 3. Attributes and attribute values are retrieved and mapped correctly. | |
| def _check_build_files(ctx, target_files): | |
| """Checks consistency of BUILD.bazel and BUILD.gn files for migrated FIDL libraries.""" | |
| # Only include the attributes which have different name strings in | |
| # bazel and gn files into this mapping. Attributes not in this | |
| # mapping are deemed to have the same name and value in both files. | |
| bazel_to_gn_mapping = { | |
| "srcs": "sources", | |
| "deps": "public_deps", | |
| "api_area": "sdk_area", | |
| "category": "sdk_category", | |
| "library_name": "name", | |
| } | |
| gn_to_bazel_mapping = { | |
| "sources": "srcs", | |
| "public_deps": "deps", | |
| "sdk_area": "api_area", | |
| "sdk_category": "category", | |
| "name": "library_name", | |
| } | |
| # Apply the check to the libraries in current CL that have both BUILD.bazel and BUILD.gn. | |
| # Exclude //sdk/fidl/BUILD.bazel and //sdk/fidl/BUILD.gn. | |
| for file_info in target_files: | |
| path = file_info.path | |
| bazel_content = file_info.bazel_content | |
| gn_content = file_info.gn_content | |
| bazel_targets = file_info.bazel_targets | |
| gn_targets = file_info.gn_targets | |
| dir_path = _get_dir_path(path) | |
| if not dir_path: | |
| ctx.emit.finding( | |
| message = "Cannot get directory path", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| # Check Item 8, target names should be the same. | |
| bazel_target_names = bazel_targets.keys() | |
| gn_target_names = gn_targets.keys() | |
| for t in bazel_target_names: | |
| if t not in gn_target_names: | |
| ctx.emit.finding( | |
| message = "FIDL library target '%s' found in BUILD.bazel does not have a corresponding target in BUILD.gn." % t, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| for t in gn_target_names: | |
| if t not in bazel_target_names: | |
| ctx.emit.finding( | |
| message = "FIDL library target '%s' found in BUILD.gn does not have a corresponding target in BUILD.bazel." % t, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| # Get the directory name. It's used for checking if the fidl target name is | |
| # the same with directory name when it's under //sdk/fidl. | |
| last_slash_idx = dir_path.rfind("/") | |
| dir_name = dir_path[last_slash_idx + 1:] if last_slash_idx != -1 else dir_path | |
| # For libraries under sdk/fidl/, the target name must match the directory name. | |
| if path.startswith("sdk/fidl/"): | |
| for t in bazel_target_names: | |
| if t != dir_name: | |
| ctx.emit.finding( | |
| message = "Target name in BUILD.bazel ('%s') must match directory name ('%s') for libraries under sdk/fidl/." % (t, dir_name), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| # Check Item 6, BUILD.bazel must load fidl_library from //build/bazel/rules/fidl:fidl_library.bzl | |
| load_matches = ctx.re.allmatches(r'load\(\s*"//build/bazel/rules/fidl:fidl_library.bzl"\s*,\s*"fidl_library"\s*\)', str(bazel_content)) | |
| if not load_matches: | |
| ctx.emit.finding( | |
| message = "BUILD.bazel must load fidl_library from //build/bazel/rules/fidl:fidl_library.bzl", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| # Check Item 5, BUILD.gn must import //build/tools/bazel2gn/bazel_migration.gni | |
| if "import(\"//build/tools/bazel2gn/bazel_migration.gni\")" not in str(gn_content): | |
| ctx.emit.finding( | |
| message = "BUILD.gn must import //build/tools/bazel2gn/bazel_migration.gni", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| # This is the dictionary to keep targets and their attributes values in both gn and bazel build files. | |
| combined_dict = {} | |
| for target, attrs in bazel_targets.items(): | |
| for attr, val in attrs.items(): | |
| key = target + "-" + attr | |
| combined_dict[key] = {"target": target, "attr": attr, "bazel": val, "gn": None} | |
| for target, attrs in gn_targets.items(): | |
| for attr, val in attrs.items(): | |
| mapped_attr = gn_to_bazel_mapping.get(attr, attr) | |
| key = target + "-" + mapped_attr | |
| if key in combined_dict: | |
| combined_dict[key]["gn"] = val | |
| else: | |
| combined_dict[key] = {"target": target, "attr": mapped_attr, "bazel": None, "gn": val} | |
| # Ensure visibility is checked for all targets even if missing in both | |
| for target in bazel_targets: | |
| key = target + "-visibility" | |
| if key not in combined_dict: | |
| combined_dict[key] = {"target": target, "attr": "visibility", "bazel": None, "gn": None} | |
| # Check Item 1 & 2, Attributes must match between BUILD.bazel and BUILD.gn | |
| for key, item in combined_dict.items(): | |
| target = item["target"] | |
| attr = item["attr"] | |
| bazel_val = item["bazel"] | |
| gn_val = item["gn"] | |
| # Check visibility by _check_visibility() function. | |
| if attr == "visibility": | |
| _check_visibility(ctx, path, target, bazel_val, gn_val) | |
| continue | |
| # Skip name check, it's handled in Check Item 8. | |
| if attr == "name": | |
| continue | |
| # WORKAROUND: The `bazel_migration_fidl.star` regex parser does not support parsing | |
| # dictionary values `{ ... }` from BUILD files. The `zither` attribute uses dictionaries | |
| # (e.g. `zither = { c = ... }`). The regex parser incorrectly extracts the dictionary keys | |
| # (`c`, `output_namespace`) as top-level attributes in GN, but fails to extract them in Bazel, | |
| # leading to false positive mismatch errors. | |
| # We explicitly ignore these attributes here since `zither` is a very rare and specialized case. | |
| if attr in ["zither", "c", "output_namespace"]: | |
| continue | |
| expected_gn_attr = bazel_to_gn_mapping.get(attr, attr) | |
| if bazel_val == None: | |
| ctx.emit.finding( | |
| message = "Attribute '%s' in BUILD.gn of FIDL library target '%s' does not have a mapped attribute in BUILD.bazel." % (expected_gn_attr, target), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| elif gn_val == None: | |
| ctx.emit.finding( | |
| message = "Attribute '%s' in BUILD.bazel of FIDL library target '%s' does not have a mapped attribute in BUILD.gn." % (attr, target), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| else: | |
| # Both are not None! Compare values! | |
| if attr == "deps": | |
| bazel_val = [_normalize_dep(d) for d in bazel_val] | |
| gn_val = [_normalize_dep(d) for d in gn_val] | |
| if type(bazel_val) == "list" and type(gn_val) == "list": | |
| if sorted(bazel_val) != sorted(gn_val): | |
| ctx.emit.finding( | |
| message = "Attribute '%s' in BUILD.bazel of FIDL library target '%s' has different values from '%s' in BUILD.gn." % (attr, target, expected_gn_attr), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| elif bazel_val != gn_val: | |
| ctx.emit.finding( | |
| message = "Attribute '%s' in BUILD.bazel of FIDL library target '%s' has different values from '%s' in BUILD.gn." % (attr, target, expected_gn_attr), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| # Check Item 4 | |
| def _check_sdk_fidl_list_inclusion(ctx, target_files): | |
| """Checks if migrated targets are added to target lists in sdk/fidl/category_lists.bzl.""" | |
| # Get //sdk/fidl/category_lists.bzl content | |
| sdk_fidl_category_lists_path = "sdk/fidl/category_lists.bzl" | |
| category_list_content = ctx.io.read_file(ctx.scm.root + "/" + sdk_fidl_category_lists_path) | |
| if category_list_content == None: | |
| ctx.emit.finding( | |
| message = "Could not read //sdk/fidl/category_lists.bzl", | |
| filepath = sdk_fidl_category_lists_path, | |
| level = "error", | |
| ) | |
| return | |
| # Apply the check to the libraries in current CL with both BUILD.bazel and BUILD.gn. | |
| # Exclude //sdk/fidl/BUILD.bazel and //sdk/fidl/BUILD.gn. | |
| for file_info in target_files: | |
| path = file_info.path | |
| bazel_targets = file_info.bazel_targets | |
| dir_path = _get_dir_path(path) | |
| if not dir_path: | |
| ctx.emit.finding( | |
| message = "Could not determine directory path for file.", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| if not bazel_targets: | |
| ctx.emit.finding( | |
| message = "Could not find any fidl_library targets in BUILD.bazel.", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| # Go through all the fidl_library targets in BUILD.bazel. | |
| for target_name, attrs in bazel_targets.items(): | |
| category = attrs.get("category") | |
| stable = attrs.get("stable") | |
| # Skip if both `category` and `stable` are not specified. | |
| if category == None and stable == None: | |
| continue | |
| # Check if both `category` and `stable` are specified together. | |
| if (category == None) != (stable == None): | |
| ctx.emit.finding( | |
| message = "Both `category` and `stable` must be specified together in BUILD.bazel for target '%s'." % target_name, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| # Identify the invalid condition. | |
| if stable == "false" and category != "partner": | |
| ctx.emit.finding( | |
| message = "`stable` must be true unless the category is 'partner' in BUILD.bazel for target '%s'." % target_name, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| # Determine the list name based on `category` and `stable` attributes. | |
| list_name = "" | |
| if category == "partner": | |
| if stable == "true": | |
| list_name = "PARTNER_IDK_STABLE_FIDL_LIBRARY_ATOMS_LIST" | |
| else: | |
| list_name = "PARTNER_IDK_UNSTABLE_FIDL_LIBRARY_ATOMS_LIST" | |
| elif category == "prebuilt": | |
| list_name = "PREBUILT_FIDL_LIBRARY_ATOMS_LIST" | |
| elif category == "host_tool": | |
| list_name = "HOST_TOOL_FIDL_LIBRARY_ATOMS_LIST" | |
| elif category == "compat_test": | |
| list_name = "COMPAT_TEST_FIDL_LIBRARY_ATOMS_LIST" | |
| if list_name: | |
| list_block = _extract_bazel_target_list_block(str(category_list_content), list_name) | |
| expected_target = '"//%s:%s_idk"' % (dir_path, target_name) | |
| if not list_block or expected_target not in list_block: | |
| ctx.emit.finding( | |
| message = "Migrated target '%s' should be added to `%s` in `sdk/fidl/category_lists.bzl`." % (expected_target, list_name), | |
| filepath = path, | |
| level = "error", | |
| ) | |
| def _get_dir_path(path): | |
| """Extracts the directory path from a file path.""" | |
| idx = path.rfind("/") | |
| return path[:idx] if idx != -1 else "" | |
| # Remove the comments in the content. | |
| # Comment is : | |
| # 1. Line comment: starts with "#" | |
| # 2. Inline comment: starts with "#" and ends with "\n" | |
| def _remove_comments(content): | |
| lines = [] | |
| for line in content.splitlines(): | |
| if line.strip().startswith("#"): | |
| continue | |
| idx = line.find("#") | |
| if idx == -1: | |
| lines.append(line) | |
| continue | |
| # A `#` is found, exclude the `#` inside a string. | |
| # Because `#` is valid in a directory name, so it could be in the label string. | |
| in_double_quotes = False | |
| in_single_quotes = False | |
| escaped = False | |
| comment_idx = -1 | |
| current_pos = 0 | |
| for _ in range(len(line)): | |
| if idx == -1: | |
| break | |
| for i in range(current_pos, idx): | |
| char = line[i] | |
| # A quote just after a backslash will be treated as a normal character. | |
| if escaped: | |
| escaped = False | |
| continue | |
| if char == "\\": | |
| escaped = True | |
| continue | |
| # Handle quotes. | |
| if char == '"' and not in_single_quotes: | |
| in_double_quotes = not in_double_quotes | |
| elif char == "'" and not in_double_quotes: | |
| in_single_quotes = not in_single_quotes | |
| # If the `#` is not inside the quotes, it is the start of a comment. | |
| if not in_double_quotes and not in_single_quotes: | |
| comment_idx = idx | |
| break | |
| # The `#` is inside the quotes, continue to find the next `#`. | |
| current_pos = idx + 1 | |
| idx = line.find("#", current_pos) | |
| if comment_idx != -1: | |
| lines.append(line[:comment_idx]) | |
| else: | |
| lines.append(line) | |
| return "\n".join(lines) | |
| # Extract the fidl targets and attributes from BUILD.gn. | |
| # FIDL target name is the string in fidl("target_name"). | |
| def _extract_gn_fidl_targets_map(ctx, content): | |
| targets = {} | |
| parts = content.split('fidl("') | |
| for i in range(1, len(parts)): | |
| # Get FIDL target name | |
| part = parts[i] | |
| idx = part.find('")') | |
| # If `")` is not found, skip this part. | |
| # If no target name is found, the caller will report error. | |
| if idx == -1: | |
| continue | |
| target_name = part[:idx].strip() | |
| # Get the attributes block. 2 = length of `")` | |
| block_content = part[idx + 2:] | |
| start_idx = block_content.find("{") | |
| if start_idx != -1: | |
| block_content = block_content[start_idx + 1:] | |
| end_idx = block_content.find("\n}") | |
| if end_idx != -1: | |
| block_content = block_content[:end_idx] | |
| # Retrieve attributes and their values from the attributes block | |
| attrs = {} | |
| attr_names = _extract_attributes(ctx, block_content) | |
| for name in attr_names: | |
| attrs[name] = _extract_attr_value(ctx, block_content, name) | |
| targets[target_name] = attrs | |
| return targets | |
| # Extract the fidl_library targets and attributes from BUILD.bazel. | |
| def _extract_bazel_fidl_targets_map(ctx, content): | |
| targets = {} | |
| parts = content.split("fidl_library(") | |
| for i in range(1, len(parts)): | |
| part = parts[i] | |
| # Truncate the part to the end of the fidl_library block. | |
| end_idx = part.find("\n)") | |
| if end_idx != -1: | |
| part = part[:end_idx] | |
| matches = ctx.re.allmatches(r'name\s*=\s*"([^"]+)"', part) | |
| if not matches: | |
| matches = ctx.re.allmatches(r"name\s*=\s*'([^']+)'", part) | |
| if not matches: | |
| continue | |
| target_name = matches[0].groups[1].strip() | |
| attrs = {} | |
| attr_names = _extract_attributes(ctx, part) | |
| for name in attr_names: | |
| attrs[name] = _extract_attr_value(ctx, part, name) | |
| targets[target_name] = attrs | |
| return targets | |
| def _extract_attributes(ctx, content): | |
| attrs = [] | |
| for line in content.splitlines(): | |
| matches = ctx.re.allmatches(r"^\s*([a-zA-Z_0-9]+)\s*=", line) | |
| if matches: | |
| attrs.append(matches[0].groups[1]) | |
| return attrs | |
| def _extract_attr_value(ctx, content, attr_name): | |
| # Try to find string value on a single line | |
| for line in content.splitlines(): | |
| matches = ctx.re.allmatches(r"^\s*" + attr_name + r'\s*=\s*"([^"]*)"', line) | |
| if matches: | |
| return matches[0].groups[1] | |
| matches = ctx.re.allmatches(r"^\s*" + attr_name + r"\s*=\s*'([^']*)'", line) | |
| if matches: | |
| return matches[0].groups[1] | |
| matches = ctx.re.allmatches(r"^\s*" + attr_name + r"\s*=\s*(true|false|True|False)", line) | |
| if matches: | |
| val = matches[0].groups[1] | |
| if val == "True": | |
| return "true" | |
| if val == "False": | |
| return "false" | |
| return val | |
| # Try to extract list value | |
| lines = content.splitlines() | |
| for i in range(len(lines)): | |
| line = lines[i] | |
| if ctx.re.allmatches(r"^\s*" + attr_name + r"\s*=\s*\[", line): | |
| values = [] | |
| matches = ctx.re.allmatches(r'["\']([^"\']+)["\']', line) | |
| for m in matches: | |
| values.append(m.groups[1]) | |
| if "]" in line: | |
| return values | |
| for j in range(i + 1, len(lines)): | |
| next_line = lines[j] | |
| if "]" in next_line: | |
| idx = next_line.find("]") | |
| part = next_line[:idx] | |
| matches = ctx.re.allmatches(r'["\']([^"\']+)["\']', part) | |
| for m in matches: | |
| values.append(m.groups[1]) | |
| return values | |
| else: | |
| matches = ctx.re.allmatches(r'["\']([^"\']+)["\']', next_line) | |
| for m in matches: | |
| values.append(m.groups[1]) | |
| return values | |
| return None | |
| def _map_gn_visibility_to_bazel(item): | |
| if item == "*": | |
| return "//visibility:public" | |
| if item == ":*": | |
| return "//visibility:private" | |
| if item.endswith("/*"): | |
| return item[:-2] + ":__subpackages__" | |
| if item.endswith(":*"): | |
| return item[:-2] + ":__pkg__" | |
| return item | |
| # Checks if the visibility attributes in BUILD.bazel and BUILD.gn are equivalent. | |
| # FIDL libraries under //sdk/fidl/ should have visibility attribute. | |
| def _check_visibility(ctx, path, target, bazel_vis, gn_vis): | |
| if not bazel_vis: | |
| ctx.emit.finding( | |
| message = "visibility attribute missing in BUILD.bazel for target `%s`" % target, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| if not gn_vis: | |
| ctx.emit.finding( | |
| message = "visibility attribute missing in BUILD.gn for target `%s`" % target, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| if bazel_vis and gn_vis: | |
| mapped_gn_visibility = [] | |
| for v in gn_vis: | |
| # bazel2gn's ensureSamePackageVisibility adds ":*" to restricted GN | |
| # visibility lists because same-package visibility is implicit in Bazel. | |
| if v == ":*" and len(gn_vis) > 1 and "//visibility:private" not in bazel_vis: | |
| continue | |
| mapped_gn_visibility.append(_map_gn_visibility_to_bazel(v)) | |
| if sorted(bazel_vis) != sorted(mapped_gn_visibility): | |
| ctx.emit.finding( | |
| message = "Visibility in BUILD.bazel for target '%s' does not match mapped visibility from BUILD.gn.\n" % target + | |
| "BUILD.bazel: %s\n" % bazel_vis + | |
| "BUILD.gn (mapped): %s" % mapped_gn_visibility, | |
| filepath = path, | |
| level = "error", | |
| ) | |
| def _normalize_dep(dep): | |
| if ":" in dep: | |
| idx = dep.find(":") | |
| dir_part = dep[:idx] | |
| target_part = dep[idx + 1:] | |
| dir_idx = dir_part.rfind("/") | |
| dir_leaf = dir_part[dir_idx + 1:] if dir_idx != -1 else dir_part | |
| if target_part == dir_leaf: | |
| return dir_part | |
| return dep | |
| def _extract_bazel_target_list_block(content, list_name): | |
| # Try variable assignment list_name = [ | |
| idx = content.find(list_name + " = [") | |
| if idx != -1: | |
| end_idx = content.find("]", idx) | |
| if end_idx != -1: | |
| return content[idx:end_idx] | |
| return "" | |
| # Files that should not be checked for the reasons specified. | |
| _BAZEL_BUILD_PATHS_TO_IGNORE = [ | |
| # There is no fidl library in this file. | |
| "sdk/fidl/BUILD.bazel", | |
| # These files currently use Bazel SDK macros and thus fail these checks. | |
| # TODO(https://fxbug.dev/493687765): Remove each entry as the use of the Bazel | |
| # SDK is removed from it. | |
| "sdk/fidl/fuchsia.boot/BUILD.bazel", | |
| "sdk/fidl/fuchsia.driver.compat/BUILD.bazel", | |
| "sdk/fidl/fuchsia.hardware.clock.measure/BUILD.bazel", | |
| "sdk/fidl/fuchsia.hardware.power.stats/BUILD.bazel", | |
| "sdk/fidl/fuchsia.hardware.qcom.hvdcpopti/BUILD.bazel", | |
| "sdk/fidl/fuchsia.hardware.sockettunnel/BUILD.bazel", | |
| # TODO(https://fxbug.dev/454449781): Enable this check when bazel2gn supports zither libraries. | |
| "sdk/fidl/zbi/BUILD.bazel", | |
| ] | |
| # The potential target file meets the criteria: | |
| # 1. It is a BUILD.bazel file. | |
| # 2. It is newly added or modified. | |
| # 3. It has a corresponding BUILD.gn file in the same directory. | |
| def _is_target_bazel_build_file(ctx, path, meta): | |
| """Returns True if the file is a newly added or modified FIDL BUILD.bazel file and the BUILD.gn is also in the directory.""" | |
| # TODO(https://fxbug.dev/487883318): Extend the check to FIDL libraries outside //sdk/fidl directory. | |
| if not path.startswith("sdk/fidl/"): | |
| return False | |
| # Only process ADDED or MODIFIED files. | |
| if meta.action not in ["A", "M"]: | |
| return False | |
| # Only process BUILD.bazel files to avoid duplicate checks. | |
| if not path.endswith("BUILD.bazel"): | |
| return False | |
| # Skip ignored BUILD.bazel files. | |
| if path in _BAZEL_BUILD_PATHS_TO_IGNORE: | |
| return False | |
| # Check if corresponding BUILD.gn file exists in the same directory. | |
| gn_path = path.replace("BUILD.bazel", "BUILD.gn") | |
| if not ctx.scm.all_files(glob = gn_path): | |
| return False | |
| return True | |
| def _fidl_gn2bazel_migration_check(ctx): | |
| """Main check for FIDL migration from GN to Bazel.""" | |
| target_files = [] | |
| for path, meta in ctx.scm.affected_files().items(): | |
| if not _is_target_bazel_build_file(ctx, path, meta): | |
| continue | |
| # Get fidl targets and attributes from BUILD.bazel. | |
| # Skip the checks if it's not a fidl library. | |
| orig_bazel_content = ctx.io.read_file(ctx.scm.root + "/" + path) | |
| bazel_content = _remove_comments(str(orig_bazel_content)) | |
| bazel_targets = _extract_bazel_fidl_targets_map(ctx, bazel_content) | |
| if not bazel_targets: | |
| continue | |
| if meta.action == "A": | |
| _check_copyright_year(ctx, path, str(orig_bazel_content)) | |
| # Get fidl targets and attributes from BUILD.gn. | |
| gn_path = path.replace("BUILD.bazel", "BUILD.gn") | |
| gn_content = ctx.io.read_file(ctx.scm.root + "/" + gn_path) | |
| gn_content = _remove_comments(str(gn_content)) | |
| gn_targets = _extract_gn_fidl_targets_map(ctx, gn_content) | |
| # Because the BUILD.bazel has the fidl_library() definition, | |
| # we expect the BUILD.gn to have the fidl() definition. If not, it's an error. | |
| if not gn_targets: | |
| ctx.emit.finding( | |
| message = "BUILD.bazel has fidl_library() but BUILD.gn lacks fidl().", | |
| filepath = path, | |
| level = "error", | |
| ) | |
| continue | |
| target_files.append(struct( | |
| path = path, | |
| bazel_content = bazel_content, | |
| gn_content = gn_content, | |
| bazel_targets = bazel_targets, | |
| gn_targets = gn_targets, | |
| )) | |
| if not target_files: | |
| return | |
| _check_build_files(ctx, target_files) | |
| _check_sdk_fidl_list_inclusion(ctx, target_files) | |
| def register_bazel_migration_fidl_checks(): | |
| shac.register_check(shac.check(_fidl_gn2bazel_migration_check)) |