R010: partname/subsystemname不匹配

SkillDev tools

A skill for dev tools by openharmonyinsight.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the R010: partname/subsystemname不匹配 skill

What this skill tells your AI

The instructions your AI receives, as published by openharmonyinsight/openharmony-skills in skills/check-test-code-quality/rules/R010/SKILL.md and read by ahel’s review.

规则信息

属性
规则编号R010
问题类型part_name/subsystem_name不匹配
严重级别Critical
规则复杂度complex
扫描范围BUILD.gn files only
testcase字段-(BUILD.gn为非测试文件,无对应it()块)

问题描述

part_name/subsystem_name不匹配。BUILD.gn中声明的part_name必须在对应subsystem_name的components列表中存在。

修复建议

使用完整的子系统-组件映射表,确保part_name在对应subsystem的components中。

映射表数据源

映射表从以下三个配置文件构建(合并所有 subsystem → component 关系,共54个子系统、332个部件):

配置文件主URL(推荐)备用URL
vendor_hihope config.jsonhttps://gitee.com/openharmony/vendor_hihope/raw/master/rk3568/config.jsonhttps://gitcode.com/openharmony/vendor_hihope/raw/master/rk3568/config.json
productdefine rich.jsonhttps://gitee.com/openharmony/productdefine_common/raw/master/inherit/rich.jsonhttps://gitcode.com/openharmony/productdefine_common/raw/master/inherit/rich.json
productdefine chipset_common.jsonhttps://gitee.com/openharmony/productdefine_common/raw/master/inherit/chipset_common.jsonhttps://gitcode.com/openharmony/productdefine_common/raw/master/inherit/chipset_common.json

重要: 优先使用gitee.com URL,gitcode.com可能需要认证。如果所有URL均不可达,必须在终端输出明确警告,不可静默返回0个问题。

本地缓存: 获取成功后可将映射表缓存到本地文件(如/tmp/r010_mapping.json),下次扫描优先读取缓存,避免每次都请求远程。

扫描逻辑

Step 1: 收集文件

扫描目标目录下所有名为BUILD.gn的文件。

import os

def find_build_gn_files(scan_root):
    build_gn_files = []
    for dirpath, dirnames, filenames in os.walk(scan_root):
        if 'BUILD.gn' in filenames:
            build_gn_files.append(os.path.join(dirpath, 'BUILD.gn'))
    return build_gn_files

Step 2: 解析BUILD.gn提取part_name和subsystem_name

对每个BUILD.gn文件,使用正则表达式提取part_namesubsystem_name字段。

import re

def extract_build_gn_fields(content):
    part_name = None
    subsystem_name = None

    part_match = re.search(r'part_name\s*=\s*["\']([^"\']+)["\']', content)
    if part_match:
        part_name = part_match.group(1)

    subsystem_match = re.search(r'subsystem_name\s*=\s*["\']([^"\']+)["\']', content)
    if subsystem_match:
        subsystem_name = subsystem_match.group(1)

    return part_name, subsystem_name

Step 3: 构建子系统-组件映射表

从三个配置文件中读取并合并映射关系。每个配置文件的结构包含子系统定义,每个子系统下有components列表。

⚠️ 数据格式陷阱: components数组中的元素不是纯字符串,而是对象{"component": "xxx", "features": [...]}。直接用set().update(components)会抛unhashable type: 'dict'异常,导致映射表构建失败、R010静默返回0个问题。必须从每个对象中提取component字段。

import json

MAPPING_URLS = [
    'https://gitee.com/openharmony/vendor_hihope/raw/master/rk3568/config.json',
    'https://gitee.com/openharmony/productdefine_common/raw/master/inherit/rich.json',
    'https://gitee.com/openharmony/productdefine_common/raw/master/inherit/chipset_common.json',
]
CACHE_FILE = '/tmp/r010_mapping.json'

def _parse_config(data, subsystem_map):
    subsystems = data.get('subsystems', [])
    for subsys in subsystems:
        name = subsys.get('subsystem', '')
        components = subsys.get('components', [])
        if name not in subsystem_map:
            subsystem_map[name] = set()
        for c in components:
            if isinstance(c, str):
                subsystem_map[name].add(c)
            elif isinstance(c, dict):
                subsystem_map[name].add(c.get('component', ''))

def load_subsystem_mapping():
    # 优先读取本地缓存
    import os
    if os.path.exists(CACHE_FILE):
        with open(CACHE_FILE, 'r', encoding='utf-8') as f:
            return {k: set(v) for k, v in json.load(f).items()}

    # 从远程获取
    import urllib.request
    subsystem_map = {}
    for url in MAPPING_URLS:
        try:
            req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
            with urllib.request.urlopen(req, timeout=30) as resp:
                data = json.loads(resp.read().decode('utf-8'))
            _parse_config(data, subsystem_map)
        except Exception as e:
            print(f"  警告: 获取映射表失败 {url}: {e}", file=sys.stderr, flush=True)

    if not subsystem_map:
        print("  警告: R010映射表为空,所有BUILD.gn将被跳过!", file=sys.stderr, flush=True)

    # 写入缓存
    with open(CACHE_FILE, 'w', encoding='utf-8') as f:
        json.dump({k: sorted(v) for k, v in subsystem_map.items()}, f, ensure_ascii=False)

    return subsystem_map

Step 4: 验证匹配关系

对每个BUILD.gn,检查提取出的part_name是否在subsystem_name对应的components集合中。

def validate_part_subsystem(part_name, subsystem_name, subsystem_map):
    if not part_name or not subsystem_name:
        return True

    if subsystem_name not in subsystem_map:
        return True

    return part_name in subsystem_map[subsystem_name]

Step 5: 生成问题报告

def scan_r010(build_gn_files, base_dir):
    issues = []
    subsystem_map = load_subsystem_mapping()
    if not subsystem_map:
        return issues

    for file_path in build_gn_files:
        with open(file_path, 'r', encoding='utf-8', errors='ignore') as f:
            content = f.read()

        part_name, subsystem_name = extract_build_gn_fields(content)

        if not part_name or not subsystem_name:
            continue

        if not validate_part_subsystem(part_name, subsystem_name, subsystem_map):
            rel_path = os.path.relpath(file_path, base_dir)

            part_line = 0
            for i, line in enumerate(content.splitlines(), 1):
                if re.search(r'part_name\s*=\s*["\']' + re.escape(part_name) + '["\']', line):
                    part_line = i
                    break

            available_parts = sorted(subsystem_map.get(subsystem_name, set()))

            issues.append({
                'rule': 'R010',
                'type': 'part_name/subsystem_name不匹配',
                'severity': 'Critical',
                'file': rel_path,
                'line': part_line,
                'testcase': '-',
                'snippet': f'part_name = "{part_name}", subsystem_name = "{subsystem_name}"',
                'suggestion': (
                    f'part_name "{part_name}" 不在 subsystem_name "{subsystem_name}" 的components中。'
                    f'可选的part_name: {available_parts}'
                ),
            })

    return issues

错误示例

# BUILD.gn - 错误:invalid_part不在arkui的components中
ohos_js_app_suite("ActsTest") {
  testonly = true
  part_name = "invalid_part"       # ✗ 错误
  subsystem_name = "arkui"
  hap_name = "ActsTest"
}

正确示例

# BUILD.gn - 正确:ace_engine在arkui的components中
ohos_js_app_suite("test") {
  testonly = true
  part_name = "ace_engine"         # ✓ 正确
  subsystem_name = "arkui"
  hap_name = "ActsTest"
}

注意事项

  1. 如果subsystem_name本身不在映射表中,跳过验证(可能是新增子系统)
  2. 映射表需从三个配置文件合并,任何一个文件中出现的映射关系都有效
  3. BUILD.gn文件中可能存在多个target,每个target都需要检查
  4. 不检查group类型的BUILD.gn(group类型通常不包含part_name/subsystem_name)
  5. 如果BUILD.gn文件中缺少part_name或subsystem_name字段,跳过该文件
  6. 映射表获取失败时必须告警: 如果远程URL不可达且本地无缓存,必须在终端输出明确警告,不可静默返回空结果

陷阱与注意事项

陷阱1: components是对象数组而非字符串数组

严重性: 严重,导致unhashable type: 'dict'异常,映射表构建失败。

三个配置文件中components字段的实际数据格式为对象数组,不是纯字符串数组:

{
  "subsystem": "arkui",
  "components": [
    {"component": "ace_engine", "features": []},
    {"component": "napi", "features": []}
  ]
}

错误做法: 直接set().update(components) → 抛异常 正确做法: 逐个判断类型,对象取component字段:

for c in components:
    if isinstance(c, str):
        mapping[name].add(c)
    elif isinstance(c, dict):
        mapping[name].add(c.get('component', ''))

陷阱2: gitcode.com URL需要认证

gitcode.com的raw文件URL需要认证才能访问,直接请求会返回HTML登录页而非JSON。必须优先使用gitee.com URL。

陷阱3: 映射表为空时静默返回0

如果映射表获取失败(网络不通、认证失败等),映射表为空,所有subsystem_name都不在映射表中,全部被跳过,R010返回0个问题且不报任何错误。必须在映射表为空时输出明确警告。

输出格式

每条issue的字段:

字段
ruleR010
typepart_name/subsystem_name不匹配
severityCritical
file相对路径(如ability/xxx/BUILD.gn
linepart_name所在行号
testcase-
snippetpart_name = "xxx", subsystem_name = "yyy"
suggestion问题描述 + 可选的part_name列表

技术挑战与解决方案

维护完整的子系统-组件映射表

挑战: 维护完整的子系统-组件映射表

解决方案:

SUBSYSTEM_PARTS_MAP = {
    "account": ["os_account"],
    "arkui": ["ace_engine", "napi", "ui_appearance", "ui_lite"],
    "bundle": ["bundle_framework"],
    "communication": ["bluetooth", "wifi", "netstack", "dsoftbus"],
    # ... 共50+个子系统
}

Signals

GitHub stars
34
Forks
7
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
r010
Source
github.com/openharmonyinsight/openharmony-skills