rubrapack 매뉴얼←↑→

20 원본 파일과 rubrapack 명령

원본 파일 하나가 패키지 하나를 기술한다. 형식은 TOML 1.0의 엄격한 부분집합이고 이름은 *.toml 이라서, 편집기와 GitHub, AI 도우미가 TOML 로 알아본다(예전 이름 *.rpk 도 똑같이 읽는다). 모든 원본은 올바른 TOML 이지만, 부분집합 밖의 TOML 기능은 무시하지 않고 오류로 거부한다.

20.1 시작하기#

rubrapack 은 프로그램 파일 하나다. 릴리스 페이지에서 rubrapack-<판>-windows-x64.exe(원하면 rubrapack.exe 로 이름을 바꾼다)나 rubrapack-<판>-linux-x86_64(chmod +x 한다)를 내려받아 그 자리에서 실행하거나 PATH 에 둔다. 따로 설치할 것은 없다: 런타임도, SDK 도, 라이브러리도 필요 없다. 같은 프로그램이 Windows 에서도 Linux 에서도 같은 패키지를 만든다.

이 절은 설치 파일을 아는 독자를 위한 짧은 판이다. 제1부 튜토리얼은 같은 것을 처음부터 한 걸음씩 가르치고, 모든 표와 옵션까지 나아간다: 시작하기 전에부터 읽는다.

첫 패키지#

배포할 것을 dist/ 폴더에 둔다: 프로그램, 그리고 그것이 필요로 하는 파일은 dist/files/ 에(하위 폴더도 그대로 간다). 그다음 rubrapack 에게 시작할 원본을 쓰게 한다:

rubrapack new app.toml      # 묻고 나서 app.toml 을 쓰고 검사한다

제품 이름, 판, 파일이 든 폴더, 주 프로그램(아키텍처는 파일에서 읽는다), Program Files 아래 폴더, 누구를 위해 설치하는지, 어떤 하위 폴더가 선택 구성요소인지, 대화창, 약관(옆에 LICENSE.txt, .md, .rtf 가 있으면 권한다), 한국어 대화창, 바로가기를 묻는다. Enter 는 대괄호 안의 값을 쓴다. 끝에는 같은 답을 한 줄 명령(rubrapack new app.toml --dist dist --ui features ...)으로 보여 주는데, 스크립트나 AI 도우미는 묻지 않고 이것을 돌리면 된다. 이름 없이 rubrapack new 만 주면 파일 이름까지 묻고, rubrapack new app(확장자 없이)은 대신 고정된 틀을 쓴다.

어느 쪽이든 app.toml 은 읽고 고칠 수 있는 평문이다 - 아무 편집기로나, 또는 rubrapack edit app.toml 으로. edit 는 같은 질문을 지금 값을 기본으로 보여 주는 메뉴다(판, 설치 폴더, 대화창과 약관, 선택 구성요소, 바로가기, 그리고 프로그램 폴더가 바뀐 뒤 파일 목록 맞추기). 그 값만 바꾸고 다른 줄, 주석, 표는 그대로 둔다. 스크립트에서는: rubrapack edit app.toml --set define.VERSION=1.1.0 --sync. 아래 원본은 프로그램과 파일을 Program Files\My App 에 설치하고, 시작 메뉴에 넣고, 사용자가 폴더를 바꿀 수 있는 대화창을 보인다:

format = 1

[package]
name = "My App"
manufacturer = "My Company"            # 설정 > 설치된 앱에 보인다
version = "$(VERSION)"
arch = "x64"                           # x64, arm64, x86
upgrade-code = "{E8C1815C-CCD7-4F3F-B914-92A4E3F3A317}"   # `new` 가 준 것; 영원히 간직한다
ui = "installdir"

[define]
VERSION = "1.0.0"

[dir.INSTALLDIR]
path = "$(ProgramFiles)/My App"

[file.App]
dir = "INSTALLDIR"
source = "dist/app.exe"

[files.Rest]
dir = "INSTALLDIR"
glob = "dist/files/**"

[shortcut.StartMenu]
dir = "Programs"
name = "My App"
target = "file:App"
rubrapack build app.toml -o app-1.0.0.msi
rubrapack build app.toml -o app-1.0.1.msi -D VERSION=1.0.1    # 다음 판

이것으로 완전한 설치 파일이다. "설치된 앱"에 나타나고, 스스로 복구하고, 설치한 것을 모두 지운다. 더 높은 판은 설치된 판을 대체하고, 같거나 낮은 판은 안내문과 함께 거부된다. 컴포넌트 GUID, 파일 키, 캐비닛과 표는 원본에서 끌어내므로, 판에서 판으로 기억해야 할 것은 업그레이드 코드뿐이다.

약관 페이지와 선택 구성요소#

설치 파일이라면 흔히 바라는 두 가지: 설치 전에 사용자가 약관에 동의하는 것, 그리고 일부 구성요소를 고를 수 있는 것. license 는 약관 페이지를 더하고("동의합니다"에 표시하기 전에는 다음 단추가 꺼져 있다), ui = "features" 는 사용자가 설치할 기능을 고르는 트리를 더한다. 아래 원본은 프로그램을 늘 설치하고, 예제는 보여 주되 고르지 않은 상태로 둔다:

format = 1

[package]
name = "My App"
manufacturer = "My Company"
version = "$(VERSION)"
arch = "x64"
upgrade-code = "{E8C1815C-CCD7-4F3F-B914-92A4E3F3A317}"
ui = "features"                        # welcome, license, folder, feature tree, ready
license = "LICENSE.txt"                # Next stays off until "I accept" is ticked

[define]
VERSION = "1.0.0"

[feature.Main]
title = "My App"
description = "The program itself."
required = true                        # always installed: the tree does not offer to leave it out

[feature.Samples]
title = "Samples"
description = "Example documents to try the program with."
level = 2                              # offered in the tree, not selected by default

[dir.INSTALLDIR]
path = "$(ProgramFiles)/My App"
feature = "Main"

[dir.SamplesDir]
path = "$(INSTALLDIR)/samples"
feature = "Samples"

[file.App]
dir = "INSTALLDIR"
source = "dist/app.exe"

[files.Rest]
dir = "INSTALLDIR"
glob = "dist/files/**"

[files.Samples]
dir = "SamplesDir"
glob = "dist/samples/**"

[shortcut.StartMenu]
dir = "Programs"
name = "My App"
target = "file:App"

약관 글은 LICENSE.txt(.txt, .md, .rtf)에, 예제 파일은 dist/samples/ 에 둔다. level = 2 인 기능은 사용자가 표시하지 않으면 설치되지 않고, required 는 트리가 그 기능을 빼자고 제안하지 못하게 한다. 나중에 사용자는 "설치된 앱"의 "변경"에서 고른 것을 바꿀 수 있고, 명령줄에서도 대화창 없이 같은 일을 한다: msiexec /i app.msi /qn ADDLOCAL=Samples 는 예제를 더하고, REMOVE=Samples 는 뺀다(required 는 트리에만 적용되고 명령줄은 묶지 않는다). 나머지는 기능(feature)과 조건: when에 있다.

나머지를 설명하는 곳#

하려는 일읽을 곳
msiexec 로 설치, 업그레이드, 제거, 기록 남기기첫 설치 파일, 판과 업그레이드
오류를 이해하고 패키지 안 보기(lint, inspect, extract, 종료 코드)검사하고 들여다보기, 진단 코드
패키지에 서명하기서명과 타임스탬프
스크립트나 CI 작업, 또는 AI 도우미로 빌드하기빨리 시작하기와 자동화

20.2 예#

format = 1

[define]
VERSION = "1.4.0"

[package]
name = "Example App"
manufacturer = "Example"
version = "$(VERSION)"
arch = "x64"                                        # x64, arm64, x86 가운데 하나 - 기본값 없음
upgrade-code = "{0B9A6C1E-3D2F-4A5B-8C7D-6E5F4A3B2C1D}" # 한 번 만들어 영원히 쓴다
language = "en-US"                                  # 또는 "ko-KR"

[dir.INSTALLDIR]
path = "$(ProgramFiles)/Example App"

[dir.Docs]
path = "$(INSTALLDIR)/docs"

[file.MainExe]
dir = "INSTALLDIR"
source = "dist/app.txt"

[file.Guide]
dir = "Docs"
source = "dist/guide.txt"
name = "User guide.txt"
rubrapack build example.toml -o example.msi -D VERSION=1.4.1
rubrapack inspect example.msi File

20.3 원본 형식#

원본의 첫 키는 어떤 표보다도 앞에 오는 format = 1 이다: 원본 형식의 판을 정수 하나로 적는다. 원본을 다르게 써야 할 때만 올라가고, 표나 키가 새로 생겨도 그대로다. 이것이 없는 원본은 rubrapack 0.18 이하의 것으로 보고 경고(RP1108)를 낸다. 그 뒤에 바뀐 것은 쓰인 자리에서 거부한다 - 형식 1 은 경로를 $(...) 로 시작한다 (경로). 더 큰 수가 적힌 원본에는 더 새 rubrapack 이 필요하다.

20.4 TOML 부분집합#

받는 것: [kind]·[kind.ID] 표, 맨 키(A-Z a-z 0-9 _ -), TOML 이스케이프가 되는 기본 문자열 "...", 리터럴 문자열 '...'(이스케이프 없음 - 백슬래시와 따옴표를 쓸 때 편하다: 'SOFTWARE\Example'), 십진수와 0x 정수, true/false, 한 가지 형만 담은 배열, # 주석. BOM 이 있거나 없는 UTF-8, 또는 BOM 이 있는 UTF-16LE. 줄 끝은 LF 나 CRLF.

오류로 거부하는 것: 여러 줄 문자열, 인라인 표, 표 배열, 점으로 이은 키와 따옴표 키, 세 부분 이상의 표 이름, 부동소수, 날짜, 숫자 속 _, 8진·2진 수, 빈 배열이나 섞인 배열, 첫 표보다 앞의 키(format 은 빼고). 표와 키의 순서는 아무 의미가 없다.

20.5 표#

표키(굵은 것은 반드시)
[package]name, manufacturer, version(a.b.c 또는 a.b.c.d), arch, upgrade-code, upgrade-code-x64 / -arm64 / -x86, product-code, summary-name(ASCII), language, scope(machine, user, dual), ui(none, basic, minimal, installdir, features), license(.txt, .md, .rtf), reboot(suppress/allow), cleanup(false: 정리 작업 없음), preflight(false: 진행 전 점검 없음), close-programs(ask, always, never), parent(추가 기능: 본체 제품의 upgrade-code), remove-addons(true: 이 제품을 지우면 추가 기능도 지운다), replaces(이 패키지가 대신하는 제품들의 업그레이드 코드), downgrade-message, compress(none, mszip, mszip:0..mszip:9, lzx, lzx:15..lzx:21; 기본 mszip:6), cab(embed 또는 external), cab-max-size(MiB), refuse-upgrade-below, refuse-upgrade-message
[define]변수: NAME = "value"
[feature.ID]title, description, level(1-32767), hidden, parent, required, follow-parent, when, default-when
[dir.ID]path = 기준/상대/경로, feature, guard(true: 설치 폴더 지키기 참고)
[file.ID]dir, source, name, vital(기본 true), any-arch, feature, component-guid, keep, when
[files.ID]dir, glob, vital, any-arch, feature, keep, when
[folder.ID]dir, name, keep, feature
[arp]no-modify, no-repair, no-remove, help(URL), about(URL), icon(.ico) - "설치된 앱"에 제품이 어떻게 보이는가
[property.ID]value, secure, hidden - 대문자 이름의 공개 속성
[action.ID]run(이 패키지의 .exe 를 가리키는 file:ID), do, undo, check
[registry.ID]root(HKLM, HKCU, HKCR, HKMU), key, name, value, type, remove, keep, view, with, feature, when
[remove.ID]dir, name(* 와 ?; 없으면 폴더 자체), on(install, uninstall, both), upgrade(false: 업그레이드가 이 판을 지울 때는 하지 않음), feature
[ini.ID]dir, file, section, key, value, mode(set, add, remove), feature, when
[require.ID]condition, message
[search.ID]property(또는 dir ID), kind(registry: root, key, name, view; file: path, file, min-version; dir: path; component: component-guid)
[service.ID]file(.exe 를 가리키는 file:ID), name, display-name, description, start(auto, demand, disabled), account(LocalSystem, LocalService, NetworkService), args, start-on-install
[assoc.ID]extension(.ext, 소문자), prog-id, target(.exe 를 가리키는 file:ID), description, icon(file:ID), args(기본 "%1"), content-type, perceived-type, default(false: "연결 프로그램"에만)
[menu.ID]on(파일 형식, "*", "folder", "background", "drive", "assoc:ID". 하위 메뉴의 항목에는 없다), text 또는 text-xx, target(.exe 를 가리키는 file:ID. 없으면 하위 메뉴), args(기본 "%1"), icon(file:ID), parent(하위 메뉴의 ID), multi(each, one, single), extended, windows11(기본 true) - 탐색기 우클릭 메뉴의 항목: 탐색기의 우클릭 메뉴 참조
[protocol.ID]name(스킴, 소문자), target(.exe 를 가리키는 file:ID), description, args(기본 "%1")
[com.ID]file(.exe 나 .dll 의 file:ID), class({GUID}), description, threading(sta 기본, mta, both, neutral; DLL), args(프로그램), prog-id, app-id({GUID}), surrogate(dllhost 에서 도는 DLL), typelib({LIBID}), typelib-version(기본 "1.0"), typelib-file(기본: 서버), msi-only - COM 클래스, COM 클래스 참고
[handler.ID]kind(thumbnail, preview, property), class([com.*] 의 DLL 클래스), types([".ext", ...]), description(미리 보기 처리기의 이름), msi-only - 탐색기 처리기, 탐색기 처리기 참고
[font.ID]file(path = "$(Fonts)" 인 dir 에 드는 파일의 file:ID), title
[permission.ID]target(dir:ID, file:ID, registry:ID), sddl
[env.ID]name, value, mode(set, append, prepend), keep, feature, when
[copy.ID]source(file:ID), dir, name(기본: 원본 파일 이름)
[merge.ID]source(.msm 병합 모듈), dir(모듈의 뿌리 폴더가 갈 곳), feature, config(설정할 수 있는 모듈에 ["이름=값", ...]). MSIX 에서는 파일과 레지스트리 값만
[module]name(모듈의 ID: 영문자·숫자·_, 35자 이내), manufacturer, version, arch, id(모듈의 GUID, 모든 판에서 그대로), language(기본 neutral, en-US, ko-KR), compress(none, mszip, mszip:0..mszip:9) - [package] 대신: 병합 모듈, 병합 모듈 만들기 참고
[ui]install-dir(dir ID; 기본 INSTALLDIR), banner(.bmp), launch(file:ID), launch-args, launch-checked, save-log(기본 true), languages(영어에 덧붙일 언어, 예 ["ko"]), license-xx, name-xx, font-xx, langid-xx - 여러 언어 참고
[ui-text.ID]text 또는 text-xx - 내장 대화창 문구 하나를 바꾼다
[dialog.ID]after(내장 페이지 또는 다른 [dialog.*]), title, description, title-xx, description-xx
[dialog-control.ID]dialog, type(text, checkbox, edit, radio, combo), x, y, width, height, text, property, values, labels, text-xx, labels-xx
[shortcut.ID]dir(dir ID, 또는 Programs, Desktop, StartMenu, Startup), name, target(file:ID), args, description, working-dir(dir ID), icon(.ico), when
[msix]identity-name, publisher, display-name, display-name-xx, publisher-display-name, publisher-display-name-xx, min-version, capabilities(이름들), file-system-virtualization, registry-virtualization(기본 true), main-package, main-publisher, modification, language-packs(기본 true), appinstaller-uri, package-uri, update-hours(0-255, 기본 24), update-prompt, update-blocks, update-background - MSIX 패키지 참고
[msix-app.ID]executable([file.*] ID), display-name, display-name-xx, description, description-xx, logo-150, logo-44, store-logo(저마다 .scale-NNN 변형을 둘 수 있다), background-color(기본 transparent, #RRGGBB), hidden(시작 메뉴 항목 없음)
[msix-dependency.ID]name, publisher, min-version - MSIX 가 필요로 하는 프레임워크 패키지 - MSIX 전용
[msix-extension.ID]kind(alias: alias; startup-task: task-id, display-name, enabled; firewall: direction(in, out), protocol(tcp, udp), ports(8080 또는 8000-8100), profile(all, domain, private, public), file(기본은 앱의 프로그램); com-server: file(.exe 나 .dll), class({GUID}), display-name, args(.exe), threading(sta 기본, mta, both, neutral; .dll); toast: class, file(기본은 앱의 프로그램), args(기본 -ToastActivated); context-menu: file(.dll), class, types([".txt", "*"]), verb(기본은 표 ID), threading), app([msix-app.*] ID; 기본은 첫째) - MSIX 전용
[chain]name, manufacturer, version, arch(설치 프로그램 자신의 것: x64, x86, arm64), elevate(기본 true) - 체인 원본: 설치 프로그램 하나에 여러 패키지 참고
[chain-package.ID]source(.msi), properties(msiexec 속성), vital(기본 true)

dir 경로의 $(기준)은 다른 dir ID 이거나 Windows 폴더다: $(ProgramFiles)(x64/arm64 는 64비트, x86 은 32비트), $(ProgramFiles(x86)), $(CommonProgramFiles), $(APPDATA), $(LOCALAPPDATA), $(ProgramData), $(TEMP), $(SystemRoot), 그리고 환경 변수가 없는 폴더 $(StartMenu), $(Programs), $(Desktop), $(Startup), $(System), $(Fonts) - Windows 이름 참고. 폴더 하나만 쓴 경로(path = "$(Fonts)")는 그 폴더 바로 안에 드는 파일에 쓴다.

ID 는 [A-Za-z_][A-Za-z0-9_]* 꼴이고(최대 72자, feature 는 38자) dir·파일·feature 를 통틀어 서로 달라야 한다.

MSIX 패키지#

출력 이름이 .msix 로 끝나면 같은 원본이 MSIX 패키지가 된다: 완전 신뢰로 도는 데스크톱 앱 하나, 아키텍처 하나. MSIX 에만 필요한 것은 표 두 개가 더 말한다:

[msix]
identity-name = "Example.App"               # 3~50자: A-Z a-z 0-9 . -
publisher = "C=KR, O=Example, CN=Example"   # 서명 인증서의 주체, 마지막 부분부터
publisher-display-name = "Example"          # 기본: [package] manufacturer
min-version = "10.0.17763.0"                # 설치되는 가장 오래된 Windows(이것이 기본값)

[msix-app.Main]
executable = "MainExe"                      # 앱을 시작하는 [file.*]
display-name = "Example App"                # 기본: [package] name
description = "An example"                  # 기본: 표시 이름
logo-150 = "assets/Square150x150.png"       # PNG, 150x150
logo-44 = "assets/Square44x44.png"          # PNG, 44x44
store-logo = "assets/StoreLogo.png"         # PNG, 50x50

설치한 프로그램으로 등록하기: [action.ID]#

[action.Tip]
run = "file:MainExe"      # 이 패키지가 설치하는 .exe
do = "--register"         # 파일을 설치한 뒤, 그리고 복구할 때 다시 실행
undo = "--unregister"     # 제거할 때, 파일을 지우기 전에 실행

rubrapack 은 이 한 쌍을 관리자 권한의 지연(deferred) 동작과 그 되돌림(rollback) 짝으로 바꾼다. 그래서 제거와 업그레이드는 전부 되거나 전혀 되지 않는다: 뒤에서 무엇이 실패하면(또는 do/undo 자체가 0 아닌 값으로 끝나면) 파일이 되돌아오고 다른 명령이 등록을 되살린다. 두 명령은 두 번 실행해도 안전해야 하고, 아무것도 묻지 않고 끝나야 한다 - 창 없이 실행되고 사용자를 기다리는 것은 없다. 인자는 쓴 그대로 넘어간다(서식 문자열이 아니다). check 는 아직 효과가 없어 경고(RP1318)만 낸다.

레지스트리 값: [registry.ID]#

[registry.InstallDir]
root = "HKLM"
key = 'SOFTWARE\Example'          # 리터럴 문자열은 백슬래시를 그대로 둔다
name = "InstallDir"               # 없으면 키의 기본값
value = "[INSTALLDIR]"            # MSI 서식 문자열: [PROPERTY], [#FileID], "[" 는 [\[]

type 은 string(기본), expand, dword(0~0xFFFFFFFF 정수), qword(정수, 또는 "0x" 와 16자 이하의 16진수), binary(16진수), multi(문자열 배열)이다. Windows Installer 는 REG_QWORD 를 스스로 쓰지 못한다. 그래서 rubrapack 이 작은 도우미 DLL 을 패키지에 넣어 쓰게 하고, 설치가 실패하면 그 DLL 이 이전 값을 되살린다. 값 하나가 구성 요소(component) 하나이고 제거할 때 지워진다(keep = true 면 남는다). with = "file:ID" 는 대신 그 파일의 구성 요소에 넣는다. 64비트 패키지에서 값은 64비트 레지스트리 보기로 가고, view = "32" 는 32비트 보기에 쓴다. remove = true(value 없이)는 설치 중에 이름 붙은 값을 - name 이 없으면 키 전체를 - 지운다.

바로가기: [shortcut.ID]#

[dir.Menu]
path = "$(Programs)/Example"         # 시작 메뉴 > Example

[shortcut.Settings]
dir = "Menu"
name = "Example settings"         # ".lnk" 는 붙여 준다
target = "file:MainExe"
args = "--settings \"[INSTALLDIR]\""

바로가기는 대상 파일(과 그 feature)에 딸린다. 바로가기 때문에 만든 폴더는 제거할 때 지운다. 컴퓨터 전체 패키지에서 Programs 와 Desktop 은 모든 사용자의 시작 메뉴와 공용 바탕화면이다.

MSIX 에서 바로가기는 앱을 시작한다(대상은 [msix-app.*] 의 실행 파일이어야 한다): Programs 나 StartMenu 의 것은 그 앱 자신의 시작 메뉴 항목이고(args 없음), Desktop 의 것은 매니페스트에 적히며 min-version = "10.0.19645.0" 이상이 필요하다(RP1614). 다른 폴더, working-dir, args 속 [...] 는 거기서 오류다(RP1613). Startup 은 시작 작업으로 쓴다.

파일 형식과 링크: [assoc.ID], [protocol.ID]#

[assoc.Doc]
extension = ".exdoc"
prog-id = "Example.Document"      # 같은 prog-id 의 표들은 한 종류의 문서를 뜻한다
description = "Example document"
target = "file:MainExe"
args = "--open \"%1\""            # 기본값은 "%1"

[protocol.Link]
name = "example"                  # example:... 가 프로그램을 연다
target = "file:MainExe"

MSI 에서는 프로그램의 구성 요소에 든 HKEY_CLASSES_ROOT 아래 레지스트리 값이다: 확장자의 기본값은 prog-id, prog-id 에는 설명과 DefaultIcon(icon, 없으면 프로그램의 첫 아이콘), shell\open\command 가 있고, 스킴에는 URL Protocol 이 붙는다. HKEY_CLASSES_ROOT 는 설치 방식을 따른다: 컴퓨터 전체면 HKLM\Software\Classes, 사용자별이면 HKCU\Software\Classes 에 들고, 제거하면 없어진다. 이 프로그램만 맡겠다는 형식은 곧바로 이 프로그램으로 열리고, 사용자가 다른 프로그램을 골라 두었으면 Windows 는 그 선택을 지킨다.

MSIX 에서는 대상 실행 파일을 가진 앱의 매니페스트에 들어가고(파일 형식 연결, 프로토콜) args 는 평문이어야 한다. icon 은 쓰지 않는다: 앱의 로고가 파일 형식을 나타낸다.

파일 형식에는 무엇이 들었는지, 그리고 프로그램이 그 형식을 가져갈지도 적을 수 있다:

[assoc.Picture]
extension = ".png"
prog-id = "Example.Picture"
target = "file:MainExe"
content-type = "image/png"        # 그 형식의 미디어 형식
perceived-type = "image"          # image, text, audio, video, compressed, document, system, application
default = false                   # "연결 프로그램"에만 올리고 형식을 가져가지 않는다

모든 [assoc.*] 는 자기 prog-id 를 확장자의 OpenWithProgids 에 올려서 "연결 프로그램"에 나오게 한다. default = false 면 그것만 한다(확장자의 기본값은 건드리지 않는다). MSIX 에서는 content-type 이 매니페스트에 들어가고, perceived-type 과 default 는 MSI 의 것이다.

탐색기의 우클릭 메뉴: [menu.ID]#

[menu.Convert]
on = [".png", ".jpg", "folder"]   # 어디에 나오는가
text = "Convert with Example"
text-ko = "Example 로 변환"        # 언어별 문구
target = "file:MainExe"
args = "--convert \"%1\""         # "%1" 은 고른 것의 경로. 기본값은 "%1" 하나
icon = "file:MainExe"             # 기본: 프로그램 자신의 아이콘

[menu.Tools]                      # target 이 없으면 하위 메뉴
on = "*"
text = "Example tools"

[menu.Checksum]
parent = "Tools"                  # 그 하위 메뉴의 항목. 하위 메뉴가 나오는 곳에 나온다
text = "Checksum"
target = "file:MainExe"
args = "--sum \"%1\""
multi = "single"                  # 하나만 골랐을 때만

항목은 우클릭한 것을 넘겨 패키지의 프로그램을 시작한다. on 은 자리 하나 또는 목록이다: 파일 형식 (".png"), "*"(모든 파일), "folder", "background"(열린 폴더의 빈 곳: 이때 %1 은 그 폴더), "drive", "assoc:ID"(어느 [assoc.*] 의 파일 형식, 그 prog-id 아래). multi 는 여러 개를 골랐을 때의 뜻이다: "each"(기본: 항목마다 한 번 실행), "one"(전부를 넘겨 한 번 실행: args 에 %* 를 쓰면 모든 경로), "single"(하나를 골랐을 때만 항목이 나온다). extended = true 면 Shift 를 누른 채일 때만 나온다. 하위 메뉴는 한 단계까지다.

같은 표가 Windows 의 두 메뉴에 다 쓰인다:

Windows 11 에서 알아 둘 것: 항목은 설치가 끝나고 몇 초 뒤에 나타난다. 한 패키지의 항목이 같은 종류의 대상에 둘 이상이면 Windows 가 제품 이름을 단 항목 하나 아래로 묶는다. "drive" 와 windows11 = false 인 항목은 옛 메뉴에만 나온다. 메뉴 부품이 무엇을 하는지 보려면 HKCU\Software\rubrapack 아래에 MenuLog 값(파일 경로)을 둔다: 고른 항목과 시작한 명령이 그 파일에 덧붙는다.

COM 클래스: [com.ID]#

[com.Widget]
file = "file:WidgetDll"           # 이 패키지의 .exe 나 .dll 이 클래스를 제공한다
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D35}"
description = "Example widget"
prog-id = "Example.Widget"        # 스크립트가 부르는 이름
threading = "both"                # DLL 의 아파트: sta(기본), mta, both, neutral
app-id = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D36}"
surrogate = true                  # 호출한 프로세스 밖 dllhost 에서 돌 수 있다
typelib = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D37}"
typelib-version = "1.2"           # 16진 숫자의 major.minor; typelib-file 로 다른 파일을 가리킨다

[com.Server]
file = "file:MainExe"             # 프로그램: LocalServer32, args 를 붙여 시작한다
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D38}"
args = "-Embedding"

MSI 에서는 파일 형식처럼 서버의 구성 요소에 든 HKEY_CLASSES_ROOT 아래 레지스트리 값이 된다: 설명을 단 CLSID\{class}, InprocServer32(DLL 과 ThreadingModel) 또는 LocalServer32(따옴표 친 프로그램 뒤에 args), prog-id 가 있으면 CLSID\{class}\ProgID 와 <prog-id>\CLSID, app-id(또는 app-id 가 없을 때 클래스 ID 를 쓰는 surrogate)가 있으면 클래스의 AppID 값과 AppID\{app-id}(서로게이트면 DllSurrogate 도), typelib 이 있으면 CLSID\{class}\TypeLib 와 TypeLib\{typelib}\<version>(패키지 아키텍처에 따라 0\win64 또는 0\win32, FLAGS, HELPDIR). 제거하면 함께 사라진다. 값은 Class·ProgId·TypeLib·AppId 표가 아니라 Registry 행으로 쓴다 - Microsoft 도구가 광고하지 않는 클래스를 쓰는 방식이다. 그 표들의 클래스는 Windows Installer 가 광고된 것(처음 쓸 때 설치)으로 등록하기 때문이다. 사용자별 설치면 HKCU\Software\Classes 에 들어간다. Microsoft 검증(ICE33)은 표를 쓰라며 이런 행에 경고를 내는데, [com] 에서는 예상된 경고다. 클래스 ID, prog-id, ID 는 각각 한 번만 쓴다(RP1301). threading 과 surrogate 는 DLL 의, args 는 프로그램의 것이다(RP1316).

MSIX 에서 [com] 은 패키지 COM 카탈로그의 클래스(com:ComServer: 프로그램은 ExeServer, DLL 은 SurrogateServer)가 되고 prog-id 는 com:ProgId 가 된다. app-id 와 typelib 은 MSI 에만 들어가며 경고가 그렇게 알린다(RP1612).

탐색기 처리기: [handler.ID]#

[handler.Thumbs]
kind = "thumbnail"                # 탐색기가 파일에 보이는 그림
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D41}"   # 패키지 DLL 의 [com.*] 클래스
types = [".exdoc"]

[handler.Preview]
kind = "preview"                  # 미리 보기 창에 보이는 것
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D43}"
types = [".exdoc"]
description = "Example document preview"
msi-only = true

[handler.Props]
kind = "property"                 # 탐색기와 검색이 쓰는 파일 속성(제목, 작성자 ...)
class = "{8D1E2F30-4A5B-4C6D-8E7F-901A2B3C4D42}"
types = [".exdoc"]
msi-only = true

처리기는 패키지 DLL 안의 COM 클래스로 [com.*](threading 포함)에 적고, [handler] 는 그것이 어떤 파일 형식을 맡는지 Windows 에 알린다. MSI 에서: 썸네일과 미리 보기 처리기는 HKEY_CLASSES_ROOT 아래 형식마다의 ShellEx 값이다(썸네일 {E357FCCD-...}, 미리 보기 {8895B1C6-...}). 미리 보기 처리기는 PreviewHandlers 목록에도 오르고, 그 클래스는 Windows 미리 보기 호스트(prevhost.exe, 패키지 아키텍처에 따라 64비트나 32비트)의 AppID 를 받으므로 그 [com] 에는 app-id 와 surrogate 를 쓰지 않는다. 속성 처리기는 HKLM 의 PropertySystem\PropertyHandlers 아래이며 Windows 가 컴퓨터 단위로만 읽으므로 패키지는 scope = "machine" 이어야 한다. 한 형식에는 종류마다 처리기 하나다(RP1301).

MSIX 에서 썸네일·미리 보기 처리기는 패키지의 파일 형식 연결(desktop2:ThumbnailHandler, desktop2:DesktopPreviewHandler, 클래스는 패키지 COM 카탈로그)에 들어간다. 형식 묶음마다 연결 하나이며, 그 형식을 여는 [assoc] 가 있으면 거기에 들어간다. 패키지가 설치되어 있는 동안 탐색기가 쓴다. 패키지는 클래스를 대리 프로세스(dllhost)에서 돌리므로 미리 보기 처리기의 클래스에는 threading = "sta" 를 준다. 다른 모델이면 그 창이 메시지 루프 없는 스레드에 놓여 빈 채로 남을 수 있다(경고, RP1612). 패키지에 든 속성 처리기는 Windows 11 에서 탐색기에 값을 주지 못했다(같은 클래스를 MSI 로 설치하면 준다). 그래서 MSIX 는 그 종류를 거절하고(RP1612) msi-only = true 가 MSI 에만 남긴다.

MSIX 전용: [msix-extension.ID]#

[msix-extension.Cli]
kind = "alias"
alias = "example.exe"             # 콘솔에서 이 이름을 치면 앱이 시작된다

[msix-extension.Boot]
kind = "startup-task"             # 앱을 한 번 실행한 뒤로 Windows 와 함께 시작된다
display-name = "Example"          # 작업 관리자에 보이는 이름; task-id 기본값은 표 ID
enabled = true
[msix-extension.Web]
kind = "firewall"                 # 패키지가 설치돼 있는 동안의 Windows 방화벽 규칙
direction = "in"
protocol = "tcp"
ports = "8080"
profile = "private"               # 기본값 "all". 다른 프로그램이면 file = "file:ID"

MSI 빌드는 이것들을 뺀다. MSIX 에서 글꼴([font.*])은 패키지의 Fonts 폴더에서 다른 앱과 나눠 쓰며(uap4:SharedFonts) title 은 쓰지 않는다.

[msix-extension.Server]
kind = "com-server"               # 프로그램(또는 DLL)이 제공하는 COM 클래스
file = "file:MainExe"
class = "{7A1B2C3D-4E5F-4A6B-8C7D-9E0F1A2B3C41}"
args = "-Embedding"

[msix-extension.Toast]
kind = "toast"                    # 앱의 알림을 누르면 이 클래스로 앱이 시작된다
class = "{7A1B2C3D-4E5F-4A6B-8C7D-9E0F1A2B3C43}"

[msix-extension.Menu]
kind = "context-menu"             # DLL(IExplorerCommand)이 처리하는 탐색기 오른쪽 메뉴 항목
file = "file:MenuDll"
class = "{7A1B2C3D-4E5F-4A6B-8C7D-9E0F1A2B3C44}"
types = [".txt", "*"]             # 파일 형식. "*" 는 모든 파일

이것들은 앱의 com:ComServer(프로그램이면 ExeServer, DLL 이면 SurrogateServer), 프로그램을 그 클래스와 args 로 등록하는 desktop:ToastNotificationActivation, 그리고 DLL 을 클래스로 쓰는 desktop4:FileExplorerContextMenus 가 된다. 패키지가 설치돼 있는 동안 Windows 는 그 클래스들을 패키지 COM 목록에 올린다. 오른쪽 메뉴는 DLL 이 그 항목을 구현해야(IExplorerCommand) 보인다. rubrapack 은 등록할 뿐 구현하지 않는다.

[service.*] 는 MSIX 에 패키지 서비스(desktop6:Service, 첫째 앱 안, 권한 packagedServices 와 account = "LocalSystem" 이면 localSystemServices)로 들어간다: 이름·시작·계정은 MSI 와 같고 args 는 평문이어야 한다. display-name 과 description 은 쓰지 않고, 서비스는 start-on-install 이 아니라 start 대로 시작한다. min-version = "10.0.19041.0" 이상이 필요하다(RP1614). 패키지를 지우면 서비스가 멈추고 지워지며, 방화벽 규칙도 함께 사라진다.

지우기와 복사: [remove.ID], [copy.ID]#

[remove.ID] 는 dir 에서 name 에 맞는 파일을 지운다 - 예를 들어 옛 판이 남긴 *.log 를 on = "install" 로, 프로그램이 실행 중에 만드는 파일을 on = "uninstall" 로. name 이 없으면 비어 있는 폴더 자체를 지운다. 설치가 실패하면 지운 파일은 되돌아온다. [copy.ID] 는 이 패키지의 파일을 다른 폴더에 한 벌 더 설치하고, 원본과 함께 들어오고 함께 나간다. 설치 전부터 있던 폴더는 절대 지우지 않는다.

업그레이드는 옛 판도 지우고, 그때 옛 판의 on = "uninstall" 행도 돈다. upgrade = false 는 그 행을 거기서 뺀다: 파일은 제품(또는 그 기능)을 정말 지울 때만 지워진다 - 새 판이 그대로 쓰는 파일이나, 옛 판을 아직 돌리는 프로그램이 다시 읽을 수 있는 파일에 쓴다. RemoveFile 표 대신 rubrapack 의 도우미 DLL 이 한다: 파일을 백업 폴더(그 볼륨의 Config.Msi, 사용자별 패키지는 임시 폴더)로 옮기고, 제거가 실패하면 되돌리고, 성공하면 지운다. 프로그램이 쥐고 있는 파일은 다음 재시작 때 지워진다. 업그레이드는 옛 판 자신의 행을 돌리므로, 이 키를 넣고 만든 첫 판부터 효과가 있다.

환경 변수: [env.ID]#

시스템(컴퓨터 전체) 변수다. mode = "set"(기본)은 값을 바꾸고, append/prepend 는 기존 값 끝에 ;value 를, 앞에 value; 를 붙인다. 제거는 정확히 그만큼 되돌린다: set 한 변수는 지우고, 붙인 부분은 떼어 내고 나머지는 둔다. keep = true 면 남긴다.

INI 파일: [ini.ID]#

mode = "set"(기본)은 파일의 [section] 에 key=value 를 쓰고, add 는 쉼표 목록에 값을 덧붙이며 (a 가 a,b 로), remove 는 설치 중에 키를 지운다. 제거하면 set 과 add 가 쓴 것을 뗀다. remove 는 파일 설치보다 먼저 실행되므로 옛 판이 남긴 INI 파일을 위한 것이다.

설치 프로그램 하나에 여러 패키지: [chain]#

[chain] 이 있는 원본은 패키지 대신 설치 프로그램을 만든다: rubrapack build suite.toml -o setup.exe. 설치 프로그램은 [chain-package.*] 패키지들을 원본에 적힌 차례로, 저마다의 SHA-256 과 함께 담는다. 실행하면 모두 확인한 뒤 Windows Installer 로 하나씩 차례로 설치한다. 이미 설치된 패키지(ProductCode 로 본다)는 건너뛴다. vital 패키지(기본)가 실패하면 체인이 멈추고 그 패키지의 오류를 돌려준다. 그 패키지는 스스로 되돌리고, 앞의 패키지들은 남는다. properties 는 msiexec 명령줄에서처럼 그 패키지에 건넨다.

병합 모듈: [merge.ID]#

병합 모듈(.msm)은 다른 회사가 자기 런타임이나 라이브러리를 위해 내놓는 설치 프로그램 조각이다. [merge.ID] 는 그 표들을 패키지에 옮겨 담는다: 모듈의 파일, 구성 요소, 레지스트리 값, 모듈 자신의 동작. 모듈의 뿌리 폴더는 dir 이 되고, 구성 요소는 feature(기본: 그 폴더의 기능, 없으면 Main)에 들어가며, 모듈의 캐비닛은 따로 두 번째 캐비닛으로 들어간다. 모듈의 표가 필요로 하는데 패키지에 없는 표준 동작(예를 들어 WriteRegistryValues)은 늘 쓰는 자리에 더한다. UTF-8 이 아닌 코드 페이지에 ASCII 밖의 글자가 있는 모듈은 합치지 않는다(RP1517). MSIX 에서 모듈은 파일(dir 아래, 또는 시스템 폴더 같은 표준 폴더로 가는 것은 가상 파일 시스템 안)과 레지스트리 값(패키지의 하이브 안)을 준다. 그 이상 - 사용자 지정 동작, 구성 요소의 조건, 환경 변수나 INI 항목, 서비스, 글꼴, COM 등록 - 이 필요한 모듈이나 설치 때 채우는 값은 거기서 거절한다(RP1517. msi-only = true 는 MSI 에만 둔다).

합친 모듈의 ModuleSignature 와 ModuleComponents 행은 Microsoft 의 병합 도구처럼 패키지에 남는다. 모듈이 ModuleIgnoreTable 에 적은 표는 들어가지 않는다.

설정할 수 있는 모듈(ModuleConfiguration 표가 있는 것)은 값 - 글, 수, 비트 묶음, 또는 모듈의 폴더 같은 키 - 을 묻고, config 가 항목마다 "이름=값" 문자열 하나로 준다:

[merge.Runtime]
source = "runtime.msm"
dir = "INSTALLDIR"
config = ["ServerName=example.com", "Port=8080", "DataDir=DataFolder.6E0A1C52_8F3B_4B7D_9A21_3C4D5E6F7A99"]

주지 않은 항목은 병합 도구가 답하지 않을 때처럼 모듈의 기본값을 쓴다. 값은 모듈의 ModuleSubstitution 표가 말하는 자리에, Microsoft 의 병합 도구가 넣는 방식대로 들어간다: 비트 묶음 항목은 마스크의 비트만 바꾸고, 키 항목은 [=Item;2] 로 키의 한 부분만 쓸 수 있고, 널 GUID 는 기능의 이름이 되며, 답을 받은 KeyNoOrphan 항목이 모두 대신한 기본 행은 빠진다. 키 값은 모듈 자신의 키(GUID 포함)로, 모듈의 이스케이프 꼴(세미콜론은 \;)로 쓴다. 모르는 항목, 수가 아닌 수 항목, 모듈이 값을 요구하는데 빈 값은 거절하고(RP1517), 설정을 받지 않는 모듈에 준 config 도 그렇다. 항목의 이름, 종류, 기본값은 모듈의 ModuleConfiguration 표에 있다: rubrapack inspect runtime.msm ModuleConfiguration.

병합 모듈 만들기: [module]#

[package] 대신 [module] 이 있는 원본은 남이 합칠 병합 모듈을 만든다: rubrapack build runtime.toml -o runtime.msm.

format = 1

[module]
name = "ExampleRuntime"
manufacturer = "Example"
version = "2.1.0"
arch = "x64"
id = "{6E0A1C52-8F3B-4B7D-9A21-3C4D5E6F7A99}"     # 모듈 자신의 GUID, 모든 판에서 그대로

[dir.RuntimeDir]
path = "$(TARGETDIR)/Example Runtime"             # TARGETDIR: 패키지가 모듈을 두는 곳

[files.Runtime]
dir = "RuntimeDir"
glob = "runtime/*"

[registry.Home]
root = "HKLM"
key = 'SOFTWARE\Example\Runtime'
name = "Home"
value = '$(RuntimeDir)'                           # 패키지가 정해 준 폴더

검색과 요구: [search.ID], [require.ID]#

검색은 무엇보다 먼저 실행되고 찾은 것을 공개 속성에 넣는다(못 찾으면 비어 있다): 레지스트리 값의 데이터, 파일의 전체 경로(path = 알려진 폴더와 상대 경로, 예 $(System) 이나 $(ProgramFiles)/Example; 프로그램 파일은 min-version), 폴더, 또는 다른 제품 구성 요소의 키 파일. 요구 사항은 condition 이 거짓이면 그 메시지로 첫 설치를 멈춘다(복구와 제거는 막지 않는다). 조건은 Windows Installer 의 문법을 쓰고 (VersionNT >= 603, FOUND_TOOL, NOT OLDSETTING) 검색 결과를 볼 수 있다.

[search.Tool]
property = "FOUND_TOOL"
kind = "file"
path = "$(System)"
file = "tool.exe"

[require.Tool]
condition = "FOUND_TOOL"
message = "[ProductName] needs tool.exe."

registry 나 dir 검색은 속성 대신 dir 을 가리킬 수 있다. 그러면 찾은 폴더가 그 dir 의 기본값이 된다. 사용자가 고른 폴더를 패키지가 기억하는 방법이 이것이다: 폴더를 적어 두고, 다음 판에서 찾아본다. 레지스트리 값은 그 폴더가 아직 있을 때만 쓰이고, 명령줄에 준 폴더(msiexec /i app.msi INSTALLDIR=D:\Apps\Example\)가 여전히 이긴다.

[registry.RememberDir]
root = "HKLM"
key = "Software\\Example"
name = "InstallDir"
value = "[INSTALLDIR]"

[search.PreviousDir]
property = "INSTALLDIR"           # dir: 레지스트리에 있는 폴더가 실제로 있으면 그것이 기본값
kind = "registry"
root = "HKLM"
key = "Software\\Example"
name = "InstallDir"

서비스, 글꼴, 권한#

[service.ID] 는 패키지의 .exe 가 돌리는 서비스를 설치한다: 파일이 바뀌기 전과 제거할 때 멈추고, 제거할 때 지우며, start-on-install = true 면 설치 뒤에 시작한다. [font.ID] 는 패키지가 Fonts 폴더에 설치하는 글꼴 파일을 등록한다. title 이 없으면 Windows 가 TrueType/OpenType 파일에서 이름을 읽는다. [permission.ID] 는 패키지가 만드는 폴더, 그 파일, 또는 그 레지스트리 값에 SDDL 보안 서술자를 건다(이것이 있으면 패키지는 Windows Installer 5.0 을 요구한다).

사용자별 패키지와 겸용 패키지: scope#

scope = "machine"(기본)은 모든 사용자에게 설치하고 관리자 권한이 필요하다. scope = "user" 는 권한 상승 없이 현재 사용자에게 설치한다: ProgramFiles 는 %LOCALAPPDATA%\Programs 가 되고, Programs 와 Desktop 은 그 사용자의 것, 레지스트리 값은 HKCU(또는 HKMU), 환경 변수는 사용자의 것이며 do/undo 동작은 그 사용자로 실행된다. 컴퓨터 전체 설치를 요청하면 거부한다. scope = "dual" 은 기본으로 사용자별로 설치하고, 관리자 명령창에서 msiexec /i x.msi ALLUSERS=1 MSIINSTALLPERUSER="" 로 하면 컴퓨터 전체로 설치한다. 그 레지스트리 값은 설치된 방식에 따라 HKLM 이나 HKCU 가 되는 HKMU 를 쓴다. 서비스, 글꼴, 권한과 컴퓨터 폴더(SystemRoot, System, Fonts, ProgramData)는 scope = "machine" 이 필요하다.

캐비닛#

파일은 패키지에 넣는 캐비닛 하나로 MSZIP(deflate)이나 LZX(compress = "lzx": 대개 5~20% 더 작고, MSZIP 이 처리기를 모두 쓰는 데 비해 처리기 하나로 초당 4 MB 쯤)로 압축한다. cab-max-size = N 은 파일이 N MiB 를 넘을 때마다 새 캐비닛을 시작하고, cab = "external" 은 캐비닛을 패키지 옆에 <name>.cab(또는 <name>-1.cab, <name>-2.cab, ...)으로 쓴다. 그 캐비닛은 패키지와 함께 다녀야 하고, cab-max-size 가 없으면 캐비닛마다 2 GiB 전에 나눈다. Windows Installer 는 2 GiB 이상인 패키지를 열지 못하므로, 그만큼 큰 내장 캐비닛은 cab = "external" 을 권하는 오류(RP1516)다. rubrapack 은 있는 캐비닛을 덮어쓰지 않고 패키지를 맨 나중에 쓴다: 패키지는 <out>.rp-map 에서 만들어지고(바이트가 메모리가 아니라 그 파일로 바로 간다) 모든 일이 성공했을 때만 제 이름으로 바뀌므로, 실패한 빌드는 패키지를 남기지 않는다. 압축은 모든 프로세서에서 돈다(덜 쓰려면 --jobs N); 어느 쪽이든 바이트는 같다. 모든 패키지에는 관리 설치(msiexec /a, 압축을 푼 네트워크 이미지)와 광고(msiexec /jm) 순서도 들어 있다.

MSI 서식 문자열로 해석되는 곳은 이것뿐이다: 레지스트리 value(와 multi 항목), 바로가기 args, 환경 변수 value, INI value, 요구 사항 message, 서비스 args. 나머지는 적은 그대로 쓴다.

설치된 앱 항목과 속성#

[arp] 는 제품이 "설정 > 설치된 앱"에 어떻게 보이는지 정한다(no-modify, no-repair, help, about). [property.NAME] 은 공개 속성을 더한다. secure = true 는 그 값이 설치의 관리자 권한 부분까지 가게 하고, hidden = true 는 로그에 값이 남지 않게 한다. 설치 엔진이나 rubrapack 이 스스로 쓰는 이름(ARP*, MSI*, RP_*, ALLUSERS, REBOOT 등)은 거부한다.

"설치된 앱"의 제거는 msiexec /qb /x {ProductCode} 를 돌린다: 패키지의 대화창이 아니라 Windows Installer 자신의 축소 창이고, 창이 있는 프로그램이 파일을 쥐고 있으면 자신의 사용 중인 파일 상자 - 영어이고 '취소'가 기본이다('무시'면 계속된다) - 를 띄운다. no-remove = true 는 그 제품의 제거를 끈다. 남는 수정은 패키지의 대화창을 돌리고(msiexec /i), 거기서 제거를 고르면 패키지 자신의 사용 중인 파일 창('계속'이 기본)으로 간다. 거의 모든 프로그램이 쥐는 입력기나 셸 확장에 쓸 만하다. [package] ui 가 minimal, installdir, features(제거 페이지가 있는 세트)여야 하고 no-modify 는 없어야 한다.

명령줄로 설치하기#

대화창이 고르는 것은 모두 msiexec 에 줄 수 있다.

속성하는 일
INSTALLDIR=D:\Apps\Example\설치 폴더(대문자 ID 의 dir 모두)
ADDLOCAL=Core,Extra이 기능들을 설치(ADDLOCAL=ALL: 모든 기능)
REMOVE=Extra설치된 제품에서 이 기능들을 제거(REMOVE=ALL: 전부)
INSTALLLEVEL=3level 이 3 이하인 기능을 모두 설치
RPLANGUAGE=ko대화창 언어([ui] languages 가 있을 때)
ALLUSERS=1 MSIINSTALLPERUSER=""겸용 패키지를 모든 사용자에게(기본: 현재 사용자만)
DESK=1, APP_MODE=server원본이 정한 속성: when 조건, 대화창 값

창 없이 설치하기#

rubrapack 이 만드는 패키지는 모두 명령줄에서 사용자 화면 없이 설치·복구·업그레이드·제거된다:

msiexec /i example.msi /qn /l*v install.log
msiexec /x {ProductCode} /qn

종료 코드 0 은 성공, 3010 은 다시 시작이 필요한 성공(rubrapack 은 컴퓨터를 스스로 다시 시작하지 않는다), 그 밖의 값은 실패이며 그 뒤 컴퓨터는 설치 전과 같다.

대화창: ui#

[package] 의 ui 가 내장 대화창 세트 하나를 고른다. 없으면(none) Windows Installer 자신의 진행 막대만 보인다.

ui설치할 때이미 설치되어 있을 때
basic진행, 끝(또는 오류)진행, 끝
minimal환영, 사용권(있으면), 진행, 끝복구 또는 제거
installdir환영, 사용권, 설치 폴더(폴더 찾아보기 포함), 준비, 진행, 끝복구 또는 제거
featuresinstalldir 에 더해 기능 트리와 필요한 디스크 공간복구 또는 제거

모든 세트에는 취소 확인, 오류 대화창, 사용 중인 파일 목록, 디스크 공간 부족 경고도 있다. 사용 중인 파일 목록은 창이 있는 프로그램이 바꾸거나 지울 파일을 쥐고 있을 때 나온다 - 입력기나 셸 확장이면 거의 모든 프로그램이다. 기본 단추는 '계속'이다: 파일은 곧바로 바뀌고, 이미 열린 프로그램은 다시 열 때까지 이전 것을 쓰며, reboot = "suppress"(기본)이면 다시 시작을 묻지 않는다(FilesInUseText; reboot = "allow" 면 Windows 가 다시 시작을 물을 수 있다고 적는 FilesInUseTextRestart). 대화창은 모두 기본값이 있는 값만 모으므로 /qn 은 여전히 창 없이 설치한다.

format = 1

[package]
ui = "installdir"
license = "LICENSE.txt"           # 동의할 때까지 설치/다음 단추가 꺼져 있다

[ui]
install-dir = "APPDIR"            # 사용자가 바꿀 수 있는 dir(기본 INSTALLDIR)
banner = "banner.bmp"             # 위쪽 띠; 약 493 x 58 화소

[ui-text.WelcomeText]
text = "This will install [ProductName]. Close other programs first."

.txt 나 .md 사용권은 평문으로 보여 준다(한글, 이모지 같은 어떤 글자도 그대로 둔다). .rtf 사용권은 그대로 쓴다. banner 가 없으면 띠는 흰색이다. [ui-text.ID] 는 문구 하나를 바꾼다. 문구는 MSI 서식 문자열이라 [ProductName] 은 치환되고 [ 자체는 [\[] 로 쓴다. ID 는 다음과 같다: Back, Next, Cancel, Install, Finish, OK, Yes, No, Retry, Ignore, Abort, Exit, Browse, WelcomeTitle, WelcomeText, LicenseTitle, LicenseText, LicenseAccept, DirTitle, DirText, DirLabel, BrowseTitle, BrowseText, BrowseLookIn, BrowseFolder, BrowseUp, BrowseNew, CustomizeTitle, CustomizeText, Reset, DiskCost, DiskCostTitle, DiskCostText, ReadyTitle, ReadyText, ProgressTitle, ProgressText, ProgressStatus, ExitTitle, ExitText, UserExitTitle, UserExitText, FatalTitle, FatalText, CancelText, FilesInUseTitle, FilesInUseText, FilesInUseTextRestart, Continue, OutOfDiskTitle, OutOfDiskText, MaintTitle, MaintText, Repair, RepairText, Remove, RemoveText, LanguageTitle, LanguageText, DirGuardText(아래 가드의 메시지), PreflightAsk, PreflightSilent, PreflightFolder, PreflightCache(사전 점검의 메시지), 그리고 제거할 때 - 유지보수 페이지의 제거, 또는 REMOVE=ALL - 쓰는 RemovalProgressTitle, RemovalExitTitle, RemovalExitText, RemovalUserExitTitle, RemovalUserExitText, RemovalFatalTitle, RemovalFatalText(이것을 주지 않는 다른 언어는 설치 문구를 쓴다). 단추 문구에서 & 는 바로 가기 키를 표시한다(&Next 는 Alt+N).

여러 언어#

대화창은 영어다. [ui] languages 는 같은 패키지에 다른 언어를 덧붙인다. 그러면 첫 페이지가 언어를 묻고, 그 뒤의 모든 페이지 - 환영, 사용권, 폴더, 사용자 페이지, 준비, 진행, 완료, 그리고 취소·오류·사용 중 파일·디스크 공간·유지보수 페이지 - 가 고른 언어로 나온다. 미리 골라 두는 언어는 사용자의 지역 형식 (UserLanguageID), 그다음 시스템 로캘(SystemLanguageID)이 LANGID 에 드는 첫 덧붙인 언어이고, 없으면 영어다. 명령줄의 RPLANGUAGE=ko 는 그 선택을 대신 정한다(언어 페이지는 그대로 나오고 그것이 골라져 있다). 무인 설치(/qn)는 아무것도 보여 주지 않으니 고를 것도 없다. 영어만 있으면 언어 페이지도 없다.

[ui]
languages = ["ko"]                # 영어는 늘 있고 기본이다
license-ko = "LICENSE-ko.txt"     # 언어별 사용권(기본: [package] license)

[ui-text.WelcomeText]
text-ko = "[ProductName]을(를) 설치합니다."     # text = 모든 언어, text-xx = 한 언어

[dialog.Options]
after = "RpInstallDirDlg"
title = "Options"
title-ko = "선택 사항"

[dialog-control.Mode]
# ...
labels = ["&Typical", "&Portable"]
labels-ko = ["표준(&T)", "휴대용(&P)"]

아이콘, 완료 페이지, 설치 범위 페이지#

나만의 대화창 페이지: [dialog.ID], [dialog-control.ID]#

minimal, installdir, features 에서는 내장 흐름에 페이지를 더할 수 있다. 더한 페이지도 다른 페이지와 같은 띠와 뒤로/다음/취소 단추를 갖고, 본문에 컨트롤을 놓는다.

[dialog.Options]
title = "Options"                 # 띠의 제목; 없으면 [ProductName]
description = "Choose how to install."
after = "RpInstallDirDlg"         # 이 페이지 바로 다음에 나온다

[dialog-control.ModeLabel]
dialog = "Options"
type = "text"
x = 20
y = 55
width = 330
height = 12
text = "Installation &mode:"

[dialog-control.Mode]
dialog = "Options"
type = "radio"
x = 20
y = 70
width = 200
height = 42                       # 값 하나에 12 이상
property = "APP_MODE"
values = ["typical", "portable", "server"]
labels = ["&Typical", "&Portable", "&Server"]

[property.APP_MODE]
value = "typical"                 # 기본값, 창 없는 설치에서도 쓰인다

아키텍처와 업그레이드 계열#

arch 는 원본 자신의 아키텍처, upgrade-code 는 그 업그레이드 계열이다. 같은 원본을 다른 아키텍처로 지으려면(--arch) 그 아키텍처에 upgrade-code-x86, upgrade-code-arm64, upgrade-code-x64 로 제 계열을 주어야 하고, 없으면 빌드를 거부한다. Windows Installer 의 업그레이드 감지는 아키텍처를 가리지 못해서, 코드를 같이 쓰면 한 아키텍처를 설치할 때 다른 아키텍처가 지워진다. MSIX 에는 업그레이드 코드가 없으므로 .msix·.msixbundle 빌드에는 필요 없다.

먼저 지워야 하는 판#

refuse-upgrade-below = "1.0.0" 은 설치된 1.0.0 미만 판의 업그레이드를 거부하고 먼저 지우라고 알린다. 메시지(refuse-upgrade-message, 또는 기본 문구)는 늘 그 일을 하는 명령으로 끝난다: msiexec /x {ProductCode} /qn MSIRESTARTMANAGERCONTROL=Disable. 옛 판을 다른 도구가 MSIRESTARTMANAGERCONTROL=Disable 없이 만들었을 때 쓴다: 그런 판을 업그레이드 도중에 지우면 그 파일을 불러 쓰는 프로그램을 모두 닫으려 한다(formats/msi-package.md 의 "사용 중인 파일" 참고).

진행하기 전에: 사전 점검#

설치·업그레이드·제거가 무엇이든 바꾸기 전에 패키지가 살핀다:

close-programs = "always" 는 묻지 않고 종료하고, "never" 는 종료하지도 묻지도 않는다(/qn 에서도). 기본은 "ask" 다. 입력기나 셸 확장에는 보통 "never" 를 쓴다: 열려 있는 거의 모든 프로그램이 그 DLL 을 싣고 있어 "ask" 면 업그레이드와 제거 때마다 질문이 나오고 조용한 실행은 매번 멈추는데, 파일은 아무것도 종료하지 않고도 안전하게 바뀐다(옛 사본은 정리 작업이 치운다). 명령줄의 RPCLOSE=yes 나 RPCLOSE=no 는 패키지의 설정보다 앞선다. preflight = false 는 이 단계를 통째로 뺀다. 문구는 PreflightAsk, PreflightSilent, PreflightFolder, PreflightCache 다([ui-text.*]; 영어와 한국어는 내장이고, 이것을 주지 않는 다른 언어는 영어를 쓴다).

Windows Installer 가 스스로 돌보는 것이 둘 있다: 다른 설치가 도는 동안 두 번째 설치는 곧바로 1618 로 끝나고, 중간에 끊긴 설치(충돌, 정전) 뒤의 다음 설치는 끝나지 않은 것을 먼저 되돌린다. 끊긴 제거 뒤에는 그 되돌리기가 제품을 도로 놓고 제거는 1605 로 끝난다: 한 번 더 제거하면 된다.

나중에 정리하기: 정리 작업#

실행 중인 프로그램이 쥐고 있는 파일은 곧바로 지울 수 없을 때가 있다. Windows Installer 는 옮길 수 있는 것은 옆으로 옮기고 나머지는 다음 재시작 때 지우도록 걸어 두는데, 입력기나 셸 확장이 있으면 컴퓨터가 몇 주씩 재시작하지 않을 수 있다. 그래서 파일을 지우거나 바꾸는 설치(제거, 업그레이드, 복구)는 예약 작업 rubrapack cleanup {ProductCode}(사용자별이면 뒤에 사용자의 SID)를 건다. 작업은 2분 뒤, 그다음엔 로그온 때마다와 15분마다 돌아서, 이 설치가 재시작 때로 미룬 것 - 패키지 폴더의 파일과 그 파일의 Config.Msi 백업 사본 - 을 아무도 쥐지 않게 되는 대로 지우고, 그 때문에만 남았던 패키지 폴더를 지운 뒤 자신도 지운다. 할 일이 없으면 첫 실행에서 사라지고, 30일이 지나면 그만두고 나머지는 재시작에 맡긴다. 컴퓨터별 패키지는 SYSTEM 으로, 사용자별 패키지는 그 사용자로(권한 상승 없이) 돌고, 그 계정만 쓸 수 있는 폴더(%ProgramData%\rubrapack\cleanup\{ProductCode}, 또는 사용자의 %LOCALAPPDATA%)에서 돈다. 컴퓨터별의 경우 그 자리의 rubrapack 폴더를 다른 누가 먼저 만들어 두었으면 쓰지 않고 작업도 걸지 않는다. 다른 것은 지우지 않는다: 이 설치가 걸어 둔 것만, 같은 파일이 아직 그 자리에 있을 때만, 그리고 설치된 제품이 지금 그 자리에 가진 파일은 지우지 않는다. [package] cleanup = false 면 넣지 않는다.

컴퓨터별 패키지는 앞선 제거로부터 자기 파일도 지킨다: 다른 제품을 지울 때 프로그램이 그 파일을 쥐고 있었으면 그 파일의 삭제가 재시작 때로 걸려 남는데, 이 패키지가 같은 자리에 똑같은 파일을 설치하면 Windows Installer 는 이미 있는 파일을 그대로 둔다 - 그러면 재시작이 그 파일을 지운다. 패키지는 설치하면서 그런 삭제를 거둬들인다(설치가 실패하면 다시 걸어 둔다).

따로 제거 프로그램은 없다: Windows 의 "설치된 앱"은 MSI 를 Windows Installer 로 지우고, 제거가 남긴 것은 정리 작업이 맡는다.

제품과 함께 지워지는 추가 기능: parent, remove-addons#

추가 기능은 다른 제품을 넓히는 따로 된 패키지다 - 언어 팩, 플러그인 - 그 제품 없이는 쓸모가 없다. 추가 기능은 본체 제품을 업그레이드 코드로 적는다: [package] parent = "{...}". 설치하면 자기 제품 코드를 SOFTWARE\rubrapack\Addons\<그 업그레이드 코드> 에 적고(컴퓨터별은 HKLM, 사용자별은 HKCU), 지우면 그 값도 지운다. 본체는 remove-addons = true 로 둔다: 본체를 정말로 지울 때(업그레이드가 바꿔 넣을 때가 아니라) 곧바로 도는 정리 작업이 제거가 끝나기를 기다렸다가, 거기 적힌 추가 기능 가운데 아직 설치된 것을 msiexec /x {ProductCode} /qn 으로 지운다 - 본체를 어느 길로 지웠든(설치된 앱, 관리 화면, msiexec /x). 그 순간 지울 수 없는 추가 기능(다른 설치가 도는 중)은 작업의 다음 실행에서 다시 해 본다. Windows Installer 는 한 번에 두 패키지를 지울 수 없으므로 추가 기능은 본체보다 먼저가 아니라 몇 초 뒤에 지워진다 - 본체 없이도 지워지게 만든다. 이 키로 구운 본체의 첫 판부터 효과가 있고, 정리 작업이 있어야 한다(cleanup = false 와 함께면 오류). 두 키 모두 MSI 패키지에만 쓴다.

다른 제품을 대신하기: replaces#

replaces = ["{UpgradeCode}", ...](16개까지)는 이 패키지가 대신하는 제품들이다 - 이를테면 따로 된 패키지였다가 이제 이 패키지의 기능이 된 추가 기능. 이 패키지를 설치하면 그 제품들의 설치된 판을 모두 같은 실행에서 지운다 - 자기 옛 판을 지우듯이(RemoveExistingProducts; 그들 자신의 제거가 UPGRADINGPRODUCTCODE 와 함께 돈다). 그래서 같은 파일의 주인이 둘이 되지 않는다. 이 패키지의 업그레이드 코드나 parent 는 쓸 수 없다. MSI 에만 쓴다.

와일드카드: [files.ID]#

glob 은 *(한 폴더 안의 아무 글자들), ?(한 글자), **(폴더 여러 단계)가 든 원본 경로다. 맞는 것은 이름순으로 정렬하므로 결과가 파일 시스템에 따라 달라지지 않는다. 첫 와일드카드 아래의 폴더는 dir 아래에 다시 만든다: glob = "dist/layouts/**/*.jmt" 는 dist/layouts/de/x.jmt 를 <dir>/de/x.jmt 로 설치한다. 맞는 것이 없거나, 심볼릭 링크이거나, 출력 파일 자신이 맞으면 오류다.

빈 폴더: [folder.ID]#

파일이 하나도 들지 않아도 dir 안에 name 폴더를 만든다. keep = true 면 제거한 뒤에도 폴더가 남는다(프로그램이 쓰는 데이터를 위해).

기능(feature)#

[feature.*] 표가 없으면 모든 것이 숨은 기능 하나에 든다. 기능을 하나라도 선언하면 모든 파일에 기능이 있어야 한다: 파일 자신의 feature 키, 또는 그 dir 의 feature. level 이 1 보다 큰 기능은 기본으로 설치하지 않는다. ui = "features" 면 사용자가 트리에서 기능을 고르고, 나중에 "설치된 앱"에서 바꿀 수 있다(유지보수 페이지의 "변경").

조건: when#

when 은 Windows Installer 조건(VersionNT64, DESK = "1", NOT OLDVERSION)을 받는다. 기능, 파일과 파일 묶음, 레지스트리 값(파일과 with 로 묶인 값은 안 된다: 파일 쪽에 둔다), 바로가기, 환경 변수, INI 값에 쓸 수 있다. 조건이 참일 때만 그것을 설치한다. 나만의 대화창 페이지에서 정한 속성, 명령줄에 준 속성, 검색 결과를 볼 수 있다. 조건은 그것을 처음 설치할 때(메이저 업그레이드 포함) 따진다 - 복구는 있는 그대로 둔다. MSIX 는 모두 설치하므로 when 을 거부한다.

[dialog-control.Desk]             # 나만의 페이지의 체크박스
dialog = "Options"
type = "checkbox"
x = 20
y = 60
width = 300
height = 16
text = "바탕화면 바로 가기 만들기(&D)"
property = "DESK"

[shortcut.Desk]
dir = "Desktop"
name = "Example"
target = "file:App"
when = "DESK"

제거할 때 파일 남기기: keep#

파일(또는 파일 묶음)에 keep = true 를 두면 제품을 제거해도 그 파일은 남는다 - 사용자가 바꿨을 수 있는 설정 파일용이다. 한 번 바뀐 파일은 복구나 다음 판이 덮어쓰지 않는다(판 없는 파일에 대한 Windows Installer 의 규칙). MSIX 는 파일을 모두 지우므로 keep 을 거부한다.

변수#

문자열에 이름을 넣는 방법은 $(NAME) 하나뿐이고, $$ 는 $ 한 글자다. 이름은 한 번만 바뀌고 그 결과는 다시 읽지 않는다($(X) 가 든 값은 그대로 남는다). 이름은 다음 가운데 하나다.

빌드 변수에는 Windows 이름이나 dir ID 를 붙일 수 없다(RP1404).

Windows 이름#

Windows 폴더와 환경 변수의 이름이고, Windows 의 철자로 쓴다. 대소문자는 가리지 않는다($(appdata) 는 $(APPDATA)). 값 안에서는 Windows Installer 가 설치할 때 설치하는 사용자의 것으로 채우는 것이 된다 - type = "expand" 레지스트리 값에서는, 읽는 프로그램이 실행할 때 실행하는 사용자의 것으로 푸는 환경 변수가 된다:

이름경로 기준설치할 때(64비트 / 32비트 패키지)실행할 때(type = "expand")
ProgramFiles예[ProgramFiles64Folder] / [ProgramFilesFolder]%ProgramFiles%
ProgramFiles(x86)예[ProgramFilesFolder]%ProgramFiles(x86)%
ProgramW6432-[ProgramFiles64Folder] / [%ProgramW6432]%ProgramW6432%
CommonProgramFiles예[CommonFiles64Folder] / [CommonFilesFolder]%CommonProgramFiles%
CommonProgramFiles(x86)-[CommonFilesFolder]%CommonProgramFiles(x86)%
CommonProgramW6432-[CommonFiles64Folder] / [%CommonProgramW6432]%CommonProgramW6432%
ProgramData, ALLUSERSPROFILE예[CommonAppDataFolder]%ProgramData% ...
APPDATA예[AppDataFolder]%APPDATA%
LOCALAPPDATA예[LocalAppDataFolder]%LOCALAPPDATA%
TEMP, TMP예[TempFolder]%TEMP% ...
SystemRoot, windir예[WindowsFolder]%SystemRoot% ...
System예[System64Folder] / [SystemFolder]-
Fonts, Desktop, StartMenu, Programs, Startup예[FontsFolder], [DesktopFolder], [StartMenuFolder], [ProgramMenuFolder], [StartupFolder]-
USERNAME-[LogonUser]%USERNAME%
COMPUTERNAME-[ComputerName]%COMPUTERNAME%
SystemDrive, USERPROFILE, PUBLIC, HOMEDRIVE, HOMEPATH, USERDOMAIN, LOGONSERVER, ComSpec, Path, PATHEXT, OS, PROCESSOR_ARCHITECTURE, NUMBER_OF_PROCESSORS-[%NAME]%NAME%

값 안의 dir ID 는 [ID], 곧 설치할 때의 그 폴더가 된다. 폴더의 Windows Installer 값은 \ 로 끝나므로 폴더 이름 바로 뒤의 \ 하나는 빠진다: '$(INSTALLDIR)\app.exe' 는 [INSTALLDIR]app.exe 가 된다. 환경 변수가 없는 폴더는 type = "expand" 값에 쓸 수 없다(RP1404). MSIX 에는 설치할 때가 없다: 그것이 필요한 값은 거기서 거부하고(RP1612, RP1613), 펼칠 수 있는 값 안의 %NAME% 은 그대로 쓸 수 있다.

있는 그대로 쓴 [NAME] 과 %NAME% 은 바뀌지 않고 지나간다. $(...) 이름이 없는 것을 위해서다: [#FileID], [ProductVersion], 글자 [ 를 뜻하는 [\[], 파일 형식 인수의 %1.

프로그램 파일#

PE 파일(.exe, .dll 등)은 검사한다: 머신 형식이 arch 와 맞아야 하고(x64 패키지의 x86 도우미는 any-arch = true 가 필요하다), 판 정보 리소스가 패키지 안 파일의 판이 된다. Windows Installer 는 그것을 보고 설치된 파일을 바꿀지 정한다.

설치 폴더 지키기#

다른 프로그램이 그 파일을 불러 쓰는 프로그램 - 입력기, 셸 확장, 서비스 - 은 남이 미리 만들어 둔 폴더에 설치되면 안 된다: 거기 있던 파일은 그대로 남고, 프로그램 옆에 심어 둔 DLL 이 그 프로그램의 권한으로 실리기 때문이다. dir 에 guard = true 를 두면, 그 폴더가 다음과 같을 때 첫 설치가 파일을 하나도 놓기 전에 멈춘다.

믿을 수 있는 주인의 폴더(예: Program Files) 아래의 아직 없는 폴더는 통과한다(설치 엔진이 만들고, [permission.*] 로 잠글 수 있다). 복구, 제자리 업그레이드, 제거는 검사하지 않는다. 메이저 업그레이드는 새 판의 첫 설치라 똑같이 검사한다(이전 판이 만든 폴더는 통과한다). 설치는 DirGuardText 메시지([1] 은 그 폴더; 대화창에서 고른 언어) 뒤에 1603 으로 끝나고, /qn 에서도 같으며, 로그에 찾은 주인이 적힌다. 검사는 rubrapack 의 도우미 DLL 이 하고, 패키지가 그 DLL 을 싣는다(REG_QWORD 값과 같다). 보는 것은 주인이다: 관리자 소유라도 권한이 누구나 쓸 수 있게 되어 있는 폴더는 거부하지 않는다([permission.*] 로 잠근다).

[dir.INSTALLDIR]
path = "$(ProgramFiles)/Example"
guard = true

경로#

설치될 경로 - [dir.*] 와 [search.*] 의 path - 는 폴더로 시작한다: dir ID 나 경로 기준이 되는 Windows 이름을 "$(ProgramFiles)/Example", "$(INSTALLDIR)/docs" 처럼 쓰고, 그다음 / 와 그 아래 폴더들을 쓴다. 뒤에는 빌드 변수가 와도 된다("$(INSTALLDIR)/v$(VERSION)").

원본 경로는 원본 파일 기준의 상대 경로이고 / 를 쓴다. 절대 경로, \, 심볼릭 링크, 없는 파일은 오류다. 설치될 이름에는 Windows 가 금하는 것(< > : " / \ | ? *, 제어 문자, 끝의 점이나 공백, CON 같은 장치 이름)만 빼고 어떤 유니코드 글자든 쓸 수 있고, 한 폴더 안의 두 이름이 대소문자만 달라서는 안 된다. Windows Installer 는 이름을 8.3 짧은 이름과 함께 UTF-16 255단위에 저장하므로, 긴 이름은 짧은 이름이 남긴 만큼만 쓸 수 있다: 세 글자 확장자의 name.ext 는 242단위, 확장자 없는 이름은 246단위(NTFS 는 255 를 받는다). 더 긴 이름은 RP1514 로 거부한다.

20.6 명령줄#

rubrapack build <src.toml> -o <out.msi|out.msm|out.msix|out.msixbundle> [-D NAME=VALUE]...
                [--arch x64|arm64|x86 | --arch <목록> (.msixbundle)]
                [--compress none|mszip|mszip:N|lzx|lzx:N] [--jobs N] [--nfc] [--reproducible]
                [<키> [--cert <chain.pem>]
                 [--timestamp <URL> [--tsa-trust <인증서>] [--tls-trust <인증서>] [--system-roots]
                  [--proxy <URL>]] [--allow-unsigned-cabs]]
                [--unsigned-test] [--msix-compress deflate|store]      (.msix, .msixbundle)
rubrapack inspect <file.msi> [table | --summary | --files | --streams]
rubrapack inspect <file.msi> [table | --summary] --json
rubrapack inspect <file.msix|file.msixbundle> [--files | --manifest]
rubrapack inspect <file.cab>
rubrapack new [msi] <name>
rubrapack new [<file>.toml] [-i]
rubrapack new <name> [--dist <folder>] [--name ...] [--ui ...] [--optional ...] ...
rubrapack edit <file>.toml [--set <table>.<key>=<value>]... [--unset <table>.<key>]... [--sync]
rubrapack guid [--from <text>]
rubrapack lint <src.toml> [-D NAME=VALUE]... [--arch x64|arm64|x86] [--target msi|msix] [--nfc] [--strict] [--json]
rubrapack lint <file.msi> [--previous <old.msi>] [--strict] [--json]
rubrapack explain [RPnnnn]
rubrapack schema
rubrapack lint <file.msix|file.msixbundle> [--strict]
rubrapack extract <file.msi|file.msix|file.msixbundle|file.cab> -d <새 폴더> [--limit-entries N] [--limit-bytes N]
rubrapack sign <file.exe|.dll|.msi|.msp|.msix|.msixbundle> <키> [--cert <chain.pem>]
               [--timestamp <URL> [--tsa-trust <인증서>] [--tls-trust <인증서>] [--system-roots]
                [--proxy <URL>]] [--allow-unsigned-cabs] [-o <out>]
rubrapack keys list [--pkcs11 <모듈> [--token-label <이름>] [--pin-env VAR | --pin-file FILE]]
rubrapack verify <file.exe|.dll|.msi|.msp|.msix|.msixbundle> [--trust <인증서>]... [--system-roots] [--tsa-trust <인증서>]...
rubrapack version | help [command]

<키> 는 다음 가운데 하나:
  --key <key.pfx|.pem> [--pass-env VAR | --pass-file FILE]                   키 파일
  --pkcs11 <모듈> --key-label <이름> [--token-label <이름>]
           [--pin-env VAR | --pin-file FILE]                                  PKCS#11 토큰 안의 키
  --key-store <SHA-1 지문> [--machine-store]                                  Windows 저장소의 키

20.7 진단 코드#

rubrapack 이 알리는 모든 문제에는 파일, 줄, 열 다음에 코드가 붙는다: error[RPnnnn] 또는 warning[RPnnnn]. 앞의 두 자리가 문제의 종류를 말한다:

코드무엇이 잘못됐나볼 곳
RP00xx명령줄: 모르는 명령이나 옵션, 읽거나 쓸 수 없는 파일, 변환이 나를 수 없는 차이(RP0013)rubrapack help <명령>
RP10xx원본 파일의 인코딩: UTF-8(또는 BOM 있는 UTF-16)이 아님, 짝 없는 캐리지 리턴파일을 UTF-8 로 저장한다
RP11xxrubrapack 이 읽는 부분집합 밖의 TOML: 여러 줄 문자열, 인라인 표, 두 번 정의한 표; format 이 없거나 너무 새 것(RP1108)TOML 부분집합, 원본 형식
RP12xx표와 키: 모르는 표나 키(제안과 함께), 빠진 필수 키나 표, 기능이 생긴 뒤 기능 없는 항목표
RP13xx값: ID(모든 표에 걸쳐 유일, 예약어 아님), GUID, 판, 범위를 벗어난 수, 없는 것을 가리키는 참조그 표의 절
RP14xx변수: 값 없는 $(NAME), 닫히지 않은 $(, 쓸 수 없는 자리의 Windows 이름이나 dir ID변수
RP15xx설치할 파일: 없음, 폴더임, 링크임, 맞는 것 없는 글롭, 다른 아키텍처의 프로그램, 너무 긴 이름, 빌드 중에 바뀐 파일, 너무 큰 패키지경로, 프로그램 파일
RP16xxMSIX: MSIX 패키지에 필요한 것, 담을 수 없는 것MSIX 패키지
RP19xx이 rubrapack 이 제공하지 않는 기능-
RP20xx완성된 표가 Windows Installer 규칙을 어김. RP1xxx 검사를 통과한 원본은 여기에 걸리지 않아야 한다: 알려 주기 바란다-
RP21xx패키지 lint: 대화창, 코드 페이지, 글자 정규화명령줄의 lint
RP22xxMSIX 패키지나 묶음의 lint명령줄의 lint
RP23xxlint --previous: 이 패키지가 앞 판을 깨끗하게 업그레이드하지 못함판과 업그레이드

무엇을 고칠지는 메시지가 말한다. 매뉴얼에서 찾을 때는 위의 코드 범위를 쓴다.