Yocto · Bài 5/9

Bài 5: Package dependencies và splitting trong Yocto

2 lượt đọc5 phút đọc

Khi xây dựng hệ thống nhúng, hai việc rất hay bị bỏ qua là quản lý phần phụ thuộc giữa các gói và chia nhỏ gói. Làm đúng, image của bạn đủ thứ cần chạy mà không thừa thứ nào. Bài này giải thích hai khái niệm đó bằng một ví dụ nhỏ trên nhánh scarthgap.

1.Phụ thuộc giữa các gói (package dependencies)

Một gói muốn chạy được thường cần gói khác: thư viện, công cụ, chương trình đi kèm. Yocto chia sự phụ thuộc thành hai loại:

  • Phụ thuộc lúc build (build time): cần có khi biên dịch gói, khai báo bằng biến DEPENDS.
  • Phụ thuộc lúc chạy (run time): cần có trên thiết bị khi gói đang chạy, khai báo bằng biến RDEPENDS.

Ví dụ chuẩn bị

Trong layer meta-helloworld, tạo recipe hello-pack-depend-split với 3 chương trình dùng thư viện libcurl:

bash
meta-helloworld/recipes-example/hello-pack-depend-split/
├── files
│   ├── hello_depen.c
│   ├── hello_pack_split.c
│   ├── hello_run_time_depen.c
│   └── Makefile
└── hello-pack-depend-split.bb

Hai chương trình đầu có cùng nội dung, chỉ khác tên. Mã mẫu của hello_depen.c:

c
#include <stdio.h>
#include <curl/curl.h>

int main(void)
{
    CURL *curl = curl_easy_init();
    if (curl) {
        printf("Init curl OK\n");
        printf("In file %s\n", __FILE__);
        curl_easy_cleanup(curl);
    }
    return 0;
}

Chương trình thứ ba, hello_run_time_depen.c, giống như trên nhưng có thêm đoạn gọi bash lúc chạy:

c
#include <stdio.h>
#include <stdlib.h>
#include <curl/curl.h>

int main(void)
{
    CURL *curl = curl_easy_init();
    if (curl) {
        printf("Init curl OK\n");
        printf("In file %s\n", __FILE__);
        curl_easy_cleanup(curl);
    }
    int status = system("bash -c 'echo This is executed by bash at runtime!'");
    if (status == -1) {
        perror("Failed to run bash command");
        return 1;
    }
    return 0;
}

Makefile:

bash
CC = gcc
CFLAGS = -Wall

all: hello_depen hello_pack_split hello_run_time_depen

hello_depen: hello_depen.c
	$(CC) hello_depen.c $(CFLAGS) -lcurl -o hello_depen

hello_pack_split: hello_pack_split.c
	$(CC) hello_pack_split.c $(CFLAGS) -lcurl -o hello_pack_split

hello_run_time_depen: hello_run_time_depen.c
	$(CC) hello_run_time_depen.c $(CFLAGS) -lcurl -o hello_run_time_depen

clean:
	rm -f hello_depen hello_pack_split hello_run_time_depen

Recipe lúc đầu, chưa khai báo phụ thuộc:

bash
DESCRIPTION = "Simple program to simulate package dependencies and splitting"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

S = "${WORKDIR}"
SRC_URI = file://hello_depen.c \
           file://hello_pack_split.c \
           file://hello_run_time_depen.c \
           file://Makefile

EXTRA_OEMAKE = "CC='${CC}' CFLAGS='${CFLAGS}'"

do_compile() {
    oe_runmake
}

do_install() {
    install -d ${D}${bindir}
    install -m 0755 hello_depen ${D}${bindir}/hello_depen
    install -m 0755 hello_pack_split ${D}${bindir}/hello_pack_split
    install -m 0755 hello_run_time_depen ${D}${bindir}/hello_run_time_depen
}

1.1. Phụ thuộc lúc build: DEPENDS

Thử build:

bash
bitbake hello-pack-depend-split

Bạn sẽ gặp lỗi:

bash
fatal error: curl/curl.h: No such file or directory

Lý do: mã dùng libcurl, nhưng Yocto chưa biết cần chuẩn bị thư viện đó trước bước do_compile. Cách sửa là khai báo trong recipe:

bash
DEPENDS = "curl"

Build lại sẽ qua. Khi có DEPENDS, BitBake build curl trước và đặt tệp header, thư viện của nó vào thư mục sysroot dành cho gói của bạn.

Bạn không cần khai báo thêm phụ thuộc lúc chạy cho libcurl. Yocto tự phát hiện những thư viện dùng chung mà chương trình liên kết và thêm vào phụ thuộc lúc chạy của gói.

1.2. Phụ thuộc lúc chạy: RDEPENDS

Cài image và chạy chương trình thứ ba trong QEMU:

bash
root@qemux86-64:~# /usr/bin/hello_run_time_depen
Init curl OK
In file hello_run_time_depen.c
sh: bash: not found

Thông báo bash: not found cho thấy chương trình gọi bash nhưng image không có bash. Yocto không thể tự đoán lệnh nào được gọi qua system(), nên bạn phải khai báo bằng tay:

bash
RDEPENDS:${PN} = "bash"

Từ bản Honister, cú pháp là RDEPENDS:${PN} (dấu hai chấm). Cú pháp cũ RDEPENDS_${PN} không còn đúng trên scarthgap. Build lại image, kết quả:

bash
root@qemux86-64:~# /usr/bin/hello_run_time_depen
Init curl OK
In file hello_run_time_depen.c
This is executed by bash at runtime!

Nhớ rằng DEPENDS là chuyện của máy build, còn RDEPENDS là chuyện của thiết bị. Nhầm hai biến này là lỗi rất phổ biến.

2.Chia nhỏ gói (package splitting)

Ở ví dụ trên, cả ba chương trình đều được cài vào image. Trong thực tế đôi khi bạn chỉ cần một phần. Chia gói cho phép tách một recipe thành nhiều gói con để cài chọn lọc. Lợi ích: image nhỏ hơn, dễ cập nhật từng phần, dễ quản lý.

Phân tích đầu ra để chia gói và xác định quan hệ giữa các gói

Dùng hai biến:

  • PACKAGES: danh sách các gói mà recipe tạo ra.
  • FILES:<ten-goi>: các tệp thuộc về gói đó.

Recipe sau khi chia gói (đặt tên gói bằng dấu gạch ngang, vì tên gói trong Yocto không nên chứa dấu gạch dưới):

bash
DESCRIPTION = "Simple program to simulate package dependencies and splitting"
LICENSE = "MIT"
LIC_FILES_CHKSUM = "file://${COMMON_LICENSE_DIR}/MIT;md5=0835ade698e0bcf8506ecda2f7b4f302"

S = "${WORKDIR}"
SRC_URI = file://hello_depen.c \
           file://hello_pack_split.c \
           file://hello_run_time_depen.c \
           file://Makefile

DEPENDS = "curl"

EXTRA_OEMAKE = "CC='${CC}' CFLAGS='${CFLAGS}'"

PACKAGES =+ "hello-depen hello-pack-split hello-run-time"

FILES:hello-depen = "${bindir}/hello_depen"
FILES:hello-pack-split = "${bindir}/hello_pack_split"
FILES:hello-run-time = "${bindir}/hello_run_time_depen"

RDEPENDS:hello-run-time = "bash"

do_compile() {
    oe_runmake
}

do_install() {
    install -d ${D}${bindir}
    install -m 0755 hello_depen ${D}${bindir}/hello_depen
    install -m 0755 hello_pack_split ${D}${bindir}/hello_pack_split
    install -m 0755 hello_run_time_depen ${D}${bindir}/hello_run_time_depen
}

Có hai điểm đáng chú ý:

  • Toán tử =+ đặt các gói mới lên đầu danh sách PACKAGES, trước gói chính ${PN}. Nhờ vậy các tệp được gán cho gói con trước, không bị gói chính "nuốt" hết.
  • RDEPENDS giờ gắn riêng cho gói hello-run-time, vì chỉ gói này cần bash.

Cài chọn lọc vào image

Trong local.conf, thay vì cài cả recipe, chỉ cài gói con mong muốn:

bash
IMAGE_INSTALL:append = " hello-run-time"

Sau khi build image và chạy QEMU:

bash
root@qemux86-64:~# ls -l /usr/bin/hello*
-rwxr-xr-x 1 root root 14456 /usr/bin/hello_run_time_depen
root@qemux86-64:~# /usr/bin/hello_run_time_depen
Init curl OK
In file hello_run_time_depen.c
This is executed by bash at runtime!

Trên thiết bị chỉ còn đúng chương trình bạn chọn, hai chương trình còn lại không được cài.

3.Kết luận

  • DEPENDS khai báo thứ cần cho lúc build, RDEPENDS khai báo thứ cần cho lúc chạy.
  • PACKAGES và FILES giúp chia một recipe thành nhiều gói con để cài chọn lọc.
  • Với bản scarthgap, nhớ dùng cú pháp mới có dấu hai chấm: RDEPENDS:, FILES:, :append.

Bài tiếp theo sẽ nói về cách tạo SDK, bộ công cụ giúp bạn biên dịch chéo ứng dụng ngay trên máy host.