Add GUIDE section for v3 docs

This commit is contained in:
crazywhalecc
2026-04-19 18:01:56 +08:00
parent a348e838d7
commit 2045055591
31 changed files with 2156 additions and 518 deletions

1
.gitignore vendored
View File

@@ -52,6 +52,7 @@ packlib_files.txt
/node_modules/
/docs/.vitepress/dist/
/docs/.vitepress/cache/
/docs/.vitepress/ext-data.json
package-lock.json
pnpm-lock.yaml

View File

@@ -1,9 +1,14 @@
<template>
<div>
<div v-if="missing" class="warning custom-block" style="margin-bottom: 16px">
<p class="custom-block-title">WARNING</p>
<p>Extension list is not generated yet. Run <code>bin/spc dev:gen-ext-docs</code> to generate it.</p>
</div>
<h2>{{ I18N[lang].selectedSystem }}</h2>
<div class="option-line">
<span v-for="(item, index) in osList" :key="index" style="margin-right: 8px">
<input type="radio" :id="'os-' + item.os" :value="item.os" :disabled="item.disabled === true" v-model="selectedSystem" />
<input type="radio" :id="'os-' + item.os" :value="item.os" v-model="selectedSystem" />
<label :for="'os-' + item.os">{{ item.label }}</label>
</span>
</div>
@@ -13,13 +18,14 @@
<option value="aarch64" :disabled="selectedSystem === 'windows'">aarch64</option>
</select>
</div>
<h2>{{ I18N[lang].selectExt }}{{ checkedExts.length > 0 ? (' (' + checkedExts.length + ')') : '' }}</h2>
<div class="box">
<input class="input" v-model="filterText" :placeholder="I18N[lang].searchPlaceholder" />
<br>
<div v-for="item in extFilter" :key="item" class="ext-item">
<div v-for="item in extByOS" :key="item" class="ext-item">
<span>
<input type="checkbox" :id="item" :value="item" v-model="checkedExts" :disabled="extDisableList.indexOf(item) !== -1">
<input type="checkbox" :id="item" :value="item" v-model="checkedExts" />
<label :for="item">
<span>{{ highlightItem(item, 0) }}</span>
<span style="color: orangered; font-weight: bolder">{{ highlightItem(item, 1) }}</span>
@@ -32,50 +38,31 @@
<div class="my-btn" v-if="selectedSystem !== 'windows'" @click="selectAll">{{ I18N[lang].selectAll }}</div>
<div class="my-btn" @click="checkedExts = []">{{ I18N[lang].selectNone }}</div>
<details class="details custom-block" open>
<summary>{{ I18N[lang].buildLibs }}{{ checkedLibs.length > 0 ? (' (' + checkedLibs.length + ')') : '' }}</summary>
<div class="box">
<div v-for="(item, index) in libContain" :key="index" class="ext-item">
<input type="checkbox" :id="index" :value="item" v-model="checkedLibs" :disabled="libDisableList.indexOf(item) !== -1">
<label :for="index">{{ item }}</label>
</div>
</div>
</details>
<div class="tip custom-block">
<p class="custom-block-title">TIP</p>
<p>{{ I18N[lang].depTips }}</p>
<p>{{ I18N[lang].depTips2 }}</p>
</div>
<h2>{{ I18N[lang].buildTarget }}</h2>
<div class="box">
<div v-for="item in TARGET" :key="item" class="ext-item">
<input type="checkbox" :id="'build_' + item" :value="item" v-model="checkedTargets" @change="onTargetChange">
<input type="checkbox" :id="'build_' + item" :value="item" v-model="checkedTargets" />
<label :for="'build_' + item">{{ item }}</label>
</div>
</div>
<div v-if="selectedPhpVersion === '7.4' && (checkedTargets.indexOf('micro') !== -1 || checkedTargets.indexOf('all') !== -1)" class="warning custom-block">
<p class="custom-block-title">WARNING</p>
<p>{{ I18N[lang].microUnavailable }}</p>
</div>
<div v-if="selectedSystem === 'windows' && (checkedTargets.indexOf('fpm') !== -1 || checkedTargets.indexOf('embed') !== -1 || checkedTargets.indexOf('frankenphp') !== -1)" class="warning custom-block">
<div v-if="selectedSystem === 'windows' && (checkedTargets.includes('fpm') || checkedTargets.includes('embed') || checkedTargets.includes('frankenphp'))" class="warning custom-block">
<p class="custom-block-title">WARNING</p>
<p>{{ I18N[lang].windowsSAPIUnavailable }}</p>
</div>
<h2>{{ I18N[lang].buildOptions }}</h2>
<!-- Refactor all build options in table -->
<table>
<!-- buildEnvironment -->
<!-- Build Environment -->
<tr>
<td>{{ I18N[lang].buildEnvironment }}</td>
<td>
<select v-model="selectedEnv">
<option value="native">{{ I18N[lang].buildEnvNative }}</option>
<option value="spc">{{ I18N[lang].buildEnvSpc }}</option>
<option value="docker" v-if="selectedSystem !== 'windows'">{{ I18N[lang].buildEnvDocker }}</option>
<option value="native">{{ I18N[lang].buildEnvNative }}</option>
</select>
</td>
</tr>
<!-- Download PHP version -->
<!-- PHP Version -->
<tr>
<td>{{ I18N[lang].downloadPhpVersion }}</td>
<td>
@@ -84,70 +71,85 @@
</select>
</td>
</tr>
<!-- Enable debug message -->
<!-- Verbose log -->
<tr>
<td>{{ I18N[lang].useDebug }}</td>
<td>{{ I18N[lang].useVerbose }}</td>
<td>
<input type="radio" id="debug-yes" :value="1" v-model="debug" />
<label for="debug-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="debug-no" :value="0" v-model="debug" />
<label for="debug-no">{{ I18N[lang].no }}</label>
<select v-model="verbosity">
<option value="">{{ I18N[lang].verboseNone }}</option>
<option value="-v">-v</option>
<option value="-vv">-vv</option>
<option value="-vvv">-vvv</option>
</select>
</td>
</tr>
<!-- Enable ZTS -->
<tr>
<td>{{ I18N[lang].useZTS }}</td>
<td>
<input type="radio" id="zts-yes" :value="1" v-model="zts" />
<input type="radio" id="zts-yes" :value="true" v-model="zts" />
<label for="zts-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="zts-no" :value="0" v-model="zts" />
<input type="radio" id="zts-no" :value="false" v-model="zts" />
<label for="zts-no">{{ I18N[lang].no }}</label>
</td>
</tr>
<!-- download corresponding extensions -->
<!-- Parallel downloads -->
<tr>
<td>{{ I18N[lang].resultShowDownload }}</td>
<td>{{ I18N[lang].dlParallel }}</td>
<td>
<input type="radio" id="show-download-yes" :value="1" v-model="downloadByExt" />
<label for="show-download-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="show-download-no" :value="0" v-model="downloadByExt" />
<label for="show-download-no">{{ I18N[lang].no }}</label>
<input class="number-input" type="number" v-model.number="dlParallel" min="1" max="50" />
</td>
</tr>
<!-- Download pre-built -->
<!-- Retry count -->
<tr>
<td>{{ I18N[lang].dlRetry }}</td>
<td>
<input class="number-input" type="number" v-model.number="dlRetry" min="0" max="100" />
</td>
</tr>
<!-- Prefer binary (pre-built) -->
<tr>
<td>{{ I18N[lang].usePreBuilt }}</td>
<td>
<input type="radio" id="pre-built-yes" :value="1" v-model="preBuilt" />
<input type="radio" id="pre-built-yes" :value="true" v-model="preBuilt" />
<label for="pre-built-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="pre-built-no" :value="0" v-model="preBuilt" />
<input type="radio" id="pre-built-no" :value="false" v-model="preBuilt" />
<label for="pre-built-no">{{ I18N[lang].no }}</label>
</td>
</tr>
<!-- Enable UPX -->
<!-- Enable UPX (linux/windows only) -->
<tr v-if="selectedSystem !== 'macos'">
<td>{{ I18N[lang].useUPX }}</td>
<td>
<input type="radio" id="upx-yes" :value="1" v-model="enableUPX" />
<input type="radio" id="upx-yes" :value="true" v-model="enableUPX" />
<label for="upx-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="upx-no" :value="0" v-model="enableUPX" />
<input type="radio" id="upx-no" :value="false" v-model="enableUPX" />
<label for="upx-no">{{ I18N[lang].no }}</label>
</td>
</tr>
<!-- Keep debug symbols (--no-strip) -->
<tr>
<td>{{ I18N[lang].noStrip }}</td>
<td>
<input type="radio" id="nostrip-yes" :value="true" v-model="noStrip" />
<label for="nostrip-yes">{{ I18N[lang].yes }}</label>
<input type="radio" id="nostrip-no" :value="false" v-model="noStrip" />
<label for="nostrip-no">{{ I18N[lang].no }}</label>
</td>
</tr>
</table>
<h2>{{ I18N[lang].hardcodedINI }}</h2>
<textarea class="textarea" :placeholder="I18N[lang].hardcodedINIPlacehoder" v-model="hardcodedINIData" rows="5" />
<textarea class="textarea" :placeholder="I18N[lang].hardcodedINIPlaceholder" v-model="hardcodedINIData" rows="5" />
<h2>{{ I18N[lang].resultShow }}</h2>
<!-- SPC Binary Download Command -->
<!-- SPC Binary Download Command (spc env only) -->
<div v-if="selectedEnv === 'spc'" class="command-container">
<b>{{ I18N[lang].downloadSPCBinaryCommand }}</b>
<div v-if="selectedSystem !== 'windows'" class="command-preview">
<div class="command-content">
{{ spcDownloadCommand }}
</div>
<button class="copy-btn" @click="copyToClipboard(spcDownloadCommand)" :class="{ 'copied': copiedStates.spcDownload }">
<div class="command-content">{{ spcDownloadCommand }}</div>
<button class="copy-btn" @click="copyToClipboard(spcDownloadCommand, 'spcDownload')" :class="{ 'copied': copiedStates.spcDownload }">
{{ copiedStates.spcDownload ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
@@ -155,56 +157,17 @@
<div class="warning custom-block">
<p class="custom-block-title">WARNING</p>
<p>{{ I18N[lang].windowsDownSPCWarning }}</p>
<a href="https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe" target="_blank">https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe</a>
<a href="https://dl.static-php.dev/v3/spc-bin/latest/spc-windows-x86_64.exe" target="_blank">https://dl.static-php.dev/v3/spc-bin/latest/spc-windows-x86_64.exe</a>
</div>
</div>
</div>
<!-- Download Commands -->
<div v-if="downloadByExt" class="command-container">
<b>{{ I18N[lang].downloadExtOnlyCommand }}</b>
<div class="command-preview">
<div class="command-content">
{{ downloadExtCommand }}
</div>
<button class="copy-btn" @click="copyToClipboard(downloadExtCommand)" :class="{ 'copied': copiedStates.downloadExt }">
{{ copiedStates.downloadExt ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
</div>
<div v-else class="command-container">
<b>{{ I18N[lang].downloadAllCommand }}</b>
<div class="command-preview">
<div class="command-content">
{{ downloadAllCommand }}
</div>
<button class="copy-btn" @click="copyToClipboard(downloadAllCommand)" :class="{ 'copied': copiedStates.downloadAll }">
{{ copiedStates.downloadAll ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
</div>
<!-- UPX Download Command -->
<div class="command-container" v-if="enableUPX">
<b>{{ I18N[lang].downloadUPXCommand }}</b>
<div class="command-preview">
<div class="command-content">
{{ downloadPkgCommand }}
</div>
<button class="copy-btn" @click="copyToClipboard(downloadPkgCommand)" :class="{ 'copied': copiedStates.downloadPkg }">
{{ copiedStates.downloadPkg ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
</div>
<!-- Doctor Command -->
<div class="command-container">
<b>{{ I18N[lang].doctorCommand }}</b>
<div class="command-preview">
<div class="command-content">
{{ doctorCommandString }}
</div>
<button class="copy-btn" @click="copyToClipboard(doctorCommandString)" :class="{ 'copied': copiedStates.doctor }">
<div class="command-content">{{ doctorCommandString }}</div>
<button class="copy-btn" @click="copyToClipboard(doctorCommandString, 'doctor')" :class="{ 'copied': copiedStates.doctor }">
{{ copiedStates.doctor ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
@@ -214,23 +177,19 @@
<div class="command-container">
<b>{{ I18N[lang].compileCommand }}</b>
<div class="command-preview">
<div class="command-content">
{{ buildCommandString }}
</div>
<button class="copy-btn" @click="copyToClipboard(buildCommandString)" :class="{ 'copied': copiedStates.build }">
<div class="command-content">{{ buildCommandString }}</div>
<button class="copy-btn" @click="copyToClipboard(buildCommandString, 'build')" :class="{ 'copied': copiedStates.build }">
{{ copiedStates.build ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
</div>
<!-- Craft.yml -->
<!-- craft.yml -->
<div class="command-container">
<b>craft.yml</b>
<div class="command-preview pre">
<div class="command-content">
{{ craftCommandString }}
</div>
<button class="copy-btn" @click="copyToClipboard(craftCommandString)" :class="{ 'copied': copiedStates.craft }">
<div class="command-content">{{ craftCommandString }}</div>
<button class="copy-btn" @click="copyToClipboard(craftCommandString, 'craft')" :class="{ 'copied': copiedStates.craft }">
{{ copiedStates.craft ? I18N[lang].copied : I18N[lang].copy }}
</button>
</div>
@@ -245,14 +204,12 @@ export default {
</script>
<script setup lang="ts">
import {computed, ref, watch} from "vue";
import extData from '../config/ext.json';
import libData from '../config/lib.json';
import { getAllExtLibsByDeps } from './DependencyUtil.js';
import { computed, ref, watch } from 'vue';
// @ts-ignore VitePress data loader — transformed at build time
import { data as extDataRaw } from '../extensions.data.js';
// Constants
const OS_MAP = new Map([['linux', 'Linux'], ['macos', 'Darwin'], ['windows', 'Windows']]);
const TARGET = ['cli', 'fpm', 'micro', 'embed', 'frankenphp', 'all'];
const TARGET = ['cli', 'fpm', 'micro', 'embed', 'frankenphp', 'cgi'];
const availablePhpVersions = ['8.0', '8.1', '8.2', '8.3', '8.4', '8.5'];
// Props
@@ -263,154 +220,135 @@ const props = defineProps({
}
});
// Reactive data
const ext = ref(extData);
const lib = ref(libData);
const libContain = ref([]);
// Extension data
const missing = extDataRaw.missing ?? false;
const allExtensions: Array<{ name: string; linux: boolean; macos: boolean; windows: boolean }> = extDataRaw.extensions ?? [];
// Reactive state
const filterText = ref('');
const checkedExts = ref([]);
const checkedLibs = ref([]);
const extDisableList = ref([]);
const libDisableList = ref([]);
const checkedTargets = ref(['cli']);
const selectedEnv = ref('spc');
const checkedExts = ref<string[]>([]);
const checkedTargets = ref<string[]>(['cli']);
const selectedEnv = ref<'spc' | 'native'>('spc');
const selectedPhpVersion = ref('8.4');
const selectedSystem = ref('linux');
const selectedArch = ref('x86_64');
const debug = ref(0);
const zts = ref(0);
const downloadByExt = ref(1);
const preBuilt = ref(1);
const enableUPX = ref(0);
const selectedSystem = ref<'linux' | 'macos' | 'windows'>('linux');
const selectedArch = ref<'x86_64' | 'aarch64'>('x86_64');
const verbosity = ref('');
const zts = ref(false);
const preBuilt = ref(true);
const enableUPX = ref(false);
const noStrip = ref(false);
const dlParallel = ref(10);
const dlRetry = ref(5);
const hardcodedINIData = ref('');
const buildCommand = ref('--build-cli');
// Copy states
const copiedStates = ref({
const copiedStates = ref<Record<string, boolean>>({
spcDownload: false,
downloadExt: false,
downloadAll: false,
downloadPkg: false,
doctor: false,
build: false,
craft: false,
doctor: false
});
// OS list
const osList = [
{ os: 'linux', label: 'Linux', disabled: false },
{ os: 'macos', label: 'macOS', disabled: false },
{ os: 'windows', label: 'Windows', disabled: false },
{ os: 'linux', label: 'Linux' },
{ os: 'macos', label: 'macOS' },
{ os: 'windows', label: 'Windows' },
];
// Computed properties
const extFilter = computed(() => {
return Object.entries(ext.value)
.filter(([name]) => isSupported(name, selectedSystem.value))
.map(([name]) => name);
// Computed: extensions filtered by selected OS
const extByOS = computed(() => {
return allExtensions
.filter(item => {
if (selectedSystem.value === 'linux') return item.linux;
if (selectedSystem.value === 'macos') return item.macos;
if (selectedSystem.value === 'windows') return item.windows;
return true;
})
.map(item => item.name);
});
const extList = computed(() => checkedExts.value.join(','));
const additionalLibs = computed(() => {
const ls = checkedLibs.value.filter(item => libDisableList.value.indexOf(item) === -1);
return ls.length > 0 ? ` --with-libs="${ls.join(',')}"` : '';
});
const extList = computed(() => [...checkedExts.value].sort().join(','));
const spcCommand = computed(() => {
switch (selectedEnv.value) {
case 'native':
return 'bin/spc';
case 'spc':
return selectedSystem.value === 'windows' ? '.\\spc.exe' : './spc';
case 'docker':
return 'bin/spc-alpine-docker';
default:
return '';
}
if (selectedEnv.value === 'native') return 'bin/spc';
return selectedSystem.value === 'windows' ? '.\\spc.exe' : './spc';
});
const spcDownloadCommand = computed(() => {
if (selectedSystem.value === 'windows') return '';
return `curl -fsSL -o spc.tgz https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-${selectedSystem.value}-${selectedArch.value}.tar.gz && tar -zxvf spc.tgz && rm spc.tgz`;
const os = selectedSystem.value === 'macos' ? 'macos' : 'linux';
const arch = selectedArch.value;
return `curl -#fSL https://dl.static-php.dev/v3/spc-bin/latest/spc-${os}-${arch} -o spc && chmod +x spc`;
});
const doctorCommandString = computed(() => `${spcCommand.value} doctor --auto-fix`);
const displayINI = computed(() => {
const split = hardcodedINIData.value.split('\n');
const validLines = split.filter(x => x.indexOf('=') >= 1);
return validLines.length > 0 ? ' ' + validLines.map(x => `-I "${x}"`).join(' ') : '';
});
const downloadAllCommand = computed(() => {
return `${spcCommand.value} download --all --with-php=${selectedPhpVersion.value}${preBuilt.value ? ' --prefer-pre-built' : ''}${debug.value ? ' --debug' : ''}`;
});
const downloadExtCommand = computed(() => {
return `${spcCommand.value} download --with-php=${selectedPhpVersion.value} --for-extensions "${extList.value}"${preBuilt.value ? ' --prefer-pre-built' : ''}${debug.value ? ' --debug' : ''}`;
});
const downloadPkgCommand = computed(() => {
return `${spcCommand.value} install-pkg upx${debug.value ? ' --debug' : ''}`;
});
const doctorCommandString = computed(() => {
return `${spcCommand.value} doctor --auto-fix${debug.value ? ' --debug' : ''}`;
const lines = hardcodedINIData.value.split('\n').filter(x => x.indexOf('=') >= 1);
return lines.length > 0 ? ' ' + lines.map(x => `-I "${x}"`).join(' ') : '';
});
const buildCommandString = computed(() => {
return `${spcCommand.value} build ${buildCommand.value} "${extList.value}"${additionalLibs.value}${debug.value ? ' --debug' : ''}${zts.value ? ' --enable-zts' : ''}${enableUPX.value ? ' --with-upx-pack' : ''}${displayINI.value}`;
const sapi = checkedTargets.value.map(x => `--build-${x}`).join(' ');
const php = ` --dl-with-php=${selectedPhpVersion.value}`;
const parallel = ` --dl-parallel=${dlParallel.value}`;
const retry = ` --dl-retry=${dlRetry.value}`;
const ignoreCache = ' --dl-ignore-cache=php-src';
const binary = preBuilt.value ? ' --dl-prefer-binary' : '';
const strip = noStrip.value ? ' --no-strip' : '';
const upx = enableUPX.value ? ' --with-upx-pack' : '';
const ztsFlag = zts.value ? ' --enable-zts' : '';
const verbose = verbosity.value ? ` ${verbosity.value}` : '';
return `${spcCommand.value} build:php "${extList.value}" ${sapi}${php}${parallel}${retry}${ignoreCache}${binary}${strip}${upx}${ztsFlag}${displayINI.value}${verbose}`;
});
const craftCommandString = computed(() => {
let str = `php-version: ${selectedPhpVersion.value}\n`;
str += `extensions: "${extList.value}"\n`;
if (checkedTargets.value.join(',') === 'all') {
str += 'sapi: ' + ['cli', 'fpm', 'micro', 'embed', 'frankenphp'].join(',') + '\n';
// sapi
if (checkedTargets.value.length === 1) {
str += `sapi:\n - ${checkedTargets.value[0]}\n`;
} else {
str += `sapi: ${checkedTargets.value.join(',')}\n`;
str += `sapi:\n`;
checkedTargets.value.forEach(s => { str += ` - ${s}\n`; });
}
if (additionalLibs.value) {
str += `libs: ${additionalLibs.value.replace('--with-libs="', '').replace('"', '').trim()}\n`;
// verbosity (Symfony OutputInterface constants: 64=-v, 128=-vv, 256=-vvv)
const verbosityMap: Record<string, number> = { '-v': 64, '-vv': 128, '-vvv': 256 };
if (verbosity.value && verbosityMap[verbosity.value]) {
str += `verbosity: ${verbosityMap[verbosity.value]}\n`;
}
if (debug.value) {
str += 'debug: true\n';
// download-options
str += `download-options:\n`;
str += ` parallel: ${dlParallel.value}\n`;
str += ` retry: ${dlRetry.value}\n`;
str += ` ignore-cache: php-src\n`;
if (preBuilt.value) str += ` prefer-binary: true\n`;
// build-options (only when needed)
const buildOpts: string[] = [];
if (noStrip.value) buildOpts.push(` no-strip: true`);
if (enableUPX.value) buildOpts.push(` with-upx-pack: true`);
if (zts.value) buildOpts.push(` enable-zts: true`);
const iniLines = hardcodedINIData.value.split('\n').filter(x => x.indexOf('=') >= 1);
if (iniLines.length > 0) {
buildOpts.push(` with-hardcoded-ini:`);
iniLines.forEach(line => buildOpts.push(` - "${line}"`));
}
if (preBuilt.value) {
str += 'download-options:\n';
str += ' prefer-pre-built: true\n';
}
str += '{{position_hold}}';
if (enableUPX.value) {
str += ' with-upx-pack: true\n';
}
if (zts.value) {
str += ' enable-zts: true\n';
}
if (!str.endsWith('{{position_hold}}')) {
str = str.replace('{{position_hold}}', 'build-options:\n');
} else {
str = str.replace('{{position_hold}}', '');
if (buildOpts.length > 0) {
str += `build-options:\n${buildOpts.join('\n')}\n`;
}
return str;
});
// Methods
const isSupported = (extName: string, os: string) => {
const osName = OS_MAP.get(os);
const osSupport = ext.value[extName]?.support?.[osName] ?? 'yes';
return osSupport === 'yes' || osSupport === 'partial';
};
const selectCommon = () => {
checkedExts.value = [
const common = [
'apcu', 'bcmath', 'calendar', 'ctype', 'curl', 'dba', 'dom', 'exif',
'filter', 'fileinfo', 'gd', 'iconv', 'intl', 'mbstring', 'mbregex',
'mysqli', 'mysqlnd', 'openssl', 'opcache', 'pcntl', 'pdo', 'pdo_mysql',
@@ -418,30 +356,18 @@ const selectCommon = () => {
'session', 'simplexml', 'sockets', 'sodium', 'sqlite3', 'tokenizer',
'xml', 'xmlreader', 'xmlwriter', 'xsl', 'zip', 'zlib',
];
const supported = new Set(extByOS.value);
checkedExts.value = common.filter(e => supported.has(e));
};
const selectAll = () => {
checkedExts.value = [...extFilter.value];
};
const onTargetChange = (event: Event) => {
const target = (event.target as HTMLInputElement).value;
if (target === 'all') {
checkedTargets.value = ['all'];
} else {
const allIndex = checkedTargets.value.indexOf('all');
if (allIndex !== -1) {
checkedTargets.value.splice(allIndex, 1);
}
}
buildCommand.value = checkedTargets.value.map(x => `--build-${x}`).join(' ');
checkedExts.value = [...extByOS.value];
};
const highlightItem = (item: string, step: number) => {
if (!filterText.value || !item.includes(filterText.value)) {
return step === 0 ? item : '';
}
const index = item.indexOf(filterText.value);
switch (step) {
case 0: return item.substring(0, index);
@@ -451,30 +377,12 @@ const highlightItem = (item: string, step: number) => {
}
};
const copyToClipboard = async (text: string) => {
const copyToClipboard = async (text: string, key: string) => {
try {
await navigator.clipboard.writeText(text);
// Find which command was copied and update its state
const commandMap = {
[spcDownloadCommand.value]: 'spcDownload',
[downloadExtCommand.value]: 'downloadExt',
[downloadAllCommand.value]: 'downloadAll',
[downloadPkgCommand.value]: 'downloadPkg',
[doctorCommandString.value]: 'doctor',
[buildCommandString.value]: 'build',
[craftCommandString.value]: 'craft'
};
const key = commandMap[text];
if (key) {
copiedStates.value[key] = true;
setTimeout(() => {
copiedStates.value[key] = false;
}, 2000);
}
} catch (err) {
console.error('Failed to copy text: ', err);
// Fallback for older browsers
copiedStates.value[key] = true;
setTimeout(() => { copiedStates.value[key] = false; }, 2000);
} catch {
const textArea = document.createElement('textarea');
textArea.value = text;
document.body.appendChild(textArea);
@@ -484,113 +392,17 @@ const copyToClipboard = async (text: string) => {
}
};
const calculateExtDepends = (input: string[]) => {
const result = new Set<string>();
const dfs = (node: string) => {
let depends: string[] = [];
if (selectedSystem.value === 'linux') {
depends = ext.value[node]?.['ext-depends-linux'] ?? ext.value[node]?.['ext-depends-unix'] ?? ext.value[node]?.['ext-depends'] ?? [];
} else if (selectedSystem.value === 'macos') {
depends = ext.value[node]?.['ext-depends-macos'] ?? ext.value[node]?.['ext-depends-unix'] ?? ext.value[node]?.['ext-depends'] ?? [];
} else if (selectedSystem.value === 'windows') {
depends = ext.value[node]?.['ext-depends-windows'] ?? ext.value[node]?.['ext-depends'] ?? [];
}
if (depends.length === 0) return;
depends.forEach(dep => {
result.add(dep);
dfs(dep);
});
};
input.forEach(dfs);
return Array.from(result);
};
const calculateExtLibDepends = (input: string[]) => {
const result = new Set<string>();
const dfsLib = (node: string) => {
let depends: string[] = [];
if (selectedSystem.value === 'linux') {
depends = lib.value[node]?.['lib-depends-linux'] ?? lib.value[node]?.['lib-depends-unix'] ?? lib.value[node]?.['lib-depends'] ?? [];
} else if (selectedSystem.value === 'macos') {
depends = lib.value[node]?.['lib-depends-macos'] ?? lib.value[node]?.['lib-depends-unix'] ?? lib.value[node]?.['lib-depends'] ?? [];
} else if (selectedSystem.value === 'windows') {
depends = lib.value[node]?.['lib-depends-windows'] ?? lib.value[node]?.['lib-depends'] ?? [];
}
if (depends.length === 0) return;
depends.forEach(dep => {
result.add(dep);
dfsLib(dep);
});
};
const dfsExt = (node: string) => {
let depends: string[] = [];
if (selectedSystem.value === 'linux') {
depends = ext.value[node]?.['lib-depends-linux'] ?? ext.value[node]?.['lib-depends-unix'] ?? ext.value[node]?.['lib-depends'] ?? [];
} else if (selectedSystem.value === 'macos') {
depends = ext.value[node]?.['lib-depends-macos'] ?? ext.value[node]?.['lib-depends-unix'] ?? ext.value[node]?.['lib-depends'] ?? [];
} else if (selectedSystem.value === 'windows') {
depends = ext.value[node]?.['lib-depends-windows'] ?? ext.value[node]?.['lib-depends'] ?? [];
}
if (depends.length === 0) return;
depends.forEach(dep => {
result.add(dep);
dfsLib(dep);
});
};
input.forEach(dfsExt);
return Array.from(result);
};
// Watchers
watch(selectedSystem, () => {
if (selectedSystem.value === 'windows') {
selectedArch.value = 'x86_64';
enableUPX.value = false;
}
// Reset related values when OS changes
checkedExts.value = [];
enableUPX.value = 0;
});
watch(checkedExts, (newValue) => {
// Apply ext-depends
extDisableList.value = calculateExtDepends(newValue);
extDisableList.value.forEach(x => {
if (checkedExts.value.indexOf(x) === -1) {
checkedExts.value.push(x);
}
});
checkedExts.value.sort();
const calculated = getAllExtLibsByDeps({ ext: ext.value, lib: lib.value, os: selectedSystem.value }, checkedExts.value);
libContain.value = calculated.libs.sort();
// Apply lib-depends
checkedLibs.value = [];
libDisableList.value = calculateExtLibDepends(calculated.exts);
libDisableList.value.forEach(x => {
if (checkedLibs.value.indexOf(x) === -1) {
checkedLibs.value.push(x);
}
});
}, { deep: true });
// I18N
const I18N = {
const I18N: Record<string, Record<string, string>> = {
zh: {
selectExt: '选择扩展',
buildTarget: '选择编译目标',
@@ -598,8 +410,8 @@ const I18N = {
buildEnvironment: '编译环境',
buildEnvNative: '本地构建Git 源码)',
buildEnvSpc: '本地构建(独立 spc 二进制)',
buildEnvDocker: 'Alpine Docker 构建',
useDebug: '是否开启调试输出',
useVerbose: '是否输出详细日志',
verboseNone: '不输出(默认)',
yes: '是',
no: '否',
resultShow: '结果展示',
@@ -608,28 +420,22 @@ const I18N = {
selectNone: '全部取消选择',
useZTS: '是否编译线程安全版',
hardcodedINI: '硬编码 INI 选项',
hardcodedINIPlacehoder: '如需要硬编码 ini每行写一个例如memory_limit=2G',
resultShowDownload: '是否展示仅下载对应扩展依赖的命令',
downloadExtOnlyCommand: '只下载对应扩展的依赖包命令',
downloadAllCommand: '下载所有依赖包命令',
downloadUPXCommand: '下载 UPX 命令',
hardcodedINIPlaceholder: '如需要硬编码 ini每行写一个例如memory_limit=2G',
compileCommand: '编译命令',
downloadPhpVersion: '下载 PHP 版本',
downloadSPCBinaryCommand: '下载 spc 二进制命令',
selectedArch: '选择系统架构',
selectedSystem: '选择操作系统',
buildLibs: '要构建的库',
depTips: '选择扩展后,不可选中的项目为必需的依赖,编译的依赖库列表中可选的为现有扩展和依赖库的可选依赖。选择可选依赖后,将生成 --with-libs 参数。',
depTips2: '无法同时构建所有扩展,因为有些扩展之间相互冲突。请根据需要选择扩展。',
microUnavailable: 'micro 不支持 PHP 7.4 及更早版本!',
windowsSAPIUnavailable: 'Windows 目前不支持 fpm、embed、frankenphp 构建!',
useUPX: '是否开启 UPX 压缩(减小二进制体积)',
windowsDownSPCWarning: 'Windows 下请手动下载 spc.exe 二进制文件,解压到当前目录并重命名为 spc.exe',
usePreBuilt: '如果可能,下载预编译的依赖库(减少编译时间)',
windowsDownSPCWarning: 'Windows 下请手动下载 spc.exe 二进制文件!',
usePreBuilt: '如果可能,使用预编译的依赖库(减少编译时间)',
searchPlaceholder: '搜索扩展...',
copy: '复制',
copied: '已复制',
doctorCommand: '自动检查和准备构建环境命令',
dlParallel: '并行下载数1-50',
dlRetry: '失败重试次数',
noStrip: '保留调试符号(--no-strip',
},
en: {
selectExt: 'Select Extensions',
@@ -638,43 +444,58 @@ const I18N = {
buildEnvironment: 'Build Environment',
buildEnvNative: 'Native build (Git source code)',
buildEnvSpc: 'Native build (standalone spc binary)',
buildEnvDocker: 'Alpine docker build',
useDebug: 'Enable debug message',
useVerbose: 'Verbose log output',
verboseNone: 'None (default)',
yes: 'Yes',
no: 'No',
resultShow: 'Result',
selectCommon: 'Select common extensions',
selectAll: 'Select all',
selectNone: 'Unselect all',
useZTS: 'Enable ZTS',
useZTS: 'Enable ZTS (thread-safe)',
hardcodedINI: 'Hardcoded INI options',
hardcodedINIPlacehoder: 'If you need to hardcode ini, write one per line, for example: memory_limit=2G',
resultShowDownload: 'Download with corresponding extension dependencies',
downloadExtOnlyCommand: 'Download sources by extensions command',
downloadAllCommand: 'Download all sources command',
downloadUPXCommand: 'Download UPX command',
compileCommand: 'Compile command',
downloadPhpVersion: 'Download PHP version',
downloadSPCBinaryCommand: 'Download spc binary command',
selectedArch: 'Select build architecture',
selectedSystem: 'Select Build OS',
buildLibs: 'Select Dependencies',
depTips: 'After selecting the extensions, the unselectable items are essential dependencies. In the compiled dependencies list, optional dependencies consist of existing extensions and optional dependencies of libraries. Optional dependencies will be added in --with-libs parameter.',
depTips2: 'It is not possible to build all extensions at the same time, as some extensions conflict with each other. Please select the extensions you need.',
microUnavailable: 'Micro does not support PHP 7.4 and earlier versions!',
hardcodedINIPlaceholder: 'If you need to hardcode ini, write one per line, for example: memory_limit=2G',
compileCommand: 'Build command',
downloadPhpVersion: 'PHP version',
downloadSPCBinaryCommand: 'Download spc binary',
selectedSystem: 'Select OS',
windowsSAPIUnavailable: 'Windows does not support fpm, embed and frankenphp build!',
useUPX: 'Enable UPX compression (reduce binary size)',
windowsDownSPCWarning: 'Please download the binary file manually, extract it to the current directory and rename to spc.exe on Windows!',
usePreBuilt: 'Download pre-built dependencies if possible (reduce compile time)',
windowsDownSPCWarning: 'Please download the spc.exe binary manually on Windows!',
usePreBuilt: 'Use pre-built dependencies where available (reduce compile time)',
searchPlaceholder: 'Search extensions...',
copy: 'Copy',
copied: 'Copied',
doctorCommand: 'Auto-check and prepare build environment command',
doctorCommand: 'Auto-check and prepare build environment',
dlParallel: 'Parallel downloads (1-50)',
dlRetry: 'Retry count on failure',
noStrip: 'Keep debug symbols (--no-strip)',
}
};
</script>
<style scoped>
.number-input {
border: 1px solid var(--vp-c-divider);
border-radius: 8px;
width: 80px;
padding: 6px 10px;
background-color: var(--vp-c-bg-soft);
color: var(--vp-c-text-1);
font-size: 14px;
outline: none;
transition: all 0.2s ease;
}
.number-input:hover {
border-color: var(--vp-c-brand-1);
}
.number-input:focus {
border-color: var(--vp-c-brand-1);
box-shadow: 0 0 0 3px var(--vp-c-brand-soft);
}
.box {
display: flex;
flex-wrap: wrap;

View File

@@ -1,36 +1,46 @@
<template>
<div>
<header class="DocSearch-SearchBar" style="padding: 0">
<form class="DocSearch-Form searchinput">
<input class="DocSearch-Input" v-model="filterText" placeholder="Filter name..." @input="doFilter" />
</form>
</header>
<table>
<thead>
<tr>
<th>Extension Name</th>
<th>Linux</th>
<th>macOS</th>
<th>FreeBSD</th>
<th>Windows</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filterData">
<td v-if="!item.notes">{{ item.name }}</td>
<td v-else>
<a :href="'./extension-notes.html#' + item.name">{{ item.name }}</a>
</td>
<td>{{ item.linux }}</td>
<td>{{ item.macos }}</td>
<td>{{ item.freebsd }}</td>
<td>{{ item.windows }}</td>
</tr>
</tbody>
</table>
<div v-if="filterData.length === 0" style="margin: 0 4px 20px 4px; color: var(--vp-c-text-2); font-size: 14px">
No result, please try another keyword.
<div v-if="missing" class="warning custom-block" style="margin-bottom: 16px">
<p class="custom-block-title">WARNING</p>
<p>Extension list is not generated yet. Run <code>bin/spc dev:gen-ext-docs</code> to generate it.</p>
</div>
<template v-else>
<header class="DocSearch-SearchBar" style="padding: 0">
<form class="DocSearch-Form searchinput">
<input class="DocSearch-Input" v-model="filterText" placeholder="Filter name..." @input="doFilter" />
</form>
</header>
<table>
<thead>
<tr>
<th>Extension Name</th>
<th>Linux</th>
<th>macOS</th>
<th>Windows</th>
<th>Website</th>
</tr>
</thead>
<tbody>
<tr v-for="item in filterData" :key="item.name">
<td>
<span v-if="!item.hasNotes">{{ item.name }}</span>
<a v-else :href="'./extension-notes.html#' + item.name">{{ item.name }}</a>
</td>
<td>{{ item.linux ? '✅' : '' }}</td>
<td>{{ item.macos ? '✅' : '' }}</td>
<td>{{ item.windows ? '✅' : '' }}</td>
<td>
<a v-if="item.url" :href="item.url" target="_blank" rel="noopener noreferrer" class="ext-source-link">
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width="12" height="12" fill="currentColor"><path d="M10 6v2H5v11h11v-5h2v6a1 1 0 01-1 1H4a1 1 0 01-1-1V7a1 1 0 011-1h6zm11-3v8h-2V6.413l-7.793 7.794-1.414-1.414L17.585 5H13V3h8z"/></svg>
</a>
</td>
</tr>
</tbody>
</table>
<div v-if="filterData.length === 0" style="margin: 0 4px 20px 4px; color: var(--vp-c-text-2); font-size: 14px">
No result, please try another keyword.
</div>
</template>
</div>
</template>
@@ -41,34 +51,22 @@ export default {
</script>
<script setup>
import {ref} from "vue";
import ext from '../../../config/ext.json';
import { ref } from 'vue'
import { data as extData } from '../extensions.data.js'
// 将 ext 转换为列表,方便后续操作
const data = ref([]);
for (const [name, item] of Object.entries(ext)) {
data.value.push({
name,
linux: item.support?.Linux === undefined ? 'yes' : (item.support?.Linux === 'wip' ? '' : item.support?.Linux),
macos: item.support?.Darwin === undefined ? 'yes' : (item.support?.Darwin === 'wip' ? '' : item.support?.Darwin),
freebsd: item.support?.BSD === undefined ? 'yes' : (item.support?.BSD === 'wip' ? '' : item.support?.BSD),
windows: item.support?.Windows === undefined ? 'yes' : (item.support?.Windows === 'wip' ? '' : item.support?.Windows),
notes: item.notes === true,
});
}
const filterData = ref(data.value);
const filterText = ref('');
const missing = extData.missing
const data = ref(extData.extensions)
const filterData = ref(extData.extensions)
const filterText = ref('')
const doFilter = () => {
if (filterText.value === '') {
filterData.value = data.value;
return;
filterData.value = data.value
return
}
filterData.value = data.value.filter(item => {
return item.name.toLowerCase().includes(filterText.value.toLowerCase());
});
filterData.value = data.value.filter(item =>
item.name.toLowerCase().includes(filterText.value.toLowerCase())
)
}
</script>
@@ -76,4 +74,14 @@ const doFilter = () => {
.searchinput {
border: 1px solid var(--vp-c-divider);
}
</style>
.ext-source-link {
color: var(--vp-c-text-3);
vertical-align: middle;
opacity: 0.6;
transition: opacity 0.2s;
}
.ext-source-link:hover {
opacity: 1;
color: var(--vp-c-brand-1);
}
</style>

View File

@@ -65,6 +65,7 @@ export default {
message: 'Released under the MIT License.',
copyright: 'Copyright © 2023-present crazywhalecc',
},
externalLinkIcon: true,
search: {
provider: 'algolia',
options: {

View File

@@ -0,0 +1,40 @@
import { readFileSync, existsSync } from 'node:fs'
import { resolve, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const DATA_PATH = resolve(__dirname, 'ext-data.json')
const NOTES_PATH = resolve(__dirname, '../en/guide/extension-notes.md')
export default {
watch: [DATA_PATH, NOTES_PATH],
load() {
if (!existsSync(DATA_PATH)) {
console.warn(
'[extensions.data.js] ext-data.json not found. ' +
'Run `bin/spc dev:gen-ext-docs` to generate it.'
)
return { extensions: [], missing: true }
}
const raw = JSON.parse(readFileSync(DATA_PATH, 'utf-8'))
// Build the set of extension names that have a section in extension-notes.md.
// Headings at level 2 or 3 are matched; leading/trailing whitespace is stripped.
const notesSet = new Set()
if (existsSync(NOTES_PATH)) {
const notesContent = readFileSync(NOTES_PATH, 'utf-8')
for (const match of notesContent.matchAll(/^#{2,3}\s+(\S+)/gm)) {
notesSet.add(match[1].toLowerCase())
}
}
const extensions = raw.extensions.map(ext => ({
...ext,
hasNotes: notesSet.has(ext.name.toLowerCase()),
}))
return { extensions, missing: false }
},
}

View File

@@ -6,6 +6,7 @@ export default {
{ text: 'Overview', link: '/en/guide/' },
{ text: 'Installation', link: '/en/guide/installation' },
{ text: 'First Build', link: '/en/guide/first-build' },
{ text: 'PHP SAPI Reference', link: '/en/guide/sapi-reference' },
{ text: 'CLI Reference', link: '/en/guide/cli-reference' },
],
},

View File

@@ -6,6 +6,7 @@ export default {
{ text: '概览', link: '/zh/guide/' },
{ text: '安装', link: '/zh/guide/installation' },
{ text: '第一次构建', link: '/zh/guide/first-build' },
{ text: 'PHP SAPI 构建参考', link: '/zh/guide/sapi-reference' },
{ text: '命令行参考', link: '/zh/guide/cli-reference' },
],
},

View File

@@ -1,10 +1,107 @@
# Frequently Asked Questions
# FAQ
Here will be some questions that you may encounter easily.
## What is the path of php.ini?
On Linux, macOS and FreeBSD, the path of `php.ini` is `/usr/local/etc/php/php.ini`.
On Windows, the path is `C:\windows\php.ini` or the current directory of `php.exe`.
The directory where to look for `php.ini` can be changed on *nix using the build option `--with-config-file-path`.
In addition, on Linux, macOS and FreeBSD, `.ini` files present in the `/usr/local/etc/php/conf.d` directory will also be loaded.
On Windows, this path is empty by default.
The directory can be changed using the build option `--with-config-file-scan-dir`.
`php.ini` will also be searched for in [the other standard locations](https://www.php.net/manual/configuration.file.php).
## Can statically-compiled PHP install extensions?
Because the principle of installing PHP extensions under the normal mode is to use `.so` type dynamic link library to install new extensions,
and we use the static link PHP compiled by this project. However, static linking has different definitions in different operating systems.
First of all, for Linux systems, statically linked binaries will not link the system's dynamic link library.
Purely statically linked binaries (`build with -all-static`) cannot load dynamic libraries, so new extensions cannot be added.
At the same time, in pure static mode, you cannot use extensions such as `ffi` to load external `.so` modules.
You can use the command `ldd buildroot/bin/php` to check whether the binary you built under Linux is purely statically linked.
If you build GNU libc based PHP, you can use the `ffi` extension to load external `.so` modules and load `.so` extensions with the same ABI.
For example, you can use the following command to build a static PHP binary dynamically linked with glibc,
supporting FFI extensions and loading the `xdebug.so` extension of the same PHP version and the same TS type:
```bash
SPC_TARGET=native-native-gnu.2.17 spc build:php "ffi,xml" --build-cli -vvv
buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" --ri xdebug
```
This uses the Zig toolchain to produce a partially static binary dynamically linked against glibc 2.17. No Docker and no extra cross-compilation toolchain are required.
For macOS platform, almost all binaries under macOS cannot be truly purely statically linked, and almost all binaries will link macOS system libraries: `/usr/lib/libresolv.9.dylib` and `/usr/lib/libSystem.B.dylib`.
So on macOS, you can **directly** use SPC to build statically compiled PHP binaries with dynamically linked extensions:
1. Build shared extension `xxx.so` using: `--build-shared=XXX` option. e.g. `spc build:php "bcmath,zlib" --build-shared=xdebug --build-cli`
2. You will get `buildroot/modules/xdebug.so` and `buildroot/bin/php`.
3. The `xdebug.so` file could be used for php that version and thread-safe are the same.
For the Windows platform, since officially built extensions (such as `php_yaml.dll`) force the use of the `php8.dll` dynamic library as a link, and statically built PHP does not include any dynamic libraries other than system libraries,
php.exe built by static-php cannot load officially built dynamic extensions. Since StaticPHP does not yet support building dynamic extensions, there is currently no way to load dynamic extensions with static-php.
However, Windows can normally use the `FFI` extension to load other dll files and call them.
## Can it support Oracle database extension?
Some extensions that rely on closed source libraries, such as `oci8`, `sourceguardian`, etc.,
they do not provide purely statically compiled dependent library files (`.a`), only dynamic dependent library files (`.so`).
These extensions cannot be compiled into StaticPHP using source code, so this project may never support these extensions.
However, in theory you can access and use such extensions under macOS and Linux according to the above questions.
## Does it support Windows?
The project currently supports Windows, but the number of supported extensions is small. Windows support is not perfect. There are mainly the following problems:
1. The compilation process of Windows is different from that of *nix, and the toolchain used is also different. The compilation tools used to compile the dependent libraries of each extension are almost completely different.
2. The demand for the Windows version will also be advanced based on the needs of all people who use this project. If many people need it, I will support related extensions as soon as possible.
## Can I protect my source code with micro?
You can't. micro.sfx is essentially combining php and php code into one file,
there is no process of compiling or encrypting the PHP code.
First of all, php-src is the official interpreter of PHP code, and there is no PHP compiler compatible with mainstream branches on the market.
I saw on the Internet that there is a project called BPC (Binary PHP Compiler?) that can compile PHP into binary,
but there are many restrictions.
The direction of encrypting and protecting the code is not the same as compiling.
After compiling, the code can also be obtained through reverse engineering and other methods.
The real protection is still carried out by means of packing and encrypting the code.
Therefore, this project (StaticPHP) and related projects (lwmbs, swoole-cli) all provide a convenient compilation tool for php-src source code.
The phpmicro referenced by this project and related projects is only a package of PHP's sapi interface, not a compilation tool for PHP code.
The compiler for PHP code is a completely different project, so the extra cases are not taken into account.
If you are interested in encryption, you can consider using existing encryption technologies,
such as Swoole Compiler, Source Guardian, etc.
## Unable to use ssl
**Update: This issue has been fixed in the latest version of StaticPHP, which now reads the system's certificate file by default. If you still have problems, try the solution below.**
When using curl, pgsql, etc. to request an HTTPS website or establish an SSL connection, there may be an `error:80000002:system library::No such file or directory` error.
This error is caused by statically compiled PHP without specifying `openssl.cafile` via `php.ini`.
You can solve this problem by specifying `php.ini` before using PHP and adding `openssl.cafile=/path/to/your-cert.pem` in the INI.
For Linux systems, you can download the [cacert.pem](https://curl.se/docs/caextract.html) file from the curl official website, or you can use the certificate file that comes with the system.
For the certificate locations of different distros, please refer to [Golang docs](https://go.dev/src/crypto/x509/root_linux.go).
> INI configuration `openssl.cafile` cannot be set dynamically using the `ini_set()` function, because `openssl.cafile` is a `PHP_INI_SYSTEM` type configuration and can only be set in the `php.ini` file.
## Why don't we support older versions of PHP?
Because older versions of PHP have many problems, such as security issues, performance issues, and functional issues.
In addition, many older versions of PHP are not compatible with the latest dependency libraries,
which is one of the reasons why older versions of PHP are not supported.
You can use older versions compiled earlier by StaticPHP, such as PHP 8.0, but earlier versions will not be explicitly supported.
<!-- TODO: Categorized FAQ.
Sections:
- Build Issues (common compile errors, missing tools)
- Extensions (dynamic loading, closed-source deps, oci8)
- Windows (icon embedding, DLL loading, FFI)
- Version Compatibility (PHP versions, glibc vs musl)
- Source Protection (micro, encryption)
Migrate and expand from v2 faq/index.md. -->

View File

@@ -2,6 +2,10 @@
aside: false
---
<script setup>
import CliGenerator from "../../.vitepress/components/CliGenerator.vue";
</script>
# Build Command Generator
<!-- TODO: Embed CliGenerator Vue component. -->
<CliGenerator lang="en" />

View File

@@ -1,6 +1,211 @@
---
outline: 'deep'
---
# CLI Reference
<!-- TODO: Full reference for every spc command and option.
One ## section per command: download, build, craft, doctor, check-update, dev:*.
Each option: type, default, description, example.
Covers platform-specific options (e.g., --embed-icon on Windows). -->
::: tip
If you installed spc as a pre-built binary, replace every `spc` in this page with `./spc` (or `.\spc.exe` on Windows).
If you installed from source, use `bin/spc` instead.
:::
## download
Download source archives and pre-built binaries required for building.
```bash
spc download [artifacts] [options]
```
`artifacts` (optional): Specific artifacts to download, comma-separated (e.g. `"php-src,openssl,curl"`).
### Options
| Option | Short | Description |
|---|---|---|
| `--for-extensions=<list>` | `-e` | Download artifacts needed by the given extensions |
| `--for-libs=<list>` | `-l` | Download artifacts needed by the given libraries |
| `--for-packages=<list>` | | Download artifacts needed by the given packages |
| `--without-suggests` | | Skip suggested packages when using `--for-extensions` |
| `--clean` | | Delete existing download cache before fetching |
| `--with-php=<ver>` | | PHP version in `major.minor` format (default: `8.4`) |
| `--prefer-binary` | `-p` | Prefer pre-built binaries over source archives |
| `--prefer-source` | | Prefer source archives over pre-built binaries |
| `--source-only` | | Only download source artifacts |
| `--binary-only` | | Only download binary artifacts |
| `--parallel=<n>` | `-P` | Number of parallel downloads (default: `1`) |
| `--retry=<n>` | `-R` | Number of retries on failure (default: `0`) |
| `--ignore-cache=<list>` | | Force re-download the specified artifacts |
| `--no-alt` | | Do not use alternative mirror URLs |
| `--no-shallow-clone` | | Do not clone git repositories shallowly |
| `--custom-url=<src:url>` | `-U` | Override the download URL for a source |
| `--custom-git=<src:branch:url>` | `-G` | Override with a custom git repository |
| `--custom-local=<src:path>` | `-L` | Use a local path as a source override |
### Examples
```bash
# Download only what the chosen extensions need
spc download --for-extensions="bcmath,openssl,curl" --with-php=8.4
# Download specific artifacts
spc download "php-src,openssl"
# Speed up with parallelism and retries
spc download --for-extensions="bcmath,openssl,curl" --parallel 8 --retry=3
# Prefer pre-built binaries
spc download --for-extensions="bcmath,openssl,curl" --prefer-binary
# Force re-download the PHP source (e.g. when switching versions)
spc download --for-extensions="bcmath,curl" --ignore-cache="php-src" --with-php=8.3
# Override a download URL
spc download --for-extensions="bcmath" --custom-url "php-src:https://downloads.php.net/~user/php-8.5.0alpha1.tar.xz"
```
## build:php {#build-php}
Build PHP and extensions from source. Alias: `build`.
```bash
spc build:php <extensions> [options]
```
`extensions` (required): Comma-separated list of extensions to compile statically (e.g. `"bcmath,openssl,curl"`).
All `download` options are also available on `build:php` with the `--dl-` prefix (e.g. `--dl-with-php=8.3`, `--dl-parallel=4`). These are passed to the automatic downloader that runs before the build.
### SAPI Selection {#sapi-selection}
These flags apply only to the combined `build:php` target. To build a specific SAPI in isolation, use its dedicated command (e.g. `spc build:php-cli`).
| Option | Description |
|---|---|
| `--build-cli` | Build the `cli` SAPI (`php` / `php.exe`) |
| `--build-fpm` | Build `php-fpm` (Linux and macOS only) |
| `--build-cgi` | Build `php-cgi` |
| `--build-micro` | Build `micro.sfx` |
| `--build-embed` | Build the embed static library (`libphp.a` / `php8embed.lib`) |
| `--build-frankenphp` | Build the FrankenPHP binary |
### Common Build Options {#common-build-options}
| Option | Short | Description |
|---|---|---|
| `--no-strip` | | Keep debug symbols; do not strip the binary |
| `--with-upx-pack` | | Compress the output binary with UPX (install first with `spc install-pkg upx`; Linux and Windows only) |
| `--disable-opcache-jit` | | Disable OPcache JIT |
| `--with-config-file-path=<path>` | | Directory where PHP looks for `php.ini` (default: `/usr/local/etc/php`) |
| `--with-config-file-scan-dir=<path>` | | Directory PHP scans for additional `.ini` files (default: `/usr/local/etc/php/conf.d`) |
| `--with-hardcoded-ini=<k=v>` | `-I` | Bake an INI setting into the binary at compile time (repeatable) |
| `--enable-zts` | | Enable thread-safe (ZTS) mode |
| `--no-smoke-test` | | Skip the post-build smoke tests |
| `--with-suggests` | `-L` / `-E` | Also resolve and install suggested packages |
| `--with-packages=<list>` | | Additional packages to install alongside the build |
| `--no-download` | | Skip the download step (use existing cached files) |
| `--with-added-patch=<file>` | `-P` | Inject an external PHP patch script (repeatable) |
| `--build-shared=<list>` | `-D` | Extensions to compile as shared `.so` / `.dll` instead of static |
### micro Options {#micro-options}
| Option | Description |
|---|---|
| `--with-micro-fake-cli` | Make `micro`'s `PHP_SAPI` report `cli` instead of `micro` |
| `--without-micro-ext-test` | Disable the post-build extension test for `micro.sfx` |
| `--with-micro-logo=<path>` | Embed a custom `.ico` icon into `micro.sfx` (Windows only) |
| `--enable-micro-win32` | Build `micro.sfx` as a Win32 GUI application instead of a console app (Windows only) |
### frankenphp Options {#frankenphp-options}
| Option | Description |
|---|---|
| `--enable-zts` | Required for FrankenPHP; enables thread-safe mode |
| `--with-frankenphp-app=<path>` | Embed a directory into the FrankenPHP binary |
### embed Options {#embed-options}
| Option | Description |
|---|---|
| `--build-shared=<list>` | Compile specific extensions as shared libraries (requires embed SAPI) |
### Download Pass-through Options {#download-options}
All downloader options are available with the `--dl-` prefix:
| Option | Description |
|---|---|
| `--dl-with-php=<ver>` | PHP version to download (default: `8.4`) |
| `--dl-prefer-binary` | Prefer pre-built binaries for dependencies |
| `--dl-parallel=<n>` | Number of parallel downloads |
| `--dl-retry=<n>` | Number of retries on failure |
| `--dl-custom-url=<src:url>` | Override a source download URL |
| `--dl-custom-git=<src:branch:url>` | Override with a custom git repository |
### Examples
```bash
# Build cli SAPI
spc build:php "bcmath,openssl,curl" --build-cli
# Build cli + micro together
spc build:php "bcmath,phar,openssl,curl" --build-cli --build-micro
# Build with a specific PHP version
spc build:php "bcmath,openssl" --build-cli --dl-with-php=8.3
# Bake INI into the binary
spc build:php "bcmath,pcntl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"
# Keep debug symbols
spc build:php "bcmath,openssl" --build-cli --no-strip
# Build FrankenPHP (ZTS required)
spc build:php "bcmath,openssl,curl" --build-frankenphp --enable-zts
```
## build:php-cli, build:php-fpm, build:php-micro, build:php-embed, build:php-cgi, build:frankenphp
Dedicated single-target build commands. These accept the same options as `build:php` except the SAPI-selection flags (`--build-*`), which are implicit.
```bash
spc build:php-cli "bcmath,openssl,curl"
spc build:php-micro "bcmath,phar,openssl"
spc build:php-fpm "bcmath,openssl,curl,pdo_mysql"
spc build:php-embed "bcmath,openssl"
spc build:frankenphp "bcmath,openssl,curl" --enable-zts
```
## craft
Read a `craft.yml` and run the full build pipeline automatically.
```bash
spc craft [path/to/craft.yml]
```
If no path is given, `craft.yml` in the current working directory is used. See [craft.yml configuration](../develop/craft-yml) for the file format.
## doctor
Diagnose whether the current environment can compile PHP normally.
```bash
spc doctor [--auto-fix[=never]]
```
| Option | Description |
|---|---|
| `--auto-fix` | Automatically fix detected issues using the system package manager |
| `--auto-fix=never` | Report issues but never attempt automatic fixes |
## dev:shell
Enter an interactive shell with StaticPHP's build environment pre-loaded (compiler wrappers, `buildroot/`, `pkgroot/` paths, etc. on `PATH`).
```bash
spc dev:shell
```
Useful for compiling small programs against `libphp.a` (embed SAPI) or inspecting the build environment manually.

View File

@@ -1,4 +1,56 @@
# Environment Variables
<!-- TODO: Full table of all env vars supported by config/env.ini and config/env.custom.ini.
Migrate and update from v2 env-vars.md. -->
All environment variables mentioned in the list on this page have default values unless otherwise noted.
You can override the default values by setting these environment variables.
## Environment variables list
StaticPHP centralizes environment variables in the `config/env.ini` file.
You can set environment variables by modifying this file.
We divide the environment variables supported by StaticPHP into three types:
- **Global internal environment variables**: declared after StaticPHP starts, you can use `getenv()` to get them internally in StaticPHP, and you can override them before starting StaticPHP.
- **Fixed environment variables**: declared after StaticPHP starts, you can only use `getenv()` to get them, but you cannot override them through shell scripts.
- **Config file environment variables**: declared before StaticPHP builds, you can set these environment variables by modifying the `config/env.ini` file or through shell scripts.
You can read the comments for each parameter in [config/env.ini](https://github.com/crazywhalecc/static-php-cli/blob/v3/config/env.ini) to understand its purpose.
## Custom environment variables
Generally, you don't need to modify any of the following environment variables as they are already set to optimal values.
However, if you have special needs, you can set these environment variables to meet your needs
(for example, you need to debug PHP performance under different compilation parameters).
If you want to use custom environment variables, you can use the `export` command in the terminal or set the environment variables directly before the command, for example:
```shell
# export first
export SPC_CONCURRENCY=4
spc build:php "mbstring,pcntl" --build-cli
# or direct use
SPC_CONCURRENCY=4 spc build:php "mbstring,pcntl" --build-cli
```
Or, if you need to modify an environment variable for a long time, you can modify the `config/env.ini` file.
`config/env.ini` is divided into three sections: `[global]` is globally effective, `[windows]`, `[macos]`, `[linux]` are only effective for the corresponding operating system.
For example, if you need to modify the `./configure` command for compiling PHP, you can find the `SPC_CMD_PREFIX_PHP_CONFIGURE` environment variable in the `config/env.ini` file, and then modify its value.
If your build conditions are more complex and require multiple `env.ini` files to switch,
we recommend that you use the `config/env.custom.ini` file.
In this way, you can specify your environment variables by writing additional override items
without modifying the default `config/env.ini` file.
```ini
; This is an example of `config/env.custom.ini` file,
; we modify the `SPC_CONCURRENCY` and linux default CFLAGS passing to libs and PHP
[global]
SPC_CONCURRENCY=4
[linux]
SPC_DEFAULT_C_FLAGS="-O3"
```

View File

@@ -1,3 +1,169 @@
# Extension Notes
<!-- TODO: Migrate and update from v2 extension-notes.md. Per-extension special compilation notes. -->
Because it is a static compilation, extensions will not compile 100% perfectly,
and different extensions have different requirements for PHP and the environment,
which will be listed one by one here.
## curl
HTTP3 support is not enabled by default, compile with `--with-libs="nghttp2,nghttp3,ngtcp2"` to enable HTTP3 support for PHP >= 8.4.
When using curl to request HTTPS, there may be an `error:80000002:system library::No such file or directory` error.
For details on the solution, see [FAQ](../faq/).
## phpmicro
1. Only PHP >= 8.0 is supported.
## swoole
1. swoole >= 5.0 Only PHP >= 8.0 is supported.
2. swoole Currently, curl hooks are not supported for PHP 8.0.x (which may be fixed in the future).
3. When compiling, if only `swoole` extension is included, the supported Swoole database coroutine hook will not be fully enabled.
If you need to use it, please add the corresponding `swoole-hook-xxx` extension.
4. The `zend_mm_heap corrupted` problem may occur in swoole under some extension combinations. The cause has not yet been found.
## swoole-hook-pgsql
swoole-hook-pgsql is not an extension, it's a Hook feature of Swoole.
If you use `swoole,swoole-hook-pgsql`, you will enable Swoole's PostgreSQL client and the coroutine mode of the `pdo_pgsql` extension.
swoole-hook-pgsql conflicts with the `pdo_pgsql` extension. If you want to use Swoole and `pdo_pgsql`, please delete the pdo_pgsql extension and enable `swoole` and `swoole-hook-pgsql`.
This extension contains an implementation of the coroutine environment for `pdo_pgsql`.
On macOS systems, `pdo_pgsql` may not be able to connect to the postgresql server normally, please use it with caution.
## swoole-hook-mysql
swoole-hook-mysql is not an extension, it's a Hook feature of Swoole.
If you use `swoole,swoole-hook-mysql`, you will enable the coroutine mode of Swoole's `mysqlnd` and `pdo_mysql`.
## swoole-hook-sqlite
swoole-hook-sqlite is not an extension, it's a Hook feature of Swoole.
If you use `swoole,swoole-hook-sqlite`, you will enable the coroutine mode of Swoole's `pdo_sqlite` (Swoole must be 5.1 or above).
swoole-hook-sqlite conflicts with the `pdo_sqlite` extension. If you want to use Swoole and `pdo_sqlite`, please delete the pdo_sqlite extension and enable `swoole` and `swoole-hook-sqlite`.
This extension contains an implementation of the coroutine environment for `pdo_sqlite`.
## swoole-hook-odbc
swoole-hook-odbc is not an extension, it's a Hook feature of Swoole.
If you use `swoole,swoole-hook-odbc`, you will enable the coroutine mode of Swoole's `odbc` extension.
swoole-hook-odbc conflicts with the `pdo_odbc` extension. If you want to use Swoole and `pdo_odbc`, please delete the `pdo_odbc` extension and enable `swoole` and `swoole-hook-odbc`.
This extension contains an implementation of the coroutine environment for `pdo_odbc`.
## swow
1. Only PHP 8.0+ is supported.
## imagick
1. OpenMP support is disabled, this is recommended by the maintainers and also the case system packages.
## imap
1. Kerberos is not supported
2. ext-imap is not thread safe due to the underlying c-client. It's not possible to use it in `--enable-zts` builds.
3. The extension was dropped from php 8.4, we recommend you look for an alternative implementation, such as [Webklex/php-imap](https://github.com/Webklex/php-imap)
## gd
1. gd Extension relies on more additional Graphics library. By default,
using `bin/spc build gd` directly will not support some Graphics library, such as `libjpeg`, `libavif`, etc.
Currently, it supports four libraries: `freetype,libjpeg,libavif,libwebp`.
Therefore, the following command can be used to introduce them into the gd library:
```bash
bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
```
## mcrypt
1. Currently not supported, and this extension will not be supported in the future. [#32](https://github.com/crazywhalecc/static-php-cli/issues/32)
## oci8
1. oci8 is an extension of the Oracle database, because the library on which the extension provided by Oracle does not provide a statically compiled version (`.a`) or source code,
and this extension cannot be compiled into php by static linking, so it cannot be supported.
## xdebug
1. Xdebug is only buildable as a shared extension. On Linux, you'll need to use a `SPC_TARGET` with a glibc variant (e.g. `native-native-gnu.2.17`) instead of the default musl static target.
2. When using Linux/glibc or macOS, you can compile Xdebug as a shared extension using `--build-shared="xdebug"`.
The compiled `./php` binary can be configured and run by specifying the INI, e.g. `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`.
## xml
1. xml includes xml, xmlreader, xmlwriter, xsl, dom, simplexml, etc.
When adding xml extensions, it is best to enable these extensions at the same time.
2. libxml is included in xml extension. Enabling xml is equivalent to enabling libxml.
## glfw
1. glfw depends on OpenGL, and linux environment also needs X11, which cannot be linked statically.
2. macOS platform, we can compile and link system builtin OpenGL and related libraries dynamically.
## rar
1. The rar extension currently has a problem when compiling phpmicro with the `common` extension collection in the macOS x86_64 environment.
## pgsql
~~pgsql ssl connection is not compatible with openssl 3.2.0. See:~~
- ~~<https://github.com/Homebrew/homebrew-core/issues/155651>~~
- ~~<https://github.com/Homebrew/homebrew-core/pull/155699>~~
- ~~<https://github.com/postgres/postgres/commit/c82207a548db47623a2bfa2447babdaa630302b9>~~
pgsql 16.2 has fixed this bug, now it's working.
When pgsql uses SSL connection, there may be `error:80000002:system library::No such file or directory` error.
For details on the solution, see [FAQ](../faq/).
## openssl
When using openssl-based extensions (such as curl, pgsql and other network libraries),
there may be an `error:80000002:system library::No such file or directory` error.
For details on the solution, see [FAQ](../faq/).
## password-argon2
1. password-argon2 is not a standard extension. The algorithm `PASSWORD_ARGON2ID` for the `password_hash` function needs libsodium or libargon2 to work.
2. using password-argon2 enables multithread support for this.
## ffi
1. Due to the limitation of musl libc's static linkage, you cannot use ffi because dynamic libraries cannot be loaded.
If you need to use the ffi extension, use a glibc-based `SPC_TARGET` (e.g. `native-native-gnu.2.17`). See [SAPI Reference](./sapi-reference) for details.
2. macOS supports the ffi extension, but errors will occur when some kernels do not contain debugging symbols.
3. Windows x64 supports the ffi extension.
## xhprof
The xhprof extension consists of three parts: `xhprof_extension`, `xhprof_html`, `xhprof_libs`.
Only `xhprof_extension` is included in the compiled binary.
If you need to use xhprof,
please download the source code from [pecl.php.net/package/xhprof](http://pecl.php.net/package/xhprof) and specify the `xhprof_libs` and `xhprof_html` paths for use.
## event
If you enable event extension on macOS, the `openpty` will be disabled due to issue:
- [static-php-cli#335](https://github.com/crazywhalecc/static-php-cli/issues/335)
## parallel
Parallel is only supported on PHP 8.0 ZTS and above.
## spx
1. SPX does not support Windows, and the official repository does not support static compilation. static-php-cli uses a [modified version](https://github.com/static-php/php-spx).
## mimalloc
1. This is not technically an extension, but a library.
2. Building with `--with-libs="mimalloc"` on Linux or macOS will override the default allocator.
3. This is experimental for now, but is recommended in threaded environments.

View File

@@ -1,3 +1,17 @@
<script setup>
import SearchTable from "../../.vitepress/components/SearchTable.vue";
</script>
# Supported Extensions
<!-- TODO: Auto-generated by `bin/spc dev:gen-ext-docs`. Placeholder until command is implemented in v3. -->
> - ✅: Supported
> - blank: Not supported or not yet ported
<search-table />
::: tip
If an extension you need is missing, you can file a [feature request](https://github.com/crazywhalecc/static-php-cli/issues).
Some extensions or their library dependencies have optional features (e.g. gd can optionally use libwebp, freetype, etc.).
Running `bin/spc build gd --build-cli` alone will not include them — StaticPHP follows a minimal-dependency principle by default.
:::

View File

@@ -15,7 +15,7 @@ StaticPHP supports two build workflows — pick the one that fits your situation
| Approach | When to use |
|---|---|
| `craft` (one-shot) | Everyday use, getting started quickly |
| Step-by-step | CI/CD pipelines, when you need to separate download and build phases |
| Step-by-step | Fine-grained control over the build pipeline |
## Option 1: One-Shot Build with `craft` (Recommended)
@@ -76,39 +76,30 @@ This approach lets you run download and compile as separate steps — useful whe
```bash
# Download only what the chosen extensions need (recommended)
spc download --for-extensions=bcmath,posix,phar,zlib,openssl,curl,fileinfo,tokenizer --with-php=8.4
spc download --for-extensions="bcmath,posix,phar,zlib,openssl,curl,fileinfo,tokenizer" --with-php=8.4
# Download by specific libraries
spc download --for-libs=curl,openssl --with-php=8.4
# Download by specific package names
spc download "curl,openssl" --with-php=8.4
```
Downloads are cached in `downloads/` and reused across builds automatically.
```bash
# Slow connection? Increase parallelism and retries
spc download --for-extensions=bcmath,openssl,curl -P 4 --retry=3
spc download --for-extensions="bcmath,openssl,curl" --parallel 10 --retry=3
# Use pre-built binaries where available — skips compiling those dependencies
spc download --for-extensions=bcmath,openssl,curl --prefer-binary
spc download --for-extensions="bcmath,openssl,curl" --prefer-binary
```
### Step 2: Build PHP
```bash
# Build the cli SAPI
spc build:php bcmath,posix,phar,zlib,openssl,curl,fileinfo,tokenizer --build-cli
spc build:php "bcmath,phar,zlib,openssl,curl,fileinfo,tokenizer" --build-cli
# Build multiple SAPIs in one go
spc build:php bcmath,posix,phar,zlib,openssl,curl --build-cli --build-micro
# Build all SAPIs
spc build:php bcmath,posix,phar,zlib,openssl,curl --build-all
```
`build:php` will automatically fetch any missing dependencies before building. If you already ran `download`, pass `--no-download` to skip that step:
```bash
spc build:php bcmath,openssl,curl --build-cli --no-download
spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
```
#### Common Build Options
@@ -118,9 +109,8 @@ spc build:php bcmath,openssl,curl --build-cli --no-download
| `--build-cli` | Build the cli SAPI |
| `--build-fpm` | Build php-fpm (not available on Windows) |
| `--build-micro` | Build micro.sfx |
| `--build-embed` | Build the embed SAPI (not available on Windows) |
| `--build-frankenphp` | Build FrankenPHP (not available on Windows) |
| `--build-all` | Build all SAPIs |
| `--build-embed` | Build the embed SAPI |
| `--build-frankenphp` | Build FrankenPHP |
| `--enable-zts` | Enable thread-safe (ZTS) mode |
| `--no-strip` | Keep debug symbols; do not strip the binary |
| `-I key=value` | Hard-compile an INI option into PHP |
@@ -129,7 +119,7 @@ spc build:php bcmath,openssl,curl --build-cli --no-download
Example — baking in a larger memory limit and disabling the `system` function:
```bash
spc build:php bcmath,pcntl,posix --build-all -I "memory_limit=4G" -I "disable_functions=system"
spc build:php "bcmath,pcntl,posix" --build-cli -I "memory_limit=4G" -I "disable_functions=system"
```
## Packaging a micro App
@@ -160,7 +150,7 @@ spc micro:combine your-app.phar --output=your-app -N /path/to/custom.ini
If a build fails or you want to trace what's happening, use `-v` / `-vv` / `-vvv`:
```bash
spc build:php bcmath,openssl --build-cli -vv
spc build:php "bcmath,openssl" --build-cli -vv
```
- `-v` shows `INFO`-level logs: which modules are running and what build commands are being executed.
@@ -172,7 +162,7 @@ To wipe compiled artifacts and start fresh without re-downloading, run `reset`:
```bash
spc reset
# Then rebuild
spc build:php bcmath,openssl --build-cli
spc build:php "bcmath,openssl" --build-cli
```
::: tip
@@ -184,6 +174,7 @@ If you're stuck, open an [Issue](https://github.com/static-php/static-php-cli/is
## What's Next
- [PHP SAPI Reference](./sapi-reference) — Build options and usage guide for each PHP SAPI
- [CLI Reference](./cli-reference) — Full documentation for every command and option
- [Extensions](./extensions) — Browse supported extensions and their dependencies
- [Troubleshooting](./troubleshooting) — Diagnose common build failures

View File

@@ -4,6 +4,8 @@
StaticPHP is a build tool that compiles the PHP interpreter together with any extensions you need into a single self-contained binary. The target system doesn't need PHP or any runtime libraries installed — just copy the binary and run it. Builds target Linux, macOS, and Windows.
StaticPHP isn't limited to PHP. Built on the same infrastructure, it can also compile standalone static binaries for common tools like `curl`, `pkg-config`, and `htop` — no dependencies required on the target machine. Support for more tools (including `openssl` and other frequently-used CLI utilities) is planned.
## Why bother with a static PHP binary?
A typical PHP installation is tightly coupled to the system: you install PHP, then extensions, then spend time dealing with version mismatches across distros. A static binary sidesteps all of that — what you get is a single executable that runs on any machine of the same architecture, no setup required.
@@ -16,7 +18,7 @@ Common use cases:
## phpmicro: ship PHP and your code as one file
[phpmicro](https://github.com/easysoft/phpmicro) is a third-party PHP SAPI that StaticPHP supports out of the box. It merges the PHP interpreter with your `.php` source or `.phar` archive into a single self-extracting executable (`.sfx`).
[phpmicro](https://micro.static-php.dev) is a third-party PHP SAPI that StaticPHP supports out of the box. It merges the PHP interpreter with your `.php` source or `.phar` archive into a single self-extracting executable (`.sfx`).
```
micro.sfx + your-app.phar = your-app # one file, zero dependencies

View File

@@ -0,0 +1,277 @@
---
outline: 'deep'
---
# PHP SAPI Reference
::: tip
If you installed spc as a pre-built binary, replace every `spc` in this page with `./spc` (or `.\spc.exe` on Windows).
If you installed from source, use `bin/spc` instead.
:::
This page describes the build options and usage for each PHP SAPI supported by StaticPHP.
## Overview
| SAPI | Build flag | Output path (Linux/macOS) | Output path (Windows) | Platform support |
|---|---|---|---|---|
| cli | `--build-cli` | `buildroot/bin/php` | `buildroot/bin/php.exe` | Linux, macOS, Windows |
| fpm | `--build-fpm` | `buildroot/bin/php-fpm` | — | Linux, macOS |
| micro | `--build-micro` | `buildroot/bin/micro.sfx` | `buildroot/bin/micro.sfx` | Linux, macOS, Windows |
| embed | `--build-embed` | `buildroot/lib/libphp.a` | `buildroot/lib/php8embed.lib` | Linux, macOS, Windows |
| frankenphp | `--build-frankenphp` | `buildroot/bin/frankenphp` | `buildroot/bin/frankenphp.exe` | Linux, macOS, Windows |
## cli
The `cli` SAPI is the standard PHP command-line binary for running scripts, interactive shells, and CLI applications.
### Build
```bash
spc build:php "bcmath,openssl,curl" --build-cli
```
The output is `buildroot/bin/php` on Linux and macOS, and `buildroot/bin/php.exe` on Windows.
See [build:php — SAPI Selection](./cli-reference#sapi-selection) and [build:php — Common Build Options](./cli-reference#common-build-options) for the full option reference.
### Usage
```bash
# Check version and loaded extensions
./buildroot/bin/php -v
./buildroot/bin/php -m
# Run a script
./buildroot/bin/php your-script.php
# Interactive mode
./buildroot/bin/php -a
```
### php.ini search path
The static PHP cli binary searches for `php.ini` in this order:
1. The path specified with the `-c /path/to/php.ini` command-line flag
2. The path set in the `PHP_INI_PATH` environment variable
3. The directory specified at compile time via `--with-config-file-path` (default: `/usr/local/etc/php`)
Run `./buildroot/bin/php --ini` to see which ini file is actually loaded.
### Hard-coded INI
Use `-I` at build time to bake INI settings directly into the binary, so no external `php.ini` is required:
```bash
spc build:php "bcmath,pcntl" --build-cli -I "memory_limit=4G" -I "disable_functions=system,exec"
```
Hard-coded INI applies to the `cli`, `micro`, and `embed` SAPIs.
## fpm
The `fpm` SAPI (FastCGI Process Manager) is used with web servers such as Nginx or Apache for traditional web application deployments.
::: warning
`fpm` is not supported on Windows.
:::
### Build
```bash
spc build:php "bcmath,openssl,curl,pdo_mysql" --build-fpm
```
The output is `buildroot/bin/php-fpm`.
See [build:php — SAPI Selection](./cli-reference#sapi-selection) and [build:php — Common Build Options](./cli-reference#common-build-options) for the full option reference.
### Usage
Copy `buildroot/bin/php-fpm` to your server and use it like a regular `php-fpm` binary:
```bash
# Check version
./buildroot/bin/php-fpm -v
# Start with a specific config file
./buildroot/bin/php-fpm -c /path/to/php.ini -y /path/to/php-fpm.conf
# Test config file
./buildroot/bin/php-fpm -t
```
### Example: Nginx + php-fpm
```nginx
server {
listen 80;
root /var/www/html;
index index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
Example `php-fpm.conf`:
```ini
[global]
pid = /var/run/php-fpm.pid
error_log = /var/log/php-fpm.log
[www]
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
```
## micro
The `micro` SAPI is built on [phpmicro](https://github.com/easysoft/phpmicro) and produces a self-contained executable stub. With `spc micro:combine`, you can merge `micro.sfx` with your PHP code into a single portable binary that requires no PHP installation on the target machine.
### Build
```bash
spc build:php "bcmath,phar,openssl,curl" --build-micro
```
The output is `buildroot/bin/micro.sfx`.
See [build:php — SAPI Selection](./cli-reference#sapi-selection), [build:php — Common Build Options](./cli-reference#common-build-options), and [build:php — micro Options](./cli-reference#micro-options) for the full option reference.
### Packaging an application
Use `micro:combine` to bundle a PHP script or phar into a standalone executable:
```bash
# Bundle a PHP script
echo "<?php echo 'Hello, World!' . PHP_EOL;" > hello.php
spc micro:combine hello.php --output=hello
./hello
# Bundle a phar
spc micro:combine your-app.phar --output=your-app
./your-app
```
### Injecting INI settings
INI configuration can be injected at packaging time via command-line options or an ini file:
```bash
# Inject via command-line options (-I is shorthand for --with-ini-set)
spc micro:combine your-app.phar --output=your-app -I "memory_limit=512M" -I "curl.cainfo=/etc/ssl/certs/ca-certificates.crt"
# Inject from an ini file (-N is shorthand for --with-ini-file)
spc micro:combine your-app.phar --output=your-app -N /path/to/custom.ini
```
::: tip
The INI injected with `-I` here is runtime configuration appended to the `micro.sfx` file as a special structure. This is distinct from INI hard-coded at compile time using `-I` during `build:php`. Both can coexist.
:::
### Pretending to be the cli SAPI
Some frameworks check the `PHP_SAPI` value and refuse to run outside `cli`. Since `micro`'s `PHP_SAPI` is `micro` by default, you can make it report `cli` instead:
```bash
spc build:php "bcmath,phar" --build-micro --with-micro-fake-cli
```
### Specifying a custom micro.sfx path
```bash
spc micro:combine your-app.phar --output=your-app --with-micro=/path/to/your/micro.sfx
```
### phar path considerations
When packaging a phar, internal relative paths may behave differently than expected. See the [Developer Guide — Phar directory issue](../develop/structure) for details.
## embed
The `embed` SAPI compiles PHP into a static library (`libphp.a` on Linux/macOS, `php8embed.lib` on Windows) that can be linked into C/C++ programs to run PHP code directly.
### Build
```bash
spc build:php "bcmath,openssl" --build-embed
```
Output:
- Linux/macOS: `buildroot/lib/libphp.a`, headers in `buildroot/include/`
- Windows: `buildroot/lib/php8embed.lib`, headers in `buildroot/include/`
See [build:php — SAPI Selection](./cli-reference#sapi-selection), [build:php — Common Build Options](./cli-reference#common-build-options), and [build:php — embed Options](./cli-reference#embed-options) for the full option reference.
::: tip
Detailed instructions for linking and using `libphp.a` / `php8embed.lib` in your own projects — including compiler selection, `dev:shell` usage, and a complete C example — will be covered in the Developer Guide.
:::
## frankenphp
The `frankenphp` SAPI builds a [FrankenPHP](https://github.com/php/frankenphp) binary — a modern PHP application server with Caddy built in, supporting HTTP/2, HTTP/3, automatic HTTPS, and more.
::: tip
The `frankenphp` binary produced by StaticPHP is a fully self-contained single-file executable. This is different from the official FrankenPHP release, which ships as a dynamically linked binary and requires a separate PHP installation.
:::
::: warning
FrankenPHP requires thread-safe mode. Always pass `--enable-zts` when building.
:::
### Build
```bash
spc build:php "bcmath,openssl,curl,pdo_mysql" --build-frankenphp --enable-zts
```
The output is `buildroot/bin/frankenphp` on Linux/macOS, and `buildroot/bin/frankenphp.exe` on Windows.
See [build:php — SAPI Selection](./cli-reference#sapi-selection), [build:php — Common Build Options](./cli-reference#common-build-options), and [build:php — frankenphp Options](./cli-reference#frankenphp-options) for the full option reference.
### Usage
```bash
# Check version
./buildroot/bin/frankenphp version
# Run in PHP development server mode
./buildroot/bin/frankenphp php-server
# Run with a Caddyfile
./buildroot/bin/frankenphp run --config /path/to/Caddyfile
```
For full usage, refer to the [FrankenPHP documentation](https://frankenphp.dev/docs/).
## Dynamic Extension Loading
Whether a static PHP binary can load extensions at runtime via `dl()` depends on how the binary was linked.
**macOS** — The build always links dynamically against system libraries. Extensions built as `.so` files can be loaded at runtime via `dl()` or `php.ini` as usual.
**Linux** — StaticPHP's default build target is `native-native-musl`: a fully static binary linked against musl libc. Because there is no dynamic linker available at runtime, `dl()` is disabled, the FFI extension cannot be used, and no external `.so` extensions can be loaded.
To support dynamic extension loading on Linux, set the `SPC_TARGET` environment variable before building:
```bash
SPC_TARGET=native-native-gnu.2.17 spc build:php "bcmath,openssl" --build-cli
```
If you installed from source, you can also set `SPC_TARGET=native-native-gnu.2.17` in `config/env.ini` to make it the default for all builds.
This uses the Zig toolchain to produce a partially static binary dynamically linked against glibc 2.17, compatible with most modern GNU/Linux distributions. No Docker and no extra cross-compilation toolchain are required. The resulting binary supports `dl()`, FFI, and loading `.so` extensions at runtime, but cannot run on musl-based systems such as Alpine Linux.
**Windows** — PHP extensions on Windows are distributed as `.dll` files that depend on the DLLs bundled with the official dynamically-built PHP. StaticPHP produces a standalone static executable that does not include those DLLs, so dynamic extension loading is not possible on Windows. All extensions must be compiled in statically at build time.

View File

@@ -1,5 +1,43 @@
# Troubleshooting
<!-- TODO: Categorized common build failures and fixes.
Sections: Download issues / Compilation errors / Extension conflicts / Windows-specific / glibc Linux.
Migrate and expand from v2 troubleshooting.md. -->
Various failures may be encountered in the process of using StaticPHP,
here will describe how to check the errors by yourself and report an Issue.
## Download Failure
Problems with downloading resources are one of the most common problems with spc.
The main reason is that the addresses used for SPC download resources are generally the official website of the corresponding project or GitHub, etc.,
and these websites may occasionally go down and block IP addresses.
After encountering a download failure,
you can try to call the download command multiple times.
When downloading extensions, you may eventually see errors like `curl: (56) The requested URL returned error: 403` which are often caused by GitHub rate limiting.
You can verify this by adding `-vvv` to the command and will see something like `[DEBU] Running command (no output) : curl -sfSL "https://api.github.com/repos/openssl/openssl/releases"`.
To fix this, [create](https://github.com/settings/tokens) a personal access token on GitHub and set it as an environment variable `GITHUB_TOKEN=<XXX>`.
If you confirm that the address is indeed inaccessible,
you can submit an Issue or PR to update the url or download type.
## Doctor Can't Fix Something
In most cases, the doctor module can automatically repair and install missing system environments,
but there are also special circumstances where the automatic repair function cannot be used normally.
Due to system limitations (for example, software such as Visual Studio cannot be automatically installed under Windows),
the automatic repair function cannot be used for some projects.
When encountering a function that cannot be automatically repaired,
if you encounter the words `Some check items can not be fixed`,
it means that it cannot be automatically repaired.
Please submit an issue according to the method displayed on the terminal or repair the environment yourself.
## Compile Error
When you encounter a compilation error, if the `-vvv` log is not enabled, please enable the debug log first,
and then determine the command that reported the error.
The error terminal output is very important for fixing compilation errors.
When submitting an issue, please upload the last error fragment of the terminal log (or the entire terminal log output),
and include the `spc` command and parameters used.
If you are rebuilding, please refer to the [First Build - Debugging and Rebuilding](./first-build#debugging-and-rebuilding) section.

View File

@@ -1,10 +1,95 @@
# 常见问题
<!-- TODO: 分类整理的 FAQ
章节:
- 构建问题(常见编译错误、工具缺失)
- 扩展动态加载、闭源依赖、oci8
- Windows图标嵌入、DLL 加载、FFI
- 版本兼容PHP 版本、glibc vs musl
- 源码保护micro、加密
从 v2 faq/index.md 迁移并扩充。 -->
这里将会编写一些你容易遇到的问题
## php.ini 的路径是什么?
在 Linux、macOS 和 FreeBSD 上,`php.ini` 的路径是 `/usr/local/etc/php/php.ini`
在 Windows 中,路径是 `C:\windows\php.ini``php.exe` 所在的当前目录。
可以在 *nix 系统中使用构建选项 `--with-config-file-path` 来更改查找 `php.ini` 的目录。
此外,在 Linux、macOS 和 FreeBSD 上,`/usr/local/etc/php/conf.d` 目录中的 `.ini` 文件也会被加载。
在 Windows 中,该路径默认为空。
可以使用构建选项 `--with-config-file-scan-dir` 更改该目录。
PHP 默认也会从 [其他标准位置](https://www.php.net/manual/zh/configuration.file.php) 中搜索 `php.ini`
## 静态编译的 PHP 可以安装扩展吗?
因为传统架构下的 PHP 安装扩展的原理是使用 `.so` 类型的动态链接的库方式安装新扩展,而使用本项目编译的静态链接的 PHP。但是静态链接在不同操作系统有不同的定义。
首先,对于 Linux 系统,静态链接的二进制文件不会链接系统的动态链接库。纯静态链接的二进制文件(`-all-static`)无法加载动态库,因此无法添加新扩展。
同时,在纯静态模式下,你也不能使用 `ffi` 等扩展来加载外部 `.so` 模块。
你可以使用命令 `ldd buildroot/bin/php` 来检查你在 Linux 下构建的二进制文件是否为纯静态链接。
如果你构建了基于 GNU libc 的 PHP你可以使用 `ffi` 扩展来加载外部 `.so` 模块,并加载具有相同 ABI 的 `.so` 扩展。
例如,你可以使用以下命令构建一个与 glibc 动态链接的静态 PHP 二进制文件,支持 FFI 扩展并加载相同 PHP 版本和相同 TS 类型的 `xdebug.so` 扩展:
```bash
SPC_TARGET=native-native-gnu.2.17 spc build:php "ffi,xml" --build-cli -vvv
buildroot/bin/php -d "zend_extension=/path/to/php{PHP_VER}-{ts/nts}/xdebug.so" --ri xdebug
```
这将使用 Zig 工具链构建出一个动态链接 glibc 2.17 的准静态二进制,无需 Docker也无需额外的交叉编译工具链。
对于 macOS 平台macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 系统库:`/usr/lib/libresolv.9.dylib``/usr/lib/libSystem.B.dylib`
因此,在 macOS 上,你可以**直接**使用 SPC 构建具有动态链接扩展的静态编译 PHP 二进制文件:
1. 使用 `--build-shared=XXX` 选项构建共享扩展 `xxx.so`。例如:`spc build:php "bcmath,zlib" --build-shared=xdebug --build-cli`
2. 你将获得 `buildroot/modules/xdebug.so``buildroot/bin/php`
3. `xdebug.so` 文件可用于版本和线程安全相同的 php。
对于 Windows 平台,由于官方构建的扩展(如 `php_yaml.dll`)强制使用了 `php8.dll` 动态库作为链接,静态构建的 PHP 不包含任何系统库以外的动态库,
所以 Windows 下无法加载官方构建的动态扩展。由于 StaticPHP 还暂未支持构建动态扩展,所以目前还没有让 static-php 加载动态扩展的方法。
不过Windows 可以正常使用 `FFI` 扩展加载其他的 dll 文件并调用。
## 可以支持 Oracle 数据库扩展吗?
部分依赖库闭源的扩展,如 `oci8``sourceguardian` 等,它们没有提供纯静态编译的依赖库文件(`.a`),仅提供了动态依赖库文件(`.so`
这些扩展无法使用源码的形式编译到 StaticPHP 中,所以本项目可能永远也不会支持这些扩展。不过,理论上你可以根据上面的问题在 macOS 和 Linux 下接入和使用这类扩展。
## 支持 Windows 吗?
该项目目前支持 Windows但支持的扩展数量较少。Windows 支持并不完善。主要有以下问题:
1. Windows 的编译过程与 *nix 不同,使用的工具链也不同。用于编译每个扩展依赖库的编译工具也几乎完全不同。
2. Windows 版本的需求也会根据所有使用本项目的人的需求推进。如果很多人需要,我会尽快支持相关扩展。
## 我可以使用 micro 保护我的源代码吗?
不可以。micro.sfx 本质上是将 php 和 php 代码合并为一个文件,没有编译或加密 PHP 代码的过程。
首先php-src 是 PHP 代码的官方解释器,市场上没有与主流分支兼容的 PHP 编译器。
我在网上看到一个名为 BPCBinary PHP Compiler的项目可以将 PHP 编译为二进制,但有很多限制。
加密和保护代码的方向与编译不同。编译后,也可以通过逆向工程等方法获得代码。真正的保护仍然通过打包和加密代码等手段进行。
因此本项目StaticPHP和相关项目lwmbs、swoole-cli都提供了 php-src 源代码的便捷编译工具。
本项目和相关项目引用的 phpmicro 只是 PHP 的 sapi 接口封装,而不是 PHP 代码的编译工具。
PHP 代码的编译器是一个完全不同的项目,因此不考虑额外的情况。
如果你对加密感兴趣,可以考虑使用现有的加密技术,如 Swoole Compiler、Source Guardian 等。
## 无法使用 ssl
**更新:该问题已在最新版本的 StaticPHP 中修复,现在默认读取系统的证书文件。如果你仍然遇到问题,请尝试下面的解决方案。**
使用 curl、pgsql 等请求 HTTPS 网站或建立 SSL 连接时,可能会出现 `error:80000002:system library::No such file or directory` 错误。
此错误是由于静态编译的 PHP 未通过 `php.ini` 指定 `openssl.cafile` 导致的。
你可以通过在使用 PHP 前指定 `php.ini` 并在 INI 中添加 `openssl.cafile=/path/to/your-cert.pem` 来解决此问题。
对于 Linux 系统,你可以从 curl 官方网站下载 [cacert.pem](https://curl.se/docs/caextract.html) 文件,也可以使用系统自带的证书文件。
有关不同发行版的证书位置,请参考 [Golang 文档](https://go.dev/src/crypto/x509/root_linux.go)。
> INI 配置 `openssl.cafile` 不能使用 `ini_set()` 函数动态设置,因为 `openssl.cafile` 是 `PHP_INI_SYSTEM` 类型的配置,只能在 `php.ini` 文件中设置。
## 为什么不支持旧版本的 PHP
因为旧版本的 PHP 有很多问题,如安全问题、性能问题和功能问题。此外,许多旧版本的 PHP 与最新的依赖库不兼容,这也是不支持旧版本 PHP 的原因之一。
你可以使用 StaticPHP 早期编译的旧版本,如 PHP 8.0,但早期版本将不会被明确支持。

View File

@@ -2,6 +2,10 @@
aside: false
---
<script setup>
import CliGenerator from "../../.vitepress/components/CliGenerator.vue";
</script>
# 编译命令生成器
<!-- TODO: 嵌入 CliGenerator Vue 组件。 -->
<CliGenerator lang="zh" />

View File

@@ -1,6 +1,212 @@
---
outline: 'deep'
---
# 命令行参考
<!-- TODO: 所有 spc 命令和选项的完整参考。
每个命令一个 ## 小节download、build、craft、doctor、check-update、dev:*
每个选项:类型、默认值、说明、示例。
涵盖平台专属选项(如 Windows 下的 --embed-icon。 -->
::: tip
如果你采用的是 spc 二进制方式安装,请将本章节中的所有 `spc` 替换为 `./spc``.\spc.exe`
如果你采用的是源码安装,请将 `spc` 替换为 `bin/spc`
:::
## download
下载构建所需的源码包和预编译二进制。
```bash
spc download [artifacts] [options]
```
`artifacts`(可选):指定要下载的制品名称,逗号分隔(如 `"php-src,openssl,curl"`)。
### 选项
| 选项 | 缩写 | 说明 |
|---|---|---|
| `--for-extensions=<list>` | `-e` | 按扩展名下载其所需的制品 |
| `--for-libs=<list>` | `-l` | 按库名下载其所需的制品 |
| `--for-packages=<list>` | | 按包名下载其所需的制品 |
| `--without-suggests` | | 使用 `--for-extensions` 时跳过建议包 |
| `--clean` | | 下载前删除旧的下载缓存 |
| `--with-php=<ver>` | | PHP 版本,格式为 `major.minor`(默认 `8.4`|
| `--prefer-binary` | `-p` | 优先使用预编译二进制 |
| `--prefer-source` | | 优先使用源码包 |
| `--source-only` | | 仅下载源码制品 |
| `--binary-only` | | 仅下载二进制制品 |
| `--parallel=<n>` | `-P` | 并行下载数(默认 `1`|
| `--retry=<n>` | `-R` | 失败重试次数(默认 `0`|
| `--ignore-cache=<list>` | | 强制重新下载指定制品 |
| `--no-alt` | | 不使用镜像站 |
| `--no-shallow-clone` | | 不使用浅层克隆 |
| `--custom-url=<src:url>` | `-U` | 覆盖指定源的下载地址 |
| `--custom-git=<src:branch:url>` | `-G` | 覆盖为自定义 git 仓库 |
| `--custom-local=<src:path>` | `-L` | 使用本地路径作为制品来源 |
### 示例
```bash
# 按扩展名下载(推荐)
spc download --for-extensions="bcmath,openssl,curl" --with-php=8.4
# 下载指定制品
spc download "php-src,openssl"
# 增加并行数和重试次数
spc download --for-extensions="bcmath,openssl,curl" --parallel 8 --retry=3
# 优先使用预编译二进制
spc download --for-extensions="bcmath,openssl,curl" --prefer-binary
# 强制重新下载 PHP 源码(如切换版本)
spc download --for-extensions="bcmath,curl" --ignore-cache="php-src" --with-php=8.3
# 覆盖下载地址
spc download --for-extensions="bcmath" --custom-url "php-src:https://downloads.php.net/~user/php-8.5.0alpha1.tar.xz"
```
## build:php {#build-php}
从源码编译 PHP 及扩展。别名:`build`
```bash
spc build:php <extensions> [options]
```
`extensions`(必填):要静态编译的扩展名列表,逗号分隔(如 `"bcmath,openssl,curl"`)。
`build:php` 上也可使用所有 `download` 选项,只需加上 `--dl-` 前缀(如 `--dl-with-php=8.3``--dl-parallel=4`),这些参数将传递给构建前自动运行的下载器。
### SAPI 选择 {#sapi-selection}
以下选项仅适用于 `build:php` 组合目标。如需单独构建某个 SAPI请使用对应的专用命令`spc build:php-cli`)。
| 选项 | 说明 |
|---|---|
| `--build-cli` | 构建 `cli` SAPI`php` / `php.exe`|
| `--build-fpm` | 构建 `php-fpm`(仅 Linux 和 macOS|
| `--build-cgi` | 构建 `php-cgi` |
| `--build-micro` | 构建 `micro.sfx` |
| `--build-embed` | 构建 embed 静态库(`libphp.a` / `php8embed.lib`|
| `--build-frankenphp` | 构建 FrankenPHP 二进制 |
### 通用构建选项 {#common-build-options}
| 选项 | 缩写 | 说明 |
|---|---|---|
| `--no-strip` | | 保留调试符号,不精简二进制 |
| `--with-upx-pack` | | 用 UPX 压缩产物(需先 `spc install-pkg upx`;仅 Linux 和 Windows|
| `--disable-opcache-jit` | | 禁用 OPcache JIT |
| `--with-config-file-path=<path>` | | PHP 查找 `php.ini` 的目录(默认:`/usr/local/etc/php`|
| `--with-config-file-scan-dir=<path>` | | PHP 扫描追加 `.ini` 文件的目录(默认:`/usr/local/etc/php/conf.d`|
| `--with-hardcoded-ini=<k=v>` | `-I` | 编译时将 INI 配置硬编码进二进制(可重复使用)|
| `--enable-zts` | | 启用线程安全ZTS模式 |
| `--no-smoke-test` | | 跳过构建后的冒烟测试 |
| `--with-suggests` | `-L` / `-E` | 同时解析并安装建议包 |
| `--with-packages=<list>` | | 额外安装的包 |
| `--no-download` | | 跳过下载步骤(使用已有缓存)|
| `--with-added-patch=<file>` | `-P` | 注入外部 PHP 补丁脚本(可重复使用)|
| `--build-shared=<list>` | `-D` | 指定编译为共享 `.so` / `.dll` 的扩展 |
### micro 专用选项 {#micro-options}
| 选项 | 说明 |
|---|---|
| `--with-micro-fake-cli` | 让 `micro``PHP_SAPI` 报告为 `cli` 而非 `micro` |
| `--without-micro-ext-test` | 跳过构建后的 `micro.sfx` 扩展测试 |
| `--with-micro-logo=<path>` | 为 `micro.sfx` 嵌入自定义 `.ico` 图标(仅 Windows|
| `--enable-micro-win32` | 将 `micro.sfx` 构建为 Win32 GUI 程序而非控制台程序(仅 Windows|
### frankenphp 专用选项 {#frankenphp-options}
| 选项 | 说明 |
|---|---|
| `--enable-zts` | FrankenPHP 必须开启线程安全 |
| `--with-frankenphp-app=<path>` | 将指定目录嵌入到 FrankenPHP 二进制中 |
### embed 专用选项 {#embed-options}
| 选项 | 说明 |
|---|---|
| `--build-shared=<list>` | 将指定扩展编译为共享库(需要 embed SAPI|
### 下载透传选项 {#download-options}
所有下载器选项均可加 `--dl-` 前缀使用:
| 选项 | 说明 |
|---|---|
| `--dl-with-php=<ver>` | 指定下载的 PHP 版本(默认 `8.4`|
| `--dl-prefer-binary` | 优先使用预编译二进制依赖 |
| `--dl-parallel=<n>` | 并行下载数 |
| `--dl-retry=<n>` | 失败重试次数 |
| `--dl-custom-url=<src:url>` | 覆盖指定源的下载地址 |
| `--dl-custom-git=<src:branch:url>` | 覆盖为自定义 git 仓库 |
### 示例
```bash
# 构建 cli SAPI
spc build:php "bcmath,openssl,curl" --build-cli
# 同时构建 cli + micro
spc build:php "bcmath,phar,openssl,curl" --build-cli --build-micro
# 指定 PHP 版本
spc build:php "bcmath,openssl" --build-cli --dl-with-php=8.3
# 硬编码 INI 到二进制
spc build:php "bcmath,pcntl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"
# 保留调试符号
spc build:php "bcmath,openssl" --build-cli --no-strip
# 构建 FrankenPHP需开启 ZTS
spc build:php "bcmath,openssl,curl" --build-frankenphp --enable-zts
```
## build:php-cli, build:php-fpm, build:php-micro, build:php-embed, build:php-cgi, build:frankenphp
专用单目标构建命令,接受与 `build:php` 相同的选项,但不需要 SAPI 选择标志(`--build-*`),目标已隐式确定。
```bash
spc build:php-cli "bcmath,openssl,curl"
spc build:php-micro "bcmath,phar,openssl"
spc build:php-fpm "bcmath,openssl,curl,pdo_mysql"
spc build:php-embed "bcmath,openssl"
spc build:frankenphp "bcmath,openssl,curl" --enable-zts
```
## craft
读取 `craft.yml` 并自动完成全流程构建。
```bash
spc craft [path/to/craft.yml]
```
未指定路径时,使用当前工作目录下的 `craft.yml`。配置格式参见 [craft.yml 配置](../develop/craft-yml)。
## doctor
检查当前环境是否满足编译要求。
```bash
spc doctor [--auto-fix[=never]]
```
| 选项 | 说明 |
|---|---|
| `--auto-fix` | 自动修复检测到的问题(使用系统包管理器)|
| `--auto-fix=never` | 仅报告问题,不尝试自动修复 |
## dev:shell
进入加载了 StaticPHP 构建环境的交互式 Shell编译器 wrapper、`buildroot/``pkgroot/` 等均已添加到 `PATH`)。
```bash
spc dev:shell
```
可用于在 embed SAPI 的 `libphp.a` 上编译小型 C 程序,或手动检查构建环境。

View File

@@ -1,4 +1,51 @@
# 环境变量
<!-- TODO: config/env.ini 和 config/env.custom.ini 支持的所有环境变量完整表格
从 v2 env-vars.md 迁移并更新。 -->
本页面的环境变量列表中所提到的所有环境变量都具有默认值,除非另有说明。你可以通过设置这些环境变量来覆盖默认值
## 环境变量列表
StaticPHP 将环境变量集中到了 `config/env.ini` 文件中,你可以通过修改这个文件来设置环境变量。
我们将 StaticPHP 支持的环境变量分为三种:
- **全局内部环境变量**:在 StaticPHP 启动后即声明,你可以在 StaticPHP 的内部使用 `getenv()` 来获取他们,也可以在启动 StaticPHP 前覆盖。
- **固定环境变量**:在 StaticPHP 启动后声明,你仅可使用 `getenv()` 获取,但无法通过 shell 脚本对其覆盖。
- **配置文件环境变量**:在 StaticPHP 构建前声明,你可以通过修改 `config/env.ini` 文件或通过 shell 脚本来设置这些环境变量。
你可以阅读 [config/env.ini](https://github.com/crazywhalecc/static-php-cli/blob/v3/config/env.ini) 中每项参数的注释来了解其作用(仅限英文版)。
## 自定义环境变量
一般情况下,你不需要修改任何以下环境变量,因为它们已经被设置为最佳值。
但是,如果你有特殊需求,你可以通过设置这些环境变量来满足你的需求(比如你需要调试不同编译参数下的 PHP 性能表现)。
如需使用自定义环境变量,你可以在终端中使用 `export` 命令或者在命令前直接设置环境变量,例如:
```shell
# export 方式
export SPC_CONCURRENCY=4
spc build:php "mbstring,pcntl" --build-cli
# 直接设置方式
SPC_CONCURRENCY=4 spc build:php "mbstring,pcntl" --build-cli
```
或者,如果你需要长期修改某个环境变量,你可以通过修改 `config/env.ini` 文件来实现。
`config/env.ini` 分为三段,其中 `[global]` 全局有效,`[windows]``[macos]``[linux]` 仅对应的操作系统有效。
例如,你需要修改编译 PHP 的 `./configure` 命令,你可以在 `config/env.ini` 文件中找到 `SPC_CMD_PREFIX_PHP_CONFIGURE` 环境变量,然后修改其值即可。
但如果你的构建条件比较复杂,需要多种 env.ini 进行切换,我们推荐你使用 `config/env.custom.ini` 文件,这样你可以在不修改默认的 `config/env.ini` 文件的情况下,
通过写入额外的重载项目指定你的环境变量。
```ini
; This is an example of `config/env.custom.ini` file,
; we modify the `SPC_CONCURRENCY` and linux default CFLAGS passing to libs and PHP
[global]
SPC_CONCURRENCY=4
[linux]
SPC_DEFAULT_C_FLAGS="-O3"
```

View File

@@ -1,3 +1,159 @@
# 扩展注意事项
<!-- TODO: 从 v2 extension-notes.md 迁移并更新。各扩展编译特殊说明。 -->
因为是静态编译,扩展不会 100% 完美编译,而且不同扩展对 PHP、环境都有不同的要求这里将一一列举。
## curl
HTTP3 支持默认未启用,需在编译时添加 `--with-libs="nghttp2,nghttp3,ngtcp2"` 以启用 PHP 8.4 及以上版本的 HTTP3 支持。
使用 curl 请求 HTTPS 时,可能存在 `error:80000002:system library::No such file or directory` 错误,
解决办法详见 [FAQ](../faq/)。
## phpmicro
1. phpmicro SAPI 仅支持 PHP >= 8.0 版本。
## swoole
1. swoole >= 5.0 版本仅支持 PHP >= 8.0 版本。
2. swoole 目前不支持 PHP 8.0 版本 curl 的 hook后续有可能会修复
3. 编译时只包含 `swoole` 扩展时不会完整开启支持的 Swoole 数据库协程 hook如需使用请加入对应的 `swoole-hook-xxx` 扩展。
4. swoole 在部分扩展组合下可能出现 `zend_mm_heap corrupted` 问题,暂未找到是什么原因导致的。
## swoole-hook-pgsql
swoole-hook-pgsql 不是一个扩展,而是 Swoole 的 Hook 特性。
如果你在编译时添加了 `swoole,swoole-hook-pgsql`,你将启用 Swoole 的 PostgreSQL 客户端和 `pdo_pgsql` 扩展的协程模式。
swoole-hook-pgsql 与 `pdo_pgsql` 扩展冲突。如需使用 Swoole 和 `pdo_pgsql`,请删除 pdo_pgsql 扩展,启用 `swoole``swoole-hook-pgsql` 即可。
该扩展包含了 `pdo_pgsql` 的协程环境的实现。
在 macOS 系统,`pdo_pgsql` 可能无法正常连接到 postgresql 服务器,请谨慎使用。
## swoole-hook-mysql
swoole-hook-mysql 不是一个扩展,而是 Swoole 的 Hook 特性。
如果你在编译时添加了 `swoole,swoole-hook-mysql`,你将启用 Swoole 的 `mysqlnd``pdo_mysql` 的协程模式。
## swoole-hook-sqlite
swoole-hook-sqlite 不是一个扩展,而是 Swoole 的 Hook 特性。
如果你在编译时添加了 `swoole,swoole-hook-sqlite`,你将启用 Swoole 的 `pdo_sqlite` 的协程模式Swoole 必须为 5.1 以上)。
swoole-hook-sqlite 与 `pdo_sqlite` 扩展冲突。如需使用 Swoole 和 `pdo_sqlite`,请删除 pdo_sqlite 扩展,启用 `swoole``swoole-hook-sqlite` 即可。
该扩展包含了 `pdo_sqlite` 的协程环境的实现。
## swoole-hook-odbc
swoole-hook-odbc 不是一个扩展,而是 Swoole 的 Hook 特性。
如果你在编译时添加了 `swoole,swoole-hook-odbc`,你将启用 Swoole 的 `odbc` 扩展的协程模式。
swoole-hook-odbc 与 `pdo_odbc` 扩展冲突。如需使用 Swoole 和 `pdo_odbc`,请删除 `pdo_odbc` 扩展,启用 `swoole``swoole-hook-odbc` 即可。
该扩展包含了 `pdo_odbc` 的协程环境的实现。
## swow
1. swow 仅支持 PHP 8.0+ 版本。
## imagick
1. OpenMP 支持已被禁用,这是维护者推荐的做法,系统软件包也是如此配置。
## imap
1. 该扩展目前不支持 Kerberos。
2. 由于底层的 c-client、ext-imap 不是线程安全的。无法在 `--enable-zts` 构建中使用它。
3. 该扩展已在 PHP 8.4 中被移除,因此我们建议您寻找替代实现,例如 [Webklex/php-imap](https://github.com/Webklex/php-imap)。
## gd
1. gd 扩展依赖了较多的额外图形库,默认情况下,直接使用 `bin/spc build gd` 不会引入和支持部分图形库,例如 `libjpeg``libavif` 等,
需要使用 `--with-libs` 参数补全。目前支持 `freetype,libjpeg,libavif,libwebp` 四个库的支持,所以这里可以使用以下命令来让 gd 库引入它们:
```bash
bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli
```
## mcrypt
1. 目前未支持,未来也不计划支持此扩展。[#32](https://github.com/crazywhalecc/static-php-cli/issues/32)
## oci8
1. oci8 是 Oracle 数据库的扩展,因为 Oracle 提供的扩展所依赖的库未提供静态编译版本(`.a`)或源代码,无法使用静态链接的方式将此扩展编译到 php 内,故无法支持。
## xdebug
1. Xdebug 只能作为共享扩展进行构建。在 Linux 上,您需要使用 glibc 变体的 `SPC_TARGET`(例如 `native-native-gnu.2.17`),而不是默认的 musl 静态目标。
2. 使用 Linux/glibc 或 macOS 时,您可以使用 `--build-shared=xdebug` 将 Xdebug 编译为共享扩展。
编译后的 `./php` 二进制文件可以通过指定 INI 文件进行配置和运行,例如 `./php -d 'zend_extension=/path/to/xdebug.so' your-code.php`
## xml
1. xml 包括 xmlreader、xmlwriter、dom、simplexml 等,添加 xml 扩展时最好同时启用这些扩展。
2. libxml 包含在 xml 扩展中。启用 xml 相当于启用 libxml。
## glfw
1. glfw 扩展依赖 OpenGL在 Linux 平台还依赖 X11 等环境,这些库都无法被轻易地静态链接。
2. 在 macOS 系统下,我们可以动态链接系统的 OpenGL 和一些相关的库。
## rar
1. rar 扩展目前在 macOS x86_64 环境下与 `common` 扩展集合编译 phpmicro 存在问题。
## pgsql
~~pgsql ssl 连接与 openssl 3.2.0 不兼容。相关链接:~~
- ~~<https://github.com/Homebrew/homebrew-core/issues/155651>~~
- ~~<https://github.com/Homebrew/homebrew-core/pull/155699>~~
- ~~<https://github.com/postgres/postgres/commit/c82207a548db47623a2bfa2447babdaa630302b9>~~
pgsql 16.2 修复了这个 Bug现在正常工作了。
在 pgsql 使用 SSL 连接时,可能存在 `error:80000002:system library::No such file or directory` 错误,
解决办法详见 [FAQ](../faq/)。
## openssl
使用基于 openssl 的扩展(如 curl、pgsql 等网络库)时,可能存在 `error:80000002:system library::No such file or directory` 错误,
解决办法详见 [FAQ](../faq/)。
## password-argon2
1. password-argon2 不是一个标准的扩展。`password_hash` 函数的 `PASSWORD_ARGON2ID` 算法需要 libsodium 或 libargon2 才能工作。
2. 使用 password-argon2 可以为此启用多线程支持。
## ffi
1. 由于 musl libc 静态链接的限制,无法加载动态库,因此无法使用 ffi。
如果您需要使用 ffi 扩展,请使用基于 glibc 的 `SPC_TARGET`(例如 `native-native-gnu.2.17`),详见 [SAPI 参考](./sapi-reference)。
2. macOS 支持 ffi 扩展,但某些内核不包含调试符号时会出现错误。
3. Windows x64 支持 ffi 扩展。
## xhprof
xhprof 扩展包含三部分:`xhprof_extension``xhprof_html``xhprof_libs`。编译的二进制中只包含 `xhprof_extension`
如果需要使用 xhprof请到 [pecl.php.net/package/xhprof](http://pecl.php.net/package/xhprof) 下载源码,指定 `xhprof_libs``xhprof_html` 路径来使用。
## event
event 扩展在 macOS 系统下编译后暂无法使用 `openpty` 特性。相关 Issue
- [static-php-cli#335](https://github.com/crazywhalecc/static-php-cli/issues/335)
## parallel
parallel 扩展只支持 PHP 8.0 及以上版本,并只支持 ZTS 构建(`--enable-zts`)。
## spx
1. SPX 目前不支持 Windows且官方仓库也不支持静态编译static-php-cli 使用了 [修改版本](https://github.com/static-php/php-spx)。
## mimalloc
1. 从技术上讲,这不是扩展,而是一个库。
2. 在 Linux 或 macOS 上使用 `--with-libs="mimalloc"` 进行构建将覆盖默认分配器。
3. 目前,这还处于实验阶段,但建议在线程环境中使用。

View File

@@ -1,3 +1,17 @@
<script setup>
import SearchTable from "../../.vitepress/components/SearchTable.vue";
</script>
# 支持的扩展列表
<!-- TODO: 由 `bin/spc dev:gen-ext-docs` 自动生成。v3 命令实现后填充。 -->
> - ✅: 已支持
> - 空白: 目前还不支持,或正在支持中
<search-table />
::: tip
如果缺少您需要的扩展,您可以创建 [功能请求](https://github.com/crazywhalecc/static-php-cli/issues)。
某些扩展或其依赖的库会有可选特性(例如 gd 可选支持 libwebp、freetype 等)。
仅使用 `bin/spc build gd --build-cli` 不会包含这些可选依赖——StaticPHP 默认遵循最小依赖原则。
:::

View File

@@ -15,7 +15,7 @@ StaticPHP 提供两种构建方式,根据使用场景选择:
| 方式 | 适合场景 |
|--------------|--------------------------|
| `craft` 一键构建 | 日常使用、快速上手 |
| 分步构建 | CI/CD 流水线、需要拆分下载与编译阶段的场景 |
| 分步构建 | 细化构建流程 |
## 方式一:`craft` 一键构建(推荐)
@@ -90,20 +90,20 @@ spc download "curl,openssl" --with-php=8.4
```bash
# 网络较慢时,可增大并发数和重试次数
spc download --for-extensions=bcmath,openssl,curl --parallel 10 --retry=3
spc download --for-extensions="bcmath,openssl,curl" --parallel 10 --retry=3
# 优先使用预编译的二进制依赖,跳过源码编译(大幅加速构建)
spc download --for-extensions=bcmath,openssl,curl --prefer-binary
spc download --for-extensions="bcmath,openssl,curl" --prefer-binary
```
### 第二步:构建 PHP
```bash
# 构建 cli SAPI
spc build:php bcmath,phar,zlib,openssl,curl,fileinfo,tokenizer --build-cli
spc build:php "bcmath,phar,zlib,openssl,curl,fileinfo,tokenizer" --build-cli
# 同时构建多个 SAPI
spc build:php bcmath,phar,zlib,openssl,curl --build-cli --build-micro
spc build:php "bcmath,phar,zlib,openssl,curl" --build-cli --build-micro
```
@@ -125,7 +125,7 @@ spc build:php bcmath,phar,zlib,openssl,curl --build-cli --build-micro
硬编译 INI 的例子——预设更大的内存限制,并禁用 `system` 函数:
```bash
spc build:php bcmath,pcntl,posix --build-cli -I "memory_limit=4G" -I "disable_functions=system"
spc build:php "bcmath,pcntl,posix" --build-cli -I "memory_limit=4G" -I "disable_functions=system"
```
## 打包 micro 应用
@@ -160,7 +160,7 @@ spc micro:combine your-app.phar --output=your-app -N /path/to/custom.ini
- `-vvv` 将显示 `DEBUG` 级别的日志,并将其他 shell 命令执行的 STDOUT 输出到终端。
```bash
spc build:php bcmath,openssl --build-cli -vv
spc build:php "bcmath,openssl" --build-cli -vv
```
或者,你也可以查看 `log/spc.shell.log``log/spc.output.log` 获取终端输出和 StaticPHP 日志。
@@ -170,7 +170,7 @@ spc build:php bcmath,openssl --build-cli -vv
```bash
spc reset
# 然后重新构建
spc build:php bcmath,openssl --build-cli
spc build:php "bcmath,openssl" --build-cli
```
::: tip
@@ -182,6 +182,7 @@ spc build:php bcmath,openssl --build-cli
## 接下来
- [PHP SAPI 构建参考](./sapi-reference) - 各个 PHP 的 SAPI 构建及使用指南
- [命令行参考](./cli-reference) — 所有命令与选项的完整说明
- [扩展列表](./extensions) — 查看支持的扩展及其依赖关系
- [常见问题](./troubleshooting) — 构建失败时的排查指南

View File

@@ -5,6 +5,8 @@
StaticPHP 是一个构建工具,能够将 PHP 解释器与你所需的扩展一起编译成一个独立的二进制文件,无需在目标系统上预先安装 PHP 或任何依赖库。
构建产物可以直接分发和运行,适用于 Linux、macOS 和 Windows 平台。
StaticPHP 的能力不止于 PHP。依托同一套构建基础设施它还可以将 `curl``pkg-config``htop` 等常用命令行工具编译为独立的静态二进制,无需在目标机器上安装任何依赖即可直接运行。后续还会持续扩充内置支持的工具集。
## 为什么要构建静态 PHP
普通 PHP 安装依赖系统环境:你需要先安装 PHP、再装扩展、再处理各个发行版之间的差异。
@@ -18,7 +20,7 @@ StaticPHP 是一个构建工具,能够将 PHP 解释器与你所需的扩展
## phpmicro把 PHP 和你的代码打包成一个文件
[phpmicro](https://github.com/easysoft/phpmicro) 是一个第三方 PHP SAPIStaticPHP 对其提供原生支持。
[phpmicro](https://micro.static-php.dev) 是一个第三方 PHP SAPIStaticPHP 对其提供原生支持。
它能将 PHP 解释器本身和你的 `.php` 源文件(或 `.phar` 打包文件)合并成单个自解压可执行文件(`sfx`)。
```
@@ -48,6 +50,6 @@ StaticPHP 支持将 FrankenPHP 连同所需扩展一起静态编译,
## 接下来
- [安装 SPC](./installation) — 安装 StaticPHP 构建工具
- [安装 StaticPHP](./installation) — 安装 StaticPHP 构建工具
- [第一次构建](./first-build) — 完整流程演示:从下载源码到得到可执行文件
- [命令行参考](./cli-reference) — 所有命令与选项速查

View File

@@ -0,0 +1,277 @@
---
outline: 'deep'
---
# PHP SAPI 构建参考
::: tip
如果你采用的是 spc 二进制方式安装,请将本章节中的所有 `spc` 替换为 `./spc``.\spc.exe`
如果你采用的是源码安装,请将 `spc` 替换为 `bin/spc`
:::
本页详细介绍 StaticPHP 支持的各类 PHP SAPI 的构建参数和使用方式。
## 概览
| SAPI | 构建参数 | 产物路径Linux/macOS| 产物路径Windows| 平台支持 |
|---|---|---|---|---|
| cli | `--build-cli` | `buildroot/bin/php` | `buildroot/bin/php.exe` | Linux、macOS、Windows |
| fpm | `--build-fpm` | `buildroot/bin/php-fpm` | — | Linux、macOS |
| micro | `--build-micro` | `buildroot/bin/micro.sfx` | `buildroot/bin/micro.sfx` | Linux、macOS、Windows |
| embed | `--build-embed` | `buildroot/lib/libphp.a` | `buildroot/lib/php8embed.lib` | Linux、macOS、Windows |
| frankenphp | `--build-frankenphp` | `buildroot/bin/frankenphp` | `buildroot/bin/frankenphp.exe` | Linux、macOS、Windows |
## cli
`cli` 是标准的 PHP 命令行程序,适用于在终端执行 PHP 脚本、交互式 shell 等场景。
### 构建
```bash
spc build:php "bcmath,openssl,curl" --build-cli
```
Windows 下产物为 `buildroot/bin/php.exe`,其他平台为 `buildroot/bin/php`
完整选项参见 [build:php — SAPI 选择](./cli-reference#sapi-selection) 和 [build:php — 通用构建选项](./cli-reference#common-build-options)。
### 使用
```bash
# 查看版本和已加载扩展
./buildroot/bin/php -v
./buildroot/bin/php -m
# 执行脚本
./buildroot/bin/php your-script.php
# 交互模式
./buildroot/bin/php -a
```
### php.ini 路径
静态编译的 PHP cli 按以下顺序搜索 `php.ini`
1. 命令行参数 `-c /path/to/php.ini` 指定的路径
2. `PHP_INI_PATH` 环境变量指定的路径
3. 编译时通过 `--with-config-file-path` 指定的目录(默认为 `/usr/local/etc/php`
可以通过 `./buildroot/bin/php --ini` 查看 PHP 实际使用的 ini 路径。
### 硬编码 INI
使用 `-I` 参数可以在编译时将 INI 配置硬编码到二进制中,无需额外的 `php.ini` 文件:
```bash
spc build:php "bcmath,pcntl" --build-cli -I "memory_limit=4G" -I "disable_functions=system,exec"
```
硬编码 INI 适用于 `cli``micro``embed` SAPI。
## fpm
`fpm`FastCGI Process Manager与 Nginx、Apache 等 Web 服务器配合使用,适用于传统的 Web 应用部署场景。
::: warning
`fpm` 不支持 Windows 平台。
:::
### 构建
```bash
spc build:php "bcmath,openssl,curl,pdo_mysql" --build-fpm
```
产物为 `buildroot/bin/php-fpm`
完整选项参见 [build:php — SAPI 选择](./cli-reference#sapi-selection) 和 [build:php — 通用构建选项](./cli-reference#common-build-options)。
### 使用
`buildroot/bin/php-fpm` 拷贝到服务器,像普通的 `php-fpm` 一样使用:
```bash
# 查看版本
./buildroot/bin/php-fpm -v
# 指定配置文件启动
./buildroot/bin/php-fpm -c /path/to/php.ini -y /path/to/php-fpm.conf
# 测试配置文件
./buildroot/bin/php-fpm -t
```
### 与 Nginx 配合使用示例
```nginx
server {
listen 80;
root /var/www/html;
index index.php;
location ~ \.php$ {
fastcgi_pass 127.0.0.1:9000;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
include fastcgi_params;
}
}
```
`php-fpm.conf` 示例:
```ini
[global]
pid = /var/run/php-fpm.pid
error_log = /var/log/php-fpm.log
[www]
listen = 127.0.0.1:9000
pm = dynamic
pm.max_children = 5
pm.start_servers = 2
pm.min_spare_servers = 1
pm.max_spare_servers = 3
```
## micro
`micro` 是基于 [phpmicro](https://github.com/easysoft/phpmicro) 的自包含可执行文件 SAPI。通过 `spc micro:combine` 命令,可以将 `micro.sfx` 与 PHP 代码合并为一个独立的可执行文件,无需在目标机器上安装 PHP。
### 构建
```bash
spc build:php "bcmath,phar,openssl,curl" --build-micro
```
产物为 `buildroot/bin/micro.sfx`
完整选项参见 [build:php — SAPI 选择](./cli-reference#sapi-selection)、[build:php — 通用构建选项](./cli-reference#common-build-options) 和 [build:php — micro 专用选项](./cli-reference#micro-options)。
### 打包应用
使用 `micro:combine` 命令将 PHP 脚本或 phar 文件打包:
```bash
# 打包 PHP 脚本
echo "<?php echo 'Hello, World!' . PHP_EOL;" > hello.php
spc micro:combine hello.php --output=hello
./hello
# 打包 phar 文件
spc micro:combine your-app.phar --output=your-app
./your-app
```
### 注入 INI 配置
打包时可以通过命令行参数或 ini 文件注入运行时配置:
```bash
# 通过命令行参数注入(-I 是 --with-ini-set 的简写)
spc micro:combine your-app.phar --output=your-app -I "memory_limit=512M" -I "curl.cainfo=/etc/ssl/certs/ca-certificates.crt"
# 通过 ini 文件注入(-N 是 --with-ini-file 的简写)
spc micro:combine your-app.phar --output=your-app -N /path/to/custom.ini
```
::: tip
`-I` 注入的 INI 是运行时配置,通过在 `micro.sfx` 末尾追加特殊结构实现。这与编译时用 `-I` 硬编码 INI 不同,两者可以共存。
:::
### 伪装为 cli SAPI
部分框架会检查 `PHP_SAPI` 的值,并限制在非 `cli` 环境下运行。`micro``PHP_SAPI` 默认值为 `micro`,可通过编译参数让其伪装为 `cli`
```bash
spc build:php "bcmath,phar" --build-micro --with-micro-fake-cli
```
### 指定自定义 micro.sfx 路径
```bash
spc micro:combine your-app.phar --output=your-app --with-micro=/path/to/your/micro.sfx
```
### 关于 phar 的路径问题
将应用打包为 phar 后phar 内部使用相对路径可能与预期不符。请参考[开发者文档 - Phar 目录问题](../develop/structure)了解详情。
## embed
`embed` SAPI 将 PHP 编译为静态库Linux/macOS 下为 `libphp.a`Windows 下为 `php8embed.lib`),可嵌入到其他 C/C++ 程序中运行 PHP 代码。
### 构建
```bash
spc build:php "bcmath,openssl" --build-embed
```
产物:
- Linux/macOS`buildroot/lib/libphp.a`,头文件在 `buildroot/include/`
- Windows`buildroot/lib/php8embed.lib`,头文件在 `buildroot/include/`
完整选项参见 [build:php — SAPI 选择](./cli-reference#sapi-selection)、[build:php — 通用构建选项](./cli-reference#common-build-options) 和 [build:php — embed 专用选项](./cli-reference#embed-options)。
::: tip
如何将 `libphp.a` / `php8embed.lib` 链接到你自己的项目(包括编译器选择、`dev:shell` 使用方式和完整 C 示例),将在开发者文档中专门介绍。
:::
## frankenphp
`frankenphp` 是基于 [FrankenPHP](https://github.com/php/frankenphp) 的现代 PHP 应用服务器,内置 Caddy支持 HTTP/2、HTTP/3、自动 HTTPS 等特性。
::: tip
StaticPHP 构建出的 `frankenphp` 是单个完全自包含的可执行文件。这与 FrankenPHP 官方提供的发行版不同,官方版本为动态链接二进制,需要单独安装 PHP。
:::
::: warning
FrankenPHP 必须启用线程安全模式,构建时务必加上 `--enable-zts`
:::
### 构建
```bash
spc build:php "bcmath,openssl,curl,pdo_mysql" --build-frankenphp --enable-zts
```
Linux/macOS 下产物为 `buildroot/bin/frankenphp`Windows 下为 `buildroot/bin/frankenphp.exe`
完整选项参见 [build:php — SAPI 选择](./cli-reference#sapi-selection)、[build:php — 通用构建选项](./cli-reference#common-build-options) 和 [build:php — frankenphp 专用选项](./cli-reference#frankenphp-options)。
### 使用
```bash
# 查看版本
./buildroot/bin/frankenphp version
# 以 PHP 内置服务器模式运行(用于开发调试)
./buildroot/bin/frankenphp php-server
# 运行 Worker 模式
./buildroot/bin/frankenphp run --config /path/to/Caddyfile
```
更多用法请参考 [FrankenPHP 官方文档](https://frankenphp.dev/docs/)。
## 动态扩展加载
静态 PHP 二进制是否能够通过 `dl()` 在运行时加载扩展,取决于其链接方式。
**macOS** — 构建产物始终动态链接系统库,支持通过 `dl()``php.ini` 在运行时加载 `.so` 扩展。
**Linux** — StaticPHP 默认构建目标为 `native-native-musl`:完全静态链接 musl libc 的二进制。由于运行时不存在动态链接器,`dl()` 被禁用FFI 扩展无法使用,也无法加载任何外部 `.so` 扩展。
如需在 Linux 上支持动态扩展加载,请在构建前设置 `SPC_TARGET` 环境变量:
```bash
SPC_TARGET=native-native-gnu.2.17 spc build:php "bcmath,openssl" --build-cli
```
如果你采用的是源码安装,也可以在 `config/env.ini` 中设置 `SPC_TARGET=native-native-gnu.2.17`,将其作为所有构建的默认值。
这将使用 Zig 工具链构建出一个准静态二进制,动态链接 glibc 2.17,可运行于大多数现代 GNU/Linux 发行版,无需 Docker也无需额外的交叉编译工具链。该产物支持 `dl()`、FFI 和运行时加载 `.so` 扩展,但无法运行于 Alpine Linux 等基于 musl 的系统。
**Windows** — Windows 上的 PHP 扩展均以 `.dll` 形式分发,且依赖官方动态构建的 PHP 中附带的 DLL 文件。StaticPHP 构建的静态 PHP 可执行文件不包含这些 DLL因此 Windows 不支持动态扩展加载,所有扩展必须在构建时静态编译进去。

View File

@@ -1,5 +1,32 @@
# 故障排除
<!-- TODO: 按类别整理常见构建失败及解决方法
章节:下载问题 / 编译错误 / 扩展冲突 / Windows 专项 / glibc Linux 专项。
从 v2 troubleshooting.md 迁移并扩充。 -->
使用 StaticPHP 过程中可能会碰到各种各样的故障,这里将讲述如何自行查看错误并反馈 Issue
## 下载失败问题
下载资源问题是 spc 最常见的问题之一。主要是由于 spc 下载资源使用的地址一般均为对应项目的官方网站或 GitHub 等,而这些网站可能偶尔会宕机、屏蔽 IP 地址。
在遇到下载失败后,可以多次尝试调用下载命令。
当下载资源时,你可能最终会看到类似 `curl: (56) The requested URL returned error: 403` 的错误,这通常是由于 GitHub 限制导致的。
你可以通过在命令中添加 `-vvv` 来验证,会看到类似 `[DEBU] Running command (no output) : curl -sfSL "https://api.github.com/repos/openssl/openssl/releases"` 的输出。
要解决这个问题,可以在 GitHub 上 [创建](https://github.com/settings/tokens) 一个个人访问令牌,并将其设置为环境变量 `GITHUB_TOKEN=<XXX>`
如果确认地址确实无法正常访问,可以提交 Issue 或 PR 更新地址或下载类型。
## Doctor 无法修复某些问题
在绝大部分情况下doctor 模块都可以对缺失的系统环境进行自动修复和安装,但也存在特殊的环境无法正常使用自动修复功能。
由于系统限制例如Windows 下无法自动安装 Visual Studio 等软件),自动修复功能无法用于某些项目。
在遇到无法自动修复功能时,如果遇到 `Some check items can not be fixed` 字样,则表明无法自动修复。
请根据终端显示的方法提交 Issue 或自行修复环境。
## 编译错误
遇到编译错误时,如果没有开启 `-vvv` 日志,请先开启调试日志,然后确定报错的命令。
报错的终端输出对于修复编译错误非常重要。
在提交 Issue 时,请上传终端日志的最后报错片段(或整个终端日志输出),并且包含使用的 `spc` 命令和参数。
如果你是重复构建,请参考 [首次构建 - 调试与重新构建](./first-build#调试与重新构建) 章节。

View File

@@ -296,18 +296,29 @@ class php extends TargetPackage
$x->isBuildStatic());
$shared_extensions = parse_extension_list($package->getBuildOption('build-shared') ?? []);
$install_packages = array_filter($installer->getResolvedPackages(), fn ($x) => $x->getType() !== 'php-extension' && $x->getName() !== 'php' && !str_starts_with($x->getName(), 'php-'));
return [
$builder = ApplicationContext::get(PackageBuilder::class);
$ignore_cache = $builder->getOption('dl-ignore-cache', false);
$php_will_be_refreshed = $ignore_cache !== false && (
$ignore_cache === null || // --dl-ignore-cache with no value (ignore all)
str_contains((string) $ignore_cache, 'php-src')
);
$info = [
'Build OS' => SystemTarget::getTargetOS() . ' (' . SystemTarget::getTargetArch() . ')',
'Build Target' => getenv('SPC_TARGET') ?: '',
'Build Toolchain' => ToolchainManager::getToolchainClass(),
'Build SAPI' => implode(', ', $sapis),
'PHP Version' => self::getPHPVersion(return_null_if_failed: true) ?? self::getPHPVersionFromArchive(return_null_if_failed: true) ?? 'Unknown',
'Static Extensions (' . count($static_extensions) . ')' => implode(',', array_map(fn ($x) => substr($x->getName(), 4), $static_extensions)),
'Shared Extensions (' . count($shared_extensions) . ')' => implode(',', $shared_extensions),
'Install Packages (' . count($install_packages) . ')' => implode(',', array_map(fn ($x) => $x->getName(), $install_packages)),
'Strip Binaries' => $package->getBuildOption('no-strip') ? 'No' : 'Yes',
'Enable ZTS' => $package->getBuildOption('enable-zts') ? 'Yes' : 'No',
];
if (!$php_will_be_refreshed) {
$info['PHP Version'] = self::getPHPVersion(return_null_if_failed: true) ?? self::getPHPVersionFromArchive(return_null_if_failed: true) ?? 'Unknown';
}
$info['Static Extensions (' . count($static_extensions) . ')'] = implode(',', array_map(fn ($x) => substr($x->getName(), 4), $static_extensions));
$info['Shared Extensions (' . count($shared_extensions) . ')'] = implode(',', $shared_extensions);
$info['Install Packages (' . count($install_packages) . ')'] = implode(',', array_map(fn ($x) => $x->getName(), $install_packages));
$info['Strip Binaries'] = $package->getBuildOption('no-strip') ? 'No' : 'Yes';
$info['Enable ZTS'] = $package->getBuildOption('enable-zts') ? 'Yes' : 'No';
return $info;
}
#[BeforeStage('php', 'build')]

View File

@@ -0,0 +1,87 @@
<?php
declare(strict_types=1);
namespace StaticPHP\Command\Dev;
use StaticPHP\Command\BaseCommand;
use StaticPHP\Config\PackageConfig;
use Symfony\Component\Console\Attribute\AsCommand;
#[AsCommand('dev:gen-ext-docs', 'Generate extension list JSON for documentation', [], true)]
class GenExtDocsCommand extends BaseCommand
{
protected bool $no_motd = true;
public function handle(): int
{
if (!spc_mode(SPC_MODE_SOURCE)) {
$this->output->writeln('<error>This command is only available in source mode.</error>');
return static::USER_ERROR;
}
$all = PackageConfig::getAll();
$extensions = [];
foreach ($all as $pkg_name => $config) {
if (($config['type'] ?? '') !== 'php-extension') {
continue;
}
// Strip ext- prefix for display name
$name = str_starts_with($pkg_name, 'ext-') ? substr($pkg_name, 4) : $pkg_name;
// Determine OS support from php-extension.os field.
// If the field is absent, the extension supports all three OSes.
$os_list = $config['php-extension']['os'] ?? null;
if ($os_list === null) {
$linux = true;
$macos = true;
$windows = true;
} else {
$linux = in_array('Linux', $os_list, true);
$macos = in_array('Darwin', $os_list, true);
$windows = in_array('Windows', $os_list, true);
}
$extensions[] = [
'name' => $name,
'linux' => $linux,
'macos' => $macos,
'windows' => $windows,
'url' => $this->resolveSourceUrl($config, $name),
];
}
// Sort alphabetically by name
usort($extensions, fn ($a, $b) => strcmp($a['name'], $b['name']));
$output = [
'generated_at' => date('c'),
'extensions' => $extensions,
];
$output_path = ROOT_DIR . '/docs/.vitepress/ext-data.json';
file_put_contents($output_path, json_encode($output, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES) . PHP_EOL);
$this->output->writeln("<info>Generated {$output_path} with " . count($extensions) . ' extensions.</info>');
return static::SUCCESS;
}
private function resolveSourceUrl(array $config, string $ext_name): ?string
{
$source = $config['artifact']['source'] ?? null;
if ($source === null) {
return null;
}
return match ($source['type'] ?? '') {
'pecl' => 'https://pecl.php.net/package/' . ($source['name'] ?? $ext_name),
'git' => rtrim($source['url'] ?? '', '/'),
'ghtar', 'ghrel', 'ghtagtar', 'pie' => isset($source['repo'])
? 'https://github.com/' . $source['repo']
: null,
default => null,
};
}
}

View File

@@ -18,7 +18,7 @@ class SPCConfigCommand extends BaseCommand
{
$this->addArgument('extensions', InputArgument::OPTIONAL, 'The extensions will be compiled, comma separated');
$this->addOption('with-libs', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('with-packages', null, InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('with-packages', 'p', InputOption::VALUE_REQUIRED, 'add additional libraries, comma separated', '');
$this->addOption('with-suggested-libs', 'L', null, 'Build with suggested libs for selected exts and libs');
$this->addOption('with-suggests', null, null, 'Build with suggested packages for selected exts and libs');
$this->addOption('with-suggested-exts', 'E', null, 'Build with suggested extensions for selected exts');

View File

@@ -11,6 +11,7 @@ use StaticPHP\Command\CraftCommand;
use StaticPHP\Command\Dev\DumpCapabilitiesCommand;
use StaticPHP\Command\Dev\DumpStagesCommand;
use StaticPHP\Command\Dev\EnvCommand;
use StaticPHP\Command\Dev\GenExtDocsCommand;
use StaticPHP\Command\Dev\IsInstalledCommand;
use StaticPHP\Command\Dev\LintConfigCommand;
use StaticPHP\Command\Dev\PackageInfoCommand;
@@ -81,6 +82,7 @@ class ConsoleApplication extends Application
new DumpStagesCommand(),
new DumpCapabilitiesCommand(),
new PackageInfoCommand(),
new GenExtDocsCommand(),
]);
// add additional commands from registries