Config File Schema API 구현

Android 플랫폼에는 구성 데이터 저장을 위한 다수의 XML 파일이 포함되어 있습니다(예: 오디오 구성). 대부분의 XML 파일은 vendor 파티션에 있지만 읽기는 system 파티션에서 이루어집니다. 이 경우 XML 파일의 스키마가 두 파티션의 인터페이스로 기능합니다. 따라서 스키마는 명시적으로 지정되어야 하며 이전 버전과의 호환이 가능한 방식으로 진화해야 합니다.

Android 10 이전에는 플랫폼에서 XML 스키마 지정 및 사용을 요구하거나 스키마에서 호환되지 않는 변경사항을 방지하기 위한 메커니즘을 제공하지 않았습니다. Android 10에서는 이러한 메커니즘을 제공하며, 이를 Config File Schema API라고 부릅니다. 이 메커니즘은 xsdc라는 도구와 xsd_config라는 빌드 규칙으로 구성됩니다.

xsdc 도구는 XML 스키마 문서(XSD) 컴파일러입니다. 이 도구는 XML 파일의 스키마를 설명하는 XSD 파일을 파싱하고 자바 및 C++ 코드를 생성합니다. 생성된 코드는 XSD 스키마와 일치하는 XML 파일을 객체의 트리에 파싱합니다. 각 객체는 XML 태그를 모델링합니다. XML 속성은 객체의 필드로 모델링됩니다.

xsd_config 빌드 규칙은 xsdc 도구를 빌드 시스템에 통합합니다. XSD 입력 파일의 경우 빌드 규칙에 의해 자바 및 C++ 라이브러리가 생성됩니다. 라이브러리는 XSD와 일치하는 XML 파일이 판독 및 사용되는 모듈에 연결할 수 있습니다. 빌드 규칙은 systemvendor 파티션에 사용되는 자체 XML 파일에 사용할 수 있습니다.

Config File Schema API 빌드

이 섹션에서는 Config File Schema API를 빌드하는 방법에 대해 설명합니다.

Android.bp에서 xsd_config 빌드 규칙 생성

xsd_config 빌드 규칙은 xsdc 도구로 파서 코드를 생성합니다. xsd_config 빌드 규칙의 package_name 속성은 생성된 자바 코드의 패키지 이름을 결정합니다.

Android.bpxsd_config 빌드 규칙 예시:

xsd_config {
    name: "hal_manifest",
    srcs: ["hal_manifest.xsd"],
    package_name: "hal.manifest",
}

디렉터리 구조 예시:

├── Android.bp
├── api
│   ├── current.txt
│   ├── last_current.txt
│   ├── last_removed.txt
│   └── removed.txt
└── hal_manifest.xsd

빌드 시스템은 생성된 자바 코드를 사용하여 API 목록을 생성한 후 이를 토대로 API를 검사합니다. 이 API 검사는 DroidCore에 추가되며 m -j에서 실행됩니다.

API 목록 파일 생성

API 검사를 진행하려면 소스 코드에 API 목록 파일이 있어야 합니다.

API 목록 파일에는 다음이 포함됩니다.

  • current.txtremoved.txt는 빌드 시간에 생성된 API 파일과 비교하여 API가 변경되었는지 확인합니다.
  • last_current.txtlast_removed.txt는 API 파일과 비교하여 API가 이전 버전과 호환되는지 확인합니다.

API 목록 파일 생성 방법:

  1. 빈 목록 파일을 만듭니다.
  2. 명령어 make update-api를 실행합니다.

생성된 파서 코드 사용

생성된 자바 코드를 사용하려면 :를 자바 srcs 속성의 xsd_config 모듈 이름에 접두사로 추가합니다. 생성된 자바 코드의 패키지는 package_name 속성과 동일합니다.

java_library {
    name: "vintf_test_java",
    srcs: [
        "srcs/**/*.java"
        ":hal_manifest"
    ],
}

생성된 C++ 코드를 사용하려면 xsd_config 모듈 이름을 generated_sourcesgenerated_headers 속성에 추가합니다. 그런 다음 libxml2가 생성된 파서 코드에 필요하므로 libxml2static_libs 또는 shared_libs에 추가합니다. 생성된 C++ 코드의 네임스페이스는 package_name 속성과 동일합니다. 예를 들어 xsd_config 모듈 이름이 hal.manifest인 경우 네임스페이스는 hal::manifest입니다.

cc_library{
    name: "vintf_test_cpp",
    srcs: ["main.cpp"],
    generated_sources: ["hal_manifest"],
    generated_headers: ["hal_manifest"],
    shared_libs: ["libxml2"],
}

파서 사용

자바 파서 코드를 사용하려면 XmlParser#read 또는 read{class-name} 메서드를 사용하여 루트 요소의 클래스를 반환해야 합니다. 파싱은 이 시점에 발생합니다.

import hal.manifest.*;

…

class HalInfo {
    public String name;
    public String format;
    public String optional;
    …
}

void readHalManifestFromXml(File file) {
    …
    try (InputStream str = new BufferedInputStream(new FileInputStream(file))) {
        Manifest manifest = XmlParser.read(str);
        for (Hal hal : manifest.getHal()) {
            HalInfo halinfo;
            HalInfo.name = hal.getName();
            HalInfo.format = hal.getFormat();
            HalInfo.optional = hal.getOptional();
            …
        }
    }
    …
}

C++ 파서 코드를 사용하려면 먼저 헤더 파일을 포함해야 합니다. 헤더 파일의 이름은 마침표(.)가 밑줄(_)로 변환된 패키지 이름입니다. 그런 다음 read 또는 read{class-name} 메서드를 사용하여 루트 요소의 클래스를 반환합니다. 파싱은 이 시점에 발생합니다. 반환 값은 std::optional<>입니다.

include "hal_manifest.h"

…
using namespace hal::manifest

struct HalInfo {
    public std::string name;
    public std::string format;
    public std::string optional;
    …
};

void readHalManifestFromXml(std::string file_name) {
    …
    Manifest manifest = *read(file_name.c_str());
    for (Hal hal : manifest.getHal()) {
        struct HalInfo halinfo;
        HalInfo.name = hal.getName();
        HalInfo.format = hal.getFormat();
        HalInfo.optional = hal.getOptional();
        …
    }
    …
}

파서를 사용하도록 제공된 모든 API는 api/current.txt에 있습니다. 일관성을 위해 모든 요소와 속성 이름은 카멜 표기법(예: ElementName)으로 변환되며 해당하는 변수, 메서드와 클래스 이름으로 사용됩니다. 파싱된 루트 요소의 클래스는 read{class-name} 함수를 사용하여 가져올 수 있습니다. 루트 요소가 하나인 경우의 함수 이름은 read입니다. 파싱된 하위 요소나 속성의 값은 get{variable-name} 함수를 사용하여 가져올 수 있습니다.

파서 코드 생성

대부분의 경우 xsdc를 직접 실행할 필요는 없습니다. Android.bp에서 xsd_config 빌드 규칙 구성에 설명된 것처럼 xsd_config 빌드 규칙을 대신 사용하세요. 이 섹션에서는 완전성을 기하기 위해 xsdc 명령줄 인터페이스에 대해 설명합니다. 이는 디버깅에 유용할 수 있습니다.

XSD 파일 경로와 패키지를 xsdc 도구에 제공해야 합니다. 패키지는 자바 코드의 패키지 이름, C++ 코드의 네임스페이스입니다. 생성된 코드가 자바인지 C인지 파악하기 위한 옵션은 각각 -j 또는 -c입니다. -o 옵션은 출력 디렉터리의 경로입니다.

usage: xsdc path/to/xsd_file.xsd [-c] [-j] [-o <arg>] [-p]
 -c,--cpp           Generate C++ code.
 -j,--java          Generate Java code.
 -o,--outDir <arg>  Out Directory
 -p,--package       Package name of the generated java file. file name of
                    generated C++ file and header

명령어 예:

$ xsdc audio_policy_configuration.xsd -p audio.policy -j