Yocto 에서 NPM 기반의 Javascript 패키지 관리

Yocto에서 Go 프로젝트 관리에 추가하여 JavaScript 기반의 프로그램을 Yocto의 패키지로 관리하는 방법을 정리해 본다.

관련 사항은 Yocto Wiki 의 NPM 기반 패키지 관리 방법에 간략하게 설명되어 있다.

Javascript 기반의 프로젝트도 Go 언어와 마찬가지로 패키지 관련한 문제가 있지만 이 부분은 어느정도 툴을 이용하여 해결된 상태이다.

임베디드 환경에서 Javascript NPM 를 사용하는 경우를 크게 보면 다음 두 경우가 있을 수 있다.

  • Node.js 기반의 프로젝트
  • Webpack/React와 같은 static page 생성

Node.js와 같은 프로젝트는 빌드 시 nodejs-native 도 필요하지만 target에서 동작하는 nodejs도 필요하다. 하지만 webpack과 같은 경우에는 nodejs-native만 있어서 configure, compile task에서 이를 이용하여 페이지를 생성하면 되고, 별도로 target에 nodejs를 설치할 필요가 없다.

둘은 recipe를 만드는 방법이 차이가 있어 이 둘을 구분하여 설명한다.

위 TipsAndTricks/NPM 문서를 보면 devtool 을 이용하여 초기에 bitbake recipe를 생성한다. 이 devtool 이 만들어진 배경과 용도에 대해서 우선 간략히 설명한다.

Yocto 의 초기에는 SDK 빌드만 제공하였다. SDK 자체는 이해 하기가 명확하다.

간단하게 말하면 Yocto 기반의 rootfs image 위에서 개발하는 사람들을 위한 개발환경 이라고 할 수 있다. Embedded linux system에 대해서만 알고 있으면 SDK를 이용하여 필요한 C, C++ 프로그램을 타겟용으로 빌드하여 실행 시키는 용도로, 해당 개발자는 Yocto 에 대해서 몰라도 된다.

우리가 보통 말하는 다음과 같은 툴체인을 빌드하는 것이라고 생각하면 된다.

SDK 에는 다음과 같은 것들이 포함되어 있다.

  • Cross-toolchain
  • Header & libraries
  • Debugging & tracing tools
  • System utilities
  • Package configuration tools
  • Documentation

이들을 포함하여 설치 가능한 install script로 만들어 주는 것이 Yocto SDK 이다.

빌드 방법은 아래 처럼 사용하는 이미지에 populate_sdk task로 빌드할 수 있다.

1
$ bitbake core-image-minimal -c populate_sdk

반면에 eSDK(Extensible Software Development Kit)는 C, C++ 프로그램 개발자가 아니라, Yocto package 개발자를 위한 것이라고 볼 수 있다.

소규모 프로젝트라면 한 명의 Yocto 관리자가 있고, 나머지 개발자들은 SDK를 이용하여 프로그램을 개발 검증하여 repository에 넣어주면, yocto 관리자가 이를 위한 yocto 패키지를 업데이트하면 프로젝트 운영이 가능할 수 있다.

하지만 yocto 로 개발하는 부분이 더 큰 규모의 프로젝트라면 yocto 패키지를 여러명이 개발 하여야 할 수 있다. eSDK는 이를 위한 개발 환경이라고 보면 된다. 다수의 yocto 개발자가 원격 서버를 통하여 yocto 빌드를 관리하고, 로컬에서는 shared state(sstate) cache를 이용하여 사전에 빌드된 것은 패키지 관리자처럼 별도의 빌드없이 바로 사용 가능하고, 원하는 recipe를 편리하게 추가나 수정할 수 있는 기능을 제공한다.

빌드 방법은 SDK와 유사하지만 실제 사용방법은 그리 쉽지는 않다.

1
$ bitbake core-image-minimal -c populate_sdk_ext

eSDK는 2016년에 릴리즈된 Yocto Krogoth (2.1) 부터 정식으로 포함되었고, 여기에 포함된 툴 중의 하나가 devtool 이다.

Devtool은 eSDK 환경 만이 아니라 yocto bitbake 빌드환경이면 기본 설치되어 있어, 일반 용도로 사용도 가능하다.

Devtool은 기본 동작은 다음과 같다.

  • build/workspace 로 임시 작업 layer를 만들어서, 이를 build/conf/bblayer.conf에 추가한다.
  • devtool add, devtool modify 와 같은 명령을 하면 이 workspace 안에 .bb 또는 .bbappend 파일을 만들고 관련된 소스들도 이곳에 설치하여 개발을 할 수 있도록 한다.
  • 이때 cmake 처럼 표준화된 소스는 자동으로 recipe의 내용도 만들어 준다.

Git 에 익숙한 사용자라면 git workspace 개념과 유사하다고 보면 된다. 수정은 workspace에서 하고, 최종적으로 작성이 완료되면 devtool finish를 이용하여 기존의 bitbake layer(local repository)로 commit 하는 구조라고 이해하면 된다.

NPM 기반의 소스코드도 devtool 에서 인식하여 recipe를 만들어 주어, 이를 이용하면 NPM 패키지를 쉽게 생성할 수 있다.

Node.js 기반의 프로젝트는 위에서 설명한 devtool을 활용하는 것이 좋다. 설치 절차는 TipsAndTricks/NPM - Yocto Project 와 같은 동일한 예제로 설명한다.

초기에 recipe를 생성하는 것은 devtool을 이용하여 다음과 같이 실행한다.

1
2
3
4
5
6
7
8
9
$ devtool add https://github.com/martinaglv/cute-files.git
NOTE: No setscene tasks
NOTE: Executing Tasks
NOTE: Tasks Summary: Attempted 2 tasks of which 0 didn't need to be rerun and all succeeded.
NOTE: Checking if npm is available ...
NOTE: Generating shrinkwrap file ...
NOTE: Fetching npm dependencies ...
NOTE: Handling licenses ...
...

실행이 성공하면 build/workspace 안에 파일들이 생성되고, build/conf/bblayer.conf 에도 workspace 가 추가된다.

Workspace의 파일 구성을 보면 다음과 같다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
workspace
├── README
├── appends
│   └── cute-files_git.bbappend
├── conf
│   └── layer.conf
├── recipes
│   └── cute-files
│       ├── cute-files
│       │   └── npm-shrinkwrap.json
│       └── cute-files_git.bb
└── sources
    └── cute-files
        ├── LICENSE
        ...

recipes/cute-files/cute-files_git.bb를 보면 다음과 같다. 내용을 이해하기 위하여 불필요한 줄은 삭제하였다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
SUMMARY = "Turn any folder on your computer into a cute file browser, available on the local network."
LICENSE = "MIT & Unknown & ISC"
LIC_FILES_CHKSUM = "file://LICENSE;md5=71d98c0a1db42956787b1909c74a86ca \
                    file://node_modules/has/LICENSE-MIT;md5=d000afc3c9ff3501a5610197db76a246 \
                    ...
                    file://node_modules/utils-merge/package.json;md5=0230ade39b9c19f5fcc29ed02dff4afe \
                    file://node_modules/vary/package.json;md5=3577fc17c1b964af7cfe2c17c73f84f3"

SRC_URI = " \
    git://github.com/martinaglv/cute-files.git;protocol=https;branch=master \
    npmsw://${THISDIR}/${BPN}/npm-shrinkwrap.json \
    "
PV = "1.0.2+git${SRCPV}"
SRCREV = "98fe76448b8367adf206de6809b4adb7189b05ee"

S = "${WORKDIR}/git"

inherit npm

LICENSE:${PN} = "MIT"
LICENSE:${PN}-accepts = "MIT"
...
LICENSE:${PN}-utils-merge = "MIT"
LICENSE:${PN}-vary = "MIT"

위 파일을 보면 다음과 같은 사항을 확인할 수 있다.

  • 버전 변경 문제를 해결하기 위하여 npm-shrinkwrap 을 이용하여 의존성있는 패키지의 버전을 고정시킨다. 파일은 recipes/cute-files/cute-files/npm-shrinkwrap.json 으로 생성된다.
  • 패키지의 라이센스도 자동으로 확인하여 패키지별로 명시된다.

이렇게 되면 Reproducible Builds 를 보장할 수 있고, 라이센스도 관리할 수 있다.

appends/cute-files_git.append 를 보면 externalsrc를 이용하여 로컬 소스로 빌드가 가능하도록 설정되어 있다. Externalsrc 에 대해서는 이전에 정리한 Yocto Project 개발하기(3) - 개발 시 로컬 패키지 관리하기를 참고할 수 있다.

1
2
3
4
5
6
7
8
9
inherit externalsrc
EXTERNALSRC = "/home/yslee/project/yocto-project/build/workspace/sources/cute-files"
EXTERNALSRC_BUILD = "/home/yslee/project/yocto-project/build/workspace/sources/cute-files"

# initial_rev: 98fe76448b8367adf206de6809b4adb7189b05ee
python do_configure:append() {
    pkgdir = d.getVar("NPM_PACKAGE")
    lockfile = os.path.join(pkgdir, "singletask.lock")
    bb.utils.remove(lockfile)

패키지 수정이 모두 완료 된 후에는 아래와 같이 필요한 layer로 옮길수 있다.

1
$ devtool finish cute-files ../meta-my-project/recipes-core 

위와 같이 devtool을 이용하여 생성된 recipe는 npm.bbclass를 상속받아서 동작한다. 이 부분의 절차가 복잡하기는 한데, 간단하게 설명하면 다음과 같다.

  • configure: 작업 파일에 npm-package로 패키지를 설치하고, npm package manager의 역할과 유사하게 npm-cache에는 의존성 있는 모든 패키지를 받아온다.
  • compile: npm install 과정을 수행한다. 이때 의존성 있는 패키지는 npm-cache 폴더를 참조하여 설치한다. 최종 결과물은 npm-build에 생성된다.
  • install: npm-build 결과물을 기반으로 설치 파일 추출
1
2
$ ls tmp/work/cortexa7t2hf-neon-poky-linux-gnueabi/cute-files/1.0.2+git999-r0
npm-build  npm-cache  npm-package  recipe-sysroot  recipe-sysroot-native  temp

만일 devtool로 정상적으로 인식되지 않는 repository 라면 위 생성된 것을 참조하여 유사하게 recipe 를 직접 만들어야 한다.

이들 프로젝트에 대해서는 npm.bbclass 을 사용하기에는 오히려 더 복잡해진다. 이 경우에는 아래처럼 간단하게 recipe를 만들어 사용할 수 있다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
SRC_URI = " \
    git://git@gitlab.com/humminglab/test-react-frontend.git;protocol=ssh;branch=main \
    "
SRCREV = "${AUTOREV}"

S = "${WORKDIR}/git"

DEPENDS = "nodejs-native"

NPM_NODEDIR ?= "${RECIPE_SYSROOT_NATIVE}${prefix_native}"

do_configure() {
    ${NPM_NODEDIR}/bin/npm install
}

do_compile() {
    ${NPM_NODEDIR}/bin/npm run build
}

do_install() {
    install -d ${D}${datadir}/www-pages
    cp -r ${S}/build/* ${D}${datadir}/www-pages
}

NPM과는 다음과 같은 차이가 있다.

  • 의존성은 nodejs-native만 명시
  • configure: 네트워크 접속이 가능하므로 npm install 로 webpack/react 등의 관련된 development package 설치
  • compile: npm run build로 static 결과물 생성
  • install: 생성된 파일들 추리기

위와 같이 하면 target 의 /usr/share/www-pages에 생성된 파일들이 설치된다.

Javascript 는 Yocto에서 어느정도 잘 관리되는 상태로 보여진다. Go, Rust 등의 다른 패키지 관리자가 포함된 언어들도 Yocto 프로젝트가 버전업 되면서 이처럼 정리될 것으로 기대된다.