Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ pkg install llvm

readelf は llvm パッケージが提供します / readelf is provided by the llvm package

On first run (when setup runs), `opencode-termux` checks for missing prerequisites: `glibc`, `patchelf`, `libc++`, `llvm` (for readelf), and `tar`. If any are missing, it shows the commands it will run and asks for confirmation (type `y`) before installing them with `pkg install`. If you answer `n`, or if the terminal is non-interactive, it installs nothing, shows the commands to run manually, and exits. If `glibc` is missing, `glibc-repo` (which adds the repository configuration) is installed first. Changes to the repository configuration are persistent, and there is no guarantee they can be reverted. If you need to remove them, check the effects on dependencies first, then remove them manually. It does not run a full upgrade (`pkg update && pkg upgrade`) automatically. For problems it cannot fix automatically (for example, package link errors or a loader that cannot run), it shows the commands to run manually and exits.

初回セットアップの実行時、`opencode-termux` は不足している前提パッケージ(`glibc`、`patchelf`、`libc++`、`llvm`(readelf 用)、`tar`)を検査します。不足があれば、実行するコマンドを表示し、確認(`y` を入力)のうえで `pkg install` により導入します。`n` と答えた場合、または対話できない(非対話の)端末の場合は、何も導入せず、手動で実行するコマンドを案内して終了します。`glibc` が無い場合は、先に `glibc-repo`(リポジトリ設定の追加)を導入します。リポジトリ設定の変更は永続的で、元に戻す保証はありません。削除が必要な場合は、依存関係などへの影響を確認したうえで、手動で行ってください。全体更新(`pkg update && pkg upgrade`)は自動では実行しません。パッケージのリンクエラーや実行できないローダーなど、自動では直せない問題の場合は、手動で実行するコマンドを案内して終了します。

### If setup fails with `cannot locate symbol "_ZNSt6__ndk1..."` / セットアップが `cannot locate symbol` で失敗する場合

On first run, `opencode` may stop with an error like this:
Expand Down
163 changes: 136 additions & 27 deletions lib/preflight.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,66 +13,90 @@ const PM_SCRIPT = TERMUX_USR + '/bin/termux-setup-package-manager';
const GLIBC_LD_PATH = TERMUX_USR + '/glibc/lib/ld-linux-aarch64.so.1';

// 自動修復の固定順序
const PKG_ORDER = ['patchelf', 'libc++', 'llvm'];
const PKG_ORDER = ['glibc', 'patchelf', 'libc++', 'llvm', 'tar'];

// 問題コードごとの固定値
const PROBLEMS = {
'glibc-missing': {
auto: false,
pkgs: [],
auto: true,
pkgs: ['glibc'],
repoPkgs: ['glibc-repo'],
ja: 'glibc のローダーが見つかりません',
en: 'glibc loader not found',
commands: ['pkg install glibc-repo && pkg install glibc'],
},
'glibc-broken': {
auto: false,
pkgs: [],
repoPkgs: [],
ja: 'glibc のローダーを実行できません',
en: 'glibc loader cannot be executed',
commands: ['pkg install glibc-repo && pkg install glibc'],
},
'patchelf-missing': {
auto: true,
pkgs: ['patchelf', 'libc++'],
repoPkgs: [],
ja: 'patchelf が見つかりません',
en: 'patchelf not found',
commands: ['pkg install patchelf libc++'],
},
'patchelf-libcxx-mismatch': {
auto: true,
pkgs: ['libc++'],
repoPkgs: [],
ja: 'patchelf が libc++ の不一致で起動できません(libc++ が古い可能性があります)',
en: 'patchelf cannot start due to a libc++ mismatch (the installed libc++ may be too old)',
commands: ['pkg install libc++'],
},
'patchelf-link-error': {
auto: false,
pkgs: [],
repoPkgs: [],
ja: 'patchelf のリンクエラーです。『pkg update && pkg upgrade』は全パッケージを更新します',
en: "patchelf failed to link. 'pkg update && pkg upgrade' updates ALL installed packages",
commands: ['pkg update && pkg upgrade'],
},
'patchelf-failed': {
auto: false,
pkgs: [],
repoPkgs: [],
ja: 'patchelf を実行できません',
en: 'patchelf cannot be executed',
commands: ['pkg install patchelf libc++'],
},
'readelf-missing': {
auto: true,
pkgs: ['llvm'],
repoPkgs: [],
ja: 'readelf が見つかりません',
en: 'readelf not found',
commands: ['pkg install llvm'],
},
'readelf-failed': {
auto: false,
pkgs: [],
repoPkgs: [],
ja: 'readelf を実行できません',
en: 'readelf cannot be executed',
commands: ['pkg install llvm'],
},
'tar-missing': {
auto: true,
pkgs: ['tar'],
repoPkgs: [],
ja: 'tar が見つかりません',
en: 'tar not found',
commands: ['pkg install tar'],
},
'tar-failed': {
auto: false,
pkgs: [],
repoPkgs: [],
ja: 'tar を実行できません',
en: 'tar cannot be executed',
commands: ['pkg install tar'],
},
};

// 原文の先頭を最大 2000 文字に切る
Expand All @@ -88,6 +112,7 @@ function makeProblem(code, detail = '') {
code,
auto: p.auto,
pkgs: [...p.pkgs],
repoPkgs: [...p.repoPkgs],
ja: p.ja,
en: p.en,
commands: [...p.commands],
Expand Down Expand Up @@ -133,16 +158,26 @@ class PrereqError extends Error {
}
}

// 自動修復の許可リストを作る。手動のみが1件でもあれば null
// 自動修復の許可リストを作る。自動対象が無ければ null。手動案内は manual に分けて返す
function buildAutoFix(problems) {
if (!Array.isArray(problems) || problems.length === 0) return null;
if (problems.some((p) => p.auto === false)) return null;
const set = new Set();
for (const p of problems) {
for (const pkg of p.pkgs) set.add(pkg);
const auto = problems.filter((p) => p.auto === true);
if (auto.length === 0) return null;
const pkgSet = new Set();
const repoSet = new Set();
for (const p of auto) {
for (const pkg of p.pkgs) pkgSet.add(pkg);
for (const repo of p.repoPkgs) repoSet.add(repo);
}
const pkgs = PKG_ORDER.filter((pkg) => set.has(pkg));
return { pkgs, args: ['install', ...pkgs] };
const pkgs = PKG_ORDER.filter((pkg) => pkgSet.has(pkg));
const repoPkgs = [...repoSet];
const steps =
repoPkgs.length > 0
? [{ args: ['install', ...repoPkgs] }, { args: ['install', ...pkgs] }]
: [{ args: ['install', ...pkgs] }];
const args = [...steps[steps.length - 1].args];
const manual = problems.filter((p) => p.auto === false);
return { steps, pkgs, args, manual };
}

// 例外の原文(message と stderr)を1つの文字列にする
Expand Down Expand Up @@ -205,6 +240,14 @@ function checkPrerequisites(io = {}) {
problems.push(makeProblem(code, processText(e)));
}

// tar: 見つからない場合と実行失敗を分ける
try {
spawn('tar', ['--version'], opts);
} catch (e) {
const code = e && e.code === 'ENOENT' ? 'tar-missing' : 'tar-failed';
problems.push(makeProblem(code, processText(e)));
}

return problems;
}

Expand Down Expand Up @@ -232,6 +275,33 @@ function pkgFailedLead(state) {
);
}

// 段の失敗見出し(index は 0 始まりの段番号。state は終了状態の文字列)
function stageFailedLead(index, command, state) {
if (index === 0) {
return (
'1 段目 ' + command + ' が終了状態 ' + state + ' で失敗しました。変更がなかった保証はありません。以降の段は実行していません。表示済みの出力を参照してください / ' +
'Stage 1 (' + command + ') failed (' + state + '). It is not guaranteed that nothing changed. Later stages were not run. See the output shown above. Manual commands:'
);
}
return (
'1 段目は成功し、リポジトリ設定などが変更されている可能性があります。' + (index + 1) + ' 段目 ' + command + ' が終了状態 ' + state + ' で失敗しました。表示済みの出力を参照してください / ' +
'Earlier stages succeeded and repository settings may have been changed. Stage ' + (index + 1) + ' (' + command + ') failed (' + state + '). See the output shown above. Manual commands:'
);
}

// 実行した段の一覧と、glibc のローダーが残っている場合の診断(自動では再実行しない)
function glibcRemainingNote(steps) {
const lines = ['実行した段と終了状態 / Stages run and exit status:'];
steps.forEach((step, i) => {
lines.push(' ' + (i + 1) + '. ' + TERMUX_PKG + ' ' + step.args.join(' ') + ' → status: 0');
});
lines.push(
'glibc のローダーが残っています。glibc パッケージは入っていてもローダーだけ欠けている場合があります。状態を確認し、必要なら pkg reinstall glibc を検討してください(自動では再実行しません) / ' +
'The glibc loader is still missing. The glibc package may be installed while only the loader is absent. Check its state and consider pkg reinstall glibc if needed (it is not run automatically).'
);
return lines.join('\n') + '\n';
}

// 手順 1: 全問題の説明と手動 commands を表示する文面
function describeProblems(problems) {
const lines = ['[opencode-termux] 前提条件の問題 / Prerequisite problems'];
Expand All @@ -242,11 +312,12 @@ function describeProblems(problems) {
return lines.join('\n') + '\n';
}

// 最終の手動案内: 問題の説明は入れず、問題コードとコマンドだけを並べる
// 最終の手動案内: 問題コードと説明、その手動 commands を並べる
function manualText(lead, problems) {
const lines = [lead];
for (const p of problems) {
for (const cmd of p.commands) lines.push(' - [' + p.code + '] ' + cmd);
lines.push(' - [' + p.code + '] ' + p.ja + ' / ' + p.en);
for (const cmd of p.commands) lines.push(' 手動 / Manual: ' + cmd);
}
return lines.join('\n') + '\n';
}
Expand Down Expand Up @@ -283,12 +354,39 @@ const CONSENT_NOTES = [
],
];

// リポジトリ追加(glibc-repo)を含む場合の注意書き(ja, en の組)
const REPO_NOTES = [
[
'glibc-repo の導入はリポジトリ設定を永続的に変更します。既に導入済みの場合は新規追加とは限りません。',
'Installing glibc-repo permanently changes the repository settings. If it is already installed, this may not be a new addition.',
],
[
'元の状態に完全に戻せる保証はありません。削除が必要な場合は、依存関係などの影響を確認したうえで手動で行ってください。',
'There is no guarantee that the original state can be fully restored. If removal is needed, check the impact on dependencies first and do it manually.',
],
];

// 同意画面(手順 5)
function consentText(problems, fix) {
const lines = ['[opencode-termux] 次の前提条件を自動で修復します / Automatic repair of prerequisites'];
for (const p of problems) lines.push('- ' + p.ja + ' / ' + p.en);
lines.push('実行するコマンド / Command: ' + TERMUX_PKG + ' ' + fix.args.join(' '));
for (const p of problems) if (p.auto === true) lines.push('- ' + p.ja + ' / ' + p.en);
if (fix.manual.length > 0) {
lines.push('手動で対応が必要な項目(自動では実行しません) / Items that need manual action (not run automatically):');
for (const p of fix.manual) {
lines.push('- ' + p.ja + ' / ' + p.en);
for (const cmd of p.commands) lines.push(' 手動 / Manual: ' + cmd);
}
}
if (fix.steps.length === 1) {
lines.push('実行するコマンド / Command: ' + TERMUX_PKG + ' ' + fix.steps[0].args.join(' '));
} else {
lines.push('実行するコマンド(上から順に実行します) / Commands (run in this order):');
fix.steps.forEach((step, i) => lines.push(' ' + (i + 1) + '. ' + TERMUX_PKG + ' ' + step.args.join(' ')));
}
for (const [ja, en] of CONSENT_NOTES) lines.push('注意 / Note: ' + ja + ' / ' + en);
if (fix.steps.length >= 2) {
for (const [ja, en] of REPO_NOTES) lines.push('注意 / Note: ' + ja + ' / ' + en);
}
return lines.join('\n') + '\n';
}

Expand Down Expand Up @@ -411,23 +509,34 @@ async function resolvePrerequisites(problems, io = {}) {
throw manualError(1, LEAD.noInput, problems);
}

// 7. 固定パスの pkg を実行する(PATH の内容に依らない)
const r = runSync(TERMUX_PKG, fix.args, {
stdio: 'inherit',
env: { ...env, TERMUX_PKG_NO_MIRROR_SELECT: '1' },
});
if (r.error || r.signal || r.status !== 0) {
let state;
if (r.error) state = 'error: ' + (r.error.message || String(r.error));
else if (r.signal) state = 'signal: ' + r.signal;
else state = 'status: ' + r.status;
throw manualError(1, pkgFailedLead(state), problems);
// 7. 固定パスの pkg を段ごとに先頭から実行する(PATH の内容に依らない)。段が失敗したら後続の段は実行しない
for (let i = 0; i < fix.steps.length; i++) {
const step = fix.steps[i];
const r = runSync(TERMUX_PKG, step.args, {
stdio: 'inherit',
env: { ...env, TERMUX_PKG_NO_MIRROR_SELECT: '1' },
});
if (r.error || r.signal || r.status !== 0) {
let state;
if (r.error) state = 'error: ' + (r.error.message || String(r.error));
else if (r.signal) state = 'signal: ' + r.signal;
else state = 'status: ' + r.status;
const lead =
fix.steps.length === 1
? pkgFailedLead(state)
: stageFailedLead(i, TERMUX_PKG + ' ' + step.args.join(' '), state);
throw manualError(1, lead, problems);
}
}

// 8. 再検査は1回だけ。残れば再実行せず手動案内で終わる
// 8. 再検査は全段の後に1回だけ。残れば再実行せず手動案内で終わる
const remaining = check(io);
if (remaining.length === 0) return;
throw manualError(1, LEAD.remaining, remaining);
let lead = LEAD.remaining;
if (remaining.some((p) => p.code === 'glibc-missing')) {
lead = LEAD.remaining + '\n' + glibcRemainingNote(fix.steps).replace(/\n$/, '');
}
throw manualError(1, lead, remaining);
}

// 前提を調べ、問題があれば解決を試みる
Expand Down
Loading
Loading