Distribution::Resource — 배포판과 함께 설치된 리소스 하나하나

Distribution::Resource — 배포판과 함께 설치된 리소스 하나하나

모듈을 배포하다 보면 실행 파일은 물론이고, libraries/ 같은 공유 라이브러리나 data/ 같은 데이터 파일까지 함께 설치해야 할 때가 있어요. 배포판과 함께 설치되는 각각의 리소스 하나하나를 나타내는 타입이 Distribution::Resource예요.

출처: Raku Documentation — Distribution::Resource

본문

class Distribution::Resource { }

Distribution::Resource%?RESOURCES 동적 변수의 값으로 반환되는 개별 리소스(파일이나 라이브러리) 각각을 나타내요. 참고로 %?RESOURCES 자체는 Distribution::Resources의 인스턴스예요. 이런 리소스들은 표준 배포판 설치 과정의 일부로 설치되며, %?RESOURCES의 정의에서 더 많은 맥락을 확인할 수 있어요.

겉으로 보기엔 이 리소스들은 IO::Path처럼 동작하고 많은 메서드를 공유하지만, 사실은 진짜 IO::Path가 아니에요. 그래서 IO::Path에 스마트매치할 수는 없어요.

이 변수는 현재의 repository 체인 구조와 함께 동작하며, 리소스가 설치되어 있든 아니든 그 리소스에 도달하는 올바른 방법을 알려줘요. 다만 이런 값들이 구현마다 일관성을 유지한다고 믿으면 안 돼요. 어쨌든 핸들을 통해서는 리소스에 접근할 수 있어요. 예를 들어 이 코드에서

unit class Resourceable;

method gimme(::?CLASS:U: ) {
    %?RESOURCES;
}

META6.json과 함께 쓰면

{
  "provides": {
    "Resourceable": "lib/Resourceable.rakumod"
  },
  "license": "Artistic-2.0",
  "description": "Testing how Distribution::Resource(s) work",
  "perl": "6.*",
  "auth": "zef:jj",
  "version": "1.2",
  "resources": [
    "libraries/whatever",
    "data/swim.csv"
  ],
  "meta-version": "0",
  "name": "Resourceable"
}

두 종류의 리소스가 있다는 걸 볼 수 있어요. 일반 리소스와, libraries/로 시작하는 리소스죠. 후자의 실제 값(그리고 핸들)은 운영체제에 따라 달라져요. 예를 들어 Windows에서는 .dll, Linux에서는 .so, macOS에서는 .dylib을 찾을 수 있어요.

bin/에 둔 이 스크립트로 접근해 보면

use Resourceable;

for <libraries/whatever data/swim.csv> -> $resource {
    with Resourceable.gimme{$resource} {
       .say;
       say "-" x 10, ">";
       ( .repo-name, .repo, .dist-id, .key )».say;
    }
}

소스 디렉터리에서 직접 실행하면 이렇게 나와요.

# raku -Ilib bin/show-resources.raku
"/home/jmerelo/progs/raku/my-raku-examples/test-resources/resources/libraries/libwhatever.so".IO
---------->
(Str)
file#/home/jmerelo/progs/raku/my-raku-examples/test-resources/lib
/home/jmerelo/progs/raku/my-raku-examples/test-resources/lib:ver<*>:auth<>:api<*>
libraries/whatever
"/home/jmerelo/progs/raku/my-raku-examples/test-resources/resources/data/swim.csv".IO
---------->
(Str)
file#/home/jmerelo/progs/raku/my-raku-examples/test-resources/lib
/home/jmerelo/progs/raku/my-raku-examples/test-resources/lib:ver<*>:auth<>:api<*>
data/swim.csv

하지만 배포판을 설치하고 설치된 스크립트를 실행하면 대신 이런 모습이 나와요.

"/home/jmerelo/.rakubrew/versions/moar-2020.05/install/share/perl6/site/resources/7127AA0E7F43E87DF309570E813E46A6E2C4D0B2.so".IO
----------
site
(Str)
1F8F9C004D7E952B297F30420DA07B354B3F2AA7
libraries/whatever
"/home/jmerelo/.rakubrew/versions/moar-2020.05/install/share/perl6/site/resources/D357F3E46256CB0DACD8975033D1CC7A17B4CF9F.csv".IO
----------
site
(Str)
1F8F9C004D7E952B297F30420DA07B354B3F2AA7
data/swim.csv

주요 차이는, 보이는 것처럼 "로컬" 배포판은 repo에 값이 있고 "설치된" 배포판은 repo-name에 값이 있다는 점이에요. dist-id는 배포판 종류에 따라 달라지고, 어쨌든 .key는 리소스 의사해시(pseudo-hash) 키의 이름을 반환해요.

또 주의할 점은, 리소스에 키로 접근하면 그 리소스의 핸들이 반환되는데, 이 핸들은 IO::Path로 gist되지만 실제로는 Distribution::Resource 객체라는 거예요. "일반" 리소스를 다시 보면 그 경로는 META6.jsonresources에 선언된 것과 같지만, "라이브러리" 리소스는 값을 정식 라이브러리 이름(첫 번째 경우 libwhatever.so, 두 번째 경우 정식 Linux 확장자인 .so가 붙은 해시 이름)으로 바꿔줘요.

Distribution::Resource는, 이를테면 파일처럼, 그것이 나타내는 리소스 그 자체로 직접 쓰이도록 설계됐어요.

my @data = %?RESOURCES<data/swim.csv>.lines.split(",");

이게 가능한 건 IO::Path를 반환해서가 아니라, open, slurp, lines, comb, split, words, copy 같은 메서드 일부를 공유하기 때문이에요. 위에서는 .lines를 쓰고 있죠.

Resource의 경우 .IO와 관련 메서드가 컴파일 단계가 아니라 런타임에 결정되는 것이 필수적이에요(특히, precomp 파일에 들어가지 않아야 해요). 파일 경로가 두 단계 사이에서 바뀔 수 있고 실제로 가끔 바뀌기 때문이에요. 그래서 absolutedirname처럼 파일 경로를 참조하는 메서드들은 피하고, 대신 open 메서드로 파일에 접근하는 게 중요해요.

libraries/ 폴더에 있는 리소스의 경우 주 사용처는 is native의 인자로서예요. 이 예시처럼요.

use NativeCall;
sub foo() is native(%?RESOURCES<libraries/whatever>)

반환된 Distribution::Resource는 배포판이 실행되는 특정 아키텍처에 맞는 올바른 이름과 확장자를 갖게 돼요.

일반적으로, 어떤 경우든 지침 원칙은 리소스를 공유 라이브러리든 일반 리소스 파일이든 그 의도된 목적에 직접 사용하라는 것이에요.

method IO

method IO()

해당 리소스를 IO::Path로 반환하고, 실제로 그렇게 쓸 수 있어요.

출처: Raku Documentation — Distribution::Resource