구성 파일 스키마 API 구현

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

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

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

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

빌드 구성 파일 스키마 API

이 섹션에서는 구성 파일 스키마 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 속성에 추가합니다. 생성된 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"],
    }
    

파서 사용

자바 파서 코드를 사용하려면 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 = read(str);
            for (Hal hal : manifest.getHal()) {
                HalInfor halinfo;
                HalInfo.name = hal.getName();
                HalInfo.format = hal.getFormat();
                HalInfor.optional = hal.getOptional();
                …
            }
        }
        …
    }
    

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

include "hal_manifest.h"

    …
    using namespace hal::manifest

    struct HalInfor {
        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 HalInfor halinfo;
            HalInfo.name = hal.getName();
            HalInfo.format = hal.getFormat();
            HalInfor.optional = hal.getOptional();
            …
        }
        …
    }
    

파서를 사용하도록 제공된 모든 API는 api/current.txt에 있습니다. 일관성을 맞추려면 모든 요소와 속성 이름을 낙타대문자(예: ElementName)로 변환하고 해당하는 변수, 메서드와 클래스 이름으로 사용해야 합니다. 파싱된 루트 요소의 클래스는 read{class-name} 함수를 사용하여 가져올 수 있습니다. 루트 요소가 하나인 경우의 함수 이름은 read입니다. 파싱된 하위 요소나 속성의 값은 get{variable-name} 함수를 사용하여 가져올 수 있습니다.

파서 코드 생성

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

xsdc 도구를 XSD 파일 경로와 패키지에 제공해야 합니다. 패키지는 자바 코드의 패키지 이름, 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