commit 5218cb639d3e09b243c0f598cde812f84fc91d80 Author: crazywhalecc Date: Mon Jul 1 02:35:17 2024 +0000 deploy: 0dc463ef2adca7de6cf42ced7dd7183ac90ae200 diff --git a/.nojekyll b/.nojekyll new file mode 100644 index 00000000..e69de29b diff --git a/404.html b/404.html new file mode 100644 index 00000000..0974a01a --- /dev/null +++ b/404.html @@ -0,0 +1,21 @@ + + + + + + 404 | static-php-cli + + + + + + + + + + +
+ + + + \ No newline at end of file diff --git a/CNAME b/CNAME new file mode 100644 index 00000000..f0282be4 --- /dev/null +++ b/CNAME @@ -0,0 +1 @@ +static-php.dev \ No newline at end of file diff --git a/assets/app.DAnd8ZqT.js b/assets/app.DAnd8ZqT.js new file mode 100644 index 00000000..4c8d6b74 --- /dev/null +++ b/assets/app.DAnd8ZqT.js @@ -0,0 +1 @@ +import{U as o,a7 as p,a8 as u,a9 as l,aa as c,ab as f,ac as d,ad as m,ae as h,af as g,ag as A,d as P,u as v,y,x as w,ah as C,ai as R,aj as b,ak as E}from"./chunks/framework.CszIUXhs.js";import{R as S}from"./chunks/theme.Yd2LEGgK.js";function i(e){if(e.extends){const a=i(e.extends);return{...a,...e,async enhanceApp(t){a.enhanceApp&&await a.enhanceApp(t),e.enhanceApp&&await e.enhanceApp(t)}}}return e}const s=i(S),T=P({name:"VitePressApp",setup(){const{site:e,lang:a,dir:t}=v();return y(()=>{w(()=>{document.documentElement.lang=a.value,document.documentElement.dir=t.value})}),e.value.router.prefetchLinks&&C(),R(),b(),s.setup&&s.setup(),()=>E(s.Layout)}});async function _(){globalThis.__VITEPRESS__=!0;const e=x(),a=j();a.provide(u,e);const t=l(e.route);return a.provide(c,t),a.component("Content",f),a.component("ClientOnly",d),Object.defineProperties(a.config.globalProperties,{$frontmatter:{get(){return t.frontmatter.value}},$params:{get(){return t.page.value.params}}}),s.enhanceApp&&await s.enhanceApp({app:a,router:e,siteData:m}),{app:a,router:e,data:t}}function j(){return h(T)}function x(){let e=o,a;return g(t=>{let n=A(t),r=null;return n&&(e&&(a=n),(e||a===n)&&(n=n.replace(/\.js$/,".lean.js")),r=import(n)),o&&(e=!1),r},s.NotFound)}o&&_().then(({app:e,router:a,data:t})=>{a.go().then(()=>{p(a.route,t.site),e.mount("#app")})});export{_ as createApp}; diff --git a/assets/chunks/CliGenerator.Bj1S5l8x.js b/assets/chunks/CliGenerator.Bj1S5l8x.js new file mode 100644 index 00000000..968d4de0 --- /dev/null +++ b/assets/chunks/CliGenerator.Bj1S5l8x.js @@ -0,0 +1,2 @@ +import{d as le,s as g,h as V,v as X,o as b,c as r,j as i,t as l,F as U,E as L,e as z,a2 as x,a3 as G,a4 as S,a5 as ne,a as oe,a6 as M,p as ae,l as de,_ as pe}from"./framework.CszIUXhs.js";const ue={support:{BSD:"wip"},type:"external","arg-type":"custom",source:"amqp","lib-depends":["librabbitmq"],"ext-depends-windows":["openssl"]},ce={type:"external",source:"apcu"},be={type:"builtin"},re={type:"builtin","arg-type-unix":"with-prefix","arg-type-windows":"with","lib-depends":["bzip2"]},we={type:"builtin"},ge={type:"builtin"},xe={notes:!0,type:"builtin","arg-type":"with","lib-depends":["curl"],"ext-depends-windows":["zlib","openssl"]},me={type:"builtin","arg-type":"custom","lib-suggests":["qdbm"]},he={support:{BSD:"wip"},type:"builtin","arg-type":"custom","arg-type-windows":"with","lib-depends":["libxml2","zlib"],"ext-depends-windows":["xml"]},ye={type:"external",source:"ext-ds"},ve={support:{Windows:"wip",BSD:"wip",Darwin:"wip",Linux:"wip"},type:"wip"},fe={support:{Windows:"wip",BSD:"wip"},notes:!0,type:"external",source:"ext-event","arg-type":"custom","lib-depends":["libevent"],"ext-depends":["openssl"],"ext-suggests":["sockets"]},ze={type:"builtin"},Se={support:{Linux:"no",BSD:"wip"},notes:!0,"arg-type":"custom",type:"builtin","lib-depends-unix":["libffi"],"lib-depends-windows":["libffi-win"]},De={type:"builtin"},ke={type:"builtin"},Be={type:"builtin","lib-suggests":["openssl"]},qe={support:{BSD:"wip"},notes:!0,type:"builtin","arg-type":"custom","arg-type-windows":"with","lib-depends":["zlib","libpng"],"ext-depends":["zlib"],"lib-suggests":["libavif","libwebp","libjpeg","freetype"]},_e={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["gettext"]},Ee={support:{Windows:"wip",BSD:"no",Linux:"no"},notes:!0,type:"external","arg-type":"custom",source:"ext-glfw","lib-depends":["glfw"],"lib-depends-windows":[]},We={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["gmp"]},Ce={support:{BSD:"wip"},type:"builtin","arg-type":"with-prefix","arg-type-windows":"with","lib-depends-unix":["libiconv"],"lib-depends-windows":["libiconv-win"]},Ie={support:{Windows:"wip",BSD:"wip"},type:"external",source:"igbinary"},Pe={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-imagick","arg-type":"custom","lib-depends":["imagemagick"]},Ue={support:{Windows:"wip",BSD:"wip"},notes:!0,type:"builtin","arg-type":"custom","lib-depends":["imap"],"ext-suggests":["openssl"]},Le={support:{Windows:"no",BSD:"wip",Darwin:"no"},type:"external",source:"inotify"},Ne={support:{Windows:"no",BSD:"wip"},type:"builtin","lib-depends":["icu"]},Oe={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["ldap"],"lib-suggests":["gmp","libsodium"],"ext-suggests":["openssl"]},Ae={support:{BSD:"wip"},type:"builtin","arg-type":"none","ext-depends":["xml"]},Ve={type:"builtin","arg-type":"custom","ext-depends":["mbstring"],"lib-depends":["onig"]},Te={type:"builtin","arg-type":"custom"},je={type:"wip",support:{Windows:"no",BSD:"no",Darwin:"no",Linux:"no"},notes:!0},$e={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-memcache","arg-type":"custom","lib-depends":["zlib"],"ext-depends":["session"]},Xe={support:{Windows:"wip",BSD:"wip",Linux:"no"},type:"external",source:"memcached","arg-type":"custom","cpp-extension":!0,"lib-depends":["libmemcached"],"ext-depends":["session","zlib"]},Ge={support:{BSD:"wip",Windows:"wip"},type:"external",source:"mongodb","arg-type":"custom","lib-suggests":["icu","openssl","zstd","zlib"]},Me={type:"builtin","arg-type":"with","ext-depends":["mysqlnd"]},Re={type:"builtin","arg-type-windows":"with","lib-depends":["zlib"]},He={type:"wip",support:{Windows:"wip",BSD:"no",Darwin:"no",Linux:"no"},notes:!0},Fe={type:"builtin","arg-type":"custom"},Ze={notes:!0,type:"builtin","arg-type":"custom","arg-type-windows":"with","lib-depends":["openssl","zlib"],"ext-depends":["zlib"]},Qe={support:{BSD:"wip"},notes:!0,type:"external",source:"parallel","arg-type-windows":"with","lib-depends-windows":["pthreads4w"]},Ke={support:{Windows:"no"},type:"builtin","unix-only":!0},Ye={type:"builtin"},Je={type:"builtin","arg-type":"with","ext-depends":["pdo","mysqlnd"]},ei={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","ext-depends":["pdo","pgsql"],"lib-depends":["postgresql"]},ii={support:{BSD:"wip"},type:"builtin","arg-type":"with","ext-depends":["pdo","sqlite3"],"lib-depends":["sqlite"]},si={support:{BSD:"wip"},type:"external",source:"pdo_sqlsrv","arg-type":"with","ext-depends":["pdo","sqlsrv"]},ti={support:{Windows:"wip",BSD:"wip"},notes:!0,type:"builtin","arg-type":"with-prefix","lib-depends":["postgresql"]},li={type:"builtin","ext-depends":["zlib"]},ni={support:{Windows:"no"},type:"builtin","unix-only":!0},oi={support:{Windows:"wip",BSD:"wip"},type:"external",source:"protobuf"},ai={support:{BSD:"wip",Darwin:"partial"},notes:!0,type:"external",source:"rar","cpp-extension":!0},di={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["readline"]},pi={support:{Windows:"wip",BSD:"wip"},type:"external",source:"redis","arg-type":"custom","ext-suggests":["session","igbinary"],"lib-suggests":["zstd","liblz4"]},ui={type:"builtin"},ci={type:"builtin"},bi={type:"external",source:"ext-simdjson","cpp-extension":!0},ri={support:{BSD:"wip"},type:"builtin","arg-type":"custom","lib-depends":["libxml2"],"ext-depends-windows":["xml"]},wi={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-snappy","cpp-extension":!0,"arg-type":"custom","lib-depends":["snappy"],"ext-suggest":["apcu"]},gi={support:{BSD:"wip"},type:"builtin","arg-type":"custom","lib-depends":["libxml2"],"ext-depends-windows":["xml"]},xi={type:"builtin"},mi={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with","lib-depends":["libsodium"]},hi={support:{BSD:"wip"},type:"builtin","arg-type":"with-prefix","arg-type-windows":"with","lib-depends":["sqlite"]},yi={support:{BSD:"wip"},type:"external",source:"sqlsrv","lib-depends-unix":["unixodbc"],"ext-depends-linux":["pcntl"],"cpp-extension":!0},vi={support:{BSD:"wip"},type:"external",source:"ext-ssh2","arg-type":"with-prefix","arg-type-windows":"with","lib-depends":["libssh2"],"ext-depends-windows":["openssl","zlib"]},fi={support:{Windows:"no",BSD:"wip"},notes:!0,type:"external",source:"swoole","arg-type":"custom","cpp-extension":!0,"unix-only":!0,"lib-depends":["libcares","brotli","nghttp2","zlib"],"ext-depends":["openssl","curl"],"ext-suggests":["swoole-hook-pgsql","swoole-hook-mysql","swoole-hook-sqlite"]},zi={support:{BSD:"wip"},notes:!0,type:"external",source:"swow","arg-type":"custom","lib-suggests":["openssl","curl"],"ext-suggests":["openssl","curl"]},Si={support:{Windows:"no",BSD:"wip"},type:"builtin","unix-only":!0},Di={support:{Windows:"no",BSD:"wip"},type:"builtin","unix-only":!0},ki={support:{BSD:"wip"},type:"builtin"},Bi={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["tidy"]},qi={type:"builtin"},_i={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-uuid","arg-type":"with-prefix","lib-depends":["libuuid"]},Ei={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-uv","arg-type":"with-prefix","lib-depends":["libuv"],"ext-depends":["sockets"]},Wi={type:"builtin",support:{Windows:"wip",BSD:"no",Darwin:"no",Linux:"no"},notes:!0},Ci={support:{Windows:"wip",BSD:"wip"},notes:!0,type:"external",source:"xhprof","ext-depends":["ctype"]},Ii={support:{Windows:"wip",BSD:"wip"},type:"external",source:"xlswriter","arg-type":"custom","ext-depends":["zlib","zip"],"lib-suggests":["openssl"]},Pi={support:{BSD:"wip"},notes:!0,type:"builtin","arg-type":"custom","arg-type-windows":"with","lib-depends":["libxml2"],"ext-depends-windows":["iconv"]},Ui={support:{BSD:"wip"},type:"builtin","arg-type":"custom","lib-depends":["libxml2"],"ext-depends-windows":["xml","dom"]},Li={support:{BSD:"wip"},type:"builtin","arg-type":"custom","lib-depends":["libxml2"],"ext-depends-windows":["xml"]},Ni={support:{Windows:"wip",BSD:"wip"},type:"builtin","arg-type":"with-prefix","lib-depends":["libxslt"],"ext-depends":["xml","dom"]},Oi={support:{BSD:"wip"},type:"external",source:"yac","arg-type-unix":"custom","ext-depends-unix":["igbinary"]},Ai={support:{BSD:"wip"},type:"external",source:"yaml","arg-type-unix":"with-prefix","arg-type-windows":"with","lib-depends":["libyaml"]},Vi={support:{BSD:"wip"},type:"builtin","arg-type":"with-prefix","arg-type-windows":"enable","lib-depends-unix":["libzip"],"ext-depends-windows":["zlib","bz2"],"lib-depends-windows":["libzip","zlib","bzip2","xz"]},Ti={type:"builtin","arg-type":"custom","arg-type-windows":"enable","lib-depends":["zlib"]},ji={support:{Windows:"wip",BSD:"wip"},type:"external",source:"ext-zstd","arg-type":"custom","lib-depends":["zstd"]},$i={amqp:ue,apcu:ce,bcmath:be,bz2:re,calendar:we,ctype:ge,curl:xe,dba:me,dom:he,ds:ye,enchant:ve,event:fe,exif:ze,ffi:Se,fileinfo:De,filter:ke,ftp:Be,gd:qe,gettext:_e,glfw:Ee,gmp:We,iconv:Ce,igbinary:Ie,imagick:Pe,imap:Ue,inotify:Le,intl:Ne,ldap:Oe,libxml:Ae,mbregex:Ve,mbstring:Te,mcrypt:je,memcache:$e,memcached:Xe,mongodb:Ge,mysqli:Me,mysqlnd:Re,oci8:He,opcache:Fe,openssl:Ze,parallel:Qe,"password-argon2":{support:{Windows:"wip",BSD:"wip"},notes:!0,type:"builtin","arg-type":"with-prefix","lib-depends":["libargon2"]},pcntl:Ke,pdo:Ye,pdo_mysql:Je,pdo_pgsql:ei,pdo_sqlite:ii,pdo_sqlsrv:si,pgsql:ti,phar:li,posix:ni,protobuf:oi,rar:ai,readline:di,redis:pi,session:ui,shmop:ci,simdjson:bi,simplexml:ri,snappy:wi,soap:gi,sockets:xi,sodium:mi,sqlite3:hi,sqlsrv:yi,ssh2:vi,swoole:fi,"swoole-hook-mysql":{support:{Windows:"no",BSD:"wip"},notes:!0,type:"addon","arg-type":"custom","ext-depends":["mysqlnd","pdo","pdo_mysql"],"ext-suggests":["mysqli"]},"swoole-hook-pgsql":{support:{Windows:"no",BSD:"wip",Darwin:"partial"},notes:!0,type:"addon","arg-type":"custom","ext-depends":["pgsql","pdo"]},"swoole-hook-sqlite":{support:{Windows:"no",BSD:"wip"},notes:!0,type:"addon","arg-type":"custom","ext-depends":["sqlite3","pdo"]},swow:zi,sysvmsg:Si,sysvsem:Di,sysvshm:ki,tidy:Bi,tokenizer:qi,uuid:_i,uv:Ei,xdebug:Wi,xhprof:Ci,xlswriter:Ii,xml:Pi,xmlreader:Ui,xmlwriter:Li,xsl:Ni,yac:Oi,yaml:Ai,zip:Vi,zlib:Ti,zstd:ji},Xi={source:"brotli","static-libs-unix":["libbrotlidec.a","libbrotlienc.a","libbrotlicommon.a"],"static-libs-windows":["brotlicommon.lib","brotlienc.lib","brotlidec.lib"],headers:["brotli"]},Gi={source:"bzip2","static-libs-unix":["libbz2.a"],"static-libs-windows":["libbz2.lib","libbz2_a.lib"],headers:["bzlib.h"]},Mi={source:"curl","static-libs-unix":["libcurl.a"],"static-libs-windows":["libcurl_a.lib"],headers:["curl"],"lib-depends-unix":["openssl","zlib"],"lib-depends-windows":["openssl","zlib","libssh2","nghttp2"],"lib-suggests-unix":["libssh2","brotli","nghttp2","zstd"],"lib-suggests-windows":["brotli","zstd"],frameworks:["CoreFoundation","CoreServices","SystemConfiguration"]},Ri={source:"freetype","static-libs-unix":["libfreetype.a"],"static-libs-windows":["libfreetype_a.lib"],"headers-unix":["freetype2/freetype/freetype.h","freetype2/ft2build.h"],"lib-depends":["zlib"],"lib-suggests":["libpng","bzip2","brotli"]},Hi={source:"gettext","static-libs-unix":["libintl.a"],"lib-depends":["libiconv"],"lib-suggests":["ncurses","libxml2"],frameworks:["CoreFoundation"]},Fi={source:"ext-glfw","static-libs-unix":["libglfw3.a"],frameworks:["CoreVideo","OpenGL","Cocoa","IOKit"]},Zi={source:"gmp","static-libs-unix":["libgmp.a"],"static-libs-windows":["libgmp.lib"],headers:["gmp.h"]},Qi={source:"icu","cpp-library":!0,"static-libs-unix":["libicui18n.a","libicuio.a","libicuuc.a","libicudata.a"]},Ki={source:"imagemagick","static-libs-unix":["libMagick++-7.Q16HDRI.a","libMagickWand-7.Q16HDRI.a","libMagickCore-7.Q16HDRI.a"],"lib-depends":["zlib","libpng","libjpeg","libwebp","freetype","libtiff"],"lib-suggests":["zstd","xz","bzip2","libzip","libxml2"]},Yi={source:"imap","static-libs-unix":["libc-client.a"],"lib-suggests":["openssl"]},Ji={source:"ldap","static-libs-unix":["liblber.a","libldap.a"],"lib-depends":["openssl","zlib","gmp","libsodium"]},es={source:"libargon2","static-libs-unix":["libargon2.a"]},is={source:"libavif","static-libs-unix":["libavif.a"],"static-libs-windows":["avif.lib"]},ss={source:"libcares","static-libs-unix":["libcares.a"],"headers-unix":["ares.h","ares_dns.h","ares_nameser.h","ares_rules.h"]},ts={source:"libevent","static-libs-unix":["libevent.a","libevent_core.a","libevent_extra.a","libevent_openssl.a"],"lib-depends":["openssl"]},ls={source:"libffi","static-libs-unix":["libffi.a"],"static-libs-windows":["libffi.lib"],"headers-unix":["ffi.h","ffitarget.h"],"headers-windows":["ffi.h","fficonfig.h","ffitarget.h"]},ns={source:"libiconv","static-libs-unix":["libiconv.a","libcharset.a"],headers:["iconv.h","libcharset.h","localcharset.h"]},os={source:"libjpeg","static-libs-unix":["libjpeg.a","libturbojpeg.a"],"static-libs-windows":["libjpeg_a.lib"],"lib-suggests-windows":["zlib"]},as={source:"liblz4","static-libs-unix":["liblz4.a"]},ds={source:"libmemcached","static-libs-unix":["libmemcached.a","libmemcachedutil.a"]},ps={source:"libpng","static-libs-unix":["libpng16.a"],"static-libs-windows":["libpng16_static.lib","libpng_a.lib"],"headers-unix":["png.h","pngconf.h","pnglibconf.h"],"headers-windows":["png.h","pngconf.h"],"lib-depends":["zlib"]},us={source:"librabbitmq","static-libs-unix":["librabbitmq.a"],"static-libs-windows":["rabbitmq.4.lib"],"lib-depends":["openssl"]},cs={source:"libsodium","static-libs-unix":["libsodium.a"]},bs={source:"libssh2","static-libs-unix":["libssh2.a"],"static-libs-windows":["libssh2.lib"],headers:["libssh2.h","libssh2_publickey.h","libssh2_sftp.h"],"lib-depends":["openssl"],"lib-suggests":["zlib"]},rs={source:"libtiff","static-libs-unix":["libtiff.a"]},ws={source:"libuuid","static-libs-unix":["libuuid.a"],headers:["uuid/uuid.h"]},gs={source:"libuv","static-libs-unix":["libuv.a"]},xs={source:"libwebp","static-libs-unix":["libwebp.a","libwebpdecoder.a","libwebpdemux.a","libwebpmux.a","libsharpyuv.a"],"static-libs-windows":["libwebp.lib","libwebpdecoder.lib","libwebpdemux.lib","libsharpyuv.lib"]},ms={source:"libxml2","static-libs-unix":["libxml2.a"],"static-libs-windows":["libxml2s.lib","libxml2_a.lib"],headers:["libxml2"],"lib-depends-unix":["libiconv"],"lib-suggests-unix":["xz","icu","zlib"],"lib-depends-windows":["libiconv-win"],"lib-suggests-windows":["zlib"]},hs={source:"libxslt","static-libs-unix":["libxslt.a","libexslt.a"],"lib-depends":["libxml2"]},ys={source:"libyaml","static-libs-unix":["libyaml.a"],"static-libs-windows":["yaml.lib"],headers:["yaml.h"]},vs={source:"libzip","static-libs-unix":["libzip.a"],"static-libs-windows":["zip.lib","libzip_a.lib"],headers:["zip.h","zipconf.h"],"lib-depends-unix":["zlib"],"lib-suggests-unix":["bzip2","xz","zstd","openssl"],"lib-depends-windows":["zlib","bzip2","xz"],"lib-suggests-windows":["zstd","openssl"]},fs={source:"ncurses","static-libs-unix":["libncurses.a"]},zs={source:"nghttp2","static-libs-unix":["libnghttp2.a"],"static-libs-windows":["nghttp2.lib"],headers:["nghttp2"],"lib-depends":["zlib","openssl"],"lib-suggests":["libxml2"]},Ss={source:"onig","static-libs-unix":["libonig.a"],"static-libs-windows":["onig.lib","onig_a.lib"],headers:["oniggnu.h","oniguruma.h"]},Ds={source:"openssl","static-libs-unix":["libssl.a","libcrypto.a"],"static-libs-windows":["libssl.lib","libcrypto.lib"],headers:["openssl"],"lib-depends":["zlib"]},ks={source:"postgresql","static-libs-unix":["libpq.a","libpgport.a","libpgcommon.a"],"lib-depends":["libiconv","libxml2","openssl","zlib","readline"],"lib-suggests":["icu","libxslt","ldap","zstd"]},Bs={source:"pthreads4w","static-libs-windows":["libpthreadVC3.lib"]},qs={source:"qdbm","static-libs-unix":["libqdbm.a"],"static-libs-windows":["qdbm_a.lib"],"headers-windows":["depot.h"]},_s={source:"readline","static-libs-unix":["libreadline.a"],"lib-depends":["ncurses"]},Es={source:"snappy","static-libs-unix":["libsnappy.a"],"headers-unix":["snappy.h","snappy-c.h","snappy-sinksource.h","snappy-stubs-public.h"],"lib-depends":["zlib"]},Ws={source:"sqlite","static-libs-unix":["libsqlite3.a"],"static-libs-windows":["libsqlite3_a.lib"],headers:["sqlite3.h","sqlite3ext.h"]},Cs={source:"tidy","static-libs-unix":["libtidy.a"]},Is={source:"unixodbc","static-libs-unix":["libodbc.a","libodbccr.a","libodbcinst.a"],"lib-depends":["libiconv"]},Ps={source:"xz","static-libs-unix":["liblzma.a"],"static-libs-windows":["liblzma.lib","liblzma_a.lib"],"headers-unix":["lzma"],"headers-windows":["lzma","lzma.h"],"lib-depends-unix":["libiconv"]},Us={source:"zlib","static-libs-unix":["libz.a"],"static-libs-windows":["zlib_a.lib"],headers:["zlib.h","zconf.h"]},Ls={source:"zstd","static-libs-unix":["libzstd.a"],"static-libs-windows":[["zstd.lib","zstd_static.lib"]],"headers-unix":["zdict.h","zstd.h","zstd_errors.h"],"headers-windows":["zstd.h","zstd_errors.h"]},Ns={brotli:Xi,bzip2:Gi,curl:Mi,freetype:Ri,gettext:Hi,glfw:Fi,gmp:Zi,icu:Qi,imagemagick:Ki,imap:Yi,ldap:Ji,libargon2:es,libavif:is,libcares:ss,libevent:ts,libffi:ls,"libffi-win":{source:"libffi-win","static-libs-windows":["libffi.lib"],"headers-windows":["ffi.h","ffitarget.h","fficonfig.h"]},libiconv:ns,"libiconv-win":{source:"libiconv-win","static-libs-windows":["libiconv.lib","libiconv_a.lib"]},libjpeg:os,liblz4:as,libmemcached:ds,libpng:ps,librabbitmq:us,libsodium:cs,libssh2:bs,libtiff:rs,libuuid:ws,libuv:gs,libwebp:xs,libxml2:ms,libxslt:hs,libyaml:ys,libzip:vs,ncurses:fs,nghttp2:zs,onig:Ss,openssl:Ds,"pkg-config":{source:"pkg-config"},postgresql:ks,pthreads4w:Bs,qdbm:qs,readline:_s,snappy:Es,sqlite:Ws,tidy:Cs,unixodbc:Is,xz:Ps,zlib:Us,zstd:Ls};function q(e,s,a,p){return e.os==="linux"?e[s][a][p+"-linux"]??e[s][a][p+"-unix"]??e[s][a][p]??[]:e.os==="macos"?e[s][a][p+"-macos"]??e[s][a][p+"-unix"]??e[s][a][p]??[]:e.os==="windows"?e[s][a][p+"-windows"]??e[s][a][p]??[]:[]}function Z(e,s){return q(e,"ext",s,"ext-depends")}function Os(e,s){return q(e,"ext",s,"ext-suggests")}function As(e,s){return q(e,"ext",s,"lib-depends")}function Vs(e,s){return q(e,"ext",s,"lib-suggests")}function Q(e,s){return q(e,"lib",s,"lib-depends")}function Ts(e,s){return q(e,"lib",s,"lib-suggests")}function js(e,s){const a=[],p=new Set,h=[];s.forEach(o=>{p.has(o)||Gs(e,o,p,a)});const f=[];return a.forEach(o=>{s.indexOf(o)===-1&&h.push(o),[...As(e,o),...Vs(e,o)].forEach(N=>{f.indexOf(N)===-1&&f.push(N)})}),{exts:a,libs:$s(e,f),notIncludedExts:h}}function $s(e,s){const a=[],p=new Set;return s.forEach(h=>{p.has(h)||(console.log("before visited"),console.log(p),Xs(e,h,p,a),console.log("after visited"),console.log(p))}),a}function Xs(e,s,a,p){if(a.has(s))return;a.add(s),[...Q(e,s),...Ts(e,s)].forEach(f=>{K(e,f,a,p)}),p.push(s)}function K(e,s,a,p){a.has(s)||(a.add(s),Q(e,s).forEach(h=>{K(e,h,a,p)}),p.push(s))}function Y(e,s,a,p){a.has(a)||(a.add(s),Z(e,s).forEach(h=>{Y(e,h,a,p)}),p.push(s))}function Gs(e,s,a,p){if(a.has(s))return;a.add(s),[...Z(e,s),...Os(e,s)].forEach(f=>{Y(e,f,a,p)}),p.push(s)}const _=e=>(ae("data-v-4cdb2891"),e=e(),de(),e),Ms={class:"option-line"},Rs=["id","value","disabled"],Hs=["for"],Fs={class:"box"},Zs={class:"ext-item"},Qs=["id","value","disabled"],Ks=["for"],Ys={class:"details custom-block"},Js={class:"box"},et={class:"ext-item"},it=["id","value","disabled"],st=["for"],tt={class:"tip custom-block"},lt=_(()=>i("p",{class:"custom-block-title"},"TIP",-1)),nt={class:"box"},ot={class:"ext-item"},at=["id","value"],dt=["for"],pt={key:1,class:"warning custom-block"},ut=_(()=>i("p",{class:"custom-block-title"},"WARNING",-1)),ct={key:2,class:"warning custom-block"},bt=_(()=>i("p",{class:"custom-block-title"},"WARNING",-1)),rt={class:"option-line"},wt={class:"option-title"},gt={value:"native"},xt={value:"spc"},mt={key:0,value:"docker"},ht={key:3,class:"option-line"},yt={class:"option-title"},vt=_(()=>i("option",{value:"x86_64"},"x86_64 (amd64)",-1)),ft={key:0,value:"aarch64"},zt={class:"option-line"},St={class:"option-title"},Dt=["value"],kt={class:"option-line"},Bt={class:"option-title"},qt={for:"debug-yes"},_t={for:"debug-no"},Et={class:"option-line"},Wt={class:"option-title"},Ct={for:"zts-yes"},It={for:"zts-no"},Pt={class:"option-line"},Ut={class:"option-title"},Lt={for:"show-download-yes"},Nt={for:"show-download-no"},Ot={key:4,class:"option-line"},At={class:"option-title"},Vt={for:"upx-yes"},Tt={for:"upx-no"},jt=["placeholder"],$t={key:5,class:"command-container"},Xt={key:0,class:"command-preview"},Gt=_(()=>i("br",null,null,-1)),Mt={key:1},Rt={class:"warning custom-block"},Ht=_(()=>i("p",{class:"custom-block-title"},"WARNING",-1)),Ft={key:6,class:"command-container"},Zt={class:"command-preview"},Qt={key:7,class:"command-container"},Kt={class:"command-preview"},Yt={key:8,class:"command-container"},Jt={class:"command-preview"},el={class:"command-container"},il={class:"command-preview"},sl={name:"CliGenerator"},tl=le({...sl,props:{lang:{type:String,default:"zh"}},setup(e){const s=g($i),a=g(Ns),p=g([]),h=[{os:"linux",label:"Linux",disabled:!1},{os:"macos",label:"macOS",disabled:!1},{os:"windows",label:"Windows",disabled:!1}],f=["7.4","8.0","8.1","8.2","8.3"],o={zh:{selectExt:"选择扩展",buildTarget:"选择编译目标",buildOptions:"编译选项",buildEnvironment:"编译环境",buildEnvNative:"本地构建(Git 源码)",buildEnvSpc:"本地构建(独立 spc 二进制)",buildEnvDocker:"Alpine Docker 构建",useDebug:"是否开启调试输出",yes:"是",no:"否",resultShow:"结果展示",selectCommon:"选择常用扩展",selectNone:"全部取消选择",useZTS:"是否编译线程安全版",hardcodedINI:"硬编码 INI 选项",hardcodedINIPlacehoder:"如需要硬编码 ini,每行写一个,例如:memory_limit=2G",resultShowDownload:"是否展示仅下载对应扩展依赖的命令",downloadExtOnlyCommand:"只下载对应扩展的依赖包命令",downloadAllCommand:"下载所有依赖包命令",downloadUPXCommand:"下载 UPX 命令",compileCommand:"编译命令",downloadPhpVersion:"下载 PHP 版本",downloadSPCBinaryCommand:"下载 spc 二进制命令",selectedArch:"选择系统架构",selectedSystem:"选择操作系统",buildLibs:"要构建的库",depTips:"选择扩展后,不可选中的项目为必需的依赖,编译的依赖库列表中可选的为现有扩展和依赖库的可选依赖。选择可选依赖后,将生成 --with-libs 参数。",microUnavailable:"micro 不支持 PHP 7.4 及更早版本!",windowsSAPIUnavailable:"Windows 目前不支持 fpm、embed 构建!",useUPX:"是否开启 UPX 压缩(减小二进制体积,但很少见的情况下 micro SAPI 无法使用)",windowsDownSPCWarning:"Windows 下请手动下载 spc.exe 二进制文件并解压到当前目录!"},en:{selectExt:"Select Extensions",buildTarget:"Build Target",buildOptions:"Build Options",buildEnvironment:"Build Environment",buildEnvNative:"Native build (Git source code)",buildEnvSpc:"Native build (standalone spc binary)",buildEnvDocker:"Alpine docker build",useDebug:"Enable debug message",yes:"Yes",no:"No",resultShow:"Result",selectCommon:"Select common extensions",selectNone:"Unselect all",useZTS:"Enable ZTS",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.",microUnavailable:"Micro does not support PHP 7.4 and earlier versions!",windowsSAPIUnavailable:"Windows does not support fpm and embed build!",useUPX:"Enable UPX compression (reduce binary size, but in rare cases micro SAPI doesn't work with UPX)",windowsDownSPCWarning:"Please download the spc.exe binary file manually and extract it to the current directory on Windows!"}},R=["cli","fpm","micro","embed","all"],N=()=>{y.value=["apcu","bcmath","calendar","ctype","curl","dba","dom","exif","filter","fileinfo","gd","iconv","intl","mbstring","mbregex","mysqli","mysqlnd","openssl","opcache","pcntl","pdo","pdo_mysql","pdo_sqlite","pdo_pgsql","pgsql","phar","posix","readline","redis","session","simplexml","sockets","sodium","sqlite3","tokenizer","xml","xmlreader","xmlwriter","xsl","zip","zlib"]},H=V(()=>y.value.join(",")),J=V(()=>{const m=D.value.filter(n=>O.value.indexOf(n)===-1);return m.length>0?' --with-libs="'+m.join(",")+'"':""}),y=g([]),D=g([]),T=g([]),O=g([]),v=g(["cli"]),E=g("native"),W=g("8.2"),k=g(0),C=g(0),I=g(1),B=g(0),j=g(""),c=g("linux"),$=g("x86_64"),A=V(()=>{switch(E.value){case"native":return"bin/spc";case"spc":return c.value==="windows"?".\\spc.exe":"./spc";case"docker":return"bin/spc-alpine-docker";default:return""}}),F=g("--build-cli"),ee=V(()=>{const m=j.value.split(` +`);let n=[];return m.forEach(t=>{t.indexOf("=")>=1&&n.push(t)})," "+n.map(t=>'-I "'+t+'"').join(" ")}),ie=m=>{let n;v.value.indexOf("all")!==-1&&m.target.value==="all"?v.value=["all"]:(n=v.value.indexOf("all"))!==-1&&m.target.value!=="all"&&v.value.splice(n,1),F.value=v.value.map(t=>"--build-"+t).join(" ")},se=m=>{const n=new Set,t=u=>{let d=[];if(c.value==="linux"){if(d=s.value[u]["ext-depends-linux"]??s.value[u]["ext-depends-unix"]??s.value[u]["ext-depends"]??[],d.length===0)return}else if(c.value==="macos"){if(d=s.value[u]["ext-depends-macos"]??s.value[u]["ext-depends-unix"]??s.value[u]["ext-depends"]??[],d.length===0)return}else if(c.value==="windows"&&(d=s.value[u]["ext-depends-windows"]??s.value[u]["ext-depends"]??[],d.length===0))return;d.forEach(w=>{n.add(w),t(w)})};return m.forEach(u=>{t(u)}),Array.from(n)},te=m=>{const n=new Set,t=d=>{let w=[];if(c.value==="linux"){if(w=a.value[d]["lib-depends-linux"]??a.value[d]["lib-depends-unix"]??a.value[d]["lib-depends"]??[],w.length===0)return}else if(c.value==="macos"){if(w=a.value[d]["lib-depends-macos"]??a.value[d]["lib-depends-unix"]??a.value[d]["lib-depends"]??[],w.length===0)return}else if(c.value==="windows"&&(w=a.value[d]["lib-depends-windows"]??a.value[d]["lib-depends"]??[],w.length===0))return;w.forEach(P=>{n.add(P),t(P)})},u=d=>{let w=[];if(c.value==="linux"){if(w=s.value[d]["lib-depends-linux"]??s.value[d]["lib-depends-unix"]??s.value[d]["lib-depends"]??[],w.length===0)return}else if(c.value==="macos"){if(w=s.value[d]["lib-depends-macos"]??s.value[d]["lib-depends-unix"]??s.value[d]["lib-depends"]??[],w.length===0)return}else if(c.value==="windows"&&(w=s.value[d]["lib-depends-windows"]??s.value[d]["lib-depends"]??[],w.length===0))return;w.forEach(P=>{n.add(P),t(P)})};return m.forEach(d=>{u(d)}),Array.from(n)};return X(c,()=>y.value=[]),X(c,()=>B.value=0),X(y,m=>{T.value=se(m),T.value.forEach(t=>{y.value.indexOf(t)===-1&&y.value.push(t)}),y.value.sort(),console.log("检测到变化!"),console.log(m);const n=js({ext:s.value,lib:a.value,os:c.value},y.value);p.value=n.libs.sort(),D.value=[],O.value=te(n.exts),O.value.forEach(t=>{D.value.indexOf(t)===-1&&D.value.push(t)})}),(m,n)=>(b(),r("div",null,[i("h2",null,l(o[e.lang].selectedSystem),1),i("div",Ms,[(b(),r(U,null,L(h,(t,u)=>i("span",{key:u,style:{"margin-right":"4px"}},[x(i("input",{type:"radio",id:"os-"+t.os,value:t.os,disabled:t.disabled===!0,"onUpdate:modelValue":n[0]||(n[0]=d=>c.value=d)},null,8,Rs),[[S,c.value]]),i("label",{for:"os-"+t.os},l(t.label),9,Hs)])),64))]),i("h2",null,l(o[e.lang].selectExt)+l(y.value.length>0?" ("+y.value.length+")":""),1),i("div",Fs,[(b(!0),r(U,null,L(s.value,(t,u)=>(b(),r("div",Zs,[x(i("input",{type:"checkbox",id:u,value:u,"onUpdate:modelValue":n[1]||(n[1]=d=>y.value=d),disabled:T.value.indexOf(u)!==-1},null,8,Qs),[[M,y.value]]),i("label",{for:u},l(u),9,Ks)]))),256))]),c.value!=="windows"?(b(),r("div",{key:0,class:"my-btn",onClick:N},l(o[e.lang].selectCommon),1)):z("",!0),i("div",{class:"my-btn",onClick:n[2]||(n[2]=t=>y.value=[])},l(o[e.lang].selectNone),1),i("details",Ys,[i("summary",null,l(o[e.lang].buildLibs)+l(D.value.length>0?" ("+D.value.length+")":""),1),i("div",Js,[(b(!0),r(U,null,L(p.value,(t,u)=>(b(),r("div",et,[x(i("input",{type:"checkbox",id:u,value:t,"onUpdate:modelValue":n[3]||(n[3]=d=>D.value=d),disabled:O.value.indexOf(t)!==-1},null,8,it),[[M,D.value]]),i("label",{for:u},l(t),9,st)]))),256))])]),i("div",tt,[lt,i("p",null,l(o[e.lang].depTips),1)]),i("h2",null,l(o[e.lang].buildTarget),1),i("div",nt,[(b(),r(U,null,L(R,t=>i("div",ot,[x(i("input",{type:"checkbox",id:"build_"+t,value:t,"onUpdate:modelValue":n[4]||(n[4]=u=>v.value=u),onChange:ie},null,40,at),[[M,v.value]]),i("label",{for:"build_"+t},l(t),9,dt)])),64))]),W.value==="7.4"&&(v.value.indexOf("micro")!==-1||v.value.indexOf("all")!==-1)?(b(),r("div",pt,[ut,i("p",null,l(o[e.lang].microUnavailable),1)])):z("",!0),c.value==="windows"&&(v.value.indexOf("fpm")!==-1||v.value.indexOf("embed")!==-1)?(b(),r("div",ct,[bt,i("p",null,l(o[e.lang].windowsSAPIUnavailable),1)])):z("",!0),i("h2",null,l(o[e.lang].buildOptions),1),i("div",rt,[i("span",wt,l(o[e.lang].buildEnvironment),1),x(i("select",{"onUpdate:modelValue":n[5]||(n[5]=t=>E.value=t)},[i("option",gt,l(o[e.lang].buildEnvNative),1),i("option",xt,l(o[e.lang].buildEnvSpc),1),c.value!=="windows"?(b(),r("option",mt,l(o[e.lang].buildEnvDocker),1)):z("",!0)],512),[[G,E.value]])]),E.value==="spc"?(b(),r("div",ht,[i("span",yt,l(o[e.lang].selectedArch),1),x(i("select",{"onUpdate:modelValue":n[6]||(n[6]=t=>$.value=t)},[vt,c.value!=="windows"?(b(),r("option",ft,"aarch64 (arm64)")):z("",!0)],512),[[G,$.value]])])):z("",!0),i("div",zt,[i("span",St,l(o[e.lang].downloadPhpVersion),1),x(i("select",{"onUpdate:modelValue":n[7]||(n[7]=t=>W.value=t)},[(b(),r(U,null,L(f,t=>i("option",{value:t},l(t),9,Dt)),64))],512),[[G,W.value]])]),i("div",kt,[i("span",Bt,l(o[e.lang].useDebug),1),x(i("input",{type:"radio",id:"debug-yes",value:1,"onUpdate:modelValue":n[8]||(n[8]=t=>k.value=t)},null,512),[[S,k.value]]),i("label",qt,l(o[e.lang].yes),1),x(i("input",{type:"radio",id:"debug-no",value:0,"onUpdate:modelValue":n[9]||(n[9]=t=>k.value=t)},null,512),[[S,k.value]]),i("label",_t,l(o[e.lang].no),1)]),i("div",Et,[i("span",Wt,l(o[e.lang].useZTS),1),x(i("input",{type:"radio",id:"zts-yes",value:1,"onUpdate:modelValue":n[10]||(n[10]=t=>C.value=t)},null,512),[[S,C.value]]),i("label",Ct,l(o[e.lang].yes),1),x(i("input",{type:"radio",id:"zts-no",value:0,"onUpdate:modelValue":n[11]||(n[11]=t=>C.value=t)},null,512),[[S,C.value]]),i("label",It,l(o[e.lang].no),1)]),i("div",Pt,[i("span",Ut,l(o[e.lang].resultShowDownload),1),x(i("input",{type:"radio",id:"show-download-yes",value:1,"onUpdate:modelValue":n[12]||(n[12]=t=>I.value=t)},null,512),[[S,I.value]]),i("label",Lt,l(o[e.lang].yes),1),x(i("input",{type:"radio",id:"show-download-no",value:0,"onUpdate:modelValue":n[13]||(n[13]=t=>I.value=t)},null,512),[[S,I.value]]),i("label",Nt,l(o[e.lang].no),1)]),c.value!=="macos"?(b(),r("div",Ot,[i("span",At,l(o[e.lang].useUPX),1),x(i("input",{type:"radio",id:"upx-yes",value:1,"onUpdate:modelValue":n[14]||(n[14]=t=>B.value=t)},null,512),[[S,B.value]]),i("label",Vt,l(o[e.lang].yes),1),x(i("input",{type:"radio",id:"upx-no",value:0,"onUpdate:modelValue":n[15]||(n[15]=t=>B.value=t)},null,512),[[S,B.value]]),i("label",Tt,l(o[e.lang].no),1)])):z("",!0),i("h2",null,l(o[e.lang].hardcodedINI),1),x(i("textarea",{class:"textarea",placeholder:o[e.lang].hardcodedINIPlacehoder,"onUpdate:modelValue":n[16]||(n[16]=t=>j.value=t),rows:"5"},null,8,jt),[[ne,j.value]]),i("h2",null,l(o[e.lang].resultShow),1),E.value==="spc"?(b(),r("div",$t,[i("b",null,l(o[e.lang].downloadSPCBinaryCommand),1),c.value!=="windows"?(b(),r("div",Xt,[oe(" curl -o spc.tgz https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-"+l(c.value)+"-"+l($.value)+".tar.gz && tar -zxvf spc.tgz && rm spc.tgz",1),Gt])):(b(),r("div",Mt,[i("div",Rt,[Ht,i("p",null,l(o[e.lang].windowsDownSPCWarning),1)])]))])):z("",!0),I.value?(b(),r("div",Ft,[i("b",null,l(o[e.lang].downloadExtOnlyCommand),1),i("div",Zt,l(A.value)+" download --with-php="+l(W.value)+' --for-extensions "'+l(H.value)+'"'+l(k.value?" --debug":""),1)])):(b(),r("div",Qt,[i("b",null,l(o[e.lang].downloadAllCommand),1),i("div",Kt,l(A.value)+" download --all --with-php="+l(W.value)+l(k.value?" --debug":""),1)])),B.value?(b(),r("div",Yt,[i("b",null,l(o[e.lang].downloadUPXCommand),1),i("div",Jt,l(A.value)+" install-pkg upx"+l(k.value?" --debug":""),1)])):z("",!0),i("div",el,[i("b",null,l(o[e.lang].compileCommand),1),i("div",il,l(A.value)+" build "+l(F.value)+' "'+l(H.value)+'"'+l(J.value)+l(k.value?" --debug":"")+l(C.value?" --enable-zts":"")+l(B.value?" --with-upx-pack":"")+l(ee.value),1)])]))}}),nl=pe(tl,[["__scopeId","data-v-4cdb2891"]]);export{nl as C}; diff --git a/assets/chunks/framework.CszIUXhs.js b/assets/chunks/framework.CszIUXhs.js new file mode 100644 index 00000000..e527e8d8 --- /dev/null +++ b/assets/chunks/framework.CszIUXhs.js @@ -0,0 +1,17 @@ +/** +* @vue/shared v3.4.31 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**//*! #__NO_SIDE_EFFECTS__ */function bs(e,t){const n=new Set(e.split(","));return s=>n.has(s)}const te={},bt=[],Se=()=>{},xo=()=>!1,Wt=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),vs=e=>e.startsWith("onUpdate:"),ie=Object.assign,ws=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},To=Object.prototype.hasOwnProperty,Y=(e,t)=>To.call(e,t),$=Array.isArray,vt=e=>qt(e)==="[object Map]",Ot=e=>qt(e)==="[object Set]",qs=e=>qt(e)==="[object Date]",K=e=>typeof e=="function",re=e=>typeof e=="string",Fe=e=>typeof e=="symbol",Z=e=>e!==null&&typeof e=="object",Wr=e=>(Z(e)||K(e))&&K(e.then)&&K(e.catch),qr=Object.prototype.toString,qt=e=>qr.call(e),Ao=e=>qt(e).slice(8,-1),Gr=e=>qt(e)==="[object Object]",Es=e=>re(e)&&e!=="NaN"&&e[0]!=="-"&&""+parseInt(e,10)===e,wt=bs(",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted"),Tn=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Oo=/-(\w)/g,$e=Tn(e=>e.replace(Oo,(t,n)=>n?n.toUpperCase():"")),Ro=/\B([A-Z])/g,gt=Tn(e=>e.replace(Ro,"-$1").toLowerCase()),An=Tn(e=>e.charAt(0).toUpperCase()+e.slice(1)),fn=Tn(e=>e?`on${An(e)}`:""),et=(e,t)=>!Object.is(e,t),un=(e,...t)=>{for(let n=0;n{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:s,value:n})},mn=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Io=e=>{const t=re(e)?Number(e):NaN;return isNaN(t)?e:t};let Gs;const Xr=()=>Gs||(Gs=typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:typeof global<"u"?global:{});function Cs(e){if($(e)){const t={};for(let n=0;n{if(n){const s=n.split(Mo);s.length>1&&(t[s[0].trim()]=s[1].trim())}}),t}function Ss(e){let t="";if(re(e))t=e;else if($(e))for(let n=0;npt(n,t))}const Jr=e=>!!(e&&e.__v_isRef===!0),jo=e=>re(e)?e:e==null?"":$(e)||Z(e)&&(e.toString===qr||!K(e.toString))?Jr(e)?jo(e.value):JSON.stringify(e,Qr,2):String(e),Qr=(e,t)=>Jr(t)?Qr(e,t.value):vt(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[s,r],i)=>(n[Kn(s,i)+" =>"]=r,n),{})}:Ot(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>Kn(n))}:Fe(t)?Kn(t):Z(t)&&!$(t)&&!Gr(t)?String(t):t,Kn=(e,t="")=>{var n;return Fe(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};/** +* @vue/reactivity v3.4.31 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/let we;class Vo{constructor(t=!1){this.detached=t,this._active=!0,this.effects=[],this.cleanups=[],this.parent=we,!t&&we&&(this.index=(we.scopes||(we.scopes=[])).push(this)-1)}get active(){return this._active}run(t){if(this._active){const n=we;try{return we=this,t()}finally{we=n}}}on(){we=this}off(){we=this.parent}stop(t){if(this._active){let n,s;for(n=0,s=this.effects.length;n=4))break}this._dirtyLevel===1&&(this._dirtyLevel=0),rt()}return this._dirtyLevel>=4}set dirty(t){this._dirtyLevel=t?4:0}run(){if(this._dirtyLevel=0,!this.active)return this.fn();let t=Je,n=ut;try{return Je=!0,ut=this,this._runnings++,zs(this),this.fn()}finally{Xs(this),this._runnings--,ut=n,Je=t}}stop(){this.active&&(zs(this),Xs(this),this.onStop&&this.onStop(),this.active=!1)}}function ko(e){return e.value}function zs(e){e._trackId++,e._depsLength=0}function Xs(e){if(e.deps.length>e._depsLength){for(let t=e._depsLength;t{const n=new Map;return n.cleanup=e,n.computed=t,n},yn=new WeakMap,dt=Symbol(""),fs=Symbol("");function be(e,t,n){if(Je&&ut){let s=yn.get(e);s||yn.set(e,s=new Map);let r=s.get(n);r||s.set(n,r=ri(()=>s.delete(n))),ni(ut,r)}}function Ue(e,t,n,s,r,i){const o=yn.get(e);if(!o)return;let l=[];if(t==="clear")l=[...o.values()];else if(n==="length"&&$(e)){const c=Number(s);o.forEach((f,d)=>{(d==="length"||!Fe(d)&&d>=c)&&l.push(f)})}else switch(n!==void 0&&l.push(o.get(n)),t){case"add":$(e)?Es(n)&&l.push(o.get("length")):(l.push(o.get(dt)),vt(e)&&l.push(o.get(fs)));break;case"delete":$(e)||(l.push(o.get(dt)),vt(e)&&l.push(o.get(fs)));break;case"set":vt(e)&&l.push(o.get(dt));break}As();for(const c of l)c&&si(c,4);Os()}function Bo(e,t){const n=yn.get(e);return n&&n.get(t)}const Ko=bs("__proto__,__v_isRef,__isVue"),ii=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!=="arguments"&&e!=="caller").map(e=>Symbol[e]).filter(Fe)),Ys=Wo();function Wo(){const e={};return["includes","indexOf","lastIndexOf"].forEach(t=>{e[t]=function(...n){const s=J(this);for(let i=0,o=this.length;i{e[t]=function(...n){st(),As();const s=J(this)[t].apply(this,n);return Os(),rt(),s}}),e}function qo(e){Fe(e)||(e=String(e));const t=J(this);return be(t,"has",e),t.hasOwnProperty(e)}class oi{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,s){const r=this._isReadonly,i=this._isShallow;if(n==="__v_isReactive")return!r;if(n==="__v_isReadonly")return r;if(n==="__v_isShallow")return i;if(n==="__v_raw")return s===(r?i?il:fi:i?ai:ci).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(s)?t:void 0;const o=$(t);if(!r){if(o&&Y(Ys,n))return Reflect.get(Ys,n,s);if(n==="hasOwnProperty")return qo}const l=Reflect.get(t,n,s);return(Fe(n)?ii.has(n):Ko(n))||(r||be(t,"get",n),i)?l:pe(l)?o&&Es(n)?l:l.value:Z(l)?r?In(l):Rn(l):l}}class li extends oi{constructor(t=!1){super(!1,t)}set(t,n,s,r){let i=t[n];if(!this._isShallow){const c=Vt(i);if(!_n(s)&&!Vt(s)&&(i=J(i),s=J(s)),!$(t)&&pe(i)&&!pe(s))return c?!1:(i.value=s,!0)}const o=$(t)&&Es(n)?Number(n)e,On=e=>Reflect.getPrototypeOf(e);function Jt(e,t,n=!1,s=!1){e=e.__v_raw;const r=J(e),i=J(t);n||(et(t,i)&&be(r,"get",t),be(r,"get",i));const{has:o}=On(r),l=s?Rs:n?Ms:Dt;if(o.call(r,t))return l(e.get(t));if(o.call(r,i))return l(e.get(i));e!==r&&e.get(t)}function Qt(e,t=!1){const n=this.__v_raw,s=J(n),r=J(e);return t||(et(e,r)&&be(s,"has",e),be(s,"has",r)),e===r?n.has(e):n.has(e)||n.has(r)}function Zt(e,t=!1){return e=e.__v_raw,!t&&be(J(e),"iterate",dt),Reflect.get(e,"size",e)}function Js(e){e=J(e);const t=J(this);return On(t).has.call(t,e)||(t.add(e),Ue(t,"add",e,e)),this}function Qs(e,t){t=J(t);const n=J(this),{has:s,get:r}=On(n);let i=s.call(n,e);i||(e=J(e),i=s.call(n,e));const o=r.call(n,e);return n.set(e,t),i?et(t,o)&&Ue(n,"set",e,t):Ue(n,"add",e,t),this}function Zs(e){const t=J(this),{has:n,get:s}=On(t);let r=n.call(t,e);r||(e=J(e),r=n.call(t,e)),s&&s.call(t,e);const i=t.delete(e);return r&&Ue(t,"delete",e,void 0),i}function er(){const e=J(this),t=e.size!==0,n=e.clear();return t&&Ue(e,"clear",void 0,void 0),n}function en(e,t){return function(s,r){const i=this,o=i.__v_raw,l=J(o),c=t?Rs:e?Ms:Dt;return!e&&be(l,"iterate",dt),o.forEach((f,d)=>s.call(r,c(f),c(d),i))}}function tn(e,t,n){return function(...s){const r=this.__v_raw,i=J(r),o=vt(i),l=e==="entries"||e===Symbol.iterator&&o,c=e==="keys"&&o,f=r[e](...s),d=n?Rs:t?Ms:Dt;return!t&&be(i,"iterate",c?fs:dt),{next(){const{value:h,done:b}=f.next();return b?{value:h,done:b}:{value:l?[d(h[0]),d(h[1])]:d(h),done:b}},[Symbol.iterator](){return this}}}}function Ke(e){return function(...t){return e==="delete"?!1:e==="clear"?void 0:this}}function Jo(){const e={get(i){return Jt(this,i)},get size(){return Zt(this)},has:Qt,add:Js,set:Qs,delete:Zs,clear:er,forEach:en(!1,!1)},t={get(i){return Jt(this,i,!1,!0)},get size(){return Zt(this)},has:Qt,add:Js,set:Qs,delete:Zs,clear:er,forEach:en(!1,!0)},n={get(i){return Jt(this,i,!0)},get size(){return Zt(this,!0)},has(i){return Qt.call(this,i,!0)},add:Ke("add"),set:Ke("set"),delete:Ke("delete"),clear:Ke("clear"),forEach:en(!0,!1)},s={get(i){return Jt(this,i,!0,!0)},get size(){return Zt(this,!0)},has(i){return Qt.call(this,i,!0)},add:Ke("add"),set:Ke("set"),delete:Ke("delete"),clear:Ke("clear"),forEach:en(!0,!0)};return["keys","values","entries",Symbol.iterator].forEach(i=>{e[i]=tn(i,!1,!1),n[i]=tn(i,!0,!1),t[i]=tn(i,!1,!0),s[i]=tn(i,!0,!0)}),[e,n,t,s]}const[Qo,Zo,el,tl]=Jo();function Is(e,t){const n=t?e?tl:el:e?Zo:Qo;return(s,r,i)=>r==="__v_isReactive"?!e:r==="__v_isReadonly"?e:r==="__v_raw"?s:Reflect.get(Y(n,r)&&r in s?n:s,r,i)}const nl={get:Is(!1,!1)},sl={get:Is(!1,!0)},rl={get:Is(!0,!1)};const ci=new WeakMap,ai=new WeakMap,fi=new WeakMap,il=new WeakMap;function ol(e){switch(e){case"Object":case"Array":return 1;case"Map":case"Set":case"WeakMap":case"WeakSet":return 2;default:return 0}}function ll(e){return e.__v_skip||!Object.isExtensible(e)?0:ol(Ao(e))}function Rn(e){return Vt(e)?e:Ls(e,!1,zo,nl,ci)}function cl(e){return Ls(e,!1,Yo,sl,ai)}function In(e){return Ls(e,!0,Xo,rl,fi)}function Ls(e,t,n,s,r){if(!Z(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const i=r.get(e);if(i)return i;const o=ll(e);if(o===0)return e;const l=new Proxy(e,o===2?s:n);return r.set(e,l),l}function Pt(e){return Vt(e)?Pt(e.__v_raw):!!(e&&e.__v_isReactive)}function Vt(e){return!!(e&&e.__v_isReadonly)}function _n(e){return!!(e&&e.__v_isShallow)}function ui(e){return e?!!e.__v_raw:!1}function J(e){const t=e&&e.__v_raw;return t?J(t):e}function dn(e){return Object.isExtensible(e)&&zr(e,"__v_skip",!0),e}const Dt=e=>Z(e)?Rn(e):e,Ms=e=>Z(e)?In(e):e;class di{constructor(t,n,s,r){this.getter=t,this._setter=n,this.dep=void 0,this.__v_isRef=!0,this.__v_isReadonly=!1,this.effect=new Ts(()=>t(this._value),()=>Nt(this,this.effect._dirtyLevel===2?2:3)),this.effect.computed=this,this.effect.active=this._cacheable=!r,this.__v_isReadonly=s}get value(){const t=J(this);return(!t._cacheable||t.effect.dirty)&&et(t._value,t._value=t.effect.run())&&Nt(t,4),Ps(t),t.effect._dirtyLevel>=2&&Nt(t,2),t._value}set value(t){this._setter(t)}get _dirty(){return this.effect.dirty}set _dirty(t){this.effect.dirty=t}}function al(e,t,n=!1){let s,r;const i=K(e);return i?(s=e,r=Se):(s=e.get,r=e.set),new di(s,r,i||!r,n)}function Ps(e){var t;Je&&ut&&(e=J(e),ni(ut,(t=e.dep)!=null?t:e.dep=ri(()=>e.dep=void 0,e instanceof di?e:void 0)))}function Nt(e,t=4,n,s){e=J(e);const r=e.dep;r&&si(r,t)}function pe(e){return!!(e&&e.__v_isRef===!0)}function fe(e){return pi(e,!1)}function hi(e){return pi(e,!0)}function pi(e,t){return pe(e)?e:new fl(e,t)}class fl{constructor(t,n){this.__v_isShallow=n,this.dep=void 0,this.__v_isRef=!0,this._rawValue=n?t:J(t),this._value=n?t:Dt(t)}get value(){return Ps(this),this._value}set value(t){const n=this.__v_isShallow||_n(t)||Vt(t);t=n?t:J(t),et(t,this._rawValue)&&(this._rawValue,this._rawValue=t,this._value=n?t:Dt(t),Nt(this,4))}}function gi(e){return pe(e)?e.value:e}const ul={get:(e,t,n)=>gi(Reflect.get(e,t,n)),set:(e,t,n,s)=>{const r=e[t];return pe(r)&&!pe(n)?(r.value=n,!0):Reflect.set(e,t,n,s)}};function mi(e){return Pt(e)?e:new Proxy(e,ul)}class dl{constructor(t){this.dep=void 0,this.__v_isRef=!0;const{get:n,set:s}=t(()=>Ps(this),()=>Nt(this));this._get=n,this._set=s}get value(){return this._get()}set value(t){this._set(t)}}function hl(e){return new dl(e)}class pl{constructor(t,n,s){this._object=t,this._key=n,this._defaultValue=s,this.__v_isRef=!0}get value(){const t=this._object[this._key];return t===void 0?this._defaultValue:t}set value(t){this._object[this._key]=t}get dep(){return Bo(J(this._object),this._key)}}class gl{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0}get value(){return this._getter()}}function ml(e,t,n){return pe(e)?e:K(e)?new gl(e):Z(e)&&arguments.length>1?yl(e,t,n):fe(e)}function yl(e,t,n){const s=e[t];return pe(s)?s:new pl(e,t,n)}/** +* @vue/runtime-core v3.4.31 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/function Qe(e,t,n,s){try{return s?e(...s):e()}catch(r){Ln(r,t,n)}}function xe(e,t,n,s){if(K(e)){const r=Qe(e,t,n,s);return r&&Wr(r)&&r.catch(i=>{Ln(i,t,n)}),r}if($(e)){const r=[];for(let i=0;i>>1,r=he[s],i=kt(r);iPe&&he.splice(t,1)}function wl(e){$(e)?Et.push(...e):(!Ge||!Ge.includes(e,e.allowRecurse?at+1:at))&&Et.push(e),_i()}function tr(e,t,n=Ut?Pe+1:0){for(;nkt(n)-kt(s));if(Et.length=0,Ge){Ge.push(...t);return}for(Ge=t,at=0;ate.id==null?1/0:e.id,El=(e,t)=>{const n=kt(e)-kt(t);if(n===0){if(e.pre&&!t.pre)return-1;if(t.pre&&!e.pre)return 1}return n};function bi(e){us=!1,Ut=!0,he.sort(El);try{for(Pe=0;Pere(T)?T.trim():T)),h&&(r=n.map(mn))}let l,c=s[l=fn(t)]||s[l=fn($e(t))];!c&&i&&(c=s[l=fn(gt(t))]),c&&xe(c,e,6,r);const f=s[l+"Once"];if(f){if(!e.emitted)e.emitted={};else if(e.emitted[l])return;e.emitted[l]=!0,xe(f,e,6,r)}}function vi(e,t,n=!1){const s=t.emitsCache,r=s.get(e);if(r!==void 0)return r;const i=e.emits;let o={},l=!1;if(!K(e)){const c=f=>{const d=vi(f,t,!0);d&&(l=!0,ie(o,d))};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}return!i&&!l?(Z(e)&&s.set(e,null),null):($(i)?i.forEach(c=>o[c]=null):ie(o,i),Z(e)&&s.set(e,o),o)}function Mn(e,t){return!e||!Wt(t)?!1:(t=t.slice(2).replace(/Once$/,""),Y(e,t[0].toLowerCase()+t.slice(1))||Y(e,gt(t))||Y(e,t))}let ce=null,Pn=null;function vn(e){const t=ce;return ce=e,Pn=e&&e.type.__scopeId||null,t}function ef(e){Pn=e}function tf(){Pn=null}function Sl(e,t=ce,n){if(!t||e._n)return e;const s=(...r)=>{s._d&&gr(-1);const i=vn(t);let o;try{o=e(...r)}finally{vn(i),s._d&&gr(1)}return o};return s._n=!0,s._c=!0,s._d=!0,s}function Wn(e){const{type:t,vnode:n,proxy:s,withProxy:r,propsOptions:[i],slots:o,attrs:l,emit:c,render:f,renderCache:d,props:h,data:b,setupState:T,ctx:M,inheritAttrs:P}=e,k=vn(e);let G,z;try{if(n.shapeFlag&4){const m=r||s,L=m;G=Oe(f.call(L,m,d,h,T,b,M)),z=l}else{const m=t;G=Oe(m.length>1?m(h,{attrs:l,slots:o,emit:c}):m(h,null)),z=t.props?l:xl(l)}}catch(m){jt.length=0,Ln(m,e,1),G=ue(me)}let g=G;if(z&&P!==!1){const m=Object.keys(z),{shapeFlag:L}=g;m.length&&L&7&&(i&&m.some(vs)&&(z=Tl(z,i)),g=tt(g,z,!1,!0))}return n.dirs&&(g=tt(g,null,!1,!0),g.dirs=g.dirs?g.dirs.concat(n.dirs):n.dirs),n.transition&&(g.transition=n.transition),G=g,vn(k),G}const xl=e=>{let t;for(const n in e)(n==="class"||n==="style"||Wt(n))&&((t||(t={}))[n]=e[n]);return t},Tl=(e,t)=>{const n={};for(const s in e)(!vs(s)||!(s.slice(9)in t))&&(n[s]=e[s]);return n};function Al(e,t,n){const{props:s,children:r,component:i}=e,{props:o,children:l,patchFlag:c}=t,f=i.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&c>=0){if(c&1024)return!0;if(c&16)return s?nr(s,o,f):!!o;if(c&8){const d=t.dynamicProps;for(let h=0;he.__isSuspense;function Si(e,t){t&&t.pendingBranch?$(e)?t.effects.push(...e):t.effects.push(e):wl(e)}function Nn(e,t,n=ae,s=!1){if(n){const r=n[e]||(n[e]=[]),i=t.__weh||(t.__weh=(...o)=>{st();const l=zt(n),c=xe(t,n,e,o);return l(),rt(),c});return s?r.unshift(i):r.push(i),i}}const Be=e=>(t,n=ae)=>{(!Vn||e==="sp")&&Nn(e,(...s)=>t(...s),n)},Il=Be("bm"),Rt=Be("m"),Ll=Be("bu"),Ml=Be("u"),xi=Be("bum"),Fn=Be("um"),Pl=Be("sp"),Nl=Be("rtg"),Fl=Be("rtc");function $l(e,t=ae){Nn("ec",e,t)}function rf(e,t){if(ce===null)return e;const n=Dn(ce),s=e.dirs||(e.dirs=[]);for(let r=0;rt(o,l,void 0,i));else{const o=Object.keys(e);r=new Array(o.length);for(let l=0,c=o.length;l!!e.type.__asyncLoader;function lf(e,t,n={},s,r){if(ce.isCE||ce.parent&&Ct(ce.parent)&&ce.parent.isCE)return t!=="default"&&(n.name=t),ue("slot",n,s&&s());let i=e[t];i&&i._c&&(i._d=!1),zi();const o=i&&Ai(i(n)),l=Yi(_e,{key:n.key||o&&o.key||`_${t}`},o||(s?s():[]),o&&e._===1?64:-2);return!r&&l.scopeId&&(l.slotScopeIds=[l.scopeId+"-s"]),i&&i._c&&(i._d=!0),l}function Ai(e){return e.some(t=>Sn(t)?!(t.type===me||t.type===_e&&!Ai(t.children)):!0)?e:null}function cf(e,t){const n={};for(const s in e)n[/[A-Z]/.test(s)?`on:${s}`:fn(s)]=e[s];return n}const ds=e=>e?eo(e)?Dn(e):ds(e.parent):null,Ft=ie(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>ds(e.parent),$root:e=>ds(e.root),$emit:e=>e.emit,$options:e=>$s(e),$forceUpdate:e=>e.f||(e.f=()=>{e.effect.dirty=!0,Fs(e.update)}),$nextTick:e=>e.n||(e.n=Gt.bind(e.proxy)),$watch:e=>oc.bind(e)}),qn=(e,t)=>e!==te&&!e.__isScriptSetup&&Y(e,t),Hl={get({_:e},t){if(t==="__v_skip")return!0;const{ctx:n,setupState:s,data:r,props:i,accessCache:o,type:l,appContext:c}=e;let f;if(t[0]!=="$"){const T=o[t];if(T!==void 0)switch(T){case 1:return s[t];case 2:return r[t];case 4:return n[t];case 3:return i[t]}else{if(qn(s,t))return o[t]=1,s[t];if(r!==te&&Y(r,t))return o[t]=2,r[t];if((f=e.propsOptions[0])&&Y(f,t))return o[t]=3,i[t];if(n!==te&&Y(n,t))return o[t]=4,n[t];hs&&(o[t]=0)}}const d=Ft[t];let h,b;if(d)return t==="$attrs"&&be(e.attrs,"get",""),d(e);if((h=l.__cssModules)&&(h=h[t]))return h;if(n!==te&&Y(n,t))return o[t]=4,n[t];if(b=c.config.globalProperties,Y(b,t))return b[t]},set({_:e},t,n){const{data:s,setupState:r,ctx:i}=e;return qn(r,t)?(r[t]=n,!0):s!==te&&Y(s,t)?(s[t]=n,!0):Y(e.props,t)||t[0]==="$"&&t.slice(1)in e?!1:(i[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:s,appContext:r,propsOptions:i}},o){let l;return!!n[o]||e!==te&&Y(e,o)||qn(t,o)||(l=i[0])&&Y(l,o)||Y(s,o)||Y(Ft,o)||Y(r.config.globalProperties,o)},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Y(n,"value")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function af(){return jl().slots}function jl(){const e=jn();return e.setupContext||(e.setupContext=no(e))}function rr(e){return $(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let hs=!0;function Vl(e){const t=$s(e),n=e.proxy,s=e.ctx;hs=!1,t.beforeCreate&&ir(t.beforeCreate,e,"bc");const{data:r,computed:i,methods:o,watch:l,provide:c,inject:f,created:d,beforeMount:h,mounted:b,beforeUpdate:T,updated:M,activated:P,deactivated:k,beforeDestroy:G,beforeUnmount:z,destroyed:g,unmounted:m,render:L,renderTracked:O,renderTriggered:U,errorCaptured:D,serverPrefetch:I,expose:w,inheritAttrs:N,components:S,directives:W,filters:ne}=t;if(f&&Dl(f,s,null),o)for(const X in o){const H=o[X];K(H)&&(s[X]=H.bind(n))}if(r){const X=r.call(n,n);Z(X)&&(e.data=Rn(X))}if(hs=!0,i)for(const X in i){const H=i[X],He=K(H)?H.bind(n,n):K(H.get)?H.get.bind(n,n):Se,Xt=!K(H)&&K(H.set)?H.set.bind(n):Se,it=se({get:He,set:Xt});Object.defineProperty(s,X,{enumerable:!0,configurable:!0,get:()=>it.value,set:Ie=>it.value=Ie})}if(l)for(const X in l)Oi(l[X],s,n,X);if(c){const X=K(c)?c.call(n):c;Reflect.ownKeys(X).forEach(H=>{ql(H,X[H])})}d&&ir(d,e,"c");function j(X,H){$(H)?H.forEach(He=>X(He.bind(n))):H&&X(H.bind(n))}if(j(Il,h),j(Rt,b),j(Ll,T),j(Ml,M),j(lc,P),j(cc,k),j($l,D),j(Fl,O),j(Nl,U),j(xi,z),j(Fn,m),j(Pl,I),$(w))if(w.length){const X=e.exposed||(e.exposed={});w.forEach(H=>{Object.defineProperty(X,H,{get:()=>n[H],set:He=>n[H]=He})})}else e.exposed||(e.exposed={});L&&e.render===Se&&(e.render=L),N!=null&&(e.inheritAttrs=N),S&&(e.components=S),W&&(e.directives=W)}function Dl(e,t,n=Se){$(e)&&(e=ps(e));for(const s in e){const r=e[s];let i;Z(r)?"default"in r?i=St(r.from||s,r.default,!0):i=St(r.from||s):i=St(r),pe(i)?Object.defineProperty(t,s,{enumerable:!0,configurable:!0,get:()=>i.value,set:o=>i.value=o}):t[s]=i}}function ir(e,t,n){xe($(e)?e.map(s=>s.bind(t.proxy)):e.bind(t.proxy),t,n)}function Oi(e,t,n,s){const r=s.includes(".")?ki(n,s):()=>n[s];if(re(e)){const i=t[e];K(i)&&Ne(r,i)}else if(K(e))Ne(r,e.bind(n));else if(Z(e))if($(e))e.forEach(i=>Oi(i,t,n,s));else{const i=K(e.handler)?e.handler.bind(n):t[e.handler];K(i)&&Ne(r,i,e)}}function $s(e){const t=e.type,{mixins:n,extends:s}=t,{mixins:r,optionsCache:i,config:{optionMergeStrategies:o}}=e.appContext,l=i.get(t);let c;return l?c=l:!r.length&&!n&&!s?c=t:(c={},r.length&&r.forEach(f=>wn(c,f,o,!0)),wn(c,t,o)),Z(t)&&i.set(t,c),c}function wn(e,t,n,s=!1){const{mixins:r,extends:i}=t;i&&wn(e,i,n,!0),r&&r.forEach(o=>wn(e,o,n,!0));for(const o in t)if(!(s&&o==="expose")){const l=Ul[o]||n&&n[o];e[o]=l?l(e[o],t[o]):t[o]}return e}const Ul={data:or,props:lr,emits:lr,methods:Mt,computed:Mt,beforeCreate:ge,created:ge,beforeMount:ge,mounted:ge,beforeUpdate:ge,updated:ge,beforeDestroy:ge,beforeUnmount:ge,destroyed:ge,unmounted:ge,activated:ge,deactivated:ge,errorCaptured:ge,serverPrefetch:ge,components:Mt,directives:Mt,watch:Bl,provide:or,inject:kl};function or(e,t){return t?e?function(){return ie(K(e)?e.call(this,this):e,K(t)?t.call(this,this):t)}:t:e}function kl(e,t){return Mt(ps(e),ps(t))}function ps(e){if($(e)){const t={};for(let n=0;n1)return n&&K(t)?t.call(s&&s.proxy):t}}const Ii={},Li=()=>Object.create(Ii),Mi=e=>Object.getPrototypeOf(e)===Ii;function Gl(e,t,n,s=!1){const r={},i=Li();e.propsDefaults=Object.create(null),Pi(e,t,r,i);for(const o in e.propsOptions[0])o in r||(r[o]=void 0);n?e.props=s?r:cl(r):e.type.props?e.props=r:e.props=i,e.attrs=i}function zl(e,t,n,s){const{props:r,attrs:i,vnode:{patchFlag:o}}=e,l=J(r),[c]=e.propsOptions;let f=!1;if((s||o>0)&&!(o&16)){if(o&8){const d=e.vnode.dynamicProps;for(let h=0;h{c=!0;const[b,T]=Ni(h,t,!0);ie(o,b),T&&l.push(...T)};!n&&t.mixins.length&&t.mixins.forEach(d),e.extends&&d(e.extends),e.mixins&&e.mixins.forEach(d)}if(!i&&!c)return Z(e)&&s.set(e,bt),bt;if($(i))for(let d=0;d-1,T[1]=P<0||M-1||Y(T,"default"))&&l.push(h)}}}const f=[o,l];return Z(e)&&s.set(e,f),f}function cr(e){return e[0]!=="$"&&!wt(e)}function ar(e){return e===null?"null":typeof e=="function"?e.name||"":typeof e=="object"&&e.constructor&&e.constructor.name||""}function fr(e,t){return ar(e)===ar(t)}function ur(e,t){return $(t)?t.findIndex(n=>fr(n,e)):K(t)&&fr(t,e)?0:-1}const Fi=e=>e[0]==="_"||e==="$stable",Hs=e=>$(e)?e.map(Oe):[Oe(e)],Xl=(e,t,n)=>{if(t._n)return t;const s=Sl((...r)=>Hs(t(...r)),n);return s._c=!1,s},$i=(e,t,n)=>{const s=e._ctx;for(const r in e){if(Fi(r))continue;const i=e[r];if(K(i))t[r]=Xl(r,i,s);else if(i!=null){const o=Hs(i);t[r]=()=>o}}},Hi=(e,t)=>{const n=Hs(t);e.slots.default=()=>n},Yl=(e,t)=>{const n=e.slots=Li();if(e.vnode.shapeFlag&32){const s=t._;s?(ie(n,t),zr(n,"_",s,!0)):$i(t,n)}else t&&Hi(e,t)},Jl=(e,t,n)=>{const{vnode:s,slots:r}=e;let i=!0,o=te;if(s.shapeFlag&32){const l=t._;l?n&&l===1?i=!1:(ie(r,t),!n&&l===1&&delete r._):(i=!t.$stable,$i(t,r)),o=t}else t&&(Hi(e,t),o={default:1});if(i)for(const l in r)!Fi(l)&&o[l]==null&&delete r[l]};function En(e,t,n,s,r=!1){if($(e)){e.forEach((b,T)=>En(b,t&&($(t)?t[T]:t),n,s,r));return}if(Ct(s)&&!r)return;const i=s.shapeFlag&4?Dn(s.component):s.el,o=r?null:i,{i:l,r:c}=e,f=t&&t.r,d=l.refs===te?l.refs={}:l.refs,h=l.setupState;if(f!=null&&f!==c&&(re(f)?(d[f]=null,Y(h,f)&&(h[f]=null)):pe(f)&&(f.value=null)),K(c))Qe(c,l,12,[o,d]);else{const b=re(c),T=pe(c);if(b||T){const M=()=>{if(e.f){const P=b?Y(h,c)?h[c]:d[c]:c.value;r?$(P)&&ws(P,i):$(P)?P.includes(i)||P.push(i):b?(d[c]=[i],Y(h,c)&&(h[c]=d[c])):(c.value=[i],e.k&&(d[e.k]=c.value))}else b?(d[c]=o,Y(h,c)&&(h[c]=o)):T&&(c.value=o,e.k&&(d[e.k]=o))};o?(M.id=-1,ye(M,n)):M()}}}let dr=!1;const _t=()=>{dr||(console.error("Hydration completed but contains mismatches."),dr=!0)},Ql=e=>e.namespaceURI.includes("svg")&&e.tagName!=="foreignObject",Zl=e=>e.namespaceURI.includes("MathML"),nn=e=>{if(Ql(e))return"svg";if(Zl(e))return"mathml"},sn=e=>e.nodeType===8;function ec(e){const{mt:t,p:n,o:{patchProp:s,createText:r,nextSibling:i,parentNode:o,remove:l,insert:c,createComment:f}}=e,d=(g,m)=>{if(!m.hasChildNodes()){n(null,g,m),bn(),m._vnode=g;return}h(m.firstChild,g,null,null,null),bn(),m._vnode=g},h=(g,m,L,O,U,D=!1)=>{D=D||!!m.dynamicChildren;const I=sn(g)&&g.data==="[",w=()=>P(g,m,L,O,U,I),{type:N,ref:S,shapeFlag:W,patchFlag:ne}=m;let oe=g.nodeType;m.el=g,ne===-2&&(D=!1,m.dynamicChildren=null);let j=null;switch(N){case xt:oe!==3?m.children===""?(c(m.el=r(""),o(g),g),j=g):j=w():(g.data!==m.children&&(_t(),g.data=m.children),j=i(g));break;case me:z(g)?(j=i(g),G(m.el=g.content.firstChild,g,L)):oe!==8||I?j=w():j=i(g);break;case Ht:if(I&&(g=i(g),oe=g.nodeType),oe===1||oe===3){j=g;const X=!m.children.length;for(let H=0;H{D=D||!!m.dynamicChildren;const{type:I,props:w,patchFlag:N,shapeFlag:S,dirs:W,transition:ne}=m,oe=I==="input"||I==="option";if(oe||N!==-1){W&&Me(m,null,L,"created");let j=!1;if(z(g)){j=ji(O,ne)&&L&&L.vnode.props&&L.vnode.props.appear;const H=g.content.firstChild;j&&ne.beforeEnter(H),G(H,g,L),m.el=g=H}if(S&16&&!(w&&(w.innerHTML||w.textContent))){let H=T(g.firstChild,m,g,L,O,U,D);for(;H;){_t();const He=H;H=H.nextSibling,l(He)}}else S&8&&g.textContent!==m.children&&(_t(),g.textContent=m.children);if(w)if(oe||!D||N&48)for(const H in w)(oe&&(H.endsWith("value")||H==="indeterminate")||Wt(H)&&!wt(H)||H[0]===".")&&s(g,H,null,w[H],void 0,void 0,L);else w.onClick&&s(g,"onClick",null,w.onClick,void 0,void 0,L);let X;(X=w&&w.onVnodeBeforeMount)&&Ce(X,L,m),W&&Me(m,null,L,"beforeMount"),((X=w&&w.onVnodeMounted)||W||j)&&Si(()=>{X&&Ce(X,L,m),j&&ne.enter(g),W&&Me(m,null,L,"mounted")},O)}return g.nextSibling},T=(g,m,L,O,U,D,I)=>{I=I||!!m.dynamicChildren;const w=m.children,N=w.length;for(let S=0;S{const{slotScopeIds:I}=m;I&&(U=U?U.concat(I):I);const w=o(g),N=T(i(g),m,w,L,O,U,D);return N&&sn(N)&&N.data==="]"?i(m.anchor=N):(_t(),c(m.anchor=f("]"),w,N),N)},P=(g,m,L,O,U,D)=>{if(_t(),m.el=null,D){const N=k(g);for(;;){const S=i(g);if(S&&S!==N)l(S);else break}}const I=i(g),w=o(g);return l(g),n(null,m,w,I,L,O,nn(w),U),I},k=(g,m="[",L="]")=>{let O=0;for(;g;)if(g=i(g),g&&sn(g)&&(g.data===m&&O++,g.data===L)){if(O===0)return i(g);O--}return g},G=(g,m,L)=>{const O=m.parentNode;O&&O.replaceChild(g,m);let U=L;for(;U;)U.vnode.el===m&&(U.vnode.el=U.subTree.el=g),U=U.parent},z=g=>g.nodeType===1&&g.tagName.toLowerCase()==="template";return[d,h]}const ye=Si;function tc(e){return nc(e,ec)}function nc(e,t){const n=Xr();n.__VUE__=!0;const{insert:s,remove:r,patchProp:i,createElement:o,createText:l,createComment:c,setText:f,setElementText:d,parentNode:h,nextSibling:b,setScopeId:T=Se,insertStaticContent:M}=e,P=(a,u,p,y=null,_=null,C=null,A=void 0,E=null,x=!!u.dynamicChildren)=>{if(a===u)return;a&&!ft(a,u)&&(y=Yt(a),Ie(a,_,C,!0),a=null),u.patchFlag===-2&&(x=!1,u.dynamicChildren=null);const{type:v,ref:R,shapeFlag:V}=u;switch(v){case xt:k(a,u,p,y);break;case me:G(a,u,p,y);break;case Ht:a==null&&z(u,p,y,A);break;case _e:S(a,u,p,y,_,C,A,E,x);break;default:V&1?L(a,u,p,y,_,C,A,E,x):V&6?W(a,u,p,y,_,C,A,E,x):(V&64||V&128)&&v.process(a,u,p,y,_,C,A,E,x,mt)}R!=null&&_&&En(R,a&&a.ref,C,u||a,!u)},k=(a,u,p,y)=>{if(a==null)s(u.el=l(u.children),p,y);else{const _=u.el=a.el;u.children!==a.children&&f(_,u.children)}},G=(a,u,p,y)=>{a==null?s(u.el=c(u.children||""),p,y):u.el=a.el},z=(a,u,p,y)=>{[a.el,a.anchor]=M(a.children,u,p,y,a.el,a.anchor)},g=({el:a,anchor:u},p,y)=>{let _;for(;a&&a!==u;)_=b(a),s(a,p,y),a=_;s(u,p,y)},m=({el:a,anchor:u})=>{let p;for(;a&&a!==u;)p=b(a),r(a),a=p;r(u)},L=(a,u,p,y,_,C,A,E,x)=>{u.type==="svg"?A="svg":u.type==="math"&&(A="mathml"),a==null?O(u,p,y,_,C,A,E,x):I(a,u,_,C,A,E,x)},O=(a,u,p,y,_,C,A,E)=>{let x,v;const{props:R,shapeFlag:V,transition:F,dirs:B}=a;if(x=a.el=o(a.type,C,R&&R.is,R),V&8?d(x,a.children):V&16&&D(a.children,x,null,y,_,Gn(a,C),A,E),B&&Me(a,null,y,"created"),U(x,a,a.scopeId,A,y),R){for(const ee in R)ee!=="value"&&!wt(ee)&&i(x,ee,null,R[ee],C,a.children,y,_,je);"value"in R&&i(x,"value",null,R.value,C),(v=R.onVnodeBeforeMount)&&Ce(v,y,a)}B&&Me(a,null,y,"beforeMount");const q=ji(_,F);q&&F.beforeEnter(x),s(x,u,p),((v=R&&R.onVnodeMounted)||q||B)&&ye(()=>{v&&Ce(v,y,a),q&&F.enter(x),B&&Me(a,null,y,"mounted")},_)},U=(a,u,p,y,_)=>{if(p&&T(a,p),y)for(let C=0;C{for(let v=x;v{const E=u.el=a.el;let{patchFlag:x,dynamicChildren:v,dirs:R}=u;x|=a.patchFlag&16;const V=a.props||te,F=u.props||te;let B;if(p&&ot(p,!1),(B=F.onVnodeBeforeUpdate)&&Ce(B,p,u,a),R&&Me(u,a,p,"beforeUpdate"),p&&ot(p,!0),v?w(a.dynamicChildren,v,E,p,y,Gn(u,_),C):A||H(a,u,E,null,p,y,Gn(u,_),C,!1),x>0){if(x&16)N(E,u,V,F,p,y,_);else if(x&2&&V.class!==F.class&&i(E,"class",null,F.class,_),x&4&&i(E,"style",V.style,F.style,_),x&8){const q=u.dynamicProps;for(let ee=0;ee{B&&Ce(B,p,u,a),R&&Me(u,a,p,"updated")},y)},w=(a,u,p,y,_,C,A)=>{for(let E=0;E{if(p!==y){if(p!==te)for(const E in p)!wt(E)&&!(E in y)&&i(a,E,p[E],null,A,u.children,_,C,je);for(const E in y){if(wt(E))continue;const x=y[E],v=p[E];x!==v&&E!=="value"&&i(a,E,v,x,A,u.children,_,C,je)}"value"in y&&i(a,"value",p.value,y.value,A)}},S=(a,u,p,y,_,C,A,E,x)=>{const v=u.el=a?a.el:l(""),R=u.anchor=a?a.anchor:l("");let{patchFlag:V,dynamicChildren:F,slotScopeIds:B}=u;B&&(E=E?E.concat(B):B),a==null?(s(v,p,y),s(R,p,y),D(u.children||[],p,R,_,C,A,E,x)):V>0&&V&64&&F&&a.dynamicChildren?(w(a.dynamicChildren,F,p,_,C,A,E),(u.key!=null||_&&u===_.subTree)&&Vi(a,u,!0)):H(a,u,p,R,_,C,A,E,x)},W=(a,u,p,y,_,C,A,E,x)=>{u.slotScopeIds=E,a==null?u.shapeFlag&512?_.ctx.activate(u,p,y,A,x):ne(u,p,y,_,C,A,x):oe(a,u,x)},ne=(a,u,p,y,_,C,A)=>{const E=a.component=vc(a,y,_);if(Hn(a)&&(E.ctx.renderer=mt),wc(E),E.asyncDep){if(_&&_.registerDep(E,j,A),!a.el){const x=E.subTree=ue(me);G(null,x,u,p)}}else j(E,a,u,p,_,C,A)},oe=(a,u,p)=>{const y=u.component=a.component;if(Al(a,u,p))if(y.asyncDep&&!y.asyncResolved){X(y,u,p);return}else y.next=u,vl(y.update),y.effect.dirty=!0,y.update();else u.el=a.el,y.vnode=u},j=(a,u,p,y,_,C,A)=>{const E=()=>{if(a.isMounted){let{next:R,bu:V,u:F,parent:B,vnode:q}=a;{const yt=Di(a);if(yt){R&&(R.el=q.el,X(a,R,A)),yt.asyncDep.then(()=>{a.isUnmounted||E()});return}}let ee=R,Q;ot(a,!1),R?(R.el=q.el,X(a,R,A)):R=q,V&&un(V),(Q=R.props&&R.props.onVnodeBeforeUpdate)&&Ce(Q,B,R,q),ot(a,!0);const le=Wn(a),Ae=a.subTree;a.subTree=le,P(Ae,le,h(Ae.el),Yt(Ae),a,_,C),R.el=le.el,ee===null&&Ol(a,le.el),F&&ye(F,_),(Q=R.props&&R.props.onVnodeUpdated)&&ye(()=>Ce(Q,B,R,q),_)}else{let R;const{el:V,props:F}=u,{bm:B,m:q,parent:ee}=a,Q=Ct(u);if(ot(a,!1),B&&un(B),!Q&&(R=F&&F.onVnodeBeforeMount)&&Ce(R,ee,u),ot(a,!0),V&&Bn){const le=()=>{a.subTree=Wn(a),Bn(V,a.subTree,a,_,null)};Q?u.type.__asyncLoader().then(()=>!a.isUnmounted&&le()):le()}else{const le=a.subTree=Wn(a);P(null,le,p,y,a,_,C),u.el=le.el}if(q&&ye(q,_),!Q&&(R=F&&F.onVnodeMounted)){const le=u;ye(()=>Ce(R,ee,le),_)}(u.shapeFlag&256||ee&&Ct(ee.vnode)&&ee.vnode.shapeFlag&256)&&a.a&&ye(a.a,_),a.isMounted=!0,u=p=y=null}},x=a.effect=new Ts(E,Se,()=>Fs(v),a.scope),v=a.update=()=>{x.dirty&&x.run()};v.id=a.uid,ot(a,!0),v()},X=(a,u,p)=>{u.component=a;const y=a.vnode.props;a.vnode=u,a.next=null,zl(a,u.props,y,p),Jl(a,u.children,p),st(),tr(a),rt()},H=(a,u,p,y,_,C,A,E,x=!1)=>{const v=a&&a.children,R=a?a.shapeFlag:0,V=u.children,{patchFlag:F,shapeFlag:B}=u;if(F>0){if(F&128){Xt(v,V,p,y,_,C,A,E,x);return}else if(F&256){He(v,V,p,y,_,C,A,E,x);return}}B&8?(R&16&&je(v,_,C),V!==v&&d(p,V)):R&16?B&16?Xt(v,V,p,y,_,C,A,E,x):je(v,_,C,!0):(R&8&&d(p,""),B&16&&D(V,p,y,_,C,A,E,x))},He=(a,u,p,y,_,C,A,E,x)=>{a=a||bt,u=u||bt;const v=a.length,R=u.length,V=Math.min(v,R);let F;for(F=0;FR?je(a,_,C,!0,!1,V):D(u,p,y,_,C,A,E,x,V)},Xt=(a,u,p,y,_,C,A,E,x)=>{let v=0;const R=u.length;let V=a.length-1,F=R-1;for(;v<=V&&v<=F;){const B=a[v],q=u[v]=x?Xe(u[v]):Oe(u[v]);if(ft(B,q))P(B,q,p,null,_,C,A,E,x);else break;v++}for(;v<=V&&v<=F;){const B=a[V],q=u[F]=x?Xe(u[F]):Oe(u[F]);if(ft(B,q))P(B,q,p,null,_,C,A,E,x);else break;V--,F--}if(v>V){if(v<=F){const B=F+1,q=BF)for(;v<=V;)Ie(a[v],_,C,!0),v++;else{const B=v,q=v,ee=new Map;for(v=q;v<=F;v++){const ve=u[v]=x?Xe(u[v]):Oe(u[v]);ve.key!=null&&ee.set(ve.key,v)}let Q,le=0;const Ae=F-q+1;let yt=!1,Bs=0;const It=new Array(Ae);for(v=0;v=Ae){Ie(ve,_,C,!0);continue}let Le;if(ve.key!=null)Le=ee.get(ve.key);else for(Q=q;Q<=F;Q++)if(It[Q-q]===0&&ft(ve,u[Q])){Le=Q;break}Le===void 0?Ie(ve,_,C,!0):(It[Le-q]=v+1,Le>=Bs?Bs=Le:yt=!0,P(ve,u[Le],p,null,_,C,A,E,x),le++)}const Ks=yt?sc(It):bt;for(Q=Ks.length-1,v=Ae-1;v>=0;v--){const ve=q+v,Le=u[ve],Ws=ve+1{const{el:C,type:A,transition:E,children:x,shapeFlag:v}=a;if(v&6){it(a.component.subTree,u,p,y);return}if(v&128){a.suspense.move(u,p,y);return}if(v&64){A.move(a,u,p,mt);return}if(A===_e){s(C,u,p);for(let V=0;VE.enter(C),_);else{const{leave:V,delayLeave:F,afterLeave:B}=E,q=()=>s(C,u,p),ee=()=>{V(C,()=>{q(),B&&B()})};F?F(C,q,ee):ee()}else s(C,u,p)},Ie=(a,u,p,y=!1,_=!1)=>{const{type:C,props:A,ref:E,children:x,dynamicChildren:v,shapeFlag:R,patchFlag:V,dirs:F,memoIndex:B}=a;if(V===-2&&(_=!1),E!=null&&En(E,null,p,a,!0),B!=null&&(u.renderCache[B]=void 0),R&256){u.ctx.deactivate(a);return}const q=R&1&&F,ee=!Ct(a);let Q;if(ee&&(Q=A&&A.onVnodeBeforeUnmount)&&Ce(Q,u,a),R&6)So(a.component,p,y);else{if(R&128){a.suspense.unmount(p,y);return}q&&Me(a,null,u,"beforeUnmount"),R&64?a.type.remove(a,u,p,mt,y):v&&(C!==_e||V>0&&V&64)?je(v,u,p,!1,!0):(C===_e&&V&384||!_&&R&16)&&je(x,u,p),y&&Us(a)}(ee&&(Q=A&&A.onVnodeUnmounted)||q)&&ye(()=>{Q&&Ce(Q,u,a),q&&Me(a,null,u,"unmounted")},p)},Us=a=>{const{type:u,el:p,anchor:y,transition:_}=a;if(u===_e){Co(p,y);return}if(u===Ht){m(a);return}const C=()=>{r(p),_&&!_.persisted&&_.afterLeave&&_.afterLeave()};if(a.shapeFlag&1&&_&&!_.persisted){const{leave:A,delayLeave:E}=_,x=()=>A(p,C);E?E(a.el,C,x):x()}else C()},Co=(a,u)=>{let p;for(;a!==u;)p=b(a),r(a),a=p;r(u)},So=(a,u,p)=>{const{bum:y,scope:_,update:C,subTree:A,um:E,m:x,a:v}=a;hr(x),hr(v),y&&un(y),_.stop(),C&&(C.active=!1,Ie(A,a,u,p)),E&&ye(E,u),ye(()=>{a.isUnmounted=!0},u),u&&u.pendingBranch&&!u.isUnmounted&&a.asyncDep&&!a.asyncResolved&&a.suspenseId===u.pendingId&&(u.deps--,u.deps===0&&u.resolve())},je=(a,u,p,y=!1,_=!1,C=0)=>{for(let A=C;Aa.shapeFlag&6?Yt(a.component.subTree):a.shapeFlag&128?a.suspense.next():b(a.anchor||a.el);let Un=!1;const ks=(a,u,p)=>{a==null?u._vnode&&Ie(u._vnode,null,null,!0):P(u._vnode||null,a,u,null,null,null,p),Un||(Un=!0,tr(),bn(),Un=!1),u._vnode=a},mt={p:P,um:Ie,m:it,r:Us,mt:ne,mc:D,pc:H,pbc:w,n:Yt,o:e};let kn,Bn;return t&&([kn,Bn]=t(mt)),{render:ks,hydrate:kn,createApp:Wl(ks,kn)}}function Gn({type:e,props:t},n){return n==="svg"&&e==="foreignObject"||n==="mathml"&&e==="annotation-xml"&&t&&t.encoding&&t.encoding.includes("html")?void 0:n}function ot({effect:e,update:t},n){e.allowRecurse=t.allowRecurse=n}function ji(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function Vi(e,t,n=!1){const s=e.children,r=t.children;if($(s)&&$(r))for(let i=0;i>1,e[n[l]]0&&(t[s]=n[i-1]),n[i]=s)}}for(i=n.length,o=n[i-1];i-- >0;)n[i]=o,o=t[o];return n}function Di(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Di(t)}function hr(e){if(e)for(let t=0;tSt(rc);function Ui(e,t){return $n(e,null,t)}function ff(e,t){return $n(e,null,{flush:"post"})}const rn={};function Ne(e,t,n){return $n(e,t,n)}function $n(e,t,{immediate:n,deep:s,flush:r,once:i,onTrack:o,onTrigger:l}=te){if(t&&i){const O=t;t=(...U)=>{O(...U),L()}}const c=ae,f=O=>s===!0?O:Ye(O,s===!1?1:void 0);let d,h=!1,b=!1;if(pe(e)?(d=()=>e.value,h=_n(e)):Pt(e)?(d=()=>f(e),h=!0):$(e)?(b=!0,h=e.some(O=>Pt(O)||_n(O)),d=()=>e.map(O=>{if(pe(O))return O.value;if(Pt(O))return f(O);if(K(O))return Qe(O,c,2)})):K(e)?t?d=()=>Qe(e,c,2):d=()=>(T&&T(),xe(e,c,3,[M])):d=Se,t&&s){const O=d;d=()=>Ye(O())}let T,M=O=>{T=g.onStop=()=>{Qe(O,c,4),T=g.onStop=void 0}},P;if(Vn)if(M=Se,t?n&&xe(t,c,3,[d(),b?[]:void 0,M]):d(),r==="sync"){const O=ic();P=O.__watcherHandles||(O.__watcherHandles=[])}else return Se;let k=b?new Array(e.length).fill(rn):rn;const G=()=>{if(!(!g.active||!g.dirty))if(t){const O=g.run();(s||h||(b?O.some((U,D)=>et(U,k[D])):et(O,k)))&&(T&&T(),xe(t,c,3,[O,k===rn?void 0:b&&k[0]===rn?[]:k,M]),k=O)}else g.run()};G.allowRecurse=!!t;let z;r==="sync"?z=G:r==="post"?z=()=>ye(G,c&&c.suspense):(G.pre=!0,c&&(G.id=c.uid),z=()=>Fs(G));const g=new Ts(d,Se,z),m=Zr(),L=()=>{g.stop(),m&&ws(m.effects,g)};return t?n?G():k=g.run():r==="post"?ye(g.run.bind(g),c&&c.suspense):g.run(),P&&P.push(L),L}function oc(e,t,n){const s=this.proxy,r=re(e)?e.includes(".")?ki(s,e):()=>s[e]:e.bind(s,s);let i;K(t)?i=t:(i=t.handler,n=t);const o=zt(this),l=$n(r,i.bind(s),n);return o(),l}function ki(e,t){const n=t.split(".");return()=>{let s=e;for(let r=0;r{Ye(s,t,n)});else if(Gr(e)){for(const s in e)Ye(e[s],t,n);for(const s of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,s)&&Ye(e[s],t,n)}return e}const Hn=e=>e.type.__isKeepAlive;function lc(e,t){Bi(e,"a",t)}function cc(e,t){Bi(e,"da",t)}function Bi(e,t,n=ae){const s=e.__wdc||(e.__wdc=()=>{let r=n;for(;r;){if(r.isDeactivated)return;r=r.parent}return e()});if(Nn(t,s,n),n){let r=n.parent;for(;r&&r.parent;)Hn(r.parent.vnode)&&ac(s,t,n,r),r=r.parent}}function ac(e,t,n,s){const r=Nn(t,e,s,!0);Fn(()=>{ws(s[t],r)},n)}const ze=Symbol("_leaveCb"),on=Symbol("_enterCb");function fc(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Rt(()=>{e.isMounted=!0}),xi(()=>{e.isUnmounting=!0}),e}const Ee=[Function,Array],Ki={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:Ee,onEnter:Ee,onAfterEnter:Ee,onEnterCancelled:Ee,onBeforeLeave:Ee,onLeave:Ee,onAfterLeave:Ee,onLeaveCancelled:Ee,onBeforeAppear:Ee,onAppear:Ee,onAfterAppear:Ee,onAppearCancelled:Ee},Wi=e=>{const t=e.subTree;return t.component?Wi(t.component):t},uc={name:"BaseTransition",props:Ki,setup(e,{slots:t}){const n=jn(),s=fc();return()=>{const r=t.default&&Gi(t.default(),!0);if(!r||!r.length)return;let i=r[0];if(r.length>1){for(const b of r)if(b.type!==me){i=b;break}}const o=J(e),{mode:l}=o;if(s.isLeaving)return zn(i);const c=pr(i);if(!c)return zn(i);let f=ms(c,o,s,n,b=>f=b);Cn(c,f);const d=n.subTree,h=d&&pr(d);if(h&&h.type!==me&&!ft(c,h)&&Wi(n).type!==me){const b=ms(h,o,s,n);if(Cn(h,b),l==="out-in"&&c.type!==me)return s.isLeaving=!0,b.afterLeave=()=>{s.isLeaving=!1,n.update.active!==!1&&(n.effect.dirty=!0,n.update())},zn(i);l==="in-out"&&c.type!==me&&(b.delayLeave=(T,M,P)=>{const k=qi(s,h);k[String(h.key)]=h,T[ze]=()=>{M(),T[ze]=void 0,delete f.delayedLeave},f.delayedLeave=P})}return i}}},dc=uc;function qi(e,t){const{leavingVNodes:n}=e;let s=n.get(t.type);return s||(s=Object.create(null),n.set(t.type,s)),s}function ms(e,t,n,s,r){const{appear:i,mode:o,persisted:l=!1,onBeforeEnter:c,onEnter:f,onAfterEnter:d,onEnterCancelled:h,onBeforeLeave:b,onLeave:T,onAfterLeave:M,onLeaveCancelled:P,onBeforeAppear:k,onAppear:G,onAfterAppear:z,onAppearCancelled:g}=t,m=String(e.key),L=qi(n,e),O=(I,w)=>{I&&xe(I,s,9,w)},U=(I,w)=>{const N=w[1];O(I,w),$(I)?I.every(S=>S.length<=1)&&N():I.length<=1&&N()},D={mode:o,persisted:l,beforeEnter(I){let w=c;if(!n.isMounted)if(i)w=k||c;else return;I[ze]&&I[ze](!0);const N=L[m];N&&ft(e,N)&&N.el[ze]&&N.el[ze](),O(w,[I])},enter(I){let w=f,N=d,S=h;if(!n.isMounted)if(i)w=G||f,N=z||d,S=g||h;else return;let W=!1;const ne=I[on]=oe=>{W||(W=!0,oe?O(S,[I]):O(N,[I]),D.delayedLeave&&D.delayedLeave(),I[on]=void 0)};w?U(w,[I,ne]):ne()},leave(I,w){const N=String(e.key);if(I[on]&&I[on](!0),n.isUnmounting)return w();O(b,[I]);let S=!1;const W=I[ze]=ne=>{S||(S=!0,w(),ne?O(P,[I]):O(M,[I]),I[ze]=void 0,L[N]===e&&delete L[N])};L[N]=e,T?U(T,[I,W]):W()},clone(I){const w=ms(I,t,n,s,r);return r&&r(w),w}};return D}function zn(e){if(Hn(e))return e=tt(e),e.children=null,e}function pr(e){if(!Hn(e))return e;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&K(n.default))return n.default()}}function Cn(e,t){e.shapeFlag&6&&e.component?Cn(e.component.subTree,t):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function Gi(e,t=!1,n){let s=[],r=0;for(let i=0;i1)for(let i=0;ie.__isTeleport,_e=Symbol.for("v-fgt"),xt=Symbol.for("v-txt"),me=Symbol.for("v-cmt"),Ht=Symbol.for("v-stc"),jt=[];let Re=null;function zi(e=!1){jt.push(Re=e?null:[])}function pc(){jt.pop(),Re=jt[jt.length-1]||null}let Bt=1;function gr(e){Bt+=e}function Xi(e){return e.dynamicChildren=Bt>0?Re||bt:null,pc(),Bt>0&&Re&&Re.push(e),e}function uf(e,t,n,s,r,i){return Xi(Qi(e,t,n,s,r,i,!0))}function Yi(e,t,n,s,r){return Xi(ue(e,t,n,s,r,!0))}function Sn(e){return e?e.__v_isVNode===!0:!1}function ft(e,t){return e.type===t.type&&e.key===t.key}const Ji=({key:e})=>e??null,hn=({ref:e,ref_key:t,ref_for:n})=>(typeof e=="number"&&(e=""+e),e!=null?re(e)||pe(e)||K(e)?{i:ce,r:e,k:t,f:!!n}:e:null);function Qi(e,t=null,n=null,s=0,r=null,i=e===_e?0:1,o=!1,l=!1){const c={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Ji(t),ref:t&&hn(t),scopeId:Pn,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetAnchor:null,staticCount:0,shapeFlag:i,patchFlag:s,dynamicProps:r,dynamicChildren:null,appContext:null,ctx:ce};return l?(js(c,n),i&128&&e.normalize(c)):n&&(c.shapeFlag|=re(n)?8:16),Bt>0&&!o&&Re&&(c.patchFlag>0||i&6)&&c.patchFlag!==32&&Re.push(c),c}const ue=gc;function gc(e,t=null,n=null,s=0,r=null,i=!1){if((!e||e===Ei)&&(e=me),Sn(e)){const l=tt(e,t,!0);return n&&js(l,n),Bt>0&&!i&&Re&&(l.shapeFlag&6?Re[Re.indexOf(e)]=l:Re.push(l)),l.patchFlag=-2,l}if(xc(e)&&(e=e.__vccOpts),t){t=mc(t);let{class:l,style:c}=t;l&&!re(l)&&(t.class=Ss(l)),Z(c)&&(ui(c)&&!$(c)&&(c=ie({},c)),t.style=Cs(c))}const o=re(e)?1:Rl(e)?128:hc(e)?64:Z(e)?4:K(e)?2:0;return Qi(e,t,n,s,r,o,i,!0)}function mc(e){return e?ui(e)||Mi(e)?ie({},e):e:null}function tt(e,t,n=!1,s=!1){const{props:r,ref:i,patchFlag:o,children:l,transition:c}=e,f=t?yc(r||{},t):r,d={__v_isVNode:!0,__v_skip:!0,type:e.type,props:f,key:f&&Ji(f),ref:t&&t.ref?n&&i?$(i)?i.concat(hn(t)):[i,hn(t)]:hn(t):i,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:l,target:e.target,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==_e?o===-1?16:o|16:o,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:c,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&tt(e.ssContent),ssFallback:e.ssFallback&&tt(e.ssFallback),el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return c&&s&&Cn(d,c.clone(d)),d}function Zi(e=" ",t=0){return ue(xt,null,e,t)}function df(e,t){const n=ue(Ht,null,e);return n.staticCount=t,n}function hf(e="",t=!1){return t?(zi(),Yi(me,null,e)):ue(me,null,e)}function Oe(e){return e==null||typeof e=="boolean"?ue(me):$(e)?ue(_e,null,e.slice()):typeof e=="object"?Xe(e):ue(xt,null,String(e))}function Xe(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:tt(e)}function js(e,t){let n=0;const{shapeFlag:s}=e;if(t==null)t=null;else if($(t))n=16;else if(typeof t=="object")if(s&65){const r=t.default;r&&(r._c&&(r._d=!1),js(e,r()),r._c&&(r._d=!0));return}else{n=32;const r=t._;!r&&!Mi(t)?t._ctx=ce:r===3&&ce&&(ce.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else K(t)?(t={default:t,_ctx:ce},n=32):(t=String(t),s&64?(n=16,t=[Zi(t)]):n=8);e.children=t,e.shapeFlag|=n}function yc(...e){const t={};for(let n=0;nae||ce;let xn,ys;{const e=Xr(),t=(n,s)=>{let r;return(r=e[n])||(r=e[n]=[]),r.push(s),i=>{r.length>1?r.forEach(o=>o(i)):r[0](i)}};xn=t("__VUE_INSTANCE_SETTERS__",n=>ae=n),ys=t("__VUE_SSR_SETTERS__",n=>Vn=n)}const zt=e=>{const t=ae;return xn(e),e.scope.on(),()=>{e.scope.off(),xn(t)}},mr=()=>{ae&&ae.scope.off(),xn(null)};function eo(e){return e.vnode.shapeFlag&4}let Vn=!1;function wc(e,t=!1){t&&ys(t);const{props:n,children:s}=e.vnode,r=eo(e);Gl(e,n,r,t),Yl(e,s);const i=r?Ec(e,t):void 0;return t&&ys(!1),i}function Ec(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Hl);const{setup:s}=n;if(s){const r=e.setupContext=s.length>1?no(e):null,i=zt(e);st();const o=Qe(s,e,0,[e.props,r]);if(rt(),i(),Wr(o)){if(o.then(mr,mr),t)return o.then(l=>{yr(e,l,t)}).catch(l=>{Ln(l,e,0)});e.asyncDep=o}else yr(e,o,t)}else to(e,t)}function yr(e,t,n){K(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Z(t)&&(e.setupState=mi(t)),to(e,n)}let _r;function to(e,t,n){const s=e.type;if(!e.render){if(!t&&_r&&!s.render){const r=s.template||$s(e).template;if(r){const{isCustomElement:i,compilerOptions:o}=e.appContext.config,{delimiters:l,compilerOptions:c}=s,f=ie(ie({isCustomElement:i,delimiters:l},o),c);s.render=_r(r,f)}}e.render=s.render||Se}{const r=zt(e);st();try{Vl(e)}finally{rt(),r()}}}const Cc={get(e,t){return be(e,"get",""),e[t]}};function no(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,Cc),slots:e.slots,emit:e.emit,expose:t}}function Dn(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(mi(dn(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ft)return Ft[n](e)},has(t,n){return n in t||n in Ft}})):e.proxy}function Sc(e,t=!0){return K(e)?e.displayName||e.name:e.name||t&&e.__name}function xc(e){return K(e)&&"__vccOpts"in e}const se=(e,t)=>al(e,t,Vn);function _s(e,t,n){const s=arguments.length;return s===2?Z(t)&&!$(t)?Sn(t)?ue(e,null,[t]):ue(e,t):ue(e,null,t):(s>3?n=Array.prototype.slice.call(arguments,2):s===3&&Sn(n)&&(n=[n]),ue(e,t,n))}const Tc="3.4.31";/** +* @vue/runtime-dom v3.4.31 +* (c) 2018-present Yuxi (Evan) You and Vue contributors +* @license MIT +**/const Ac="http://www.w3.org/2000/svg",Oc="http://www.w3.org/1998/Math/MathML",Ve=typeof document<"u"?document:null,br=Ve&&Ve.createElement("template"),Rc={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,s)=>{const r=t==="svg"?Ve.createElementNS(Ac,e):t==="mathml"?Ve.createElementNS(Oc,e):n?Ve.createElement(e,{is:n}):Ve.createElement(e);return e==="select"&&s&&s.multiple!=null&&r.setAttribute("multiple",s.multiple),r},createText:e=>Ve.createTextNode(e),createComment:e=>Ve.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Ve.querySelector(e),setScopeId(e,t){e.setAttribute(t,"")},insertStaticContent(e,t,n,s,r,i){const o=n?n.previousSibling:t.lastChild;if(r&&(r===i||r.nextSibling))for(;t.insertBefore(r.cloneNode(!0),n),!(r===i||!(r=r.nextSibling)););else{br.innerHTML=s==="svg"?`${e}`:s==="mathml"?`${e}`:e;const l=br.content;if(s==="svg"||s==="mathml"){const c=l.firstChild;for(;c.firstChild;)l.appendChild(c.firstChild);l.removeChild(c)}t.insertBefore(l,n)}return[o?o.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},We="transition",Lt="animation",Kt=Symbol("_vtc"),so=(e,{slots:t})=>_s(dc,Ic(e),t);so.displayName="Transition";const ro={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String};so.props=ie({},Ki,ro);const lt=(e,t=[])=>{$(e)?e.forEach(n=>n(...t)):e&&e(...t)},vr=e=>e?$(e)?e.some(t=>t.length>1):e.length>1:!1;function Ic(e){const t={};for(const S in e)S in ro||(t[S]=e[S]);if(e.css===!1)return t;const{name:n="v",type:s,duration:r,enterFromClass:i=`${n}-enter-from`,enterActiveClass:o=`${n}-enter-active`,enterToClass:l=`${n}-enter-to`,appearFromClass:c=i,appearActiveClass:f=o,appearToClass:d=l,leaveFromClass:h=`${n}-leave-from`,leaveActiveClass:b=`${n}-leave-active`,leaveToClass:T=`${n}-leave-to`}=e,M=Lc(r),P=M&&M[0],k=M&&M[1],{onBeforeEnter:G,onEnter:z,onEnterCancelled:g,onLeave:m,onLeaveCancelled:L,onBeforeAppear:O=G,onAppear:U=z,onAppearCancelled:D=g}=t,I=(S,W,ne)=>{ct(S,W?d:l),ct(S,W?f:o),ne&&ne()},w=(S,W)=>{S._isLeaving=!1,ct(S,h),ct(S,T),ct(S,b),W&&W()},N=S=>(W,ne)=>{const oe=S?U:z,j=()=>I(W,S,ne);lt(oe,[W,j]),wr(()=>{ct(W,S?c:i),qe(W,S?d:l),vr(oe)||Er(W,s,P,j)})};return ie(t,{onBeforeEnter(S){lt(G,[S]),qe(S,i),qe(S,o)},onBeforeAppear(S){lt(O,[S]),qe(S,c),qe(S,f)},onEnter:N(!1),onAppear:N(!0),onLeave(S,W){S._isLeaving=!0;const ne=()=>w(S,W);qe(S,h),qe(S,b),Nc(),wr(()=>{S._isLeaving&&(ct(S,h),qe(S,T),vr(m)||Er(S,s,k,ne))}),lt(m,[S,ne])},onEnterCancelled(S){I(S,!1),lt(g,[S])},onAppearCancelled(S){I(S,!0),lt(D,[S])},onLeaveCancelled(S){w(S),lt(L,[S])}})}function Lc(e){if(e==null)return null;if(Z(e))return[Xn(e.enter),Xn(e.leave)];{const t=Xn(e);return[t,t]}}function Xn(e){return Io(e)}function qe(e,t){t.split(/\s+/).forEach(n=>n&&e.classList.add(n)),(e[Kt]||(e[Kt]=new Set)).add(t)}function ct(e,t){t.split(/\s+/).forEach(s=>s&&e.classList.remove(s));const n=e[Kt];n&&(n.delete(t),n.size||(e[Kt]=void 0))}function wr(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let Mc=0;function Er(e,t,n,s){const r=e._endId=++Mc,i=()=>{r===e._endId&&s()};if(n)return setTimeout(i,n);const{type:o,timeout:l,propCount:c}=Pc(e,t);if(!o)return s();const f=o+"end";let d=0;const h=()=>{e.removeEventListener(f,b),i()},b=T=>{T.target===e&&++d>=c&&h()};setTimeout(()=>{d(n[M]||"").split(", "),r=s(`${We}Delay`),i=s(`${We}Duration`),o=Cr(r,i),l=s(`${Lt}Delay`),c=s(`${Lt}Duration`),f=Cr(l,c);let d=null,h=0,b=0;t===We?o>0&&(d=We,h=o,b=i.length):t===Lt?f>0&&(d=Lt,h=f,b=c.length):(h=Math.max(o,f),d=h>0?o>f?We:Lt:null,b=d?d===We?i.length:c.length:0);const T=d===We&&/\b(transform|all)(,|$)/.test(s(`${We}Property`).toString());return{type:d,timeout:h,propCount:b,hasTransform:T}}function Cr(e,t){for(;e.lengthSr(n)+Sr(e[s])))}function Sr(e){return e==="auto"?0:Number(e.slice(0,-1).replace(",","."))*1e3}function Nc(){return document.body.offsetHeight}function Fc(e,t,n){const s=e[Kt];s&&(t=(t?[t,...s]:[...s]).join(" ")),t==null?e.removeAttribute("class"):n?e.setAttribute("class",t):e.className=t}const xr=Symbol("_vod"),$c=Symbol("_vsh"),Hc=Symbol(""),jc=/(^|;)\s*display\s*:/;function Vc(e,t,n){const s=e.style,r=re(n);let i=!1;if(n&&!r){if(t)if(re(t))for(const o of t.split(";")){const l=o.slice(0,o.indexOf(":")).trim();n[l]==null&&pn(s,l,"")}else for(const o in t)n[o]==null&&pn(s,o,"");for(const o in n)o==="display"&&(i=!0),pn(s,o,n[o])}else if(r){if(t!==n){const o=s[Hc];o&&(n+=";"+o),s.cssText=n,i=jc.test(n)}}else t&&e.removeAttribute("style");xr in e&&(e[xr]=i?s.display:"",e[$c]&&(s.display="none"))}const Tr=/\s*!important$/;function pn(e,t,n){if($(n))n.forEach(s=>pn(e,t,s));else if(n==null&&(n=""),t.startsWith("--"))e.setProperty(t,n);else{const s=Dc(e,t);Tr.test(n)?e.setProperty(gt(s),n.replace(Tr,""),"important"):e[s]=n}}const Ar=["Webkit","Moz","ms"],Yn={};function Dc(e,t){const n=Yn[t];if(n)return n;let s=$e(t);if(s!=="filter"&&s in e)return Yn[t]=s;s=An(s);for(let r=0;rJn||(Wc.then(()=>Jn=0),Jn=Date.now());function Gc(e,t){const n=s=>{if(!s._vts)s._vts=Date.now();else if(s._vts<=n.attached)return;xe(zc(s,n.value),t,5,[s])};return n.value=e,n.attached=qc(),n}function zc(e,t){if($(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(s=>r=>!r._stopped&&s&&s(r))}else return t}const Mr=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,Xc=(e,t,n,s,r,i,o,l,c)=>{const f=r==="svg";t==="class"?Fc(e,s,f):t==="style"?Vc(e,n,s):Wt(t)?vs(t)||Bc(e,t,n,s,o):(t[0]==="."?(t=t.slice(1),!0):t[0]==="^"?(t=t.slice(1),!1):Yc(e,t,s,f))?(Uc(e,t,s,i,o,l,c),!e.tagName.includes("-")&&(t==="value"||t==="checked"||t==="selected")&&Rr(e,t,s,f,o,t!=="value")):(t==="true-value"?e._trueValue=s:t==="false-value"&&(e._falseValue=s),Rr(e,t,s,f))};function Yc(e,t,n,s){if(s)return!!(t==="innerHTML"||t==="textContent"||t in e&&Mr(t)&&K(n));if(t==="spellcheck"||t==="draggable"||t==="translate"||t==="form"||t==="list"&&e.tagName==="INPUT"||t==="type"&&e.tagName==="TEXTAREA")return!1;if(t==="width"||t==="height"){const r=e.tagName;if(r==="IMG"||r==="VIDEO"||r==="CANVAS"||r==="SOURCE")return!1}return Mr(t)&&re(n)?!1:t in e}const nt=e=>{const t=e.props["onUpdate:modelValue"]||!1;return $(t)?n=>un(t,n):t};function Jc(e){e.target.composing=!0}function Pr(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event("input")))}const Te=Symbol("_assign"),pf={created(e,{modifiers:{lazy:t,trim:n,number:s}},r){e[Te]=nt(r);const i=s||r.props&&r.props.type==="number";De(e,t?"change":"input",o=>{if(o.target.composing)return;let l=e.value;n&&(l=l.trim()),i&&(l=mn(l)),e[Te](l)}),n&&De(e,"change",()=>{e.value=e.value.trim()}),t||(De(e,"compositionstart",Jc),De(e,"compositionend",Pr),De(e,"change",Pr))},mounted(e,{value:t}){e.value=t??""},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:s,trim:r,number:i}},o){if(e[Te]=nt(o),e.composing)return;const l=(i||e.type==="number")&&!/^0\d/.test(e.value)?mn(e.value):e.value,c=t??"";l!==c&&(document.activeElement===e&&e.type!=="range"&&(s&&t===n||r&&e.value.trim()===c)||(e.value=c))}},gf={deep:!0,created(e,t,n){e[Te]=nt(n),De(e,"change",()=>{const s=e._modelValue,r=Tt(e),i=e.checked,o=e[Te];if($(s)){const l=xs(s,r),c=l!==-1;if(i&&!c)o(s.concat(r));else if(!i&&c){const f=[...s];f.splice(l,1),o(f)}}else if(Ot(s)){const l=new Set(s);i?l.add(r):l.delete(r),o(l)}else o(io(e,i))})},mounted:Nr,beforeUpdate(e,t,n){e[Te]=nt(n),Nr(e,t,n)}};function Nr(e,{value:t,oldValue:n},s){e._modelValue=t,$(t)?e.checked=xs(t,s.props.value)>-1:Ot(t)?e.checked=t.has(s.props.value):t!==n&&(e.checked=pt(t,io(e,!0)))}const mf={created(e,{value:t},n){e.checked=pt(t,n.props.value),e[Te]=nt(n),De(e,"change",()=>{e[Te](Tt(e))})},beforeUpdate(e,{value:t,oldValue:n},s){e[Te]=nt(s),t!==n&&(e.checked=pt(t,s.props.value))}},yf={deep:!0,created(e,{value:t,modifiers:{number:n}},s){const r=Ot(t);De(e,"change",()=>{const i=Array.prototype.filter.call(e.options,o=>o.selected).map(o=>n?mn(Tt(o)):Tt(o));e[Te](e.multiple?r?new Set(i):i:i[0]),e._assigning=!0,Gt(()=>{e._assigning=!1})}),e[Te]=nt(s)},mounted(e,{value:t,modifiers:{number:n}}){Fr(e,t)},beforeUpdate(e,t,n){e[Te]=nt(n)},updated(e,{value:t,modifiers:{number:n}}){e._assigning||Fr(e,t)}};function Fr(e,t,n){const s=e.multiple,r=$(t);if(!(s&&!r&&!Ot(t))){for(let i=0,o=e.options.length;iString(d)===String(c)):l.selected=xs(t,c)>-1}else l.selected=t.has(c);else if(pt(Tt(l),t)){e.selectedIndex!==i&&(e.selectedIndex=i);return}}!s&&e.selectedIndex!==-1&&(e.selectedIndex=-1)}}function Tt(e){return"_value"in e?e._value:e.value}function io(e,t){const n=t?"_trueValue":"_falseValue";return n in e?e[n]:t}const Qc=["ctrl","shift","alt","meta"],Zc={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>"button"in e&&e.button!==0,middle:e=>"button"in e&&e.button!==1,right:e=>"button"in e&&e.button!==2,exact:(e,t)=>Qc.some(n=>e[`${n}Key`]&&!t.includes(n))},_f=(e,t)=>{const n=e._withMods||(e._withMods={}),s=t.join(".");return n[s]||(n[s]=(r,...i)=>{for(let o=0;o{const n=e._withKeys||(e._withKeys={}),s=t.join(".");return n[s]||(n[s]=r=>{if(!("key"in r))return;const i=gt(r.key);if(t.some(o=>o===i||ea[o]===i))return e(r)})},ta=ie({patchProp:Xc},Rc);let Qn,$r=!1;function na(){return Qn=$r?Qn:tc(ta),$r=!0,Qn}const vf=(...e)=>{const t=na().createApp(...e),{mount:n}=t;return t.mount=s=>{const r=ra(s);if(r)return n(r,!0,sa(r))},t};function sa(e){if(e instanceof SVGElement)return"svg";if(typeof MathMLElement=="function"&&e instanceof MathMLElement)return"mathml"}function ra(e){return re(e)?document.querySelector(e):e}const wf=(e,t)=>{const n=e.__vccOpts||e;for(const[s,r]of t)n[s]=r;return n},ia="modulepreload",oa=function(e){return"/"+e},Hr={},Ef=function(t,n,s){let r=Promise.resolve();if(n&&n.length>0){document.getElementsByTagName("link");const i=document.querySelector("meta[property=csp-nonce]"),o=(i==null?void 0:i.nonce)||(i==null?void 0:i.getAttribute("nonce"));r=Promise.all(n.map(l=>{if(l=oa(l),l in Hr)return;Hr[l]=!0;const c=l.endsWith(".css"),f=c?'[rel="stylesheet"]':"";if(document.querySelector(`link[href="${l}"]${f}`))return;const d=document.createElement("link");if(d.rel=c?"stylesheet":ia,c||(d.as="script",d.crossOrigin=""),d.href=l,o&&d.setAttribute("nonce",o),document.head.appendChild(d),c)return new Promise((h,b)=>{d.addEventListener("load",h),d.addEventListener("error",()=>b(new Error(`Unable to preload CSS for ${l}`)))})}))}return r.then(()=>t()).catch(i=>{const o=new Event("vite:preloadError",{cancelable:!0});if(o.payload=i,window.dispatchEvent(o),!o.defaultPrevented)throw i})},la=window.__VP_SITE_DATA__;function Vs(e){return Zr()?(Uo(e),!0):!1}function Ze(e){return typeof e=="function"?e():gi(e)}const oo=typeof window<"u"&&typeof document<"u";typeof WorkerGlobalScope<"u"&&globalThis instanceof WorkerGlobalScope;const ca=Object.prototype.toString,aa=e=>ca.call(e)==="[object Object]",lo=()=>{},jr=fa();function fa(){var e,t;return oo&&((e=window==null?void 0:window.navigator)==null?void 0:e.userAgent)&&(/iP(?:ad|hone|od)/.test(window.navigator.userAgent)||((t=window==null?void 0:window.navigator)==null?void 0:t.maxTouchPoints)>2&&/iPad|Macintosh/.test(window==null?void 0:window.navigator.userAgent))}function ua(e,t){function n(...s){return new Promise((r,i)=>{Promise.resolve(e(()=>t.apply(this,s),{fn:t,thisArg:this,args:s})).then(r).catch(i)})}return n}const co=e=>e();function da(e=co){const t=fe(!0);function n(){t.value=!1}function s(){t.value=!0}const r=(...i)=>{t.value&&e(...i)};return{isActive:In(t),pause:n,resume:s,eventFilter:r}}function ha(e){return jn()}function ao(...e){if(e.length!==1)return ml(...e);const t=e[0];return typeof t=="function"?In(hl(()=>({get:t,set:lo}))):fe(t)}function pa(e,t,n={}){const{eventFilter:s=co,...r}=n;return Ne(e,ua(s,t),r)}function ga(e,t,n={}){const{eventFilter:s,...r}=n,{eventFilter:i,pause:o,resume:l,isActive:c}=da(s);return{stop:pa(e,t,{...r,eventFilter:i}),pause:o,resume:l,isActive:c}}function Ds(e,t=!0,n){ha()?Rt(e,n):t?e():Gt(e)}function fo(e){var t;const n=Ze(e);return(t=n==null?void 0:n.$el)!=null?t:n}const ke=oo?window:void 0;function At(...e){let t,n,s,r;if(typeof e[0]=="string"||Array.isArray(e[0])?([n,s,r]=e,t=ke):[t,n,s,r]=e,!t)return lo;Array.isArray(n)||(n=[n]),Array.isArray(s)||(s=[s]);const i=[],o=()=>{i.forEach(d=>d()),i.length=0},l=(d,h,b,T)=>(d.addEventListener(h,b,T),()=>d.removeEventListener(h,b,T)),c=Ne(()=>[fo(t),Ze(r)],([d,h])=>{if(o(),!d)return;const b=aa(h)?{...h}:h;i.push(...n.flatMap(T=>s.map(M=>l(d,T,M,b))))},{immediate:!0,flush:"post"}),f=()=>{c(),o()};return Vs(f),f}function ma(e){return typeof e=="function"?e:typeof e=="string"?t=>t.key===e:Array.isArray(e)?t=>e.includes(t.key):()=>!0}function Cf(...e){let t,n,s={};e.length===3?(t=e[0],n=e[1],s=e[2]):e.length===2?typeof e[1]=="object"?(t=!0,n=e[0],s=e[1]):(t=e[0],n=e[1]):(t=!0,n=e[0]);const{target:r=ke,eventName:i="keydown",passive:o=!1,dedupe:l=!1}=s,c=ma(t);return At(r,i,d=>{d.repeat&&Ze(l)||c(d)&&n(d)},o)}function ya(){const e=fe(!1),t=jn();return t&&Rt(()=>{e.value=!0},t),e}function _a(e){const t=ya();return se(()=>(t.value,!!e()))}function uo(e,t={}){const{window:n=ke}=t,s=_a(()=>n&&"matchMedia"in n&&typeof n.matchMedia=="function");let r;const i=fe(!1),o=f=>{i.value=f.matches},l=()=>{r&&("removeEventListener"in r?r.removeEventListener("change",o):r.removeListener(o))},c=Ui(()=>{s.value&&(l(),r=n.matchMedia(Ze(e)),"addEventListener"in r?r.addEventListener("change",o):r.addListener(o),i.value=r.matches)});return Vs(()=>{c(),l(),r=void 0}),i}const ln=typeof globalThis<"u"?globalThis:typeof window<"u"?window:typeof global<"u"?global:typeof self<"u"?self:{},cn="__vueuse_ssr_handlers__",ba=va();function va(){return cn in ln||(ln[cn]=ln[cn]||{}),ln[cn]}function ho(e,t){return ba[e]||t}function wa(e){return e==null?"any":e instanceof Set?"set":e instanceof Map?"map":e instanceof Date?"date":typeof e=="boolean"?"boolean":typeof e=="string"?"string":typeof e=="object"?"object":Number.isNaN(e)?"any":"number"}const Ea={boolean:{read:e=>e==="true",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},Vr="vueuse-storage";function Ca(e,t,n,s={}){var r;const{flush:i="pre",deep:o=!0,listenToStorageChanges:l=!0,writeDefaults:c=!0,mergeDefaults:f=!1,shallow:d,window:h=ke,eventFilter:b,onError:T=w=>{console.error(w)},initOnMounted:M}=s,P=(d?hi:fe)(typeof t=="function"?t():t);if(!n)try{n=ho("getDefaultStorage",()=>{var w;return(w=ke)==null?void 0:w.localStorage})()}catch(w){T(w)}if(!n)return P;const k=Ze(t),G=wa(k),z=(r=s.serializer)!=null?r:Ea[G],{pause:g,resume:m}=ga(P,()=>O(P.value),{flush:i,deep:o,eventFilter:b});h&&l&&Ds(()=>{At(h,"storage",D),At(h,Vr,I),M&&D()}),M||D();function L(w,N){h&&h.dispatchEvent(new CustomEvent(Vr,{detail:{key:e,oldValue:w,newValue:N,storageArea:n}}))}function O(w){try{const N=n.getItem(e);if(w==null)L(N,null),n.removeItem(e);else{const S=z.write(w);N!==S&&(n.setItem(e,S),L(N,S))}}catch(N){T(N)}}function U(w){const N=w?w.newValue:n.getItem(e);if(N==null)return c&&k!=null&&n.setItem(e,z.write(k)),k;if(!w&&f){const S=z.read(N);return typeof f=="function"?f(S,k):G==="object"&&!Array.isArray(S)?{...k,...S}:S}else return typeof N!="string"?N:z.read(N)}function D(w){if(!(w&&w.storageArea!==n)){if(w&&w.key==null){P.value=k;return}if(!(w&&w.key!==e)){g();try{(w==null?void 0:w.newValue)!==z.write(P.value)&&(P.value=U(w))}catch(N){T(N)}finally{w?Gt(m):m()}}}}function I(w){D(w.detail)}return P}function po(e){return uo("(prefers-color-scheme: dark)",e)}function Sa(e={}){const{selector:t="html",attribute:n="class",initialValue:s="auto",window:r=ke,storage:i,storageKey:o="vueuse-color-scheme",listenToStorageChanges:l=!0,storageRef:c,emitAuto:f,disableTransition:d=!0}=e,h={auto:"",light:"light",dark:"dark",...e.modes||{}},b=po({window:r}),T=se(()=>b.value?"dark":"light"),M=c||(o==null?ao(s):Ca(o,s,i,{window:r,listenToStorageChanges:l})),P=se(()=>M.value==="auto"?T.value:M.value),k=ho("updateHTMLAttrs",(m,L,O)=>{const U=typeof m=="string"?r==null?void 0:r.document.querySelector(m):fo(m);if(!U)return;let D;if(d&&(D=r.document.createElement("style"),D.appendChild(document.createTextNode("*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}")),r.document.head.appendChild(D)),L==="class"){const I=O.split(/\s/g);Object.values(h).flatMap(w=>(w||"").split(/\s/g)).filter(Boolean).forEach(w=>{I.includes(w)?U.classList.add(w):U.classList.remove(w)})}else U.setAttribute(L,O);d&&(r.getComputedStyle(D).opacity,document.head.removeChild(D))});function G(m){var L;k(t,n,(L=h[m])!=null?L:m)}function z(m){e.onChanged?e.onChanged(m,G):G(m)}Ne(P,z,{flush:"post",immediate:!0}),Ds(()=>z(P.value));const g=se({get(){return f?M.value:P.value},set(m){M.value=m}});try{return Object.assign(g,{store:M,system:T,state:P})}catch{return g}}function xa(e={}){const{valueDark:t="dark",valueLight:n="",window:s=ke}=e,r=Sa({...e,onChanged:(l,c)=>{var f;e.onChanged?(f=e.onChanged)==null||f.call(e,l==="dark",c,l):c(l)},modes:{dark:t,light:n}}),i=se(()=>r.system?r.system.value:po({window:s}).value?"dark":"light");return se({get(){return r.value==="dark"},set(l){const c=l?"dark":"light";i.value===c?r.value="auto":r.value=c}})}function Zn(e){return typeof Window<"u"&&e instanceof Window?e.document.documentElement:typeof Document<"u"&&e instanceof Document?e.documentElement:e}function go(e){const t=window.getComputedStyle(e);if(t.overflowX==="scroll"||t.overflowY==="scroll"||t.overflowX==="auto"&&e.clientWidth1?!0:(t.preventDefault&&t.preventDefault(),!1)}const es=new WeakMap;function Sf(e,t=!1){const n=fe(t);let s=null,r="";Ne(ao(e),l=>{const c=Zn(Ze(l));if(c){const f=c;if(es.get(f)||es.set(f,f.style.overflow),f.style.overflow!=="hidden"&&(r=f.style.overflow),f.style.overflow==="hidden")return n.value=!0;if(n.value)return f.style.overflow="hidden"}},{immediate:!0});const i=()=>{const l=Zn(Ze(e));!l||n.value||(jr&&(s=At(l,"touchmove",c=>{Ta(c)},{passive:!1})),l.style.overflow="hidden",n.value=!0)},o=()=>{const l=Zn(Ze(e));!l||!n.value||(jr&&(s==null||s()),l.style.overflow=r,es.delete(l),n.value=!1)};return Vs(o),se({get(){return n.value},set(l){l?i():o()}})}function xf(e={}){const{window:t=ke,behavior:n="auto"}=e;if(!t)return{x:fe(0),y:fe(0)};const s=fe(t.scrollX),r=fe(t.scrollY),i=se({get(){return s.value},set(l){scrollTo({left:l,behavior:n})}}),o=se({get(){return r.value},set(l){scrollTo({top:l,behavior:n})}});return At(t,"scroll",()=>{s.value=t.scrollX,r.value=t.scrollY},{capture:!1,passive:!0}),{x:i,y:o}}function Tf(e={}){const{window:t=ke,initialWidth:n=Number.POSITIVE_INFINITY,initialHeight:s=Number.POSITIVE_INFINITY,listenOrientation:r=!0,includeScrollbar:i=!0}=e,o=fe(n),l=fe(s),c=()=>{t&&(i?(o.value=t.innerWidth,l.value=t.innerHeight):(o.value=t.document.documentElement.clientWidth,l.value=t.document.documentElement.clientHeight))};if(c(),Ds(c),At("resize",c,{passive:!0}),r){const f=uo("(orientation: portrait)");Ne(f,()=>c())}return{width:o,height:l}}var ts={BASE_URL:"/",MODE:"production",DEV:!1,PROD:!0,SSR:!1},ns={};const mo=/^(?:[a-z]+:|\/\/)/i,Aa="vitepress-theme-appearance",Oa=/#.*$/,Ra=/[?#].*$/,Ia=/(?:(^|\/)index)?\.(?:md|html)$/,de=typeof document<"u",yo={relativePath:"404.md",filePath:"",title:"404",description:"Not Found",headers:[],frontmatter:{sidebar:!1,layout:"page"},lastUpdated:0,isNotFound:!0};function La(e,t,n=!1){if(t===void 0)return!1;if(e=Dr(`/${e}`),n)return new RegExp(t).test(e);if(Dr(t)!==e)return!1;const s=t.match(Oa);return s?(de?location.hash:"")===s[0]:!0}function Dr(e){return decodeURI(e).replace(Ra,"").replace(Ia,"$1")}function Ma(e){return mo.test(e)}function Pa(e,t){return Object.keys((e==null?void 0:e.locales)||{}).find(n=>n!=="root"&&!Ma(n)&&La(t,`/${n}/`,!0))||"root"}function Na(e,t){var s,r,i,o,l,c,f;const n=Pa(e,t);return Object.assign({},e,{localeIndex:n,lang:((s=e.locales[n])==null?void 0:s.lang)??e.lang,dir:((r=e.locales[n])==null?void 0:r.dir)??e.dir,title:((i=e.locales[n])==null?void 0:i.title)??e.title,titleTemplate:((o=e.locales[n])==null?void 0:o.titleTemplate)??e.titleTemplate,description:((l=e.locales[n])==null?void 0:l.description)??e.description,head:bo(e.head,((c=e.locales[n])==null?void 0:c.head)??[]),themeConfig:{...e.themeConfig,...(f=e.locales[n])==null?void 0:f.themeConfig}})}function _o(e,t){const n=t.title||e.title,s=t.titleTemplate??e.titleTemplate;if(typeof s=="string"&&s.includes(":title"))return s.replace(/:title/g,n);const r=Fa(e.title,s);return n===r.slice(3)?n:`${n}${r}`}function Fa(e,t){return t===!1?"":t===!0||t===void 0?` | ${e}`:e===t?"":` | ${t}`}function $a(e,t){const[n,s]=t;if(n!=="meta")return!1;const r=Object.entries(s)[0];return r==null?!1:e.some(([i,o])=>i===n&&o[r[0]]===r[1])}function bo(e,t){return[...e.filter(n=>!$a(t,n)),...t]}const Ha=/[\u0000-\u001F"#$&*+,:;<=>?[\]^`{|}\u007F]/g,ja=/^[a-z]:/i;function Ur(e){const t=ja.exec(e),n=t?t[0]:"";return n+e.slice(n.length).replace(Ha,"_").replace(/(^|\/)_+(?=[^/]*$)/,"$1")}const ss=new Set;function Va(e){if(ss.size===0){const n=typeof process=="object"&&(ns==null?void 0:ns.VITE_EXTRA_EXTENSIONS)||(ts==null?void 0:ts.VITE_EXTRA_EXTENSIONS)||"";("3g2,3gp,aac,ai,apng,au,avif,bin,bmp,cer,class,conf,crl,css,csv,dll,doc,eps,epub,exe,gif,gz,ics,ief,jar,jpe,jpeg,jpg,js,json,jsonld,m4a,man,mid,midi,mjs,mov,mp2,mp3,mp4,mpe,mpeg,mpg,mpp,oga,ogg,ogv,ogx,opus,otf,p10,p7c,p7m,p7s,pdf,png,ps,qt,roff,rtf,rtx,ser,svg,t,tif,tiff,tr,ts,tsv,ttf,txt,vtt,wav,weba,webm,webp,woff,woff2,xhtml,xml,yaml,yml,zip"+(n&&typeof n=="string"?","+n:"")).split(",").forEach(s=>ss.add(s))}const t=e.split(".").pop();return t==null||!ss.has(t.toLowerCase())}const Da=Symbol(),ht=hi(la);function Af(e){const t=se(()=>Na(ht.value,e.data.relativePath)),n=t.value.appearance,s=n==="force-dark"?fe(!0):n?xa({storageKey:Aa,initialValue:()=>typeof n=="string"?n:"auto",...typeof n=="object"?n:{}}):fe(!1),r=fe(de?location.hash:"");return de&&window.addEventListener("hashchange",()=>{r.value=location.hash}),Ne(()=>e.data,()=>{r.value=de?location.hash:""}),{site:t,theme:se(()=>t.value.themeConfig),page:se(()=>e.data),frontmatter:se(()=>e.data.frontmatter),params:se(()=>e.data.params),lang:se(()=>t.value.lang),dir:se(()=>e.data.frontmatter.dir||t.value.dir),localeIndex:se(()=>t.value.localeIndex||"root"),title:se(()=>_o(t.value,e.data)),description:se(()=>e.data.description||t.value.description),isDark:s,hash:se(()=>r.value)}}function Ua(){const e=St(Da);if(!e)throw new Error("vitepress data not properly injected in app");return e}function ka(e,t){return`${e}${t}`.replace(/\/+/g,"/")}function kr(e){return mo.test(e)||!e.startsWith("/")?e:ka(ht.value.base,e)}function Ba(e){let t=e.replace(/\.html$/,"");if(t=decodeURIComponent(t),t=t.replace(/\/$/,"/index"),de){const n="/";t=Ur(t.slice(n.length).replace(/\//g,"_")||"index")+".md";let s=__VP_HASH_MAP__[t.toLowerCase()];if(s||(t=t.endsWith("_index.md")?t.slice(0,-9)+".md":t.slice(0,-3)+"_index.md",s=__VP_HASH_MAP__[t.toLowerCase()]),!s)return null;t=`${n}assets/${t}.${s}.js`}else t=`./${Ur(t.slice(1).replace(/\//g,"_"))}.md.js`;return t}let gn=[];function Of(e){gn.push(e),Fn(()=>{gn=gn.filter(t=>t!==e)})}function Ka(){let e=ht.value.scrollOffset,t=0,n=24;if(typeof e=="object"&&"padding"in e&&(n=e.padding,e=e.selector),typeof e=="number")t=e;else if(typeof e=="string")t=Br(e,n);else if(Array.isArray(e))for(const s of e){const r=Br(s,n);if(r){t=r;break}}return t}function Br(e,t){const n=document.querySelector(e);if(!n)return 0;const s=n.getBoundingClientRect().bottom;return s<0?0:s+t}const Wa=Symbol(),vo="http://a.com",qa=()=>({path:"/",component:null,data:yo});function Rf(e,t){const n=Rn(qa()),s={route:n,go:r};async function r(l=de?location.href:"/"){var c,f;l=rs(l),await((c=s.onBeforeRouteChange)==null?void 0:c.call(s,l))!==!1&&(de&&l!==rs(location.href)&&(history.replaceState({scrollPosition:window.scrollY},""),history.pushState({},"",l)),await o(l),await((f=s.onAfterRouteChanged)==null?void 0:f.call(s,l)))}let i=null;async function o(l,c=0,f=!1){var b;if(await((b=s.onBeforePageLoad)==null?void 0:b.call(s,l))===!1)return;const d=new URL(l,vo),h=i=d.pathname;try{let T=await e(h);if(!T)throw new Error(`Page not found: ${h}`);if(i===h){i=null;const{default:M,__pageData:P}=T;if(!M)throw new Error(`Invalid route component: ${M}`);n.path=de?h:kr(h),n.component=dn(M),n.data=dn(P),de&&Gt(()=>{let k=ht.value.base+P.relativePath.replace(/(?:(^|\/)index)?\.md$/,"$1");if(!ht.value.cleanUrls&&!k.endsWith("/")&&(k+=".html"),k!==d.pathname&&(d.pathname=k,l=k+d.search+d.hash,history.replaceState({},"",l)),d.hash&&!c){let G=null;try{G=document.getElementById(decodeURIComponent(d.hash).slice(1))}catch(z){console.warn(z)}if(G){Kr(G,d.hash);return}}window.scrollTo(0,c)})}}catch(T){if(!/fetch|Page not found/.test(T.message)&&!/^\/404(\.html|\/)?$/.test(l)&&console.error(T),!f)try{const M=await fetch(ht.value.base+"hashmap.json");window.__VP_HASH_MAP__=await M.json(),await o(l,c,!0);return}catch{}if(i===h){i=null,n.path=de?h:kr(h),n.component=t?dn(t):null;const M=de?h.replace(/(^|\/)$/,"$1index").replace(/(\.html)?$/,".md").replace(/^\//,""):"404.md";n.data={...yo,relativePath:M}}}}return de&&(history.state===null&&history.replaceState({},""),window.addEventListener("click",l=>{if(l.target.closest("button"))return;const f=l.target.closest("a");if(f&&!f.closest(".vp-raw")&&(f instanceof SVGElement||!f.download)){const{target:d}=f,{href:h,origin:b,pathname:T,hash:M,search:P}=new URL(f.href instanceof SVGAnimatedString?f.href.animVal:f.href,f.baseURI),k=new URL(location.href);!l.ctrlKey&&!l.shiftKey&&!l.altKey&&!l.metaKey&&!d&&b===k.origin&&Va(T)&&(l.preventDefault(),T===k.pathname&&P===k.search?(M!==k.hash&&(history.pushState({},"",h),window.dispatchEvent(new HashChangeEvent("hashchange",{oldURL:k.href,newURL:h}))),M?Kr(f,M,f.classList.contains("header-anchor")):window.scrollTo(0,0)):r(h))}},{capture:!0}),window.addEventListener("popstate",async l=>{var c;l.state!==null&&(await o(rs(location.href),l.state&&l.state.scrollPosition||0),(c=s.onAfterRouteChanged)==null||c.call(s,location.href))}),window.addEventListener("hashchange",l=>{l.preventDefault()})),s}function Ga(){const e=St(Wa);if(!e)throw new Error("useRouter() is called without provider.");return e}function wo(){return Ga().route}function Kr(e,t,n=!1){let s=null;try{s=e.classList.contains("header-anchor")?e:document.getElementById(decodeURIComponent(t).slice(1))}catch(r){console.warn(r)}if(s){let r=function(){!n||Math.abs(o-window.scrollY)>window.innerHeight?window.scrollTo(0,o):window.scrollTo({left:0,top:o,behavior:"smooth"})};const i=parseInt(window.getComputedStyle(s).paddingTop,10),o=window.scrollY+s.getBoundingClientRect().top-Ka()+i;requestAnimationFrame(r)}}function rs(e){const t=new URL(e,vo);return t.pathname=t.pathname.replace(/(^|\/)index(\.html)?$/,"$1"),ht.value.cleanUrls?t.pathname=t.pathname.replace(/\.html$/,""):!t.pathname.endsWith("/")&&!t.pathname.endsWith(".html")&&(t.pathname+=".html"),t.pathname+t.search+t.hash}const is=()=>gn.forEach(e=>e()),If=Ti({name:"VitePressContent",props:{as:{type:[Object,String],default:"div"}},setup(e){const t=wo(),{site:n}=Ua();return()=>_s(e.as,n.value.contentProps??{style:{position:"relative"}},[t.component?_s(t.component,{onVnodeMounted:is,onVnodeUpdated:is,onVnodeUnmounted:is}):"404 Page Not Found"])}}),Lf=Ti({setup(e,{slots:t}){const n=fe(!1);return Rt(()=>{n.value=!0}),()=>n.value&&t.default?t.default():null}});function Mf(){de&&window.addEventListener("click",e=>{var n;const t=e.target;if(t.matches(".vp-code-group input")){const s=(n=t.parentElement)==null?void 0:n.parentElement;if(!s)return;const r=Array.from(s.querySelectorAll("input")).indexOf(t);if(r<0)return;const i=s.querySelector(".blocks");if(!i)return;const o=Array.from(i.children).find(f=>f.classList.contains("active"));if(!o)return;const l=i.children[r];if(!l||o===l)return;o.classList.remove("active"),l.classList.add("active");const c=s==null?void 0:s.querySelector(`label[for="${t.id}"]`);c==null||c.scrollIntoView({block:"nearest"})}})}function Pf(){if(de){const e=new WeakMap;window.addEventListener("click",t=>{var s;const n=t.target;if(n.matches('div[class*="language-"] > button.copy')){const r=n.parentElement,i=(s=n.nextElementSibling)==null?void 0:s.nextElementSibling;if(!r||!i)return;const o=/language-(shellscript|shell|bash|sh|zsh)/.test(r.className),l=[".vp-copy-ignore",".diff.remove"],c=i.cloneNode(!0);c.querySelectorAll(l.join(",")).forEach(d=>d.remove());let f=c.textContent||"";o&&(f=f.replace(/^ *(\$|>) /gm,"").trim()),za(f).then(()=>{n.classList.add("copied"),clearTimeout(e.get(n));const d=setTimeout(()=>{n.classList.remove("copied"),n.blur(),e.delete(n)},2e3);e.set(n,d)})}})}}async function za(e){try{return navigator.clipboard.writeText(e)}catch{const t=document.createElement("textarea"),n=document.activeElement;t.value=e,t.setAttribute("readonly",""),t.style.contain="strict",t.style.position="absolute",t.style.left="-9999px",t.style.fontSize="12pt";const s=document.getSelection(),r=s?s.rangeCount>0&&s.getRangeAt(0):null;document.body.appendChild(t),t.select(),t.selectionStart=0,t.selectionEnd=e.length,document.execCommand("copy"),document.body.removeChild(t),r&&(s.removeAllRanges(),s.addRange(r)),n&&n.focus()}}function Nf(e,t){let n=!0,s=[];const r=i=>{if(n){n=!1,i.forEach(l=>{const c=os(l);for(const f of document.head.children)if(f.isEqualNode(c)){s.push(f);return}});return}const o=i.map(os);s.forEach((l,c)=>{const f=o.findIndex(d=>d==null?void 0:d.isEqualNode(l??null));f!==-1?delete o[f]:(l==null||l.remove(),delete s[c])}),o.forEach(l=>l&&document.head.appendChild(l)),s=[...s,...o].filter(Boolean)};Ui(()=>{const i=e.data,o=t.value,l=i&&i.description,c=i&&i.frontmatter.head||[],f=_o(o,i);f!==document.title&&(document.title=f);const d=l||o.description;let h=document.querySelector("meta[name=description]");h?h.getAttribute("content")!==d&&h.setAttribute("content",d):os(["meta",{name:"description",content:d}]),r(bo(o.head,Ya(c)))})}function os([e,t,n]){const s=document.createElement(e);for(const r in t)s.setAttribute(r,t[r]);return n&&(s.innerHTML=n),e==="script"&&!t.async&&(s.async=!1),s}function Xa(e){return e[0]==="meta"&&e[1]&&e[1].name==="description"}function Ya(e){return e.filter(t=>!Xa(t))}const ls=new Set,Eo=()=>document.createElement("link"),Ja=e=>{const t=Eo();t.rel="prefetch",t.href=e,document.head.appendChild(t)},Qa=e=>{const t=new XMLHttpRequest;t.open("GET",e,t.withCredentials=!0),t.send()};let an;const Za=de&&(an=Eo())&&an.relList&&an.relList.supports&&an.relList.supports("prefetch")?Ja:Qa;function Ff(){if(!de||!window.IntersectionObserver)return;let e;if((e=navigator.connection)&&(e.saveData||/2g/.test(e.effectiveType)))return;const t=window.requestIdleCallback||setTimeout;let n=null;const s=()=>{n&&n.disconnect(),n=new IntersectionObserver(i=>{i.forEach(o=>{if(o.isIntersecting){const l=o.target;n.unobserve(l);const{pathname:c}=l;if(!ls.has(c)){ls.add(c);const f=Ba(c);f&&Za(f)}}})}),t(()=>{document.querySelectorAll("#app a").forEach(i=>{const{hostname:o,pathname:l}=new URL(i.href instanceof SVGAnimatedString?i.href.animVal:i.href,i.baseURI),c=l.match(/\.\w+$/);c&&c[0]!==".html"||i.target!=="_blank"&&o===location.hostname&&(l!==location.pathname?n.observe(i):ls.add(l))})})};Rt(s);const r=wo();Ne(()=>r.path,s),Fn(()=>{n&&n.disconnect()})}export{_f as $,ff as A,Ml as B,Ka as C,nf as D,of as E,_e as F,hi as G,Of as H,ue as I,sf as J,mo as K,wo as L,yc as M,St as N,Tf as O,Cs as P,Cf as Q,Gt as R,xf as S,so as T,de as U,In as V,Sf as W,ql as X,bf as Y,cf as Z,wf as _,Zi as a,af as a0,df as a1,rf as a2,yf as a3,mf as a4,pf as a5,gf as a6,Nf as a7,Wa as a8,Af as a9,Da as aa,If as ab,Lf as ac,ht as ad,vf as ae,Rf as af,Ba as ag,Ff as ah,Pf as ai,Mf as aj,_s as ak,Ef as al,Yi as b,uf as c,Ti as d,hf as e,Va as f,kr as g,se as h,Ma as i,Qi as j,gi as k,tf as l,La as m,Ss as n,zi as o,ef as p,uo as q,lf as r,fe as s,jo as t,Ua as u,Ne as v,Sl as w,Ui as x,Rt as y,Fn as z}; diff --git a/assets/chunks/theme.Yd2LEGgK.js b/assets/chunks/theme.Yd2LEGgK.js new file mode 100644 index 00000000..4959a739 --- /dev/null +++ b/assets/chunks/theme.Yd2LEGgK.js @@ -0,0 +1 @@ +import{d as _,o as a,c,r as l,n as w,a as D,t as I,b,w as v,e as f,T as de,_ as k,u as ge,i as Ue,f as Ge,g as ve,h as $,j as p,k as r,p as C,l as H,m as z,q as ie,s as T,v as j,x as J,y as R,z as pe,A as ye,B as je,C as ze,D as q,F as M,E,G as Pe,H as ee,I as m,J as K,K as Le,L as te,M as X,N as oe,O as qe,P as Ve,Q as We,R as Ke,S as Se,U as Y,V as Re,W as Ie,X as Te,Y as Je,Z as Ye,$ as Qe,a0 as Xe}from"./framework.CszIUXhs.js";const Ze=_({__name:"VPBadge",props:{text:{},type:{default:"tip"}},setup(o){return(e,t)=>(a(),c("span",{class:w(["VPBadge",e.type])},[l(e.$slots,"default",{},()=>[D(I(e.text),1)])],2))}}),xe={key:0,class:"VPBackdrop"},et=_({__name:"VPBackdrop",props:{show:{type:Boolean}},setup(o){return(e,t)=>(a(),b(de,{name:"fade"},{default:v(()=>[e.show?(a(),c("div",xe)):f("",!0)]),_:1}))}}),tt=k(et,[["__scopeId","data-v-c79a1216"]]),L=ge;function ot(o,e){let t,n=!1;return()=>{t&&clearTimeout(t),n?t=setTimeout(o,e):(o(),(n=!0)&&setTimeout(()=>n=!1,e))}}function le(o){return/^\//.test(o)?o:`/${o}`}function he(o){const{pathname:e,search:t,hash:n,protocol:s}=new URL(o,"http://a.com");if(Ue(o)||o.startsWith("#")||!s.startsWith("http")||!Ge(e))return o;const{site:i}=L(),u=e.endsWith("/")||e.endsWith(".html")?o:o.replace(/(?:(^\.+)\/)?.*$/,`$1${e.replace(/(\.md)?$/,i.value.cleanUrls?"":".html")}${t}${n}`);return ve(u)}function Q({correspondingLink:o=!1}={}){const{site:e,localeIndex:t,page:n,theme:s,hash:i}=L(),u=$(()=>{var d,g;return{label:(d=e.value.locales[t.value])==null?void 0:d.label,link:((g=e.value.locales[t.value])==null?void 0:g.link)||(t.value==="root"?"/":`/${t.value}/`)}});return{localeLinks:$(()=>Object.entries(e.value.locales).flatMap(([d,g])=>u.value.label===g.label?[]:{text:g.label,link:st(g.link||(d==="root"?"/":`/${d}/`),s.value.i18nRouting!==!1&&o,n.value.relativePath.slice(u.value.link.length-1),!e.value.cleanUrls)+i.value})),currentLang:u}}function st(o,e,t,n){return e?o.replace(/\/$/,"")+le(t.replace(/(^|\/)index\.md$/,"$1").replace(/\.md$/,n?".html":"")):o}const nt=o=>(C("data-v-d6be1790"),o=o(),H(),o),at={class:"NotFound"},rt={class:"code"},it={class:"title"},lt=nt(()=>p("div",{class:"divider"},null,-1)),ct={class:"quote"},ut={class:"action"},dt=["href","aria-label"],vt=_({__name:"NotFound",setup(o){const{theme:e}=L(),{currentLang:t}=Q();return(n,s)=>{var i,u,h,d,g;return a(),c("div",at,[p("p",rt,I(((i=r(e).notFound)==null?void 0:i.code)??"404"),1),p("h1",it,I(((u=r(e).notFound)==null?void 0:u.title)??"PAGE NOT FOUND"),1),lt,p("blockquote",ct,I(((h=r(e).notFound)==null?void 0:h.quote)??"But if you don't change your direction, and if you keep looking, you may end up where you are heading."),1),p("div",ut,[p("a",{class:"link",href:r(ve)(r(t).link),"aria-label":((d=r(e).notFound)==null?void 0:d.linkLabel)??"go to home"},I(((g=r(e).notFound)==null?void 0:g.linkText)??"Take me home"),9,dt)])])}}}),pt=k(vt,[["__scopeId","data-v-d6be1790"]]);function we(o,e){if(Array.isArray(o))return Z(o);if(o==null)return[];e=le(e);const t=Object.keys(o).sort((s,i)=>i.split("/").length-s.split("/").length).find(s=>e.startsWith(le(s))),n=t?o[t]:[];return Array.isArray(n)?Z(n):Z(n.items,n.base)}function ht(o){const e=[];let t=0;for(const n in o){const s=o[n];if(s.items){t=e.push(s);continue}e[t]||e.push({items:[]}),e[t].items.push(s)}return e}function ft(o){const e=[];function t(n){for(const s of n)s.text&&s.link&&e.push({text:s.text,link:s.link,docFooterText:s.docFooterText}),s.items&&t(s.items)}return t(o),e}function ce(o,e){return Array.isArray(e)?e.some(t=>ce(o,t)):z(o,e.link)?!0:e.items?ce(o,e.items):!1}function Z(o,e){return[...o].map(t=>{const n={...t},s=n.base||e;return s&&n.link&&(n.link=s+n.link),n.items&&(n.items=Z(n.items,s)),n})}function O(){const{frontmatter:o,page:e,theme:t}=L(),n=ie("(min-width: 960px)"),s=T(!1),i=$(()=>{const B=t.value.sidebar,S=e.value.relativePath;return B?we(B,S):[]}),u=T(i.value);j(i,(B,S)=>{JSON.stringify(B)!==JSON.stringify(S)&&(u.value=i.value)});const h=$(()=>o.value.sidebar!==!1&&u.value.length>0&&o.value.layout!=="home"),d=$(()=>g?o.value.aside==null?t.value.aside==="left":o.value.aside==="left":!1),g=$(()=>o.value.layout==="home"?!1:o.value.aside!=null?!!o.value.aside:t.value.aside!==!1),P=$(()=>h.value&&n.value),y=$(()=>h.value?ht(u.value):[]);function V(){s.value=!0}function N(){s.value=!1}function A(){s.value?N():V()}return{isOpen:s,sidebar:u,sidebarGroups:y,hasSidebar:h,hasAside:g,leftAside:d,isSidebarEnabled:P,open:V,close:N,toggle:A}}function _t(o,e){let t;J(()=>{t=o.value?document.activeElement:void 0}),R(()=>{window.addEventListener("keyup",n)}),pe(()=>{window.removeEventListener("keyup",n)});function n(s){s.key==="Escape"&&o.value&&(e(),t==null||t.focus())}}function mt(o){const{page:e,hash:t}=L(),n=T(!1),s=$(()=>o.value.collapsed!=null),i=$(()=>!!o.value.link),u=T(!1),h=()=>{u.value=z(e.value.relativePath,o.value.link)};j([e,o,t],h),R(h);const d=$(()=>u.value?!0:o.value.items?ce(e.value.relativePath,o.value.items):!1),g=$(()=>!!(o.value.items&&o.value.items.length));J(()=>{n.value=!!(s.value&&o.value.collapsed)}),ye(()=>{(u.value||d.value)&&(n.value=!1)});function P(){s.value&&(n.value=!n.value)}return{collapsed:n,collapsible:s,isLink:i,isActiveLink:u,hasActiveLink:d,hasChildren:g,toggle:P}}function kt(){const{hasSidebar:o}=O(),e=ie("(min-width: 960px)"),t=ie("(min-width: 1280px)");return{isAsideEnabled:$(()=>!t.value&&!e.value?!1:o.value?t.value:e.value)}}const ue=[];function Ne(o){return typeof o.outline=="object"&&!Array.isArray(o.outline)&&o.outline.label||o.outlineTitle||"On this page"}function fe(o){const e=[...document.querySelectorAll(".VPDoc :where(h1,h2,h3,h4,h5,h6)")].filter(t=>t.id&&t.hasChildNodes()).map(t=>{const n=Number(t.tagName[1]);return{element:t,title:bt(t),link:"#"+t.id,level:n}});return $t(e,o)}function bt(o){let e="";for(const t of o.childNodes)if(t.nodeType===1){if(t.classList.contains("VPBadge")||t.classList.contains("header-anchor")||t.classList.contains("ignore-header"))continue;e+=t.textContent}else t.nodeType===3&&(e+=t.textContent);return e.trim()}function $t(o,e){if(e===!1)return[];const t=(typeof e=="object"&&!Array.isArray(e)?e.level:e)||2,[n,s]=typeof t=="number"?[t,t]:t==="deep"?[2,6]:t;o=o.filter(u=>u.level>=n&&u.level<=s),ue.length=0;for(const{element:u,link:h}of o)ue.push({element:u,link:h});const i=[];e:for(let u=0;u=0;d--){const g=o[d];if(g.level{requestAnimationFrame(i),window.addEventListener("scroll",n)}),je(()=>{u(location.hash)}),pe(()=>{window.removeEventListener("scroll",n)});function i(){if(!t.value)return;const h=window.scrollY,d=window.innerHeight,g=document.body.offsetHeight,P=Math.abs(h+d-g)<1,y=ue.map(({element:N,link:A})=>({link:A,top:yt(N)})).filter(({top:N})=>!Number.isNaN(N)).sort((N,A)=>N.top-A.top);if(!y.length){u(null);return}if(h<1){u(null);return}if(P){u(y[y.length-1].link);return}let V=null;for(const{link:N,top:A}of y){if(A>h+ze()+4)break;V=N}u(V)}function u(h){s&&s.classList.remove("active"),h==null?s=null:s=o.value.querySelector(`a[href="${decodeURIComponent(h)}"]`);const d=s;d?(d.classList.add("active"),e.value.style.top=d.offsetTop+39+"px",e.value.style.opacity="1"):(e.value.style.top="33px",e.value.style.opacity="0")}}function yt(o){let e=0;for(;o!==document.body;){if(o===null)return NaN;e+=o.offsetTop,o=o.offsetParent}return e}const Pt=["href","title"],Lt=_({__name:"VPDocOutlineItem",props:{headers:{},root:{type:Boolean}},setup(o){function e({target:t}){const n=t.href.split("#")[1],s=document.getElementById(decodeURIComponent(n));s==null||s.focus({preventScroll:!0})}return(t,n)=>{const s=q("VPDocOutlineItem",!0);return a(),c("ul",{class:w(["VPDocOutlineItem",t.root?"root":"nested"])},[(a(!0),c(M,null,E(t.headers,({children:i,link:u,title:h})=>(a(),c("li",null,[p("a",{class:"outline-link",href:u,onClick:e,title:h},I(h),9,Pt),i!=null&&i.length?(a(),b(s,{key:0,headers:i},null,8,["headers"])):f("",!0)]))),256))],2)}}}),Me=k(Lt,[["__scopeId","data-v-b933a997"]]),Vt={class:"content"},St={"aria-level":"2",class:"outline-title",id:"doc-outline-aria-label",role:"heading"},It=_({__name:"VPDocAsideOutline",setup(o){const{frontmatter:e,theme:t}=L(),n=Pe([]);ee(()=>{n.value=fe(e.value.outline??t.value.outline)});const s=T(),i=T();return gt(s,i),(u,h)=>(a(),c("nav",{"aria-labelledby":"doc-outline-aria-label",class:w(["VPDocAsideOutline",{"has-outline":n.value.length>0}]),ref_key:"container",ref:s},[p("div",Vt,[p("div",{class:"outline-marker",ref_key:"marker",ref:i},null,512),p("div",St,I(r(Ne)(r(t))),1),m(Me,{headers:n.value,root:!0},null,8,["headers"])])],2))}}),Tt=k(It,[["__scopeId","data-v-a5bbad30"]]),wt={class:"VPDocAsideCarbonAds"},Nt=_({__name:"VPDocAsideCarbonAds",props:{carbonAds:{}},setup(o){const e=()=>null;return(t,n)=>(a(),c("div",wt,[m(r(e),{"carbon-ads":t.carbonAds},null,8,["carbon-ads"])]))}}),Mt=o=>(C("data-v-3f215769"),o=o(),H(),o),At={class:"VPDocAside"},Bt=Mt(()=>p("div",{class:"spacer"},null,-1)),Ct=_({__name:"VPDocAside",setup(o){const{theme:e}=L();return(t,n)=>(a(),c("div",At,[l(t.$slots,"aside-top",{},void 0,!0),l(t.$slots,"aside-outline-before",{},void 0,!0),m(Tt),l(t.$slots,"aside-outline-after",{},void 0,!0),Bt,l(t.$slots,"aside-ads-before",{},void 0,!0),r(e).carbonAds?(a(),b(Nt,{key:0,"carbon-ads":r(e).carbonAds},null,8,["carbon-ads"])):f("",!0),l(t.$slots,"aside-ads-after",{},void 0,!0),l(t.$slots,"aside-bottom",{},void 0,!0)]))}}),Ht=k(Ct,[["__scopeId","data-v-3f215769"]]);function Et(){const{theme:o,page:e}=L();return $(()=>{const{text:t="Edit this page",pattern:n=""}=o.value.editLink||{};let s;return typeof n=="function"?s=n(e.value):s=n.replace(/:path/g,e.value.filePath),{url:s,text:t}})}function Ft(){const{page:o,theme:e,frontmatter:t}=L();return $(()=>{var g,P,y,V,N,A,B,S;const n=we(e.value.sidebar,o.value.relativePath),s=ft(n),i=Dt(s,U=>U.link.replace(/[?#].*$/,"")),u=i.findIndex(U=>z(o.value.relativePath,U.link)),h=((g=e.value.docFooter)==null?void 0:g.prev)===!1&&!t.value.prev||t.value.prev===!1,d=((P=e.value.docFooter)==null?void 0:P.next)===!1&&!t.value.next||t.value.next===!1;return{prev:h?void 0:{text:(typeof t.value.prev=="string"?t.value.prev:typeof t.value.prev=="object"?t.value.prev.text:void 0)??((y=i[u-1])==null?void 0:y.docFooterText)??((V=i[u-1])==null?void 0:V.text),link:(typeof t.value.prev=="object"?t.value.prev.link:void 0)??((N=i[u-1])==null?void 0:N.link)},next:d?void 0:{text:(typeof t.value.next=="string"?t.value.next:typeof t.value.next=="object"?t.value.next.text:void 0)??((A=i[u+1])==null?void 0:A.docFooterText)??((B=i[u+1])==null?void 0:B.text),link:(typeof t.value.next=="object"?t.value.next.link:void 0)??((S=i[u+1])==null?void 0:S.link)}}})}function Dt(o,e){const t=new Set;return o.filter(n=>{const s=e(n);return t.has(s)?!1:t.add(s)})}const F=_({__name:"VPLink",props:{tag:{},href:{},noIcon:{type:Boolean},target:{},rel:{}},setup(o){const e=o,t=$(()=>e.tag??(e.href?"a":"span")),n=$(()=>e.href&&Le.test(e.href)||e.target==="_blank");return(s,i)=>(a(),b(K(t.value),{class:w(["VPLink",{link:s.href,"vp-external-link-icon":n.value,"no-icon":s.noIcon}]),href:s.href?r(he)(s.href):void 0,target:s.target??(n.value?"_blank":void 0),rel:s.rel??(n.value?"noreferrer":void 0)},{default:v(()=>[l(s.$slots,"default")]),_:3},8,["class","href","target","rel"]))}}),Ot={class:"VPLastUpdated"},Ut=["datetime"],Gt=_({__name:"VPDocFooterLastUpdated",setup(o){const{theme:e,page:t,frontmatter:n,lang:s}=L(),i=$(()=>new Date(n.value.lastUpdated??t.value.lastUpdated)),u=$(()=>i.value.toISOString()),h=T("");return R(()=>{J(()=>{var d,g,P;h.value=new Intl.DateTimeFormat((g=(d=e.value.lastUpdated)==null?void 0:d.formatOptions)!=null&&g.forceLocale?s.value:void 0,((P=e.value.lastUpdated)==null?void 0:P.formatOptions)??{dateStyle:"short",timeStyle:"short"}).format(i.value)})}),(d,g)=>{var P;return a(),c("p",Ot,[D(I(((P=r(e).lastUpdated)==null?void 0:P.text)||r(e).lastUpdatedText||"Last updated")+": ",1),p("time",{datetime:u.value},I(h.value),9,Ut)])}}}),jt=k(Gt,[["__scopeId","data-v-7e05ebdb"]]),Ae=o=>(C("data-v-d4a0bba5"),o=o(),H(),o),zt={key:0,class:"VPDocFooter"},qt={key:0,class:"edit-info"},Wt={key:0,class:"edit-link"},Kt=Ae(()=>p("span",{class:"vpi-square-pen edit-link-icon"},null,-1)),Rt={key:1,class:"last-updated"},Jt={key:1,class:"prev-next","aria-labelledby":"doc-footer-aria-label"},Yt=Ae(()=>p("span",{class:"visually-hidden",id:"doc-footer-aria-label"},"Pager",-1)),Qt={class:"pager"},Xt=["innerHTML"],Zt=["innerHTML"],xt={class:"pager"},eo=["innerHTML"],to=["innerHTML"],oo=_({__name:"VPDocFooter",setup(o){const{theme:e,page:t,frontmatter:n}=L(),s=Et(),i=Ft(),u=$(()=>e.value.editLink&&n.value.editLink!==!1),h=$(()=>t.value.lastUpdated&&n.value.lastUpdated!==!1),d=$(()=>u.value||h.value||i.value.prev||i.value.next);return(g,P)=>{var y,V,N,A;return d.value?(a(),c("footer",zt,[l(g.$slots,"doc-footer-before",{},void 0,!0),u.value||h.value?(a(),c("div",qt,[u.value?(a(),c("div",Wt,[m(F,{class:"edit-link-button",href:r(s).url,"no-icon":!0},{default:v(()=>[Kt,D(" "+I(r(s).text),1)]),_:1},8,["href"])])):f("",!0),h.value?(a(),c("div",Rt,[m(jt)])):f("",!0)])):f("",!0),(y=r(i).prev)!=null&&y.link||(V=r(i).next)!=null&&V.link?(a(),c("nav",Jt,[Yt,p("div",Qt,[(N=r(i).prev)!=null&&N.link?(a(),b(F,{key:0,class:"pager-link prev",href:r(i).prev.link},{default:v(()=>{var B;return[p("span",{class:"desc",innerHTML:((B=r(e).docFooter)==null?void 0:B.prev)||"Previous page"},null,8,Xt),p("span",{class:"title",innerHTML:r(i).prev.text},null,8,Zt)]}),_:1},8,["href"])):f("",!0)]),p("div",xt,[(A=r(i).next)!=null&&A.link?(a(),b(F,{key:0,class:"pager-link next",href:r(i).next.link},{default:v(()=>{var B;return[p("span",{class:"desc",innerHTML:((B=r(e).docFooter)==null?void 0:B.next)||"Next page"},null,8,eo),p("span",{class:"title",innerHTML:r(i).next.text},null,8,to)]}),_:1},8,["href"])):f("",!0)])])):f("",!0)])):f("",!0)}}}),so=k(oo,[["__scopeId","data-v-d4a0bba5"]]),no=o=>(C("data-v-39a288b8"),o=o(),H(),o),ao={class:"container"},ro=no(()=>p("div",{class:"aside-curtain"},null,-1)),io={class:"aside-container"},lo={class:"aside-content"},co={class:"content"},uo={class:"content-container"},vo={class:"main"},po=_({__name:"VPDoc",setup(o){const{theme:e}=L(),t=te(),{hasSidebar:n,hasAside:s,leftAside:i}=O(),u=$(()=>t.path.replace(/[./]+/g,"_").replace(/_html$/,""));return(h,d)=>{const g=q("Content");return a(),c("div",{class:w(["VPDoc",{"has-sidebar":r(n),"has-aside":r(s)}])},[l(h.$slots,"doc-top",{},void 0,!0),p("div",ao,[r(s)?(a(),c("div",{key:0,class:w(["aside",{"left-aside":r(i)}])},[ro,p("div",io,[p("div",lo,[m(Ht,null,{"aside-top":v(()=>[l(h.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[l(h.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[l(h.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(h.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(h.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(h.$slots,"aside-ads-after",{},void 0,!0)]),_:3})])])],2)):f("",!0),p("div",co,[p("div",uo,[l(h.$slots,"doc-before",{},void 0,!0),p("main",vo,[m(g,{class:w(["vp-doc",[u.value,r(e).externalLinkIcon&&"external-link-icon-enabled"]])},null,8,["class"])]),m(so,null,{"doc-footer-before":v(()=>[l(h.$slots,"doc-footer-before",{},void 0,!0)]),_:3}),l(h.$slots,"doc-after",{},void 0,!0)])])]),l(h.$slots,"doc-bottom",{},void 0,!0)],2)}}}),ho=k(po,[["__scopeId","data-v-39a288b8"]]),fo=_({__name:"VPButton",props:{tag:{},size:{default:"medium"},theme:{default:"brand"},text:{},href:{},target:{},rel:{}},setup(o){const e=o,t=$(()=>e.href&&Le.test(e.href)),n=$(()=>e.tag||e.href?"a":"button");return(s,i)=>(a(),b(K(n.value),{class:w(["VPButton",[s.size,s.theme]]),href:s.href?r(he)(s.href):void 0,target:e.target??(t.value?"_blank":void 0),rel:e.rel??(t.value?"noreferrer":void 0)},{default:v(()=>[D(I(s.text),1)]),_:1},8,["class","href","target","rel"]))}}),_o=k(fo,[["__scopeId","data-v-cad61b99"]]),mo=["src","alt"],ko=_({inheritAttrs:!1,__name:"VPImage",props:{image:{},alt:{}},setup(o){return(e,t)=>{const n=q("VPImage",!0);return e.image?(a(),c(M,{key:0},[typeof e.image=="string"||"src"in e.image?(a(),c("img",X({key:0,class:"VPImage"},typeof e.image=="string"?e.$attrs:{...e.image,...e.$attrs},{src:r(ve)(typeof e.image=="string"?e.image:e.image.src),alt:e.alt??(typeof e.image=="string"?"":e.image.alt||"")}),null,16,mo)):(a(),c(M,{key:1},[m(n,X({class:"dark",image:e.image.dark,alt:e.image.alt},e.$attrs),null,16,["image","alt"]),m(n,X({class:"light",image:e.image.light,alt:e.image.alt},e.$attrs),null,16,["image","alt"])],64))],64)):f("",!0)}}}),x=k(ko,[["__scopeId","data-v-8426fc1a"]]),bo=o=>(C("data-v-303bb580"),o=o(),H(),o),$o={class:"container"},go={class:"main"},yo={key:0,class:"name"},Po=["innerHTML"],Lo=["innerHTML"],Vo=["innerHTML"],So={key:0,class:"actions"},Io={key:0,class:"image"},To={class:"image-container"},wo=bo(()=>p("div",{class:"image-bg"},null,-1)),No=_({__name:"VPHero",props:{name:{},text:{},tagline:{},image:{},actions:{}},setup(o){const e=oe("hero-image-slot-exists");return(t,n)=>(a(),c("div",{class:w(["VPHero",{"has-image":t.image||r(e)}])},[p("div",$o,[p("div",go,[l(t.$slots,"home-hero-info-before",{},void 0,!0),l(t.$slots,"home-hero-info",{},()=>[t.name?(a(),c("h1",yo,[p("span",{innerHTML:t.name,class:"clip"},null,8,Po)])):f("",!0),t.text?(a(),c("p",{key:1,innerHTML:t.text,class:"text"},null,8,Lo)):f("",!0),t.tagline?(a(),c("p",{key:2,innerHTML:t.tagline,class:"tagline"},null,8,Vo)):f("",!0)],!0),l(t.$slots,"home-hero-info-after",{},void 0,!0),t.actions?(a(),c("div",So,[(a(!0),c(M,null,E(t.actions,s=>(a(),c("div",{key:s.link,class:"action"},[m(_o,{tag:"a",size:"medium",theme:s.theme,text:s.text,href:s.link,target:s.target,rel:s.rel},null,8,["theme","text","href","target","rel"])]))),128))])):f("",!0),l(t.$slots,"home-hero-actions-after",{},void 0,!0)]),t.image||r(e)?(a(),c("div",Io,[p("div",To,[wo,l(t.$slots,"home-hero-image",{},()=>[t.image?(a(),b(x,{key:0,class:"image-src",image:t.image},null,8,["image"])):f("",!0)],!0)])])):f("",!0)])],2))}}),Mo=k(No,[["__scopeId","data-v-303bb580"]]),Ao=_({__name:"VPHomeHero",setup(o){const{frontmatter:e}=L();return(t,n)=>r(e).hero?(a(),b(Mo,{key:0,class:"VPHomeHero",name:r(e).hero.name,text:r(e).hero.text,tagline:r(e).hero.tagline,image:r(e).hero.image,actions:r(e).hero.actions},{"home-hero-info-before":v(()=>[l(t.$slots,"home-hero-info-before")]),"home-hero-info":v(()=>[l(t.$slots,"home-hero-info")]),"home-hero-info-after":v(()=>[l(t.$slots,"home-hero-info-after")]),"home-hero-actions-after":v(()=>[l(t.$slots,"home-hero-actions-after")]),"home-hero-image":v(()=>[l(t.$slots,"home-hero-image")]),_:3},8,["name","text","tagline","image","actions"])):f("",!0)}}),Bo=o=>(C("data-v-a3976bdc"),o=o(),H(),o),Co={class:"box"},Ho={key:0,class:"icon"},Eo=["innerHTML"],Fo=["innerHTML"],Do=["innerHTML"],Oo={key:4,class:"link-text"},Uo={class:"link-text-value"},Go=Bo(()=>p("span",{class:"vpi-arrow-right link-text-icon"},null,-1)),jo=_({__name:"VPFeature",props:{icon:{},title:{},details:{},link:{},linkText:{},rel:{},target:{}},setup(o){return(e,t)=>(a(),b(F,{class:"VPFeature",href:e.link,rel:e.rel,target:e.target,"no-icon":!0,tag:e.link?"a":"div"},{default:v(()=>[p("article",Co,[typeof e.icon=="object"&&e.icon.wrap?(a(),c("div",Ho,[m(x,{image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])])):typeof e.icon=="object"?(a(),b(x,{key:1,image:e.icon,alt:e.icon.alt,height:e.icon.height||48,width:e.icon.width||48},null,8,["image","alt","height","width"])):e.icon?(a(),c("div",{key:2,class:"icon",innerHTML:e.icon},null,8,Eo)):f("",!0),p("h2",{class:"title",innerHTML:e.title},null,8,Fo),e.details?(a(),c("p",{key:3,class:"details",innerHTML:e.details},null,8,Do)):f("",!0),e.linkText?(a(),c("div",Oo,[p("p",Uo,[D(I(e.linkText)+" ",1),Go])])):f("",!0)])]),_:1},8,["href","rel","target","tag"]))}}),zo=k(jo,[["__scopeId","data-v-a3976bdc"]]),qo={key:0,class:"VPFeatures"},Wo={class:"container"},Ko={class:"items"},Ro=_({__name:"VPFeatures",props:{features:{}},setup(o){const e=o,t=$(()=>{const n=e.features.length;if(n){if(n===2)return"grid-2";if(n===3)return"grid-3";if(n%3===0)return"grid-6";if(n>3)return"grid-4"}else return});return(n,s)=>n.features?(a(),c("div",qo,[p("div",Wo,[p("div",Ko,[(a(!0),c(M,null,E(n.features,i=>(a(),c("div",{key:i.title,class:w(["item",[t.value]])},[m(zo,{icon:i.icon,title:i.title,details:i.details,link:i.link,"link-text":i.linkText,rel:i.rel,target:i.target},null,8,["icon","title","details","link","link-text","rel","target"])],2))),128))])])])):f("",!0)}}),Jo=k(Ro,[["__scopeId","data-v-a6181336"]]),Yo=_({__name:"VPHomeFeatures",setup(o){const{frontmatter:e}=L();return(t,n)=>r(e).features?(a(),b(Jo,{key:0,class:"VPHomeFeatures",features:r(e).features},null,8,["features"])):f("",!0)}}),Qo=_({__name:"VPHomeContent",setup(o){const{width:e}=qe({initialWidth:0,includeScrollbar:!1});return(t,n)=>(a(),c("div",{class:"vp-doc container",style:Ve(r(e)?{"--vp-offset":`calc(50% - ${r(e)/2}px)`}:{})},[l(t.$slots,"default",{},void 0,!0)],4))}}),Xo=k(Qo,[["__scopeId","data-v-8e2d4988"]]),Zo={class:"VPHome"},xo=_({__name:"VPHome",setup(o){const{frontmatter:e}=L();return(t,n)=>{const s=q("Content");return a(),c("div",Zo,[l(t.$slots,"home-hero-before",{},void 0,!0),m(Ao,null,{"home-hero-info-before":v(()=>[l(t.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(t.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(t.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(t.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(t.$slots,"home-hero-image",{},void 0,!0)]),_:3}),l(t.$slots,"home-hero-after",{},void 0,!0),l(t.$slots,"home-features-before",{},void 0,!0),m(Yo),l(t.$slots,"home-features-after",{},void 0,!0),r(e).markdownStyles!==!1?(a(),b(Xo,{key:0},{default:v(()=>[m(s)]),_:1})):(a(),b(s,{key:1}))])}}}),es=k(xo,[["__scopeId","data-v-686f80a6"]]),ts={},os={class:"VPPage"};function ss(o,e){const t=q("Content");return a(),c("div",os,[l(o.$slots,"page-top"),m(t),l(o.$slots,"page-bottom")])}const ns=k(ts,[["render",ss]]),as=_({__name:"VPContent",setup(o){const{page:e,frontmatter:t}=L(),{hasSidebar:n}=O();return(s,i)=>(a(),c("div",{class:w(["VPContent",{"has-sidebar":r(n),"is-home":r(t).layout==="home"}]),id:"VPContent"},[r(e).isNotFound?l(s.$slots,"not-found",{key:0},()=>[m(pt)],!0):r(t).layout==="page"?(a(),b(ns,{key:1},{"page-top":v(()=>[l(s.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[l(s.$slots,"page-bottom",{},void 0,!0)]),_:3})):r(t).layout==="home"?(a(),b(es,{key:2},{"home-hero-before":v(()=>[l(s.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[l(s.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(s.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(s.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(s.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(s.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[l(s.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[l(s.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[l(s.$slots,"home-features-after",{},void 0,!0)]),_:3})):r(t).layout&&r(t).layout!=="doc"?(a(),b(K(r(t).layout),{key:3})):(a(),b(ho,{key:4},{"doc-top":v(()=>[l(s.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[l(s.$slots,"doc-bottom",{},void 0,!0)]),"doc-footer-before":v(()=>[l(s.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[l(s.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[l(s.$slots,"doc-after",{},void 0,!0)]),"aside-top":v(()=>[l(s.$slots,"aside-top",{},void 0,!0)]),"aside-outline-before":v(()=>[l(s.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(s.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(s.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(s.$slots,"aside-ads-after",{},void 0,!0)]),"aside-bottom":v(()=>[l(s.$slots,"aside-bottom",{},void 0,!0)]),_:3}))],2))}}),rs=k(as,[["__scopeId","data-v-1428d186"]]),is={class:"container"},ls=["innerHTML"],cs=["innerHTML"],us=_({__name:"VPFooter",setup(o){const{theme:e,frontmatter:t}=L(),{hasSidebar:n}=O();return(s,i)=>r(e).footer&&r(t).footer!==!1?(a(),c("footer",{key:0,class:w(["VPFooter",{"has-sidebar":r(n)}])},[p("div",is,[r(e).footer.message?(a(),c("p",{key:0,class:"message",innerHTML:r(e).footer.message},null,8,ls)):f("",!0),r(e).footer.copyright?(a(),c("p",{key:1,class:"copyright",innerHTML:r(e).footer.copyright},null,8,cs)):f("",!0)])],2)):f("",!0)}}),ds=k(us,[["__scopeId","data-v-e315a0ad"]]);function vs(){const{theme:o,frontmatter:e}=L(),t=Pe([]),n=$(()=>t.value.length>0);return ee(()=>{t.value=fe(e.value.outline??o.value.outline)}),{headers:t,hasLocalNav:n}}const ps=o=>(C("data-v-17a5e62e"),o=o(),H(),o),hs={class:"menu-text"},fs=ps(()=>p("span",{class:"vpi-chevron-right icon"},null,-1)),_s={class:"header"},ms={class:"outline"},ks=_({__name:"VPLocalNavOutlineDropdown",props:{headers:{},navHeight:{}},setup(o){const e=o,{theme:t}=L(),n=T(!1),s=T(0),i=T(),u=T();function h(y){var V;(V=i.value)!=null&&V.contains(y.target)||(n.value=!1)}j(n,y=>{if(y){document.addEventListener("click",h);return}document.removeEventListener("click",h)}),We("Escape",()=>{n.value=!1}),ee(()=>{n.value=!1});function d(){n.value=!n.value,s.value=window.innerHeight+Math.min(window.scrollY-e.navHeight,0)}function g(y){y.target.classList.contains("outline-link")&&(u.value&&(u.value.style.transition="none"),Ke(()=>{n.value=!1}))}function P(){n.value=!1,window.scrollTo({top:0,left:0,behavior:"smooth"})}return(y,V)=>(a(),c("div",{class:"VPLocalNavOutlineDropdown",style:Ve({"--vp-vh":s.value+"px"}),ref_key:"main",ref:i},[y.headers.length>0?(a(),c("button",{key:0,onClick:d,class:w({open:n.value})},[p("span",hs,I(r(Ne)(r(t))),1),fs],2)):(a(),c("button",{key:1,onClick:P},I(r(t).returnToTopLabel||"Return to top"),1)),m(de,{name:"flyout"},{default:v(()=>[n.value?(a(),c("div",{key:0,ref_key:"items",ref:u,class:"items",onClick:g},[p("div",_s,[p("a",{class:"top-link",href:"#",onClick:P},I(r(t).returnToTopLabel||"Return to top"),1)]),p("div",ms,[m(Me,{headers:y.headers},null,8,["headers"])])],512)):f("",!0)]),_:1})],4))}}),bs=k(ks,[["__scopeId","data-v-17a5e62e"]]),$s=o=>(C("data-v-a6f0e41e"),o=o(),H(),o),gs={class:"container"},ys=["aria-expanded"],Ps=$s(()=>p("span",{class:"vpi-align-left menu-icon"},null,-1)),Ls={class:"menu-text"},Vs=_({__name:"VPLocalNav",props:{open:{type:Boolean}},emits:["open-menu"],setup(o){const{theme:e,frontmatter:t}=L(),{hasSidebar:n}=O(),{headers:s}=vs(),{y:i}=Se(),u=T(0);R(()=>{u.value=parseInt(getComputedStyle(document.documentElement).getPropertyValue("--vp-nav-height"))}),ee(()=>{s.value=fe(t.value.outline??e.value.outline)});const h=$(()=>s.value.length===0),d=$(()=>h.value&&!n.value),g=$(()=>({VPLocalNav:!0,"has-sidebar":n.value,empty:h.value,fixed:d.value}));return(P,y)=>r(t).layout!=="home"&&(!d.value||r(i)>=u.value)?(a(),c("div",{key:0,class:w(g.value)},[p("div",gs,[r(n)?(a(),c("button",{key:0,class:"menu","aria-expanded":P.open,"aria-controls":"VPSidebarNav",onClick:y[0]||(y[0]=V=>P.$emit("open-menu"))},[Ps,p("span",Ls,I(r(e).sidebarMenuLabel||"Menu"),1)],8,ys)):f("",!0),m(bs,{headers:r(s),navHeight:u.value},null,8,["headers","navHeight"])])],2)):f("",!0)}}),Ss=k(Vs,[["__scopeId","data-v-a6f0e41e"]]);function Is(){const o=T(!1);function e(){o.value=!0,window.addEventListener("resize",s)}function t(){o.value=!1,window.removeEventListener("resize",s)}function n(){o.value?t():e()}function s(){window.outerWidth>=768&&t()}const i=te();return j(()=>i.path,t),{isScreenOpen:o,openScreen:e,closeScreen:t,toggleScreen:n}}const Ts={},ws={class:"VPSwitch",type:"button",role:"switch"},Ns={class:"check"},Ms={key:0,class:"icon"};function As(o,e){return a(),c("button",ws,[p("span",Ns,[o.$slots.default?(a(),c("span",Ms,[l(o.$slots,"default",{},void 0,!0)])):f("",!0)])])}const Bs=k(Ts,[["render",As],["__scopeId","data-v-1d5665e3"]]),Be=o=>(C("data-v-d1f28634"),o=o(),H(),o),Cs=Be(()=>p("span",{class:"vpi-sun sun"},null,-1)),Hs=Be(()=>p("span",{class:"vpi-moon moon"},null,-1)),Es=_({__name:"VPSwitchAppearance",setup(o){const{isDark:e,theme:t}=L(),n=oe("toggle-appearance",()=>{e.value=!e.value}),s=$(()=>e.value?t.value.lightModeSwitchTitle||"Switch to light theme":t.value.darkModeSwitchTitle||"Switch to dark theme");return(i,u)=>(a(),b(Bs,{title:s.value,class:"VPSwitchAppearance","aria-checked":r(e),onClick:r(n)},{default:v(()=>[Cs,Hs]),_:1},8,["title","aria-checked","onClick"]))}}),_e=k(Es,[["__scopeId","data-v-d1f28634"]]),Fs={key:0,class:"VPNavBarAppearance"},Ds=_({__name:"VPNavBarAppearance",setup(o){const{site:e}=L();return(t,n)=>r(e).appearance&&r(e).appearance!=="force-dark"?(a(),c("div",Fs,[m(_e)])):f("",!0)}}),Os=k(Ds,[["__scopeId","data-v-e6aabb21"]]),me=T();let Ce=!1,re=0;function Us(o){const e=T(!1);if(Y){!Ce&&Gs(),re++;const t=j(me,n=>{var s,i,u;n===o.el.value||(s=o.el.value)!=null&&s.contains(n)?(e.value=!0,(i=o.onFocus)==null||i.call(o)):(e.value=!1,(u=o.onBlur)==null||u.call(o))});pe(()=>{t(),re--,re||js()})}return Re(e)}function Gs(){document.addEventListener("focusin",He),Ce=!0,me.value=document.activeElement}function js(){document.removeEventListener("focusin",He)}function He(){me.value=document.activeElement}const zs={class:"VPMenuLink"},qs=_({__name:"VPMenuLink",props:{item:{}},setup(o){const{page:e}=L();return(t,n)=>(a(),c("div",zs,[m(F,{class:w({active:r(z)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,target:t.item.target,rel:t.item.rel},{default:v(()=>[D(I(t.item.text),1)]),_:1},8,["class","href","target","rel"])]))}}),se=k(qs,[["__scopeId","data-v-43f1e123"]]),Ws={class:"VPMenuGroup"},Ks={key:0,class:"title"},Rs=_({__name:"VPMenuGroup",props:{text:{},items:{}},setup(o){return(e,t)=>(a(),c("div",Ws,[e.text?(a(),c("p",Ks,I(e.text),1)):f("",!0),(a(!0),c(M,null,E(e.items,n=>(a(),c(M,null,["link"in n?(a(),b(se,{key:0,item:n},null,8,["item"])):f("",!0)],64))),256))]))}}),Js=k(Rs,[["__scopeId","data-v-69e747b5"]]),Ys={class:"VPMenu"},Qs={key:0,class:"items"},Xs=_({__name:"VPMenu",props:{items:{}},setup(o){return(e,t)=>(a(),c("div",Ys,[e.items?(a(),c("div",Qs,[(a(!0),c(M,null,E(e.items,n=>(a(),c(M,{key:n.text},["link"in n?(a(),b(se,{key:0,item:n},null,8,["item"])):(a(),b(Js,{key:1,text:n.text,items:n.items},null,8,["text","items"]))],64))),128))])):f("",!0),l(e.$slots,"default",{},void 0,!0)]))}}),Zs=k(Xs,[["__scopeId","data-v-e7ea1737"]]),xs=o=>(C("data-v-b6c34ac9"),o=o(),H(),o),en=["aria-expanded","aria-label"],tn={key:0,class:"text"},on=["innerHTML"],sn=xs(()=>p("span",{class:"vpi-chevron-down text-icon"},null,-1)),nn={key:1,class:"vpi-more-horizontal icon"},an={class:"menu"},rn=_({__name:"VPFlyout",props:{icon:{},button:{},label:{},items:{}},setup(o){const e=T(!1),t=T();Us({el:t,onBlur:n});function n(){e.value=!1}return(s,i)=>(a(),c("div",{class:"VPFlyout",ref_key:"el",ref:t,onMouseenter:i[1]||(i[1]=u=>e.value=!0),onMouseleave:i[2]||(i[2]=u=>e.value=!1)},[p("button",{type:"button",class:"button","aria-haspopup":"true","aria-expanded":e.value,"aria-label":s.label,onClick:i[0]||(i[0]=u=>e.value=!e.value)},[s.button||s.icon?(a(),c("span",tn,[s.icon?(a(),c("span",{key:0,class:w([s.icon,"option-icon"])},null,2)):f("",!0),s.button?(a(),c("span",{key:1,innerHTML:s.button},null,8,on)):f("",!0),sn])):(a(),c("span",nn))],8,en),p("div",an,[m(Zs,{items:s.items},{default:v(()=>[l(s.$slots,"default",{},void 0,!0)]),_:3},8,["items"])])],544))}}),ke=k(rn,[["__scopeId","data-v-b6c34ac9"]]),ln=["href","aria-label","innerHTML"],cn=_({__name:"VPSocialLink",props:{icon:{},link:{},ariaLabel:{}},setup(o){const e=o,t=$(()=>typeof e.icon=="object"?e.icon.svg:``);return(n,s)=>(a(),c("a",{class:"VPSocialLink no-icon",href:n.link,"aria-label":n.ariaLabel??(typeof n.icon=="string"?n.icon:""),target:"_blank",rel:"noopener",innerHTML:t.value},null,8,ln))}}),un=k(cn,[["__scopeId","data-v-eee4e7cb"]]),dn={class:"VPSocialLinks"},vn=_({__name:"VPSocialLinks",props:{links:{}},setup(o){return(e,t)=>(a(),c("div",dn,[(a(!0),c(M,null,E(e.links,({link:n,icon:s,ariaLabel:i})=>(a(),b(un,{key:n,icon:s,link:n,ariaLabel:i},null,8,["icon","link","ariaLabel"]))),128))]))}}),be=k(vn,[["__scopeId","data-v-7bc22406"]]),pn={key:0,class:"group translations"},hn={class:"trans-title"},fn={key:1,class:"group"},_n={class:"item appearance"},mn={class:"label"},kn={class:"appearance-action"},bn={key:2,class:"group"},$n={class:"item social-links"},gn=_({__name:"VPNavBarExtra",setup(o){const{site:e,theme:t}=L(),{localeLinks:n,currentLang:s}=Q({correspondingLink:!0}),i=$(()=>n.value.length&&s.value.label||e.value.appearance||t.value.socialLinks);return(u,h)=>i.value?(a(),b(ke,{key:0,class:"VPNavBarExtra",label:"extra navigation"},{default:v(()=>[r(n).length&&r(s).label?(a(),c("div",pn,[p("p",hn,I(r(s).label),1),(a(!0),c(M,null,E(r(n),d=>(a(),b(se,{key:d.link,item:d},null,8,["item"]))),128))])):f("",!0),r(e).appearance&&r(e).appearance!=="force-dark"?(a(),c("div",fn,[p("div",_n,[p("p",mn,I(r(t).darkModeSwitchLabel||"Appearance"),1),p("div",kn,[m(_e)])])])):f("",!0),r(t).socialLinks?(a(),c("div",bn,[p("div",$n,[m(be,{class:"social-links-list",links:r(t).socialLinks},null,8,["links"])])])):f("",!0)]),_:1})):f("",!0)}}),yn=k(gn,[["__scopeId","data-v-d0bd9dde"]]),Pn=o=>(C("data-v-e5dd9c1c"),o=o(),H(),o),Ln=["aria-expanded"],Vn=Pn(()=>p("span",{class:"container"},[p("span",{class:"top"}),p("span",{class:"middle"}),p("span",{class:"bottom"})],-1)),Sn=[Vn],In=_({__name:"VPNavBarHamburger",props:{active:{type:Boolean}},emits:["click"],setup(o){return(e,t)=>(a(),c("button",{type:"button",class:w(["VPNavBarHamburger",{active:e.active}]),"aria-label":"mobile navigation","aria-expanded":e.active,"aria-controls":"VPNavScreen",onClick:t[0]||(t[0]=n=>e.$emit("click"))},Sn,10,Ln))}}),Tn=k(In,[["__scopeId","data-v-e5dd9c1c"]]),wn=["innerHTML"],Nn=_({__name:"VPNavBarMenuLink",props:{item:{}},setup(o){const{page:e}=L();return(t,n)=>(a(),b(F,{class:w({VPNavBarMenuLink:!0,active:r(z)(r(e).relativePath,t.item.activeMatch||t.item.link,!!t.item.activeMatch)}),href:t.item.link,noIcon:t.item.noIcon,target:t.item.target,rel:t.item.rel,tabindex:"0"},{default:v(()=>[p("span",{innerHTML:t.item.text},null,8,wn)]),_:1},8,["class","href","noIcon","target","rel"]))}}),Mn=k(Nn,[["__scopeId","data-v-9c663999"]]),An=_({__name:"VPNavBarMenuGroup",props:{item:{}},setup(o){const e=o,{page:t}=L(),n=i=>"link"in i?z(t.value.relativePath,i.link,!!e.item.activeMatch):i.items.some(n),s=$(()=>n(e.item));return(i,u)=>(a(),b(ke,{class:w({VPNavBarMenuGroup:!0,active:r(z)(r(t).relativePath,i.item.activeMatch,!!i.item.activeMatch)||s.value}),button:i.item.text,items:i.item.items},null,8,["class","button","items"]))}}),Bn=o=>(C("data-v-7f418b0f"),o=o(),H(),o),Cn={key:0,"aria-labelledby":"main-nav-aria-label",class:"VPNavBarMenu"},Hn=Bn(()=>p("span",{id:"main-nav-aria-label",class:"visually-hidden"},"Main Navigation",-1)),En=_({__name:"VPNavBarMenu",setup(o){const{theme:e}=L();return(t,n)=>r(e).nav?(a(),c("nav",Cn,[Hn,(a(!0),c(M,null,E(r(e).nav,s=>(a(),c(M,{key:s.text},["link"in s?(a(),b(Mn,{key:0,item:s},null,8,["item"])):(a(),b(An,{key:1,item:s},null,8,["item"]))],64))),128))])):f("",!0)}}),Fn=k(En,[["__scopeId","data-v-7f418b0f"]]);function Dn(o){const{localeIndex:e,theme:t}=L();function n(s){var A,B,S;const i=s.split("."),u=(A=t.value.search)==null?void 0:A.options,h=u&&typeof u=="object",d=h&&((S=(B=u.locales)==null?void 0:B[e.value])==null?void 0:S.translations)||null,g=h&&u.translations||null;let P=d,y=g,V=o;const N=i.pop();for(const U of i){let G=null;const W=V==null?void 0:V[U];W&&(G=V=W);const ne=y==null?void 0:y[U];ne&&(G=y=ne);const ae=P==null?void 0:P[U];ae&&(G=P=ae),W||(V=G),ne||(y=G),ae||(P=G)}return(P==null?void 0:P[N])??(y==null?void 0:y[N])??(V==null?void 0:V[N])??""}return n}const On=["aria-label"],Un={class:"DocSearch-Button-Container"},Gn=p("span",{class:"vp-icon DocSearch-Search-Icon"},null,-1),jn={class:"DocSearch-Button-Placeholder"},zn=p("span",{class:"DocSearch-Button-Keys"},[p("kbd",{class:"DocSearch-Button-Key"}),p("kbd",{class:"DocSearch-Button-Key"},"K")],-1),$e=_({__name:"VPNavBarSearchButton",setup(o){const t=Dn({button:{buttonText:"Search",buttonAriaLabel:"Search"}});return(n,s)=>(a(),c("button",{type:"button",class:"DocSearch DocSearch-Button","aria-label":r(t)("button.buttonAriaLabel")},[p("span",Un,[Gn,p("span",jn,I(r(t)("button.buttonText")),1)]),zn],8,On))}}),qn={class:"VPNavBarSearch"},Wn={id:"local-search"},Kn={key:1,id:"docsearch"},Rn=_({__name:"VPNavBarSearch",setup(o){const e=()=>null,t=()=>null,{theme:n}=L(),s=T(!1),i=T(!1);R(()=>{});function u(){s.value||(s.value=!0,setTimeout(h,16))}function h(){const P=new Event("keydown");P.key="k",P.metaKey=!0,window.dispatchEvent(P),setTimeout(()=>{document.querySelector(".DocSearch-Modal")||h()},16)}const d=T(!1),g="";return(P,y)=>{var V;return a(),c("div",qn,[r(g)==="local"?(a(),c(M,{key:0},[d.value?(a(),b(r(e),{key:0,onClose:y[0]||(y[0]=N=>d.value=!1)})):f("",!0),p("div",Wn,[m($e,{onClick:y[1]||(y[1]=N=>d.value=!0)})])],64)):r(g)==="algolia"?(a(),c(M,{key:1},[s.value?(a(),b(r(t),{key:0,algolia:((V=r(n).search)==null?void 0:V.options)??r(n).algolia,onVnodeBeforeMount:y[2]||(y[2]=N=>i.value=!0)},null,8,["algolia"])):f("",!0),i.value?f("",!0):(a(),c("div",Kn,[m($e,{onClick:u})]))],64)):f("",!0)])}}}),Jn=_({__name:"VPNavBarSocialLinks",setup(o){const{theme:e}=L();return(t,n)=>r(e).socialLinks?(a(),b(be,{key:0,class:"VPNavBarSocialLinks",links:r(e).socialLinks},null,8,["links"])):f("",!0)}}),Yn=k(Jn,[["__scopeId","data-v-0394ad82"]]),Qn=["href","rel","target"],Xn={key:1},Zn={key:2},xn=_({__name:"VPNavBarTitle",setup(o){const{site:e,theme:t}=L(),{hasSidebar:n}=O(),{currentLang:s}=Q(),i=$(()=>{var d;return typeof t.value.logoLink=="string"?t.value.logoLink:(d=t.value.logoLink)==null?void 0:d.link}),u=$(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.rel}),h=$(()=>{var d;return typeof t.value.logoLink=="string"||(d=t.value.logoLink)==null?void 0:d.target});return(d,g)=>(a(),c("div",{class:w(["VPNavBarTitle",{"has-sidebar":r(n)}])},[p("a",{class:"title",href:i.value??r(he)(r(s).link),rel:u.value,target:h.value},[l(d.$slots,"nav-bar-title-before",{},void 0,!0),r(t).logo?(a(),b(x,{key:0,class:"logo",image:r(t).logo},null,8,["image"])):f("",!0),r(t).siteTitle?(a(),c("span",Xn,I(r(t).siteTitle),1)):r(t).siteTitle===void 0?(a(),c("span",Zn,I(r(e).title),1)):f("",!0),l(d.$slots,"nav-bar-title-after",{},void 0,!0)],8,Qn)],2))}}),ea=k(xn,[["__scopeId","data-v-ab179fa1"]]),ta={class:"items"},oa={class:"title"},sa=_({__name:"VPNavBarTranslations",setup(o){const{theme:e}=L(),{localeLinks:t,currentLang:n}=Q({correspondingLink:!0});return(s,i)=>r(t).length&&r(n).label?(a(),b(ke,{key:0,class:"VPNavBarTranslations",icon:"vpi-languages",label:r(e).langMenuLabel||"Change language"},{default:v(()=>[p("div",ta,[p("p",oa,I(r(n).label),1),(a(!0),c(M,null,E(r(t),u=>(a(),b(se,{key:u.link,item:u},null,8,["item"]))),128))])]),_:1},8,["label"])):f("",!0)}}),na=k(sa,[["__scopeId","data-v-88af2de4"]]),aa=o=>(C("data-v-ccf7ddec"),o=o(),H(),o),ra={class:"wrapper"},ia={class:"container"},la={class:"title"},ca={class:"content"},ua={class:"content-body"},da=aa(()=>p("div",{class:"divider"},[p("div",{class:"divider-line"})],-1)),va=_({__name:"VPNavBar",props:{isScreenOpen:{type:Boolean}},emits:["toggle-screen"],setup(o){const{y:e}=Se(),{hasSidebar:t}=O(),{frontmatter:n}=L(),s=T({});return ye(()=>{s.value={"has-sidebar":t.value,home:n.value.layout==="home",top:e.value===0}}),(i,u)=>(a(),c("div",{class:w(["VPNavBar",s.value])},[p("div",ra,[p("div",ia,[p("div",la,[m(ea,null,{"nav-bar-title-before":v(()=>[l(i.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(i.$slots,"nav-bar-title-after",{},void 0,!0)]),_:3})]),p("div",ca,[p("div",ua,[l(i.$slots,"nav-bar-content-before",{},void 0,!0),m(Rn,{class:"search"}),m(Fn,{class:"menu"}),m(na,{class:"translations"}),m(Os,{class:"appearance"}),m(Yn,{class:"social-links"}),m(yn,{class:"extra"}),l(i.$slots,"nav-bar-content-after",{},void 0,!0),m(Tn,{class:"hamburger",active:i.isScreenOpen,onClick:u[0]||(u[0]=h=>i.$emit("toggle-screen"))},null,8,["active"])])])])]),da],2))}}),pa=k(va,[["__scopeId","data-v-ccf7ddec"]]),ha={key:0,class:"VPNavScreenAppearance"},fa={class:"text"},_a=_({__name:"VPNavScreenAppearance",setup(o){const{site:e,theme:t}=L();return(n,s)=>r(e).appearance&&r(e).appearance!=="force-dark"?(a(),c("div",ha,[p("p",fa,I(r(t).darkModeSwitchLabel||"Appearance"),1),m(_e)])):f("",!0)}}),ma=k(_a,[["__scopeId","data-v-2d7af913"]]),ka=_({__name:"VPNavScreenMenuLink",props:{item:{}},setup(o){const e=oe("close-screen");return(t,n)=>(a(),b(F,{class:"VPNavScreenMenuLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:r(e),innerHTML:t.item.text},null,8,["href","target","rel","onClick","innerHTML"]))}}),ba=k(ka,[["__scopeId","data-v-7f31e1f6"]]),$a=_({__name:"VPNavScreenMenuGroupLink",props:{item:{}},setup(o){const e=oe("close-screen");return(t,n)=>(a(),b(F,{class:"VPNavScreenMenuGroupLink",href:t.item.link,target:t.item.target,rel:t.item.rel,onClick:r(e)},{default:v(()=>[D(I(t.item.text),1)]),_:1},8,["href","target","rel","onClick"]))}}),Ee=k($a,[["__scopeId","data-v-19976ae1"]]),ga={class:"VPNavScreenMenuGroupSection"},ya={key:0,class:"title"},Pa=_({__name:"VPNavScreenMenuGroupSection",props:{text:{},items:{}},setup(o){return(e,t)=>(a(),c("div",ga,[e.text?(a(),c("p",ya,I(e.text),1)):f("",!0),(a(!0),c(M,null,E(e.items,n=>(a(),b(Ee,{key:n.text,item:n},null,8,["item"]))),128))]))}}),La=k(Pa,[["__scopeId","data-v-8133b170"]]),Va=o=>(C("data-v-ff6087d4"),o=o(),H(),o),Sa=["aria-controls","aria-expanded"],Ia=["innerHTML"],Ta=Va(()=>p("span",{class:"vpi-plus button-icon"},null,-1)),wa=["id"],Na={key:1,class:"group"},Ma=_({__name:"VPNavScreenMenuGroup",props:{text:{},items:{}},setup(o){const e=o,t=T(!1),n=$(()=>`NavScreenGroup-${e.text.replace(" ","-").toLowerCase()}`);function s(){t.value=!t.value}return(i,u)=>(a(),c("div",{class:w(["VPNavScreenMenuGroup",{open:t.value}])},[p("button",{class:"button","aria-controls":n.value,"aria-expanded":t.value,onClick:s},[p("span",{class:"button-text",innerHTML:i.text},null,8,Ia),Ta],8,Sa),p("div",{id:n.value,class:"items"},[(a(!0),c(M,null,E(i.items,h=>(a(),c(M,{key:h.text},["link"in h?(a(),c("div",{key:h.text,class:"item"},[m(Ee,{item:h},null,8,["item"])])):(a(),c("div",Na,[m(La,{text:h.text,items:h.items},null,8,["text","items"])]))],64))),128))],8,wa)],2))}}),Aa=k(Ma,[["__scopeId","data-v-ff6087d4"]]),Ba={key:0,class:"VPNavScreenMenu"},Ca=_({__name:"VPNavScreenMenu",setup(o){const{theme:e}=L();return(t,n)=>r(e).nav?(a(),c("nav",Ba,[(a(!0),c(M,null,E(r(e).nav,s=>(a(),c(M,{key:s.text},["link"in s?(a(),b(ba,{key:0,item:s},null,8,["item"])):(a(),b(Aa,{key:1,text:s.text||"",items:s.items},null,8,["text","items"]))],64))),128))])):f("",!0)}}),Ha=_({__name:"VPNavScreenSocialLinks",setup(o){const{theme:e}=L();return(t,n)=>r(e).socialLinks?(a(),b(be,{key:0,class:"VPNavScreenSocialLinks",links:r(e).socialLinks},null,8,["links"])):f("",!0)}}),Fe=o=>(C("data-v-858fe1a4"),o=o(),H(),o),Ea=Fe(()=>p("span",{class:"vpi-languages icon lang"},null,-1)),Fa=Fe(()=>p("span",{class:"vpi-chevron-down icon chevron"},null,-1)),Da={class:"list"},Oa=_({__name:"VPNavScreenTranslations",setup(o){const{localeLinks:e,currentLang:t}=Q({correspondingLink:!0}),n=T(!1);function s(){n.value=!n.value}return(i,u)=>r(e).length&&r(t).label?(a(),c("div",{key:0,class:w(["VPNavScreenTranslations",{open:n.value}])},[p("button",{class:"title",onClick:s},[Ea,D(" "+I(r(t).label)+" ",1),Fa]),p("ul",Da,[(a(!0),c(M,null,E(r(e),h=>(a(),c("li",{key:h.link,class:"item"},[m(F,{class:"link",href:h.link},{default:v(()=>[D(I(h.text),1)]),_:2},1032,["href"])]))),128))])],2)):f("",!0)}}),Ua=k(Oa,[["__scopeId","data-v-858fe1a4"]]),Ga={class:"container"},ja=_({__name:"VPNavScreen",props:{open:{type:Boolean}},setup(o){const e=T(null),t=Ie(Y?document.body:null);return(n,s)=>(a(),b(de,{name:"fade",onEnter:s[0]||(s[0]=i=>t.value=!0),onAfterLeave:s[1]||(s[1]=i=>t.value=!1)},{default:v(()=>[n.open?(a(),c("div",{key:0,class:"VPNavScreen",ref_key:"screen",ref:e,id:"VPNavScreen"},[p("div",Ga,[l(n.$slots,"nav-screen-content-before",{},void 0,!0),m(Ca,{class:"menu"}),m(Ua,{class:"translations"}),m(ma,{class:"appearance"}),m(Ha,{class:"social-links"}),l(n.$slots,"nav-screen-content-after",{},void 0,!0)])],512)):f("",!0)]),_:3}))}}),za=k(ja,[["__scopeId","data-v-cc5739dd"]]),qa={key:0,class:"VPNav"},Wa=_({__name:"VPNav",setup(o){const{isScreenOpen:e,closeScreen:t,toggleScreen:n}=Is(),{frontmatter:s}=L(),i=$(()=>s.value.navbar!==!1);return Te("close-screen",t),J(()=>{Y&&document.documentElement.classList.toggle("hide-nav",!i.value)}),(u,h)=>i.value?(a(),c("header",qa,[m(pa,{"is-screen-open":r(e),onToggleScreen:r(n)},{"nav-bar-title-before":v(()=>[l(u.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(u.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[l(u.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[l(u.$slots,"nav-bar-content-after",{},void 0,!0)]),_:3},8,["is-screen-open","onToggleScreen"]),m(za,{open:r(e)},{"nav-screen-content-before":v(()=>[l(u.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[l(u.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3},8,["open"])])):f("",!0)}}),Ka=k(Wa,[["__scopeId","data-v-ae24b3ad"]]),De=o=>(C("data-v-b8d55f3b"),o=o(),H(),o),Ra=["role","tabindex"],Ja=De(()=>p("div",{class:"indicator"},null,-1)),Ya=De(()=>p("span",{class:"vpi-chevron-right caret-icon"},null,-1)),Qa=[Ya],Xa={key:1,class:"items"},Za=_({__name:"VPSidebarItem",props:{item:{},depth:{}},setup(o){const e=o,{collapsed:t,collapsible:n,isLink:s,isActiveLink:i,hasActiveLink:u,hasChildren:h,toggle:d}=mt($(()=>e.item)),g=$(()=>h.value?"section":"div"),P=$(()=>s.value?"a":"div"),y=$(()=>h.value?e.depth+2===7?"p":`h${e.depth+2}`:"p"),V=$(()=>s.value?void 0:"button"),N=$(()=>[[`level-${e.depth}`],{collapsible:n.value},{collapsed:t.value},{"is-link":s.value},{"is-active":i.value},{"has-active":u.value}]);function A(S){"key"in S&&S.key!=="Enter"||!e.item.link&&d()}function B(){e.item.link&&d()}return(S,U)=>{const G=q("VPSidebarItem",!0);return a(),b(K(g.value),{class:w(["VPSidebarItem",N.value])},{default:v(()=>[S.item.text?(a(),c("div",X({key:0,class:"item",role:V.value},Ye(S.item.items?{click:A,keydown:A}:{},!0),{tabindex:S.item.items&&0}),[Ja,S.item.link?(a(),b(F,{key:0,tag:P.value,class:"link",href:S.item.link,rel:S.item.rel,target:S.item.target},{default:v(()=>[(a(),b(K(y.value),{class:"text",innerHTML:S.item.text},null,8,["innerHTML"]))]),_:1},8,["tag","href","rel","target"])):(a(),b(K(y.value),{key:1,class:"text",innerHTML:S.item.text},null,8,["innerHTML"])),S.item.collapsed!=null&&S.item.items&&S.item.items.length?(a(),c("div",{key:2,class:"caret",role:"button","aria-label":"toggle section",onClick:B,onKeydown:Je(B,["enter"]),tabindex:"0"},Qa,32)):f("",!0)],16,Ra)):f("",!0),S.item.items&&S.item.items.length?(a(),c("div",Xa,[S.depth<5?(a(!0),c(M,{key:0},E(S.item.items,W=>(a(),b(G,{key:W.text,item:W,depth:S.depth+1},null,8,["item","depth"]))),128)):f("",!0)])):f("",!0)]),_:1},8,["class"])}}}),xa=k(Za,[["__scopeId","data-v-b8d55f3b"]]),Oe=o=>(C("data-v-575e6a36"),o=o(),H(),o),er=Oe(()=>p("div",{class:"curtain"},null,-1)),tr={class:"nav",id:"VPSidebarNav","aria-labelledby":"sidebar-aria-label",tabindex:"-1"},or=Oe(()=>p("span",{class:"visually-hidden",id:"sidebar-aria-label"}," Sidebar Navigation ",-1)),sr=_({__name:"VPSidebar",props:{open:{type:Boolean}},setup(o){const{sidebarGroups:e,hasSidebar:t}=O(),n=o,s=T(null),i=Ie(Y?document.body:null);return j([n,s],()=>{var u;n.open?(i.value=!0,(u=s.value)==null||u.focus()):i.value=!1},{immediate:!0,flush:"post"}),(u,h)=>r(t)?(a(),c("aside",{key:0,class:w(["VPSidebar",{open:u.open}]),ref_key:"navEl",ref:s,onClick:h[0]||(h[0]=Qe(()=>{},["stop"]))},[er,p("nav",tr,[or,l(u.$slots,"sidebar-nav-before",{},void 0,!0),(a(!0),c(M,null,E(r(e),d=>(a(),c("div",{key:d.text,class:"group"},[m(xa,{item:d,depth:0},null,8,["item"])]))),128)),l(u.$slots,"sidebar-nav-after",{},void 0,!0)])],2)):f("",!0)}}),nr=k(sr,[["__scopeId","data-v-575e6a36"]]),ar=_({__name:"VPSkipLink",setup(o){const e=te(),t=T();j(()=>e.path,()=>t.value.focus());function n({target:s}){const i=document.getElementById(decodeURIComponent(s.hash).slice(1));if(i){const u=()=>{i.removeAttribute("tabindex"),i.removeEventListener("blur",u)};i.setAttribute("tabindex","-1"),i.addEventListener("blur",u),i.focus(),window.scrollTo(0,0)}}return(s,i)=>(a(),c(M,null,[p("span",{ref_key:"backToTop",ref:t,tabindex:"-1"},null,512),p("a",{href:"#VPContent",class:"VPSkipLink visually-hidden",onClick:n}," Skip to content ")],64))}}),rr=k(ar,[["__scopeId","data-v-0f60ec36"]]),ir=_({__name:"Layout",setup(o){const{isOpen:e,open:t,close:n}=O(),s=te();j(()=>s.path,n),_t(e,n);const{frontmatter:i}=L(),u=Xe(),h=$(()=>!!u["home-hero-image"]);return Te("hero-image-slot-exists",h),(d,g)=>{const P=q("Content");return r(i).layout!==!1?(a(),c("div",{key:0,class:w(["Layout",r(i).pageClass])},[l(d.$slots,"layout-top",{},void 0,!0),m(rr),m(tt,{class:"backdrop",show:r(e),onClick:r(n)},null,8,["show","onClick"]),m(Ka,null,{"nav-bar-title-before":v(()=>[l(d.$slots,"nav-bar-title-before",{},void 0,!0)]),"nav-bar-title-after":v(()=>[l(d.$slots,"nav-bar-title-after",{},void 0,!0)]),"nav-bar-content-before":v(()=>[l(d.$slots,"nav-bar-content-before",{},void 0,!0)]),"nav-bar-content-after":v(()=>[l(d.$slots,"nav-bar-content-after",{},void 0,!0)]),"nav-screen-content-before":v(()=>[l(d.$slots,"nav-screen-content-before",{},void 0,!0)]),"nav-screen-content-after":v(()=>[l(d.$slots,"nav-screen-content-after",{},void 0,!0)]),_:3}),m(Ss,{open:r(e),onOpenMenu:r(t)},null,8,["open","onOpenMenu"]),m(nr,{open:r(e)},{"sidebar-nav-before":v(()=>[l(d.$slots,"sidebar-nav-before",{},void 0,!0)]),"sidebar-nav-after":v(()=>[l(d.$slots,"sidebar-nav-after",{},void 0,!0)]),_:3},8,["open"]),m(rs,null,{"page-top":v(()=>[l(d.$slots,"page-top",{},void 0,!0)]),"page-bottom":v(()=>[l(d.$slots,"page-bottom",{},void 0,!0)]),"not-found":v(()=>[l(d.$slots,"not-found",{},void 0,!0)]),"home-hero-before":v(()=>[l(d.$slots,"home-hero-before",{},void 0,!0)]),"home-hero-info-before":v(()=>[l(d.$slots,"home-hero-info-before",{},void 0,!0)]),"home-hero-info":v(()=>[l(d.$slots,"home-hero-info",{},void 0,!0)]),"home-hero-info-after":v(()=>[l(d.$slots,"home-hero-info-after",{},void 0,!0)]),"home-hero-actions-after":v(()=>[l(d.$slots,"home-hero-actions-after",{},void 0,!0)]),"home-hero-image":v(()=>[l(d.$slots,"home-hero-image",{},void 0,!0)]),"home-hero-after":v(()=>[l(d.$slots,"home-hero-after",{},void 0,!0)]),"home-features-before":v(()=>[l(d.$slots,"home-features-before",{},void 0,!0)]),"home-features-after":v(()=>[l(d.$slots,"home-features-after",{},void 0,!0)]),"doc-footer-before":v(()=>[l(d.$slots,"doc-footer-before",{},void 0,!0)]),"doc-before":v(()=>[l(d.$slots,"doc-before",{},void 0,!0)]),"doc-after":v(()=>[l(d.$slots,"doc-after",{},void 0,!0)]),"doc-top":v(()=>[l(d.$slots,"doc-top",{},void 0,!0)]),"doc-bottom":v(()=>[l(d.$slots,"doc-bottom",{},void 0,!0)]),"aside-top":v(()=>[l(d.$slots,"aside-top",{},void 0,!0)]),"aside-bottom":v(()=>[l(d.$slots,"aside-bottom",{},void 0,!0)]),"aside-outline-before":v(()=>[l(d.$slots,"aside-outline-before",{},void 0,!0)]),"aside-outline-after":v(()=>[l(d.$slots,"aside-outline-after",{},void 0,!0)]),"aside-ads-before":v(()=>[l(d.$slots,"aside-ads-before",{},void 0,!0)]),"aside-ads-after":v(()=>[l(d.$slots,"aside-ads-after",{},void 0,!0)]),_:3}),m(ds),l(d.$slots,"layout-bottom",{},void 0,!0)],2)):(a(),b(P,{key:1}))}}}),lr=k(ir,[["__scopeId","data-v-5d98c3a5"]]),cr={Layout:lr,enhanceApp:({app:o})=>{o.component("Badge",Ze)}},dr={...cr,setup(){const{lang:o}=ge();J(()=>{Y&&(document.cookie=`nf_lang=${o.value}; expires=Mon, 1 Jan 2024 00:00:00 UTC; path=/`)})}};export{dr as R}; diff --git a/assets/en_contributing_index.md.0xRtVBv6.js b/assets/en_contributing_index.md.0xRtVBv6.js new file mode 100644 index 00000000..07d42693 --- /dev/null +++ b/assets/en_contributing_index.md.0xRtVBv6.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"Contributing","description":"","frontmatter":{},"headers":[],"relativePath":"en/contributing/index.md","filePath":"en/contributing/index.md"}'),n={name:"en/contributing/index.md"},a=i('

Contributing

Thank you for being here, this project welcomes your contributions!

Contribution Guide

If you have code or documentation to contribute, here's what you need to know first.

  1. What type of code are you contributing? (new extensions, bug fixes, security issues, project framework optimizations, documentation)
  2. If you contribute new files or new snippets, is your code checked by php-cs-fixer and phpstan?
  3. Have you fully read the Developer Guide before contributing code?

If you can answer the above questions and have made changes to the code, you can initiate a Pull Request in the project GitHub repository in time. After the code review is completed, the code can be modified according to the suggestion, or directly merged into the main branch.

Contribution Type

The main purpose of this project is to compile statically linked PHP binaries, and the command line processing function is written based on symfony/console. Before development, if you are not familiar with it, Check out the symfony/console documentation first.

Security Update

Because this project is basically a PHP project running locally, generally speaking, there will be no remote attacks. But if you find such a problem, please **DO NOT submit a PR or Issue in the GitHub repository, You need to contact the project maintainer (crazywhalecc) via mail.

Fix Bugs

Fixing bugs generally does not involve modification of the project structure and framework, so if you can locate the wrong code and fix it directly, please submit a PR directly.

New Extensions

For adding a new extension, you need to understand some basic structure of the project and how to add a new extension according to the existing logic. It will be covered in detail in the next section on this page. In general, you will need:

  1. Evaluate whether the extension can be compiled inline into PHP.
  2. Evaluate whether the extension's dependent libraries (if any) can be compiled statically.
  3. Write library compile commands on different platforms.
  4. Verify that the extension and its dependencies are compatible with existing extensions and dependencies.
  5. Verify that the extension works normally in cli, micro, fpm, embed SAPIs.
  6. Write documentation and add your extension.

Project Framework Optimization

If you are already familiar with the working principle of symfony/console, and at the same time want to make some modifications or optimizations to the framework of the project, please understand the following things first:

  1. Adding extensions does not belong to project framework optimization, but if you find that you have to optimize the framework when adding new extensions, you need to modify the framework itself before adding extensions.
  2. For some large-scale logical modifications (such as those involving LibraryBase, Extension objects, etc.), it is recommended to submit an Issue or Draft PR for discussion first.
  3. In the early stage of the project, it was a pure private development project, and there were some Chinese comments in the code. After internationalizing your project you can submit a PR to translate these comments into English.
  4. Please do not submit more useless code fragments in the code, such as a large number of unused variables, methods, classes, and code that has been rewritten many times.
',18),r=[a];function s(c,l,d,u,h,m){return o(),t("div",null,r)}const b=e(n,[["render",s]]);export{f as __pageData,b as default}; diff --git a/assets/en_contributing_index.md.0xRtVBv6.lean.js b/assets/en_contributing_index.md.0xRtVBv6.lean.js new file mode 100644 index 00000000..9266688e --- /dev/null +++ b/assets/en_contributing_index.md.0xRtVBv6.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"Contributing","description":"","frontmatter":{},"headers":[],"relativePath":"en/contributing/index.md","filePath":"en/contributing/index.md"}'),n={name:"en/contributing/index.md"},a=i("",18),r=[a];function s(c,l,d,u,h,m){return o(),t("div",null,r)}const b=e(n,[["render",s]]);export{f as __pageData,b as default}; diff --git a/assets/en_develop_doctor-module.md.M_P38WuA.js b/assets/en_develop_doctor-module.md.M_P38WuA.js new file mode 100644 index 00000000..4e7c8e78 --- /dev/null +++ b/assets/en_develop_doctor-module.md.M_P38WuA.js @@ -0,0 +1,29 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Doctor module","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/doctor-module.md","filePath":"en/develop/doctor-module.md"}'),t={name:"en/develop/doctor-module.md"},h=e(`

Doctor module

The Doctor module is a relatively independent module used to check the system environment, which can be entered with the command bin/spc doctor, and the entry command class is in DoctorCommand.php.

The Doctor module is a checklist with a series of check items and automatic repair items. These items are stored in the src/SPC/doctor/item/ directory, And two Attributes are used as check item tags and auto-fix item tags: #[AsCheckItem] and #[AsFixItem].

Take the existing check item if necessary tools are installed, which is used to check whether the packages necessary for compilation are installed in the macOS system. The following is its source code:

php
use SPC\\doctor\\AsCheckItem;
+use SPC\\doctor\\AsFixItem;
+use SPC\\doctor\\CheckResult;
+
+#[AsCheckItem('if necessary tools are installed', limit_os: 'Darwin', level: 997)]
+public function checkCliTools(): ?CheckResult
+{
+    $missing = [];
+    foreach (self::REQUIRED_COMMANDS as $cmd) {
+        if ($this->findCommand($cmd) === null) {
+            $missing[] = $cmd;
+        }
+    }
+    if (!empty($missing)) {
+        return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools', [$missing]);
+    }
+    return CheckResult::ok();
+}

The first parameter of the attribute is the name of the check item, and the following limit_os parameter restricts the check item to be triggered only under the specified system, and level is the priority of executing the check item, the larger the number, the higher the priority higher.

The $this->findCommand() method used in it is the method of SPC\\builder\\traits\\UnixSystemUtilTrait, the purpose is to find the location of the system command, and return NULL if it cannot be found.

Each check item method should return a SPC\\doctor\\CheckResult:

  • When returning CheckResult::fail(), the first parameter is used to output the error prompt of the terminal, and the second parameter is the name of the repair item when this check item can be automatically repaired.
  • When CheckResult::ok() is returned, the check passed. You can also pass a parameter to return the check result, for example: CheckResult::ok('OS supported').
  • When returning CheckResult::fail(), if the third parameter is included, the array of the third parameter will be used as the parameter of AsFixItem.

The following is the method for automatically repairing items corresponding to this check item:

php
#[AsFixItem('build-tools')]
+public function fixBuildTools(array $missing): bool
+{
+    foreach ($missing as $cmd) {
+        try {
+            shell(true)->exec('brew install ' . escapeshellarg($cmd));
+        } catch (RuntimeException) {
+            return false;
+        }
+    }
+    return true;
+}

#[AsFixItem()] first parameter is the name of the fix item, and this method must return True or False. When False is returned, the automatic repair failed and manual handling is required.

In the code here, shell()->exec() is the method of executing commands of the project, which is used to replace exec() and system(), and also provides debugging, obtaining execution status, entering directories, etc. characteristic.

`,13),n=[h];function l(k,p,r,d,o,c){return a(),i("div",null,n)}const y=s(t,[["render",l]]);export{g as __pageData,y as default}; diff --git a/assets/en_develop_doctor-module.md.M_P38WuA.lean.js b/assets/en_develop_doctor-module.md.M_P38WuA.lean.js new file mode 100644 index 00000000..fd7eafc2 --- /dev/null +++ b/assets/en_develop_doctor-module.md.M_P38WuA.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Doctor module","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/doctor-module.md","filePath":"en/develop/doctor-module.md"}'),t={name:"en/develop/doctor-module.md"},h=e("",13),n=[h];function l(k,p,r,d,o,c){return a(),i("div",null,n)}const y=s(t,[["render",l]]);export{g as __pageData,y as default}; diff --git a/assets/en_develop_index.md.BqNiKnHj.js b/assets/en_develop_index.md.BqNiKnHj.js new file mode 100644 index 00000000..a5d9e88e --- /dev/null +++ b/assets/en_develop_index.md.BqNiKnHj.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as n}from"./chunks/framework.CszIUXhs.js";const v=JSON.parse('{"title":"Start Developing","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/index.md","filePath":"en/develop/index.md"}'),i={name:"en/develop/index.md"},s=n('

Start Developing

Developing this project requires the installation and deployment of a PHP environment, as well as some extensions and Composer commonly used in PHP projects.

The development environment and running environment of the project are almost exactly the same. You can refer to the Manual Build section to install system PHP or use the pre-built static PHP of this project as the environment. I will not go into details here.

Regardless of its purpose, this project itself is actually a php-cli program. You can edit and develop it as a normal PHP project. At the same time, you need to understand the Shell languages of different systems.

The current purpose of this project is to compile statically compiled independent PHP, but the main part also includes compiling static versions of many dependent libraries, so you can reuse this set of compilation logic to build independent binary versions of other programs, such as Nginx, etc.

Environment preparation

A PHP environment is required to develop this project. You can use the PHP that comes with the system, or you can use the static PHP built by this project.

Regardless of which PHP you use, in your development environment you need to install these extensions:

curl,dom,filter,mbstring,openssl,pcntl,phar,posix,sodium,tokenizer,xml,xmlwriter

The static-php-cli project itself does not require so many extensions, but during the development process, you will use tools such as Composer and PHPUnit, which require these extensions.

For micro self-executing binaries built by static-php-cli itself, only pcntl,posix,mbstring,tokenizer,phar is required.

Start development

Continuing down to see the project structure documentation, you can learn how static-php-cli works.

',13),a=[s];function r(p,l,c,d,h,u){return o(),t("div",null,a)}const g=e(i,[["render",r]]);export{v as __pageData,g as default}; diff --git a/assets/en_develop_index.md.BqNiKnHj.lean.js b/assets/en_develop_index.md.BqNiKnHj.lean.js new file mode 100644 index 00000000..94b02d15 --- /dev/null +++ b/assets/en_develop_index.md.BqNiKnHj.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as n}from"./chunks/framework.CszIUXhs.js";const v=JSON.parse('{"title":"Start Developing","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/index.md","filePath":"en/develop/index.md"}'),i={name:"en/develop/index.md"},s=n("",13),a=[s];function r(p,l,c,d,h,u){return o(),t("div",null,a)}const g=e(i,[["render",r]]);export{v as __pageData,g as default}; diff --git a/assets/en_develop_source-module.md.CuG52-lh.js b/assets/en_develop_source-module.md.CuG52-lh.js new file mode 100644 index 00000000..19be7b6b --- /dev/null +++ b/assets/en_develop_source-module.md.CuG52-lh.js @@ -0,0 +1,105 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"Source module","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/source-module.md","filePath":"en/develop/source-module.md"}'),t={name:"en/develop/source-module.md"},n=e(`

Source module

The download source module of static-php-cli is a major module. It includes dependent libraries, external extensions, PHP source code download methods and file decompression methods. The download configuration file mainly involves the source.json and pkg.json file, which records the download method of all downloadable sources.

The main commands involved in the download function are bin/spc download and bin/spc extract. The download command is a downloader that downloads sources according to the configuration file, and the extract command is an extractor that extract sources from downloaded files.

Generally speaking, downloading sources may be slow because these sources come from various official websites, GitHub, and other different locations. At the same time, they also occupy a large space, so you can download the sources once and reuse them.

The configuration file of the downloader is source.json, which contains the download methods of all sources. You can add the source download methods you need, or modify the existing source download methods.

The download configuration structure of each source is as follows. The following is the source download configuration corresponding to the libevent extension:

json
{
+  "libevent": {
+    "type": "ghrel",
+    "repo": "libevent/libevent",
+    "match": "libevent.+\\\\.tar\\\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

The most important field here is type. Currently, the types it supports are:

  • url: Directly use URL to download, for example: https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz.
  • ghrel: Use the GitHub Release API to download, download the artifacts uploaded from the latest version released by maintainers.
  • ghtar: Use the GitHub Release API to download. Different from ghrel, ghtar is downloaded from the source code (tar.gz) in the latest Release of the project.
  • ghtagtar: Use GitHub Release API to download. Compared with ghtar, ghtagtar can find the latest one from the tags list and download the source code in tar.gz format (because some projects only use tag release version).
  • bitbuckettag: Download using BitBucket API, basically the same as ghtagtar, except this one applies to BitBucket.
  • git: Clone the project directly from a Git address to download sources, applicable to any public Git repository.
  • filelist: Use a crawler to crawl the Web download site that provides file index, and get the latest version of the file name and download it.
  • custom: If none of the above download methods are satisfactory, you can write custom, create a new class under src/SPC/store/source/, extends CustomSourceBase, and write the download script yourself.

source.json Common parameters

Each source file in source.json has the following params:

  • license: the open source license of the source code, see Open Source License section below
  • type: must be one of the types mentioned above
  • path (optional): release the source code to the specified directory instead of source/{name}

TIP

The path parameter in source.json can specify a relative or absolute path. When specified as a relative path, the path is based on source/.

Download type - url

URL type sources refer to downloading files directly from the URL.

The parameters included are:

  • url: The download address of the file, such as https://example.com/file.tgz
  • filename (optional): The file name saved to the local area. If not specified, the file name of the url will be used.

Example (download the imagick extension and extract it to the extension storage path of the php source code):

json
{
+   "ext-imagick": {
+     "type": "url",
+     "url": "https://pecl.php.net/get/imagick",
+     "path": "php-src/ext/imagick",
+     "filename": "imagick.tgz",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghrel

ghrel will download files from Assets uploaded in GitHub Release. First use the GitHub Release API to get the latest version, and then download the corresponding files according to the regular matching method.

The parameters included are:

  • repo: GitHub repository name
  • match: regular expression matching Assets files
  • prefer-stable: Whether to download stable versions first (default is false)

Example (download the libsodium library, matching the libsodium-x.y.tar.gz file in Release):

json
{
+   "libsodium": {
+     "type": "ghrel",
+     "repo": "jedisct1/libsodium",
+     "match": "libsodium-\\\\d+(\\\\.\\\\d+)*\\\\.tar\\\\.gz",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghtar

ghtar will download the file from the GitHub Release Tag. Unlike ghrel, ghtar will download the source code (tar.gz) from the latest Release of the project.

The parameters included are:

  • repo: GitHub repository name
  • prefer-stable: Whether to download stable versions first (default is false)

Example (brotli library):

json
{
+   "brotli": {
+     "type": "ghtar",
+     "repo": "google/brotli",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghtagtar

Use the GitHub Release API to download. Compared with ghtar, ghtagtar can find the latest one from the tags list and download the source code in tar.gz format (because some projects only use the tag version).

The parameters included are:

  • repo: GitHub repository name
  • prefer-stable: Whether to download stable versions first (default is false)

Example (gmp library):

json
{
+   "gmp": {
+     "type": "ghtagtar",
+     "repo": "alisw/GMP",
+     "license": {
+       "type": "text",
+       "text": "EXAMPLE LICENSE"
+     }
+   }
+}

Download Type - bitbuckettag

Download using BitBucket API, basically the same as ghtagtar, except this one works with BitBucket.

The parameters included are:

  • repo: BitBucket repository name

Download type - git

Clone the project directly from a Git address to download sources, applicable to any public Git repository.

The parameters included are:

  • url: Git link (HTTPS only)
  • rev: branch name
json
{
+   "imap": {
+     "type": "git",
+     "url": "https://github.com/static-php/imap.git",
+     "rev": "master",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - filelist

Use a crawler to crawl a web download site that provides a file index and get the latest version of the file name and download it.

Note that this method is only applicable to static sites with page index functions such as mirror sites and GNU official websites.

The parameters included are:

  • url: The URL of the page to crawl the latest version of the file
  • regex: regular expression matching file names and download links

Example (download the libiconv library from the GNU official website):

json
{
+   "libiconv": {
+     "type": "filelist",
+     "url": "https://ftp.gnu.org/gnu/libiconv/",
+     "regex": "/href=\\"(?<file>libiconv-(?<version>[^\\"]+)\\\\.tar\\\\.gz)\\"/",
+     "license": {
+       "type": "file",
+       "path": "COPYING"
+     }
+   }
+}

Download type - custom

If the above downloading methods are not satisfactory, you can write custom, create a new class under src/SPC/store/source/, extends CustomSourceBase, and write the download script yourself.

I won’t go into details here, you can look at src/SPC/store/source/PhpSource.php or src/SPC/store/source/PostgreSQLSource.php as examples.

pkg.json General parameters

pkg.json stores non-source-code files, such as precompiled tools musl-toolchain and UPX. It includes:

  • type: The same type as source.json and different kinds of parameters.
  • extract (optional): The path to decompress after downloading, the default is pkgroot/{pkg_name}.
  • extract-files (optional): Extract only the specified files to the specified location after downloading.

It should be noted that pkg.json does not involve compilation, modification and distribution of source code, so there is no license open source license field. And you cannot use the extract and extract-files parameters at the same time.

Example (download nasm locally and extract only program files to PHP SDK):

json
{
+   "nasm-x86_64-win": {
+     "type": "url",
+     "url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
+     "extract-files": {
+       "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
+       "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
+     }
+   }
+}

The key name in extract-files is the file in the source folder, and the key value is the storage path. The storage path can use the following variables:

  • {php_sdk_path}: (Windows only) PHP SDK path
  • {pkg_root_path}: pkgroot/
  • {working_dir}: current working directory
  • {download_path}: download directory
  • {source_path}: source code decompression directory

When extract-files does not use variables and is a relative path, the directory of the relative path is {working_dir}.

Open source license

For source.json, each source file should contain an open source license. The license field stores the open source license information.

Each license contains the following parameters:

  • type: file or text
  • path: the license file in the source code directory (required when type is file)
  • text: License text (required when type is text)

Example (yaml extension source code with LICENSE file):

json
{
+   "yaml": {
+     "type": "git",
+     "path": "php-src/ext/yaml",
+     "rev": "php7",
+     "url": "https://github.com/php/pecl-file_formats-yaml",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

When an open source project has multiple licenses, multiple files can be specified:

json
{
+   "libuv": {
+     "type": "ghtar",
+     "repo": "libuv/libuv",
+     "license": [
+       {
+         "type": "file",
+         "path": "LICENSE"
+       },
+       {
+         "type": "file",
+         "path": "LICENSE-extra"
+       }
+     ]
+   }
+}
`,73),l=[n];function h(p,o,d,k,r,c){return a(),i("div",null,l)}const g=s(t,[["render",h]]);export{u as __pageData,g as default}; diff --git a/assets/en_develop_source-module.md.CuG52-lh.lean.js b/assets/en_develop_source-module.md.CuG52-lh.lean.js new file mode 100644 index 00000000..419fd5f0 --- /dev/null +++ b/assets/en_develop_source-module.md.CuG52-lh.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"Source module","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/source-module.md","filePath":"en/develop/source-module.md"}'),t={name:"en/develop/source-module.md"},n=e("",73),l=[n];function h(p,o,d,k,r,c){return a(),i("div",null,l)}const g=s(t,[["render",h]]);export{u as __pageData,g as default}; diff --git a/assets/en_develop_structure.md.wZEZWbru.js b/assets/en_develop_structure.md.wZEZWbru.js new file mode 100644 index 00000000..ea94f143 --- /dev/null +++ b/assets/en_develop_structure.md.wZEZWbru.js @@ -0,0 +1,10 @@ +import{_ as e,c as o,o as t,a1 as c}from"./chunks/framework.CszIUXhs.js";const y=JSON.parse('{"title":"Introduction to project structure","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/structure.md","filePath":"en/develop/structure.md"}'),d={name:"en/develop/structure.md"},a=c(`

Introduction to project structure

static-php-cli mainly contains three logical components: sources, dependent libraries, and extensions. These components contains 4 configuration files: source.json, pkg.json, lib.json, and ext.json.

A complete process for building standalone static PHP is:

  1. Use the source download module Downloader to download specified or all source codes. These sources include PHP source code, dependent library source code, and extension source code.
  2. Use the source decompression module SourceExtractor to decompress the downloaded sources to the compilation directory.
  3. Use the dependency tool to calculate the dependent extensions and dependent libraries of the currently added extension, and then compile each library that needs to be compiled in the order of dependencies.
  4. After building each dependent library using Builder under the corresponding operating system, install it to the buildroot directory.
  5. If external extensions are included (the source code does not contain extensions within PHP), copy the external extensions to the source/php-src/ext/ directory.
  6. Use Builder to build the PHP source code and build target to the buildroot directory.

The project is mainly divided into several folders:

  • bin/: used to store program entry files, including bin/spc, bin/spc-alpine-docker, bin/setup-runtime.
  • config/: Contains all the extensions and dependent libraries supported by the project, as well as the download link and download methods of these sources. It is divided into files: lib.json, ext.json, source.json, pkg.json .
  • src/: The core code of the project, including the entire framework and commands for compiling various extensions and libraries.
  • vendor/: The directory that Composer depends on, you do not need to make any modifications to it.

The operating principle is to start a ConsoleApplication of symfony/console, and then parse the commands entered by the user in the terminal.

Basic command line structure

bin/spc is an entry file, including the Unix common #!/usr/bin/env php, which is used to allow the system to automatically execute with the PHP interpreter installed on the system. After the project executes new ConsoleApplication(), the framework will automatically register them as commands.

The project does not directly use the Command registration method and command execution method recommended by Symfony. Here are small changes:

  1. Each command uses the #[AsCommand()] Attribute to register the name and description.
  2. Abstract execute() so that all commands are based on BaseCommand (which is based on Symfony\\Component\\Console\\Command\\Command), and the execution code of each command itself is written in the handle() method .
  3. Added variable $no_motd to BaseCommand, which is used to display the Figlet greeting when the command is executed.
  4. BaseCommand saves InputInterface and OutputInterface as member variables. You can use $this->input and $this->output within the command class.

Basic source code structure

The source code of the project is located in the src/SPC directory, supports automatic loading of the PSR-4 standard, and contains the following subdirectories and classes:

  • src/SPC/builder/: The core compilation command code used to build libraries, PHP and related extensions under different operating systems, and also includes some compilation system tool methods.
  • src/SPC/command/: All commands of the project are here.
  • src/SPC/doctor/: Doctor module, which is a relatively independent module used to check the system environment. It can be entered using the command bin/spc doctor.
  • src/SPC/exception/: exception class.
  • src/SPC/store/: Classes related to storage, files and sources are all here.
  • src/SPC/util/: Some reusable tool methods are here.
  • src/SPC/ConsoleApplication.php: command line program entry file.

If you have read the source code, you may find that there is also a src/globals/ directory, which is used to store some global variables, global methods, and non-PSR-4 standard PHP source code that is relied upon during the build process, such as extension sanity check code etc.

Phar application directory issue

Like other php-cli projects, spc itself has additional considerations for paths. Because spc can run in multiple modes such as php-cli directly, micro SAPI, php-cli with Phar, vendor with Phar, etc., there are ambiguities in various root directories. A complete explanation is given here. This problem is generally common in the base class path selection problem of accessing files in PHP projects, especially when used with micro.sfx.

Note that this may only be useful for you when developing Phar projects or PHP frameworks.

Next, we will treat static-php-cli (that is, spc) as a normal php command line program. You can understand spc as any of your own php-cli applications for reference.

There are three basic constant theoretical values below. We recommend that you introduce these three constants when writing PHP projects:

  • WORKING_DIR: the working directory when executing PHP scripts

  • SOURCE_ROOT_DIR or ROOT_DIR: the root directory of the project folder, generally the directory where composer.json is located

  • FRAMEWORK_ROOT_DIR: the root directory of the framework used, which may be used by self-developed frameworks. Generally, the framework directory is read-only

You can define these constants in your framework entry or cli applications to facilitate the use of paths in your project.

The following are PHP built-in constant values, which have been defined inside the PHP interpreter:

  • __DIR__: the directory where the file of the currently executed script is located

  • __FILE__: the file path of the currently executed script

Git project mode (source)

Git project mode refers to a framework or program itself stored in plain text in the current folder, and running through php path/to/entry.php.

Assume that your project is stored in the /home/example/static-php-cli/ directory, or your project is the framework itself, which contains project files such as composer.json:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

We assume that the above constants are obtained from src/App/MyCommand.php:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIR/home/example/static-php-cli
FRAMEWORK_ROOT_DIR/home/example/static-php-cli
__DIR__/home/example/static-php-cli/src/App
__FILE__/home/example/static-php-cli/src/App/MyCommand.php

In this case, the values of WORKING_DIR, SOURCE_ROOT_DIR, and FRAMEWORK_ROOT_DIR are exactly the same: /home/example/static-php-cli.

The source code of the framework and the source code of the application are both in the current path.

Vendor library mode (vendor)

The vendor library mode generally means that your project is a framework or is installed into the project as a composer dependency by other applications, and the storage location is in the vendor/author/XXX directory.

Suppose your project is crazywhalecc/static-php-cli, and you or others install this project in another project using composer require.

We assume that static-php-cli contains all files except the vendor directory with the same Git mode, and get the constant value from src/App/MyCommand, Directory constant should be:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIR/home/example/another-app
FRAMEWORK_ROOT_DIR/home/example/another-app/vendor/crazywhalecc/static-php-cli
__DIR__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App
__FILE__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

Here SOURCE_ROOT_DIR refers to the root directory of the project using static-php-cli.

Git project Phar mode (source-phar)

Git project Phar mode refers to the mode of packaging the project directory of the Git project mode into a phar file. We assume that /home/example/static-php-cli will be packaged into a Phar file, and the directory has the following files:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

When packaged into app.phar and stored in the /home/example/static-php-cli directory, app.phar is executed at this time. Assuming that the src/App/MyCommand code is executed, the constant is obtained in the file:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
__DIR__phar:///home/example/static-php-cli/app.phar/src/App
__FILE__phar:///home/example/static-php-cli/app.phar/src/App/MyCommand.php

Because the phar:// protocol is required to read files in the phar itself, the project root directory and the framework directory will be different from WORKING_DIR.

Vendor Library Phar Mode (vendor-phar)

Vendor Library Phar Mode means that your project is installed as a framework in other projects and stored in the vendor directory.

We assume that your project directory structure is as follows:

composer.json                           # Composer configuration file of the current project
+box.json                                # Configuration file for packaging Phar
+another-app.php                         # Entry file of another project
+vendor/crazywhalecc/static-php-cli/*    # Your project is used as a dependent library

When packaging these files under the directory /home/example/another-app/ into app.phar, the value of the following constant for your project should be:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIRphar:///home/example/another-app/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli
__DIR__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App
__FILE__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php
`,50),r=[a];function i(s,n,p,l,h,m){return t(),o("div",null,r)}const b=e(d,[["render",i]]);export{y as __pageData,b as default}; diff --git a/assets/en_develop_structure.md.wZEZWbru.lean.js b/assets/en_develop_structure.md.wZEZWbru.lean.js new file mode 100644 index 00000000..d5d7c91d --- /dev/null +++ b/assets/en_develop_structure.md.wZEZWbru.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t,a1 as c}from"./chunks/framework.CszIUXhs.js";const y=JSON.parse('{"title":"Introduction to project structure","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/structure.md","filePath":"en/develop/structure.md"}'),d={name:"en/develop/structure.md"},a=c("",50),r=[a];function i(s,n,p,l,h,m){return t(),o("div",null,r)}const b=e(d,[["render",i]]);export{y as __pageData,b as default}; diff --git a/assets/en_develop_system-build-tools.md.Ds5Kgdf6.js b/assets/en_develop_system-build-tools.md.Ds5Kgdf6.js new file mode 100644 index 00000000..1aedb481 --- /dev/null +++ b/assets/en_develop_system-build-tools.md.Ds5Kgdf6.js @@ -0,0 +1,68 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Compilation Tools","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/system-build-tools.md","filePath":"en/develop/system-build-tools.md"}'),n={name:"en/develop/system-build-tools.md"},t=e(`

Compilation Tools

static-php-cli uses many system compilation tools when building static PHP. These tools mainly include:

  • autoconf: used to generate configure scripts.
  • make: used to execute Makefile.
  • cmake: used to execute CMakeLists.txt.
  • pkg-config: Used to find the installation path of dependent libraries.
  • gcc: used to compile C/C++ projects under Linux.
  • clang: used to compile C/C++ projects under macOS.

For Linux and macOS operating systems, these tools can usually be installed through the package manager, which is written in the doctor module. Theoretically we can also compile and download these tools manually, but this will increase the complexity of compilation, so we do not recommend this.

Linux Compilation Tools

For Linux systems, different distributions have different installation methods for compilation tools. And for static compilation, the package management of some distributions cannot install libraries and tools for pure static compilation. Therefore, for the Linux platform and its different distributions, we currently provide a variety of compilation environment preparations.

Glibc Environment

The glibc environment refers to the underlying libc library of the system (that is, the C standard library that all programs written in C language are dynamically linked to) uses glibc, which is the default environment for most distributions. For example: Ubuntu, Debian, CentOS, RHEL, openSUSE, Arch Linux, etc.

In the glibc environment, the package management and compiler we use point to glibc by default, and glibc cannot be statically linked well. One of the reasons it cannot be statically linked is that its network library nss cannot be compiled statically.

For the glibc environment, in static-php-cli and spc in 2.0-RC8 and later, you can choose two ways to build static PHP:

  1. Use Docker to build, you can use bin/spc-alpine-docker to build, it will build an Alpine Linux docker image.
  2. Use bin/spc doctor --auto-fix to install the musl-wrapper and musl-cross-make packages, and then build directly. (Related source code)

Generally speaking, the build results in these two environments are consistent, and you can choose according to actual needs.

In the doctor module, static-php-cli will first detect the current Linux distribution. If the current distribution is a glibc environment, you will be prompted to install the musl-wrapper and musl-cross-make packages.

The process of installing musl-wrapper in the glibc environment is as follows:

  1. Download the specific version of musl-wrapper source code from the musl official website.
  2. Use gcc installed from the package management to compile the musl-wrapper source code and generate musl-libc and other libraries: ./configure --disable-gcc-wrapper && make -j && sudo make install.
  3. The musl-wrapper related libraries will be installed in the /usr/local/musl directory.

The process of installing musl-cross-make in the glibc environment is as follows:

  1. Download the precompiled musl-cross-make compressed package from dl.static-php.dev .
  2. Unzip to the /usr/local/musl directory.

TIP

In the glibc environment, static compilation can be achieved by directly installing musl-wrapper, but musl-wrapper only contains musl-gcc and not musl-g++, which means that C++ code cannot be compiled. So we need musl-cross-make to provide musl-g++.

The reason why the musl-cross-make package cannot be compiled directly locally is that its compilation environment requirements are relatively high (requires more than 36GB of memory, compiled under Alpine Linux), so we provide precompiled binary packages that can be used for all Linux distributions.

At the same time, the package management of some distributions provides musl-wrapper, but musl-cross-make needs to match the corresponding musl-wrapper version, so we do not use package management to install musl-wrapper.

Compiling musl-cross-make will be introduced in the musl-cross-make Toolchain Compilation section of this chapter.

Musl Environment

The musl environment refers to the system's underlying libc library that uses musl, which is a lightweight C standard library that can be well statically linked.

For the currently popular Linux distributions, Alpine Linux uses the musl environment, so static-php-cli can directly build static PHP under Alpine Linux. You only need to install basic compilation tools (such as gcc, cmake, etc.) directly from the package management.

For other distributions, if your distribution uses the musl environment, you can also use static-php-cli to build static PHP directly after installing the necessary compilation tools.

TIP

In the musl environment, static-php-cli will automatically skip the installation of musl-wrapper and musl-cross-make.

Docker Environment

The Docker environment refers to using Docker containers to build static PHP. You can use bin/spc-alpine-docker to build. Before executing this command, you need to install Docker first, and then execute bin/spc-alpine-docker in the project root directory.

After executing bin/spc-alpine-docker, static-php-cli will automatically download the Alpine Linux image and then build a cwcc-spc-x86_64 or cwcc-spc-aarch64 image. Then all build process is performed within this image, which is equivalent to compiling in Alpine Linux.

musl-cross-make Toolchain Compilation

In Linux, although you do not need to manually compile the musl-cross-make tool, if you want to understand its compilation process, you can refer here. Another important reason is that this may not be compiled using automated tools such as CI and Actions, because the existing CI service compilation environment does not meet the compilation requirements of musl-cross-make, and the configuration that meets the requirements is too expensive.

The compilation process of musl-cross-make is as follows:

Prepare an Alpine Linux environment (either directly installed or using Docker). The compilation process requires more than 36GB of memory, so you need to compile on a machine with larger memory. Without this much memory, compilation may fail.

Then write the following content into the config.mak file:

makefile
STAT = -static --static
+FLAG = -g0 -Os -Wno-error
+
+ifneq ($(NATIVE),)
+COMMON_CONFIG += CC="$(HOST)-gcc \${STAT}" CXX="$(HOST)-g++ \${STAT}"
+else
+COMMON_CONFIG += CC="gcc \${STAT}" CXX="g++ \${STAT}"
+endif
+
+COMMON_CONFIG += CFLAGS="\${FLAG}" CXXFLAGS="\${FLAG}" LDFLAGS="\${STAT}"
+
+BINUTILS_CONFIG += --enable-gold=yes --enable-gprofng=no
+GCC_CONFIG += --enable-static-pie --disable-cet --enable-default-pie  
+#--enable-default-pie
+
+CONFIG_SUB_REV = 888c8e3d5f7b
+GCC_VER = 13.2.0
+BINUTILS_VER = 2.40
+MUSL_VER = 1.2.4
+GMP_VER = 6.2.1
+MPC_VER = 1.2.1
+MPFR_VER = 4.2.0
+LINUX_VER = 6.1.36

And also you need to add gcc-13.2.0.tar.xz.sha1 file, contents here:

5f95b6d042fb37d45c6cbebfc91decfbc4fb493c  gcc-13.2.0.tar.xz

If you are using Docker to build, create a new Dockerfile file and write the following content:

dockerfile
FROM alpine:edge
+
+RUN apk add --no-cache \\
+gcc g++ git make curl perl \\
+rsync patch wget libtool \\
+texinfo autoconf automake \\
+bison tar xz bzip2 zlib \\
+file binutils flex \\
+linux-headers libintl \\
+gettext gettext-dev icu-libs pkgconf \\
+pkgconfig icu-dev bash \\
+ccache libarchive-tools zip
+
+WORKDIR /opt
+
+RUN git clone https://git.zv.io/toolchains/musl-cross-make.git
+WORKDIR /opt/musl-cross-make
+COPY config.mak /opt/musl-cross-make
+COPY gcc-13.2.0.tar.xz.sha1 /opt/musl-cross-make/hashes
+
+RUN make TARGET=x86_64-linux-musl -j || :
+RUN sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+RUN make TARGET=x86_64-linux-musl -j
+RUN make TARGET=x86_64-linux-musl install -j
+RUN tar cvzf x86_64-musl-toolchain.tgz output/*

If you are using Alpine Linux in a non-Docker environment, you can directly execute the commands in the Dockerfile, for example:

bash
apk add --no-cache \\
+gcc g++ git make curl perl \\
+rsync patch wget libtool \\
+texinfo autoconf automake \\
+bison tar xz bzip2 zlib \\
+file binutils flex \\
+linux-headers libintl \\
+gettext gettext-dev icu-libs pkgconf \\
+pkgconfig icu-dev bash \\
+ccache libarchive-tools zip
+
+git clone https://git.zv.io/toolchains/musl-cross-make.git
+# Copy config.mak to the working directory of musl-cross-make.
+# You need to replace /path/to/config.mak with your config.mak file path.
+cp /path/to/config.mak musl-cross-make/
+cp /path/to/gcc-13.2.0.tar.xz.sha1 musl-cross-make/hashes
+
+make TARGET=x86_64-linux-musl -j || :
+sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+make TARGET=x86_64-linux-musl -j
+make TARGET=x86_64-linux-musl install -j
+tar cvzf x86_64-musl-toolchain.tgz output/*

TIP

All the above scripts are suitable for x86_64 architecture Linux. If you need to build musl-cross-make for the ARM environment, just replace all x86_64 above with aarch64.

This compilation process may fail due to insufficient memory, network problems, etc. You can try a few more times, or use a machine with larger memory to compile. If you encounter problems or you have better improvement solutions, go to Discussion.

macOS Environment

For macOS systems, the main compilation tool we use is clang, which is the default compiler for macOS systems and is also the compiler of Xcode.

Compiling under macOS mainly relies on Xcode or Xcode Command Line Tools. You can download Xcode from the App Store, or execute xcode-select --install in the terminal to install Xcode Command Line Tools.

In addition, in the doctor environment check module, static-php-cli will check whether Homebrew, compilation tools, etc. are installed on the macOS system. If not, you will be prompted to install them. I will not go into details here.

FreeBSD Environment

FreeBSD is also a Unix system, and its compilation tools are similar to macOS. You can directly use the package management pkg to install clang and other compilation tools through the doctor command.

pkg-config Compilation (*nix only)

If you observe the compilation log when using static-php-cli to build static PHP, you will find that no matter what is compiled, pkg-config will be compiled first. This is because pkg-config is a library used to find dependencies. In earlier versions of static-php-cli, we directly used the pkg-config tool installed by package management, but this would cause some problems, such as:

  • Even if PKG_CONFIG_PATH is specified, pkg-config will try to find dependent packages from the system path.
  • Since pkg-config will look for dependent packages from the system path, if a dependent package with the same name exists in the system, compilation may fail.

In order to avoid the above problems, we compile pkg-config into buildroot/bin in user mode and use it. We use parameters such as --without-sysroot to avoid looking for dependent packages from the system path.

`,50),l=[t];function o(p,h,c,r,k,d){return a(),i("div",null,l)}const E=s(n,[["render",o]]);export{g as __pageData,E as default}; diff --git a/assets/en_develop_system-build-tools.md.Ds5Kgdf6.lean.js b/assets/en_develop_system-build-tools.md.Ds5Kgdf6.lean.js new file mode 100644 index 00000000..2724c392 --- /dev/null +++ b/assets/en_develop_system-build-tools.md.Ds5Kgdf6.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Compilation Tools","description":"","frontmatter":{},"headers":[],"relativePath":"en/develop/system-build-tools.md","filePath":"en/develop/system-build-tools.md"}'),n={name:"en/develop/system-build-tools.md"},t=e("",50),l=[t];function o(p,h,c,r,k,d){return a(),i("div",null,l)}const E=s(n,[["render",o]]);export{g as __pageData,E as default}; diff --git a/assets/en_faq_index.md.DM_hczmb.js b/assets/en_faq_index.md.DM_hczmb.js new file mode 100644 index 00000000..60a4491a --- /dev/null +++ b/assets/en_faq_index.md.DM_hczmb.js @@ -0,0 +1,2 @@ +import{_ as e,c as o,o as t,a1 as i}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"FAQ","description":"","frontmatter":{},"headers":[],"relativePath":"en/faq/index.md","filePath":"en/faq/index.md"}'),a={name:"en/faq/index.md"},s=i(`

FAQ

Here will be some questions that you may encounter easily. There are currently many, but I need to take time to organize them.

Can statically compiled PHP install extensions?

Because the principle of installing extensions in PHP under the traditional architecture is to install new extensions using .so type dynamic link libraries, and statically linked PHP compiled using this project cannot directly install new extensions using dynamic link libraries.

For the macOS platform, almost all binary files under macOS cannot be linked purely statically, and almost all binary files will link macOS system libraries: /usr/lib/libresolv.9.dylib and /usr/lib/libSystem.B.dylib. So under macOS system, statically compiled php binary files can be used under certain compilation conditions, and dynamic link extensions can be used at the same time:

  1. Using the --no-strip parameter will not strip information such as debugging symbols from the binary file for use with external Zend extensions such as Xdebug.
  2. If you want to compile some Zend extensions, use Homebrew, MacPorts, source code compilation, and install a normal version of PHP on your operating system.
  3. Use the phpize && ./configure && make command to compile the extensions you want to use.
  4. Copy the extension file xxxx.so to the outside, use the statically compiled PHP binary, for example to use the Xdebug extension: cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so".
bash
# build statically linked php-cli but not stripped
+bin/spc build ffi --build-cli --no-strip

For the Linux platform, the current compilation result is a purely statically linked binary file, and new extensions cannot be installed using a dynamic link library.

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 static-php-cli from source, so this project may never support them. However, in theory, you can access and use such extensions under macOS according to the above questions.

If you have a need for such extensions, or most people have needs for these closed-source extensions, see the discussion on standalone-php-cli. Welcome to leave a message.

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 (static-php-cli) 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

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 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.

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.

`,24),n=[s];function r(c,l,d,p,h,u){return t(),o("div",null,n)}const y=e(a,[["render",r]]);export{f as __pageData,y as default}; diff --git a/assets/en_faq_index.md.DM_hczmb.lean.js b/assets/en_faq_index.md.DM_hczmb.lean.js new file mode 100644 index 00000000..9d9753f2 --- /dev/null +++ b/assets/en_faq_index.md.DM_hczmb.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t,a1 as i}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"FAQ","description":"","frontmatter":{},"headers":[],"relativePath":"en/faq/index.md","filePath":"en/faq/index.md"}'),a={name:"en/faq/index.md"},s=i("",24),n=[s];function r(c,l,d,p,h,u){return t(),o("div",null,n)}const y=e(a,[["render",r]]);export{f as __pageData,y as default}; diff --git a/assets/en_guide_action-build.md.DqfXKtKF.js b/assets/en_guide_action-build.md.DqfXKtKF.js new file mode 100644 index 00000000..38e7682d --- /dev/null +++ b/assets/en_guide_action-build.md.DqfXKtKF.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"GitHub Action Build","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/action-build.md","filePath":"en/guide/action-build.md"}'),n={name:"en/guide/action-build.md"},a=i('

GitHub Action Build

Action Build refers to compiling directly using GitHub Action.

If you don't want to compile it yourself, you can download the artifact from the existing Action in this project, or you can download it from a self-hosted server:Enter.

Self-hosted binaries are also built from Actions: repo. The extensions included are: bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gmp,iconv,xml,mbstring,mbregex,mysqlnd,openssl, pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,redis,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip

Build Guide

Using GitHub Action makes it easy to build a statically compiled PHP and phpmicro, while also defining the extensions to compile.

  1. Fork project.
  2. Go to the Actions of the project and select CI.
  3. Select Run workflow, fill in the PHP version you want to compile, the target type, and the list of extensions. (extensions comma separated, e.g. bcmath,curl,mbstring)
  4. After waiting for about a period of time, enter the corresponding task and get Artifacts.

If you enable debug, all logs will be output at build time, including compiled logs, for troubleshooting.

If you need to build in other environments, you can use manual build.

Extensions

You can go to extensions check here to see if all the extensions you need currently support. and then go to command generator select the extension you need to compile, copy the extensions string to extensions option.

',11),l=[a];function s(r,c,d,u,p,h){return o(),t("div",null,l)}const f=e(n,[["render",s]]);export{b as __pageData,f as default}; diff --git a/assets/en_guide_action-build.md.DqfXKtKF.lean.js b/assets/en_guide_action-build.md.DqfXKtKF.lean.js new file mode 100644 index 00000000..a51cdd0c --- /dev/null +++ b/assets/en_guide_action-build.md.DqfXKtKF.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"GitHub Action Build","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/action-build.md","filePath":"en/guide/action-build.md"}'),n={name:"en/guide/action-build.md"},a=i("",11),l=[a];function s(r,c,d,u,p,h){return o(),t("div",null,l)}const f=e(n,[["render",s]]);export{b as __pageData,f as default}; diff --git a/assets/en_guide_build-on-windows.md.Bw1buXoR.js b/assets/en_guide_build-on-windows.md.Bw1buXoR.js new file mode 100644 index 00000000..32260e0d --- /dev/null +++ b/assets/en_guide_build-on-windows.md.Bw1buXoR.js @@ -0,0 +1,23 @@ +import{_ as e,c as i,o as s,a1 as a}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"Build on Windows","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/build-on-windows.md","filePath":"en/guide/build-on-windows.md"}'),t={name:"en/guide/build-on-windows.md"},o=a(`

Build on Windows

Because the Windows system is an NT kernel, the compilation tools and operating system interfaces used by Unix-like operating systems are almost completely different, so the build process on Windows will be slightly different from that of Unix systems.

GitHub Actions Build

Building the Windows version of static-php from Actions is now supported. Like Linux and macOS, you need to Fork the static-php-cli repository to your GitHub account first, then you can enter Extension List to select the extension to be compiled, and then go to your own CI on Windows select the PHP version, fill in the extension list (comma separated), and click Run.

If you're going to develop or build locally, please read on.

Requirements

The tools required to build static PHP on Windows are the same as PHP's official Windows build tools. You can read Official Documentation.

To sum up, you need the following environment and tools:

  • Windows 10/11 (requires build 17063 or later)
  • Visual Studio 2019/2022 (recommended 2022)
  • C++ desktop development for Visual Studio
  • Git for Windows
  • php-sdk-binary-tools (can be installed automatically using doctor)
  • strawberry-perl (can be installed automatically using doctor)
  • nasm (can be installed automatically using doctor)

TIP

The construction of static-php-cli on Windows refers to using MSVC to build PHP and is not based on MinGW, Cygwin, WSL and other environments.

If you prefer to use WSL, please refer to the chapter on Building on Linux.

After installing Visual Studio and selecting the C++ desktop development workload, you may download about 8GB of compilation tools, and the download speed depends on your network conditions.

Install Git

Git for Windows can be downloaded and installed from here Standalone Installer 64-bit version, installed in the default location (C:\\Program Files\\Git\\). If you don't want to download and install manually, you can also use Visual Studio Installer and check Git in the Individual component tab.

Prepare static-php-cli

Downloading the static-php-cli project is very simple, just use git clone. It is recommended to place the project in C:\\spc-build\\ or a similar directory. It is best not to have spaces in the path.

shell
mkdir "C:\\spc-build"
+cd C:\\spc-build
+git clone https://github.com/crazywhalecc/static-php-cli.git
+cd static-php-cli

It is a bit strange that static-php-cli itself requires a PHP environment, but now you can quickly install the PHP environment through a script. Generally, your computer will not have the Windows version of PHP installed, so we recommend that you use bin/setup-runtime directly after downloading static-php-cli to install PHP and Composer in the current directory.

shell
# Install PHP and Composer to the ./runtime/ directory
+bin/setup-runtime
+
+# After installation, if you need to use PHP and Composer in global commands, 
+# use the following command to add the runtime/ directory to PATH
+bin/setup-runtime -action add-path
+
+# Delete the runtime/ directory in PATH
+bin/setup-runtime -action remove-path

Install other Tools (automatic)

For php-sdk-binary-tools, strawberry-perl, and nasm, we recommend that you directly use the command bin/spc doctor to check and install them.

If doctor successfully installs automatically, please skip the steps below to manually install the above tools.

But if the automatic installation fails, please refer to the manual installation method below.

Install php-sdk-binary-tools (manual)

shell
cd C:\\spc-build\\static-php-cli
+git clone https://github.com/php/php-sdk-binary-tools.git

You can also set the global variable PHP_SDK_PATH in Windows settings and clone the project to the path corresponding to the variable. Under normal circumstances, you don't need to change it.

Install strawberry-perl (manual)

If you don't need to compile the openssl extension, you don't need to install perl.

  1. Download the latest version of strawberry-perl from GitHub.
  2. Install to the C:\\spc-build\\static-php-cli\\pkgroot\\perl\\ directory.

You can download the -portable version and extract it directly to the above directory. The last perl.exe should be located at C:\\spc-build\\static-php-cli\\pkgroot\\perl\\perl\\bin\\perl.exe.

Install nasm (manual)

If you don't need to compile openssl extension, you don't need to install nasm.

  1. Download the nasm tool (x64) from official website.
  2. Place nasm.exe and ndisasm.exe in the C:\\spc-build\\static-php-cli\\php-sdk-binary-tools\\bin\\ directory.

Download required sources

Same as Manual build - Download

Build PHP

Use the build command to start building the static php binary. Before executing the bin/spc build command, be sure to use the download command to download sources. It is recommended to use doctor to check the environment.

Build SAPI

You need to go to Extension List or Command Generator to select the extension you want to add, and then use the command bin/spc build to compile. You need to specify targets, choose from the following parameters (at least one):

  • --build-cli: Build a cli sapi (command line interface, which can execute PHP code on the command line)
  • --build-micro: Build a micro sapi (used to build a standalone executable binary containing PHP code)
shell
# Compile PHP with bcmath,openssl,zlib extensions, the compilation target is cli
+bin/spc build "bcmath,openssl,zlib" --build-cli
+
+# Compile PHP with phar,curl,posix,pcntl,tokenizer extensions, compile target is micro and cli
+bin/spc build "bcmath,openssl,zlib" --build-micro --build-cli

WARNING

In Windows, it is best to use double quotes to wrap parameters containing commas, such as "bcmath,openssl,mbstring".

Debug

If you encounter problems during the compilation process, or want to view each executing shell command, you can use --debug to enable debug mode and view all terminal logs:

shell
bin/spc build "openssl" --build-cli --debug

Build Options

During the compilation process, in some special cases, the compiler and the content of the compilation directory need to be intervened. You can try to use the following commands:

  • --with-clean: clean up old make files before compiling PHP
  • --enable-zts: Make compiled PHP thread-safe version (default is NTS version)
  • --with-libs=XXX,YYY: Compile the specified dependent library before compiling PHP, and activate some extension optional functions
  • -I xxx=yyy: Hard compile INI options into PHP before compiling (support multiple options, alias is --with-hardcoded-ini)
  • --with-micro-fake-cli: When compiling micro, let micro's PHP_SAPI pretend to be cli (for compatibility with some programs that check PHP_SAPI)
  • --disable-opcache-jit: Disable opcache jit (enabled by default)
  • --without-micro-ext-test: After building micro.sfx, do not test the running results of different extensions in micro.sfx
  • --with-suggested-exts: Add ext-suggests as dependencies when compiling
  • --with-suggested-libs: Add lib-suggests as dependencies when compiling
  • --with-upx-pack: Use UPX to reduce the size of the binary file after compilation (you need to use bin/spc install-pkg upx to install upx first)
  • --with-micro-logo=XXX.ico: Customize the icon of the exe executable file after customizing the micro build (in the format of .ico)

Here is a simple example where we preset a larger memory_limit and disable the system function:

shell
bin/spc build "bcmath,openssl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"

Another example: Customize our hello-world.exe program logo:

shell
bin/spc build "ffi,bcmath" --build-micro --with-micro-logo=mylogo.ico --debug
+bin/spc micro:combine hello.php
+# Then we got \`my-app.exe\` with custom logo!
+my-app.exe

Use php.exe

After php.exe is compiled, it is located in the buildroot\\bin\\ directory. You can copy it to any location for use.

shell
.\\php -v

Use micro.sfx

phpmicro is a SelF-extracted eXecutable SAPI module, provided by phpmicro project. But this project is using a fork of phpmicro, because we need to add some features to it. It can put php runtime and your source code together.

The final compilation result will output a file named ./micro.sfx, which needs to be used with your PHP source code like code.php. This file will be located in the path buildroot/bin/micro.sfx.

Prepare your project source code, which can be a single PHP file or a Phar file, for use.

If you want to combine phar files, you must add phar extension when compiling!

shell
# code.php "<?php echo 'Hello world' . PHP_EOL;"
+bin/spc micro:combine code.php -O my-app.exe
+# Run it!!! Copy it to another computer!!!
+./my-app.exe

If you package a PHAR file, just replace code.php with the phar file path. You can use box-project/box to package your CLI project as Phar, It is then combined with phpmicro to produce a standalone executable binary.

For more details on the micro:combine command, refer to command on Unix systems.

`,62),l=[o];function n(p,d,h,r,c,u){return s(),i("div",null,l)}const b=e(t,[["render",n]]);export{m as __pageData,b as default}; diff --git a/assets/en_guide_build-on-windows.md.Bw1buXoR.lean.js b/assets/en_guide_build-on-windows.md.Bw1buXoR.lean.js new file mode 100644 index 00000000..4358bc92 --- /dev/null +++ b/assets/en_guide_build-on-windows.md.Bw1buXoR.lean.js @@ -0,0 +1 @@ +import{_ as e,c as i,o as s,a1 as a}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"Build on Windows","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/build-on-windows.md","filePath":"en/guide/build-on-windows.md"}'),t={name:"en/guide/build-on-windows.md"},o=a("",62),l=[o];function n(p,d,h,r,c,u){return s(),i("div",null,l)}const b=e(t,[["render",n]]);export{m as __pageData,b as default}; diff --git a/assets/en_guide_cli-generator.md.B6SIOY9P.js b/assets/en_guide_cli-generator.md.B6SIOY9P.js new file mode 100644 index 00000000..8ba1c38c --- /dev/null +++ b/assets/en_guide_cli-generator.md.B6SIOY9P.js @@ -0,0 +1 @@ +import{C as e}from"./chunks/CliGenerator.Bj1S5l8x.js";import{d as t,c as a,I as o,a1 as n,o as r}from"./chunks/framework.CszIUXhs.js";const i=n('

CLI Build Command Generator

TIP

The extensions selected below may contain extensions that are not supported by the selected operating system, which may cause compilation to fail. Please check Supported Extensions first.

',2),u=JSON.parse('{"title":"CLI Build Command Generator","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/cli-generator.md","filePath":"en/guide/cli-generator.md"}'),s={name:"en/guide/cli-generator.md"},h=t({...s,setup(c){return(l,d)=>(r(),a("div",null,[i,o(e,{lang:"en"})]))}});export{u as __pageData,h as default}; diff --git a/assets/en_guide_cli-generator.md.B6SIOY9P.lean.js b/assets/en_guide_cli-generator.md.B6SIOY9P.lean.js new file mode 100644 index 00000000..8ba1c38c --- /dev/null +++ b/assets/en_guide_cli-generator.md.B6SIOY9P.lean.js @@ -0,0 +1 @@ +import{C as e}from"./chunks/CliGenerator.Bj1S5l8x.js";import{d as t,c as a,I as o,a1 as n,o as r}from"./chunks/framework.CszIUXhs.js";const i=n('

CLI Build Command Generator

TIP

The extensions selected below may contain extensions that are not supported by the selected operating system, which may cause compilation to fail. Please check Supported Extensions first.

',2),u=JSON.parse('{"title":"CLI Build Command Generator","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/cli-generator.md","filePath":"en/guide/cli-generator.md"}'),s={name:"en/guide/cli-generator.md"},h=t({...s,setup(c){return(l,d)=>(r(),a("div",null,[i,o(e,{lang:"en"})]))}});export{u as __pageData,h as default}; diff --git a/assets/en_guide_env-vars.md.XRLVeMgw.js b/assets/en_guide_env-vars.md.XRLVeMgw.js new file mode 100644 index 00000000..ad870ba5 --- /dev/null +++ b/assets/en_guide_env-vars.md.XRLVeMgw.js @@ -0,0 +1,6 @@ +import{_ as e,c as t,o as d,a1 as o}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Environment variables","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"en/guide/env-vars.md","filePath":"en/guide/env-vars.md"}'),a={name:"en/guide/env-vars.md"},r=o(`

Environment variables

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.

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
+bin/spc build mbstring,pcntl --build-cli
+
+# or direct use
+SPC_CONCURRENCY=4 bin/spc build mbstring,pcntl --build-cli

General environment variables

General environment variables can be used by all build targets.

var namedefault valuecomment
BUILD_ROOT_PATH{pwd}/buildrootThe root directory of the build target
BUILD_LIB_PATH{pwd}/buildroot/libThe root directory of compilation libraries
BUILD_INCLUDE_PATH{pwd}/buildroot/includeHeader file directory for compiling libraries
BUILD_BIN_PATH{pwd}/buildroot/binCompiled binary file directory
PKG_ROOT_PATH{pwd}/pkgrootDirectory where precompiled tools are installed
SOURCE_PATH{pwd}/sourceThe source code extract directory
DOWNLOAD_PATH{pwd}/downloadsDownloaded file directory
SPC_CONCURRENCYDepends on CPU coresNumber of parallel compilations
SPC_SKIP_PHP_VERSION_CHECKemptySkip PHP version check when set to yes

OS specific variables

These environment variables are system-specific and will only take effect on a specific OS.

Windows

var namedefault valuecomment
PHP_SDK_PATH{pwd}\\php-sdk-binary-toolsPHP SDK tools path
UPX_EXEC$PKG_ROOT_PATH\\bin\\upx.exeUPX compression tool path

macOS

var namedefault valuecomment
CCclangC Compiler
CXXclang++C++ Compiler
SPC_DEFAULT_C_FLAGS--target=arm64-apple-darwin or --target=x86_64-apple-darwinDefault C flags (not the same as CFLAGS)
SPC_DEFAULT_CXX_FLAGS--target=arm64-apple-darwin or --target=x86_64-apple-darwinDefault C flags (not the same as CPPFLAGS)
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --forcePHP buildconf command prefix
SPC_CMD_PREFIX_PHP_CONFIGURE./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbgPHP configure command prefix
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCYPHP make command prefix
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGS -Werror=unknown-warning-optionCFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHCPPFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHLDFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os or -g -O0 (the latter when using --no-strip)EXTRA_CFLAGS variable of PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS-lresolvExtra EXTRA_LIBS variables for PHP make command

Linux

var namedefault valuecomment
UPX_EXEC$PKG_ROOT_PATH/bin/upxUPX compression tool path
GNU_ARCHx86_64 or aarch64CPU architecture
CCAlpine: gcc, Other: $GNU_ARCH-linux-musl-gccC Compiler
CXXAlpine: g++, Other: $GNU_ARCH-linux-musl-g++C++ Compiler
ARAlpine: ar, Other: $GNU_ARCH-linux-musl-arStatic library tools
LDld.goldLinker
PATH/usr/local/musl/bin:/usr/local/musl/$GNU_ARCH-linux-musl/bin:$PATHSystem PATH
SPC_DEFAULT_C_FLAGSemptyDefault C flags
SPC_DEFAULT_CXX_FLAGSemptyDefault C++ flags
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --forcePHP buildconf command prefix
SPC_CMD_PREFIX_PHP_CONFIGURELD_LIBRARY_PATH={ld_lib_path} ./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbgPHP configure command prefix
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCYPHP make command prefix
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGSCFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHCPPFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHLDFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LIBS-ldl -lpthreadLIBS variable of PHP configure command
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os -fno-ident -fPIE or -g -O0 -fno-ident -fPIE (the latter when using --no-strip)EXTRA_CFLAGS variable of PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBSemptyExtra EXTRA_LIBS variables for PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM-all-static (when using clang: -Xcompiler -fuse-ld=lld -all-static)Additional LDFLAGS variable for make command
SPC_NO_MUSL_PATHemptyWhether to not insert the PATH of the musl toolchain (not inserted when the value is yes)

{ld_lib_path} value is /usr/local/musl/$GNU_ARCH-linux-musl/lib

FreeBSD

Due to the small number of users of the FreeBSD system, we do not provide environment variables for the FreeBSD system for the time being.

Unix

For Unix systems such as macOS, Linux, FreeBSD, etc., the following environment variables are common.

var namedefault valuecomment
PATH$BUILD_BIN_PATH:$PATHSystem PATH
PKG_CONFIG_PATH$BUILD_LIB_PATH/pkgconfigpkg-config search path
PKG_CONFIG$BUILD_BIN_PATH/pkg-configpkg-config executable path

Library Environment variables (Unix only)

Starting from 2.2.0, static-php-cli supports custom environment variables for all compilation dependent library commands of macOS, Linux, FreeBSD and other Unix systems.

In this way, you can adjust the behavior of compiling dependent libraries through environment variables at any time. For example, you can set the optimization parameters for compiling the xxx library through xxx_CFLAGS=-O0.

Of course, not every library supports the injection of environment variables. We currently provide three wildcard environment variables with the suffixes:

  • _CFLAGS: CFLAGS for the compiler
  • _LDFLAGS: LDFLAGS for the linker
  • _LIBS: LIBS for the linker

The prefix is the name of the dependent library, and the specific name of the library is subject to lib.json. Among them, the library name with - needs to replace - with _.

Here is an example of an optimization option that replaces the openssl library compilation:

shell
openssl_CFLAGS="-O0"

The library name uses the same name listed in lib.json and is case-sensitive.

TIP

When no relevant environment variables are specified, except for the following variables, the remaining values are empty by default:

var namevar default value
pkg_config_CFLAGSmacOS: $SPC_DEFAULT_C_FLAGS -Wimplicit-function-declaration -Wno-int-conversion, Other: empty
pkg_config_LDFLAGSLinux: --static, Other: empty
imagemagick_LDFLAGSLinux: -static, Other: empty
imagemagick_LIBSmacOS: -liconv, Other: empty
ldap_LDFLAGS-L$BUILD_LIB_PATH
openssl_CFLAGSLinux: $SPC_DEFAULT_C_FLAGS, Other: empty
others...empty

The following table is a list of library names that support customizing the above three variables:

lib name
brotli
bzip
curl
freetype
gettext
gmp
imagemagick
ldap
libargon2
libavif
libcares
libevent
openssl

TIP

Because adapting custom environment variables to each library is a particularly tedious task, and in most cases you do not need custom environment variables for these libraries, so we currently only support custom environment variables for some libraries.

If the library you need to customize environment variables is not listed above, you can submit your request through GitHub Issue.

`,35),i=[r];function c(n,s,l,h,p,m){return d(),t("div",null,i)}const u=e(a,[["render",c]]);export{b as __pageData,u as default}; diff --git a/assets/en_guide_env-vars.md.XRLVeMgw.lean.js b/assets/en_guide_env-vars.md.XRLVeMgw.lean.js new file mode 100644 index 00000000..301a61bd --- /dev/null +++ b/assets/en_guide_env-vars.md.XRLVeMgw.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as d,a1 as o}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Environment variables","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"en/guide/env-vars.md","filePath":"en/guide/env-vars.md"}'),a={name:"en/guide/env-vars.md"},r=o("",35),i=[r];function c(n,s,l,h,p,m){return d(),t("div",null,i)}const u=e(a,[["render",c]]);export{b as __pageData,u as default}; diff --git a/assets/en_guide_extension-notes.md.CLQNfx2s.js b/assets/en_guide_extension-notes.md.CLQNfx2s.js new file mode 100644 index 00000000..15992860 --- /dev/null +++ b/assets/en_guide_extension-notes.md.CLQNfx2s.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"Extension Notes","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/extension-notes.md","filePath":"en/guide/extension-notes.md"}'),i={name:"en/guide/extension-notes.md"},l=t('

Extension 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

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 - Unable to use ssl.

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.

swow

  1. Only PHP version >= 8.0 is supported.

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. Because the extension may be dropped from php, we recommend you look for an alternative implementation, such as 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

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 a Zend extension. The functions of Xdebug depend on PHP's Zend engine and underlying code. If you want to statically compile it into PHP, you may need a huge amount of patch code, which is not feasible.
  2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform, extract the xdebug.so file, and then use the --no-strip parameter in static-php-cli to retain the debug symbol table and add the ffi extension. The compiled ./php binary can be configured and run by specifying the INI, eg ./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:

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 - Unable to use ssl.

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 - Unable to use ssl.

password-argon2

  1. password-argon2 is not a standard extension, it is an additional algorithm for the password_hash function.
  2. On Linux systems, password-argon2 dependency libargon2 conflicts with the libsodium library.

ffi

  1. Linux not supported yet: Due to limitations of the Linux system, although the ffi extension can be compiled successfully, it cannot be used to load other so extensions. The prerequisite for Linux to support loading so extensions is dynamic compilation, but dynamic compilation conflicts with the purpose of this project.
  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 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:

parallel

Parallel is only supported on PHP 8.0 ZTS and above.

',54),s=[l];function n(r,d,c,h,p,u){return a(),o("div",null,s)}const f=e(i,[["render",n]]);export{m as __pageData,f as default}; diff --git a/assets/en_guide_extension-notes.md.CLQNfx2s.lean.js b/assets/en_guide_extension-notes.md.CLQNfx2s.lean.js new file mode 100644 index 00000000..81f1b8ea --- /dev/null +++ b/assets/en_guide_extension-notes.md.CLQNfx2s.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"Extension Notes","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/extension-notes.md","filePath":"en/guide/extension-notes.md"}'),i={name:"en/guide/extension-notes.md"},l=t("",54),s=[l];function n(r,d,c,h,p,u){return a(),o("div",null,s)}const f=e(i,[["render",n]]);export{m as __pageData,f as default}; diff --git a/assets/en_guide_extensions.md.DETBhSn0.js b/assets/en_guide_extensions.md.DETBhSn0.js new file mode 100644 index 00000000..73e4743b --- /dev/null +++ b/assets/en_guide_extensions.md.DETBhSn0.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"Extensions","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/extensions.md","filePath":"en/guide/extensions.md"}'),r={name:"en/guide/extensions.md"},y=s('

Extensions

  • yes: supported
  • blank: not supported yet, or WIP
  • no with issue link: confirmed to be unavailable due to issue
  • partial with issue link: supported but not perfect due to issue
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes

TIP

If an extension you need is missing, you can create a Feature Request.

Some extensions or libraries that the extension depends on will have some optional features. For example, the gd library optionally supports libwebp, freetype, etc. If you only use bin/spc build gd --build-cli they will not be included (static-php-cli defaults to the minimum dependency principle).

You can use --with-libs= to add these libraries when compiling. When the dependent libraries of this compilation include them, gd will automatically use them to enable these features. (For example: bin/spc build gd --with-libs=libwebp,freetype --build-cli)

Alternatively you can use --with-suggested-exts and --with-suggested-libs to enable all optional dependencies of these extensions and libraries. (For example: bin/spc build gd --with-suggested-libs --build-cli)

If you don't know whether an extension has optional features, you can check the spc configuration file or use the command bin/spc dev:extensions (library dependency is lib-suggests, extension dependency is ext-suggests).

',4),o=[y];function n(i,a,l,c,h,p){return e(),d("div",null,o)}const f=t(r,[["render",n]]);export{u as __pageData,f as default}; diff --git a/assets/en_guide_extensions.md.DETBhSn0.lean.js b/assets/en_guide_extensions.md.DETBhSn0.lean.js new file mode 100644 index 00000000..ec3179f7 --- /dev/null +++ b/assets/en_guide_extensions.md.DETBhSn0.lean.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"Extensions","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/extensions.md","filePath":"en/guide/extensions.md"}'),r={name:"en/guide/extensions.md"},y=s("",4),o=[y];function n(i,a,l,c,h,p){return e(),d("div",null,o)}const f=t(r,[["render",n]]);export{u as __pageData,f as default}; diff --git a/assets/en_guide_index.md.DzPC1rL-.js b/assets/en_guide_index.md.DzPC1rL-.js new file mode 100644 index 00000000..5d3380d5 --- /dev/null +++ b/assets/en_guide_index.md.DzPC1rL-.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Guide","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/index.md","filePath":"en/guide/index.md"}'),n={name:"en/guide/index.md"},r=i('

Guide

Static php cli is a tool used to build statically compiled PHP binaries, currently supporting Linux and macOS systems.

In the guide section, you will learn how to use static php cli to build standalone PHP programs.

TIP

If you are a native English speaker, some corrections to the documentation are welcome.

Compilation Environment

The following is the architecture support situation, where ⚙️ represents support for GitHub Action build, 💻 represents support for local manual build, and empty represents temporarily not supported.

x86_64aarch64
macOS⚙️ 💻⚙️ 💻
Linux⚙️ 💻⚙️ 💻
Windows⚙️ 💻
FreeBSD💻💻

Among them, Linux is currently only tested on Ubuntu, Debian, and Alpine distributions, and other distributions have not been tested, which cannot guarantee successful compilation. For untested distributions, local compilation can be done using methods such as Docker to avoid environmental issues.

There are two architectures for macOS: x86_64 and Arm, but binaries compiled on one architecture cannot be directly used on the other architecture. Rosetta 2 cannot guarantee that programs compiled with Arm architecture can fully run on x86_64 environment.

Windows currently only supports the x86_64 architecture, and does not support 32-bit x86 or arm64 architecture.

Supported PHP Version

Currently, static php cli supports PHP versions 8.0 to 8.3, and theoretically supports PHP 7.4 and earlier versions. Simply select the earlier version when downloading. However, due to some extensions and special components that have stopped supporting earlier versions of PHP, static-php-cli will not explicitly support earlier versions. We recommend that you compile the latest PHP version possible for a better experience.

',13),a=[r];function s(d,l,c,p,u,h){return o(),t("div",null,a)}const _=e(n,[["render",s]]);export{b as __pageData,_ as default}; diff --git a/assets/en_guide_index.md.DzPC1rL-.lean.js b/assets/en_guide_index.md.DzPC1rL-.lean.js new file mode 100644 index 00000000..96f6fa86 --- /dev/null +++ b/assets/en_guide_index.md.DzPC1rL-.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Guide","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/index.md","filePath":"en/guide/index.md"}'),n={name:"en/guide/index.md"},r=i("",13),a=[r];function s(d,l,c,p,u,h){return o(),t("div",null,a)}const _=e(n,[["render",s]]);export{b as __pageData,_ as default}; diff --git a/assets/en_guide_manual-build.md.CiZNh_BU.js b/assets/en_guide_manual-build.md.CiZNh_BU.js new file mode 100644 index 00000000..39e6289f --- /dev/null +++ b/assets/en_guide_manual-build.md.CiZNh_BU.js @@ -0,0 +1,139 @@ +import{_ as s,c as i,o as e,a1 as a}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Build (Linux, macOS, FreeBSD)","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/manual-build.md","filePath":"en/guide/manual-build.md"}'),n={name:"en/guide/manual-build.md"},t=a(`

Build (Linux, macOS, FreeBSD)

This section covers the build process for Linux, macOS, and FreeBSD. If you want to build on Windows, also need to read Build on Windows.

This project provides a binary file of static-php-cli. You can directly download the binary file of the corresponding platform and then use it to build static PHP. Currently, the platforms supported by spc binary are Linux and macOS.

Here's how to download from self-hosted server:

bash
# Download from self-hosted nightly builds (sync with main branch)
+# For Linux x86_64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# For Linux aarch64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+# macOS x86_64 (Intel)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+# macOS aarch64 (Apple)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+# Windows (x86_64, win10 build 17063 or later)
+curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+
+# Add execute perm (Linux and macOS only)
+chmod +x ./spc
+
+# Run (Linux and macOS)
+./spc --version
+# Run (Windows powershell)
+.\\spc.exe --version

If you are using the packaged spc binary, you will need to replace the leading bin/spc with ./spc in all the commands below.

Build locally (using source code)

If you have problems using the spc binary, or if you need to modify the static-php-cli source code, download static-php-cli from the source code.

Currently, it supports building on macOS and Linux. macOS supports the latest version of the operating system and two architectures, while Linux supports Debian and derivative distributions, as well as Alpine Linux.

Because this project itself is developed using PHP, it is also necessary to install PHP on the system during compilation. This project also provides static binary PHP suitable for this project, which can be selected and used according to actual situations.

bash
# clone repo
+git clone https://github.com/crazywhalecc/static-php-cli.git --depth=1
+cd static-php-cli
+
+# You need to install the PHP environment first before running Composer and this project. The installation method can be referred to below.
+composer update

Use System PHP

Below are some example commands for installing PHP and Composer in the system. It is recommended to search for the specific installation method yourself or ask the AI search engine to obtain the answer, which will not be elaborated here.

bash
# [macOS], need install Homebrew first. See https://brew.sh/
+# Remember change your composer executable path. For M1/M2 Chip mac, "/opt/homebrew/bin/", for Intel mac, "/usr/local/bin/". Or add it to your own path.
+brew install php wget
+wget https://getcomposer.org/download/latest-stable/composer.phar -O /path/to/your/bin/composer && chmod +x /path/to/your/bin/composer
+
+# [Debian], you need to make sure your php version >= 8.1 and composer >= 2.0
+sudo apt install php-cli composer php-tokenizer
+
+# [Alpine]
+apk add bash file wget xz php81 php81-common php81-pcntl php81-tokenizer php81-phar php81-posix php81-xml composer

TIP

Currently, some versions of Ubuntu install older PHP versions, so no installation commands are provided. If necessary, it is recommended to add software sources such as ppa first, and then install the latest version of PHP and tokenizer, XML, and phar extensions.

Older versions of Debian may have an older (<= 7.4) version of PHP installed by default, it is recommended to upgrade Debian first.

Use Docker

If you don't want to install PHP and Composer runtime environment on your system, you can use the built-in Docker environment build script.

bash
# To use directly, replace \`bin/spc\` with \`bin/spc-alpine-docker\` in all used commands
+bin/spc-alpine-docker

The first time the command is executed, docker build will be used to build a Docker image. The default built Docker image is the x86_64 architecture, and the image name is cwcc-spc-x86_64.

If you want to build aarch64 static-php-cli in x86_64 environment, you can use qemu to emulate the arm image to run Docker, but the speed will be very slow. Use command: SPC_USE_ARCH=aarch64 bin/spc-alpine-docker.

If it prompts that sudo is required to run after running, execute the following command once to grant static-php-cli permission to execute sudo:

bash
export SPC_USE_SUDO=yes

Use Precompiled Static PHP Binaries

If you don't want to use Docker and install PHP in the system, you can directly download the php binary cli program compiled by this project itself. The usage process is as follows:

Deploy the environment using the command, the command will download a static php-cli binary from self-hosted server. Next, it will automatically download Composer from getcomposer or Aliyun mirror.

TIP

Using precompiled static PHP binaries is currently only supported on Linux and macOS. The FreeBSD environment is currently not supported due to the lack of an automated build environment.

bash
bin/setup-runtime
+
+# For users with special network environments such as mainland China, you can use mirror sites (aliyun) to speed up the download speed
+bin/setup-runtime --mirror china

This script will download two files in total: bin/php and bin/composer. After the download is complete, there are two ways to use it:

  1. Add the bin/ directory to the PATH: export PATH="/path/to/your/static-php-cli/bin:$PATH", after adding the path, it is equivalent to installing PHP in the system, you can directly Use commands such as composer, php -v, or directly use bin/spc.
  2. Direct call, such as executing static-php-cli command: bin/php bin/spc --help, executing Composer: bin/php bin/composer update.

Command - download

Use the command bin/spc download to download the source code required for compilation, including php-src and the source code of various dependent libraries.

bash
# Download all dependencies
+bin/spc download --all
+
+# Download all dependent packages, and specify the main version of PHP to download, optional: 7.3, 7.4, 8.0, 8.1, 8.2, 8.3
+bin/spc download --all --with-php=8.2
+
+# Show download progress bar while downloading (curl)
+bin/spc download --all --debug
+
+# Delete old download data
+bin/spc download --clean
+
+# Download specified dependencies
+bin/spc download php-src,micro,zstd,ext-zstd
+
+# Download only extensions and libraries to be compiled (use extensions, including suggested libraries)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl,zstd
+
+# Download only the extensions and dependent libraries to be compiled (use extensions, excluding suggested libraries)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl --without-suggestions
+
+# Download only libraries to be compiled (use libraries, including suggested libraries and required libraries, can use --for-extensions together)
+bin/spc download  --for-libs=liblz4,libevent --for-extensions=pcntl,rar,xml
+
+# Download only libraries to be compiled (use libraries, excluding suggested libraries)
+bin/spc download --for-libs=liblz4,libevent --without-suggestions
+
+# When downloading sources, ignore some source caches (always force download, e.g. switching PHP version)
+bin/spc download --for-extensions=curl,pcntl,xml --ignore-cache-sources=php-src --with-php=8.3
+
+# Set retry times (default is 0)
+bin/spc download --all --retry=2

If the network in your area is not good, or the speed of downloading the dependency package is too slow, you can download download.zip which is packaged regularly every week from GitHub Action, and use the command to directly use the zip archive as a dependency.

Dependent packages can be downloaded locally from Action. Enter Action and select the latest Workflow that has been successfully run, and download download-files-x.y.

bash
bin/spc download --from-zip=/path/to/your/download.zip

If a source cannot be downloaded all the time, or you need to download some specific version of the package, such as downloading the beta version of PHP, the old version of the library, etc., you can use the parameter -U or --custom-url to rewrite the download link, Make the downloader force the link you specify to download packages from this source. The method of use is {source-name}:{url}, which can rewrite the download URLs of multiple libraries at the same time. Also, it is available when downloading with the --for-extensions option.

bash
# Specifying to download a beta version of PHP8.3
+bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+
+# Specifying to download an older version of the curl library
+bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"

Command - doctor

If you can run bin/spc normally but cannot compile static PHP or dependent libraries normally, you can run bin/spc doctor first to check whether the system itself lacks dependencies.

bash
# Quick check
+bin/spc doctor
+
+# Quickly check and fix when it can be automatically repaired (use package management to install dependent packages, only support the above-mentioned operating systems and distributions)
+bin/spc doctor --auto-fix

Command - build

Use the build command to start building the static php binary. Before executing the bin/spc build command, be sure to use the download command to download sources. It is recommended to use doctor to check the environment.

Basic build

You need to go to Extension List or Command Generator to select the extension you want to add, and then use the command bin/spc build to compile. You need to specify a compilation target, choose from the following parameters:

  • --build-cli: Build a cli sapi (command line interface, which can execute PHP code on the command line)
  • --build-fpm: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx)
  • --build-micro: Build a micro sapi (used to build a standalone executable binary containing PHP code)
  • --build-embed: Build an embed sapi (used to embed into other C language programs)
  • --build-all: build all above sapi
bash
# Compile PHP with bcmath,curl,openssl,ftp,posix,pcntl extensions, the compilation target is cli
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+
+# Compile PHP with phar,curl,posix,pcntl,tokenizer extensions, compile target is micro
+bin/spc build phar,curl,posix,pcntl,tokenizer --build-micro

TIP

If you need to repeatedly build and debug, you can delete the buildroot/ and source/ directories so that you can re-extract and build all you need from the downloaded source code package:

shell
# remove
+rm -rf buildroot source
+# build again
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

TIP

If you want to build multiple versions of PHP and don't want to build other dependent libraries repeatedly each time, you can use switch-php-version to quickly switch to another version and compile after compiling one version:

shell
# switch to 8.3
+bin/spc switch-php-version 8.3
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+# switch to 8.0
+bin/spc switch-php-version 8.0
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

Debug

If you encounter problems during the compilation process, or want to view each executing shell command, you can use --debug to enable debug mode and view all terminal logs:

bash
bin/spc build mysqlnd,pdo_mysql --build-all --debug

Build Options

During the compilation process, in some special cases, the compiler and the content of the compilation directory need to be intervened. You can try to use the following commands:

  • --cc=XXX: Specifies the execution command of the C language compiler (Linux default musl-gcc or gcc, macOS default clang)
  • --cxx=XXX: Specifies the execution command of the C++ language compiler (Linux defaults to g++, macOS defaults to clang++)
  • --with-clean: clean up old make files before compiling PHP
  • --enable-zts: Make compiled PHP thread-safe version (default is NTS version)
  • --no-strip: Do not run strip after compiling the PHP library to trim the binary file to reduce its size (the macOS binary file without trim can use dynamically linked third-party extensions)
  • --with-libs=XXX,YYY: Compile the specified dependent library before compiling PHP, and activate some extended optional functions (such as libavif of the gd library, etc.)
  • -I xxx=yyy: Hard compile INI options into PHP before compiling (support multiple options, alias is --with-hardcoded-ini)
  • --with-micro-fake-cli: When compiling micro, let micro's PHP_SAPI pretend to be cli (for compatibility with some programs that check PHP_SAPI)
  • --disable-opcache-jit: Disable opcache jit (enabled by default)
  • -P xxx.php: Inject external scripts during static-php-cli compilation (see Inject external scripts below for details)
  • --without-micro-ext-test: After building micro.sfx, do not test the running results of different extensions in micro.sfx
  • --with-suggested-exts: Add ext-suggests as dependencies when compiling
  • --with-suggested-libs: Add lib-suggests as dependencies when compiling
  • --with-upx-pack: Use UPX to reduce the size of the binary file after compilation (you need to use bin/spc install-pkg upx to install upx first)

For hardcoding INI options, it works for cli, micro, embed sapi. Here is a simple example where we preset a larger memory_limit and disable the system function:

bash
bin/spc build bcmath,pcntl,posix --build-all -I "memory_limit=4G" -I "disable_functions=system"

Command - micro:combine

Use the micro:combine command to build the compiled micro.sfx and your code (.php or .phar file) into an executable binary. You can also use this command to directly build a micro binary injected with ini configuration.

TIP

Injecting ini configuration refers to adding a special structure after micro.sfx to save ini configuration items before combining micro.sfx with PHP source code.

micro.sfx can identify the INI file header through a special byte, and the micro can be started with INI through the INI file header.

The original wiki of this feature is in phpmicro - Wiki, and this feature may change in the future.

The following is the general usage, directly packaging the php source code into a file:

bash
# Before doing the packaging process, you should use \`build --build-micro\` to compile micro.sfx
+echo "<?php echo 'hello';" > a.php
+bin/spc micro:combine a.php
+
+# Just use it
+./my-app

You can use the following options to specify the file name to be output, and you can also specify micro.sfx in other paths for packaging.

bash
# specify the output filename
+bin/spc micro:combine a.php --output=custom-bin
+# Use absolute path
+bin/spc micro:combine a.php -O /tmp/my-custom-app
+
+# Specify micro.sfx in other locations for packaging
+bin/spc micro:combine a.app --with-micro=/path/to/your/micro.sfx

If you want to inject ini configuration items, you can use the following parameters to add ini to the executable file from a file or command line option.

bash
# Specified using command-line options (-I is shorthand for --with-ini-set)
+bin/spc micro:combine a.php -I "a=b" -I "foo=bar"
+
+# Use ini file specification (-N is shorthand for --with-ini-file)
+bin/spc micro:combine a.php -N /path/to/your/custom.ini

WARNING

Note, please do not directly use the PHP source code or the php.ini file in the system-installed PHP, it is best to manually write an ini configuration file that you need, for example:

ini
; custom.ini
+curl.cainfo=/path/to/your/cafile.pem
+memory_limit=1G

The ini injection of this command is achieved by appending a special structure after micro.sfx, which is different from the function of inserting hard-coded INI during compilation.

If you want to package phar, just replace a.php with the packaged phar file. But please note that micro.sfx under phar needs extra attention to the path problem, see Developing - Phar directory issue.

Command - extract

Use the command bin/spc extract to unpack and copy the source code required for compilation, including php-src and the source code of various dependent libraries (you need to specify the name of the library to be unpacked).

For example, after we have downloaded sources, we want to distribute and execute the build process, manually unpack and copy the package to a specified location, and we can use commands.

bash
# Unzip the downloaded compressed package of php-src and libxml2, and store the decompressed source code in the source directory
+bin/spc extract php-src,libxml2

Dev Command - dev

Debug commands refer to a collection of commands that can assist in outputting some information when you use static-php-cli to build PHP or modify and enhance the static-php-cli project itself.

  • dev:extensions: output all currently supported extension names, or output the specified extension information
  • dev:php-version: output the currently compiled PHP version (by reading php_version.h)
  • dev:sort-config: Sort the list of configuration files in the config/ directory in alphabetical order
  • dev:lib-ver <lib-name>: Read the version from the source code of the dependency library (only available for specific dependency libraries)
  • dev:ext-ver <ext-name>: Read the corresponding version from the source code of the extension (only available for specific extensions)
bash
# output all extensions information
+bin/spc dev:extensions
+
+# Output the meta information of the specified extension
+bin/spc dev:extensions mongodb,curl,openssl
+
+# Output the specified columns
+# Available column name: lib-depends, lib-suggests, ext-depends, ext-suggests, unix-only, type
+bin/spc dev:extensions --columns=lib-depends,type,ext-depends
+
+# Output the currently compiled PHP version
+# You need to decompress the downloaded PHP source code to the source directory first
+# You can use \`bin/spc extract php-src\` to decompress the source code separately
+bin/spc dev:php-version
+
+# Sort the configuration files in the config/ directory in alphabetical order (e.g. ext.json)
+bin/spc dev:sort-config ext

Command - install-pkg

Use the command bin/spc install-pkg to download some precompiled or closed source tools and install them into the pkgroot directory.

When bin/spc doctor automatically repairs the Windows environment, tools such as nasm and perl will be downloaded, and the installation process of install-pkg will also be used.

Here is an example of installing the tool:

  • Download and install UPX (Linux and Windows only): bin/spc install-pkg upx

Command - del-download

In some cases, you need to delete single or multiple specified download source files and re-download them, such as switching PHP versions. The bin/spc del-download command is provided after the 2.1.0-beta.4 version. Specified source files can be deleted.

Deletes downloaded source files containing precompiled packages and source code named as keys in source.json or pkg.json. Here are some examples:

  • Delete the old PHP source code and switch to download the 8.3 version: bin/spc del-download php-src && bin/spc download php-src --with-php=8.3
  • Delete the download file of redis extension: bin/spc del-download redis
  • Delete the downloaded musl-toolchain x86_64: bin/spc del-download musl-toolchain-x86_64-linux

Inject External Script

Injecting external scripts refers to inserting one or more scripts during the static-php-cli compilation process to more flexibly support parameter modifications and source code patches in different environments.

Under normal circumstances, this function mainly solves the problem that the patch cannot be modified by modifying the static-php-cli code when compiling with spc binary.

There is another situation: your project directly depends on the crazywhalecc/static-php-cli repository and is synchronized with main branch, but some proprietary modifications are required, and these feature are not suitable for merging into the main branch.

In view of the above situation, in the official version 2.0.0, static-php-cli has added multiple event trigger points. You can write an external xx.php script and pass it in through the command line parameter -P and execute.

When writing to inject external scripts, the methods you will use are builder() and patch_point(). Among them, patch_point() obtains the name of the current event, and builder() obtains the BuilderBase object.

Because the incoming patch point does not distinguish between events, you must write the code you want to execute in if(patch_point() === 'your_event_name'), otherwise it will be executed repeatedly in other events.

The following are the supported patch_point event names and corresponding locations:

Event nameEvent description
before-libs-extractTriggered before the dependent libraries extracted
after-libs-extractTriggered after the compiled dependent libraries extracted
before-php-extractTriggered before PHP source code extracted
after-php-extractTriggered after PHP source code extracted
before-micro-extractTriggered before phpmicro extract
after-micro-extractTriggered after phpmicro extracted
before-exts-extractTriggered before the extension (to be compiled) extracted to the PHP source directory
after-exts-extractTriggered after the extension extracted to the PHP source directory
before-library[name]-buildTriggered before the library named name is compiled (such as before-library[postgresql]-build)
after-library[name]-buildTriggered after the library named name is compiled
before-php-buildconfTriggered before compiling PHP command ./buildconf
before-php-configureTriggered before compiling PHP command ./configure
before-php-makeTriggered before compiling PHP command make
before-sanity-checkTriggered after compiling PHP but before running extended checks

The following is a simple example of temporarily modifying the PHP source code. Enable the CLI function to search for the php.ini configuration in the current working directory:

php
// a.php
+<?php
+// patch it before \`./buildconf\` executed
+if (patch_point() === 'before-php-buildconf') {
+    \\SPC\\store\\FileSystem::replaceFileStr(
+        SOURCE_PATH . '/php-src/sapi/cli/php_cli.c',
+        'sapi_module->php_ini_ignore_cwd = 1;',
+        'sapi_module->php_ini_ignore_cwd = 0;'
+    );
+}
bash
bin/spc build mbstring --build-cli -P a.php
+# Write in ./
+echo 'memory_limit=8G' > ./php.ini
$ buildroot/bin/php -i | grep Loaded
+Loaded Configuration File => /Users/jerry/project/git-project/static-php-cli/php.ini
+
+$ buildroot/bin/php -i | grep memory
+memory_limit => 8G => 8G

For the objects, methods and interfaces supported by static-php-cli, you can read the source code. Most methods and objects have corresponding comments.

Commonly used objects and functions using the -P function are:

  • SPC\\store\\FileSystem: file management class
    • ::replaceFileStr(string $filename, string $search, $replace): Replace file string content
    • ::replaceFileStr(string $filename, string $pattern, $replace): Regularly replace file content
    • ::replaceFileUser(string $filename, $callback): User-defined function replaces file content
    • ::copyDir(string $from, string $to): Recursively copy a directory to another location
    • ::convertPath(string $path): Convert the path delimiter to the current system delimiter
    • ::scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): Traverse directory files
  • SPC\\builder\\BuilderBase: Build object
    • ->getPatchPoint(): Get the current injection point name
    • ->getOption(string $key, $default = null): Get command line and compile-time options
    • ->getPHPVersionID(): Get the currently compiled PHP version ID
    • ->getPHPVersion(): Get the currently compiled PHP version number
    • ->setOption(string $key, $value): Set options
    • ->setOptionIfNotExists(string $key, $value): Set option if option does not exist

TIP

static-php-cli has many open methods, which cannot be listed in the docs, but as long as it is a public function and is not marked as @internal, it theoretically can be called.

`,102),l=[t];function p(o,h,d,c,r,k){return e(),i("div",null,l)}const m=s(n,[["render",p]]);export{g as __pageData,m as default}; diff --git a/assets/en_guide_manual-build.md.CiZNh_BU.lean.js b/assets/en_guide_manual-build.md.CiZNh_BU.lean.js new file mode 100644 index 00000000..d98b7f12 --- /dev/null +++ b/assets/en_guide_manual-build.md.CiZNh_BU.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as e,a1 as a}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Build (Linux, macOS, FreeBSD)","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/manual-build.md","filePath":"en/guide/manual-build.md"}'),n={name:"en/guide/manual-build.md"},t=a("",102),l=[t];function p(o,h,d,c,r,k){return e(),i("div",null,l)}const m=s(n,[["render",p]]);export{g as __pageData,m as default}; diff --git a/assets/en_guide_troubleshooting.md.BZNNttUZ.js b/assets/en_guide_troubleshooting.md.BZNNttUZ.js new file mode 100644 index 00000000..c0966340 --- /dev/null +++ b/assets/en_guide_troubleshooting.md.BZNNttUZ.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t,a1 as a}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"Troubleshooting","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/troubleshooting.md","filePath":"en/guide/troubleshooting.md"}'),r={name:"en/guide/troubleshooting.md"},n=a('

Troubleshooting

Various failures may be encountered in the process of using static-php-cli, here will describe how to check the errors by yourself and report 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. Currently, version 2.0.0 has not added an automatic retry mechanism, so after encountering a download failure, you can try to call the download command multiple times. 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 --debug 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.

',9),i=[n];function s(l,c,d,u,h,m){return t(),o("div",null,i)}const b=e(r,[["render",s]]);export{f as __pageData,b as default}; diff --git a/assets/en_guide_troubleshooting.md.BZNNttUZ.lean.js b/assets/en_guide_troubleshooting.md.BZNNttUZ.lean.js new file mode 100644 index 00000000..1ff796ae --- /dev/null +++ b/assets/en_guide_troubleshooting.md.BZNNttUZ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as t,a1 as a}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"Troubleshooting","description":"","frontmatter":{},"headers":[],"relativePath":"en/guide/troubleshooting.md","filePath":"en/guide/troubleshooting.md"}'),r={name:"en/guide/troubleshooting.md"},n=a("",9),i=[n];function s(l,c,d,u,h,m){return t(),o("div",null,i)}const b=e(r,[["render",s]]);export{f as __pageData,b as default}; diff --git a/assets/en_index.md.B7rqxnyF.js b/assets/en_index.md.B7rqxnyF.js new file mode 100644 index 00000000..55a6c8ab --- /dev/null +++ b/assets/en_index.md.B7rqxnyF.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.","actions":[{"theme":"brand","text":"Guide","link":"./guide/"}]},"features":[{"title":"Static CLI Binary","details":"You can easily compile a standalone php binary for general use. Including CLI, FPM sapi."},{"title":"Micro Self-Extracted Executable","details":"You can compile a self-extracted executable and build with your php source code."},{"title":"Dependency Management","details":"static-php-cli comes with dependency management and supports installation of different types of PHP extensions."}]},"headers":[],"relativePath":"en/index.md","filePath":"en/index.md"}'),n={name:"en/index.md"};function i(o,s,c,d,r,l){return a(),t("div")}const m=e(n,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/en_index.md.B7rqxnyF.lean.js b/assets/en_index.md.B7rqxnyF.lean.js new file mode 100644 index 00000000..55a6c8ab --- /dev/null +++ b/assets/en_index.md.B7rqxnyF.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.","actions":[{"theme":"brand","text":"Guide","link":"./guide/"}]},"features":[{"title":"Static CLI Binary","details":"You can easily compile a standalone php binary for general use. Including CLI, FPM sapi."},{"title":"Micro Self-Extracted Executable","details":"You can compile a self-extracted executable and build with your php source code."},{"title":"Dependency Management","details":"static-php-cli comes with dependency management and supports installation of different types of PHP extensions."}]},"headers":[],"relativePath":"en/index.md","filePath":"en/index.md"}'),n={name:"en/index.md"};function i(o,s,c,d,r,l){return a(),t("div")}const m=e(n,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/extension-notes.md.CYTuu5Xm.js b/assets/extension-notes.md.CYTuu5Xm.js new file mode 100644 index 00000000..19dc3377 --- /dev/null +++ b/assets/extension-notes.md.CYTuu5Xm.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as n}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"extension-notes.md","filePath":"extension-notes.md"}'),o={name:"extension-notes.md"};function s(a,r,c,i,p,_){return n(),t("div")}const f=e(o,[["render",s]]);export{m as __pageData,f as default}; diff --git a/assets/extension-notes.md.CYTuu5Xm.lean.js b/assets/extension-notes.md.CYTuu5Xm.lean.js new file mode 100644 index 00000000..19dc3377 --- /dev/null +++ b/assets/extension-notes.md.CYTuu5Xm.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as n}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"extension-notes.md","filePath":"extension-notes.md"}'),o={name:"extension-notes.md"};function s(a,r,c,i,p,_){return n(),t("div")}const f=e(o,[["render",s]]);export{m as __pageData,f as default}; diff --git a/assets/extensions.md.C4hBsrw7.js b/assets/extensions.md.C4hBsrw7.js new file mode 100644 index 00000000..01911d08 --- /dev/null +++ b/assets/extensions.md.C4hBsrw7.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"extensions.md","filePath":"extensions.md"}'),y={name:"extensions.md"},r=s('
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes
',1),o=[r];function n(a,l,i,h,m,p){return e(),d("div",null,o)}const c=t(y,[["render",n]]);export{f as __pageData,c as default}; diff --git a/assets/extensions.md.C4hBsrw7.lean.js b/assets/extensions.md.C4hBsrw7.lean.js new file mode 100644 index 00000000..e01d4de2 --- /dev/null +++ b/assets/extensions.md.C4hBsrw7.lean.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"","description":"","frontmatter":{},"headers":[],"relativePath":"extensions.md","filePath":"extensions.md"}'),y={name:"extensions.md"},r=s("",1),o=[r];function n(a,l,i,h,m,p){return e(),d("div",null,o)}const c=t(y,[["render",n]]);export{f as __pageData,c as default}; diff --git a/assets/index.md.DDaDbFm-.js b/assets/index.md.DDaDbFm-.js new file mode 100644 index 00000000..d3756a99 --- /dev/null +++ b/assets/index.md.DDaDbFm-.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.","actions":[{"theme":"brand","text":"Get Started","link":"/en/guide/"},{"theme":"alt","text":"中文文档","link":"/zh/"}]},"features":[{"title":"Static CLI Binary","details":"You can easily compile a standalone php binary for general use. Including CLI, FPM sapi."},{"title":"Micro Self-Extracted Executable","details":"You can compile a self-extracted executable and build with your php source code."},{"title":"Dependency Management","details":"static-php-cli comes with dependency management and supports installation of different types of PHP extensions."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"};function i(o,s,c,r,d,l){return a(),t("div")}const m=e(n,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/index.md.DDaDbFm-.lean.js b/assets/index.md.DDaDbFm-.lean.js new file mode 100644 index 00000000..d3756a99 --- /dev/null +++ b/assets/index.md.DDaDbFm-.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.","actions":[{"theme":"brand","text":"Get Started","link":"/en/guide/"},{"theme":"alt","text":"中文文档","link":"/zh/"}]},"features":[{"title":"Static CLI Binary","details":"You can easily compile a standalone php binary for general use. Including CLI, FPM sapi."},{"title":"Micro Self-Extracted Executable","details":"You can compile a self-extracted executable and build with your php source code."},{"title":"Dependency Management","details":"static-php-cli comes with dependency management and supports installation of different types of PHP extensions."}]},"headers":[],"relativePath":"index.md","filePath":"index.md"}'),n={name:"index.md"};function i(o,s,c,r,d,l){return a(),t("div")}const m=e(n,[["render",i]]);export{u as __pageData,m as default}; diff --git a/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 new file mode 100644 index 00000000..b6b603d5 Binary files /dev/null and b/assets/inter-italic-cyrillic-ext.r48I6akx.woff2 differ diff --git a/assets/inter-italic-cyrillic.By2_1cv3.woff2 b/assets/inter-italic-cyrillic.By2_1cv3.woff2 new file mode 100644 index 00000000..def40a4f Binary files /dev/null and b/assets/inter-italic-cyrillic.By2_1cv3.woff2 differ diff --git a/assets/inter-italic-greek-ext.1u6EdAuj.woff2 b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 new file mode 100644 index 00000000..e070c3d3 Binary files /dev/null and b/assets/inter-italic-greek-ext.1u6EdAuj.woff2 differ diff --git a/assets/inter-italic-greek.DJ8dCoTZ.woff2 b/assets/inter-italic-greek.DJ8dCoTZ.woff2 new file mode 100644 index 00000000..a3c16ca4 Binary files /dev/null and b/assets/inter-italic-greek.DJ8dCoTZ.woff2 differ diff --git a/assets/inter-italic-latin-ext.CN1xVJS-.woff2 b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 new file mode 100644 index 00000000..2210a899 Binary files /dev/null and b/assets/inter-italic-latin-ext.CN1xVJS-.woff2 differ diff --git a/assets/inter-italic-latin.C2AdPX0b.woff2 b/assets/inter-italic-latin.C2AdPX0b.woff2 new file mode 100644 index 00000000..790d62dc Binary files /dev/null and b/assets/inter-italic-latin.C2AdPX0b.woff2 differ diff --git a/assets/inter-italic-vietnamese.BSbpV94h.woff2 b/assets/inter-italic-vietnamese.BSbpV94h.woff2 new file mode 100644 index 00000000..1eec0775 Binary files /dev/null and b/assets/inter-italic-vietnamese.BSbpV94h.woff2 differ diff --git a/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 new file mode 100644 index 00000000..2cfe6153 Binary files /dev/null and b/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2 differ diff --git a/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 new file mode 100644 index 00000000..e3886dd1 Binary files /dev/null and b/assets/inter-roman-cyrillic.C5lxZ8CY.woff2 differ diff --git a/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 new file mode 100644 index 00000000..36d67487 Binary files /dev/null and b/assets/inter-roman-greek-ext.CqjqNYQ-.woff2 differ diff --git a/assets/inter-roman-greek.BBVDIX6e.woff2 b/assets/inter-roman-greek.BBVDIX6e.woff2 new file mode 100644 index 00000000..2bed1e85 Binary files /dev/null and b/assets/inter-roman-greek.BBVDIX6e.woff2 differ diff --git a/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 new file mode 100644 index 00000000..9a8d1e2b Binary files /dev/null and b/assets/inter-roman-latin-ext.4ZJIpNVo.woff2 differ diff --git a/assets/inter-roman-latin.Di8DUHzh.woff2 b/assets/inter-roman-latin.Di8DUHzh.woff2 new file mode 100644 index 00000000..07d3c53a Binary files /dev/null and b/assets/inter-roman-latin.Di8DUHzh.woff2 differ diff --git a/assets/inter-roman-vietnamese.BjW4sHH5.woff2 b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 new file mode 100644 index 00000000..57bdc22a Binary files /dev/null and b/assets/inter-roman-vietnamese.BjW4sHH5.woff2 differ diff --git a/assets/style.BJI_MZVL.css b/assets/style.BJI_MZVL.css new file mode 100644 index 00000000..f7ab2723 --- /dev/null +++ b/assets/style.BJI_MZVL.css @@ -0,0 +1 @@ +.box[data-v-4cdb2891]{display:flex;flex-wrap:wrap;max-width:100%}.ext-item[data-v-4cdb2891]{margin:4px 8px}h2[data-v-4cdb2891]{margin-bottom:8px}.command-preview[data-v-4cdb2891]{padding:1.2rem;background:var(--vp-c-divider);font-family:monospace;overflow-wrap:break-word}.option-line[data-v-4cdb2891]{padding:4px 8px}.option-title[data-v-4cdb2891]{margin:4px 8px 4px 4px;font-weight:700}select[data-v-4cdb2891]{border-radius:4px;border:1px solid var(--vp-c-divider);padding:0 4px;width:300px}.my-btn[data-v-4cdb2891]{color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg);border-radius:8px;padding:0 16px;line-height:32px;font-size:14px;display:inline-block;text-align:center;font-weight:600;margin-right:8px;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s;cursor:pointer;border:1px solid var(--vp-button-alt-border)}.my-btn[data-v-4cdb2891]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.my-btn[data-v-4cdb2891]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.textarea[data-v-4cdb2891]{border:1px solid var(--vp-button-alt-border);padding:0 4px;min-width:300px}.command-container[data-v-4cdb2891]{margin-bottom:24px}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic-ext.BBPuwvHQ.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-cyrillic.C5lxZ8CY.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek-ext.CqjqNYQ-.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-greek.BBVDIX6e.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-vietnamese.BjW4sHH5.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin-ext.4ZJIpNVo.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:normal;font-weight:100 900;font-display:swap;src:url(/assets/inter-roman-latin.Di8DUHzh.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic-ext.r48I6akx.woff2) format("woff2");unicode-range:U+0460-052F,U+1C80-1C88,U+20B4,U+2DE0-2DFF,U+A640-A69F,U+FE2E-FE2F}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-cyrillic.By2_1cv3.woff2) format("woff2");unicode-range:U+0301,U+0400-045F,U+0490-0491,U+04B0-04B1,U+2116}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek-ext.1u6EdAuj.woff2) format("woff2");unicode-range:U+1F00-1FFF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-greek.DJ8dCoTZ.woff2) format("woff2");unicode-range:U+0370-0377,U+037A-037F,U+0384-038A,U+038C,U+038E-03A1,U+03A3-03FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-vietnamese.BSbpV94h.woff2) format("woff2");unicode-range:U+0102-0103,U+0110-0111,U+0128-0129,U+0168-0169,U+01A0-01A1,U+01AF-01B0,U+0300-0301,U+0303-0304,U+0308-0309,U+0323,U+0329,U+1EA0-1EF9,U+20AB}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin-ext.CN1xVJS-.woff2) format("woff2");unicode-range:U+0100-02AF,U+0304,U+0308,U+0329,U+1E00-1E9F,U+1EF2-1EFF,U+2020,U+20A0-20AB,U+20AD-20C0,U+2113,U+2C60-2C7F,U+A720-A7FF}@font-face{font-family:Inter;font-style:italic;font-weight:100 900;font-display:swap;src:url(/assets/inter-italic-latin.C2AdPX0b.woff2) format("woff2");unicode-range:U+0000-00FF,U+0131,U+0152-0153,U+02BB-02BC,U+02C6,U+02DA,U+02DC,U+0304,U+0308,U+0329,U+2000-206F,U+2074,U+20AC,U+2122,U+2191,U+2193,U+2212,U+2215,U+FEFF,U+FFFD}@font-face{font-family:Punctuation SC;font-weight:400;src:local("PingFang SC Regular"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:500;src:local("PingFang SC Medium"),local("Noto Sans CJK SC"),local("Microsoft YaHei");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:600;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}@font-face{font-family:Punctuation SC;font-weight:700;src:local("PingFang SC Semibold"),local("Noto Sans CJK SC Bold"),local("Microsoft YaHei Bold");unicode-range:U+201C,U+201D,U+2018,U+2019,U+2E3A,U+2014,U+2013,U+2026,U+00B7,U+007E,U+002F}:root{--vp-c-white: #ffffff;--vp-c-black: #000000;--vp-c-neutral: var(--vp-c-black);--vp-c-neutral-inverse: var(--vp-c-white)}.dark{--vp-c-neutral: var(--vp-c-white);--vp-c-neutral-inverse: var(--vp-c-black)}:root{--vp-c-gray-1: #dddde3;--vp-c-gray-2: #e4e4e9;--vp-c-gray-3: #ebebef;--vp-c-gray-soft: rgba(142, 150, 170, .14);--vp-c-indigo-1: #3451b2;--vp-c-indigo-2: #3a5ccc;--vp-c-indigo-3: #5672cd;--vp-c-indigo-soft: rgba(100, 108, 255, .14);--vp-c-purple-1: #6f42c1;--vp-c-purple-2: #7e4cc9;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .14);--vp-c-green-1: #18794e;--vp-c-green-2: #299764;--vp-c-green-3: #30a46c;--vp-c-green-soft: rgba(16, 185, 129, .14);--vp-c-yellow-1: #915930;--vp-c-yellow-2: #946300;--vp-c-yellow-3: #9f6a00;--vp-c-yellow-soft: rgba(234, 179, 8, .14);--vp-c-red-1: #b8272c;--vp-c-red-2: #d5393e;--vp-c-red-3: #e0575b;--vp-c-red-soft: rgba(244, 63, 94, .14);--vp-c-sponsor: #db2777}.dark{--vp-c-gray-1: #515c67;--vp-c-gray-2: #414853;--vp-c-gray-3: #32363f;--vp-c-gray-soft: rgba(101, 117, 133, .16);--vp-c-indigo-1: #a8b1ff;--vp-c-indigo-2: #5c73e7;--vp-c-indigo-3: #3e63dd;--vp-c-indigo-soft: rgba(100, 108, 255, .16);--vp-c-purple-1: #c8abfa;--vp-c-purple-2: #a879e6;--vp-c-purple-3: #8e5cd9;--vp-c-purple-soft: rgba(159, 122, 234, .16);--vp-c-green-1: #3dd68c;--vp-c-green-2: #30a46c;--vp-c-green-3: #298459;--vp-c-green-soft: rgba(16, 185, 129, .16);--vp-c-yellow-1: #f9b44e;--vp-c-yellow-2: #da8b17;--vp-c-yellow-3: #a46a0a;--vp-c-yellow-soft: rgba(234, 179, 8, .16);--vp-c-red-1: #f66f81;--vp-c-red-2: #f14158;--vp-c-red-3: #b62a3c;--vp-c-red-soft: rgba(244, 63, 94, .16)}:root{--vp-c-bg: #ffffff;--vp-c-bg-alt: #f6f6f7;--vp-c-bg-elv: #ffffff;--vp-c-bg-soft: #f6f6f7}.dark{--vp-c-bg: #1b1b1f;--vp-c-bg-alt: #161618;--vp-c-bg-elv: #202127;--vp-c-bg-soft: #202127}:root{--vp-c-border: #c2c2c4;--vp-c-divider: #e2e2e3;--vp-c-gutter: #e2e2e3}.dark{--vp-c-border: #3c3f44;--vp-c-divider: #2e2e32;--vp-c-gutter: #000000}:root{--vp-c-text-1: rgba(60, 60, 67);--vp-c-text-2: rgba(60, 60, 67, .78);--vp-c-text-3: rgba(60, 60, 67, .56)}.dark{--vp-c-text-1: rgba(255, 255, 245, .86);--vp-c-text-2: rgba(235, 235, 245, .6);--vp-c-text-3: rgba(235, 235, 245, .38)}:root{--vp-c-default-1: var(--vp-c-gray-1);--vp-c-default-2: var(--vp-c-gray-2);--vp-c-default-3: var(--vp-c-gray-3);--vp-c-default-soft: var(--vp-c-gray-soft);--vp-c-brand-1: var(--vp-c-indigo-1);--vp-c-brand-2: var(--vp-c-indigo-2);--vp-c-brand-3: var(--vp-c-indigo-3);--vp-c-brand-soft: var(--vp-c-indigo-soft);--vp-c-brand: var(--vp-c-brand-1);--vp-c-tip-1: var(--vp-c-brand-1);--vp-c-tip-2: var(--vp-c-brand-2);--vp-c-tip-3: var(--vp-c-brand-3);--vp-c-tip-soft: var(--vp-c-brand-soft);--vp-c-note-1: var(--vp-c-brand-1);--vp-c-note-2: var(--vp-c-brand-2);--vp-c-note-3: var(--vp-c-brand-3);--vp-c-note-soft: var(--vp-c-brand-soft);--vp-c-success-1: var(--vp-c-green-1);--vp-c-success-2: var(--vp-c-green-2);--vp-c-success-3: var(--vp-c-green-3);--vp-c-success-soft: var(--vp-c-green-soft);--vp-c-important-1: var(--vp-c-purple-1);--vp-c-important-2: var(--vp-c-purple-2);--vp-c-important-3: var(--vp-c-purple-3);--vp-c-important-soft: var(--vp-c-purple-soft);--vp-c-warning-1: var(--vp-c-yellow-1);--vp-c-warning-2: var(--vp-c-yellow-2);--vp-c-warning-3: var(--vp-c-yellow-3);--vp-c-warning-soft: var(--vp-c-yellow-soft);--vp-c-danger-1: var(--vp-c-red-1);--vp-c-danger-2: var(--vp-c-red-2);--vp-c-danger-3: var(--vp-c-red-3);--vp-c-danger-soft: var(--vp-c-red-soft);--vp-c-caution-1: var(--vp-c-red-1);--vp-c-caution-2: var(--vp-c-red-2);--vp-c-caution-3: var(--vp-c-red-3);--vp-c-caution-soft: var(--vp-c-red-soft)}:root{--vp-font-family-base: "Inter", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";--vp-font-family-mono: ui-monospace, "Menlo", "Monaco", "Consolas", "Liberation Mono", "Courier New", monospace;font-optical-sizing:auto}:root:where(:lang(zh)){--vp-font-family-base: "Punctuation SC", "Inter", ui-sans-serif, system-ui, "PingFang SC", "Noto Sans CJK SC", "Noto Sans SC", "Heiti SC", "Microsoft YaHei", "DengXian", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"}:root{--vp-shadow-1: 0 1px 2px rgba(0, 0, 0, .04), 0 1px 2px rgba(0, 0, 0, .06);--vp-shadow-2: 0 3px 12px rgba(0, 0, 0, .07), 0 1px 4px rgba(0, 0, 0, .07);--vp-shadow-3: 0 12px 32px rgba(0, 0, 0, .1), 0 2px 6px rgba(0, 0, 0, .08);--vp-shadow-4: 0 14px 44px rgba(0, 0, 0, .12), 0 3px 9px rgba(0, 0, 0, .12);--vp-shadow-5: 0 18px 56px rgba(0, 0, 0, .16), 0 4px 12px rgba(0, 0, 0, .16)}:root{--vp-z-index-footer: 10;--vp-z-index-local-nav: 20;--vp-z-index-nav: 30;--vp-z-index-layout-top: 40;--vp-z-index-backdrop: 50;--vp-z-index-sidebar: 60}@media (min-width: 960px){:root{--vp-z-index-sidebar: 25}}:root{--vp-layout-max-width: 1440px}:root{--vp-header-anchor-symbol: "#"}:root{--vp-code-line-height: 1.7;--vp-code-font-size: .875em;--vp-code-color: var(--vp-c-brand-1);--vp-code-link-color: var(--vp-c-brand-1);--vp-code-link-hover-color: var(--vp-c-brand-2);--vp-code-bg: var(--vp-c-default-soft);--vp-code-block-color: var(--vp-c-text-2);--vp-code-block-bg: var(--vp-c-bg-alt);--vp-code-block-divider-color: var(--vp-c-gutter);--vp-code-lang-color: var(--vp-c-text-3);--vp-code-line-highlight-color: var(--vp-c-default-soft);--vp-code-line-number-color: var(--vp-c-text-3);--vp-code-line-diff-add-color: var(--vp-c-success-soft);--vp-code-line-diff-add-symbol-color: var(--vp-c-success-1);--vp-code-line-diff-remove-color: var(--vp-c-danger-soft);--vp-code-line-diff-remove-symbol-color: var(--vp-c-danger-1);--vp-code-line-warning-color: var(--vp-c-warning-soft);--vp-code-line-error-color: var(--vp-c-danger-soft);--vp-code-copy-code-border-color: var(--vp-c-divider);--vp-code-copy-code-bg: var(--vp-c-bg-soft);--vp-code-copy-code-hover-border-color: var(--vp-c-divider);--vp-code-copy-code-hover-bg: var(--vp-c-bg);--vp-code-copy-code-active-text: var(--vp-c-text-2);--vp-code-copy-copied-text-content: "Copied";--vp-code-tab-divider: var(--vp-code-block-divider-color);--vp-code-tab-text-color: var(--vp-c-text-2);--vp-code-tab-bg: var(--vp-code-block-bg);--vp-code-tab-hover-text-color: var(--vp-c-text-1);--vp-code-tab-active-text-color: var(--vp-c-text-1);--vp-code-tab-active-bar-color: var(--vp-c-brand-1)}:root{--vp-button-brand-border: transparent;--vp-button-brand-text: var(--vp-c-white);--vp-button-brand-bg: var(--vp-c-brand-3);--vp-button-brand-hover-border: transparent;--vp-button-brand-hover-text: var(--vp-c-white);--vp-button-brand-hover-bg: var(--vp-c-brand-2);--vp-button-brand-active-border: transparent;--vp-button-brand-active-text: var(--vp-c-white);--vp-button-brand-active-bg: var(--vp-c-brand-1);--vp-button-alt-border: transparent;--vp-button-alt-text: var(--vp-c-text-1);--vp-button-alt-bg: var(--vp-c-default-3);--vp-button-alt-hover-border: transparent;--vp-button-alt-hover-text: var(--vp-c-text-1);--vp-button-alt-hover-bg: var(--vp-c-default-2);--vp-button-alt-active-border: transparent;--vp-button-alt-active-text: var(--vp-c-text-1);--vp-button-alt-active-bg: var(--vp-c-default-1);--vp-button-sponsor-border: var(--vp-c-text-2);--vp-button-sponsor-text: var(--vp-c-text-2);--vp-button-sponsor-bg: transparent;--vp-button-sponsor-hover-border: var(--vp-c-sponsor);--vp-button-sponsor-hover-text: var(--vp-c-sponsor);--vp-button-sponsor-hover-bg: transparent;--vp-button-sponsor-active-border: var(--vp-c-sponsor);--vp-button-sponsor-active-text: var(--vp-c-sponsor);--vp-button-sponsor-active-bg: transparent}:root{--vp-custom-block-font-size: 14px;--vp-custom-block-code-font-size: 13px;--vp-custom-block-info-border: transparent;--vp-custom-block-info-text: var(--vp-c-text-1);--vp-custom-block-info-bg: var(--vp-c-default-soft);--vp-custom-block-info-code-bg: var(--vp-c-default-soft);--vp-custom-block-note-border: transparent;--vp-custom-block-note-text: var(--vp-c-text-1);--vp-custom-block-note-bg: var(--vp-c-default-soft);--vp-custom-block-note-code-bg: var(--vp-c-default-soft);--vp-custom-block-tip-border: transparent;--vp-custom-block-tip-text: var(--vp-c-text-1);--vp-custom-block-tip-bg: var(--vp-c-tip-soft);--vp-custom-block-tip-code-bg: var(--vp-c-tip-soft);--vp-custom-block-important-border: transparent;--vp-custom-block-important-text: var(--vp-c-text-1);--vp-custom-block-important-bg: var(--vp-c-important-soft);--vp-custom-block-important-code-bg: var(--vp-c-important-soft);--vp-custom-block-warning-border: transparent;--vp-custom-block-warning-text: var(--vp-c-text-1);--vp-custom-block-warning-bg: var(--vp-c-warning-soft);--vp-custom-block-warning-code-bg: var(--vp-c-warning-soft);--vp-custom-block-danger-border: transparent;--vp-custom-block-danger-text: var(--vp-c-text-1);--vp-custom-block-danger-bg: var(--vp-c-danger-soft);--vp-custom-block-danger-code-bg: var(--vp-c-danger-soft);--vp-custom-block-caution-border: transparent;--vp-custom-block-caution-text: var(--vp-c-text-1);--vp-custom-block-caution-bg: var(--vp-c-caution-soft);--vp-custom-block-caution-code-bg: var(--vp-c-caution-soft);--vp-custom-block-details-border: var(--vp-custom-block-info-border);--vp-custom-block-details-text: var(--vp-custom-block-info-text);--vp-custom-block-details-bg: var(--vp-custom-block-info-bg);--vp-custom-block-details-code-bg: var(--vp-custom-block-info-code-bg)}:root{--vp-input-border-color: var(--vp-c-border);--vp-input-bg-color: var(--vp-c-bg-alt);--vp-input-switch-bg-color: var(--vp-c-default-soft)}:root{--vp-nav-height: 64px;--vp-nav-bg-color: var(--vp-c-bg);--vp-nav-screen-bg-color: var(--vp-c-bg);--vp-nav-logo-height: 24px}.hide-nav{--vp-nav-height: 0px}.hide-nav .VPSidebar{--vp-nav-height: 22px}:root{--vp-local-nav-bg-color: var(--vp-c-bg)}:root{--vp-sidebar-width: 272px;--vp-sidebar-bg-color: var(--vp-c-bg-alt)}:root{--vp-backdrop-bg-color: rgba(0, 0, 0, .6)}:root{--vp-home-hero-name-color: var(--vp-c-brand-1);--vp-home-hero-name-background: transparent;--vp-home-hero-image-background-image: none;--vp-home-hero-image-filter: none}:root{--vp-badge-info-border: transparent;--vp-badge-info-text: var(--vp-c-text-2);--vp-badge-info-bg: var(--vp-c-default-soft);--vp-badge-tip-border: transparent;--vp-badge-tip-text: var(--vp-c-tip-1);--vp-badge-tip-bg: var(--vp-c-tip-soft);--vp-badge-warning-border: transparent;--vp-badge-warning-text: var(--vp-c-warning-1);--vp-badge-warning-bg: var(--vp-c-warning-soft);--vp-badge-danger-border: transparent;--vp-badge-danger-text: var(--vp-c-danger-1);--vp-badge-danger-bg: var(--vp-c-danger-soft)}:root{--vp-carbon-ads-text-color: var(--vp-c-text-1);--vp-carbon-ads-poweredby-color: var(--vp-c-text-2);--vp-carbon-ads-bg-color: var(--vp-c-bg-soft);--vp-carbon-ads-hover-text-color: var(--vp-c-brand-1);--vp-carbon-ads-hover-poweredby-color: var(--vp-c-text-1)}:root{--vp-local-search-bg: var(--vp-c-bg);--vp-local-search-result-bg: var(--vp-c-bg);--vp-local-search-result-border: var(--vp-c-divider);--vp-local-search-result-selected-bg: var(--vp-c-bg);--vp-local-search-result-selected-border: var(--vp-c-brand-1);--vp-local-search-highlight-bg: var(--vp-c-brand-1);--vp-local-search-highlight-text: var(--vp-c-neutral-inverse)}@media (prefers-reduced-motion: reduce){*,:before,:after{animation-delay:-1ms!important;animation-duration:1ms!important;animation-iteration-count:1!important;background-attachment:initial!important;scroll-behavior:auto!important;transition-duration:0s!important;transition-delay:0s!important}}*,:before,:after{box-sizing:border-box}html{line-height:1.4;font-size:16px;-webkit-text-size-adjust:100%}html.dark{color-scheme:dark}body{margin:0;width:100%;min-width:320px;min-height:100vh;line-height:24px;font-family:var(--vp-font-family-base);font-size:16px;font-weight:400;color:var(--vp-c-text-1);background-color:var(--vp-c-bg);font-synthesis:style;text-rendering:optimizeLegibility;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}main{display:block}h1,h2,h3,h4,h5,h6{margin:0;line-height:24px;font-size:16px;font-weight:400}p{margin:0}strong,b{font-weight:600}a,area,button,[role=button],input,label,select,summary,textarea{touch-action:manipulation}a{color:inherit;text-decoration:inherit}ol,ul{list-style:none;margin:0;padding:0}blockquote{margin:0}pre,code,kbd,samp{font-family:var(--vp-font-family-mono)}img,svg,video,canvas,audio,iframe,embed,object{display:block}figure{margin:0}img,video{max-width:100%;height:auto}button,input,optgroup,select,textarea{border:0;padding:0;line-height:inherit;color:inherit}button{padding:0;font-family:inherit;background-color:transparent;background-image:none}button:enabled,[role=button]:enabled{cursor:pointer}button:focus,button:focus-visible{outline:1px dotted;outline:4px auto -webkit-focus-ring-color}button:focus:not(:focus-visible){outline:none!important}input:focus,textarea:focus,select:focus{outline:none}table{border-collapse:collapse}input{background-color:transparent}input:-ms-input-placeholder,textarea:-ms-input-placeholder{color:var(--vp-c-text-3)}input::-ms-input-placeholder,textarea::-ms-input-placeholder{color:var(--vp-c-text-3)}input::placeholder,textarea::placeholder{color:var(--vp-c-text-3)}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{-webkit-appearance:none;margin:0}input[type=number]{-moz-appearance:textfield}textarea{resize:vertical}select{-webkit-appearance:none}fieldset{margin:0;padding:0}h1,h2,h3,h4,h5,h6,li,p{overflow-wrap:break-word}vite-error-overlay{z-index:9999}mjx-container{overflow-x:auto}mjx-container>svg{display:inline-block;margin:auto}[class^=vpi-],[class*=" vpi-"],.vp-icon{width:1em;height:1em}[class^=vpi-].bg,[class*=" vpi-"].bg,.vp-icon.bg{background-size:100% 100%;background-color:transparent}[class^=vpi-]:not(.bg),[class*=" vpi-"]:not(.bg),.vp-icon:not(.bg){-webkit-mask:var(--icon) no-repeat;mask:var(--icon) no-repeat;-webkit-mask-size:100% 100%;mask-size:100% 100%;background-color:currentColor;color:inherit}.vpi-align-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M21 6H3M15 12H3M17 18H3'/%3E%3C/svg%3E")}.vpi-arrow-right,.vpi-arrow-down,.vpi-arrow-left,.vpi-arrow-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5l7 7-7 7'/%3E%3C/svg%3E")}.vpi-chevron-right,.vpi-chevron-down,.vpi-chevron-left,.vpi-chevron-up{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 18 6-6-6-6'/%3E%3C/svg%3E")}.vpi-chevron-down,.vpi-arrow-down{transform:rotate(90deg)}.vpi-chevron-left,.vpi-arrow-left{transform:rotate(180deg)}.vpi-chevron-up,.vpi-arrow-up{transform:rotate(-90deg)}.vpi-square-pen{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3H5a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7'/%3E%3Cpath d='M18.375 2.625a2.121 2.121 0 1 1 3 3L12 15l-4 1 1-4Z'/%3E%3C/svg%3E")}.vpi-plus{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M5 12h14M12 5v14'/%3E%3C/svg%3E")}.vpi-sun{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='4'/%3E%3Cpath d='M12 2v2M12 20v2M4.93 4.93l1.41 1.41M17.66 17.66l1.41 1.41M2 12h2M20 12h2M6.34 17.66l-1.41 1.41M19.07 4.93l-1.41 1.41'/%3E%3C/svg%3E")}.vpi-moon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z'/%3E%3C/svg%3E")}.vpi-more-horizontal{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='12' cy='12' r='1'/%3E%3Ccircle cx='19' cy='12' r='1'/%3E%3Ccircle cx='5' cy='12' r='1'/%3E%3C/svg%3E")}.vpi-languages{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m5 8 6 6M4 14l6-6 2-3M2 5h12M7 2h1M22 22l-5-10-5 10M14 18h6'/%3E%3C/svg%3E")}.vpi-heart{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M19 14c1.49-1.46 3-3.21 3-5.5A5.5 5.5 0 0 0 16.5 3c-1.76 0-3 .5-4.5 2-1.5-1.5-2.74-2-4.5-2A5.5 5.5 0 0 0 2 8.5c0 2.3 1.5 4.05 3 5.5l7 7Z'/%3E%3C/svg%3E")}.vpi-search{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Ccircle cx='11' cy='11' r='8'/%3E%3Cpath d='m21 21-4.3-4.3'/%3E%3C/svg%3E")}.vpi-layout-list{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='7' height='7' x='3' y='3' rx='1'/%3E%3Crect width='7' height='7' x='3' y='14' rx='1'/%3E%3Cpath d='M14 4h7M14 9h7M14 15h7M14 20h7'/%3E%3C/svg%3E")}.vpi-delete{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='M20 5H9l-7 7 7 7h11a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2ZM18 9l-6 6M12 9l6 6'/%3E%3C/svg%3E")}.vpi-corner-down-left{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Cpath d='m9 10-5 5 5 5'/%3E%3Cpath d='M20 4v7a4 4 0 0 1-4 4H4'/%3E%3C/svg%3E")}:root{--vp-icon-copy: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3C/svg%3E");--vp-icon-copied: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' stroke='rgba(128,128,128,1)' stroke-linecap='round' stroke-linejoin='round' stroke-width='2' viewBox='0 0 24 24'%3E%3Crect width='8' height='4' x='8' y='2' rx='1' ry='1'/%3E%3Cpath d='M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2'/%3E%3Cpath d='m9 14 2 2 4-4'/%3E%3C/svg%3E")}.vpi-social-discord{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.317 4.37a19.791 19.791 0 0 0-4.885-1.515.074.074 0 0 0-.079.037c-.21.375-.444.864-.608 1.25a18.27 18.27 0 0 0-5.487 0 12.64 12.64 0 0 0-.617-1.25.077.077 0 0 0-.079-.037A19.736 19.736 0 0 0 3.677 4.37a.07.07 0 0 0-.032.027C.533 9.046-.32 13.58.099 18.057a.082.082 0 0 0 .031.057 19.9 19.9 0 0 0 5.993 3.03.078.078 0 0 0 .084-.028c.462-.63.874-1.295 1.226-1.994a.076.076 0 0 0-.041-.106 13.107 13.107 0 0 1-1.872-.892.077.077 0 0 1-.008-.128 10.2 10.2 0 0 0 .372-.292.074.074 0 0 1 .077-.01c3.928 1.793 8.18 1.793 12.062 0a.074.074 0 0 1 .078.01c.12.098.246.198.373.292a.077.077 0 0 1-.006.127 12.299 12.299 0 0 1-1.873.892.077.077 0 0 0-.041.107c.36.698.772 1.362 1.225 1.993a.076.076 0 0 0 .084.028 19.839 19.839 0 0 0 6.002-3.03.077.077 0 0 0 .032-.054c.5-5.177-.838-9.674-3.549-13.66a.061.061 0 0 0-.031-.03zM8.02 15.33c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.956-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.956 2.418-2.157 2.418zm7.975 0c-1.183 0-2.157-1.085-2.157-2.419 0-1.333.955-2.419 2.157-2.419 1.21 0 2.176 1.096 2.157 2.42 0 1.333-.946 2.418-2.157 2.418Z'/%3E%3C/svg%3E")}.vpi-social-facebook{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z'/%3E%3C/svg%3E")}.vpi-social-github{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12'/%3E%3C/svg%3E")}.vpi-social-instagram{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M7.03.084c-1.277.06-2.149.264-2.91.563a5.874 5.874 0 0 0-2.124 1.388 5.878 5.878 0 0 0-1.38 2.127C.321 4.926.12 5.8.064 7.076.008 8.354-.005 8.764.001 12.023c.007 3.259.021 3.667.083 4.947.061 1.277.264 2.149.563 2.911.308.789.72 1.457 1.388 2.123a5.872 5.872 0 0 0 2.129 1.38c.763.295 1.636.496 2.913.552 1.278.056 1.689.069 4.947.063 3.257-.007 3.668-.021 4.947-.082 1.28-.06 2.147-.265 2.91-.563a5.881 5.881 0 0 0 2.123-1.388 5.881 5.881 0 0 0 1.38-2.129c.295-.763.496-1.636.551-2.912.056-1.28.07-1.69.063-4.948-.006-3.258-.02-3.667-.081-4.947-.06-1.28-.264-2.148-.564-2.911a5.892 5.892 0 0 0-1.387-2.123 5.857 5.857 0 0 0-2.128-1.38C19.074.322 18.202.12 16.924.066 15.647.009 15.236-.006 11.977 0 8.718.008 8.31.021 7.03.084m.14 21.693c-1.17-.05-1.805-.245-2.228-.408a3.736 3.736 0 0 1-1.382-.895 3.695 3.695 0 0 1-.9-1.378c-.165-.423-.363-1.058-.417-2.228-.06-1.264-.072-1.644-.08-4.848-.006-3.204.006-3.583.061-4.848.05-1.169.246-1.805.408-2.228.216-.561.477-.96.895-1.382a3.705 3.705 0 0 1 1.379-.9c.423-.165 1.057-.361 2.227-.417 1.265-.06 1.644-.072 4.848-.08 3.203-.006 3.583.006 4.85.062 1.168.05 1.804.244 2.227.408.56.216.96.475 1.382.895.421.42.681.817.9 1.378.165.422.362 1.056.417 2.227.06 1.265.074 1.645.08 4.848.005 3.203-.006 3.583-.061 4.848-.051 1.17-.245 1.805-.408 2.23-.216.56-.477.96-.896 1.38a3.705 3.705 0 0 1-1.378.9c-.422.165-1.058.362-2.226.418-1.266.06-1.645.072-4.85.079-3.204.007-3.582-.006-4.848-.06m9.783-16.192a1.44 1.44 0 1 0 1.437-1.442 1.44 1.44 0 0 0-1.437 1.442M5.839 12.012a6.161 6.161 0 1 0 12.323-.024 6.162 6.162 0 0 0-12.323.024M8 12.008A4 4 0 1 1 12.008 16 4 4 0 0 1 8 12.008'/%3E%3C/svg%3E")}.vpi-social-linkedin{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 0 1-2.063-2.065 2.064 2.064 0 1 1 2.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z'/%3E%3C/svg%3E")}.vpi-social-mastodon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.268 5.313c-.35-2.578-2.617-4.61-5.304-5.004C17.51.242 15.792 0 11.813 0h-.03c-3.98 0-4.835.242-5.288.309C3.882.692 1.496 2.518.917 5.127.64 6.412.61 7.837.661 9.143c.074 1.874.088 3.745.26 5.611.118 1.24.325 2.47.62 3.68.55 2.237 2.777 4.098 4.96 4.857 2.336.792 4.849.923 7.256.38.265-.061.527-.132.786-.213.585-.184 1.27-.39 1.774-.753a.057.057 0 0 0 .023-.043v-1.809a.052.052 0 0 0-.02-.041.053.053 0 0 0-.046-.01 20.282 20.282 0 0 1-4.709.545c-2.73 0-3.463-1.284-3.674-1.818a5.593 5.593 0 0 1-.319-1.433.053.053 0 0 1 .066-.054c1.517.363 3.072.546 4.632.546.376 0 .75 0 1.125-.01 1.57-.044 3.224-.124 4.768-.422.038-.008.077-.015.11-.024 2.435-.464 4.753-1.92 4.989-5.604.008-.145.03-1.52.03-1.67.002-.512.167-3.63-.024-5.545zm-3.748 9.195h-2.561V8.29c0-1.309-.55-1.976-1.67-1.976-1.23 0-1.846.79-1.846 2.35v3.403h-2.546V8.663c0-1.56-.617-2.35-1.848-2.35-1.112 0-1.668.668-1.67 1.977v6.218H4.822V8.102c0-1.31.337-2.35 1.011-3.12.696-.77 1.608-1.164 2.74-1.164 1.311 0 2.302.5 2.962 1.498l.638 1.06.638-1.06c.66-.999 1.65-1.498 2.96-1.498 1.13 0 2.043.395 2.74 1.164.675.77 1.012 1.81 1.012 3.12z'/%3E%3C/svg%3E")}.vpi-social-npm{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z'/%3E%3C/svg%3E")}.vpi-social-slack{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zm1.271 0a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zm0 1.271a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zm10.122 2.521a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zm-1.268 0a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zm-2.523 10.122a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zm0-1.268a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z'/%3E%3C/svg%3E")}.vpi-social-twitter,.vpi-social-x{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z'/%3E%3C/svg%3E")}.vpi-social-youtube{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath d='M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z'/%3E%3C/svg%3E")}.visually-hidden{position:absolute;width:1px;height:1px;white-space:nowrap;clip:rect(0 0 0 0);clip-path:inset(50%);overflow:hidden}.custom-block{border:1px solid transparent;border-radius:8px;padding:16px 16px 8px;line-height:24px;font-size:var(--vp-custom-block-font-size);color:var(--vp-c-text-2)}.custom-block.info{border-color:var(--vp-custom-block-info-border);color:var(--vp-custom-block-info-text);background-color:var(--vp-custom-block-info-bg)}.custom-block.info a,.custom-block.info code{color:var(--vp-c-brand-1)}.custom-block.info a:hover,.custom-block.info a:hover>code{color:var(--vp-c-brand-2)}.custom-block.info code{background-color:var(--vp-custom-block-info-code-bg)}.custom-block.note{border-color:var(--vp-custom-block-note-border);color:var(--vp-custom-block-note-text);background-color:var(--vp-custom-block-note-bg)}.custom-block.note a,.custom-block.note code{color:var(--vp-c-brand-1)}.custom-block.note a:hover,.custom-block.note a:hover>code{color:var(--vp-c-brand-2)}.custom-block.note code{background-color:var(--vp-custom-block-note-code-bg)}.custom-block.tip{border-color:var(--vp-custom-block-tip-border);color:var(--vp-custom-block-tip-text);background-color:var(--vp-custom-block-tip-bg)}.custom-block.tip a,.custom-block.tip code{color:var(--vp-c-tip-1)}.custom-block.tip a:hover,.custom-block.tip a:hover>code{color:var(--vp-c-tip-2)}.custom-block.tip code{background-color:var(--vp-custom-block-tip-code-bg)}.custom-block.important{border-color:var(--vp-custom-block-important-border);color:var(--vp-custom-block-important-text);background-color:var(--vp-custom-block-important-bg)}.custom-block.important a,.custom-block.important code{color:var(--vp-c-important-1)}.custom-block.important a:hover,.custom-block.important a:hover>code{color:var(--vp-c-important-2)}.custom-block.important code{background-color:var(--vp-custom-block-important-code-bg)}.custom-block.warning{border-color:var(--vp-custom-block-warning-border);color:var(--vp-custom-block-warning-text);background-color:var(--vp-custom-block-warning-bg)}.custom-block.warning a,.custom-block.warning code{color:var(--vp-c-warning-1)}.custom-block.warning a:hover,.custom-block.warning a:hover>code{color:var(--vp-c-warning-2)}.custom-block.warning code{background-color:var(--vp-custom-block-warning-code-bg)}.custom-block.danger{border-color:var(--vp-custom-block-danger-border);color:var(--vp-custom-block-danger-text);background-color:var(--vp-custom-block-danger-bg)}.custom-block.danger a,.custom-block.danger code{color:var(--vp-c-danger-1)}.custom-block.danger a:hover,.custom-block.danger a:hover>code{color:var(--vp-c-danger-2)}.custom-block.danger code{background-color:var(--vp-custom-block-danger-code-bg)}.custom-block.caution{border-color:var(--vp-custom-block-caution-border);color:var(--vp-custom-block-caution-text);background-color:var(--vp-custom-block-caution-bg)}.custom-block.caution a,.custom-block.caution code{color:var(--vp-c-caution-1)}.custom-block.caution a:hover,.custom-block.caution a:hover>code{color:var(--vp-c-caution-2)}.custom-block.caution code{background-color:var(--vp-custom-block-caution-code-bg)}.custom-block.details{border-color:var(--vp-custom-block-details-border);color:var(--vp-custom-block-details-text);background-color:var(--vp-custom-block-details-bg)}.custom-block.details a{color:var(--vp-c-brand-1)}.custom-block.details a:hover,.custom-block.details a:hover>code{color:var(--vp-c-brand-2)}.custom-block.details code{background-color:var(--vp-custom-block-details-code-bg)}.custom-block-title{font-weight:600}.custom-block p+p{margin:8px 0}.custom-block.details summary{margin:0 0 8px;font-weight:700;cursor:pointer;-webkit-user-select:none;user-select:none}.custom-block.details summary+p{margin:8px 0}.custom-block a{color:inherit;font-weight:600;text-decoration:underline;text-underline-offset:2px;transition:opacity .25s}.custom-block a:hover{opacity:.75}.custom-block code{font-size:var(--vp-custom-block-code-font-size)}.custom-block.custom-block th,.custom-block.custom-block blockquote>p{font-size:var(--vp-custom-block-font-size);color:inherit}.dark .vp-code span{color:var(--shiki-dark, inherit)}html:not(.dark) .vp-code span{color:var(--shiki-light, inherit)}.vp-code-group{margin-top:16px}.vp-code-group .tabs{position:relative;display:flex;margin-right:-24px;margin-left:-24px;padding:0 12px;background-color:var(--vp-code-tab-bg);overflow-x:auto;overflow-y:hidden;box-shadow:inset 0 -1px var(--vp-code-tab-divider)}@media (min-width: 640px){.vp-code-group .tabs{margin-right:0;margin-left:0;border-radius:8px 8px 0 0}}.vp-code-group .tabs input{position:fixed;opacity:0;pointer-events:none}.vp-code-group .tabs label{position:relative;display:inline-block;border-bottom:1px solid transparent;padding:0 12px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-code-tab-text-color);white-space:nowrap;cursor:pointer;transition:color .25s}.vp-code-group .tabs label:after{position:absolute;right:8px;bottom:-1px;left:8px;z-index:1;height:2px;border-radius:2px;content:"";background-color:transparent;transition:background-color .25s}.vp-code-group label:hover{color:var(--vp-code-tab-hover-text-color)}.vp-code-group input:checked+label{color:var(--vp-code-tab-active-text-color)}.vp-code-group input:checked+label:after{background-color:var(--vp-code-tab-active-bar-color)}.vp-code-group div[class*=language-],.vp-block{display:none;margin-top:0!important;border-top-left-radius:0!important;border-top-right-radius:0!important}.vp-code-group div[class*=language-].active,.vp-block.active{display:block}.vp-block{padding:20px 24px}.vp-doc h1,.vp-doc h2,.vp-doc h3,.vp-doc h4,.vp-doc h5,.vp-doc h6{position:relative;font-weight:600;outline:none}.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:28px}.vp-doc h2{margin:48px 0 16px;border-top:1px solid var(--vp-c-divider);padding-top:24px;letter-spacing:-.02em;line-height:32px;font-size:24px}.vp-doc h3{margin:32px 0 0;letter-spacing:-.01em;line-height:28px;font-size:20px}.vp-doc .header-anchor{position:absolute;top:0;left:0;margin-left:-.87em;font-weight:500;-webkit-user-select:none;user-select:none;opacity:0;text-decoration:none;transition:color .25s,opacity .25s}.vp-doc .header-anchor:before{content:var(--vp-header-anchor-symbol)}.vp-doc h1:hover .header-anchor,.vp-doc h1 .header-anchor:focus,.vp-doc h2:hover .header-anchor,.vp-doc h2 .header-anchor:focus,.vp-doc h3:hover .header-anchor,.vp-doc h3 .header-anchor:focus,.vp-doc h4:hover .header-anchor,.vp-doc h4 .header-anchor:focus,.vp-doc h5:hover .header-anchor,.vp-doc h5 .header-anchor:focus,.vp-doc h6:hover .header-anchor,.vp-doc h6 .header-anchor:focus{opacity:1}@media (min-width: 768px){.vp-doc h1{letter-spacing:-.02em;line-height:40px;font-size:32px}}.vp-doc h2 .header-anchor{top:24px}.vp-doc p,.vp-doc summary{margin:16px 0}.vp-doc p{line-height:28px}.vp-doc blockquote{margin:16px 0;border-left:2px solid var(--vp-c-divider);padding-left:16px;transition:border-color .5s}.vp-doc blockquote>p{margin:0;font-size:16px;color:var(--vp-c-text-2);transition:color .5s}.vp-doc a{font-weight:500;color:var(--vp-c-brand-1);text-decoration:underline;text-underline-offset:2px;transition:color .25s,opacity .25s}.vp-doc a:hover{color:var(--vp-c-brand-2)}.vp-doc strong{font-weight:600}.vp-doc ul,.vp-doc ol{padding-left:1.25rem;margin:16px 0}.vp-doc ul{list-style:disc}.vp-doc ol{list-style:decimal}.vp-doc li+li{margin-top:8px}.vp-doc li>ol,.vp-doc li>ul{margin:8px 0 0}.vp-doc table{display:block;border-collapse:collapse;margin:20px 0;overflow-x:auto}.vp-doc tr{background-color:var(--vp-c-bg);border-top:1px solid var(--vp-c-divider);transition:background-color .5s}.vp-doc tr:nth-child(2n){background-color:var(--vp-c-bg-soft)}.vp-doc th,.vp-doc td{border:1px solid var(--vp-c-divider);padding:8px 16px}.vp-doc th{text-align:left;font-size:14px;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-doc td{font-size:14px}.vp-doc hr{margin:16px 0;border:none;border-top:1px solid var(--vp-c-divider)}.vp-doc .custom-block{margin:16px 0}.vp-doc .custom-block p{margin:8px 0;line-height:24px}.vp-doc .custom-block p:first-child{margin:0}.vp-doc .custom-block div[class*=language-]{margin:8px 0;border-radius:8px}.vp-doc .custom-block div[class*=language-] code{font-weight:400;background-color:transparent}.vp-doc .custom-block .vp-code-group .tabs{margin:0;border-radius:8px 8px 0 0}.vp-doc :not(pre,h1,h2,h3,h4,h5,h6)>code{font-size:var(--vp-code-font-size);color:var(--vp-code-color)}.vp-doc :not(pre)>code{border-radius:4px;padding:3px 6px;background-color:var(--vp-code-bg);transition:color .25s,background-color .5s}.vp-doc a>code{color:var(--vp-code-link-color)}.vp-doc a:hover>code{color:var(--vp-code-link-hover-color)}.vp-doc h1>code,.vp-doc h2>code,.vp-doc h3>code{font-size:.9em}.vp-doc div[class*=language-],.vp-block{position:relative;margin:16px -24px;background-color:var(--vp-code-block-bg);overflow-x:auto;transition:background-color .5s}@media (min-width: 640px){.vp-doc div[class*=language-],.vp-block{border-radius:8px;margin:16px 0}}@media (max-width: 639px){.vp-doc li div[class*=language-]{border-radius:8px 0 0 8px}}.vp-doc div[class*=language-]+div[class*=language-],.vp-doc div[class$=-api]+div[class*=language-],.vp-doc div[class*=language-]+div[class$=-api]>div[class*=language-]{margin-top:-8px}.vp-doc [class*=language-] pre,.vp-doc [class*=language-] code{direction:ltr;text-align:left;white-space:pre;word-spacing:normal;word-break:normal;word-wrap:normal;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-hyphens:none;-moz-hyphens:none;-ms-hyphens:none;hyphens:none}.vp-doc [class*=language-] pre{position:relative;z-index:1;margin:0;padding:20px 0;background:transparent;overflow-x:auto}.vp-doc [class*=language-] code{display:block;padding:0 24px;width:fit-content;min-width:100%;line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-block-color);transition:color .5s}.vp-doc [class*=language-] code .highlighted{background-color:var(--vp-code-line-highlight-color);transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .highlighted.error{background-color:var(--vp-code-line-error-color)}.vp-doc [class*=language-] code .highlighted.warning{background-color:var(--vp-code-line-warning-color)}.vp-doc [class*=language-] code .diff{transition:background-color .5s;margin:0 -24px;padding:0 24px;width:calc(100% + 48px);display:inline-block}.vp-doc [class*=language-] code .diff:before{position:absolute;left:10px}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){filter:blur(.095rem);opacity:.4;transition:filter .35s,opacity .35s}.vp-doc [class*=language-] .has-focused-lines .line:not(.has-focus){opacity:.7;transition:filter .35s,opacity .35s}.vp-doc [class*=language-]:hover .has-focused-lines .line:not(.has-focus){filter:blur(0);opacity:1}.vp-doc [class*=language-] code .diff.remove{background-color:var(--vp-code-line-diff-remove-color);opacity:.7}.vp-doc [class*=language-] code .diff.remove:before{content:"-";color:var(--vp-code-line-diff-remove-symbol-color)}.vp-doc [class*=language-] code .diff.add{background-color:var(--vp-code-line-diff-add-color)}.vp-doc [class*=language-] code .diff.add:before{content:"+";color:var(--vp-code-line-diff-add-symbol-color)}.vp-doc div[class*=language-].line-numbers-mode{padding-left:32px}.vp-doc .line-numbers-wrapper{position:absolute;top:0;bottom:0;left:0;z-index:3;border-right:1px solid var(--vp-code-block-divider-color);padding-top:20px;width:32px;text-align:center;font-family:var(--vp-font-family-mono);line-height:var(--vp-code-line-height);font-size:var(--vp-code-font-size);color:var(--vp-code-line-number-color);transition:border-color .5s,color .5s}.vp-doc [class*=language-]>button.copy{direction:ltr;position:absolute;top:12px;right:12px;z-index:3;border:1px solid var(--vp-code-copy-code-border-color);border-radius:4px;width:40px;height:40px;background-color:var(--vp-code-copy-code-bg);opacity:0;cursor:pointer;background-image:var(--vp-icon-copy);background-position:50%;background-size:20px;background-repeat:no-repeat;transition:border-color .25s,background-color .25s,opacity .25s}.vp-doc [class*=language-]:hover>button.copy,.vp-doc [class*=language-]>button.copy:focus{opacity:1}.vp-doc [class*=language-]>button.copy:hover,.vp-doc [class*=language-]>button.copy.copied{border-color:var(--vp-code-copy-code-hover-border-color);background-color:var(--vp-code-copy-code-hover-bg)}.vp-doc [class*=language-]>button.copy.copied,.vp-doc [class*=language-]>button.copy:hover.copied{border-radius:0 4px 4px 0;background-color:var(--vp-code-copy-code-hover-bg);background-image:var(--vp-icon-copied)}.vp-doc [class*=language-]>button.copy.copied:before,.vp-doc [class*=language-]>button.copy:hover.copied:before{position:relative;top:-1px;transform:translate(calc(-100% - 1px));display:flex;justify-content:center;align-items:center;border:1px solid var(--vp-code-copy-code-hover-border-color);border-right:0;border-radius:4px 0 0 4px;padding:0 10px;width:fit-content;height:40px;text-align:center;font-size:12px;font-weight:500;color:var(--vp-code-copy-code-active-text);background-color:var(--vp-code-copy-code-hover-bg);white-space:nowrap;content:var(--vp-code-copy-copied-text-content)}.vp-doc [class*=language-]>span.lang{position:absolute;top:2px;right:8px;z-index:2;font-size:12px;font-weight:500;color:var(--vp-code-lang-color);transition:color .4s,opacity .4s}.vp-doc [class*=language-]:hover>button.copy+span.lang,.vp-doc [class*=language-]>button.copy:focus+span.lang{opacity:0}.vp-doc .VPTeamMembers{margin-top:24px}.vp-doc .VPTeamMembers.small.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}.vp-doc .VPTeamMembers.small.count-2 .container,.vp-doc .VPTeamMembers.small.count-3 .container{max-width:100%!important}.vp-doc .VPTeamMembers.medium.count-1 .container{margin:0!important;max-width:calc((100% - 24px)/2)!important}:is(.vp-external-link-icon,.vp-doc a[href*="://"],.vp-doc a[target=_blank]):not(.no-icon):after{display:inline-block;margin-top:-1px;margin-left:4px;width:11px;height:11px;background:currentColor;color:var(--vp-c-text-3);flex-shrink:0;--icon: url("data:image/svg+xml, %3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' %3E%3Cpath d='M0 0h24v24H0V0z' fill='none' /%3E%3Cpath d='M9 5v2h6.59L4 18.59 5.41 20 17 8.41V15h2V5H9z' /%3E%3C/svg%3E");-webkit-mask-image:var(--icon);mask-image:var(--icon)}.vp-external-link-icon:after{content:""}.external-link-icon-enabled :is(.vp-doc a[href*="://"],.vp-doc a[target=_blank]):after{content:"";color:currentColor}.vp-sponsor{border-radius:16px;overflow:hidden}.vp-sponsor.aside{border-radius:12px}.vp-sponsor-section+.vp-sponsor-section{margin-top:4px}.vp-sponsor-tier{margin:0 0 4px!important;text-align:center;letter-spacing:1px!important;line-height:24px;width:100%;font-weight:600;color:var(--vp-c-text-2);background-color:var(--vp-c-bg-soft)}.vp-sponsor.normal .vp-sponsor-tier{padding:13px 0 11px;font-size:14px}.vp-sponsor.aside .vp-sponsor-tier{padding:9px 0 7px;font-size:12px}.vp-sponsor-grid+.vp-sponsor-tier{margin-top:4px}.vp-sponsor-grid{display:flex;flex-wrap:wrap;gap:4px}.vp-sponsor-grid.xmini .vp-sponsor-grid-link{height:64px}.vp-sponsor-grid.xmini .vp-sponsor-grid-image{max-width:64px;max-height:22px}.vp-sponsor-grid.mini .vp-sponsor-grid-link{height:72px}.vp-sponsor-grid.mini .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.small .vp-sponsor-grid-link{height:96px}.vp-sponsor-grid.small .vp-sponsor-grid-image{max-width:96px;max-height:24px}.vp-sponsor-grid.medium .vp-sponsor-grid-link{height:112px}.vp-sponsor-grid.medium .vp-sponsor-grid-image{max-width:120px;max-height:36px}.vp-sponsor-grid.big .vp-sponsor-grid-link{height:184px}.vp-sponsor-grid.big .vp-sponsor-grid-image{max-width:192px;max-height:56px}.vp-sponsor-grid[data-vp-grid="2"] .vp-sponsor-grid-item{width:calc((100% - 4px)/2)}.vp-sponsor-grid[data-vp-grid="3"] .vp-sponsor-grid-item{width:calc((100% - 4px * 2) / 3)}.vp-sponsor-grid[data-vp-grid="4"] .vp-sponsor-grid-item{width:calc((100% - 12px)/4)}.vp-sponsor-grid[data-vp-grid="5"] .vp-sponsor-grid-item{width:calc((100% - 16px)/5)}.vp-sponsor-grid[data-vp-grid="6"] .vp-sponsor-grid-item{width:calc((100% - 4px * 5) / 6)}.vp-sponsor-grid-item{flex-shrink:0;width:100%;background-color:var(--vp-c-bg-soft);transition:background-color .25s}.vp-sponsor-grid-item:hover{background-color:var(--vp-c-default-soft)}.vp-sponsor-grid-item:hover .vp-sponsor-grid-image{filter:grayscale(0) invert(0)}.vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.dark .vp-sponsor-grid-item:hover{background-color:var(--vp-c-white)}.dark .vp-sponsor-grid-item.empty:hover{background-color:var(--vp-c-bg-soft)}.vp-sponsor-grid-link{display:flex}.vp-sponsor-grid-box{display:flex;justify-content:center;align-items:center;width:100%}.vp-sponsor-grid-image{max-width:100%;filter:grayscale(1);transition:filter .25s}.dark .vp-sponsor-grid-image{filter:grayscale(1) invert(1)}.VPBadge{display:inline-block;margin-left:2px;border:1px solid transparent;border-radius:12px;padding:0 10px;line-height:22px;font-size:12px;font-weight:500;transform:translateY(-2px)}.VPBadge.small{padding:0 6px;line-height:18px;font-size:10px;transform:translateY(-8px)}.VPDocFooter .VPBadge{display:none}.vp-doc h1>.VPBadge{margin-top:4px;vertical-align:top}.vp-doc h2>.VPBadge{margin-top:3px;padding:0 8px;vertical-align:top}.vp-doc h3>.VPBadge{vertical-align:middle}.vp-doc h4>.VPBadge,.vp-doc h5>.VPBadge,.vp-doc h6>.VPBadge{vertical-align:middle;line-height:18px}.VPBadge.info{border-color:var(--vp-badge-info-border);color:var(--vp-badge-info-text);background-color:var(--vp-badge-info-bg)}.VPBadge.tip{border-color:var(--vp-badge-tip-border);color:var(--vp-badge-tip-text);background-color:var(--vp-badge-tip-bg)}.VPBadge.warning{border-color:var(--vp-badge-warning-border);color:var(--vp-badge-warning-text);background-color:var(--vp-badge-warning-bg)}.VPBadge.danger{border-color:var(--vp-badge-danger-border);color:var(--vp-badge-danger-text);background-color:var(--vp-badge-danger-bg)}.VPBackdrop[data-v-c79a1216]{position:fixed;top:0;right:0;bottom:0;left:0;z-index:var(--vp-z-index-backdrop);background:var(--vp-backdrop-bg-color);transition:opacity .5s}.VPBackdrop.fade-enter-from[data-v-c79a1216],.VPBackdrop.fade-leave-to[data-v-c79a1216]{opacity:0}.VPBackdrop.fade-leave-active[data-v-c79a1216]{transition-duration:.25s}@media (min-width: 1280px){.VPBackdrop[data-v-c79a1216]{display:none}}.NotFound[data-v-d6be1790]{padding:64px 24px 96px;text-align:center}@media (min-width: 768px){.NotFound[data-v-d6be1790]{padding:96px 32px 168px}}.code[data-v-d6be1790]{line-height:64px;font-size:64px;font-weight:600}.title[data-v-d6be1790]{padding-top:12px;letter-spacing:2px;line-height:20px;font-size:20px;font-weight:700}.divider[data-v-d6be1790]{margin:24px auto 18px;width:64px;height:1px;background-color:var(--vp-c-divider)}.quote[data-v-d6be1790]{margin:0 auto;max-width:256px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.action[data-v-d6be1790]{padding-top:20px}.link[data-v-d6be1790]{display:inline-block;border:1px solid var(--vp-c-brand-1);border-radius:16px;padding:3px 16px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:border-color .25s,color .25s}.link[data-v-d6be1790]:hover{border-color:var(--vp-c-brand-2);color:var(--vp-c-brand-2)}.root[data-v-b933a997]{position:relative;z-index:1}.nested[data-v-b933a997]{padding-right:16px;padding-left:16px}.outline-link[data-v-b933a997]{display:block;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-2);white-space:nowrap;overflow:hidden;text-overflow:ellipsis;transition:color .5s}.outline-link[data-v-b933a997]:hover,.outline-link.active[data-v-b933a997]{color:var(--vp-c-text-1);transition:color .25s}.outline-link.nested[data-v-b933a997]{padding-left:13px}.VPDocAsideOutline[data-v-a5bbad30]{display:none}.VPDocAsideOutline.has-outline[data-v-a5bbad30]{display:block}.content[data-v-a5bbad30]{position:relative;border-left:1px solid var(--vp-c-divider);padding-left:16px;font-size:13px;font-weight:500}.outline-marker[data-v-a5bbad30]{position:absolute;top:32px;left:-1px;z-index:0;opacity:0;width:2px;border-radius:2px;height:18px;background-color:var(--vp-c-brand-1);transition:top .25s cubic-bezier(0,1,.5,1),background-color .5s,opacity .25s}.outline-title[data-v-a5bbad30]{line-height:32px;font-size:14px;font-weight:600}.VPDocAside[data-v-3f215769]{display:flex;flex-direction:column;flex-grow:1}.spacer[data-v-3f215769]{flex-grow:1}.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideSponsors,.VPDocAside[data-v-3f215769] .spacer+.VPDocAsideCarbonAds{margin-top:24px}.VPDocAside[data-v-3f215769] .VPDocAsideSponsors+.VPDocAsideCarbonAds{margin-top:16px}.VPLastUpdated[data-v-7e05ebdb]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 640px){.VPLastUpdated[data-v-7e05ebdb]{line-height:32px;font-size:14px;font-weight:500}}.VPDocFooter[data-v-d4a0bba5]{margin-top:64px}.edit-info[data-v-d4a0bba5]{padding-bottom:18px}@media (min-width: 640px){.edit-info[data-v-d4a0bba5]{display:flex;justify-content:space-between;align-items:center;padding-bottom:14px}}.edit-link-button[data-v-d4a0bba5]{display:flex;align-items:center;border:0;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.edit-link-button[data-v-d4a0bba5]:hover{color:var(--vp-c-brand-2)}.edit-link-icon[data-v-d4a0bba5]{margin-right:8px}.prev-next[data-v-d4a0bba5]{border-top:1px solid var(--vp-c-divider);padding-top:24px;display:grid;grid-row-gap:8px}@media (min-width: 640px){.prev-next[data-v-d4a0bba5]{grid-template-columns:repeat(2,1fr);grid-column-gap:16px}}.pager-link[data-v-d4a0bba5]{display:block;border:1px solid var(--vp-c-divider);border-radius:8px;padding:11px 16px 13px;width:100%;height:100%;transition:border-color .25s}.pager-link[data-v-d4a0bba5]:hover{border-color:var(--vp-c-brand-1)}.pager-link.next[data-v-d4a0bba5]{margin-left:auto;text-align:right}.desc[data-v-d4a0bba5]{display:block;line-height:20px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.title[data-v-d4a0bba5]{display:block;line-height:20px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1);transition:color .25s}.VPDoc[data-v-39a288b8]{padding:32px 24px 96px;width:100%}@media (min-width: 768px){.VPDoc[data-v-39a288b8]{padding:48px 32px 128px}}@media (min-width: 960px){.VPDoc[data-v-39a288b8]{padding:48px 32px 0}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{display:flex;justify-content:center;max-width:992px}.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:752px}}@media (min-width: 1280px){.VPDoc .container[data-v-39a288b8]{display:flex;justify-content:center}.VPDoc .aside[data-v-39a288b8]{display:block}}@media (min-width: 1440px){.VPDoc:not(.has-sidebar) .content[data-v-39a288b8]{max-width:784px}.VPDoc:not(.has-sidebar) .container[data-v-39a288b8]{max-width:1104px}}.container[data-v-39a288b8]{margin:0 auto;width:100%}.aside[data-v-39a288b8]{position:relative;display:none;order:2;flex-grow:1;padding-left:32px;width:100%;max-width:256px}.left-aside[data-v-39a288b8]{order:1;padding-left:unset;padding-right:32px}.aside-container[data-v-39a288b8]{position:fixed;top:0;padding-top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + var(--vp-doc-top-height, 0px) + 48px);width:224px;height:100vh;overflow-x:hidden;overflow-y:auto;scrollbar-width:none}.aside-container[data-v-39a288b8]::-webkit-scrollbar{display:none}.aside-curtain[data-v-39a288b8]{position:fixed;bottom:0;z-index:10;width:224px;height:32px;background:linear-gradient(transparent,var(--vp-c-bg) 70%)}.aside-content[data-v-39a288b8]{display:flex;flex-direction:column;min-height:calc(100vh - (var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px));padding-bottom:32px}.content[data-v-39a288b8]{position:relative;margin:0 auto;width:100%}@media (min-width: 960px){.content[data-v-39a288b8]{padding:0 32px 128px}}@media (min-width: 1280px){.content[data-v-39a288b8]{order:1;margin:0;min-width:640px}}.content-container[data-v-39a288b8]{margin:0 auto}.VPDoc.has-aside .content-container[data-v-39a288b8]{max-width:688px}.VPButton[data-v-cad61b99]{display:inline-block;border:1px solid transparent;text-align:center;font-weight:600;white-space:nowrap;transition:color .25s,border-color .25s,background-color .25s}.VPButton[data-v-cad61b99]:active{transition:color .1s,border-color .1s,background-color .1s}.VPButton.medium[data-v-cad61b99]{border-radius:20px;padding:0 20px;line-height:38px;font-size:14px}.VPButton.big[data-v-cad61b99]{border-radius:24px;padding:0 24px;line-height:46px;font-size:16px}.VPButton.brand[data-v-cad61b99]{border-color:var(--vp-button-brand-border);color:var(--vp-button-brand-text);background-color:var(--vp-button-brand-bg)}.VPButton.brand[data-v-cad61b99]:hover{border-color:var(--vp-button-brand-hover-border);color:var(--vp-button-brand-hover-text);background-color:var(--vp-button-brand-hover-bg)}.VPButton.brand[data-v-cad61b99]:active{border-color:var(--vp-button-brand-active-border);color:var(--vp-button-brand-active-text);background-color:var(--vp-button-brand-active-bg)}.VPButton.alt[data-v-cad61b99]{border-color:var(--vp-button-alt-border);color:var(--vp-button-alt-text);background-color:var(--vp-button-alt-bg)}.VPButton.alt[data-v-cad61b99]:hover{border-color:var(--vp-button-alt-hover-border);color:var(--vp-button-alt-hover-text);background-color:var(--vp-button-alt-hover-bg)}.VPButton.alt[data-v-cad61b99]:active{border-color:var(--vp-button-alt-active-border);color:var(--vp-button-alt-active-text);background-color:var(--vp-button-alt-active-bg)}.VPButton.sponsor[data-v-cad61b99]{border-color:var(--vp-button-sponsor-border);color:var(--vp-button-sponsor-text);background-color:var(--vp-button-sponsor-bg)}.VPButton.sponsor[data-v-cad61b99]:hover{border-color:var(--vp-button-sponsor-hover-border);color:var(--vp-button-sponsor-hover-text);background-color:var(--vp-button-sponsor-hover-bg)}.VPButton.sponsor[data-v-cad61b99]:active{border-color:var(--vp-button-sponsor-active-border);color:var(--vp-button-sponsor-active-text);background-color:var(--vp-button-sponsor-active-bg)}html:not(.dark) .VPImage.dark[data-v-8426fc1a]{display:none}.dark .VPImage.light[data-v-8426fc1a]{display:none}.VPHero[data-v-303bb580]{margin-top:calc((var(--vp-nav-height) + var(--vp-layout-top-height, 0px)) * -1);padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 48px) 24px 48px}@media (min-width: 640px){.VPHero[data-v-303bb580]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 48px 64px}}@media (min-width: 960px){.VPHero[data-v-303bb580]{padding:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 80px) 64px 64px}}.container[data-v-303bb580]{display:flex;flex-direction:column;margin:0 auto;max-width:1152px}@media (min-width: 960px){.container[data-v-303bb580]{flex-direction:row}}.main[data-v-303bb580]{position:relative;z-index:10;order:2;flex-grow:1;flex-shrink:0}.VPHero.has-image .container[data-v-303bb580]{text-align:center}@media (min-width: 960px){.VPHero.has-image .container[data-v-303bb580]{text-align:left}}@media (min-width: 960px){.main[data-v-303bb580]{order:1;width:calc((100% / 3) * 2)}.VPHero.has-image .main[data-v-303bb580]{max-width:592px}}.name[data-v-303bb580],.text[data-v-303bb580]{max-width:392px;letter-spacing:-.4px;line-height:40px;font-size:32px;font-weight:700;white-space:pre-wrap}.VPHero.has-image .name[data-v-303bb580],.VPHero.has-image .text[data-v-303bb580]{margin:0 auto}.name[data-v-303bb580]{color:var(--vp-home-hero-name-color)}.clip[data-v-303bb580]{background:var(--vp-home-hero-name-background);-webkit-background-clip:text;background-clip:text;-webkit-text-fill-color:var(--vp-home-hero-name-color)}@media (min-width: 640px){.name[data-v-303bb580],.text[data-v-303bb580]{max-width:576px;line-height:56px;font-size:48px}}@media (min-width: 960px){.name[data-v-303bb580],.text[data-v-303bb580]{line-height:64px;font-size:56px}.VPHero.has-image .name[data-v-303bb580],.VPHero.has-image .text[data-v-303bb580]{margin:0}}.tagline[data-v-303bb580]{padding-top:8px;max-width:392px;line-height:28px;font-size:18px;font-weight:500;white-space:pre-wrap;color:var(--vp-c-text-2)}.VPHero.has-image .tagline[data-v-303bb580]{margin:0 auto}@media (min-width: 640px){.tagline[data-v-303bb580]{padding-top:12px;max-width:576px;line-height:32px;font-size:20px}}@media (min-width: 960px){.tagline[data-v-303bb580]{line-height:36px;font-size:24px}.VPHero.has-image .tagline[data-v-303bb580]{margin:0}}.actions[data-v-303bb580]{display:flex;flex-wrap:wrap;margin:-6px;padding-top:24px}.VPHero.has-image .actions[data-v-303bb580]{justify-content:center}@media (min-width: 640px){.actions[data-v-303bb580]{padding-top:32px}}@media (min-width: 960px){.VPHero.has-image .actions[data-v-303bb580]{justify-content:flex-start}}.action[data-v-303bb580]{flex-shrink:0;padding:6px}.image[data-v-303bb580]{order:1;margin:-76px -24px -48px}@media (min-width: 640px){.image[data-v-303bb580]{margin:-108px -24px -48px}}@media (min-width: 960px){.image[data-v-303bb580]{flex-grow:1;order:2;margin:0;min-height:100%}}.image-container[data-v-303bb580]{position:relative;margin:0 auto;width:320px;height:320px}@media (min-width: 640px){.image-container[data-v-303bb580]{width:392px;height:392px}}@media (min-width: 960px){.image-container[data-v-303bb580]{display:flex;justify-content:center;align-items:center;width:100%;height:100%;transform:translate(-32px,-32px)}}.image-bg[data-v-303bb580]{position:absolute;top:50%;left:50%;border-radius:50%;width:192px;height:192px;background-image:var(--vp-home-hero-image-background-image);filter:var(--vp-home-hero-image-filter);transform:translate(-50%,-50%)}@media (min-width: 640px){.image-bg[data-v-303bb580]{width:256px;height:256px}}@media (min-width: 960px){.image-bg[data-v-303bb580]{width:320px;height:320px}}[data-v-303bb580] .image-src{position:absolute;top:50%;left:50%;max-width:192px;max-height:192px;transform:translate(-50%,-50%)}@media (min-width: 640px){[data-v-303bb580] .image-src{max-width:256px;max-height:256px}}@media (min-width: 960px){[data-v-303bb580] .image-src{max-width:320px;max-height:320px}}.VPFeature[data-v-a3976bdc]{display:block;border:1px solid var(--vp-c-bg-soft);border-radius:12px;height:100%;background-color:var(--vp-c-bg-soft);transition:border-color .25s,background-color .25s}.VPFeature.link[data-v-a3976bdc]:hover{border-color:var(--vp-c-brand-1)}.box[data-v-a3976bdc]{display:flex;flex-direction:column;padding:24px;height:100%}.box[data-v-a3976bdc]>.VPImage{margin-bottom:20px}.icon[data-v-a3976bdc]{display:flex;justify-content:center;align-items:center;margin-bottom:20px;border-radius:6px;background-color:var(--vp-c-default-soft);width:48px;height:48px;font-size:24px;transition:background-color .25s}.title[data-v-a3976bdc]{line-height:24px;font-size:16px;font-weight:600}.details[data-v-a3976bdc]{flex-grow:1;padding-top:8px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.link-text[data-v-a3976bdc]{padding-top:8px}.link-text-value[data-v-a3976bdc]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.link-text-icon[data-v-a3976bdc]{margin-left:6px}.VPFeatures[data-v-a6181336]{position:relative;padding:0 24px}@media (min-width: 640px){.VPFeatures[data-v-a6181336]{padding:0 48px}}@media (min-width: 960px){.VPFeatures[data-v-a6181336]{padding:0 64px}}.container[data-v-a6181336]{margin:0 auto;max-width:1152px}.items[data-v-a6181336]{display:flex;flex-wrap:wrap;margin:-8px}.item[data-v-a6181336]{padding:8px;width:100%}@media (min-width: 640px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:50%}}@media (min-width: 768px){.item.grid-2[data-v-a6181336],.item.grid-4[data-v-a6181336]{width:50%}.item.grid-3[data-v-a6181336],.item.grid-6[data-v-a6181336]{width:calc(100% / 3)}}@media (min-width: 960px){.item.grid-4[data-v-a6181336]{width:25%}}.container[data-v-8e2d4988]{margin:auto;width:100%;max-width:1280px;padding:0 24px}@media (min-width: 640px){.container[data-v-8e2d4988]{padding:0 48px}}@media (min-width: 960px){.container[data-v-8e2d4988]{width:100%;padding:0 64px}}.vp-doc[data-v-8e2d4988] .VPHomeSponsors,.vp-doc[data-v-8e2d4988] .VPTeamPage{margin-left:var(--vp-offset, calc(50% - 50vw) );margin-right:var(--vp-offset, calc(50% - 50vw) )}.vp-doc[data-v-8e2d4988] .VPHomeSponsors h2{border-top:none;letter-spacing:normal}.vp-doc[data-v-8e2d4988] .VPHomeSponsors a,.vp-doc[data-v-8e2d4988] .VPTeamPage a{text-decoration:none}.VPHome[data-v-686f80a6]{margin-bottom:96px}@media (min-width: 768px){.VPHome[data-v-686f80a6]{margin-bottom:128px}}.VPContent[data-v-1428d186]{flex-grow:1;flex-shrink:0;margin:var(--vp-layout-top-height, 0px) auto 0;width:100%}.VPContent.is-home[data-v-1428d186]{width:100%;max-width:100%}.VPContent.has-sidebar[data-v-1428d186]{margin:0}@media (min-width: 960px){.VPContent[data-v-1428d186]{padding-top:var(--vp-nav-height)}.VPContent.has-sidebar[data-v-1428d186]{margin:var(--vp-layout-top-height, 0px) 0 0;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPContent.has-sidebar[data-v-1428d186]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.VPFooter[data-v-e315a0ad]{position:relative;z-index:var(--vp-z-index-footer);border-top:1px solid var(--vp-c-gutter);padding:32px 24px;background-color:var(--vp-c-bg)}.VPFooter.has-sidebar[data-v-e315a0ad]{display:none}.VPFooter[data-v-e315a0ad] a{text-decoration-line:underline;text-underline-offset:2px;transition:color .25s}.VPFooter[data-v-e315a0ad] a:hover{color:var(--vp-c-text-1)}@media (min-width: 768px){.VPFooter[data-v-e315a0ad]{padding:32px}}.container[data-v-e315a0ad]{margin:0 auto;max-width:var(--vp-layout-max-width);text-align:center}.message[data-v-e315a0ad],.copyright[data-v-e315a0ad]{line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-2)}.VPLocalNavOutlineDropdown[data-v-17a5e62e]{padding:12px 20px 11px}@media (min-width: 960px){.VPLocalNavOutlineDropdown[data-v-17a5e62e]{padding:12px 36px 11px}}.VPLocalNavOutlineDropdown button[data-v-17a5e62e]{display:block;font-size:12px;font-weight:500;line-height:24px;color:var(--vp-c-text-2);transition:color .5s;position:relative}.VPLocalNavOutlineDropdown button[data-v-17a5e62e]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPLocalNavOutlineDropdown button.open[data-v-17a5e62e]{color:var(--vp-c-text-1)}.icon[data-v-17a5e62e]{display:inline-block;vertical-align:middle;margin-left:2px;font-size:14px;transform:rotate(0);transition:transform .25s}@media (min-width: 960px){.VPLocalNavOutlineDropdown button[data-v-17a5e62e]{font-size:14px}.icon[data-v-17a5e62e]{font-size:16px}}.open>.icon[data-v-17a5e62e]{transform:rotate(90deg)}.items[data-v-17a5e62e]{position:absolute;top:40px;right:16px;left:16px;display:grid;gap:1px;border:1px solid var(--vp-c-border);border-radius:8px;background-color:var(--vp-c-gutter);max-height:calc(var(--vp-vh, 100vh) - 86px);overflow:hidden auto;box-shadow:var(--vp-shadow-3)}@media (min-width: 960px){.items[data-v-17a5e62e]{right:auto;left:calc(var(--vp-sidebar-width) + 32px);width:320px}}.header[data-v-17a5e62e]{background-color:var(--vp-c-bg-soft)}.top-link[data-v-17a5e62e]{display:block;padding:0 16px;line-height:48px;font-size:14px;font-weight:500;color:var(--vp-c-brand-1)}.outline[data-v-17a5e62e]{padding:8px 0;background-color:var(--vp-c-bg-soft)}.flyout-enter-active[data-v-17a5e62e]{transition:all .2s ease-out}.flyout-leave-active[data-v-17a5e62e]{transition:all .15s ease-in}.flyout-enter-from[data-v-17a5e62e],.flyout-leave-to[data-v-17a5e62e]{opacity:0;transform:translateY(-16px)}.VPLocalNav[data-v-a6f0e41e]{position:sticky;top:0;left:0;z-index:var(--vp-z-index-local-nav);border-bottom:1px solid var(--vp-c-gutter);padding-top:var(--vp-layout-top-height, 0px);width:100%;background-color:var(--vp-local-nav-bg-color)}.VPLocalNav.fixed[data-v-a6f0e41e]{position:fixed}@media (min-width: 960px){.VPLocalNav[data-v-a6f0e41e]{top:var(--vp-nav-height)}.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:var(--vp-sidebar-width)}.VPLocalNav.empty[data-v-a6f0e41e]{display:none}}@media (min-width: 1280px){.VPLocalNav[data-v-a6f0e41e]{display:none}}@media (min-width: 1440px){.VPLocalNav.has-sidebar[data-v-a6f0e41e]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.container[data-v-a6f0e41e]{display:flex;justify-content:space-between;align-items:center}.menu[data-v-a6f0e41e]{display:flex;align-items:center;padding:12px 24px 11px;line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.menu[data-v-a6f0e41e]:hover{color:var(--vp-c-text-1);transition:color .25s}@media (min-width: 768px){.menu[data-v-a6f0e41e]{padding:0 32px}}@media (min-width: 960px){.menu[data-v-a6f0e41e]{display:none}}.menu-icon[data-v-a6f0e41e]{margin-right:8px;font-size:14px}.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 24px 11px}@media (min-width: 768px){.VPOutlineDropdown[data-v-a6f0e41e]{padding:12px 32px 11px}}.VPSwitch[data-v-1d5665e3]{position:relative;border-radius:11px;display:block;width:40px;height:22px;flex-shrink:0;border:1px solid var(--vp-input-border-color);background-color:var(--vp-input-switch-bg-color);transition:border-color .25s!important}.VPSwitch[data-v-1d5665e3]:hover{border-color:var(--vp-c-brand-1)}.check[data-v-1d5665e3]{position:absolute;top:1px;left:1px;width:18px;height:18px;border-radius:50%;background-color:var(--vp-c-neutral-inverse);box-shadow:var(--vp-shadow-1);transition:transform .25s!important}.icon[data-v-1d5665e3]{position:relative;display:block;width:18px;height:18px;border-radius:50%;overflow:hidden}.icon[data-v-1d5665e3] [class^=vpi-]{position:absolute;top:3px;left:3px;width:12px;height:12px;color:var(--vp-c-text-2)}.dark .icon[data-v-1d5665e3] [class^=vpi-]{color:var(--vp-c-text-1);transition:opacity .25s!important}.sun[data-v-d1f28634]{opacity:1}.moon[data-v-d1f28634],.dark .sun[data-v-d1f28634]{opacity:0}.dark .moon[data-v-d1f28634]{opacity:1}.dark .VPSwitchAppearance[data-v-d1f28634] .check{transform:translate(18px)}.VPNavBarAppearance[data-v-e6aabb21]{display:none}@media (min-width: 1280px){.VPNavBarAppearance[data-v-e6aabb21]{display:flex;align-items:center}}.VPMenuGroup+.VPMenuLink[data-v-43f1e123]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.link[data-v-43f1e123]{display:block;border-radius:6px;padding:0 12px;line-height:32px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);white-space:nowrap;transition:background-color .25s,color .25s}.link[data-v-43f1e123]:hover{color:var(--vp-c-brand-1);background-color:var(--vp-c-default-soft)}.link.active[data-v-43f1e123]{color:var(--vp-c-brand-1)}.VPMenuGroup[data-v-69e747b5]{margin:12px -12px 0;border-top:1px solid var(--vp-c-divider);padding:12px 12px 0}.VPMenuGroup[data-v-69e747b5]:first-child{margin-top:0;border-top:0;padding-top:0}.VPMenuGroup+.VPMenuGroup[data-v-69e747b5]{margin-top:12px;border-top:1px solid var(--vp-c-divider)}.title[data-v-69e747b5]{padding:0 12px;line-height:32px;font-size:14px;font-weight:600;color:var(--vp-c-text-2);white-space:nowrap;transition:color .25s}.VPMenu[data-v-e7ea1737]{border-radius:12px;padding:12px;min-width:128px;border:1px solid var(--vp-c-divider);background-color:var(--vp-c-bg-elv);box-shadow:var(--vp-shadow-3);transition:background-color .5s;max-height:calc(100vh - var(--vp-nav-height));overflow-y:auto}.VPMenu[data-v-e7ea1737] .group{margin:0 -12px;padding:0 12px 12px}.VPMenu[data-v-e7ea1737] .group+.group{border-top:1px solid var(--vp-c-divider);padding:11px 12px 12px}.VPMenu[data-v-e7ea1737] .group:last-child{padding-bottom:0}.VPMenu[data-v-e7ea1737] .group+.item{border-top:1px solid var(--vp-c-divider);padding:11px 16px 0}.VPMenu[data-v-e7ea1737] .item{padding:0 16px;white-space:nowrap}.VPMenu[data-v-e7ea1737] .label{flex-grow:1;line-height:28px;font-size:12px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.VPMenu[data-v-e7ea1737] .action{padding-left:24px}.VPFlyout[data-v-b6c34ac9]{position:relative}.VPFlyout[data-v-b6c34ac9]:hover{color:var(--vp-c-brand-1);transition:color .25s}.VPFlyout:hover .text[data-v-b6c34ac9]{color:var(--vp-c-text-2)}.VPFlyout:hover .icon[data-v-b6c34ac9]{fill:var(--vp-c-text-2)}.VPFlyout.active .text[data-v-b6c34ac9]{color:var(--vp-c-brand-1)}.VPFlyout.active:hover .text[data-v-b6c34ac9]{color:var(--vp-c-brand-2)}.VPFlyout:hover .menu[data-v-b6c34ac9],.button[aria-expanded=true]+.menu[data-v-b6c34ac9]{opacity:1;visibility:visible;transform:translateY(0)}.button[aria-expanded=false]+.menu[data-v-b6c34ac9]{opacity:0;visibility:hidden;transform:translateY(0)}.button[data-v-b6c34ac9]{display:flex;align-items:center;padding:0 12px;height:var(--vp-nav-height);color:var(--vp-c-text-1);transition:color .5s}.text[data-v-b6c34ac9]{display:flex;align-items:center;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.option-icon[data-v-b6c34ac9]{margin-right:0;font-size:16px}.text-icon[data-v-b6c34ac9]{margin-left:4px;font-size:14px}.icon[data-v-b6c34ac9]{font-size:20px;transition:fill .25s}.menu[data-v-b6c34ac9]{position:absolute;top:calc(var(--vp-nav-height) / 2 + 20px);right:0;opacity:0;visibility:hidden;transition:opacity .25s,visibility .25s,transform .25s}.VPSocialLink[data-v-eee4e7cb]{display:flex;justify-content:center;align-items:center;width:36px;height:36px;color:var(--vp-c-text-2);transition:color .5s}.VPSocialLink[data-v-eee4e7cb]:hover{color:var(--vp-c-text-1);transition:color .25s}.VPSocialLink[data-v-eee4e7cb]>svg,.VPSocialLink[data-v-eee4e7cb]>[class^=vpi-social-]{width:20px;height:20px;fill:currentColor}.VPSocialLinks[data-v-7bc22406]{display:flex;justify-content:center}.VPNavBarExtra[data-v-d0bd9dde]{display:none;margin-right:-12px}@media (min-width: 768px){.VPNavBarExtra[data-v-d0bd9dde]{display:block}}@media (min-width: 1280px){.VPNavBarExtra[data-v-d0bd9dde]{display:none}}.trans-title[data-v-d0bd9dde]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.item.appearance[data-v-d0bd9dde],.item.social-links[data-v-d0bd9dde]{display:flex;align-items:center;padding:0 12px}.item.appearance[data-v-d0bd9dde]{min-width:176px}.appearance-action[data-v-d0bd9dde]{margin-right:-2px}.social-links-list[data-v-d0bd9dde]{margin:-4px -8px}.VPNavBarHamburger[data-v-e5dd9c1c]{display:flex;justify-content:center;align-items:center;width:48px;height:var(--vp-nav-height)}@media (min-width: 768px){.VPNavBarHamburger[data-v-e5dd9c1c]{display:none}}.container[data-v-e5dd9c1c]{position:relative;width:16px;height:14px;overflow:hidden}.VPNavBarHamburger:hover .top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(4px)}.VPNavBarHamburger:hover .middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(0)}.VPNavBarHamburger:hover .bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(8px)}.VPNavBarHamburger.active .top[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(225deg)}.VPNavBarHamburger.active .middle[data-v-e5dd9c1c]{top:6px;transform:translate(16px)}.VPNavBarHamburger.active .bottom[data-v-e5dd9c1c]{top:6px;transform:translate(0) rotate(135deg)}.VPNavBarHamburger.active:hover .top[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .middle[data-v-e5dd9c1c],.VPNavBarHamburger.active:hover .bottom[data-v-e5dd9c1c]{background-color:var(--vp-c-text-2);transition:top .25s,background-color .25s,transform .25s}.top[data-v-e5dd9c1c],.middle[data-v-e5dd9c1c],.bottom[data-v-e5dd9c1c]{position:absolute;width:16px;height:2px;background-color:var(--vp-c-text-1);transition:top .25s,background-color .5s,transform .25s}.top[data-v-e5dd9c1c]{top:0;left:0;transform:translate(0)}.middle[data-v-e5dd9c1c]{top:6px;left:0;transform:translate(8px)}.bottom[data-v-e5dd9c1c]{top:12px;left:0;transform:translate(4px)}.VPNavBarMenuLink[data-v-9c663999]{display:flex;align-items:center;padding:0 12px;line-height:var(--vp-nav-height);font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.VPNavBarMenuLink.active[data-v-9c663999],.VPNavBarMenuLink[data-v-9c663999]:hover{color:var(--vp-c-brand-1)}.VPNavBarMenu[data-v-7f418b0f]{display:none}@media (min-width: 768px){.VPNavBarMenu[data-v-7f418b0f]{display:flex}}/*! @docsearch/css 3.6.0 | MIT License | © Algolia, Inc. and contributors | https://docsearch.algolia.com */:root{--docsearch-primary-color:#5468ff;--docsearch-text-color:#1c1e21;--docsearch-spacing:12px;--docsearch-icon-stroke-width:1.4;--docsearch-highlight-color:var(--docsearch-primary-color);--docsearch-muted-color:#969faf;--docsearch-container-background:rgba(101,108,133,.8);--docsearch-logo-color:#5468ff;--docsearch-modal-width:560px;--docsearch-modal-height:600px;--docsearch-modal-background:#f5f6f7;--docsearch-modal-shadow:inset 1px 1px 0 0 hsla(0,0%,100%,.5),0 3px 8px 0 #555a64;--docsearch-searchbox-height:56px;--docsearch-searchbox-background:#ebedf0;--docsearch-searchbox-focus-background:#fff;--docsearch-searchbox-shadow:inset 0 0 0 2px var(--docsearch-primary-color);--docsearch-hit-height:56px;--docsearch-hit-color:#444950;--docsearch-hit-active-color:#fff;--docsearch-hit-background:#fff;--docsearch-hit-shadow:0 1px 3px 0 #d4d9e1;--docsearch-key-gradient:linear-gradient(-225deg,#d5dbe4,#f8f8f8);--docsearch-key-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px rgba(30,35,90,.4);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #cdcde6,inset 0 0 1px 1px #fff,0 1px 1px 0 rgba(30,35,90,.4);--docsearch-footer-height:44px;--docsearch-footer-background:#fff;--docsearch-footer-shadow:0 -1px 0 0 #e0e3e8,0 -3px 6px 0 rgba(69,98,155,.12)}html[data-theme=dark]{--docsearch-text-color:#f5f6f7;--docsearch-container-background:rgba(9,10,17,.8);--docsearch-modal-background:#15172a;--docsearch-modal-shadow:inset 1px 1px 0 0 #2c2e40,0 3px 8px 0 #000309;--docsearch-searchbox-background:#090a11;--docsearch-searchbox-focus-background:#000;--docsearch-hit-color:#bec3c9;--docsearch-hit-shadow:none;--docsearch-hit-background:#090a11;--docsearch-key-gradient:linear-gradient(-26.5deg,#565872,#31355b);--docsearch-key-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 2px 2px 0 rgba(3,4,9,.3);--docsearch-key-pressed-shadow:inset 0 -2px 0 0 #282d55,inset 0 0 1px 1px #51577d,0 1px 1px 0 rgba(3,4,9,.30196078431372547);--docsearch-footer-background:#1e2136;--docsearch-footer-shadow:inset 0 1px 0 0 rgba(73,76,106,.5),0 -4px 8px 0 rgba(0,0,0,.2);--docsearch-logo-color:#fff;--docsearch-muted-color:#7f8497}.DocSearch-Button{align-items:center;background:var(--docsearch-searchbox-background);border:0;border-radius:40px;color:var(--docsearch-muted-color);cursor:pointer;display:flex;font-weight:500;height:36px;justify-content:space-between;margin:0 0 0 16px;padding:0 8px;-webkit-user-select:none;user-select:none}.DocSearch-Button:active,.DocSearch-Button:focus,.DocSearch-Button:hover{background:var(--docsearch-searchbox-focus-background);box-shadow:var(--docsearch-searchbox-shadow);color:var(--docsearch-text-color);outline:none}.DocSearch-Button-Container{align-items:center;display:flex}.DocSearch-Search-Icon{stroke-width:1.6}.DocSearch-Button .DocSearch-Search-Icon{color:var(--docsearch-text-color)}.DocSearch-Button-Placeholder{font-size:1rem;padding:0 12px 0 6px}.DocSearch-Button-Keys{display:flex;min-width:calc(40px + .8em)}.DocSearch-Button-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:3px;box-shadow:var(--docsearch-key-shadow);color:var(--docsearch-muted-color);display:flex;height:18px;justify-content:center;margin-right:.4em;position:relative;padding:0 0 2px;border:0;top:-1px;width:20px}.DocSearch-Button-Key--pressed{transform:translate3d(0,1px,0);box-shadow:var(--docsearch-key-pressed-shadow)}@media (max-width:768px){.DocSearch-Button-Keys,.DocSearch-Button-Placeholder{display:none}}.DocSearch--active{overflow:hidden!important}.DocSearch-Container,.DocSearch-Container *{box-sizing:border-box}.DocSearch-Container{background-color:var(--docsearch-container-background);height:100vh;left:0;position:fixed;top:0;width:100vw;z-index:200}.DocSearch-Container a{text-decoration:none}.DocSearch-Link{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;font:inherit;margin:0;padding:0}.DocSearch-Modal{background:var(--docsearch-modal-background);border-radius:6px;box-shadow:var(--docsearch-modal-shadow);flex-direction:column;margin:60px auto auto;max-width:var(--docsearch-modal-width);position:relative}.DocSearch-SearchBar{display:flex;padding:var(--docsearch-spacing) var(--docsearch-spacing) 0}.DocSearch-Form{align-items:center;background:var(--docsearch-searchbox-focus-background);border-radius:4px;box-shadow:var(--docsearch-searchbox-shadow);display:flex;height:var(--docsearch-searchbox-height);margin:0;padding:0 var(--docsearch-spacing);position:relative;width:100%}.DocSearch-Input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:transparent;border:0;color:var(--docsearch-text-color);flex:1;font:inherit;font-size:1.2em;height:100%;outline:none;padding:0 0 0 8px;width:80%}.DocSearch-Input::placeholder{color:var(--docsearch-muted-color);opacity:1}.DocSearch-Input::-webkit-search-cancel-button,.DocSearch-Input::-webkit-search-decoration,.DocSearch-Input::-webkit-search-results-button,.DocSearch-Input::-webkit-search-results-decoration{display:none}.DocSearch-LoadingIndicator,.DocSearch-MagnifierLabel,.DocSearch-Reset{margin:0;padding:0}.DocSearch-MagnifierLabel,.DocSearch-Reset{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}.DocSearch-Container--Stalled .DocSearch-MagnifierLabel,.DocSearch-LoadingIndicator{display:none}.DocSearch-Container--Stalled .DocSearch-LoadingIndicator{align-items:center;color:var(--docsearch-highlight-color);display:flex;justify-content:center}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Reset{animation:none;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;right:0;stroke-width:var(--docsearch-icon-stroke-width)}}.DocSearch-Reset{animation:fade-in .1s ease-in forwards;-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:var(--docsearch-icon-color);cursor:pointer;padding:2px;right:0;stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Reset[hidden]{display:none}.DocSearch-Reset:hover{color:var(--docsearch-highlight-color)}.DocSearch-LoadingIndicator svg,.DocSearch-MagnifierLabel svg{height:24px;width:24px}.DocSearch-Cancel{display:none}.DocSearch-Dropdown{max-height:calc(var(--docsearch-modal-height) - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height));min-height:var(--docsearch-spacing);overflow-y:auto;overflow-y:overlay;padding:0 var(--docsearch-spacing);scrollbar-color:var(--docsearch-muted-color) var(--docsearch-modal-background);scrollbar-width:thin}.DocSearch-Dropdown::-webkit-scrollbar{width:12px}.DocSearch-Dropdown::-webkit-scrollbar-track{background:transparent}.DocSearch-Dropdown::-webkit-scrollbar-thumb{background-color:var(--docsearch-muted-color);border:3px solid var(--docsearch-modal-background);border-radius:20px}.DocSearch-Dropdown ul{list-style:none;margin:0;padding:0}.DocSearch-Label{font-size:.75em;line-height:1.6em}.DocSearch-Help,.DocSearch-Label{color:var(--docsearch-muted-color)}.DocSearch-Help{font-size:.9em;margin:0;-webkit-user-select:none;user-select:none}.DocSearch-Title{font-size:1.2em}.DocSearch-Logo a{display:flex}.DocSearch-Logo svg{color:var(--docsearch-logo-color);margin-left:8px}.DocSearch-Hits:last-of-type{margin-bottom:24px}.DocSearch-Hits mark{background:none;color:var(--docsearch-highlight-color)}.DocSearch-HitsFooter{color:var(--docsearch-muted-color);display:flex;font-size:.85em;justify-content:center;margin-bottom:var(--docsearch-spacing);padding:var(--docsearch-spacing)}.DocSearch-HitsFooter a{border-bottom:1px solid;color:inherit}.DocSearch-Hit{border-radius:4px;display:flex;padding-bottom:4px;position:relative}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--deleting{transition:none}}.DocSearch-Hit--deleting{opacity:0;transition:all .25s linear}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit--favoriting{transition:none}}.DocSearch-Hit--favoriting{transform:scale(0);transform-origin:top center;transition:all .25s linear;transition-delay:.25s}.DocSearch-Hit a{background:var(--docsearch-hit-background);border-radius:4px;box-shadow:var(--docsearch-hit-shadow);display:block;padding-left:var(--docsearch-spacing);width:100%}.DocSearch-Hit-source{background:var(--docsearch-modal-background);color:var(--docsearch-highlight-color);font-size:.85em;font-weight:600;line-height:32px;margin:0 -4px;padding:8px 4px 0;position:sticky;top:0;z-index:10}.DocSearch-Hit-Tree{color:var(--docsearch-muted-color);height:var(--docsearch-hit-height);opacity:.5;stroke-width:var(--docsearch-icon-stroke-width);width:24px}.DocSearch-Hit[aria-selected=true] a{background-color:var(--docsearch-highlight-color)}.DocSearch-Hit[aria-selected=true] mark{text-decoration:underline}.DocSearch-Hit-Container{align-items:center;color:var(--docsearch-hit-color);display:flex;flex-direction:row;height:var(--docsearch-hit-height);padding:0 var(--docsearch-spacing) 0 0}.DocSearch-Hit-icon{height:20px;width:20px}.DocSearch-Hit-action,.DocSearch-Hit-icon{color:var(--docsearch-muted-color);stroke-width:var(--docsearch-icon-stroke-width)}.DocSearch-Hit-action{align-items:center;display:flex;height:22px;width:22px}.DocSearch-Hit-action svg{display:block;height:18px;width:18px}.DocSearch-Hit-action+.DocSearch-Hit-action{margin-left:6px}.DocSearch-Hit-action-button{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:50%;color:inherit;cursor:pointer;padding:2px}svg.DocSearch-Hit-Select-Icon{display:none}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Select-Icon{display:block}.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:background-color .1s ease-in}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{transition:none}}.DocSearch-Hit-action-button:focus path,.DocSearch-Hit-action-button:hover path{fill:#fff}.DocSearch-Hit-content-wrapper{display:flex;flex:1 1 auto;flex-direction:column;font-weight:500;justify-content:center;line-height:1.2em;margin:0 8px;overflow-x:hidden;position:relative;text-overflow:ellipsis;white-space:nowrap;width:80%}.DocSearch-Hit-title{font-size:.9em}.DocSearch-Hit-path{color:var(--docsearch-muted-color);font-size:.75em}.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-action,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-icon,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-path,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-text,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-title,.DocSearch-Hit[aria-selected=true] .DocSearch-Hit-Tree,.DocSearch-Hit[aria-selected=true] mark{color:var(--docsearch-hit-active-color)!important}@media screen and (prefers-reduced-motion:reduce){.DocSearch-Hit-action-button:focus,.DocSearch-Hit-action-button:hover{background:#0003;transition:none}}.DocSearch-ErrorScreen,.DocSearch-NoResults,.DocSearch-StartScreen{font-size:.9em;margin:0 auto;padding:36px 0;text-align:center;width:80%}.DocSearch-Screen-Icon{color:var(--docsearch-muted-color);padding-bottom:12px}.DocSearch-NoResults-Prefill-List{display:inline-block;padding-bottom:24px;text-align:left}.DocSearch-NoResults-Prefill-List ul{display:inline-block;padding:8px 0 0}.DocSearch-NoResults-Prefill-List li{list-style-position:inside;list-style-type:"» "}.DocSearch-Prefill{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;border-radius:1em;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;font-size:1em;font-weight:700;padding:0}.DocSearch-Prefill:focus,.DocSearch-Prefill:hover{outline:none;text-decoration:underline}.DocSearch-Footer{align-items:center;background:var(--docsearch-footer-background);border-radius:0 0 8px 8px;box-shadow:var(--docsearch-footer-shadow);display:flex;flex-direction:row-reverse;flex-shrink:0;height:var(--docsearch-footer-height);justify-content:space-between;padding:0 var(--docsearch-spacing);position:relative;-webkit-user-select:none;user-select:none;width:100%;z-index:300}.DocSearch-Commands{color:var(--docsearch-muted-color);display:flex;list-style:none;margin:0;padding:0}.DocSearch-Commands li{align-items:center;display:flex}.DocSearch-Commands li:not(:last-of-type){margin-right:.8em}.DocSearch-Commands-Key{align-items:center;background:var(--docsearch-key-gradient);border-radius:2px;box-shadow:var(--docsearch-key-shadow);display:flex;height:18px;justify-content:center;margin-right:.4em;padding:0 0 1px;color:var(--docsearch-muted-color);border:0;width:20px}.DocSearch-VisuallyHiddenForAccessibility{clip:rect(0 0 0 0);clip-path:inset(50%);height:1px;overflow:hidden;position:absolute;white-space:nowrap;width:1px}@media (max-width:768px){:root{--docsearch-spacing:10px;--docsearch-footer-height:40px}.DocSearch-Dropdown{height:100%}.DocSearch-Container{height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);position:absolute}.DocSearch-Footer{border-radius:0;bottom:0;position:absolute}.DocSearch-Hit-content-wrapper{display:flex;position:relative;width:80%}.DocSearch-Modal{border-radius:0;box-shadow:none;height:100vh;height:-webkit-fill-available;height:calc(var(--docsearch-vh, 1vh)*100);margin:0;max-width:100%;width:100%}.DocSearch-Dropdown{max-height:calc(var(--docsearch-vh, 1vh)*100 - var(--docsearch-searchbox-height) - var(--docsearch-spacing) - var(--docsearch-footer-height))}.DocSearch-Cancel{-webkit-appearance:none;-moz-appearance:none;appearance:none;background:none;border:0;color:var(--docsearch-highlight-color);cursor:pointer;display:inline-block;flex:none;font:inherit;font-size:1em;font-weight:500;margin-left:var(--docsearch-spacing);outline:none;overflow:hidden;padding:0;-webkit-user-select:none;user-select:none;white-space:nowrap}.DocSearch-Commands,.DocSearch-Hit-Tree{display:none}}@keyframes fade-in{0%{opacity:0}to{opacity:1}}[class*=DocSearch]{--docsearch-primary-color: var(--vp-c-brand-1);--docsearch-highlight-color: var(--docsearch-primary-color);--docsearch-text-color: var(--vp-c-text-1);--docsearch-muted-color: var(--vp-c-text-2);--docsearch-searchbox-shadow: none;--docsearch-searchbox-background: transparent;--docsearch-searchbox-focus-background: transparent;--docsearch-key-gradient: transparent;--docsearch-key-shadow: none;--docsearch-modal-background: var(--vp-c-bg-soft);--docsearch-footer-background: var(--vp-c-bg)}.dark [class*=DocSearch]{--docsearch-modal-shadow: none;--docsearch-footer-shadow: none;--docsearch-logo-color: var(--vp-c-text-2);--docsearch-hit-background: var(--vp-c-default-soft);--docsearch-hit-color: var(--vp-c-text-2);--docsearch-hit-shadow: none}.DocSearch-Button{display:flex;justify-content:center;align-items:center;margin:0;padding:0;width:48px;height:55px;background:transparent;transition:border-color .25s}.DocSearch-Button:hover{background:transparent}.DocSearch-Button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}.DocSearch-Button-Key--pressed{transform:none;box-shadow:none}.DocSearch-Button:focus:not(:focus-visible){outline:none!important}@media (min-width: 768px){.DocSearch-Button{justify-content:flex-start;border:1px solid transparent;border-radius:8px;padding:0 10px 0 12px;width:100%;height:40px;background-color:var(--vp-c-bg-alt)}.DocSearch-Button:hover{border-color:var(--vp-c-brand-1);background:var(--vp-c-bg-alt)}}.DocSearch-Button .DocSearch-Button-Container{display:flex;align-items:center}.DocSearch-Button .DocSearch-Search-Icon{position:relative;width:16px;height:16px;color:var(--vp-c-text-1);fill:currentColor;transition:color .5s}.DocSearch-Button:hover .DocSearch-Search-Icon{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Search-Icon{top:1px;margin-right:8px;width:14px;height:14px;color:var(--vp-c-text-2)}}.DocSearch-Button .DocSearch-Button-Placeholder{display:none;margin-top:2px;padding:0 16px 0 0;font-size:13px;font-weight:500;color:var(--vp-c-text-2);transition:color .5s}.DocSearch-Button:hover .DocSearch-Button-Placeholder{color:var(--vp-c-text-1)}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Placeholder{display:inline-block}}.DocSearch-Button .DocSearch-Button-Keys{direction:ltr;display:none;min-width:auto}@media (min-width: 768px){.DocSearch-Button .DocSearch-Button-Keys{display:flex;align-items:center}}.DocSearch-Button .DocSearch-Button-Key{display:block;margin:2px 0 0;border:1px solid var(--vp-c-divider);border-right:none;border-radius:4px 0 0 4px;padding-left:6px;min-width:0;width:auto;height:22px;line-height:22px;font-family:var(--vp-font-family-base);font-size:12px;font-weight:500;transition:color .5s,border-color .5s}.DocSearch-Button .DocSearch-Button-Key+.DocSearch-Button-Key{border-right:1px solid var(--vp-c-divider);border-left:none;border-radius:0 4px 4px 0;padding-left:2px;padding-right:6px}.DocSearch-Button .DocSearch-Button-Key:first-child{font-size:0!important}.DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"Ctrl";font-size:12px;letter-spacing:normal;color:var(--docsearch-muted-color)}.mac .DocSearch-Button .DocSearch-Button-Key:first-child:after{content:"⌘"}.DocSearch-Button .DocSearch-Button-Key:first-child>*{display:none}.DocSearch-Search-Icon{--icon: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' stroke-width='1.6' viewBox='0 0 20 20'%3E%3Cpath fill='none' stroke='currentColor' stroke-linecap='round' stroke-linejoin='round' d='m14.386 14.386 4.088 4.088-4.088-4.088A7.533 7.533 0 1 1 3.733 3.733a7.533 7.533 0 0 1 10.653 10.653z'/%3E%3C/svg%3E")}.VPNavBarSearch{display:flex;align-items:center}@media (min-width: 768px){.VPNavBarSearch{flex-grow:1;padding-left:24px}}@media (min-width: 960px){.VPNavBarSearch{padding-left:32px}}.dark .DocSearch-Footer{border-top:1px solid var(--vp-c-divider)}.DocSearch-Form{border:1px solid var(--vp-c-brand-1);background-color:var(--vp-c-white)}.dark .DocSearch-Form{background-color:var(--vp-c-default-soft)}.DocSearch-Screen-Icon>svg{margin:auto}.VPNavBarSocialLinks[data-v-0394ad82]{display:none}@media (min-width: 1280px){.VPNavBarSocialLinks[data-v-0394ad82]{display:flex;align-items:center}}.title[data-v-ab179fa1]{display:flex;align-items:center;border-bottom:1px solid transparent;width:100%;height:var(--vp-nav-height);font-size:16px;font-weight:600;color:var(--vp-c-text-1);transition:opacity .25s}@media (min-width: 960px){.title[data-v-ab179fa1]{flex-shrink:0}.VPNavBarTitle.has-sidebar .title[data-v-ab179fa1]{border-bottom-color:var(--vp-c-divider)}}[data-v-ab179fa1] .logo{margin-right:8px;height:var(--vp-nav-logo-height)}.VPNavBarTranslations[data-v-88af2de4]{display:none}@media (min-width: 1280px){.VPNavBarTranslations[data-v-88af2de4]{display:flex;align-items:center}}.title[data-v-88af2de4]{padding:0 24px 0 12px;line-height:32px;font-size:14px;font-weight:700;color:var(--vp-c-text-1)}.VPNavBar[data-v-ccf7ddec]{position:relative;height:var(--vp-nav-height);pointer-events:none;white-space:nowrap;transition:background-color .5s}.VPNavBar[data-v-ccf7ddec]:not(.home){background-color:var(--vp-nav-bg-color)}@media (min-width: 960px){.VPNavBar[data-v-ccf7ddec]:not(.home){background-color:transparent}.VPNavBar[data-v-ccf7ddec]:not(.has-sidebar):not(.home.top){background-color:var(--vp-nav-bg-color)}}.wrapper[data-v-ccf7ddec]{padding:0 8px 0 24px}@media (min-width: 768px){.wrapper[data-v-ccf7ddec]{padding:0 32px}}@media (min-width: 960px){.VPNavBar.has-sidebar .wrapper[data-v-ccf7ddec]{padding:0}}.container[data-v-ccf7ddec]{display:flex;justify-content:space-between;margin:0 auto;max-width:calc(var(--vp-layout-max-width) - 64px);height:var(--vp-nav-height);pointer-events:none}.container>.title[data-v-ccf7ddec],.container>.content[data-v-ccf7ddec]{pointer-events:none}.container[data-v-ccf7ddec] *{pointer-events:auto}@media (min-width: 960px){.VPNavBar.has-sidebar .container[data-v-ccf7ddec]{max-width:100%}}.title[data-v-ccf7ddec]{flex-shrink:0;height:calc(var(--vp-nav-height) - 1px);transition:background-color .5s}@media (min-width: 960px){.VPNavBar.has-sidebar .title[data-v-ccf7ddec]{position:absolute;top:0;left:0;z-index:2;padding:0 32px;width:var(--vp-sidebar-width);height:var(--vp-nav-height);background-color:transparent}}@media (min-width: 1440px){.VPNavBar.has-sidebar .title[data-v-ccf7ddec]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}.content[data-v-ccf7ddec]{flex-grow:1}@media (min-width: 960px){.VPNavBar.has-sidebar .content[data-v-ccf7ddec]{position:relative;z-index:1;padding-right:32px;padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .content[data-v-ccf7ddec]{padding-right:calc((100vw - var(--vp-layout-max-width)) / 2 + 32px);padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.content-body[data-v-ccf7ddec]{display:flex;justify-content:flex-end;align-items:center;height:var(--vp-nav-height);transition:background-color .5s}@media (min-width: 960px){.VPNavBar:not(.home.top) .content-body[data-v-ccf7ddec]{position:relative;background-color:var(--vp-nav-bg-color)}.VPNavBar:not(.has-sidebar):not(.home.top) .content-body[data-v-ccf7ddec]{background-color:transparent}}@media (max-width: 767px){.content-body[data-v-ccf7ddec]{column-gap:.5rem}}.menu+.translations[data-v-ccf7ddec]:before,.menu+.appearance[data-v-ccf7ddec]:before,.menu+.social-links[data-v-ccf7ddec]:before,.translations+.appearance[data-v-ccf7ddec]:before,.appearance+.social-links[data-v-ccf7ddec]:before{margin-right:8px;margin-left:8px;width:1px;height:24px;background-color:var(--vp-c-divider);content:""}.menu+.appearance[data-v-ccf7ddec]:before,.translations+.appearance[data-v-ccf7ddec]:before{margin-right:16px}.appearance+.social-links[data-v-ccf7ddec]:before{margin-left:16px}.social-links[data-v-ccf7ddec]{margin-right:-8px}.divider[data-v-ccf7ddec]{width:100%;height:1px}@media (min-width: 960px){.VPNavBar.has-sidebar .divider[data-v-ccf7ddec]{padding-left:var(--vp-sidebar-width)}}@media (min-width: 1440px){.VPNavBar.has-sidebar .divider[data-v-ccf7ddec]{padding-left:calc((100vw - var(--vp-layout-max-width)) / 2 + var(--vp-sidebar-width))}}.divider-line[data-v-ccf7ddec]{width:100%;height:1px;transition:background-color .5s}.VPNavBar:not(.home) .divider-line[data-v-ccf7ddec]{background-color:var(--vp-c-gutter)}@media (min-width: 960px){.VPNavBar:not(.home.top) .divider-line[data-v-ccf7ddec]{background-color:var(--vp-c-gutter)}.VPNavBar:not(.has-sidebar):not(.home.top) .divider[data-v-ccf7ddec]{background-color:var(--vp-c-gutter)}}.VPNavScreenAppearance[data-v-2d7af913]{display:flex;justify-content:space-between;align-items:center;border-radius:8px;padding:12px 14px 12px 16px;background-color:var(--vp-c-bg-soft)}.text[data-v-2d7af913]{line-height:24px;font-size:12px;font-weight:500;color:var(--vp-c-text-2)}.VPNavScreenMenuLink[data-v-7f31e1f6]{display:block;border-bottom:1px solid var(--vp-c-divider);padding:12px 0 11px;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:border-color .25s,color .25s}.VPNavScreenMenuLink[data-v-7f31e1f6]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupLink[data-v-19976ae1]{display:block;margin-left:12px;line-height:32px;font-size:14px;font-weight:400;color:var(--vp-c-text-1);transition:color .25s}.VPNavScreenMenuGroupLink[data-v-19976ae1]:hover{color:var(--vp-c-brand-1)}.VPNavScreenMenuGroupSection[data-v-8133b170]{display:block}.title[data-v-8133b170]{line-height:32px;font-size:13px;font-weight:700;color:var(--vp-c-text-2);transition:color .25s}.VPNavScreenMenuGroup[data-v-ff6087d4]{border-bottom:1px solid var(--vp-c-divider);height:48px;overflow:hidden;transition:border-color .5s}.VPNavScreenMenuGroup .items[data-v-ff6087d4]{visibility:hidden}.VPNavScreenMenuGroup.open .items[data-v-ff6087d4]{visibility:visible}.VPNavScreenMenuGroup.open[data-v-ff6087d4]{padding-bottom:10px;height:auto}.VPNavScreenMenuGroup.open .button[data-v-ff6087d4]{padding-bottom:6px;color:var(--vp-c-brand-1)}.VPNavScreenMenuGroup.open .button-icon[data-v-ff6087d4]{transform:rotate(45deg)}.button[data-v-ff6087d4]{display:flex;justify-content:space-between;align-items:center;padding:12px 4px 11px 0;width:100%;line-height:24px;font-size:14px;font-weight:500;color:var(--vp-c-text-1);transition:color .25s}.button[data-v-ff6087d4]:hover{color:var(--vp-c-brand-1)}.button-icon[data-v-ff6087d4]{transition:transform .25s}.group[data-v-ff6087d4]:first-child{padding-top:0}.group+.group[data-v-ff6087d4],.group+.item[data-v-ff6087d4]{padding-top:4px}.VPNavScreenTranslations[data-v-858fe1a4]{height:24px;overflow:hidden}.VPNavScreenTranslations.open[data-v-858fe1a4]{height:auto}.title[data-v-858fe1a4]{display:flex;align-items:center;font-size:14px;font-weight:500;color:var(--vp-c-text-1)}.icon[data-v-858fe1a4]{font-size:16px}.icon.lang[data-v-858fe1a4]{margin-right:8px}.icon.chevron[data-v-858fe1a4]{margin-left:4px}.list[data-v-858fe1a4]{padding:4px 0 0 24px}.link[data-v-858fe1a4]{line-height:32px;font-size:13px;color:var(--vp-c-text-1)}.VPNavScreen[data-v-cc5739dd]{position:fixed;top:calc(var(--vp-nav-height) + var(--vp-layout-top-height, 0px) + 1px);right:0;bottom:0;left:0;padding:0 32px;width:100%;background-color:var(--vp-nav-screen-bg-color);overflow-y:auto;transition:background-color .5s;pointer-events:auto}.VPNavScreen.fade-enter-active[data-v-cc5739dd],.VPNavScreen.fade-leave-active[data-v-cc5739dd]{transition:opacity .25s}.VPNavScreen.fade-enter-active .container[data-v-cc5739dd],.VPNavScreen.fade-leave-active .container[data-v-cc5739dd]{transition:transform .25s ease}.VPNavScreen.fade-enter-from[data-v-cc5739dd],.VPNavScreen.fade-leave-to[data-v-cc5739dd]{opacity:0}.VPNavScreen.fade-enter-from .container[data-v-cc5739dd],.VPNavScreen.fade-leave-to .container[data-v-cc5739dd]{transform:translateY(-8px)}@media (min-width: 768px){.VPNavScreen[data-v-cc5739dd]{display:none}}.container[data-v-cc5739dd]{margin:0 auto;padding:24px 0 96px;max-width:288px}.menu+.translations[data-v-cc5739dd],.menu+.appearance[data-v-cc5739dd],.translations+.appearance[data-v-cc5739dd]{margin-top:24px}.menu+.social-links[data-v-cc5739dd]{margin-top:16px}.appearance+.social-links[data-v-cc5739dd]{margin-top:16px}.VPNav[data-v-ae24b3ad]{position:relative;top:var(--vp-layout-top-height, 0px);left:0;z-index:var(--vp-z-index-nav);width:100%;pointer-events:none;transition:background-color .5s}@media (min-width: 960px){.VPNav[data-v-ae24b3ad]{position:fixed}}.VPSidebarItem.level-0[data-v-b8d55f3b]{padding-bottom:24px}.VPSidebarItem.collapsed.level-0[data-v-b8d55f3b]{padding-bottom:10px}.item[data-v-b8d55f3b]{position:relative;display:flex;width:100%}.VPSidebarItem.collapsible>.item[data-v-b8d55f3b]{cursor:pointer}.indicator[data-v-b8d55f3b]{position:absolute;top:6px;bottom:6px;left:-17px;width:2px;border-radius:2px;transition:background-color .25s}.VPSidebarItem.level-2.is-active>.item>.indicator[data-v-b8d55f3b],.VPSidebarItem.level-3.is-active>.item>.indicator[data-v-b8d55f3b],.VPSidebarItem.level-4.is-active>.item>.indicator[data-v-b8d55f3b],.VPSidebarItem.level-5.is-active>.item>.indicator[data-v-b8d55f3b]{background-color:var(--vp-c-brand-1)}.link[data-v-b8d55f3b]{display:flex;align-items:center;flex-grow:1}.text[data-v-b8d55f3b]{flex-grow:1;padding:4px 0;line-height:24px;font-size:14px;transition:color .25s}.VPSidebarItem.level-0 .text[data-v-b8d55f3b]{font-weight:700;color:var(--vp-c-text-1)}.VPSidebarItem.level-1 .text[data-v-b8d55f3b],.VPSidebarItem.level-2 .text[data-v-b8d55f3b],.VPSidebarItem.level-3 .text[data-v-b8d55f3b],.VPSidebarItem.level-4 .text[data-v-b8d55f3b],.VPSidebarItem.level-5 .text[data-v-b8d55f3b]{font-weight:500;color:var(--vp-c-text-2)}.VPSidebarItem.level-0.is-link>.item>.link:hover .text[data-v-b8d55f3b],.VPSidebarItem.level-1.is-link>.item>.link:hover .text[data-v-b8d55f3b],.VPSidebarItem.level-2.is-link>.item>.link:hover .text[data-v-b8d55f3b],.VPSidebarItem.level-3.is-link>.item>.link:hover .text[data-v-b8d55f3b],.VPSidebarItem.level-4.is-link>.item>.link:hover .text[data-v-b8d55f3b],.VPSidebarItem.level-5.is-link>.item>.link:hover .text[data-v-b8d55f3b]{color:var(--vp-c-brand-1)}.VPSidebarItem.level-0.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-1.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-2.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-3.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-4.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-5.has-active>.item>.text[data-v-b8d55f3b],.VPSidebarItem.level-0.has-active>.item>.link>.text[data-v-b8d55f3b],.VPSidebarItem.level-1.has-active>.item>.link>.text[data-v-b8d55f3b],.VPSidebarItem.level-2.has-active>.item>.link>.text[data-v-b8d55f3b],.VPSidebarItem.level-3.has-active>.item>.link>.text[data-v-b8d55f3b],.VPSidebarItem.level-4.has-active>.item>.link>.text[data-v-b8d55f3b],.VPSidebarItem.level-5.has-active>.item>.link>.text[data-v-b8d55f3b]{color:var(--vp-c-text-1)}.VPSidebarItem.level-0.is-active>.item .link>.text[data-v-b8d55f3b],.VPSidebarItem.level-1.is-active>.item .link>.text[data-v-b8d55f3b],.VPSidebarItem.level-2.is-active>.item .link>.text[data-v-b8d55f3b],.VPSidebarItem.level-3.is-active>.item .link>.text[data-v-b8d55f3b],.VPSidebarItem.level-4.is-active>.item .link>.text[data-v-b8d55f3b],.VPSidebarItem.level-5.is-active>.item .link>.text[data-v-b8d55f3b]{color:var(--vp-c-brand-1)}.caret[data-v-b8d55f3b]{display:flex;justify-content:center;align-items:center;margin-right:-7px;width:32px;height:32px;color:var(--vp-c-text-3);cursor:pointer;transition:color .25s;flex-shrink:0}.item:hover .caret[data-v-b8d55f3b]{color:var(--vp-c-text-2)}.item:hover .caret[data-v-b8d55f3b]:hover{color:var(--vp-c-text-1)}.caret-icon[data-v-b8d55f3b]{font-size:18px;transform:rotate(90deg);transition:transform .25s}.VPSidebarItem.collapsed .caret-icon[data-v-b8d55f3b]{transform:rotate(0)}.VPSidebarItem.level-1 .items[data-v-b8d55f3b],.VPSidebarItem.level-2 .items[data-v-b8d55f3b],.VPSidebarItem.level-3 .items[data-v-b8d55f3b],.VPSidebarItem.level-4 .items[data-v-b8d55f3b],.VPSidebarItem.level-5 .items[data-v-b8d55f3b]{border-left:1px solid var(--vp-c-divider);padding-left:16px}.VPSidebarItem.collapsed .items[data-v-b8d55f3b]{display:none}.VPSidebar[data-v-575e6a36]{position:fixed;top:var(--vp-layout-top-height, 0px);bottom:0;left:0;z-index:var(--vp-z-index-sidebar);padding:32px 32px 96px;width:calc(100vw - 64px);max-width:320px;background-color:var(--vp-sidebar-bg-color);opacity:0;box-shadow:var(--vp-c-shadow-3);overflow-x:hidden;overflow-y:auto;transform:translate(-100%);transition:opacity .5s,transform .25s ease;overscroll-behavior:contain}.VPSidebar.open[data-v-575e6a36]{opacity:1;visibility:visible;transform:translate(0);transition:opacity .25s,transform .5s cubic-bezier(.19,1,.22,1)}.dark .VPSidebar[data-v-575e6a36]{box-shadow:var(--vp-shadow-1)}@media (min-width: 960px){.VPSidebar[data-v-575e6a36]{padding-top:var(--vp-nav-height);width:var(--vp-sidebar-width);max-width:100%;background-color:var(--vp-sidebar-bg-color);opacity:1;visibility:visible;box-shadow:none;transform:translate(0)}}@media (min-width: 1440px){.VPSidebar[data-v-575e6a36]{padding-left:max(32px,calc((100% - (var(--vp-layout-max-width) - 64px)) / 2));width:calc((100% - (var(--vp-layout-max-width) - 64px)) / 2 + var(--vp-sidebar-width) - 32px)}}@media (min-width: 960px){.curtain[data-v-575e6a36]{position:sticky;top:-64px;left:0;z-index:1;margin-top:calc(var(--vp-nav-height) * -1);margin-right:-32px;margin-left:-32px;height:var(--vp-nav-height);background-color:var(--vp-sidebar-bg-color)}}.nav[data-v-575e6a36]{outline:0}.group+.group[data-v-575e6a36]{border-top:1px solid var(--vp-c-divider);padding-top:10px}@media (min-width: 960px){.group[data-v-575e6a36]{padding-top:10px;width:calc(var(--vp-sidebar-width) - 64px)}}.VPSkipLink[data-v-0f60ec36]{top:8px;left:8px;padding:8px 16px;z-index:999;border-radius:8px;font-size:12px;font-weight:700;text-decoration:none;color:var(--vp-c-brand-1);box-shadow:var(--vp-shadow-3);background-color:var(--vp-c-bg)}.VPSkipLink[data-v-0f60ec36]:focus{height:auto;width:auto;clip:auto;clip-path:none}@media (min-width: 1280px){.VPSkipLink[data-v-0f60ec36]{top:14px;left:16px}}.Layout[data-v-5d98c3a5]{display:flex;flex-direction:column;min-height:100vh}.VPHomeSponsors[data-v-3d121b4a]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPHomeSponsors[data-v-3d121b4a]{margin:96px 0}@media (min-width: 768px){.VPHomeSponsors[data-v-3d121b4a]{margin:128px 0}}.VPHomeSponsors[data-v-3d121b4a]{padding:0 24px}@media (min-width: 768px){.VPHomeSponsors[data-v-3d121b4a]{padding:0 48px}}@media (min-width: 960px){.VPHomeSponsors[data-v-3d121b4a]{padding:0 64px}}.container[data-v-3d121b4a]{margin:0 auto;max-width:1152px}.love[data-v-3d121b4a]{margin:0 auto;width:fit-content;font-size:28px;color:var(--vp-c-text-3)}.icon[data-v-3d121b4a]{display:inline-block}.message[data-v-3d121b4a]{margin:0 auto;padding-top:10px;max-width:320px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.sponsors[data-v-3d121b4a]{padding-top:32px}.action[data-v-3d121b4a]{padding-top:40px;text-align:center}.VPTeamPage[data-v-7c57f839]{margin:96px 0}@media (min-width: 768px){.VPTeamPage[data-v-7c57f839]{margin:128px 0}}.VPHome .VPTeamPageTitle[data-v-7c57f839-s]{border-top:1px solid var(--vp-c-gutter);padding-top:88px!important}.VPTeamPageSection+.VPTeamPageSection[data-v-7c57f839-s],.VPTeamMembers+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:64px}.VPTeamMembers+.VPTeamMembers[data-v-7c57f839-s]{margin-top:24px}@media (min-width: 768px){.VPTeamPageTitle+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:16px}.VPTeamPageSection+.VPTeamPageSection[data-v-7c57f839-s],.VPTeamMembers+.VPTeamPageSection[data-v-7c57f839-s]{margin-top:96px}}.VPTeamMembers[data-v-7c57f839-s]{padding:0 24px}@media (min-width: 768px){.VPTeamMembers[data-v-7c57f839-s]{padding:0 48px}}@media (min-width: 960px){.VPTeamMembers[data-v-7c57f839-s]{padding:0 64px}}.VPTeamPageTitle[data-v-bf2cbdac]{padding:48px 32px;text-align:center}@media (min-width: 768px){.VPTeamPageTitle[data-v-bf2cbdac]{padding:64px 48px 48px}}@media (min-width: 960px){.VPTeamPageTitle[data-v-bf2cbdac]{padding:80px 64px 48px}}.title[data-v-bf2cbdac]{letter-spacing:0;line-height:44px;font-size:36px;font-weight:500}@media (min-width: 768px){.title[data-v-bf2cbdac]{letter-spacing:-.5px;line-height:56px;font-size:48px}}.lead[data-v-bf2cbdac]{margin:0 auto;max-width:512px;padding-top:12px;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}@media (min-width: 768px){.lead[data-v-bf2cbdac]{max-width:592px;letter-spacing:.15px;line-height:28px;font-size:20px}}.VPTeamPageSection[data-v-b1a88750]{padding:0 32px}@media (min-width: 768px){.VPTeamPageSection[data-v-b1a88750]{padding:0 48px}}@media (min-width: 960px){.VPTeamPageSection[data-v-b1a88750]{padding:0 64px}}.title[data-v-b1a88750]{position:relative;margin:0 auto;max-width:1152px;text-align:center;color:var(--vp-c-text-2)}.title-line[data-v-b1a88750]{position:absolute;top:16px;left:0;width:100%;height:1px;background-color:var(--vp-c-divider)}.title-text[data-v-b1a88750]{position:relative;display:inline-block;padding:0 24px;letter-spacing:0;line-height:32px;font-size:20px;font-weight:500;background-color:var(--vp-c-bg)}.lead[data-v-b1a88750]{margin:0 auto;max-width:480px;padding-top:12px;text-align:center;line-height:24px;font-size:16px;font-weight:500;color:var(--vp-c-text-2)}.members[data-v-b1a88750]{padding-top:40px}.VPTeamMembersItem[data-v-f3fa364a]{display:flex;flex-direction:column;gap:2px;border-radius:12px;width:100%;height:100%;overflow:hidden}.VPTeamMembersItem.small .profile[data-v-f3fa364a]{padding:32px}.VPTeamMembersItem.small .data[data-v-f3fa364a]{padding-top:20px}.VPTeamMembersItem.small .avatar[data-v-f3fa364a]{width:64px;height:64px}.VPTeamMembersItem.small .name[data-v-f3fa364a]{line-height:24px;font-size:16px}.VPTeamMembersItem.small .affiliation[data-v-f3fa364a]{padding-top:4px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .desc[data-v-f3fa364a]{padding-top:12px;line-height:20px;font-size:14px}.VPTeamMembersItem.small .links[data-v-f3fa364a]{margin:0 -16px -20px;padding:10px 0 0}.VPTeamMembersItem.medium .profile[data-v-f3fa364a]{padding:48px 32px}.VPTeamMembersItem.medium .data[data-v-f3fa364a]{padding-top:24px;text-align:center}.VPTeamMembersItem.medium .avatar[data-v-f3fa364a]{width:96px;height:96px}.VPTeamMembersItem.medium .name[data-v-f3fa364a]{letter-spacing:.15px;line-height:28px;font-size:20px}.VPTeamMembersItem.medium .affiliation[data-v-f3fa364a]{padding-top:4px;font-size:16px}.VPTeamMembersItem.medium .desc[data-v-f3fa364a]{padding-top:16px;max-width:288px;font-size:16px}.VPTeamMembersItem.medium .links[data-v-f3fa364a]{margin:0 -16px -12px;padding:16px 12px 0}.profile[data-v-f3fa364a]{flex-grow:1;background-color:var(--vp-c-bg-soft)}.data[data-v-f3fa364a]{text-align:center}.avatar[data-v-f3fa364a]{position:relative;flex-shrink:0;margin:0 auto;border-radius:50%;box-shadow:var(--vp-shadow-3)}.avatar-img[data-v-f3fa364a]{position:absolute;top:0;right:0;bottom:0;left:0;border-radius:50%;object-fit:cover}.name[data-v-f3fa364a]{margin:0;font-weight:600}.affiliation[data-v-f3fa364a]{margin:0;font-weight:500;color:var(--vp-c-text-2)}.org.link[data-v-f3fa364a]{color:var(--vp-c-text-2);transition:color .25s}.org.link[data-v-f3fa364a]:hover{color:var(--vp-c-brand-1)}.desc[data-v-f3fa364a]{margin:0 auto}.desc[data-v-f3fa364a] a{font-weight:500;color:var(--vp-c-brand-1);text-decoration-style:dotted;transition:color .25s}.links[data-v-f3fa364a]{display:flex;justify-content:center;height:56px}.sp-link[data-v-f3fa364a]{display:flex;justify-content:center;align-items:center;text-align:center;padding:16px;font-size:14px;font-weight:500;color:var(--vp-c-sponsor);background-color:var(--vp-c-bg-soft);transition:color .25s,background-color .25s}.sp .sp-link.link[data-v-f3fa364a]:hover,.sp .sp-link.link[data-v-f3fa364a]:focus{outline:none;color:var(--vp-c-white);background-color:var(--vp-c-sponsor)}.sp-icon[data-v-f3fa364a]{margin-right:8px;font-size:16px}.VPTeamMembers.small .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(224px,1fr))}.VPTeamMembers.small.count-1 .container[data-v-6cb0dbc4]{max-width:276px}.VPTeamMembers.small.count-2 .container[data-v-6cb0dbc4]{max-width:576px}.VPTeamMembers.small.count-3 .container[data-v-6cb0dbc4]{max-width:876px}.VPTeamMembers.medium .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(256px,1fr))}@media (min-width: 375px){.VPTeamMembers.medium .container[data-v-6cb0dbc4]{grid-template-columns:repeat(auto-fit,minmax(288px,1fr))}}.VPTeamMembers.medium.count-1 .container[data-v-6cb0dbc4]{max-width:368px}.VPTeamMembers.medium.count-2 .container[data-v-6cb0dbc4]{max-width:760px}.container[data-v-6cb0dbc4]{display:grid;gap:24px;margin:0 auto;max-width:1152px} diff --git a/assets/zh_contributing_index.md.BgLPhRbJ.js b/assets/zh_contributing_index.md.BgLPhRbJ.js new file mode 100644 index 00000000..b19744bf --- /dev/null +++ b/assets/zh_contributing_index.md.BgLPhRbJ.js @@ -0,0 +1 @@ +import{_ as e,c as a,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const _=JSON.parse('{"title":"贡献指南","description":"","frontmatter":{},"headers":[],"relativePath":"zh/contributing/index.md","filePath":"zh/contributing/index.md"}'),l={name:"zh/contributing/index.md"},t=i('

贡献指南

感谢你能够看到这里,本项目非常欢迎你的贡献!

贡献方法

如果你有代码或文档想要贡献,需要先了解以下内容。

  1. 你要贡献什么类型的代码?(新扩展、修复 Bug、安全问题、项目框架优化、文档)
  2. 如果你贡献了新文件或新片段,你的代码是否经过 php-cs-fixerphpstan 的检查?
  3. 在贡献代码前是否充分阅读了 开发指南

如果你可以回答以上问题,并已经对代码做出了修改,可以及时在项目 GitHub 仓库发起 Pull Request。待代码审查完毕后,可根据建议修改代码,或直接合并到主分支。

贡献类型

本项目主要用途是编译静态链接的 PHP 二进制,基于 symfony/console 编写了命令行处理功能。在开发之前,如果你对它不够熟悉, 可以先查看 symfony/console 文档

安全问题

因为本项目基本上是属于本地运行的 PHP 项目,一般来说不会存在远程攻击行为。但如果你发现了此类问题,请不要在 GitHub 仓库提交 PR 或 Issue, 你需要通过 邮件 的方式联系项目维护者(crazywhalecc)。

修复 Bug

修复 Bug 一般不涉及项目结构和框架的修改,所以如果你可以定位到错误代码并直接修复它,请直接提交 PR。

新扩展

对于添加一个新扩展来说,你需要先了解一些本项目的基本结构,以及如何根据现有的逻辑添加新扩展。在本页的下一章节将会详细介绍。 总的来说,你需要:

  1. 评估扩展是否可以内联编译到 PHP 中。
  2. 评估扩展的依赖库(如果有)是否可以静态编译。
  3. 写出扩展的依赖库在不同平台编译命令。
  4. 验证扩展及其依赖库能否与现有扩展和依赖库兼容。
  5. 验证扩展在 climicrofpmembed 几种 SAPI 中均正常工作。
  6. 编写文档,加入你的扩展。

项目框架优化

如果你已经熟悉 symfony/console 的工作原理,并同时要对项目的框架进行一些修改或优化,请先了解以下事情:

  1. 加入扩展不属于项目框架优化,但如果你在加入新的扩展时发现不得不优化框架,则需先对框架本身进行修改,然后再加入扩展。
  2. 对于一些大规模逻辑修改(例如涉及 LibraryBase、Extension 对象等的修改)时,建议先提交 Issue 或 Draft PR 进行讨论方案。
  3. 项目早期为纯中文开发项目,代码中存在一部分中文的注释。国际化项目后你可以提交 PR 将这些注释翻译为英语。
  4. 请不要在代码中提交包含较多无用的代码片段,例如大量未被使用的变量、方法、类、重复写了很多次的代码。
',18),r=[t];function n(c,d,s,h,p,u){return o(),a("div",null,r)}const f=e(l,[["render",n]]);export{_ as __pageData,f as default}; diff --git a/assets/zh_contributing_index.md.BgLPhRbJ.lean.js b/assets/zh_contributing_index.md.BgLPhRbJ.lean.js new file mode 100644 index 00000000..8df89bde --- /dev/null +++ b/assets/zh_contributing_index.md.BgLPhRbJ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const _=JSON.parse('{"title":"贡献指南","description":"","frontmatter":{},"headers":[],"relativePath":"zh/contributing/index.md","filePath":"zh/contributing/index.md"}'),l={name:"zh/contributing/index.md"},t=i("",18),r=[t];function n(c,d,s,h,p,u){return o(),a("div",null,r)}const f=e(l,[["render",n]]);export{_ as __pageData,f as default}; diff --git a/assets/zh_develop_doctor-module.md.CPRdzud3.js b/assets/zh_develop_doctor-module.md.CPRdzud3.js new file mode 100644 index 00000000..21890f06 --- /dev/null +++ b/assets/zh_develop_doctor-module.md.CPRdzud3.js @@ -0,0 +1,29 @@ +import{_ as s,c as i,o as a,a1 as n}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Doctor 模块","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/doctor-module.md","filePath":"zh/develop/doctor-module.md"}'),h={name:"zh/develop/doctor-module.md"},l=n(`

Doctor 模块

Doctor 模块是一个较为独立的用于检查系统环境的模块,可使用命令 bin/spc doctor 进入,入口的命令类在 DoctorCommand.php 中。

Doctor 模块是一个检查单,里面有一系列的检查项目和自动修复项目。这些项目都存放在 src/SPC/doctor/item/ 目录中, 并且使用了两种 Attribute 用作检查项标记和自动修复项目标记:#[AsCheckItem]#[AsFixItem]

以现有的检查项 if necessary tools are installed,它是用于检查编译必需的包是否安装在 macOS 系统内,下面是它的源码:

php
use SPC\\doctor\\AsCheckItem;
+use SPC\\doctor\\AsFixItem;
+use SPC\\doctor\\CheckResult;
+
+#[AsCheckItem('if necessary tools are installed', limit_os: 'Darwin', level: 997)]
+public function checkCliTools(): ?CheckResult
+{
+    $missing = [];
+    foreach (self::REQUIRED_COMMANDS as $cmd) {
+        if ($this->findCommand($cmd) === null) {
+            $missing[] = $cmd;
+        }
+    }
+    if (!empty($missing)) {
+        return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools', [$missing]);
+    }
+    return CheckResult::ok();
+}

属性的第一个参数就是检查项目的名称,后面的 limit_os 参数是限制了该检查项仅在指定的系统下触发,level 是执行该检查项的优先级,数字越大,优先级越高。

里面用到的 $this->findCommand() 方法为 SPC\\builder\\traits\\UnixSystemUtilTrait 的方法,用途是查找系统命令所在位置,找不到时返回 NULL。

每个检查项的方法都应该返回一个 SPC\\doctor\\CheckResult

  • 在返回 CheckResult::fail() 时,第一个参数用于输出终端的错误提示,第二个参数是在这个检查项可自动修复时的修复项目名称。
  • 在返回 CheckResult::ok() 时,表明检查通过。你也可以传递一个参数,用于返回检查结果,例如:CheckResult::ok('OS supported')
  • 在返回 CheckResult::fail() 时,如果包含了第三个参数,第三个参数的数组将被当作 AsFixItem 的参数。

下面是这个检查项对应的自动修复项的方法:

php
#[AsFixItem('build-tools')]
+public function fixBuildTools(array $missing): bool
+{
+    foreach ($missing as $cmd) {
+        try {
+            shell(true)->exec('brew install ' . escapeshellarg($cmd));
+        } catch (RuntimeException) {
+            return false;
+        }
+    }
+    return true;
+}

#[AsFixItem()] 属性传入的参数即修复项的名称,该方法必须返回 True 或 False。当返回 False 时,表明自动修复失败,需要手动处理。

此处的代码中 shell()->exec() 是项目的执行命令的方法,用于替代 exec()system(),同时提供了 debug、获取执行状态、进入目录等特性。

`,13),k=[l];function t(p,e,d,E,r,c){return a(),i("div",null,k)}const y=s(h,[["render",t]]);export{g as __pageData,y as default}; diff --git a/assets/zh_develop_doctor-module.md.CPRdzud3.lean.js b/assets/zh_develop_doctor-module.md.CPRdzud3.lean.js new file mode 100644 index 00000000..ccdee222 --- /dev/null +++ b/assets/zh_develop_doctor-module.md.CPRdzud3.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as n}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"Doctor 模块","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/doctor-module.md","filePath":"zh/develop/doctor-module.md"}'),h={name:"zh/develop/doctor-module.md"},l=n("",13),k=[l];function t(p,e,d,E,r,c){return a(),i("div",null,k)}const y=s(h,[["render",t]]);export{g as __pageData,y as default}; diff --git a/assets/zh_develop_index.md.CISWAEXj.js b/assets/zh_develop_index.md.CISWAEXj.js new file mode 100644 index 00000000..031e7c09 --- /dev/null +++ b/assets/zh_develop_index.md.CISWAEXj.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as t,a1 as o}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"开发简介","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/index.md","filePath":"zh/develop/index.md"}'),p={name:"zh/develop/index.md"},i=o('

开发简介

开发本项目需要安装部署 PHP 环境,以及一些 PHP 项目常用的扩展和 Composer。

项目的开发环境和运行环境几乎完全一致,你可以参照 指南-本地构建 部分安装系统 PHP 或使用本项目预构建的静态 PHP 作为环境,这里不再赘述。

抛开用途,本项目本身其实就是一个 php-cli 程序,你可以将它当作一个正常的 PHP 项目进行编辑和开发,同时你需要了解不同系统的 Shell 命令行。

本项目目前的目的就是为了编译静态编译的独立 PHP,但主体部分也包含编译很多依赖库的静态版本,所以你可以复用这套编译逻辑,用于构建其他程序的独立二进制版本,例如 Nginx 等。

环境准备

开发本项目需要 PHP 环境。你可以使用系统自带的 PHP,也可以使用本项目构建的静态 PHP。

无论是使用哪种 PHP,在开发环境,你需要安装这些扩展:

curl,dom,filter,mbstring,openssl,pcntl,phar,posix,sodium,tokenizer,xml,xmlwriter

static-php-cli 项目本身不需要这么多扩展,但在开发过程中,你会用到 Composer、PHPUnit 等工具,它们需要这些扩展。

对于 static-php-cli 自身构建的 micro 自执行二进制,仅需要 pcntl,posix,mbstring,tokenizer,phar

开始开发

继续向下查看项目结构的文档,你可以从中了解 static-php-cli 是如何运作的。

',13),s=[i];function n(c,r,l,d,h,P){return t(),a("div",null,s)}const u=e(p,[["render",n]]);export{m as __pageData,u as default}; diff --git a/assets/zh_develop_index.md.CISWAEXj.lean.js b/assets/zh_develop_index.md.CISWAEXj.lean.js new file mode 100644 index 00000000..16bb2e77 --- /dev/null +++ b/assets/zh_develop_index.md.CISWAEXj.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o as t,a1 as o}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"开发简介","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/index.md","filePath":"zh/develop/index.md"}'),p={name:"zh/develop/index.md"},i=o("",13),s=[i];function n(c,r,l,d,h,P){return t(),a("div",null,s)}const u=e(p,[["render",n]]);export{m as __pageData,u as default}; diff --git a/assets/zh_develop_source-module.md.DMk5GAAn.js b/assets/zh_develop_source-module.md.DMk5GAAn.js new file mode 100644 index 00000000..e9b2bb2a --- /dev/null +++ b/assets/zh_develop_source-module.md.DMk5GAAn.js @@ -0,0 +1,105 @@ +import{_ as s,c as i,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"资源模块","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/source-module.md","filePath":"zh/develop/source-module.md"}'),n={name:"zh/develop/source-module.md"},l=t(`

资源模块

static-php-cli 的下载资源模块是一个主要的功能,它包含了所依赖的库、外部扩展、PHP 源码的下载方式和资源解压方式。 下载的配置文件主要涉及 source.jsonpkg.json 文件,这个文件记录了所有可下载的资源的下载方式。

下载功能主要涉及的命令有 bin/spc downloadbin/spc extract。其中 download 命令是一个下载器,它会根据配置文件下载资源; extract 命令是一个解压器,它会根据配置文件解压资源。

一般来说,下载资源可能会比较慢,因为这些资源来源于各个官网、GitHub 等不同位置,同时它们也占用了较大空间,所以你可以在一次下载资源后,可重复使用。

下载器的配置文件是 source.json,它包含了所有资源的下载方式,你可以在其中添加你需要的资源下载方式,也可以修改已有的资源下载方式。

每个资源的下载配置结构如下,下面是 libevent 扩展对应的资源下载配置:

json
{
+  "libevent": {
+    "type": "ghrel",
+    "repo": "libevent/libevent",
+    "match": "libevent.+\\\\.tar\\\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

这里最主要的字段是 type,目前它支持的类型有:

  • url: 直接使用 URL 下载,例如:https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz
  • ghrel: 使用 GitHub Release API 下载,即从 GitHub 项目发布的最新版本中上传的附件下载。
  • ghtar: 使用 GitHub Release API 下载,与 ghrel 不同的是,ghtar 是从项目的最新 Release 中找 source code (tar.gz) 下载的。
  • ghtagtar: 使用 GitHub Release API 下载,与 ghtar 相比,ghtagtar 可以从 tags 列表找最新的,并下载 tar.gz 格式的源码(因为有些项目只使用了 tag 发布版本)。
  • bitbuckettag: 使用 BitBucket API 下载,基本和 ghtagtar 相同,只是这个适用于 BitBucket。
  • git: 直接从一个 Git 地址克隆项目来下载资源,适用于任何公开 Git 仓库。
  • filelist: 使用爬虫爬取提供文件索引的 Web 下载站点,并获取最新版本的文件名并下载。
  • custom: 如果以上下载方式都不能满足,你可以编写 custom 后,在 src/SPC/store/source/ 下新建一个类,并继承 CustomSourceBase,自己编写下载脚本。

source.json 通用参数

source.json 中每个源文件拥有以下字段:

  • license: 源代码的开源许可证,见下方 开源许可证 章节
  • type: 必须为上面提到的类型之一
  • path(可选): 释放源码到指定目录而非 source/{name}

TIP

source.json 中的 path 参数可指定相对路径或绝对路径。当指定为相对路径时,路径基于 source/

下载类型 - url

url 类型的资源指的是从 URL 直接下载文件。

包含的参数有:

  • url: 文件的下载地址,如 https://example.com/file.tgz
  • filename(可选): 保存到本地的文件名,如不指定,则使用 url 的文件名

例子(下载 imagick 扩展,并解压缩到 php 源码的扩展存放路径):

json
{
+  "ext-imagick": {
+    "type": "url",
+    "url": "https://pecl.php.net/get/imagick",
+    "path": "php-src/ext/imagick",
+    "filename": "imagick.tgz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghrel

ghrel 会从 GitHub Release 中上传的 Assets 下载文件。首先使用 GitHub Release API 获取最新版本,然后根据正则匹配方式下载相应的文件。

包含的参数有:

  • repo: GitHub 仓库名称
  • match: 匹配 Assets 文件的正则表达式
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(下载 libsodium 库,匹配 Release 中的 libsodium-x.y.tar.gz 文件):

json
{
+  "libsodium": {
+    "type": "ghrel",
+    "repo": "jedisct1/libsodium",
+    "match": "libsodium-\\\\d+(\\\\.\\\\d+)*\\\\.tar\\\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghtar

ghtar 会从 GitHub Release Tag 下载文件,与 ghrel 不同的是,ghtar 是从项目的最新 Release 中找 source code (tar.gz) 下载的。

包含的参数有:

  • repo: GitHub 仓库名称
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(brotli 库):

json
{
+  "brotli": {
+    "type": "ghtar",
+    "repo": "google/brotli",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghtagtar

使用 GitHub Release API 下载,与 ghtar 相比,ghtagtar 可以从 tags 列表找最新的,并下载 tar.gz 格式的源码(因为有些项目只使用了 tag 发布版本)。

包含的参数有:

  • repo: GitHub 仓库名称
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(gmp 库):

json
{
+  "gmp": {
+    "type": "ghtagtar",
+    "repo": "alisw/GMP",
+    "license": {
+      "type": "text",
+      "text": "EXAMPLE LICENSE"
+    }
+  }
+}

下载类型 - bitbuckettag

使用 BitBucket API 下载,基本和 ghtagtar 相同,只是这个适用于 BitBucket。

包含的参数有:

  • repo: BitBucket 仓库名称

下载类型 - git

直接从一个 Git 地址克隆项目来下载资源,适用于任何公开 Git 仓库。

包含的参数有:

  • url: Git 链接(仅限 HTTPS)
  • rev: 分支名称
json
{
+  "imap": {
+    "type": "git",
+    "url": "https://github.com/static-php/imap.git",
+    "rev": "master",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - filelist

使用爬虫爬取提供文件索引的 Web 下载站点,并获取最新版本的文件名并下载。

注意,该方法仅限于镜像站、GNU 官网等具有页面 index 功能的静态站点使用。

包含的参数有:

  • url: 要爬取文件最新版本的页面 URL
  • regex: 匹配文件名及下载链接的正则表达式

例子(从 GNU 官网下载 libiconv 库):

json
{
+  "libiconv": {
+    "type": "filelist",
+    "url": "https://ftp.gnu.org/gnu/libiconv/",
+    "regex": "/href=\\"(?<file>libiconv-(?<version>[^\\"]+)\\\\.tar\\\\.gz)\\"/",
+    "license": {
+      "type": "file",
+      "path": "COPYING"
+    }
+  }
+}

下载类型 - custom

如果以上下载方式都不能满足,你可以编写 custom 后,在 src/SPC/store/source/ 下新建一个类,并继承 CustomSourceBase,自己编写下载脚本。

这里不再赘述,你可以查看 src/SPC/store/source/PhpSource.phpsrc/SPC/store/source/PostgreSQLSource.php 作为例子。

pkg.json 通用参数

pkg.json 存放的是非源码类型的文件资源,例如 musl-toolchain、UPX 等预编译的工具。它的使用包含:

  • type: 与 source.json 相同的类型及不同种类的参数。
  • extract(可选): 下载后解压缩的路径,默认为 pkgroot/{pkg_name}
  • extract-files(可选): 下载后仅解压指定的文件到指定位置。

需要注意的是,pkg.json 不涉及源代码的编译和修改分发,所以没有 license 开源许可证字段。并且你不能同时使用 extractextract-files 参数。

例子(下载 nasm 到本地,并只提取程序文件到 PHP SDK):

json
{
+  "nasm-x86_64-win": {
+    "type": "url",
+    "url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
+    "extract-files": {
+      "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
+      "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
+    }
+  }
+}

extract-files 中的键名为源文件夹下的文件,键值为存放的路径。存放的路径可以使用以下变量:

  • {php_sdk_path}: (仅限 Windows)PHP SDK 路径
  • {pkg_root_path}: pkgroot/
  • {working_dir}: 当前工作目录
  • {download_path}: 下载目录
  • {source_path}: 源码解压缩目录

extract-files 不使用变量且为相对路径时,相对路径的目录为 {working_dir}

开源许可证

对于 source.json 而言,每个源文件都应包含开源许可证。license 字段存放了开源许可证的信息。

每个 license 包含的参数有:

  • type: filetext
  • path: 源代码目录中的许可证文件(当 typefile 时,此项必填)
  • text: 许可证文本(当 typetext 时,此项必填)

例子(yaml 扩展的源代码中带有 LICENSE 文件):

json
{
+  "yaml": {
+    "type": "git",
+    "path": "php-src/ext/yaml",
+    "rev": "php7",
+    "url": "https://github.com/php/pecl-file_formats-yaml",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

当开源项目拥有多个许可证时,可指定多个文件:

json
{
+  "libuv": {
+    "type": "ghtar",
+    "repo": "libuv/libuv",
+    "license": [
+      {
+        "type": "file",
+        "path": "LICENSE"
+      },
+      {
+        "type": "file",
+        "path": "LICENSE-extra"
+      }
+    ]
+  }
+}
`,73),p=[l];function e(h,k,o,E,d,r){return a(),i("div",null,p)}const u=s(n,[["render",e]]);export{g as __pageData,u as default}; diff --git a/assets/zh_develop_source-module.md.DMk5GAAn.lean.js b/assets/zh_develop_source-module.md.DMk5GAAn.lean.js new file mode 100644 index 00000000..d77ebcf9 --- /dev/null +++ b/assets/zh_develop_source-module.md.DMk5GAAn.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"资源模块","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/source-module.md","filePath":"zh/develop/source-module.md"}'),n={name:"zh/develop/source-module.md"},l=t("",73),p=[l];function e(h,k,o,E,d,r){return a(),i("div",null,p)}const u=s(n,[["render",e]]);export{g as __pageData,u as default}; diff --git a/assets/zh_develop_structure.md.DJyPDdQ4.js b/assets/zh_develop_structure.md.DJyPDdQ4.js new file mode 100644 index 00000000..8f33abb4 --- /dev/null +++ b/assets/zh_develop_structure.md.DJyPDdQ4.js @@ -0,0 +1,10 @@ +import{_ as e,c as o,o as c,a1 as d}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"项目结构简介","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/structure.md","filePath":"zh/develop/structure.md"}'),a={name:"zh/develop/structure.md"},t=d(`

项目结构简介

static-php-cli 主要包含三种逻辑组件:资源、依赖库、扩展。这三种组件四个配置文件:source.jsonlib.jsonext.jsonpkg.json

一个完整的构建静态 PHP 流程是:

  1. 使用资源下载模块 Downloader 下载指定或所有资源,这些资源包含 PHP 源码、依赖库源码、扩展源码。
  2. 使用资源解压模块 SourceExtractor 解压下载的资源到编译目录。
  3. 使用依赖工具计算出当前加入的扩展的依赖扩展、依赖库,然后对每个需要编译的依赖库进行编译,按照依赖顺序。
  4. 使用对应操作系统下的 Builder 构建每个依赖库后,将其安装到 buildroot 目录。
  5. 如果包含外部扩展(源码没有包含在 PHP 内的扩展),将外部扩展拷贝到 source/php-src/ext/ 目录。
  6. 使用 Builder 构建 PHP 源码,将其安装到 buildroot 目录。

项目主要分为几个文件夹:

  • bin/: 用于存放程序入口文件,包含 bin/spcbin/spc-alpine-dockerbin/setup-runtime
  • config/: 包含了所有项目支持的扩展、依赖库以及这些资源下载的地址、下载方式等,分为四个文件:lib.jsonext.jsonsource.jsonpkg.json
  • src/SPC/: 项目的核心代码,包含了整个框架以及编译各种扩展和库的命令。
  • src/globals/: 项目的全局方法和常量、运行时需要的测试文件(例如:扩展的可用性检查代码)。
  • vendor/: Composer 依赖的目录,你无需对它做出任何修改。

其中运行原理就是启动一个 symfony/consoleConsoleApplication,然后解析用户在终端输入的命令。

基本命令行结构

bin/spc 是一个 PHP 代码入口文件,包含了 Unix 通用的 #!/usr/bin/env php 用来让系统自动以系统安装好的 PHP 解释器执行。 在项目执行了 new ConsoleApplication() 后,框架会自动使用反射的方式,解析 src/SPC/command 目录下的所有类,并将其注册成为命令。

项目并没有直接使用 Symfony 推荐的 Command 注册方式和命令执行方式,这里做出了一点小变动:

  1. 每个命令都使用 #[AsCommand()] Attribute 来注册名称和简介。
  2. execute() 抽象化,让所有命令基于 BaseCommand(它基于 Symfony\\Component\\Console\\Command\\Command),每个命令本身的执行代码写到了 handle() 方法中。
  3. BaseCommand 添加了变量 $no_motd,用于是否在该命令执行时显示 Figlet 欢迎词。
  4. BaseCommandInputInterfaceOutputInterface 保存为成员变量,你可以在命令的类内使用 $this->input$this->output

基本源码结构

项目的源码位于 src/SPC 目录,支持 PSR-4 标准的自动加载,包含以下子目录和类:

  • src/SPC/builder/: 用于不同操作系统下构建依赖库、PHP 及相关扩展的核心编译命令代码,还包含了一些编译的系统工具方法。
  • src/SPC/command/: 项目的所有命令都在这里。
  • src/SPC/doctor/: Doctor 模块,它是一个较为独立的用于检查系统环境的模块,可使用命令 bin/spc doctor 进入。
  • src/SPC/exception/: 异常类。
  • src/SPC/store/: 有关存储、文件和资源的类都在这里。
  • src/SPC/util/: 一些可以复用的工具方法都在这里。
  • src/SPC/ConsoleApplication.php: 命令行程序入口文件。

如果你阅读过源码,你可能会发现还有一个 src/globals/ 目录,它是用于存放一些全局变量、全局方法、构建过程中依赖的非 PSR-4 标准的 PHP 源码,例如测试扩展代码等。

Phar 应用目录问题

和其他 php-cli 项目一样,spc 自身对路径有额外的考虑。 因为 spc 可以在 php-cli directlymicro SAPIphp-cli with Pharvendor with Phar 等多种模式下运行,各类根目录存在歧义。这里会进行一个完整的说明。 此问题一般常见于 PHP 项目中存取文件的基类路径选择问题,尤其是在配合 micro.sfx 使用时容易出现路径问题。

注意,此处仅对你在开发 Phar 项目或 PHP 框架时可能有用。

接下来我们都将 static-php-cli(也就是 spc)当作一个普通的 php 命令行程序来看,你可以将 spc 理解为你自己的任何 php-cli 应用以参考。

下面主要有三个基本的常量理论值,我们建议你在编写 php 项目时引入这三种常量:

  • WORKING_DIR:执行 PHP 脚本时的工作目录
  • SOURCE_ROOT_DIRROOT_DIR:项目文件夹的根目录,一般为 composer.json 所在目录
  • FRAMEWORK_ROOT_DIR:使用框架的根目录,自行开发的框架可能会用到,一般框架目录为只读

你可以在你的框架或者 cli 应用程序入口中定义这些常量,以方便在你的项目中使用路径。

下面是 PHP 内置的常量值,在 PHP 解释器内部已被定义:

  • __DIR__:当前执行脚本的文件所在目录
  • __FILE__:当前执行脚本的文件路径

Git 项目模式(source)

Git 项目模式指的是一个框架或程序本身在当前文件夹以纯文本形式存放,运行通过 php path/to/entry.php 方式。

假设你的项目存放在 /home/example/static-php-cli/ 目录下,或你的项目就是框架本身,里面包含 composer.json 等项目文件:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

我们假设从 src/App/MyCommand.php 中获取以上常量:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIR/home/example/static-php-cli
FRAMEWORK_ROOT_DIR/home/example/static-php-cli
__DIR__/home/example/static-php-cli/src/App
__FILE__/home/example/static-php-cli/src/App/MyCommand.php

这种情况下,WORKING_DIRSOURCE_ROOT_DIRFRAMEWORK_ROOT_DIR 的值是完全一致的:/home/example/static-php-cli。 框架的源码和应用的源码都在当前路径下。

Vendor 库模式(vendor)

Vendor 库模式一般是指你的项目为框架类或者被其他应用作为 composer 依赖项安装到项目中,存放位置在 vendor/author/XXX 目录。

假设你的项目是 crazywhalecc/static-php-cli,你或其他人在另一个项目使用 composer require 安装了这个项目。

我们假设 static-php-cli 中包含同 Git 模式 的除 vendor 目录外的所有文件,并从 src/App/MyCommand 中获取常量值, 目录常量应该是:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIR/home/example/another-app
FRAMEWORK_ROOT_DIR/home/example/another-app/vendor/crazywhalecc/static-php-cli
__DIR__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App
__FILE__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

这里的 SOURCE_ROOT_DIR 就指的是使用 static-php-cli 的项目的根目录。

Git 项目 Phar 模式(source-phar)

Git 项目 Phar 模式指的是将 Git 项目模式的项目目录打包为一个 phar 文件的模式。我们假设 /home/example/static-php-cli 将打包为一个 Phar 文件,目录有以下文件:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

打包为 app.phar 并存放到 /home/example/static-php-cli 目录下时,此时执行 app.phar,假设执行了 src/App/MyCommand 代码,常量在该文件内获取:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
__DIR__phar:///home/example/static-php-cli/app.phar/src/App
__FILE__phar:///home/example/static-php-cli/app.phar/src/App/MyCommand.php

因为在 phar 内读取自身 phar 的文件需要 phar:// 协议进行,所以项目根目录和框架目录将会和 WORKING_DIR 不同。

Vendor 库 Phar 模式(vendor-phar)

Vendor 库 Phar 模式指的是你的项目作为框架安装在其他项目内,存储于 vendor 目录下。

我们假设你的项目目录结构如下:

composer.json # 当前项目的 Composer 配置文件
+box.json # 打包 Phar 的配置文件
+another-app.php # 另一个项目的入口文件
+vendor/crazywhalecc/static-php-cli/* # 你的项目被作为依赖库

将该目录 /home/example/another-app/ 下的这些文件打包为 app.phar 时,对于你的项目而言,下面常量的值应为:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIRphar:///home/example/another-app/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli
__DIR__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App
__FILE__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php
`,49),p=[t];function r(s,l,i,n,h,m){return c(),o("div",null,p)}const b=e(a,[["render",r]]);export{u as __pageData,b as default}; diff --git a/assets/zh_develop_structure.md.DJyPDdQ4.lean.js b/assets/zh_develop_structure.md.DJyPDdQ4.lean.js new file mode 100644 index 00000000..a34faad0 --- /dev/null +++ b/assets/zh_develop_structure.md.DJyPDdQ4.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as c,a1 as d}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"项目结构简介","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/structure.md","filePath":"zh/develop/structure.md"}'),a={name:"zh/develop/structure.md"},t=d("",49),p=[t];function r(s,l,i,n,h,m){return c(),o("div",null,p)}const b=e(a,[["render",r]]);export{u as __pageData,b as default}; diff --git a/assets/zh_develop_system-build-tools.md.DvA9SnOG.js b/assets/zh_develop_system-build-tools.md.DvA9SnOG.js new file mode 100644 index 00000000..230a277a --- /dev/null +++ b/assets/zh_develop_system-build-tools.md.DvA9SnOG.js @@ -0,0 +1,67 @@ +import{_ as s,c as i,o as a,a1 as l}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"系统编译工具","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/system-build-tools.md","filePath":"zh/develop/system-build-tools.md"}'),n={name:"zh/develop/system-build-tools.md"},p=l(`

系统编译工具

static-php-cli 在构建静态 PHP 时使用了许多系统编译工具,这些工具主要包括:

  • autoconf: 用于生成 configure 脚本。
  • make: 用于执行 Makefile
  • cmake: 用于执行 CMakeLists.txt
  • pkg-config: 用于查找依赖库的安装路径。
  • gcc: 用于在 Linux 下编译 C/C++ 语言代码。
  • clang: 用于在 macOS 下编译 C/C++ 语言代码。

对于 Linux 和 macOS 操作系统,这些工具通常可以通过包管理安装,这部分在 doctor 模块中编写了。 理论上我们也可以通过编译和手动下载这些工具,但这样会增加编译的复杂度,所以我们不推荐这样做。

Linux 环境编译工具

对于 Linux 系统来说,不同发行版的编译工具安装方式不同。而且对于静态编译来说,某些发行版的包管理无法安装用于纯静态编译的库和工具, 所以对于 Linux 平台及其不同发行版,我们目前提供了多种编译环境的部署措施。

glibc 环境

glibc 环境指的是系统底层的 libc 库(即所有 C 语言编写的程序动态链接的 C 标准库)使用的是 glibc,这是大多数发行版的默认环境。 例如:Ubuntu、Debian、CentOS、RHEL、openSUSE、Arch Linux 等。

而 glibc 环境下,我们使用的包管理、编译器都是默认指向 glibc 的,glibc 不能被良好地静态链接。它不能被静态链接的原因之一是它的网络库 nss 无法静态编译。

对于 glibc 环境,在 2.0 RC8 及以后的 static-php-cli 及 spc 中,你可以选择两种方式来构建静态 PHP:

  1. 使用 Docker 构建,这是最简单的方式,你可以使用 bin/spc-alpine-docker 来构建,它会在 Alpine Linux 环境下构建。
  2. 使用 bin/spc doctor 安装 musl-wrapper 和 musl-cross-make 套件,然后直接正常构建。(相关源码

一般来说,这两种构建方式的构建结果是一致的,你可以根据实际需求选择。

在 doctor 模块中,static-php-cli 会先检测当前的 Linux 发行版。如果当前发行版是 glibc 环境,会提示需要安装 musl-wrapper 和 musl-cross-make 套件。

在 glibc 环境下安装 musl-wrapper 的过程如下:

  1. 从 musl 官网下载特定版本的 musl-wrapper 源码
  2. 使用从包管理安装的 gcc 编译 musl-wrapper 源码,生成 musl-libc 等库:./configure --disable-gcc-wrapper && make -j && sudo make install
  3. musl-wrapper 相关库将被安装在 /usr/local/musl 目录。

在 glibc 环境下安装 musl-cross-make 的过程如下:

  1. 从 dl.static-php.dev 下载预编译好的 musl-cross-make 压缩包。
  2. 解压到 /usr/local/musl 目录。

TIP

在 glibc 环境下,静态编译可以通过直接安装 musl-wrapper 来实现,但是 musl-wrapper 仅包含了 musl-gcc,而没有 musl-g++,这也就意味着无法编译 C++ 代码。 所以我们需要 musl-cross-make 来提供 musl-g++

而 musl-cross-make 套件无法在本地直接编译的原因是它的编译环境要求比较高(需要 36GB 以上内存,Alpine Linux 下编译),所以我们提供了预编译好的二进制包,可用于所有 Linux 发行版。

同时,部分发行版的包管理提供了 musl-wrapper,但 musl-cross-make 需要匹配对应的 musl-wrapper 版本,所以我们不使用包管理安装 musl-wrapper。

对于如何编译 musl-cross-make,将在本章节内的 编译 musl-cross-make 小节中介绍。

musl 环境

musl 环境指的是系统底层的 libc 库使用的是 musl,这是一种轻量级的 C 标准库,它的特点是可以被良好地静态链接。

对于目前流行的 Linux 发行版,Alpine Linux 使用的就是 musl 环境,所以 static-php-cli 在 Alpine Linux 下可以直接构建静态 PHP,仅需直接从包管理安装基础编译工具(如 gcc、cmake 等)即可。

对于其他发行版,如果你的发行版使用的是 musl 环境,那么你也可以在安装必要的编译工具后直接使用 static-php-cli 构建静态 PHP。

TIP

在 musl 环境下,static-php-cli 会自动跳过 musl-wrapper 和 musl-cross-make 的安装。

Docker 环境

Docker 环境指的是使用 Docker 容器来构建静态 PHP,你可以使用 bin/spc-alpine-docker 来构建。 执行这个命令前需要先安装 Docker,然后在项目根目录执行 bin/spc-alpine-docker 即可。

在执行 bin/spc-alpine-docker 后,static-php-cli 会自动下载 Alpine Linux 镜像,然后构建一个 cwcc-spc-x86_64cwcc-spc-aarch64 的镜像。 然后一切的构建都在这个镜像内进行,相当于在 Alpine Linux 内编译。总的来说,Docker 环境就是 musl 环境。

musl-cross-make 工具链编译

在 Linux 中,尽管你不需要手动编译 musl-cross-make 工具,但是如果你想了解它的编译过程,可以参考这里。 还有一个重要的原因就是,这个可能无法使用 CI、Actions 等自动化工具编译,因为现有的 CI 服务编译环境不满足 musl-cross-make 的编译要求,满足要求的配置价格太高。

musl-cross-make 的编译过程如下:

准备一个 Alpine Linux 环境(直接安装或使用 Docker 均可),编译的过程需要 36GB 以上内存,所以你需要在内存较大的机器上编译。如果没有这么多内存,可能会导致编译失败。

然后将以下内容写入 config.mak 文件内:

makefile
STAT = -static --static
+FLAG = -g0 -Os -Wno-error
+
+ifneq ($(NATIVE),)
+COMMON_CONFIG += CC="$(HOST)-gcc \${STAT}" CXX="$(HOST)-g++ \${STAT}"
+else
+COMMON_CONFIG += CC="gcc \${STAT}" CXX="g++ \${STAT}"
+endif
+
+COMMON_CONFIG += CFLAGS="\${FLAG}" CXXFLAGS="\${FLAG}" LDFLAGS="\${STAT}"
+
+BINUTILS_CONFIG += --enable-gold=yes --enable-gprofng=no
+GCC_CONFIG += --enable-static-pie --disable-cet --enable-default-pie  
+#--enable-default-pie
+
+CONFIG_SUB_REV = 888c8e3d5f7b
+GCC_VER = 13.2.0
+BINUTILS_VER = 2.40
+MUSL_VER = 1.2.4
+GMP_VER = 6.2.1
+MPC_VER = 1.2.1
+MPFR_VER = 4.2.0
+LINUX_VER = 6.1.36

同时,你需要新建一个 gcc-13.2.0.tar.xz.sha1 文件,文件内容如下:

5f95b6d042fb37d45c6cbebfc91decfbc4fb493c  gcc-13.2.0.tar.xz

如果你使用的是 Docker 构建,新建一个 Dockerfile 文件,写入以下内容:

dockerfile
FROM alpine:edge
+
+RUN apk add --no-cache \\
+gcc g++ git make curl perl \\
+rsync patch wget libtool \\
+texinfo autoconf automake \\
+bison tar xz bzip2 zlib \\
+file binutils flex \\
+linux-headers libintl \\
+gettext gettext-dev icu-libs pkgconf \\
+pkgconfig icu-dev bash \\
+ccache libarchive-tools zip
+
+WORKDIR /opt
+
+RUN git clone https://git.zv.io/toolchains/musl-cross-make.git
+WORKDIR /opt/musl-cross-make
+COPY config.mak /opt/musl-cross-make
+COPY gcc-13.2.0.tar.xz.sha1 /opt/musl-cross-make/hashes
+
+RUN make TARGET=x86_64-linux-musl -j || :
+RUN sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+RUN make TARGET=x86_64-linux-musl -j
+RUN make TARGET=x86_64-linux-musl install -j
+RUN tar cvzf x86_64-musl-toolchain.tgz output/*

如果你使用的是非 Docker 环境的 Alpine Linux,可以直接执行 Dockerfile 中的命令,例如:

bash
apk add --no-cache \\
+gcc g++ git make curl perl \\
+rsync patch wget libtool \\
+texinfo autoconf automake \\
+bison tar xz bzip2 zlib \\
+file binutils flex \\
+linux-headers libintl \\
+gettext gettext-dev icu-libs pkgconf \\
+pkgconfig icu-dev bash \\
+ccache libarchive-tools zip
+
+git clone https://git.zv.io/toolchains/musl-cross-make.git
+# 将 config.mak 拷贝到 musl-cross-make 的工作目录内,你需要将 /path/to/config.mak 替换为你的 config.mak 文件路径
+cp /path/to/config.mak musl-cross-make/
+cp /path/to/gcc-13.2.0.tar.xz.sha1 musl-cross-make/hashes
+
+make TARGET=x86_64-linux-musl -j || :
+sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+make TARGET=x86_64-linux-musl -j
+make TARGET=x86_64-linux-musl install -j
+tar cvzf x86_64-musl-toolchain.tgz output/*

TIP

以上所有脚本都适用于 x86_64 架构的 Linux。如果你需要构建 ARM 环境的 musl-cross-make,只需要将上方所有 x86_64 替换为 aarch64 即可。

这个编译过程可能会因为内存不足、网络问题等原因导致编译失败,你可以多尝试几次,或者使用更大内存的机器来编译。 如果遇到了问题,或者你有更好的改进方案,可以在 讨论 中提出。

macOS 环境编译工具

对于 macOS 系统来说,我们使用的编译工具主要是 clang,它是 macOS 系统默认的编译器,同时也是 Xcode 的编译器。

在 macOS 下编译,主要依赖于 Xcode 或 Xcode Command Line Tools,你可以在 App Store 下载 Xcode,或者在终端执行 xcode-select --install 来安装 Xcode Command Line Tools。

此外,在 doctor 环境检查模块中,static-php-cli 会检查 macOS 系统是否安装了 Homebrew、编译工具等,如果没有,会提示你安装,这里不再赘述。

FreeBSD 环境编译工具

FreeBSD 也是 Unix 系统,它的编译工具和 macOS 类似,你可以直接使用包管理 pkg 安装 clang 等编译工具,通过 doctor 命令。

pkg-config 编译

如果你在使用 static-php-cli 构建静态 PHP 时仔细观察编译的日志,你会发现无论编译什么,都会先编译 pkg-config,这是因为 pkg-config 是一个用于查找依赖库的工具。 在早期的 static-php-cli 版本中,我们直接使用了包管理安装的 pkg-config 工具,但是这样会导致一些问题,例如:

  • 即使指定了 PKG_CONFIG_PATHpkg-config 也会尝试从系统路径中查找依赖包。
  • 由于 pkg-config 会从系统路径中查找依赖包,所以如果系统中存在同名的依赖包,可能会导致编译失败。

为了避免以上问题,我们将 pkg-config 编译到用户态的 buildroot/bin 内并使用,使用了 --without-sysroot 等参数来避免从系统路径中查找依赖包。

`,50),e=[p];function h(t,k,c,d,o,r){return a(),i("div",null,e)}const F=s(n,[["render",h]]);export{g as __pageData,F as default}; diff --git a/assets/zh_develop_system-build-tools.md.DvA9SnOG.lean.js b/assets/zh_develop_system-build-tools.md.DvA9SnOG.lean.js new file mode 100644 index 00000000..6a8af1c6 --- /dev/null +++ b/assets/zh_develop_system-build-tools.md.DvA9SnOG.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as l}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"系统编译工具","description":"","frontmatter":{},"headers":[],"relativePath":"zh/develop/system-build-tools.md","filePath":"zh/develop/system-build-tools.md"}'),n={name:"zh/develop/system-build-tools.md"},p=l("",50),e=[p];function h(t,k,c,d,o,r){return a(),i("div",null,e)}const F=s(n,[["render",h]]);export{g as __pageData,F as default}; diff --git a/assets/zh_faq_index.md.Bs3v_2I2.js b/assets/zh_faq_index.md.Bs3v_2I2.js new file mode 100644 index 00000000..786e793d --- /dev/null +++ b/assets/zh_faq_index.md.Bs3v_2I2.js @@ -0,0 +1,2 @@ +import{_ as e,c as a,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"zh/faq/index.md","filePath":"zh/faq/index.md"}'),s={name:"zh/faq/index.md"},c=i(`

常见问题

这里将会编写一些你容易遇到的问题。目前有很多,但是我需要花时间来整理一下。

静态编译的 PHP 可以安装扩展吗

因为传统架构下的 PHP 安装扩展的原理是使用 .so 类型的动态链接的库方式安装新扩展,而使用本项目编译的静态链接的 PHP 无法直接使用动态链接库安装新扩展。

对于 macOS 平台来说,macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 的系统库:/usr/lib/libresolv.9.dylib/usr/lib/libSystem.B.dylib。 所以在 macOS 系统下,在特定的编译条件下可以使用静态编译的 php 二进制文件,同时使用动态链接的扩展:

  1. 使用 --no-strip 参数,将不会对二进制文件去除调试符号等信息,以供使用 Xdebug 等外部 Zend 扩展。
  2. 如果要编译某些 Zend 扩展,使用 Homebrew、MacPorts、源码编译的形式,在所在的操作系统安装一个普通版本的 PHP。
  3. 使用 phpize && ./configure && make 命令编译想要使用的扩展。
  4. 将扩展文件 xxxx.so 拷贝到外部,使用静态编译的 PHP 二进制,例如使用 Xdebug 扩展:cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so"
bash
# 构建静态 php-cli
+bin/spc build ffi --build-cli --no-strip

对于 Linux 平台来说,目前的编译结果为纯静态链接的二进制文件,无法使用动态链接库安装新扩展。

可以支持 Oracle 数据库扩展吗

部分依赖库闭源的扩展,如 oci8sourceguardian 等,它们没有提供纯静态编译的依赖库文件(.a),仅提供了动态依赖库文件(.so), 这些扩展无法使用源码的形式编译到 static-php-cli 中,所以本项目可能永远也不会支持这些扩展。不过,理论上你可以根据上面的问题在 macOS 下接入和使用这类扩展。

如果你对此类扩展有需求,或者大部分人都对这些闭源扩展使用有需求, 可以看看有关 standalone-php-cli 的讨论。欢迎留言。

支持 Windows 吗

该项目目前已支持 Windows,但支持的扩展数量较少,Windows 的支持并不完美,主要有以下几个问题:

  1. Windows 的编译流程与 *nix 不同,使用的工具链也不同,编译各个扩展的依赖库使用的编译工具也几乎完全不同。
  2. Windows 版本的需求也会根据所有使用本项目的人的需求推进,如果有很多人需要,我会尽快支持相关扩展。

使用 micro 可以保护我的源码吗

不可以。micro.sfx 本质上是将 php 和 php 代码结合为一个文件,没有 PHP 代码编译或加密的过程。 首先 php-src 是 PHP 代码的官方解释器,而且现在市面上还没有一个能兼容主流分支的 PHP 编译器。 之前我在网上看到有一个项目是 BPC(Binary PHP Compiler?)可以把 PHP 编译为二进制,但是限制也是很多很多。

加密保护代码的方向和编译也不是一回事,编译过后也可以通过逆向工程等方式拿到代码,真正保护还是通过加壳、加密代码等手段进行。

所以本项目(static-php-cli)、相关项目(lwmbs、swoole-cli)都是提供一个对 php-src 源码的便捷编译工具, 本项目和相关项目引用的 phpmicro 也仅仅是 PHP 的 sapi 接口封装,而不是 PHP 代码的编译工具。 PHP 代码的编译器是完全不同的项目,因此不会考虑额外的情况。如果你对加密感兴趣,可以考虑使用现有的加密技术,如 Swoole Compiler、Source Guardian 等。

无法使用 ssl

使用 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 文件,也可以使用系统自带的证书文件。 有关不同发行版的证书位置,可参考 Go 标准库

INI 配置 openssl.cafile 不可以使用 ini_set() 函数动态设置,因为 openssl.cafile 是一个 PHP_INI_SYSTEM 类型的配置,只能在 php.ini 文件中设置。

`,23),l=[c];function r(t,p,d,n,h,u){return o(),a("div",null,l)}const b=e(s,[["render",r]]);export{m as __pageData,b as default}; diff --git a/assets/zh_faq_index.md.Bs3v_2I2.lean.js b/assets/zh_faq_index.md.Bs3v_2I2.lean.js new file mode 100644 index 00000000..8c893576 --- /dev/null +++ b/assets/zh_faq_index.md.Bs3v_2I2.lean.js @@ -0,0 +1 @@ +import{_ as e,c as a,o,a1 as i}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"常见问题","description":"","frontmatter":{},"headers":[],"relativePath":"zh/faq/index.md","filePath":"zh/faq/index.md"}'),s={name:"zh/faq/index.md"},c=i("",23),l=[c];function r(t,p,d,n,h,u){return o(),a("div",null,l)}const b=e(s,[["render",r]]);export{m as __pageData,b as default}; diff --git a/assets/zh_guide_action-build.md.BQOsJgGT.js b/assets/zh_guide_action-build.md.BQOsJgGT.js new file mode 100644 index 00000000..1db656bb --- /dev/null +++ b/assets/zh_guide_action-build.md.BQOsJgGT.js @@ -0,0 +1 @@ +import{_ as t,c as e,o,a1 as a}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Action 构建","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/action-build.md","filePath":"zh/guide/action-build.md"}'),i={name:"zh/guide/action-build.md"},c=a('

Action 构建

Action 构建指的是直接使用 GitHub Action 进行编译。

如果你不想自行编译,可以从本项目现有的 Action 下载 Artifact,也可以从自托管的服务器下载:进入

自托管的二进制也是由 Action 构建而来,项目仓库地址

构建方法

使用 GitHub Action 可以方便地构建一个静态编译的 PHP 和 phpmicro,同时可以自行定义要编译的扩展。

  1. Fork 本项目。
  2. 进入项目的 Actions,选择 CI 开头的 Workflow(根据你需要的操作系统选择)。
  3. 选择 Run workflow,填入你要编译的 PHP 版本、目标类型、扩展列表。(扩展列表使用英文逗号分割,例如 bcmath,curl,mbstring
  4. 等待大约一段时间后,进入对应的任务中,获取 Artifacts

如果你选择了 debug,则会在构建时输出所有日志,包括编译的日志,以供排查错误。

如果你需要在其他环境构建,可以使用 手动构建

扩展选择

你可以到 扩展列表 中查看目前你需要的扩展是否均支持, 然后到 在线命令生成 中选择你需要编译的扩展,复制扩展字符串到 Action 的 extensions 中,编译即可。

',11),r=[c];function n(l,d,h,s,p,u){return o(),e("div",null,r)}const f=t(i,[["render",n]]);export{b as __pageData,f as default}; diff --git a/assets/zh_guide_action-build.md.BQOsJgGT.lean.js b/assets/zh_guide_action-build.md.BQOsJgGT.lean.js new file mode 100644 index 00000000..cb0cc5e9 --- /dev/null +++ b/assets/zh_guide_action-build.md.BQOsJgGT.lean.js @@ -0,0 +1 @@ +import{_ as t,c as e,o,a1 as a}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"Action 构建","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/action-build.md","filePath":"zh/guide/action-build.md"}'),i={name:"zh/guide/action-build.md"},c=a("",11),r=[c];function n(l,d,h,s,p,u){return o(),e("div",null,r)}const f=t(i,[["render",n]]);export{b as __pageData,f as default}; diff --git a/assets/zh_guide_build-on-windows.md.C1RFP4Q6.js b/assets/zh_guide_build-on-windows.md.C1RFP4Q6.js new file mode 100644 index 00000000..acb6f5d0 --- /dev/null +++ b/assets/zh_guide_build-on-windows.md.C1RFP4Q6.js @@ -0,0 +1,22 @@ +import{_ as i,c as s,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"在 Windows 上构建","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/build-on-windows.md","filePath":"zh/guide/build-on-windows.md"}'),l={name:"zh/guide/build-on-windows.md"},t=e(`

在 Windows 上构建

因为 Windows 系统是 NT 内核,与类 Unix 的操作系统使用的编译工具及操作系统接口几乎完全不同,所以在 Windows 上的构建流程会与 Unix 系统有些许不同。

GitHub Actions 构建

现在已支持从 Actions 构建 Windows 版本的 static-php 了。 和 Linux、macOS 一样,你需要先 Fork static-php-cli 仓库到你的 GitHub 账户中,然后你可以进入 扩展列表 选择要编译的扩展,然后进入自己仓库的 CI on Windows 选择 PHP 版本、填入扩展列表(逗号分割),点击 Run 即可。

如果你要在本地开发或构建,请继续向下阅读。

环境准备

在 Windows 上构建静态 PHP 所需要的工具与 PHP 官方的 Windows 构建工具是相同的。你可以阅读 官方文档

总结下来,你需要以下环境及工具:

  • Windows 10(需要 build 17063 或以后的更新)
  • Visual Studio 2019/2022(推荐 2022)
  • Visual Studio 的 C++ 桌面开发
  • Git for Windows
  • static-php-cli 仓库
  • PHP 和 Composer(static-php-cli 需要它们,可使用 bin/setup-runtime 自动安装)
  • php-sdk-binary-tools(可使用 doctor 自动安装)
  • strawberry-perl(可使用 doctor 自动安装)
  • nasm(可使用 doctor 自动安装)

TIP

static-php-cli 在 Windows 上的构建指的是使用 MSVC 构建 PHP,不基于 MinGW、Cygwin、WSL 等环境。

如果你更倾向使用 WSL,请参考在 Linux 上构建的章节。

在安装 Visual Studio 后,选择 C++ 桌面开发的工作负荷后,可能会下载 8GB 左右的编译工具,下载速度取决于你的网络状况。

安装 Git

Git for Windows 可以从 这里 下载并安装 Standalone Installer 64-bit 版本,安装在默认位置(C:\\Program Files\\Git\\)。 如果不想手动下载和安装,你也可以使用 Visual Studio Installer,在单个组件的选择列表中,勾选 Git。

准备 static-php-cli

static-php-cli 项目的下载方式很简单,只需要使用 git clone 即可。推荐将项目放在 C:\\spc-build\\ 或类似目录,路径最好不要有空格。

shell
mkdir "C:\\spc-build"
+cd C:\\spc-build
+git clone https://github.com/crazywhalecc/static-php-cli.git
+cd static-php-cli

static-php-cli 自身需要 PHP 环境,是有点奇怪,但现在可以通过脚本快速安装 PHP 环境。 一般你的电脑不会安装 Windows 版本的 PHP,所以我们建议你在下载 static-php-cli 后,直接使用 bin/setup-runtime,在当前目录安装 PHP 和 Composer。

shell
# 安装 PHP 和 Composer 到 ./runtime/ 目录
+bin/setup-runtime
+
+# 安装后,如需在全局命令中使用 PHP 和 Composer,使用下面的命令将 runtime/ 目录添加到 PATH
+bin/setup-runtime -action add-path
+# 删除 PATH 中的 runtime/ 目录
+bin/setup-runtime -action remove-path

在准备好 PHP 和 Composer 环境后,使用 composer 安装 static-php-cli 的依赖:

shell
cd C:\\spc-build\\static-php-cli
+runtime/composer install --no-dev

自动安装其他依赖

对于 php-sdk-binary-toolsstrawberry-perlnasm,我们更建议你直接使用命令 bin/spc doctor --auto-fix 检查并安装。

如果 doctor 成功自动安装,请跳过下方手动安装上述工具的步骤。

如果自动安装无法成功的话,再参考下方手动安装的方式。

手动安装 php-sdk-binary-tools

bat
cd C:\\spc-build\\static-php-cli
+git clone https://github.com/php/php-sdk-binary-tools.git

你也可以在 Windows 设置中设置全局变量 PHP_SDK_PATH,并将该项目克隆至变量对应的路径。一般情况下,默认即可。

手动安装 strawberry-perl

如果你不需要编译 openssl 扩展,可不安装 perl。

  1. GitHub 下载 strawberry-perl 最新版。
  2. 安装到 C:\\spc-build\\static-php-cli\\pkgroot\\perl\\ 目录。

你可以下载 -portable 版本,并直接解压到上述目录。 最后的 perl.exe 应该位于 C:\\spc-build\\static-php-cli\\pkgroot\\perl\\perl\\bin\\perl.exe

手动安装 nasm

如果你不需要编译 openssl 扩展,可不安装 nasm。

  1. 官网 下载 nasm 工具(x64)。
  2. nasm.exendisasm.exe 放在 C:\\spc-build\\static-php-cli\\php-sdk-binary-tools\\bin\\ 目录。

下载源码

本地构建 - download

编译 PHP

使用 build 命令可以开始构建静态 php 二进制,在执行 bin/spc build 命令前,务必先使用 download 命令下载资源,建议使用 doctor 检查环境。

基本用法

你需要先到 扩展列表命令生成器 选择你要加入的扩展,然后使用命令 bin/spc build 进行编译。你需要指定编译目标,从如下参数中选择:

  • --build-cli: 构建一个 cli sapi(命令行界面,可在命令行执行 PHP 代码)
  • --build-micro: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
shell
# 编译 PHP,附带 bcmath,openssl,zlib 扩展,编译目标为 cli
+bin/spc build "bcmath,openssl,zlib" --build-cli
+
+# 编译 PHP,附带 bcmath,openssl,zlib 扩展,编译目标为 micro 和 cli
+bin/spc build "bcmath,openssl,zlib" --build-micro --build-cli

WARNING

在Windows中,最好使用双引号包裹包含逗号的参数,例如 "bcmath,openssl,mbstring"

调试

如果你在编译过程中遇到了问题,或者想查看每个执行的 shell 命令,可以使用 --debug 开启 debug 模式,查看所有终端日志:

shell
bin/spc build "openssl" --build-cli --debug

编译运行选项

在编译过程中,有些特殊情况需要对编译器、编译目录的内容进行干预,可以尝试使用以下命令:

  • --with-clean: 编译 PHP 前先清理旧的 make 产生的文件
  • --enable-zts: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
  • --with-libs=XXX,YYY: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能
  • -I xxx=yyy: 编译前将 INI 选项硬编译到 PHP 内(支持多个选项,别名是 --with-hardcoded-ini
  • --with-micro-fake-cli: 在编译 micro 时,让 micro 的 SAPI 伪装为 cli(用于兼容一些检查 PHP_SAPI 的程序)
  • --disable-opcache-jit: 禁用 opcache jit(默认启用)
  • --without-micro-ext-test: 在构建 micro.sfx 后,禁用测试不同扩展在 micro.sfx 的运行结果
  • --with-suggested-exts: 编译时将 ext-suggests 也作为编译依赖加入
  • --with-suggested-libs: 编译时将 lib-suggests 也作为编译依赖加入
  • --with-upx-pack: 编译后使用 UPX 减小二进制文件体积(需先使用 bin/spc install-pkg upx 安装 upx)
  • --with-micro-logo=XXX.ico: 自定义 micro 构建组合后的 exe 可执行文件的图标(格式为 .ico

有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 memory_limit,并且禁用 system 函数:

shell
bin/spc build "bcmath,openssl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"

另一个例子:自定义 micro 构建后的 exe 程序图标:

shell
bin/spc build "ffi,bcmath" --build-micro --with-micro-logo=mylogo.ico --debug
+bin/spc micro:combine hello.php
+# Then we got \`my-app.exe\` with custom logo!
+my-app.exe

使用 php.exe

php.exe 编译后位于 buildroot\\bin\\ 目录,你可以将其拷贝到任意位置使用。

shell
.\\php -v

使用 micro

phpmicro 是一个提供自执行二进制 PHP 的项目,本项目依赖 phpmicro 进行编译自执行二进制。详见 dixyes/phpmicro

最后编译结果会输出一个 ./micro.sfx 的文件,此文件需要配合你的 PHP 源码使用。 该文件编译后会存放在 buildroot/bin/ 目录中。

使用时应准备好你的项目源码文件,可以是单个 PHP 文件,也可以是 Phar 文件。

如果要结合 phar 文件,编译时必须包含 phar 扩展!

shell
# code.php "<?php echo 'Hello world' . PHP_EOL;"
+bin/spc micro:combine code.php -O my-app.exe
+# Run it!!! Copy it to another computer!!!
+./my-app.exe

如果打包 PHAR 文件,仅需把 code.php 更换为 phar 文件路径即可。 你可以使用 box-project/box 将你的 CLI 项目打包为 Phar, 然后将它与 phpmicro 结合,生成独立可执行的二进制文件。

有关 micro:combine 命令的更多细节,请参考 Unix 系统上的 命令

`,64),p=[t];function n(h,o,d,c,r,k){return a(),s("div",null,p)}const g=i(l,[["render",n]]);export{u as __pageData,g as default}; diff --git a/assets/zh_guide_build-on-windows.md.C1RFP4Q6.lean.js b/assets/zh_guide_build-on-windows.md.C1RFP4Q6.lean.js new file mode 100644 index 00000000..46363e63 --- /dev/null +++ b/assets/zh_guide_build-on-windows.md.C1RFP4Q6.lean.js @@ -0,0 +1 @@ +import{_ as i,c as s,o as a,a1 as e}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"在 Windows 上构建","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/build-on-windows.md","filePath":"zh/guide/build-on-windows.md"}'),l={name:"zh/guide/build-on-windows.md"},t=e("",64),p=[t];function n(h,o,d,c,r,k){return a(),s("div",null,p)}const g=i(l,[["render",n]]);export{u as __pageData,g as default}; diff --git a/assets/zh_guide_cli-generator.md.CMA84kUR.js b/assets/zh_guide_cli-generator.md.CMA84kUR.js new file mode 100644 index 00000000..31e00112 --- /dev/null +++ b/assets/zh_guide_cli-generator.md.CMA84kUR.js @@ -0,0 +1 @@ +import{C as e}from"./chunks/CliGenerator.Bj1S5l8x.js";import{d as t,c as a,I as o,a1 as r,o as i}from"./chunks/framework.CszIUXhs.js";const c=r('

CLI 编译命令生成器

TIP

下面选择扩展可能包含所选操作系统不支持的扩展,这可能导致编译失败。请先查阅 支持的扩展

',2),p=JSON.parse('{"title":"CLI 编译命令生成器","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/cli-generator.md","filePath":"zh/guide/cli-generator.md"}'),s={name:"zh/guide/cli-generator.md"},_=t({...s,setup(l){return(n,d)=>(i(),a("div",null,[c,o(e,{lang:"zh"})]))}});export{p as __pageData,_ as default}; diff --git a/assets/zh_guide_cli-generator.md.CMA84kUR.lean.js b/assets/zh_guide_cli-generator.md.CMA84kUR.lean.js new file mode 100644 index 00000000..31e00112 --- /dev/null +++ b/assets/zh_guide_cli-generator.md.CMA84kUR.lean.js @@ -0,0 +1 @@ +import{C as e}from"./chunks/CliGenerator.Bj1S5l8x.js";import{d as t,c as a,I as o,a1 as r,o as i}from"./chunks/framework.CszIUXhs.js";const c=r('

CLI 编译命令生成器

TIP

下面选择扩展可能包含所选操作系统不支持的扩展,这可能导致编译失败。请先查阅 支持的扩展

',2),p=JSON.parse('{"title":"CLI 编译命令生成器","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/cli-generator.md","filePath":"zh/guide/cli-generator.md"}'),s={name:"zh/guide/cli-generator.md"},_=t({...s,setup(l){return(n,d)=>(i(),a("div",null,[c,o(e,{lang:"zh"})]))}});export{p as __pageData,_ as default}; diff --git a/assets/zh_guide_env-vars.md.Dn5AS_wq.js b/assets/zh_guide_env-vars.md.Dn5AS_wq.js new file mode 100644 index 00000000..2eb04526 --- /dev/null +++ b/assets/zh_guide_env-vars.md.Dn5AS_wq.js @@ -0,0 +1,6 @@ +import{_ as d,c as t,o as e,a1 as o}from"./chunks/framework.CszIUXhs.js";const C=JSON.parse('{"title":"环境变量列表","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"zh/guide/env-vars.md","filePath":"zh/guide/env-vars.md"}'),c={name:"zh/guide/env-vars.md"},a=o(`

环境变量列表

本页面的环境变量列表中所提到的所有环境变量都具有默认值,除非另有说明。你可以通过设置这些环境变量来覆盖默认值。

一般情况下,你不需要修改任何以下环境变量,因为它们已经被设置为最佳值。 但是,如果你有特殊需求,你可以通过设置这些环境变量来满足你的需求(比如你需要调试不同编译参数下的 PHP 性能表现)。

如需使用自定义环境变量,你可以在终端中使用 export 命令或者在命令前直接设置环境变量,例如:

shell
# export 方式
+export SPC_CONCURRENCY=4
+bin/spc build mbstring,pcntl --build-cli
+
+# 直接设置方式
+SPC_CONCURRENCY=4 bin/spc build mbstring,pcntl --build-cli

通用环境变量

通用环境变量是所有构建目标都可以使用的环境变量。

var namedefault valuecomment
BUILD_ROOT_PATH{pwd}/buildroot编译目标的根目录
BUILD_LIB_PATH{pwd}/buildroot/lib编译依赖库的根目录
BUILD_INCLUDE_PATH{pwd}/buildroot/include编译依赖库的头文件目录
BUILD_BIN_PATH{pwd}/buildroot/bin编译依赖库的二进制文件目录
PKG_ROOT_PATH{pwd}/pkgroot闭源或预编译工具下载后安装的目录
SOURCE_PATH{pwd}/source编译项目的源码解压缩目录
DOWNLOAD_PATH{pwd}/downloads下载的文件存放目录
SPC_CONCURRENCY取决于当前 CPU 核心数量并行编译的数量
SPC_SKIP_PHP_VERSION_CHECK设置为 yes 时,跳过扩展对 PHP 版本的检查

系统特定变量

这些环境变量是特定于系统的,它们只在特定的系统上才会生效。

Windows

var namedefault valuecomment
PHP_SDK_PATH{pwd}\\php-sdk-binary-toolsPHP SDK 工具的安装目录
UPX_EXEC$PKG_ROOT_PATH\\bin\\upx.exeUPX 压缩工具的路径

macOS

var namedefault valuecomment
CCclangC 编译器
CXXclang++C++ 编译器
SPC_DEFAULT_C_FLAGS--target=arm64-apple-darwin--target=x86_64-apple-darwin默认 C 编译标志(与 CFLAGS 不同)
SPC_DEFAULT_CXX_FLAGS--target=arm64-apple-darwin--target=x86_64-apple-darwin默认 C++ 编译标志(与 CXXFLAGS 不同)
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --force编译 PHP buildconf 命令前缀
SPC_CMD_PREFIX_PHP_CONFIGURE./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbg编译 PHP configure 命令前缀
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCY编译 PHP make 命令前缀
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGS -Werror=unknown-warning-optionPHP configure 命令的 CFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHPHP configure 命令的 CPPFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHPHP configure 命令的 LDFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os-g -O0(当使用 --no-strip 时为后者)PHP make 命令的 EXTRA_CFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS-lresolvPHP make 命令的额外 EXTRA_LIBS 变量

Linux

var namedefault valuecomment
UPX_EXEC$PKG_ROOT_PATH/bin/upxUPX 压缩工具的路径
GNU_ARCHx86_64aarch64当前环境的 CPU 架构
CCAlpine: gcc, Other: $GNU_ARCH-linux-musl-gccC 编译器
CXXAlpine: g++, Other: $GNU_ARCH-linux-musl-g++C++ 编译器
ARAlpine: ar, Other: $GNU_ARCH-linux-musl-ar静态库工具
LDld.gold链接器
PATH/usr/local/musl/bin:/usr/local/musl/$GNU_ARCH-linux-musl/bin:$PATH系统 PATH
SPC_DEFAULT_C_FLAGSempty默认 C 编译标志
SPC_DEFAULT_CXX_FLAGSempty默认 C++ 编译标志
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --force编译 PHP buildconf 命令前缀
SPC_CMD_PREFIX_PHP_CONFIGURELD_LIBRARY_PATH={ld_lib_path} ./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbg编译 PHP configure 命令前缀
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCY编译 PHP make 命令前缀
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGSPHP configure 命令的 CFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHPHP configure 命令的 CPPFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHPHP configure 命令的 LDFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LIBS-ldl -lpthreadPHP configure 命令的 LIBS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os -fno-ident -fPIE-g -O0 -fno-ident -fPIE(当使用 --no-strip 时为后者)PHP make 命令的 EXTRA_CFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBSemptyPHP make 命令的额外 EXTRA_LIBS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM-all-static(当使用 clang 时:-Xcompiler -fuse-ld=lld -all-staticmake 命令的额外 LDFLAGS 变量(用于编译程序)
SPC_NO_MUSL_PATHempty是否不插入 musl 工具链的 PATH(值为 yes 时不插入)

{ld_lib_path} 值为 /usr/local/musl/$GNU_ARCH-linux-musl/lib

FreeBSD

因 FreeBSD 系统的用户较少,我们暂时不提供 FreeBSD 系统的环境变量。

Unix

对于 macOS、Linux、FreeBSD 等 Unix 系统,以下环境变量是通用的。

var namedefault valuecomment
PATH$BUILD_BIN_PATH:$PATH系统 PATH
PKG_CONFIG_PATH$BUILD_LIB_PATH/pkgconfigpkg-config 的搜索路径
PKG_CONFIG$BUILD_BIN_PATH/pkg-configpkg-config 命令路径

编译依赖库的环境变量(仅限 Unix 系统)

从 2.2.0 开始,static-php-cli 对所有 macOS、Linux、FreeBSD 等 Unix 系统的编译依赖库的命令均支持自定义环境变量。

这样你就可以随时通过环境变量来调整编译依赖库的行为。例如你可以通过 xxx_CFLAGS=-O0 来设置编译 xxx 库的优化参数。

当然,不是每个依赖库都支持注入环境变量,我们目前提供了三个通配的环境变量,后缀分别为:

  • _CFLAGS: C 编译器的参数
  • _LDFLAGS: 链接器的参数
  • _LIBS: 额外的链接库

前缀为依赖库的名称,具体依赖库的名称以 lib.json 为准。其中,带有 - 的依赖库名称需要将 - 替换为 _

下面是一个替换 openssl 库编译的优化选项示例:

shell
openssl_CFLAGS="-O0"

库名称使用同 lib.json 中列举的名称,区分大小写。

TIP

当未指定相关环境变量时,除以下变量外,其余值均默认为空:

var namevar default value
pkg_config_CFLAGSmacOS: $SPC_DEFAULT_C_FLAGS -Wimplicit-function-declaration -Wno-int-conversion, Other: empty
pkg_config_LDFLAGSLinux: --static, Other: empty
imagemagick_LDFLAGSLinux: -static, Other: empty
imagemagick_LIBSmacOS: -liconv, Other: empty
ldap_LDFLAGS-L$BUILD_LIB_PATH
openssl_CFLAGSLinux: $SPC_DEFAULT_C_FLAGS, Other: empty
others...empty

下表是支持自定义以上三种变量的依赖库名称列表:

lib name
brotli
bzip
curl
freetype
gettext
gmp
imagemagick
ldap
libargon2
libavif
libcares
libevent
openssl

TIP

因为给每个库适配自定义环境变量是一项特别繁琐的工作,且大部分情况下你都不需要这些库的自定义环境变量,所以我们目前只支持了部分库的自定义环境变量。

如果你需要自定义环境变量的库不在上方列表,可以通过 GitHub Issue 来提出需求。

`,35),i=[a];function r(s,l,n,_,h,p){return e(),t("div",null,i)}const A=d(c,[["render",r]]);export{C as __pageData,A as default}; diff --git a/assets/zh_guide_env-vars.md.Dn5AS_wq.lean.js b/assets/zh_guide_env-vars.md.Dn5AS_wq.lean.js new file mode 100644 index 00000000..587234f0 --- /dev/null +++ b/assets/zh_guide_env-vars.md.Dn5AS_wq.lean.js @@ -0,0 +1 @@ +import{_ as d,c as t,o as e,a1 as o}from"./chunks/framework.CszIUXhs.js";const C=JSON.parse('{"title":"环境变量列表","description":"","frontmatter":{"aside":false},"headers":[],"relativePath":"zh/guide/env-vars.md","filePath":"zh/guide/env-vars.md"}'),c={name:"zh/guide/env-vars.md"},a=o("",35),i=[a];function r(s,l,n,_,h,p){return e(),t("div",null,i)}const A=d(c,[["render",r]]);export{C as __pageData,A as default}; diff --git a/assets/zh_guide_extension-notes.md.CfCgmU-D.js b/assets/zh_guide_extension-notes.md.CfCgmU-D.js new file mode 100644 index 00000000..d9537b3e --- /dev/null +++ b/assets/zh_guide_extension-notes.md.CfCgmU-D.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as l,a1 as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"扩展注意事项","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/extension-notes.md","filePath":"zh/guide/extension-notes.md"}'),s={name:"zh/guide/extension-notes.md"},i=a('

扩展注意事项

因为是静态编译,扩展不会 100% 完美编译,而且不同扩展对 PHP、环境都有不同的要求,这里将一一列举。

curl

使用 curl 请求 HTTPS 时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

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 扩展,启用 swooleswoole-hook-pgsql 即可。 该扩展包含了 pdo_pgsql 的协程环境的实现。

在 macOS 系统,pdo_pgsql 可能无法正常连接到 postgresql 服务器,请谨慎使用。

swoole-hook-mysql

swoole-hook-mysql 不是一个扩展,而是 Swoole 的 Hook 特性。 如果你在编译时添加了 swoole,swoole-hook-mysql,你将启用 Swoole 的 mysqlndpdo_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 扩展,启用 swooleswoole-hook-sqlite 即可。 该扩展包含了 pdo_sqlite 的协程环境的实现。

swow

  1. swow 仅支持 PHP >= 8.0 版本。

imap

  1. 该扩展目前不支持 Kerberos。
  2. 由于底层的 c-client、ext-imap 不是线程安全的。 无法在 --enable-zts 构建中使用它。
  3. 由于该扩展可能会从未来的 PHP 中删除,因此我们建议您寻找替代实现,例如 Webklex/php-imap

gd

  1. gd 扩展依赖了较多的额外图形库,默认情况下,直接使用 bin/spc build gd 不会引入和支持部分图形库,例如 libjpeglibavif 等, 需要使用 --with-libs 参数补全。目前支持 freetype,libjpeg,libavif,libwebp 四个库的支持,所以这里可以使用以下命令来让 gd 库引入它们:
bash
bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli

mcrypt

  1. 目前未支持,未来也不计划支持此扩展。#32

oci8

  1. oci8 是 Oracle 数据库的扩展,因为 Oracle 提供的扩展所依赖的库未提供静态编译版本(.a)或源代码,无法使用静态链接的方式将此扩展编译到 php 内,故无法支持。

xdebug

  1. Xdebug 是一个 Zend 扩展,Xdebug 的功能依赖于 PHP 的 Zend 引擎和底层代码,如果要将其静态编译到 PHP 中,可能需要巨量的 patch 代码,这是不可行的。
  2. macOS 平台可以通过在相同平台编译的 PHP 下编译一个 xdebug 扩展,并提取其中的 xdebug.so 文件,再在 static-php-cli 中使用 --no-strip 参数保留调试符号表,同时加入 ffi 扩展。 编译的 ./php 二进制可以通过指定 INI 配置并运行,例如./php -d 'zend_extension=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 不兼容。相关链接:

pgsql 16.2 修复了这个 Bug,现在正常工作了。

在 pgsql 使用 SSL 连接时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

openssl

使用基于 openssl 的扩展(如 curl、pgsql 等网络库)时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

password-argon2

  1. password-argon2不是一个标准的扩展,它是 password_hash 函数的额外算法。
  2. 在Linux系统,password-argon2 的依赖库 libargon2libsodium 库冲突。

ffi

  1. 因为 Linux 系统的限制,虽然可以成功编译 ffi 扩展,但无法使用它加载其他 so 扩展。Linux 支持加载 so 扩展的前提是非静态编译,但动态编译和本项目的目的冲突。
  2. macOS 支持 ffi 扩展,但是部分内核下不包含调试符号时会出现错误。
  3. Windows 支持 ffi 扩展。

xhprof

xhprof 扩展包含三部分:xhprof_extensionxhprof_htmlxhprof_libs。编译的二进制中只包含 xhprof_extension。 如果需要使用 xhprof,请到 pecl.php.net/package/xhprof 下载源码,指定 xhprof_libsxhprof_html 路径来使用。

event

event 扩展在 macOS 系统下编译后暂无法使用 openpty 特性。相关 Issue:

parallel

parallel 扩展只支持 PHP 8.0 及以上版本,并只支持 ZTS 构建(--enable-zts)。

',54),r=[i];function t(d,c,h,p,n,b){return l(),o("div",null,r)}const g=e(s,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/zh_guide_extension-notes.md.CfCgmU-D.lean.js b/assets/zh_guide_extension-notes.md.CfCgmU-D.lean.js new file mode 100644 index 00000000..fd1c6453 --- /dev/null +++ b/assets/zh_guide_extension-notes.md.CfCgmU-D.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as l,a1 as a}from"./chunks/framework.CszIUXhs.js";const u=JSON.parse('{"title":"扩展注意事项","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/extension-notes.md","filePath":"zh/guide/extension-notes.md"}'),s={name:"zh/guide/extension-notes.md"},i=a("",54),r=[i];function t(d,c,h,p,n,b){return l(),o("div",null,r)}const g=e(s,[["render",t]]);export{u as __pageData,g as default}; diff --git a/assets/zh_guide_extensions.md.BkAzY34J.js b/assets/zh_guide_extensions.md.BkAzY34J.js new file mode 100644 index 00000000..255506c2 --- /dev/null +++ b/assets/zh_guide_extensions.md.BkAzY34J.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"扩展列表","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/extensions.md","filePath":"zh/guide/extensions.md"}'),r={name:"zh/guide/extensions.md"},y=s('

扩展列表

  • yes: 已支持
  • 空白: 目前还不支持,或正在支持中
  • no with issue link: 确定不支持或无法支持
  • partial with issue link: 已支持,但是无法完美工作
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes

TIP

如果缺少您需要的扩展,您可以创建 功能请求

有些扩展或扩展依赖的库会有一些可选的特性,例如 gd 库可选支持 libwebp、freetype 等。 如果你只使用 bin/spc build gd --build-cli 是不会包含它们(static-php-cli 默认为最小依赖原则)。

你可以在编译时使用 --with-libs= 加入这些库,当本次编译的依赖库中包含它们,gd 会自动依赖它们启用这些特性。 (如:bin/spc build gd --with-libs=libwebp,freetype --build-cli

或者你也可以使用 --with-suggested-exts--with-suggested-libs 启用这些扩展和库所有可选的依赖。 (如:bin/spc build gd --with-suggested-libs --build-cli

如果你不知道某个扩展是否有可选特性,可以通过查看 spc 配置文件 或使用命令 bin/spc dev:extensions 查看(库依赖为 lib-suggests,扩展依赖为 ext-suggests)。

',4),o=[y];function n(i,a,l,h,c,p){return e(),d("div",null,o)}const x=t(r,[["render",n]]);export{f as __pageData,x as default}; diff --git a/assets/zh_guide_extensions.md.BkAzY34J.lean.js b/assets/zh_guide_extensions.md.BkAzY34J.lean.js new file mode 100644 index 00000000..de4732fb --- /dev/null +++ b/assets/zh_guide_extensions.md.BkAzY34J.lean.js @@ -0,0 +1 @@ +import{_ as t,c as d,o as e,a1 as s}from"./chunks/framework.CszIUXhs.js";const f=JSON.parse('{"title":"扩展列表","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/extensions.md","filePath":"zh/guide/extensions.md"}'),r={name:"zh/guide/extensions.md"},y=s("",4),o=[y];function n(i,a,l,h,c,p){return e(),d("div",null,o)}const x=t(r,[["render",n]]);export{f as __pageData,x as default}; diff --git a/assets/zh_guide_index.md.D0Jfo4Dz.js b/assets/zh_guide_index.md.D0Jfo4Dz.js new file mode 100644 index 00000000..e4c3a5a8 --- /dev/null +++ b/assets/zh_guide_index.md.D0Jfo4Dz.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as e,a1 as d}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"指南","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/index.md","filePath":"zh/guide/index.md"}'),i={name:"zh/guide/index.md"},r=d('

指南

static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目前支持 Linux 和 macOS 系统。

在指南章节中,你将了解到如何使用 static-php-cli 构建独立的 php 程序。

编译环境

下面是架构支持情况,⚙️ 代表支持 GitHub Action 构建,💻 代表支持本地构建,空 代表暂不支持。

x86_64aarch64
macOS⚙️ 💻⚙️ 💻
Linux⚙️ 💻⚙️ 💻
Windows⚙️ 💻
FreeBSD💻💻

其中,Linux 目前仅在 Ubuntu、Debian、Alpine 发行版测试通过,其他发行版未进行测试,不能保证编译成功。 对于未经过测试的发行版,可以使用 Docker 等方式本地编译,避免环境导致的问题。

macOS 下支持 x86_64 和 Arm 两种架构,但在其中一个架构上编译的二进制无法直接在另一个架构上使用。 Rosetta 2 不能保证 Arm 架构编译的程序可以完全运行在 x86_64 环境下。

Windows 目前只支持 x86_64 架构,不支持 32 位 x86、不支持 arm64 架构。

PHP 支持版本

目前,static-php-cli 对 PHP 7.4 ~ 8.3 版本是支持的,对于 PHP 7.4 及更早版本理论上支持,只需下载时选择早期版本即可。 但由于部分扩展和特殊组件已对早期版本的 PHP 停止了支持,所以 static-php-cli 不会明确支持早期版本。 我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。

',12),h=[r];function n(o,l,c,p,s,_){return e(),a("div",null,h)}const P=t(i,[["render",n]]);export{m as __pageData,P as default}; diff --git a/assets/zh_guide_index.md.D0Jfo4Dz.lean.js b/assets/zh_guide_index.md.D0Jfo4Dz.lean.js new file mode 100644 index 00000000..1b50c290 --- /dev/null +++ b/assets/zh_guide_index.md.D0Jfo4Dz.lean.js @@ -0,0 +1 @@ +import{_ as t,c as a,o as e,a1 as d}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"指南","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/index.md","filePath":"zh/guide/index.md"}'),i={name:"zh/guide/index.md"},r=d("",12),h=[r];function n(o,l,c,p,s,_){return e(),a("div",null,h)}const P=t(i,[["render",n]]);export{m as __pageData,P as default}; diff --git a/assets/zh_guide_manual-build.md.C58zH3IF.js b/assets/zh_guide_manual-build.md.C58zH3IF.js new file mode 100644 index 00000000..43527a93 --- /dev/null +++ b/assets/zh_guide_manual-build.md.C58zH3IF.js @@ -0,0 +1,134 @@ +import{_ as s,c as i,o as a,a1 as n}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"本地构建(Linux、macOS、FreeBSD)","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/manual-build.md","filePath":"zh/guide/manual-build.md"}'),p={name:"zh/guide/manual-build.md"},l=n(`

本地构建(Linux、macOS、FreeBSD)

本章节为 Linux、macOS、FreeBSD 的构建过程,如果你要在 Windows 上构建,请到 在 Windows 上构建

手动构建(使用 SPC 二进制)(推荐)

本项目提供了一个 static-php-cli 的二进制文件,你可以直接下载对应平台的二进制文件,然后使用它来构建静态的 PHP。目前 spc 二进制支持的平台有 Linux 和 macOS。

使用以下命令从自托管服务器下载:

bash
# Download from self-hosted nightly builds (sync with main branch)
+# For Linux x86_64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# For Linux aarch64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+# macOS x86_64 (Intel)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+# macOS aarch64 (Apple)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+# Windows (x86_64, win10 build 17063 or later)
+curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+
+# Add execute perm (Linux and macOS only)
+chmod +x ./spc
+
+# Run (Linux and macOS)
+./spc --version
+# Run (Windows powershell)
+.\\spc.exe --version

如果你使用的是打包好的 spc 二进制,你需要将下面所有命令中 bin/spc 开头替换为 ./spc

手动构建(使用源码)

如果使用 spc 二进制出现问题,或你有修改 static-php-cli 源码需求,请从源码下载 static-php-cli。

目前支持在 macOS、Linux 上构建,macOS 支持最新版操作系统和两种架构,Linux 支持 Debian、RHEL 及衍生发行版、Alpine Linux 等。

因为本项目本身采用 PHP 开发,所以在编译时也需要系统安装 PHP。本项目本身也提供了适用于本项目的静态二进制 php,可以根据实际情况自行选择使用。

下载本项目

bash
git clone https://github.com/crazywhalecc/static-php-cli.git --depth=1
+cd static-php-cli
+
+# 你需要先安装 PHP 环境后再运行 Composer 和本项目,安装方式可参考下面。
+composer update

使用系统 PHP 环境

下面是系统安装 PHP、Composer 的一些示例命令。具体安装方式建议自行搜索或询问 AI 搜索引擎获取答案,这里不多赘述。

bash
# [macOS], 需要先安装 Homebrew. See https://brew.sh/
+# Remember change your composer executable path. For M1/M2 Chip mac, "/opt/homebrew/bin/", for Intel mac, "/usr/local/bin/". Or add it to your own path.
+brew install php wget
+wget https://getcomposer.org/download/latest-stable/composer.phar -O /path/to/your/bin/composer && chmod +x /path/to/your/bin/composer
+
+# [Debian], you need to make sure your php version >= 8.1 and composer >= 2.0
+sudo apt install php-cli composer php-tokenizer
+
+# [Alpine]
+apk add bash file wget xz php81 php81-common php81-pcntl php81-tokenizer php81-phar php81-posix php81-xml composer

TIP

目前 Ubuntu 部分版本的 apt 安装的 php 版本较旧,故不提供安装命令。如有需要,建议先添加 ppa 等软件源后,安装最新版的 PHP 以及 tokenizer、xml、phar 扩展。

较老版本的 Debian 默认安装的可能为旧版本(<= 7.4)版本的 PHP,建议先升级 Debian。

使用 Docker 环境

如果你不愿意在系统安装 PHP 和 Composer 运行环境,可以使用内置的 Docker 环境构建脚本。

bash
# 直接使用,将所有使用的命令中 \`bin/spc\` 替换为 \`bin/spc-alpine-docker\` 即可
+bin/spc-alpine-docker

首次执行命令会使用 docker build 构建一个 Docker 镜像,默认构建的 Docker 镜像为 x86_64 架构,镜像名称为 cwcc-spc-x86_64

如果你想在 x86_64 环境下构建 aarch64 的 static-php-cli,可以使用 qemu 模拟 arm 镜像运行 Docker,但速度会非常慢。使用参数:SPC_USE_ARCH=aarch64 bin/spc-alpine-docker

如果运行后提示需要 sudo 才能运行,执行一次以下命令可授予 static-php-cli 执行 sudo 的权限:

bash
export SPC_USE_SUDO=yes

使用预编译静态 PHP 二进制

如果你不想使用 Docker、在系统内安装 PHP,可以直接下载本项目自身编译好的 php 二进制 cli 程序。使用流程如下:

使用命令部署环境,此脚本会从 自托管的服务器 下载一个当前操作系统的 php-cli 包, 并从 getcomposerAliyun(镜像) 下载 Composer。

TIP

使用预编译静态 PHP 二进制目前仅支持 Linux 和 macOS。FreeBSD 环境因为缺少自动化构建环境,所以暂不支持。

bash
bin/setup-runtime
+
+# 对于中国大陆地区等网络环境特殊的用户,可使用镜像站加快下载速度
+bin/setup-runtime --mirror china

此脚本总共会下载两个文件:bin/phpbin/composer,下载完成后,有两种使用方式:

  1. bin/ 目录添加到 PATH 路径中:export PATH="/path/to/your/static-php-cli/bin:$PATH",添加路径后,相当于系统安装了 PHP,可直接使用 composerphp -v 等命令,也可以直接使用 bin/spc
  2. 直接调用,比如执行 static-php-cli 命令:bin/php bin/spc --help,执行 Composer:bin/php bin/composer update

命令 download - 下载依赖包

使用命令 bin/spc download 可以下载编译需要的源代码,包括 php-src 以及依赖的各种库的源码。

bash
# 仅下载要编译的扩展及依赖库(使用扩展名,包含可选库)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl,zstd
+
+# 仅下载要编译的扩展及依赖库(使用扩展名,不包含可选库)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl --without-suggestions
+
+# 仅下载要编译的库(包括其依赖,使用库名,包含可选库,可以和 --for-extensions 组合使用)
+bin/spc download --for-libs=liblz4,libevent --for-extensions=pcntl,rar,xml
+
+# 仅下载要编译的库(包括其依赖,使用库名,不包含可选库)
+bin/spc download --for-libs=liblz4,libevent --without-suggestions
+
+# 下载资源时,忽略部分资源的缓存,强制下载(如切换 PHP 版本)
+bin/spc download --for-extensions=curl,pcntl,xml --ignore-cache-sources=php-src --with-php=8.3
+
+# 下载所有依赖包
+bin/spc download --all
+
+# 下载所有依赖包,并指定下载的 PHP 主版本,可选:7.3,7.4,8.0,8.1,8.2,8.3。
+bin/spc download --all --with-php=8.2
+
+# 下载时显示下载进度条(curl)
+bin/spc download --all --debug
+
+# 删除旧的下载数据
+bin/spc download --clean
+
+# 仅下载指定的资源(使用资源名)
+bin/spc download php-src,micro,zstd,ext-zstd
+
+# 设置重试次数
+bin/spc download --all --retry=2

如果你所在地区的网络不好,或者下载依赖包速度过于缓慢,可以从 GitHub Action 下载每周定时打包的 download.zip,并使用命令直接使用 zip 压缩包作为依赖。 依赖包可以从 Action 下载到本地。 进入 Action 并选择一个最新成功运行的 Workflow,下载 download-files-x.y 即可。

bash
bin/spc download --from-zip=/path/to/your/download.zip

如果某个 source 始终无法下载,或者你需要下载一些特定版本的包,例如下载测试版 PHP、旧版本库等,可以使用参数 -U--custom-url 重写下载链接, 让下载器强制使用你指定的链接下载此 source 的包。使用方法为 {source-name}:{url} 即可,可同时重写多个库的下载地址。在使用 --for-extensions 选项下载时同样可用。

bash
# 例如:指定下载测试版的 PHP8.3
+bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+
+# 指定下载旧版本的 curl 库
+bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"

命令 doctor - 环境检查

如果你可以正常运行 bin/spc 但无法正常编译静态的 PHP 或依赖库,可以先运行 bin/spc doctor 检查系统自身是否缺少依赖。

bash
# 快速检查
+bin/spc doctor
+
+# 快速检查,并在可以自动修复的时候修复(使用包管理安装依赖包,仅支持上述提到的操作系统及发行版)
+bin/spc doctor --auto-fix

命令 build - 编译 PHP

使用 build 命令可以开始构建静态 php 二进制,在执行 bin/spc build 命令前,务必先使用 download 命令下载资源,建议使用 doctor 检查环境。

基本用法

你需要先到 扩展列表命令生成器 选择你要加入的扩展,然后使用命令 bin/spc build 进行编译。你需要指定一个编译目标,从如下参数中选择:

  • --build-cli: 构建一个 cli sapi(命令行界面,可在命令行执行 PHP 代码)
  • --build-fpm: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
  • --build-micro: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
  • --build-embed: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中)
  • --build-all: 构建以上所有 sapi
bash
# 编译 PHP,附带 bcmath,curl,openssl,ftp,posix,pcntl 扩展,编译目标为 cli
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+
+# 编译 PHP,附带 phar,curl,posix,pcntl,tokenizer 扩展,编译目标为 micro
+bin/spc build phar,curl,posix,pcntl,tokenizer --build-micro

TIP

如果你需要重复构建、调试,你可以删除 buildroot/source/ 两个目录,这样你可以从已下载的源码压缩包重新解压并构建:

shell
# remove
+rm -rf buildroot source
+# build again
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

TIP

如果你想构建多个版本的 PHP,且不想每次都重复构建其他依赖库,可以使用 switch-php-version 在编译好一个版本后快速切换至另一个版本并编译:

shell
# switch to 8.3
+bin/spc switch-php-version 8.3
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+# switch to 8.0
+bin/spc switch-php-version 8.0
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

调试

如果你在编译过程中遇到了问题,或者想查看每个执行的 shell 命令,可以使用 --debug 开启 debug 模式,查看所有终端日志:

bash
bin/spc build mysqlnd,pdo_mysql --build-all --debug

编译运行选项

在编译过程中,有些特殊情况需要对编译器、编译目录的内容进行干预,可以尝试使用以下命令:

  • --cc=XXX: 指定 C 语言编译器的执行命令(Linux 默认 musl-gccgcc,macOS 默认 clang
  • --cxx=XXX: 指定 C++ 语言编译器的执行命令(Linux 默认 g++,macOS 默认 clang++
  • --with-clean: 编译 PHP 前先清理旧的 make 产生的文件
  • --enable-zts: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
  • --no-strip: 编译 PHP 库后不运行 strip 裁剪二进制文件缩小体积(不裁剪的 macOS 二进制文件可使用动态链接的第三方扩展)
  • --with-libs=XXX,YYY: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能(例如 gd 库的 libavif 等)
  • -I xxx=yyy: 编译前将 INI 选项硬编译到 PHP 内(支持多个选项,别名是 --with-hardcoded-ini
  • --with-micro-fake-cli: 在编译 micro 时,让 micro 的 SAPI 伪装为 cli(用于兼容一些检查 PHP_SAPI 的程序)
  • --disable-opcache-jit: 禁用 opcache jit(默认启用)
  • -P xxx.php: 在 static-php-cli 编译过程中注入外部脚本(详见下方 注入外部脚本
  • --without-micro-ext-test: 在构建 micro.sfx 后,禁用测试不同扩展在 micro.sfx 的运行结果
  • --with-suggested-exts: 编译时将 ext-suggests 也作为编译依赖加入
  • --with-suggested-libs: 编译时将 lib-suggests 也作为编译依赖加入
  • --with-upx-pack: 编译后使用 UPX 减小二进制文件体积(需先使用 bin/spc install-pkg upx 安装 upx)

硬编码 INI 选项适用于 cli、micro、embed。有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 memory_limit,并且禁用 system 函数:

bash
bin/spc build bcmath,pcntl,posix --build-all -I "memory_limit=4G" -I "disable_functions=system"

命令 micro:combine - 打包 micro 二进制

使用 micro:combine 命令可以将上面编译好的 micro.sfx 和你的代码(.php.phar 文件)构建为一个可执行二进制。 你也可以使用该命令直接构建一个注入了 ini 配置的 micro 自执行二进制文件。

TIP

注入 ini 配置指的是,在将 micro.sfx 和 PHP 源码结合前,在 micro.sfx 后追加一段特殊的结构用于保存 ini 配置项。

micro.sfx 可通过特殊的字节来标识 INI 文件头,通过 INI 文件头可以实现 micro 带 INI 启动。

此特性的原说明地址在 phpmicro - Wiki,这个特性也有可能在未来发生变化。

下面是常规用法,直接打包 php 源码到一个文件中:

bash
# 在做打包流程前,你应该先使用 \`build --build-micro\` 编译好 micro.sfx
+echo "<?php echo 'hello';" > a.php
+bin/spc micro:combine a.php
+
+# 使用
+./my-app

你可以使用以下参数指定要输出的文件名,你也可以指定其他路径的 micro.sfx 进行打包。

bash
# 指定输出文件名
+bin/spc micro:combine a.php --output=custom-bin
+# 使用绝对路径,也可以使用简化参数名
+bin/spc micro:combine a.php -O /tmp/my-custom-app
+
+# 指定其他位置的 micro.sfx 进行打包
+bin/spc micro:combine a.app --with-micro=/path/to/your/micro.sfx

如果想注入 ini 配置项,可以使用下面的参数,从文件或命令行选项添加 ini 到可执行文件中。

bash
# 使用命令行选项指定(-I 是 --with-ini-set 的简写)
+bin/spc micro:combine a.php -I "a=b" -I "foo=bar"
+
+# 使用 ini 文件指定(-N 是 --with-ini-file 的简写)
+bin/spc micro:combine a.php -N /path/to/your/custom.ini

WARNING

注意,请不要直接使用 PHP 源码或系统安装的 PHP 中的 php.ini 文件,最好手动编写一个自己需要的参数配置文件,例如:

ini
; custom.ini
+curl.cainfo=/path/to/your/cafile.pem
+memory_limit=1G

该命令的注入 ini 是通过在 micro.sfx 后追加一段特殊的结构来实现的,和编译时插入硬编码 INI 的功能不同。

如果要打包 phar,只需要将 a.php 替换为打包好的 phar 文件即可。但要注意,phar 下的 micro.sfx 需要额外注意路径问题,见 Developing - Phar 路径问题

命令 extract - 手动解压某个库

使用命令 bin/spc extract 可以解包和拷贝编译需要的源代码,包括 php-src 以及依赖的各种库的源码(需要自己指定要解包的库名)。

例如,我们在下载好资源后,想分布执行构建流程,手动解包和拷贝包到指定位置,可以使用命令。

bash
# 解压 php-src 和 libxml2 的下载压缩包,解压的源码存放在 source 目录
+bin/spc extract php-src,libxml2

调试命令 dev - 调试命令集合

调试命令指的是你在使用 static-php-cli 构建 PHP 或改造、增强 static-php-cli 项目本身的时候,可以辅助输出一些信息的命令集合。

  • dev:extensions: 输出目前所有支持的扩展信息,或者输出指定的扩展信息
  • dev:php-version: 输出当前编译的 PHP 版本(通过读取 php_version.h 实现)
  • dev:sort-config: 对 config/ 目录下的配置文件的列表按照字母表排序
  • dev:lib-ver <lib-name>: 从依赖库的源码中读取版本(仅特定依赖库可用)
  • dev:ext-ver <ext-name>: 从扩展的源码中读取对应版本(仅特定扩展可用)
bash
# 输出所有扩展
+bin/spc dev:extensions
+
+# 输出指定扩展的信息
+bin/spc dev:extensions mongodb,curl,openssl
+
+# 输出指定列,可选:lib-depends, lib-suggests, ext-depends, ext-suggests, unix-only, type
+bin/spc dev:extensions --columns=lib-depends,type,ext-depends
+
+# 输出当前编译的 PHP 版本(需要先将下载好的 PHP 源码解压到 source 目录,你可以使用 \`bin/spc extract php-src\` 单独解压缩源码)
+bin/spc dev:php-version
+
+# 排序配置文件 ext.json(也可以排序 lib、source)
+bin/spc dev:sort-config ext

命令 install-pkg - 下载二进制包

使用命令 bin/spc install-pkg 可以下载一些预编译或闭源的工具,并将其安装到 pkgroot 目录中。

bin/spc doctor 自动修复 Windows 环境时会下载 nasm、perl 等工具,使用的也是 install-pkg 的安装过程。

下面是安装工具的示例:

  • 下载安装 UPX(仅限 Linux 和 Windows): bin/spc install-pkg upx

命令 del-download - 删除已下载的资源

一些情况下,你需要删除单个或多个指定的下载源文件,并重新下载他们,例如切换 PHP 版本,2.1.0-beta.4 版本后提供了 bin/spc del-download 命令,可以删除指定源文件。

删除已下载的源文件包含预编译的包以及源代码,名称是 source.jsonpkg.json 中的键名。下面是一些例子:

  • 删除 PHP 8.2 源码并切换下载为 8.3 版本: bin/spc del-download php-src && bin/spc download php-src --with-php=8.3
  • 删除 redis 扩展的下载文件: bin/spc del-download redis
  • 删除下载好的 musl-toolchain x86_64: bin/spc del-download musl-toolchain-x86_64-linux

注入外部脚本

注入外部脚本指的是在 static-php-cli 编译过程中插入一个或多个脚本,用于更灵活地支持不同环境下的参数修改、源代码补丁。

一般情况下,该功能主要解决使用 spc 二进制进行编译时无法通过修改 static-php-cli 代码来实现修改补丁的功能。 还有一种情况:你的项目直接依赖了 crazywhalecc/static-php-cli 仓库并同步,但因为项目特性需要做出一些专有的修改,而这些特性并不适合合并到主分支。

鉴于以上情况,在 2.0.1 正式版本中,static-php-cli 加入了多个事件的触发点,你可以通过编写外部的 xx.php 脚本,并通过命令行参数 -P 传入并执行。

在编写注入外部脚本时,你一定会用到的方法是 builder()patch_point()。其中,patch_point() 获取的是当前正在执行的事件名称,builder() 获取的是 BuilderBase 对象。

因为传入的注入点不区分事件,所以你必须将你要执行的代码写在 if(patch_point() === 'your_event_name') 中,否则会重复在其他事件中执行。

下面是支持的 patch_point 事件名称及对应位置:

事件名称事件描述
before-libs-extract在编译的依赖库解压前触发
after-libs-extract在编译的依赖库解压后触发
before-php-extract在 PHP 源码解压前触发
after-php-extract在 PHP 源码解压后触发
before-micro-extract在 phpmicro 解压前触发
after-micro-extract在 phpmicro 解压后触发
before-exts-extract在要编译的扩展解压到 PHP 源码目录前触发
after-exts-extract在要编译的扩展解压到 PHP 源码目录后触发
before-library[name]-build在名称为 name 的库编译前触发(如 before-library[postgresql]-build
after-library[name]-build在名称为 name 的库编译后触发
before-php-buildconf在编译 PHP 命令 ./buildconf 前触发
before-php-configure在编译 PHP 命令 ./configure 前触发
before-php-make在编译 PHP 命令 make 前触发
before-sanity-check在编译 PHP 后,运行扩展检查前触发

下面是一个简单的临时修改 PHP 源码的例子,开启 CLI 下在当前工作目录查找 php.ini 配置的功能:

php
// a.php
+<?php
+if (patch_point() === 'before-php-buildconf') {
+    // replace php source code
+    \\SPC\\store\\FileSystem::replaceFileStr(
+        SOURCE_PATH . '/php-src/sapi/cli/php_cli.c',
+        'sapi_module->php_ini_ignore_cwd = 1;',
+        'sapi_module->php_ini_ignore_cwd = 0;'
+    );
+}
bash
bin/spc build mbstring --build-cli -P a.php
+echo 'memory_limit=8G' > ./php.ini
$ buildroot/bin/php -i | grep Loaded
+Loaded Configuration File => /Users/jerry/project/git-project/static-php-cli/php.ini
+
+$ buildroot/bin/php -i | grep memory
+memory_limit => 8G => 8G

对于 static-php-cli 支持的对象、方法及接口,可以阅读源码,大部分的方法和对象都有相应的注释。

一般使用 -P 功能常用的对象及函数有:

  • SPC\\store\\FileSystem: 文件管理类
    • ::replaceFileStr(string $filename, string $search, $replace): 替换文件字符串内容
    • ::replaceFileStr(string $filename, string $pattern, $replace): 正则替换文件内容
    • ::replaceFileUser(string $filename, $callback): 用户自定义函数替换文件内容
    • ::copyDir(string $from, string $to): 递归拷贝某个目录到另一个位置
    • ::convertPath(string $path): 转换路径的分隔符为当前系统分隔符
    • ::scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): 遍历目录文件
  • SPC\\builder\\BuilderBase: 构建对象
    • ->getPatchPoint(): 获取当前的注入点名称
    • ->getOption(string $key, $default = null): 获取命令行和编译时的选项
    • ->getPHPVersionID(): 获取当前编译的 PHP 版本 ID
    • ->getPHPVersion(): 获取当前编译的 PHP 版本号
    • ->setOption(string $key, $value): 设定选项
    • ->setOptionIfNotExists(string $key, $value): 如果选项不存在则设定选项

TIP

static-php-cli 开放的方法非常多,文档中无法一一列举,但只要是 public function 并且不被标注为 @internal,均可调用。

`,101),e=[l];function t(h,d,c,k,o,r){return a(),i("div",null,e)}const b=s(p,[["render",t]]);export{g as __pageData,b as default}; diff --git a/assets/zh_guide_manual-build.md.C58zH3IF.lean.js b/assets/zh_guide_manual-build.md.C58zH3IF.lean.js new file mode 100644 index 00000000..9eea7f28 --- /dev/null +++ b/assets/zh_guide_manual-build.md.C58zH3IF.lean.js @@ -0,0 +1 @@ +import{_ as s,c as i,o as a,a1 as n}from"./chunks/framework.CszIUXhs.js";const g=JSON.parse('{"title":"本地构建(Linux、macOS、FreeBSD)","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/manual-build.md","filePath":"zh/guide/manual-build.md"}'),p={name:"zh/guide/manual-build.md"},l=n("",101),e=[l];function t(h,d,c,k,o,r){return a(),i("div",null,e)}const b=s(p,[["render",t]]);export{g as __pageData,b as default}; diff --git a/assets/zh_guide_troubleshooting.md.CSXAWaMN.js b/assets/zh_guide_troubleshooting.md.CSXAWaMN.js new file mode 100644 index 00000000..c78ef88b --- /dev/null +++ b/assets/zh_guide_troubleshooting.md.CSXAWaMN.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"故障排除","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/troubleshooting.md","filePath":"zh/guide/troubleshooting.md"}'),r={name:"zh/guide/troubleshooting.md"},s=t('

故障排除

使用 static-php-cli 过程中可能会碰到各种各样的故障,这里将讲述如何自行查看错误并反馈 Issue。

下载失败问题

下载资源问题是 spc 最常见的问题之一。主要是由于 spc 下载资源使用的地址一般均为对应项目的官方网站或 GitHub 等,而这些网站可能偶尔会宕机、屏蔽 IP 地址。 目前 2.0.0 版本还没有加入自动重试机制,所以在遇到下载失败后,可以多次尝试调用下载命令。如果确认地址确实无法正常访问,可以提交 Issue 或 PR 更新地址。

doctor 无法修复

在绝大部分情况下,doctor 模块都可以对缺失的系统环境进行自动修复和安装,但也存在特殊的环境无法正常使用自动修复功能。

部分项目由于系统局限(如 Windows 下无法自动安装 Visual Studio 等软件),无法使用自动修复功能。 在遇到无法自动修复功能时,如果遇到 Some check items can not be fixed 字样,则表明无法自动修复,请根据终端显示的方法提交 Issue 或自行修复环境。

编译错误

遇到编译错误时,如果没有开启 --debug 日志,请先开启调试日志,然后确定报错的命令。 报错的终端输出对于修复编译错误非常重要,请在提交 Issue 时一并将终端日志的最后报错片段(或整个终端日志输出)上传,并且包含使用的 spc 命令和参数。

',9),c=[s];function i(d,n,h,l,u,_){return a(),o("div",null,c)}const m=e(r,[["render",i]]);export{b as __pageData,m as default}; diff --git a/assets/zh_guide_troubleshooting.md.CSXAWaMN.lean.js b/assets/zh_guide_troubleshooting.md.CSXAWaMN.lean.js new file mode 100644 index 00000000..fc8347da --- /dev/null +++ b/assets/zh_guide_troubleshooting.md.CSXAWaMN.lean.js @@ -0,0 +1 @@ +import{_ as e,c as o,o as a,a1 as t}from"./chunks/framework.CszIUXhs.js";const b=JSON.parse('{"title":"故障排除","description":"","frontmatter":{},"headers":[],"relativePath":"zh/guide/troubleshooting.md","filePath":"zh/guide/troubleshooting.md"}'),r={name:"zh/guide/troubleshooting.md"},s=t("",9),c=[s];function i(d,n,h,l,u,_){return a(),o("div",null,c)}const m=e(r,[["render",i]]);export{b as __pageData,m as default}; diff --git a/assets/zh_index.md.Bu-me8xZ.js b/assets/zh_index.md.Bu-me8xZ.js new file mode 100644 index 00000000..569f0ff4 --- /dev/null +++ b/assets/zh_index.md.Bu-me8xZ.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as i}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"在 Linux、macOS、FreeBSD、Windows 上与 PHP 项目一起构建独立的 PHP 二进制文件,并包含流行的扩展。","actions":[{"theme":"brand","text":"指南","link":"./guide/"}]},"features":[{"title":"静态二进制","details":"您可以轻松地编译一个独立的 PHP 二进制文件以供嵌入程序使用。包括 cli、fpm、micro。"},{"title":"phpmicro 自执行二进制","details":"您可以使用 micro SAPI 编译一个自解压的可执行文件,并将 PHP 代码与二进制文件打包为一个文件。"},{"title":"依赖管理","details":"static-php-cli 附带依赖项管理,支持安装不同类型的 PHP 扩展和不同的依赖库。"}]},"headers":[],"relativePath":"zh/index.md","filePath":"zh/index.md"}'),a={name:"zh/index.md"};function n(c,o,r,s,d,l){return i(),t("div")}const h=e(a,[["render",n]]);export{m as __pageData,h as default}; diff --git a/assets/zh_index.md.Bu-me8xZ.lean.js b/assets/zh_index.md.Bu-me8xZ.lean.js new file mode 100644 index 00000000..569f0ff4 --- /dev/null +++ b/assets/zh_index.md.Bu-me8xZ.lean.js @@ -0,0 +1 @@ +import{_ as e,c as t,o as i}from"./chunks/framework.CszIUXhs.js";const m=JSON.parse('{"title":"","description":"","frontmatter":{"layout":"home","hero":{"name":"static-php-cli","tagline":"在 Linux、macOS、FreeBSD、Windows 上与 PHP 项目一起构建独立的 PHP 二进制文件,并包含流行的扩展。","actions":[{"theme":"brand","text":"指南","link":"./guide/"}]},"features":[{"title":"静态二进制","details":"您可以轻松地编译一个独立的 PHP 二进制文件以供嵌入程序使用。包括 cli、fpm、micro。"},{"title":"phpmicro 自执行二进制","details":"您可以使用 micro SAPI 编译一个自解压的可执行文件,并将 PHP 代码与二进制文件打包为一个文件。"},{"title":"依赖管理","details":"static-php-cli 附带依赖项管理,支持安装不同类型的 PHP 扩展和不同的依赖库。"}]},"headers":[],"relativePath":"zh/index.md","filePath":"zh/index.md"}'),a={name:"zh/index.md"};function n(c,o,r,s,d,l){return i(),t("div")}const h=e(a,[["render",n]]);export{m as __pageData,h as default}; diff --git a/en/contributing/index.html b/en/contributing/index.html new file mode 100644 index 00000000..c092ee5c --- /dev/null +++ b/en/contributing/index.html @@ -0,0 +1,24 @@ + + + + + + Contributing | static-php-cli + + + + + + + + + + + + + +
Skip to content

Contributing

Thank you for being here, this project welcomes your contributions!

Contribution Guide

If you have code or documentation to contribute, here's what you need to know first.

  1. What type of code are you contributing? (new extensions, bug fixes, security issues, project framework optimizations, documentation)
  2. If you contribute new files or new snippets, is your code checked by php-cs-fixer and phpstan?
  3. Have you fully read the Developer Guide before contributing code?

If you can answer the above questions and have made changes to the code, you can initiate a Pull Request in the project GitHub repository in time. After the code review is completed, the code can be modified according to the suggestion, or directly merged into the main branch.

Contribution Type

The main purpose of this project is to compile statically linked PHP binaries, and the command line processing function is written based on symfony/console. Before development, if you are not familiar with it, Check out the symfony/console documentation first.

Security Update

Because this project is basically a PHP project running locally, generally speaking, there will be no remote attacks. But if you find such a problem, please **DO NOT submit a PR or Issue in the GitHub repository, You need to contact the project maintainer (crazywhalecc) via mail.

Fix Bugs

Fixing bugs generally does not involve modification of the project structure and framework, so if you can locate the wrong code and fix it directly, please submit a PR directly.

New Extensions

For adding a new extension, you need to understand some basic structure of the project and how to add a new extension according to the existing logic. It will be covered in detail in the next section on this page. In general, you will need:

  1. Evaluate whether the extension can be compiled inline into PHP.
  2. Evaluate whether the extension's dependent libraries (if any) can be compiled statically.
  3. Write library compile commands on different platforms.
  4. Verify that the extension and its dependencies are compatible with existing extensions and dependencies.
  5. Verify that the extension works normally in cli, micro, fpm, embed SAPIs.
  6. Write documentation and add your extension.

Project Framework Optimization

If you are already familiar with the working principle of symfony/console, and at the same time want to make some modifications or optimizations to the framework of the project, please understand the following things first:

  1. Adding extensions does not belong to project framework optimization, but if you find that you have to optimize the framework when adding new extensions, you need to modify the framework itself before adding extensions.
  2. For some large-scale logical modifications (such as those involving LibraryBase, Extension objects, etc.), it is recommended to submit an Issue or Draft PR for discussion first.
  3. In the early stage of the project, it was a pure private development project, and there were some Chinese comments in the code. After internationalizing your project you can submit a PR to translate these comments into English.
  4. Please do not submit more useless code fragments in the code, such as a large number of unused variables, methods, classes, and code that has been rewritten many times.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/develop/doctor-module.html b/en/develop/doctor-module.html new file mode 100644 index 00000000..0c78a0db --- /dev/null +++ b/en/develop/doctor-module.html @@ -0,0 +1,52 @@ + + + + + + Doctor module | static-php-cli + + + + + + + + + + + + + +
Skip to content

Doctor module

The Doctor module is a relatively independent module used to check the system environment, which can be entered with the command bin/spc doctor, and the entry command class is in DoctorCommand.php.

The Doctor module is a checklist with a series of check items and automatic repair items. These items are stored in the src/SPC/doctor/item/ directory, And two Attributes are used as check item tags and auto-fix item tags: #[AsCheckItem] and #[AsFixItem].

Take the existing check item if necessary tools are installed, which is used to check whether the packages necessary for compilation are installed in the macOS system. The following is its source code:

php
use SPC\doctor\AsCheckItem;
+use SPC\doctor\AsFixItem;
+use SPC\doctor\CheckResult;
+
+#[AsCheckItem('if necessary tools are installed', limit_os: 'Darwin', level: 997)]
+public function checkCliTools(): ?CheckResult
+{
+    $missing = [];
+    foreach (self::REQUIRED_COMMANDS as $cmd) {
+        if ($this->findCommand($cmd) === null) {
+            $missing[] = $cmd;
+        }
+    }
+    if (!empty($missing)) {
+        return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools', [$missing]);
+    }
+    return CheckResult::ok();
+}

The first parameter of the attribute is the name of the check item, and the following limit_os parameter restricts the check item to be triggered only under the specified system, and level is the priority of executing the check item, the larger the number, the higher the priority higher.

The $this->findCommand() method used in it is the method of SPC\builder\traits\UnixSystemUtilTrait, the purpose is to find the location of the system command, and return NULL if it cannot be found.

Each check item method should return a SPC\doctor\CheckResult:

  • When returning CheckResult::fail(), the first parameter is used to output the error prompt of the terminal, and the second parameter is the name of the repair item when this check item can be automatically repaired.
  • When CheckResult::ok() is returned, the check passed. You can also pass a parameter to return the check result, for example: CheckResult::ok('OS supported').
  • When returning CheckResult::fail(), if the third parameter is included, the array of the third parameter will be used as the parameter of AsFixItem.

The following is the method for automatically repairing items corresponding to this check item:

php
#[AsFixItem('build-tools')]
+public function fixBuildTools(array $missing): bool
+{
+    foreach ($missing as $cmd) {
+        try {
+            shell(true)->exec('brew install ' . escapeshellarg($cmd));
+        } catch (RuntimeException) {
+            return false;
+        }
+    }
+    return true;
+}

#[AsFixItem()] first parameter is the name of the fix item, and this method must return True or False. When False is returned, the automatic repair failed and manual handling is required.

In the code here, shell()->exec() is the method of executing commands of the project, which is used to replace exec() and system(), and also provides debugging, obtaining execution status, entering directories, etc. characteristic.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/develop/index.html b/en/develop/index.html new file mode 100644 index 00000000..91f4e405 --- /dev/null +++ b/en/develop/index.html @@ -0,0 +1,24 @@ + + + + + + Start Developing | static-php-cli + + + + + + + + + + + + + +
Skip to content

Start Developing

Developing this project requires the installation and deployment of a PHP environment, as well as some extensions and Composer commonly used in PHP projects.

The development environment and running environment of the project are almost exactly the same. You can refer to the Manual Build section to install system PHP or use the pre-built static PHP of this project as the environment. I will not go into details here.

Regardless of its purpose, this project itself is actually a php-cli program. You can edit and develop it as a normal PHP project. At the same time, you need to understand the Shell languages of different systems.

The current purpose of this project is to compile statically compiled independent PHP, but the main part also includes compiling static versions of many dependent libraries, so you can reuse this set of compilation logic to build independent binary versions of other programs, such as Nginx, etc.

Environment preparation

A PHP environment is required to develop this project. You can use the PHP that comes with the system, or you can use the static PHP built by this project.

Regardless of which PHP you use, in your development environment you need to install these extensions:

curl,dom,filter,mbstring,openssl,pcntl,phar,posix,sodium,tokenizer,xml,xmlwriter

The static-php-cli project itself does not require so many extensions, but during the development process, you will use tools such as Composer and PHPUnit, which require these extensions.

For micro self-executing binaries built by static-php-cli itself, only pcntl,posix,mbstring,tokenizer,phar is required.

Start development

Continuing down to see the project structure documentation, you can learn how static-php-cli works.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/develop/source-module.html b/en/develop/source-module.html new file mode 100644 index 00000000..ec594482 --- /dev/null +++ b/en/develop/source-module.html @@ -0,0 +1,128 @@ + + + + + + Source module | static-php-cli + + + + + + + + + + + + + +
Skip to content

Source module

The download source module of static-php-cli is a major module. It includes dependent libraries, external extensions, PHP source code download methods and file decompression methods. The download configuration file mainly involves the source.json and pkg.json file, which records the download method of all downloadable sources.

The main commands involved in the download function are bin/spc download and bin/spc extract. The download command is a downloader that downloads sources according to the configuration file, and the extract command is an extractor that extract sources from downloaded files.

Generally speaking, downloading sources may be slow because these sources come from various official websites, GitHub, and other different locations. At the same time, they also occupy a large space, so you can download the sources once and reuse them.

The configuration file of the downloader is source.json, which contains the download methods of all sources. You can add the source download methods you need, or modify the existing source download methods.

The download configuration structure of each source is as follows. The following is the source download configuration corresponding to the libevent extension:

json
{
+  "libevent": {
+    "type": "ghrel",
+    "repo": "libevent/libevent",
+    "match": "libevent.+\\.tar\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

The most important field here is type. Currently, the types it supports are:

  • url: Directly use URL to download, for example: https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz.
  • ghrel: Use the GitHub Release API to download, download the artifacts uploaded from the latest version released by maintainers.
  • ghtar: Use the GitHub Release API to download. Different from ghrel, ghtar is downloaded from the source code (tar.gz) in the latest Release of the project.
  • ghtagtar: Use GitHub Release API to download. Compared with ghtar, ghtagtar can find the latest one from the tags list and download the source code in tar.gz format (because some projects only use tag release version).
  • bitbuckettag: Download using BitBucket API, basically the same as ghtagtar, except this one applies to BitBucket.
  • git: Clone the project directly from a Git address to download sources, applicable to any public Git repository.
  • filelist: Use a crawler to crawl the Web download site that provides file index, and get the latest version of the file name and download it.
  • custom: If none of the above download methods are satisfactory, you can write custom, create a new class under src/SPC/store/source/, extends CustomSourceBase, and write the download script yourself.

source.json Common parameters

Each source file in source.json has the following params:

  • license: the open source license of the source code, see Open Source License section below
  • type: must be one of the types mentioned above
  • path (optional): release the source code to the specified directory instead of source/{name}

TIP

The path parameter in source.json can specify a relative or absolute path. When specified as a relative path, the path is based on source/.

Download type - url

URL type sources refer to downloading files directly from the URL.

The parameters included are:

  • url: The download address of the file, such as https://example.com/file.tgz
  • filename (optional): The file name saved to the local area. If not specified, the file name of the url will be used.

Example (download the imagick extension and extract it to the extension storage path of the php source code):

json
{
+   "ext-imagick": {
+     "type": "url",
+     "url": "https://pecl.php.net/get/imagick",
+     "path": "php-src/ext/imagick",
+     "filename": "imagick.tgz",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghrel

ghrel will download files from Assets uploaded in GitHub Release. First use the GitHub Release API to get the latest version, and then download the corresponding files according to the regular matching method.

The parameters included are:

  • repo: GitHub repository name
  • match: regular expression matching Assets files
  • prefer-stable: Whether to download stable versions first (default is false)

Example (download the libsodium library, matching the libsodium-x.y.tar.gz file in Release):

json
{
+   "libsodium": {
+     "type": "ghrel",
+     "repo": "jedisct1/libsodium",
+     "match": "libsodium-\\d+(\\.\\d+)*\\.tar\\.gz",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghtar

ghtar will download the file from the GitHub Release Tag. Unlike ghrel, ghtar will download the source code (tar.gz) from the latest Release of the project.

The parameters included are:

  • repo: GitHub repository name
  • prefer-stable: Whether to download stable versions first (default is false)

Example (brotli library):

json
{
+   "brotli": {
+     "type": "ghtar",
+     "repo": "google/brotli",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - ghtagtar

Use the GitHub Release API to download. Compared with ghtar, ghtagtar can find the latest one from the tags list and download the source code in tar.gz format (because some projects only use the tag version).

The parameters included are:

  • repo: GitHub repository name
  • prefer-stable: Whether to download stable versions first (default is false)

Example (gmp library):

json
{
+   "gmp": {
+     "type": "ghtagtar",
+     "repo": "alisw/GMP",
+     "license": {
+       "type": "text",
+       "text": "EXAMPLE LICENSE"
+     }
+   }
+}

Download Type - bitbuckettag

Download using BitBucket API, basically the same as ghtagtar, except this one works with BitBucket.

The parameters included are:

  • repo: BitBucket repository name

Download type - git

Clone the project directly from a Git address to download sources, applicable to any public Git repository.

The parameters included are:

  • url: Git link (HTTPS only)
  • rev: branch name
json
{
+   "imap": {
+     "type": "git",
+     "url": "https://github.com/static-php/imap.git",
+     "rev": "master",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

Download type - filelist

Use a crawler to crawl a web download site that provides a file index and get the latest version of the file name and download it.

Note that this method is only applicable to static sites with page index functions such as mirror sites and GNU official websites.

The parameters included are:

  • url: The URL of the page to crawl the latest version of the file
  • regex: regular expression matching file names and download links

Example (download the libiconv library from the GNU official website):

json
{
+   "libiconv": {
+     "type": "filelist",
+     "url": "https://ftp.gnu.org/gnu/libiconv/",
+     "regex": "/href=\"(?<file>libiconv-(?<version>[^\"]+)\\.tar\\.gz)\"/",
+     "license": {
+       "type": "file",
+       "path": "COPYING"
+     }
+   }
+}

Download type - custom

If the above downloading methods are not satisfactory, you can write custom, create a new class under src/SPC/store/source/, extends CustomSourceBase, and write the download script yourself.

I won’t go into details here, you can look at src/SPC/store/source/PhpSource.php or src/SPC/store/source/PostgreSQLSource.php as examples.

pkg.json General parameters

pkg.json stores non-source-code files, such as precompiled tools musl-toolchain and UPX. It includes:

  • type: The same type as source.json and different kinds of parameters.
  • extract (optional): The path to decompress after downloading, the default is pkgroot/{pkg_name}.
  • extract-files (optional): Extract only the specified files to the specified location after downloading.

It should be noted that pkg.json does not involve compilation, modification and distribution of source code, so there is no license open source license field. And you cannot use the extract and extract-files parameters at the same time.

Example (download nasm locally and extract only program files to PHP SDK):

json
{
+   "nasm-x86_64-win": {
+     "type": "url",
+     "url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
+     "extract-files": {
+       "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
+       "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
+     }
+   }
+}

The key name in extract-files is the file in the source folder, and the key value is the storage path. The storage path can use the following variables:

  • {php_sdk_path}: (Windows only) PHP SDK path
  • {pkg_root_path}: pkgroot/
  • {working_dir}: current working directory
  • {download_path}: download directory
  • {source_path}: source code decompression directory

When extract-files does not use variables and is a relative path, the directory of the relative path is {working_dir}.

Open source license

For source.json, each source file should contain an open source license. The license field stores the open source license information.

Each license contains the following parameters:

  • type: file or text
  • path: the license file in the source code directory (required when type is file)
  • text: License text (required when type is text)

Example (yaml extension source code with LICENSE file):

json
{
+   "yaml": {
+     "type": "git",
+     "path": "php-src/ext/yaml",
+     "rev": "php7",
+     "url": "https://github.com/php/pecl-file_formats-yaml",
+     "license": {
+       "type": "file",
+       "path": "LICENSE"
+     }
+   }
+}

When an open source project has multiple licenses, multiple files can be specified:

json
{
+   "libuv": {
+     "type": "ghtar",
+     "repo": "libuv/libuv",
+     "license": [
+       {
+         "type": "file",
+         "path": "LICENSE"
+       },
+       {
+         "type": "file",
+         "path": "LICENSE-extra"
+       }
+     ]
+   }
+}

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/develop/structure.html b/en/develop/structure.html new file mode 100644 index 00000000..89e993b9 --- /dev/null +++ b/en/develop/structure.html @@ -0,0 +1,33 @@ + + + + + + Introduction to project structure | static-php-cli + + + + + + + + + + + + + +
Skip to content

Introduction to project structure

static-php-cli mainly contains three logical components: sources, dependent libraries, and extensions. These components contains 4 configuration files: source.json, pkg.json, lib.json, and ext.json.

A complete process for building standalone static PHP is:

  1. Use the source download module Downloader to download specified or all source codes. These sources include PHP source code, dependent library source code, and extension source code.
  2. Use the source decompression module SourceExtractor to decompress the downloaded sources to the compilation directory.
  3. Use the dependency tool to calculate the dependent extensions and dependent libraries of the currently added extension, and then compile each library that needs to be compiled in the order of dependencies.
  4. After building each dependent library using Builder under the corresponding operating system, install it to the buildroot directory.
  5. If external extensions are included (the source code does not contain extensions within PHP), copy the external extensions to the source/php-src/ext/ directory.
  6. Use Builder to build the PHP source code and build target to the buildroot directory.

The project is mainly divided into several folders:

  • bin/: used to store program entry files, including bin/spc, bin/spc-alpine-docker, bin/setup-runtime.
  • config/: Contains all the extensions and dependent libraries supported by the project, as well as the download link and download methods of these sources. It is divided into files: lib.json, ext.json, source.json, pkg.json .
  • src/: The core code of the project, including the entire framework and commands for compiling various extensions and libraries.
  • vendor/: The directory that Composer depends on, you do not need to make any modifications to it.

The operating principle is to start a ConsoleApplication of symfony/console, and then parse the commands entered by the user in the terminal.

Basic command line structure

bin/spc is an entry file, including the Unix common #!/usr/bin/env php, which is used to allow the system to automatically execute with the PHP interpreter installed on the system. After the project executes new ConsoleApplication(), the framework will automatically register them as commands.

The project does not directly use the Command registration method and command execution method recommended by Symfony. Here are small changes:

  1. Each command uses the #[AsCommand()] Attribute to register the name and description.
  2. Abstract execute() so that all commands are based on BaseCommand (which is based on Symfony\Component\Console\Command\Command), and the execution code of each command itself is written in the handle() method .
  3. Added variable $no_motd to BaseCommand, which is used to display the Figlet greeting when the command is executed.
  4. BaseCommand saves InputInterface and OutputInterface as member variables. You can use $this->input and $this->output within the command class.

Basic source code structure

The source code of the project is located in the src/SPC directory, supports automatic loading of the PSR-4 standard, and contains the following subdirectories and classes:

  • src/SPC/builder/: The core compilation command code used to build libraries, PHP and related extensions under different operating systems, and also includes some compilation system tool methods.
  • src/SPC/command/: All commands of the project are here.
  • src/SPC/doctor/: Doctor module, which is a relatively independent module used to check the system environment. It can be entered using the command bin/spc doctor.
  • src/SPC/exception/: exception class.
  • src/SPC/store/: Classes related to storage, files and sources are all here.
  • src/SPC/util/: Some reusable tool methods are here.
  • src/SPC/ConsoleApplication.php: command line program entry file.

If you have read the source code, you may find that there is also a src/globals/ directory, which is used to store some global variables, global methods, and non-PSR-4 standard PHP source code that is relied upon during the build process, such as extension sanity check code etc.

Phar application directory issue

Like other php-cli projects, spc itself has additional considerations for paths. Because spc can run in multiple modes such as php-cli directly, micro SAPI, php-cli with Phar, vendor with Phar, etc., there are ambiguities in various root directories. A complete explanation is given here. This problem is generally common in the base class path selection problem of accessing files in PHP projects, especially when used with micro.sfx.

Note that this may only be useful for you when developing Phar projects or PHP frameworks.

Next, we will treat static-php-cli (that is, spc) as a normal php command line program. You can understand spc as any of your own php-cli applications for reference.

There are three basic constant theoretical values below. We recommend that you introduce these three constants when writing PHP projects:

  • WORKING_DIR: the working directory when executing PHP scripts

  • SOURCE_ROOT_DIR or ROOT_DIR: the root directory of the project folder, generally the directory where composer.json is located

  • FRAMEWORK_ROOT_DIR: the root directory of the framework used, which may be used by self-developed frameworks. Generally, the framework directory is read-only

You can define these constants in your framework entry or cli applications to facilitate the use of paths in your project.

The following are PHP built-in constant values, which have been defined inside the PHP interpreter:

  • __DIR__: the directory where the file of the currently executed script is located

  • __FILE__: the file path of the currently executed script

Git project mode (source)

Git project mode refers to a framework or program itself stored in plain text in the current folder, and running through php path/to/entry.php.

Assume that your project is stored in the /home/example/static-php-cli/ directory, or your project is the framework itself, which contains project files such as composer.json:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

We assume that the above constants are obtained from src/App/MyCommand.php:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIR/home/example/static-php-cli
FRAMEWORK_ROOT_DIR/home/example/static-php-cli
__DIR__/home/example/static-php-cli/src/App
__FILE__/home/example/static-php-cli/src/App/MyCommand.php

In this case, the values of WORKING_DIR, SOURCE_ROOT_DIR, and FRAMEWORK_ROOT_DIR are exactly the same: /home/example/static-php-cli.

The source code of the framework and the source code of the application are both in the current path.

Vendor library mode (vendor)

The vendor library mode generally means that your project is a framework or is installed into the project as a composer dependency by other applications, and the storage location is in the vendor/author/XXX directory.

Suppose your project is crazywhalecc/static-php-cli, and you or others install this project in another project using composer require.

We assume that static-php-cli contains all files except the vendor directory with the same Git mode, and get the constant value from src/App/MyCommand, Directory constant should be:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIR/home/example/another-app
FRAMEWORK_ROOT_DIR/home/example/another-app/vendor/crazywhalecc/static-php-cli
__DIR__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App
__FILE__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

Here SOURCE_ROOT_DIR refers to the root directory of the project using static-php-cli.

Git project Phar mode (source-phar)

Git project Phar mode refers to the mode of packaging the project directory of the Git project mode into a phar file. We assume that /home/example/static-php-cli will be packaged into a Phar file, and the directory has the following files:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

When packaged into app.phar and stored in the /home/example/static-php-cli directory, app.phar is executed at this time. Assuming that the src/App/MyCommand code is executed, the constant is obtained in the file:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
__DIR__phar:///home/example/static-php-cli/app.phar/src/App
__FILE__phar:///home/example/static-php-cli/app.phar/src/App/MyCommand.php

Because the phar:// protocol is required to read files in the phar itself, the project root directory and the framework directory will be different from WORKING_DIR.

Vendor Library Phar Mode (vendor-phar)

Vendor Library Phar Mode means that your project is installed as a framework in other projects and stored in the vendor directory.

We assume that your project directory structure is as follows:

composer.json                           # Composer configuration file of the current project
+box.json                                # Configuration file for packaging Phar
+another-app.php                         # Entry file of another project
+vendor/crazywhalecc/static-php-cli/*    # Your project is used as a dependent library

When packaging these files under the directory /home/example/another-app/ into app.phar, the value of the following constant for your project should be:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIRphar:///home/example/another-app/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli
__DIR__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App
__FILE__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/develop/system-build-tools.html b/en/develop/system-build-tools.html new file mode 100644 index 00000000..01f1b074 --- /dev/null +++ b/en/develop/system-build-tools.html @@ -0,0 +1,91 @@ + + + + + + Compilation Tools | static-php-cli + + + + + + + + + + + + + +
Skip to content

Compilation Tools

static-php-cli uses many system compilation tools when building static PHP. These tools mainly include:

  • autoconf: used to generate configure scripts.
  • make: used to execute Makefile.
  • cmake: used to execute CMakeLists.txt.
  • pkg-config: Used to find the installation path of dependent libraries.
  • gcc: used to compile C/C++ projects under Linux.
  • clang: used to compile C/C++ projects under macOS.

For Linux and macOS operating systems, these tools can usually be installed through the package manager, which is written in the doctor module. Theoretically we can also compile and download these tools manually, but this will increase the complexity of compilation, so we do not recommend this.

Linux Compilation Tools

For Linux systems, different distributions have different installation methods for compilation tools. And for static compilation, the package management of some distributions cannot install libraries and tools for pure static compilation. Therefore, for the Linux platform and its different distributions, we currently provide a variety of compilation environment preparations.

Glibc Environment

The glibc environment refers to the underlying libc library of the system (that is, the C standard library that all programs written in C language are dynamically linked to) uses glibc, which is the default environment for most distributions. For example: Ubuntu, Debian, CentOS, RHEL, openSUSE, Arch Linux, etc.

In the glibc environment, the package management and compiler we use point to glibc by default, and glibc cannot be statically linked well. One of the reasons it cannot be statically linked is that its network library nss cannot be compiled statically.

For the glibc environment, in static-php-cli and spc in 2.0-RC8 and later, you can choose two ways to build static PHP:

  1. Use Docker to build, you can use bin/spc-alpine-docker to build, it will build an Alpine Linux docker image.
  2. Use bin/spc doctor --auto-fix to install the musl-wrapper and musl-cross-make packages, and then build directly. (Related source code)

Generally speaking, the build results in these two environments are consistent, and you can choose according to actual needs.

In the doctor module, static-php-cli will first detect the current Linux distribution. If the current distribution is a glibc environment, you will be prompted to install the musl-wrapper and musl-cross-make packages.

The process of installing musl-wrapper in the glibc environment is as follows:

  1. Download the specific version of musl-wrapper source code from the musl official website.
  2. Use gcc installed from the package management to compile the musl-wrapper source code and generate musl-libc and other libraries: ./configure --disable-gcc-wrapper && make -j && sudo make install.
  3. The musl-wrapper related libraries will be installed in the /usr/local/musl directory.

The process of installing musl-cross-make in the glibc environment is as follows:

  1. Download the precompiled musl-cross-make compressed package from dl.static-php.dev .
  2. Unzip to the /usr/local/musl directory.

TIP

In the glibc environment, static compilation can be achieved by directly installing musl-wrapper, but musl-wrapper only contains musl-gcc and not musl-g++, which means that C++ code cannot be compiled. So we need musl-cross-make to provide musl-g++.

The reason why the musl-cross-make package cannot be compiled directly locally is that its compilation environment requirements are relatively high (requires more than 36GB of memory, compiled under Alpine Linux), so we provide precompiled binary packages that can be used for all Linux distributions.

At the same time, the package management of some distributions provides musl-wrapper, but musl-cross-make needs to match the corresponding musl-wrapper version, so we do not use package management to install musl-wrapper.

Compiling musl-cross-make will be introduced in the musl-cross-make Toolchain Compilation section of this chapter.

Musl Environment

The musl environment refers to the system's underlying libc library that uses musl, which is a lightweight C standard library that can be well statically linked.

For the currently popular Linux distributions, Alpine Linux uses the musl environment, so static-php-cli can directly build static PHP under Alpine Linux. You only need to install basic compilation tools (such as gcc, cmake, etc.) directly from the package management.

For other distributions, if your distribution uses the musl environment, you can also use static-php-cli to build static PHP directly after installing the necessary compilation tools.

TIP

In the musl environment, static-php-cli will automatically skip the installation of musl-wrapper and musl-cross-make.

Docker Environment

The Docker environment refers to using Docker containers to build static PHP. You can use bin/spc-alpine-docker to build. Before executing this command, you need to install Docker first, and then execute bin/spc-alpine-docker in the project root directory.

After executing bin/spc-alpine-docker, static-php-cli will automatically download the Alpine Linux image and then build a cwcc-spc-x86_64 or cwcc-spc-aarch64 image. Then all build process is performed within this image, which is equivalent to compiling in Alpine Linux.

musl-cross-make Toolchain Compilation

In Linux, although you do not need to manually compile the musl-cross-make tool, if you want to understand its compilation process, you can refer here. Another important reason is that this may not be compiled using automated tools such as CI and Actions, because the existing CI service compilation environment does not meet the compilation requirements of musl-cross-make, and the configuration that meets the requirements is too expensive.

The compilation process of musl-cross-make is as follows:

Prepare an Alpine Linux environment (either directly installed or using Docker). The compilation process requires more than 36GB of memory, so you need to compile on a machine with larger memory. Without this much memory, compilation may fail.

Then write the following content into the config.mak file:

makefile
STAT = -static --static
+FLAG = -g0 -Os -Wno-error
+
+ifneq ($(NATIVE),)
+COMMON_CONFIG += CC="$(HOST)-gcc ${STAT}" CXX="$(HOST)-g++ ${STAT}"
+else
+COMMON_CONFIG += CC="gcc ${STAT}" CXX="g++ ${STAT}"
+endif
+
+COMMON_CONFIG += CFLAGS="${FLAG}" CXXFLAGS="${FLAG}" LDFLAGS="${STAT}"
+
+BINUTILS_CONFIG += --enable-gold=yes --enable-gprofng=no
+GCC_CONFIG += --enable-static-pie --disable-cet --enable-default-pie  
+#--enable-default-pie
+
+CONFIG_SUB_REV = 888c8e3d5f7b
+GCC_VER = 13.2.0
+BINUTILS_VER = 2.40
+MUSL_VER = 1.2.4
+GMP_VER = 6.2.1
+MPC_VER = 1.2.1
+MPFR_VER = 4.2.0
+LINUX_VER = 6.1.36

And also you need to add gcc-13.2.0.tar.xz.sha1 file, contents here:

5f95b6d042fb37d45c6cbebfc91decfbc4fb493c  gcc-13.2.0.tar.xz

If you are using Docker to build, create a new Dockerfile file and write the following content:

dockerfile
FROM alpine:edge
+
+RUN apk add --no-cache \
+gcc g++ git make curl perl \
+rsync patch wget libtool \
+texinfo autoconf automake \
+bison tar xz bzip2 zlib \
+file binutils flex \
+linux-headers libintl \
+gettext gettext-dev icu-libs pkgconf \
+pkgconfig icu-dev bash \
+ccache libarchive-tools zip
+
+WORKDIR /opt
+
+RUN git clone https://git.zv.io/toolchains/musl-cross-make.git
+WORKDIR /opt/musl-cross-make
+COPY config.mak /opt/musl-cross-make
+COPY gcc-13.2.0.tar.xz.sha1 /opt/musl-cross-make/hashes
+
+RUN make TARGET=x86_64-linux-musl -j || :
+RUN sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+RUN make TARGET=x86_64-linux-musl -j
+RUN make TARGET=x86_64-linux-musl install -j
+RUN tar cvzf x86_64-musl-toolchain.tgz output/*

If you are using Alpine Linux in a non-Docker environment, you can directly execute the commands in the Dockerfile, for example:

bash
apk add --no-cache \
+gcc g++ git make curl perl \
+rsync patch wget libtool \
+texinfo autoconf automake \
+bison tar xz bzip2 zlib \
+file binutils flex \
+linux-headers libintl \
+gettext gettext-dev icu-libs pkgconf \
+pkgconfig icu-dev bash \
+ccache libarchive-tools zip
+
+git clone https://git.zv.io/toolchains/musl-cross-make.git
+# Copy config.mak to the working directory of musl-cross-make.
+# You need to replace /path/to/config.mak with your config.mak file path.
+cp /path/to/config.mak musl-cross-make/
+cp /path/to/gcc-13.2.0.tar.xz.sha1 musl-cross-make/hashes
+
+make TARGET=x86_64-linux-musl -j || :
+sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+make TARGET=x86_64-linux-musl -j
+make TARGET=x86_64-linux-musl install -j
+tar cvzf x86_64-musl-toolchain.tgz output/*

TIP

All the above scripts are suitable for x86_64 architecture Linux. If you need to build musl-cross-make for the ARM environment, just replace all x86_64 above with aarch64.

This compilation process may fail due to insufficient memory, network problems, etc. You can try a few more times, or use a machine with larger memory to compile. If you encounter problems or you have better improvement solutions, go to Discussion.

macOS Environment

For macOS systems, the main compilation tool we use is clang, which is the default compiler for macOS systems and is also the compiler of Xcode.

Compiling under macOS mainly relies on Xcode or Xcode Command Line Tools. You can download Xcode from the App Store, or execute xcode-select --install in the terminal to install Xcode Command Line Tools.

In addition, in the doctor environment check module, static-php-cli will check whether Homebrew, compilation tools, etc. are installed on the macOS system. If not, you will be prompted to install them. I will not go into details here.

FreeBSD Environment

FreeBSD is also a Unix system, and its compilation tools are similar to macOS. You can directly use the package management pkg to install clang and other compilation tools through the doctor command.

pkg-config Compilation (*nix only)

If you observe the compilation log when using static-php-cli to build static PHP, you will find that no matter what is compiled, pkg-config will be compiled first. This is because pkg-config is a library used to find dependencies. In earlier versions of static-php-cli, we directly used the pkg-config tool installed by package management, but this would cause some problems, such as:

  • Even if PKG_CONFIG_PATH is specified, pkg-config will try to find dependent packages from the system path.
  • Since pkg-config will look for dependent packages from the system path, if a dependent package with the same name exists in the system, compilation may fail.

In order to avoid the above problems, we compile pkg-config into buildroot/bin in user mode and use it. We use parameters such as --without-sysroot to avoid looking for dependent packages from the system path.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/faq/index.html b/en/faq/index.html new file mode 100644 index 00000000..63fbd71c --- /dev/null +++ b/en/faq/index.html @@ -0,0 +1,25 @@ + + + + + + FAQ | static-php-cli + + + + + + + + + + + + + +
Skip to content

FAQ

Here will be some questions that you may encounter easily. There are currently many, but I need to take time to organize them.

Can statically compiled PHP install extensions?

Because the principle of installing extensions in PHP under the traditional architecture is to install new extensions using .so type dynamic link libraries, and statically linked PHP compiled using this project cannot directly install new extensions using dynamic link libraries.

For the macOS platform, almost all binary files under macOS cannot be linked purely statically, and almost all binary files will link macOS system libraries: /usr/lib/libresolv.9.dylib and /usr/lib/libSystem.B.dylib. So under macOS system, statically compiled php binary files can be used under certain compilation conditions, and dynamic link extensions can be used at the same time:

  1. Using the --no-strip parameter will not strip information such as debugging symbols from the binary file for use with external Zend extensions such as Xdebug.
  2. If you want to compile some Zend extensions, use Homebrew, MacPorts, source code compilation, and install a normal version of PHP on your operating system.
  3. Use the phpize && ./configure && make command to compile the extensions you want to use.
  4. Copy the extension file xxxx.so to the outside, use the statically compiled PHP binary, for example to use the Xdebug extension: cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so".
bash
# build statically linked php-cli but not stripped
+bin/spc build ffi --build-cli --no-strip

For the Linux platform, the current compilation result is a purely statically linked binary file, and new extensions cannot be installed using a dynamic link library.

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 static-php-cli from source, so this project may never support them. However, in theory, you can access and use such extensions under macOS according to the above questions.

If you have a need for such extensions, or most people have needs for these closed-source extensions, see the discussion on standalone-php-cli. Welcome to leave a message.

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 (static-php-cli) 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

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 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.

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.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/action-build.html b/en/guide/action-build.html new file mode 100644 index 00000000..0928778d --- /dev/null +++ b/en/guide/action-build.html @@ -0,0 +1,24 @@ + + + + + + GitHub Action Build | static-php-cli + + + + + + + + + + + + + +
Skip to content

GitHub Action Build

Action Build refers to compiling directly using GitHub Action.

If you don't want to compile it yourself, you can download the artifact from the existing Action in this project, or you can download it from a self-hosted server:Enter.

Self-hosted binaries are also built from Actions: repo. The extensions included are: bcmath,bz2,calendar,ctype,curl,dom,exif,fileinfo,filter,ftp,gd,gmp,iconv,xml,mbstring,mbregex,mysqlnd,openssl, pcntl,pdo,pdo_mysql,pdo_sqlite,phar,posix,redis,session,simplexml,soap,sockets,sqlite3,tokenizer,xmlwriter,xmlreader,zlib,zip

Build Guide

Using GitHub Action makes it easy to build a statically compiled PHP and phpmicro, while also defining the extensions to compile.

  1. Fork project.
  2. Go to the Actions of the project and select CI.
  3. Select Run workflow, fill in the PHP version you want to compile, the target type, and the list of extensions. (extensions comma separated, e.g. bcmath,curl,mbstring)
  4. After waiting for about a period of time, enter the corresponding task and get Artifacts.

If you enable debug, all logs will be output at build time, including compiled logs, for troubleshooting.

If you need to build in other environments, you can use manual build.

Extensions

You can go to extensions check here to see if all the extensions you need currently support. and then go to command generator select the extension you need to compile, copy the extensions string to extensions option.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/build-on-windows.html b/en/guide/build-on-windows.html new file mode 100644 index 00000000..2b537192 --- /dev/null +++ b/en/guide/build-on-windows.html @@ -0,0 +1,46 @@ + + + + + + Build on Windows | static-php-cli + + + + + + + + + + + + + +
Skip to content

Build on Windows

Because the Windows system is an NT kernel, the compilation tools and operating system interfaces used by Unix-like operating systems are almost completely different, so the build process on Windows will be slightly different from that of Unix systems.

GitHub Actions Build

Building the Windows version of static-php from Actions is now supported. Like Linux and macOS, you need to Fork the static-php-cli repository to your GitHub account first, then you can enter Extension List to select the extension to be compiled, and then go to your own CI on Windows select the PHP version, fill in the extension list (comma separated), and click Run.

If you're going to develop or build locally, please read on.

Requirements

The tools required to build static PHP on Windows are the same as PHP's official Windows build tools. You can read Official Documentation.

To sum up, you need the following environment and tools:

  • Windows 10/11 (requires build 17063 or later)
  • Visual Studio 2019/2022 (recommended 2022)
  • C++ desktop development for Visual Studio
  • Git for Windows
  • php-sdk-binary-tools (can be installed automatically using doctor)
  • strawberry-perl (can be installed automatically using doctor)
  • nasm (can be installed automatically using doctor)

TIP

The construction of static-php-cli on Windows refers to using MSVC to build PHP and is not based on MinGW, Cygwin, WSL and other environments.

If you prefer to use WSL, please refer to the chapter on Building on Linux.

After installing Visual Studio and selecting the C++ desktop development workload, you may download about 8GB of compilation tools, and the download speed depends on your network conditions.

Install Git

Git for Windows can be downloaded and installed from here Standalone Installer 64-bit version, installed in the default location (C:\Program Files\Git\). If you don't want to download and install manually, you can also use Visual Studio Installer and check Git in the Individual component tab.

Prepare static-php-cli

Downloading the static-php-cli project is very simple, just use git clone. It is recommended to place the project in C:\spc-build\ or a similar directory. It is best not to have spaces in the path.

shell
mkdir "C:\spc-build"
+cd C:\spc-build
+git clone https://github.com/crazywhalecc/static-php-cli.git
+cd static-php-cli

It is a bit strange that static-php-cli itself requires a PHP environment, but now you can quickly install the PHP environment through a script. Generally, your computer will not have the Windows version of PHP installed, so we recommend that you use bin/setup-runtime directly after downloading static-php-cli to install PHP and Composer in the current directory.

shell
# Install PHP and Composer to the ./runtime/ directory
+bin/setup-runtime
+
+# After installation, if you need to use PHP and Composer in global commands, 
+# use the following command to add the runtime/ directory to PATH
+bin/setup-runtime -action add-path
+
+# Delete the runtime/ directory in PATH
+bin/setup-runtime -action remove-path

Install other Tools (automatic)

For php-sdk-binary-tools, strawberry-perl, and nasm, we recommend that you directly use the command bin/spc doctor to check and install them.

If doctor successfully installs automatically, please skip the steps below to manually install the above tools.

But if the automatic installation fails, please refer to the manual installation method below.

Install php-sdk-binary-tools (manual)

shell
cd C:\spc-build\static-php-cli
+git clone https://github.com/php/php-sdk-binary-tools.git

You can also set the global variable PHP_SDK_PATH in Windows settings and clone the project to the path corresponding to the variable. Under normal circumstances, you don't need to change it.

Install strawberry-perl (manual)

If you don't need to compile the openssl extension, you don't need to install perl.

  1. Download the latest version of strawberry-perl from GitHub.
  2. Install to the C:\spc-build\static-php-cli\pkgroot\perl\ directory.

You can download the -portable version and extract it directly to the above directory. The last perl.exe should be located at C:\spc-build\static-php-cli\pkgroot\perl\perl\bin\perl.exe.

Install nasm (manual)

If you don't need to compile openssl extension, you don't need to install nasm.

  1. Download the nasm tool (x64) from official website.
  2. Place nasm.exe and ndisasm.exe in the C:\spc-build\static-php-cli\php-sdk-binary-tools\bin\ directory.

Download required sources

Same as Manual build - Download

Build PHP

Use the build command to start building the static php binary. Before executing the bin/spc build command, be sure to use the download command to download sources. It is recommended to use doctor to check the environment.

Build SAPI

You need to go to Extension List or Command Generator to select the extension you want to add, and then use the command bin/spc build to compile. You need to specify targets, choose from the following parameters (at least one):

  • --build-cli: Build a cli sapi (command line interface, which can execute PHP code on the command line)
  • --build-micro: Build a micro sapi (used to build a standalone executable binary containing PHP code)
shell
# Compile PHP with bcmath,openssl,zlib extensions, the compilation target is cli
+bin/spc build "bcmath,openssl,zlib" --build-cli
+
+# Compile PHP with phar,curl,posix,pcntl,tokenizer extensions, compile target is micro and cli
+bin/spc build "bcmath,openssl,zlib" --build-micro --build-cli

WARNING

In Windows, it is best to use double quotes to wrap parameters containing commas, such as "bcmath,openssl,mbstring".

Debug

If you encounter problems during the compilation process, or want to view each executing shell command, you can use --debug to enable debug mode and view all terminal logs:

shell
bin/spc build "openssl" --build-cli --debug

Build Options

During the compilation process, in some special cases, the compiler and the content of the compilation directory need to be intervened. You can try to use the following commands:

  • --with-clean: clean up old make files before compiling PHP
  • --enable-zts: Make compiled PHP thread-safe version (default is NTS version)
  • --with-libs=XXX,YYY: Compile the specified dependent library before compiling PHP, and activate some extension optional functions
  • -I xxx=yyy: Hard compile INI options into PHP before compiling (support multiple options, alias is --with-hardcoded-ini)
  • --with-micro-fake-cli: When compiling micro, let micro's PHP_SAPI pretend to be cli (for compatibility with some programs that check PHP_SAPI)
  • --disable-opcache-jit: Disable opcache jit (enabled by default)
  • --without-micro-ext-test: After building micro.sfx, do not test the running results of different extensions in micro.sfx
  • --with-suggested-exts: Add ext-suggests as dependencies when compiling
  • --with-suggested-libs: Add lib-suggests as dependencies when compiling
  • --with-upx-pack: Use UPX to reduce the size of the binary file after compilation (you need to use bin/spc install-pkg upx to install upx first)
  • --with-micro-logo=XXX.ico: Customize the icon of the exe executable file after customizing the micro build (in the format of .ico)

Here is a simple example where we preset a larger memory_limit and disable the system function:

shell
bin/spc build "bcmath,openssl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"

Another example: Customize our hello-world.exe program logo:

shell
bin/spc build "ffi,bcmath" --build-micro --with-micro-logo=mylogo.ico --debug
+bin/spc micro:combine hello.php
+# Then we got `my-app.exe` with custom logo!
+my-app.exe

Use php.exe

After php.exe is compiled, it is located in the buildroot\bin\ directory. You can copy it to any location for use.

shell
.\php -v

Use micro.sfx

phpmicro is a SelF-extracted eXecutable SAPI module, provided by phpmicro project. But this project is using a fork of phpmicro, because we need to add some features to it. It can put php runtime and your source code together.

The final compilation result will output a file named ./micro.sfx, which needs to be used with your PHP source code like code.php. This file will be located in the path buildroot/bin/micro.sfx.

Prepare your project source code, which can be a single PHP file or a Phar file, for use.

If you want to combine phar files, you must add phar extension when compiling!

shell
# code.php "<?php echo 'Hello world' . PHP_EOL;"
+bin/spc micro:combine code.php -O my-app.exe
+# Run it!!! Copy it to another computer!!!
+./my-app.exe

If you package a PHAR file, just replace code.php with the phar file path. You can use box-project/box to package your CLI project as Phar, It is then combined with phpmicro to produce a standalone executable binary.

For more details on the micro:combine command, refer to command on Unix systems.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/cli-generator.html b/en/guide/cli-generator.html new file mode 100644 index 00000000..a7bfa9b8 --- /dev/null +++ b/en/guide/cli-generator.html @@ -0,0 +1,25 @@ + + + + + + CLI Build Command Generator | static-php-cli + + + + + + + + + + + + + + +
Skip to content

CLI Build Command Generator

TIP

The extensions selected below may contain extensions that are not supported by the selected operating system, which may cause compilation to fail. Please check Supported Extensions first.

Select Build OS

Select Extensions

Select common extensions
Unselect all
Select Dependencies

TIP

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.

Build Target

Build Options

Build Environment
Download PHP version
Enable debug message
Enable ZTS
Download with corresponding extension dependencies
Enable UPX compression (reduce binary size, but in rare cases micro SAPI doesn't work with UPX)

Hardcoded INI options

Result

Download sources by extensions command
bin/spc download --with-php=8.2 --for-extensions ""
Compile command
bin/spc build --build-cli ""

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/env-vars.html b/en/guide/env-vars.html new file mode 100644 index 00000000..77f4042f --- /dev/null +++ b/en/guide/env-vars.html @@ -0,0 +1,29 @@ + + + + + + Environment variables | static-php-cli + + + + + + + + + + + + + +
Skip to content

Environment variables

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.

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
+bin/spc build mbstring,pcntl --build-cli
+
+# or direct use
+SPC_CONCURRENCY=4 bin/spc build mbstring,pcntl --build-cli

General environment variables

General environment variables can be used by all build targets.

var namedefault valuecomment
BUILD_ROOT_PATH{pwd}/buildrootThe root directory of the build target
BUILD_LIB_PATH{pwd}/buildroot/libThe root directory of compilation libraries
BUILD_INCLUDE_PATH{pwd}/buildroot/includeHeader file directory for compiling libraries
BUILD_BIN_PATH{pwd}/buildroot/binCompiled binary file directory
PKG_ROOT_PATH{pwd}/pkgrootDirectory where precompiled tools are installed
SOURCE_PATH{pwd}/sourceThe source code extract directory
DOWNLOAD_PATH{pwd}/downloadsDownloaded file directory
SPC_CONCURRENCYDepends on CPU coresNumber of parallel compilations
SPC_SKIP_PHP_VERSION_CHECKemptySkip PHP version check when set to yes

OS specific variables

These environment variables are system-specific and will only take effect on a specific OS.

Windows

var namedefault valuecomment
PHP_SDK_PATH{pwd}\php-sdk-binary-toolsPHP SDK tools path
UPX_EXEC$PKG_ROOT_PATH\bin\upx.exeUPX compression tool path

macOS

var namedefault valuecomment
CCclangC Compiler
CXXclang++C++ Compiler
SPC_DEFAULT_C_FLAGS--target=arm64-apple-darwin or --target=x86_64-apple-darwinDefault C flags (not the same as CFLAGS)
SPC_DEFAULT_CXX_FLAGS--target=arm64-apple-darwin or --target=x86_64-apple-darwinDefault C flags (not the same as CPPFLAGS)
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --forcePHP buildconf command prefix
SPC_CMD_PREFIX_PHP_CONFIGURE./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbgPHP configure command prefix
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCYPHP make command prefix
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGS -Werror=unknown-warning-optionCFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHCPPFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHLDFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os or -g -O0 (the latter when using --no-strip)EXTRA_CFLAGS variable of PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS-lresolvExtra EXTRA_LIBS variables for PHP make command

Linux

var namedefault valuecomment
UPX_EXEC$PKG_ROOT_PATH/bin/upxUPX compression tool path
GNU_ARCHx86_64 or aarch64CPU architecture
CCAlpine: gcc, Other: $GNU_ARCH-linux-musl-gccC Compiler
CXXAlpine: g++, Other: $GNU_ARCH-linux-musl-g++C++ Compiler
ARAlpine: ar, Other: $GNU_ARCH-linux-musl-arStatic library tools
LDld.goldLinker
PATH/usr/local/musl/bin:/usr/local/musl/$GNU_ARCH-linux-musl/bin:$PATHSystem PATH
SPC_DEFAULT_C_FLAGSemptyDefault C flags
SPC_DEFAULT_CXX_FLAGSemptyDefault C++ flags
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --forcePHP buildconf command prefix
SPC_CMD_PREFIX_PHP_CONFIGURELD_LIBRARY_PATH={ld_lib_path} ./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbgPHP configure command prefix
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCYPHP make command prefix
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGSCFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHCPPFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHLDFLAGS variable of PHP configure command
SPC_CMD_VAR_PHP_CONFIGURE_LIBS-ldl -lpthreadLIBS variable of PHP configure command
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os -fno-ident -fPIE or -g -O0 -fno-ident -fPIE (the latter when using --no-strip)EXTRA_CFLAGS variable of PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBSemptyExtra EXTRA_LIBS variables for PHP make command
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM-all-static (when using clang: -Xcompiler -fuse-ld=lld -all-static)Additional LDFLAGS variable for make command
SPC_NO_MUSL_PATHemptyWhether to not insert the PATH of the musl toolchain (not inserted when the value is yes)

{ld_lib_path} value is /usr/local/musl/$GNU_ARCH-linux-musl/lib

FreeBSD

Due to the small number of users of the FreeBSD system, we do not provide environment variables for the FreeBSD system for the time being.

Unix

For Unix systems such as macOS, Linux, FreeBSD, etc., the following environment variables are common.

var namedefault valuecomment
PATH$BUILD_BIN_PATH:$PATHSystem PATH
PKG_CONFIG_PATH$BUILD_LIB_PATH/pkgconfigpkg-config search path
PKG_CONFIG$BUILD_BIN_PATH/pkg-configpkg-config executable path

Library Environment variables (Unix only)

Starting from 2.2.0, static-php-cli supports custom environment variables for all compilation dependent library commands of macOS, Linux, FreeBSD and other Unix systems.

In this way, you can adjust the behavior of compiling dependent libraries through environment variables at any time. For example, you can set the optimization parameters for compiling the xxx library through xxx_CFLAGS=-O0.

Of course, not every library supports the injection of environment variables. We currently provide three wildcard environment variables with the suffixes:

  • _CFLAGS: CFLAGS for the compiler
  • _LDFLAGS: LDFLAGS for the linker
  • _LIBS: LIBS for the linker

The prefix is the name of the dependent library, and the specific name of the library is subject to lib.json. Among them, the library name with - needs to replace - with _.

Here is an example of an optimization option that replaces the openssl library compilation:

shell
openssl_CFLAGS="-O0"

The library name uses the same name listed in lib.json and is case-sensitive.

TIP

When no relevant environment variables are specified, except for the following variables, the remaining values are empty by default:

var namevar default value
pkg_config_CFLAGSmacOS: $SPC_DEFAULT_C_FLAGS -Wimplicit-function-declaration -Wno-int-conversion, Other: empty
pkg_config_LDFLAGSLinux: --static, Other: empty
imagemagick_LDFLAGSLinux: -static, Other: empty
imagemagick_LIBSmacOS: -liconv, Other: empty
ldap_LDFLAGS-L$BUILD_LIB_PATH
openssl_CFLAGSLinux: $SPC_DEFAULT_C_FLAGS, Other: empty
others...empty

The following table is a list of library names that support customizing the above three variables:

lib name
brotli
bzip
curl
freetype
gettext
gmp
imagemagick
ldap
libargon2
libavif
libcares
libevent
openssl

TIP

Because adapting custom environment variables to each library is a particularly tedious task, and in most cases you do not need custom environment variables for these libraries, so we currently only support custom environment variables for some libraries.

If the library you need to customize environment variables is not listed above, you can submit your request through GitHub Issue.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/extension-notes.html b/en/guide/extension-notes.html new file mode 100644 index 00000000..4cb2b1f0 --- /dev/null +++ b/en/guide/extension-notes.html @@ -0,0 +1,24 @@ + + + + + + Extension Notes | static-php-cli + + + + + + + + + + + + + +
Skip to content

Extension 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

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 - Unable to use ssl.

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.

swow

  1. Only PHP version >= 8.0 is supported.

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. Because the extension may be dropped from php, we recommend you look for an alternative implementation, such as 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

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 a Zend extension. The functions of Xdebug depend on PHP's Zend engine and underlying code. If you want to statically compile it into PHP, you may need a huge amount of patch code, which is not feasible.
  2. The macOS platform can compile an xdebug extension under PHP compiled on the same platform, extract the xdebug.so file, and then use the --no-strip parameter in static-php-cli to retain the debug symbol table and add the ffi extension. The compiled ./php binary can be configured and run by specifying the INI, eg ./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:

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 - Unable to use ssl.

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 - Unable to use ssl.

password-argon2

  1. password-argon2 is not a standard extension, it is an additional algorithm for the password_hash function.
  2. On Linux systems, password-argon2 dependency libargon2 conflicts with the libsodium library.

ffi

  1. Linux not supported yet: Due to limitations of the Linux system, although the ffi extension can be compiled successfully, it cannot be used to load other so extensions. The prerequisite for Linux to support loading so extensions is dynamic compilation, but dynamic compilation conflicts with the purpose of this project.
  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 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:

parallel

Parallel is only supported on PHP 8.0 ZTS and above.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/extensions.html b/en/guide/extensions.html new file mode 100644 index 00000000..8d510fb2 --- /dev/null +++ b/en/guide/extensions.html @@ -0,0 +1,24 @@ + + + + + + Extensions | static-php-cli + + + + + + + + + + + + + +
Skip to content

Extensions

  • yes: supported
  • blank: not supported yet, or WIP
  • no with issue link: confirmed to be unavailable due to issue
  • partial with issue link: supported but not perfect due to issue
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes

TIP

If an extension you need is missing, you can create a Feature Request.

Some extensions or libraries that the extension depends on will have some optional features. For example, the gd library optionally supports libwebp, freetype, etc. If you only use bin/spc build gd --build-cli they will not be included (static-php-cli defaults to the minimum dependency principle).

You can use --with-libs= to add these libraries when compiling. When the dependent libraries of this compilation include them, gd will automatically use them to enable these features. (For example: bin/spc build gd --with-libs=libwebp,freetype --build-cli)

Alternatively you can use --with-suggested-exts and --with-suggested-libs to enable all optional dependencies of these extensions and libraries. (For example: bin/spc build gd --with-suggested-libs --build-cli)

If you don't know whether an extension has optional features, you can check the spc configuration file or use the command bin/spc dev:extensions (library dependency is lib-suggests, extension dependency is ext-suggests).

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/index.html b/en/guide/index.html new file mode 100644 index 00000000..9723047a --- /dev/null +++ b/en/guide/index.html @@ -0,0 +1,24 @@ + + + + + + Guide | static-php-cli + + + + + + + + + + + + + +
Skip to content

Guide

Static php cli is a tool used to build statically compiled PHP binaries, currently supporting Linux and macOS systems.

In the guide section, you will learn how to use static php cli to build standalone PHP programs.

TIP

If you are a native English speaker, some corrections to the documentation are welcome.

Compilation Environment

The following is the architecture support situation, where ⚙️ represents support for GitHub Action build, 💻 represents support for local manual build, and empty represents temporarily not supported.

x86_64aarch64
macOS⚙️ 💻⚙️ 💻
Linux⚙️ 💻⚙️ 💻
Windows⚙️ 💻
FreeBSD💻💻

Among them, Linux is currently only tested on Ubuntu, Debian, and Alpine distributions, and other distributions have not been tested, which cannot guarantee successful compilation. For untested distributions, local compilation can be done using methods such as Docker to avoid environmental issues.

There are two architectures for macOS: x86_64 and Arm, but binaries compiled on one architecture cannot be directly used on the other architecture. Rosetta 2 cannot guarantee that programs compiled with Arm architecture can fully run on x86_64 environment.

Windows currently only supports the x86_64 architecture, and does not support 32-bit x86 or arm64 architecture.

Supported PHP Version

Currently, static php cli supports PHP versions 8.0 to 8.3, and theoretically supports PHP 7.4 and earlier versions. Simply select the earlier version when downloading. However, due to some extensions and special components that have stopped supporting earlier versions of PHP, static-php-cli will not explicitly support earlier versions. We recommend that you compile the latest PHP version possible for a better experience.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/manual-build.html b/en/guide/manual-build.html new file mode 100644 index 00000000..f8a33a9b --- /dev/null +++ b/en/guide/manual-build.html @@ -0,0 +1,162 @@ + + + + + + Build (Linux, macOS, FreeBSD) | static-php-cli + + + + + + + + + + + + + +
Skip to content

Build (Linux, macOS, FreeBSD)

This section covers the build process for Linux, macOS, and FreeBSD. If you want to build on Windows, also need to read Build on Windows.

This project provides a binary file of static-php-cli. You can directly download the binary file of the corresponding platform and then use it to build static PHP. Currently, the platforms supported by spc binary are Linux and macOS.

Here's how to download from self-hosted server:

bash
# Download from self-hosted nightly builds (sync with main branch)
+# For Linux x86_64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# For Linux aarch64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+# macOS x86_64 (Intel)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+# macOS aarch64 (Apple)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+# Windows (x86_64, win10 build 17063 or later)
+curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+
+# Add execute perm (Linux and macOS only)
+chmod +x ./spc
+
+# Run (Linux and macOS)
+./spc --version
+# Run (Windows powershell)
+.\spc.exe --version

If you are using the packaged spc binary, you will need to replace the leading bin/spc with ./spc in all the commands below.

Build locally (using source code)

If you have problems using the spc binary, or if you need to modify the static-php-cli source code, download static-php-cli from the source code.

Currently, it supports building on macOS and Linux. macOS supports the latest version of the operating system and two architectures, while Linux supports Debian and derivative distributions, as well as Alpine Linux.

Because this project itself is developed using PHP, it is also necessary to install PHP on the system during compilation. This project also provides static binary PHP suitable for this project, which can be selected and used according to actual situations.

bash
# clone repo
+git clone https://github.com/crazywhalecc/static-php-cli.git --depth=1
+cd static-php-cli
+
+# You need to install the PHP environment first before running Composer and this project. The installation method can be referred to below.
+composer update

Use System PHP

Below are some example commands for installing PHP and Composer in the system. It is recommended to search for the specific installation method yourself or ask the AI search engine to obtain the answer, which will not be elaborated here.

bash
# [macOS], need install Homebrew first. See https://brew.sh/
+# Remember change your composer executable path. For M1/M2 Chip mac, "/opt/homebrew/bin/", for Intel mac, "/usr/local/bin/". Or add it to your own path.
+brew install php wget
+wget https://getcomposer.org/download/latest-stable/composer.phar -O /path/to/your/bin/composer && chmod +x /path/to/your/bin/composer
+
+# [Debian], you need to make sure your php version >= 8.1 and composer >= 2.0
+sudo apt install php-cli composer php-tokenizer
+
+# [Alpine]
+apk add bash file wget xz php81 php81-common php81-pcntl php81-tokenizer php81-phar php81-posix php81-xml composer

TIP

Currently, some versions of Ubuntu install older PHP versions, so no installation commands are provided. If necessary, it is recommended to add software sources such as ppa first, and then install the latest version of PHP and tokenizer, XML, and phar extensions.

Older versions of Debian may have an older (<= 7.4) version of PHP installed by default, it is recommended to upgrade Debian first.

Use Docker

If you don't want to install PHP and Composer runtime environment on your system, you can use the built-in Docker environment build script.

bash
# To use directly, replace `bin/spc` with `bin/spc-alpine-docker` in all used commands
+bin/spc-alpine-docker

The first time the command is executed, docker build will be used to build a Docker image. The default built Docker image is the x86_64 architecture, and the image name is cwcc-spc-x86_64.

If you want to build aarch64 static-php-cli in x86_64 environment, you can use qemu to emulate the arm image to run Docker, but the speed will be very slow. Use command: SPC_USE_ARCH=aarch64 bin/spc-alpine-docker.

If it prompts that sudo is required to run after running, execute the following command once to grant static-php-cli permission to execute sudo:

bash
export SPC_USE_SUDO=yes

Use Precompiled Static PHP Binaries

If you don't want to use Docker and install PHP in the system, you can directly download the php binary cli program compiled by this project itself. The usage process is as follows:

Deploy the environment using the command, the command will download a static php-cli binary from self-hosted server. Next, it will automatically download Composer from getcomposer or Aliyun mirror.

TIP

Using precompiled static PHP binaries is currently only supported on Linux and macOS. The FreeBSD environment is currently not supported due to the lack of an automated build environment.

bash
bin/setup-runtime
+
+# For users with special network environments such as mainland China, you can use mirror sites (aliyun) to speed up the download speed
+bin/setup-runtime --mirror china

This script will download two files in total: bin/php and bin/composer. After the download is complete, there are two ways to use it:

  1. Add the bin/ directory to the PATH: export PATH="/path/to/your/static-php-cli/bin:$PATH", after adding the path, it is equivalent to installing PHP in the system, you can directly Use commands such as composer, php -v, or directly use bin/spc.
  2. Direct call, such as executing static-php-cli command: bin/php bin/spc --help, executing Composer: bin/php bin/composer update.

Command - download

Use the command bin/spc download to download the source code required for compilation, including php-src and the source code of various dependent libraries.

bash
# Download all dependencies
+bin/spc download --all
+
+# Download all dependent packages, and specify the main version of PHP to download, optional: 7.3, 7.4, 8.0, 8.1, 8.2, 8.3
+bin/spc download --all --with-php=8.2
+
+# Show download progress bar while downloading (curl)
+bin/spc download --all --debug
+
+# Delete old download data
+bin/spc download --clean
+
+# Download specified dependencies
+bin/spc download php-src,micro,zstd,ext-zstd
+
+# Download only extensions and libraries to be compiled (use extensions, including suggested libraries)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl,zstd
+
+# Download only the extensions and dependent libraries to be compiled (use extensions, excluding suggested libraries)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl --without-suggestions
+
+# Download only libraries to be compiled (use libraries, including suggested libraries and required libraries, can use --for-extensions together)
+bin/spc download  --for-libs=liblz4,libevent --for-extensions=pcntl,rar,xml
+
+# Download only libraries to be compiled (use libraries, excluding suggested libraries)
+bin/spc download --for-libs=liblz4,libevent --without-suggestions
+
+# When downloading sources, ignore some source caches (always force download, e.g. switching PHP version)
+bin/spc download --for-extensions=curl,pcntl,xml --ignore-cache-sources=php-src --with-php=8.3
+
+# Set retry times (default is 0)
+bin/spc download --all --retry=2

If the network in your area is not good, or the speed of downloading the dependency package is too slow, you can download download.zip which is packaged regularly every week from GitHub Action, and use the command to directly use the zip archive as a dependency.

Dependent packages can be downloaded locally from Action. Enter Action and select the latest Workflow that has been successfully run, and download download-files-x.y.

bash
bin/spc download --from-zip=/path/to/your/download.zip

If a source cannot be downloaded all the time, or you need to download some specific version of the package, such as downloading the beta version of PHP, the old version of the library, etc., you can use the parameter -U or --custom-url to rewrite the download link, Make the downloader force the link you specify to download packages from this source. The method of use is {source-name}:{url}, which can rewrite the download URLs of multiple libraries at the same time. Also, it is available when downloading with the --for-extensions option.

bash
# Specifying to download a beta version of PHP8.3
+bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+
+# Specifying to download an older version of the curl library
+bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"

Command - doctor

If you can run bin/spc normally but cannot compile static PHP or dependent libraries normally, you can run bin/spc doctor first to check whether the system itself lacks dependencies.

bash
# Quick check
+bin/spc doctor
+
+# Quickly check and fix when it can be automatically repaired (use package management to install dependent packages, only support the above-mentioned operating systems and distributions)
+bin/spc doctor --auto-fix

Command - build

Use the build command to start building the static php binary. Before executing the bin/spc build command, be sure to use the download command to download sources. It is recommended to use doctor to check the environment.

Basic build

You need to go to Extension List or Command Generator to select the extension you want to add, and then use the command bin/spc build to compile. You need to specify a compilation target, choose from the following parameters:

  • --build-cli: Build a cli sapi (command line interface, which can execute PHP code on the command line)
  • --build-fpm: Build a fpm sapi (php-fpm, used in conjunction with other traditional fpm architecture software such as nginx)
  • --build-micro: Build a micro sapi (used to build a standalone executable binary containing PHP code)
  • --build-embed: Build an embed sapi (used to embed into other C language programs)
  • --build-all: build all above sapi
bash
# Compile PHP with bcmath,curl,openssl,ftp,posix,pcntl extensions, the compilation target is cli
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+
+# Compile PHP with phar,curl,posix,pcntl,tokenizer extensions, compile target is micro
+bin/spc build phar,curl,posix,pcntl,tokenizer --build-micro

TIP

If you need to repeatedly build and debug, you can delete the buildroot/ and source/ directories so that you can re-extract and build all you need from the downloaded source code package:

shell
# remove
+rm -rf buildroot source
+# build again
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

TIP

If you want to build multiple versions of PHP and don't want to build other dependent libraries repeatedly each time, you can use switch-php-version to quickly switch to another version and compile after compiling one version:

shell
# switch to 8.3
+bin/spc switch-php-version 8.3
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+# switch to 8.0
+bin/spc switch-php-version 8.0
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

Debug

If you encounter problems during the compilation process, or want to view each executing shell command, you can use --debug to enable debug mode and view all terminal logs:

bash
bin/spc build mysqlnd,pdo_mysql --build-all --debug

Build Options

During the compilation process, in some special cases, the compiler and the content of the compilation directory need to be intervened. You can try to use the following commands:

  • --cc=XXX: Specifies the execution command of the C language compiler (Linux default musl-gcc or gcc, macOS default clang)
  • --cxx=XXX: Specifies the execution command of the C++ language compiler (Linux defaults to g++, macOS defaults to clang++)
  • --with-clean: clean up old make files before compiling PHP
  • --enable-zts: Make compiled PHP thread-safe version (default is NTS version)
  • --no-strip: Do not run strip after compiling the PHP library to trim the binary file to reduce its size (the macOS binary file without trim can use dynamically linked third-party extensions)
  • --with-libs=XXX,YYY: Compile the specified dependent library before compiling PHP, and activate some extended optional functions (such as libavif of the gd library, etc.)
  • -I xxx=yyy: Hard compile INI options into PHP before compiling (support multiple options, alias is --with-hardcoded-ini)
  • --with-micro-fake-cli: When compiling micro, let micro's PHP_SAPI pretend to be cli (for compatibility with some programs that check PHP_SAPI)
  • --disable-opcache-jit: Disable opcache jit (enabled by default)
  • -P xxx.php: Inject external scripts during static-php-cli compilation (see Inject external scripts below for details)
  • --without-micro-ext-test: After building micro.sfx, do not test the running results of different extensions in micro.sfx
  • --with-suggested-exts: Add ext-suggests as dependencies when compiling
  • --with-suggested-libs: Add lib-suggests as dependencies when compiling
  • --with-upx-pack: Use UPX to reduce the size of the binary file after compilation (you need to use bin/spc install-pkg upx to install upx first)

For hardcoding INI options, it works for cli, micro, embed sapi. Here is a simple example where we preset a larger memory_limit and disable the system function:

bash
bin/spc build bcmath,pcntl,posix --build-all -I "memory_limit=4G" -I "disable_functions=system"

Command - micro:combine

Use the micro:combine command to build the compiled micro.sfx and your code (.php or .phar file) into an executable binary. You can also use this command to directly build a micro binary injected with ini configuration.

TIP

Injecting ini configuration refers to adding a special structure after micro.sfx to save ini configuration items before combining micro.sfx with PHP source code.

micro.sfx can identify the INI file header through a special byte, and the micro can be started with INI through the INI file header.

The original wiki of this feature is in phpmicro - Wiki, and this feature may change in the future.

The following is the general usage, directly packaging the php source code into a file:

bash
# Before doing the packaging process, you should use `build --build-micro` to compile micro.sfx
+echo "<?php echo 'hello';" > a.php
+bin/spc micro:combine a.php
+
+# Just use it
+./my-app

You can use the following options to specify the file name to be output, and you can also specify micro.sfx in other paths for packaging.

bash
# specify the output filename
+bin/spc micro:combine a.php --output=custom-bin
+# Use absolute path
+bin/spc micro:combine a.php -O /tmp/my-custom-app
+
+# Specify micro.sfx in other locations for packaging
+bin/spc micro:combine a.app --with-micro=/path/to/your/micro.sfx

If you want to inject ini configuration items, you can use the following parameters to add ini to the executable file from a file or command line option.

bash
# Specified using command-line options (-I is shorthand for --with-ini-set)
+bin/spc micro:combine a.php -I "a=b" -I "foo=bar"
+
+# Use ini file specification (-N is shorthand for --with-ini-file)
+bin/spc micro:combine a.php -N /path/to/your/custom.ini

WARNING

Note, please do not directly use the PHP source code or the php.ini file in the system-installed PHP, it is best to manually write an ini configuration file that you need, for example:

ini
; custom.ini
+curl.cainfo=/path/to/your/cafile.pem
+memory_limit=1G

The ini injection of this command is achieved by appending a special structure after micro.sfx, which is different from the function of inserting hard-coded INI during compilation.

If you want to package phar, just replace a.php with the packaged phar file. But please note that micro.sfx under phar needs extra attention to the path problem, see Developing - Phar directory issue.

Command - extract

Use the command bin/spc extract to unpack and copy the source code required for compilation, including php-src and the source code of various dependent libraries (you need to specify the name of the library to be unpacked).

For example, after we have downloaded sources, we want to distribute and execute the build process, manually unpack and copy the package to a specified location, and we can use commands.

bash
# Unzip the downloaded compressed package of php-src and libxml2, and store the decompressed source code in the source directory
+bin/spc extract php-src,libxml2

Dev Command - dev

Debug commands refer to a collection of commands that can assist in outputting some information when you use static-php-cli to build PHP or modify and enhance the static-php-cli project itself.

  • dev:extensions: output all currently supported extension names, or output the specified extension information
  • dev:php-version: output the currently compiled PHP version (by reading php_version.h)
  • dev:sort-config: Sort the list of configuration files in the config/ directory in alphabetical order
  • dev:lib-ver <lib-name>: Read the version from the source code of the dependency library (only available for specific dependency libraries)
  • dev:ext-ver <ext-name>: Read the corresponding version from the source code of the extension (only available for specific extensions)
bash
# output all extensions information
+bin/spc dev:extensions
+
+# Output the meta information of the specified extension
+bin/spc dev:extensions mongodb,curl,openssl
+
+# Output the specified columns
+# Available column name: lib-depends, lib-suggests, ext-depends, ext-suggests, unix-only, type
+bin/spc dev:extensions --columns=lib-depends,type,ext-depends
+
+# Output the currently compiled PHP version
+# You need to decompress the downloaded PHP source code to the source directory first
+# You can use `bin/spc extract php-src` to decompress the source code separately
+bin/spc dev:php-version
+
+# Sort the configuration files in the config/ directory in alphabetical order (e.g. ext.json)
+bin/spc dev:sort-config ext

Command - install-pkg

Use the command bin/spc install-pkg to download some precompiled or closed source tools and install them into the pkgroot directory.

When bin/spc doctor automatically repairs the Windows environment, tools such as nasm and perl will be downloaded, and the installation process of install-pkg will also be used.

Here is an example of installing the tool:

  • Download and install UPX (Linux and Windows only): bin/spc install-pkg upx

Command - del-download

In some cases, you need to delete single or multiple specified download source files and re-download them, such as switching PHP versions. The bin/spc del-download command is provided after the 2.1.0-beta.4 version. Specified source files can be deleted.

Deletes downloaded source files containing precompiled packages and source code named as keys in source.json or pkg.json. Here are some examples:

  • Delete the old PHP source code and switch to download the 8.3 version: bin/spc del-download php-src && bin/spc download php-src --with-php=8.3
  • Delete the download file of redis extension: bin/spc del-download redis
  • Delete the downloaded musl-toolchain x86_64: bin/spc del-download musl-toolchain-x86_64-linux

Inject External Script

Injecting external scripts refers to inserting one or more scripts during the static-php-cli compilation process to more flexibly support parameter modifications and source code patches in different environments.

Under normal circumstances, this function mainly solves the problem that the patch cannot be modified by modifying the static-php-cli code when compiling with spc binary.

There is another situation: your project directly depends on the crazywhalecc/static-php-cli repository and is synchronized with main branch, but some proprietary modifications are required, and these feature are not suitable for merging into the main branch.

In view of the above situation, in the official version 2.0.0, static-php-cli has added multiple event trigger points. You can write an external xx.php script and pass it in through the command line parameter -P and execute.

When writing to inject external scripts, the methods you will use are builder() and patch_point(). Among them, patch_point() obtains the name of the current event, and builder() obtains the BuilderBase object.

Because the incoming patch point does not distinguish between events, you must write the code you want to execute in if(patch_point() === 'your_event_name'), otherwise it will be executed repeatedly in other events.

The following are the supported patch_point event names and corresponding locations:

Event nameEvent description
before-libs-extractTriggered before the dependent libraries extracted
after-libs-extractTriggered after the compiled dependent libraries extracted
before-php-extractTriggered before PHP source code extracted
after-php-extractTriggered after PHP source code extracted
before-micro-extractTriggered before phpmicro extract
after-micro-extractTriggered after phpmicro extracted
before-exts-extractTriggered before the extension (to be compiled) extracted to the PHP source directory
after-exts-extractTriggered after the extension extracted to the PHP source directory
before-library[name]-buildTriggered before the library named name is compiled (such as before-library[postgresql]-build)
after-library[name]-buildTriggered after the library named name is compiled
before-php-buildconfTriggered before compiling PHP command ./buildconf
before-php-configureTriggered before compiling PHP command ./configure
before-php-makeTriggered before compiling PHP command make
before-sanity-checkTriggered after compiling PHP but before running extended checks

The following is a simple example of temporarily modifying the PHP source code. Enable the CLI function to search for the php.ini configuration in the current working directory:

php
// a.php
+<?php
+// patch it before `./buildconf` executed
+if (patch_point() === 'before-php-buildconf') {
+    \SPC\store\FileSystem::replaceFileStr(
+        SOURCE_PATH . '/php-src/sapi/cli/php_cli.c',
+        'sapi_module->php_ini_ignore_cwd = 1;',
+        'sapi_module->php_ini_ignore_cwd = 0;'
+    );
+}
bash
bin/spc build mbstring --build-cli -P a.php
+# Write in ./
+echo 'memory_limit=8G' > ./php.ini
$ buildroot/bin/php -i | grep Loaded
+Loaded Configuration File => /Users/jerry/project/git-project/static-php-cli/php.ini
+
+$ buildroot/bin/php -i | grep memory
+memory_limit => 8G => 8G

For the objects, methods and interfaces supported by static-php-cli, you can read the source code. Most methods and objects have corresponding comments.

Commonly used objects and functions using the -P function are:

  • SPC\store\FileSystem: file management class
    • ::replaceFileStr(string $filename, string $search, $replace): Replace file string content
    • ::replaceFileStr(string $filename, string $pattern, $replace): Regularly replace file content
    • ::replaceFileUser(string $filename, $callback): User-defined function replaces file content
    • ::copyDir(string $from, string $to): Recursively copy a directory to another location
    • ::convertPath(string $path): Convert the path delimiter to the current system delimiter
    • ::scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): Traverse directory files
  • SPC\builder\BuilderBase: Build object
    • ->getPatchPoint(): Get the current injection point name
    • ->getOption(string $key, $default = null): Get command line and compile-time options
    • ->getPHPVersionID(): Get the currently compiled PHP version ID
    • ->getPHPVersion(): Get the currently compiled PHP version number
    • ->setOption(string $key, $value): Set options
    • ->setOptionIfNotExists(string $key, $value): Set option if option does not exist

TIP

static-php-cli has many open methods, which cannot be listed in the docs, but as long as it is a public function and is not marked as @internal, it theoretically can be called.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/guide/troubleshooting.html b/en/guide/troubleshooting.html new file mode 100644 index 00000000..d3842f91 --- /dev/null +++ b/en/guide/troubleshooting.html @@ -0,0 +1,24 @@ + + + + + + Troubleshooting | static-php-cli + + + + + + + + + + + + + +
Skip to content

Troubleshooting

Various failures may be encountered in the process of using static-php-cli, here will describe how to check the errors by yourself and report 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. Currently, version 2.0.0 has not added an automatic retry mechanism, so after encountering a download failure, you can try to call the download command multiple times. 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 --debug 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.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/en/index.html b/en/index.html new file mode 100644 index 00000000..a4b5cca8 --- /dev/null +++ b/en/index.html @@ -0,0 +1,24 @@ + + + + + + static-php-cli + + + + + + + + + + + + + +
Skip to content

static-php-cli

Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/extension-notes.html b/extension-notes.html new file mode 100644 index 00000000..bd7183f4 --- /dev/null +++ b/extension-notes.html @@ -0,0 +1,24 @@ + + + + + + static-php-cli + + + + + + + + + + + + + +
Skip to content
+ + + + \ No newline at end of file diff --git a/extensions.html b/extensions.html new file mode 100644 index 00000000..ad65959f --- /dev/null +++ b/extensions.html @@ -0,0 +1,24 @@ + + + + + + static-php-cli + + + + + + + + + + + + + +
Skip to content
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes
+ + + + \ No newline at end of file diff --git a/hashmap.json b/hashmap.json new file mode 100644 index 00000000..a5261648 --- /dev/null +++ b/hashmap.json @@ -0,0 +1 @@ +{"extension-notes.md":"CYTuu5Xm","zh_guide_index.md":"D0Jfo4Dz","zh_guide_extensions.md":"BkAzY34J","en_guide_extension-notes.md":"CLQNfx2s","en_guide_extensions.md":"DETBhSn0","en_guide_action-build.md":"DqfXKtKF","zh_guide_cli-generator.md":"CMA84kUR","en_contributing_index.md":"0xRtVBv6","en_develop_index.md":"BqNiKnHj","zh_guide_build-on-windows.md":"C1RFP4Q6","en_develop_structure.md":"wZEZWbru","index.md":"DDaDbFm-","en_develop_system-build-tools.md":"Ds5Kgdf6","en_guide_troubleshooting.md":"BZNNttUZ","zh_contributing_index.md":"BgLPhRbJ","zh_index.md":"Bu-me8xZ","en_faq_index.md":"DM_hczmb","extensions.md":"C4hBsrw7","en_develop_source-module.md":"CuG52-lh","en_develop_doctor-module.md":"M_P38WuA","zh_guide_troubleshooting.md":"CSXAWaMN","zh_faq_index.md":"Bs3v_2I2","zh_develop_structure.md":"DJyPDdQ4","zh_develop_doctor-module.md":"CPRdzud3","en_guide_cli-generator.md":"B6SIOY9P","en_index.md":"B7rqxnyF","zh_guide_env-vars.md":"Dn5AS_wq","zh_guide_extension-notes.md":"CfCgmU-D","zh_develop_index.md":"CISWAEXj","en_guide_index.md":"DzPC1rL-","zh_develop_source-module.md":"DMk5GAAn","en_guide_env-vars.md":"XRLVeMgw","zh_guide_manual-build.md":"C58zH3IF","en_guide_build-on-windows.md":"Bw1buXoR","en_guide_manual-build.md":"CiZNh_BU","zh_guide_action-build.md":"BQOsJgGT","zh_develop_system-build-tools.md":"DvA9SnOG"} diff --git a/index.html b/index.html new file mode 100644 index 00000000..6691b9ae --- /dev/null +++ b/index.html @@ -0,0 +1,24 @@ + + + + + + static-php-cli + + + + + + + + + + + + + +
Skip to content

static-php-cli

Build standalone PHP binary on Linux, macOS, FreeBSD, Windows, with PHP project together, with popular extensions included.

+ + + + \ No newline at end of file diff --git a/zh/contributing/index.html b/zh/contributing/index.html new file mode 100644 index 00000000..d3dd2eae --- /dev/null +++ b/zh/contributing/index.html @@ -0,0 +1,24 @@ + + + + + + 贡献指南 | static-php-cli + + + + + + + + + + + + + +
Skip to content

贡献指南

感谢你能够看到这里,本项目非常欢迎你的贡献!

贡献方法

如果你有代码或文档想要贡献,需要先了解以下内容。

  1. 你要贡献什么类型的代码?(新扩展、修复 Bug、安全问题、项目框架优化、文档)
  2. 如果你贡献了新文件或新片段,你的代码是否经过 php-cs-fixerphpstan 的检查?
  3. 在贡献代码前是否充分阅读了 开发指南

如果你可以回答以上问题,并已经对代码做出了修改,可以及时在项目 GitHub 仓库发起 Pull Request。待代码审查完毕后,可根据建议修改代码,或直接合并到主分支。

贡献类型

本项目主要用途是编译静态链接的 PHP 二进制,基于 symfony/console 编写了命令行处理功能。在开发之前,如果你对它不够熟悉, 可以先查看 symfony/console 文档

安全问题

因为本项目基本上是属于本地运行的 PHP 项目,一般来说不会存在远程攻击行为。但如果你发现了此类问题,请不要在 GitHub 仓库提交 PR 或 Issue, 你需要通过 邮件 的方式联系项目维护者(crazywhalecc)。

修复 Bug

修复 Bug 一般不涉及项目结构和框架的修改,所以如果你可以定位到错误代码并直接修复它,请直接提交 PR。

新扩展

对于添加一个新扩展来说,你需要先了解一些本项目的基本结构,以及如何根据现有的逻辑添加新扩展。在本页的下一章节将会详细介绍。 总的来说,你需要:

  1. 评估扩展是否可以内联编译到 PHP 中。
  2. 评估扩展的依赖库(如果有)是否可以静态编译。
  3. 写出扩展的依赖库在不同平台编译命令。
  4. 验证扩展及其依赖库能否与现有扩展和依赖库兼容。
  5. 验证扩展在 climicrofpmembed 几种 SAPI 中均正常工作。
  6. 编写文档,加入你的扩展。

项目框架优化

如果你已经熟悉 symfony/console 的工作原理,并同时要对项目的框架进行一些修改或优化,请先了解以下事情:

  1. 加入扩展不属于项目框架优化,但如果你在加入新的扩展时发现不得不优化框架,则需先对框架本身进行修改,然后再加入扩展。
  2. 对于一些大规模逻辑修改(例如涉及 LibraryBase、Extension 对象等的修改)时,建议先提交 Issue 或 Draft PR 进行讨论方案。
  3. 项目早期为纯中文开发项目,代码中存在一部分中文的注释。国际化项目后你可以提交 PR 将这些注释翻译为英语。
  4. 请不要在代码中提交包含较多无用的代码片段,例如大量未被使用的变量、方法、类、重复写了很多次的代码。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/develop/doctor-module.html b/zh/develop/doctor-module.html new file mode 100644 index 00000000..561423a3 --- /dev/null +++ b/zh/develop/doctor-module.html @@ -0,0 +1,52 @@ + + + + + + Doctor 模块 | static-php-cli + + + + + + + + + + + + + +
Skip to content

Doctor 模块

Doctor 模块是一个较为独立的用于检查系统环境的模块,可使用命令 bin/spc doctor 进入,入口的命令类在 DoctorCommand.php 中。

Doctor 模块是一个检查单,里面有一系列的检查项目和自动修复项目。这些项目都存放在 src/SPC/doctor/item/ 目录中, 并且使用了两种 Attribute 用作检查项标记和自动修复项目标记:#[AsCheckItem]#[AsFixItem]

以现有的检查项 if necessary tools are installed,它是用于检查编译必需的包是否安装在 macOS 系统内,下面是它的源码:

php
use SPC\doctor\AsCheckItem;
+use SPC\doctor\AsFixItem;
+use SPC\doctor\CheckResult;
+
+#[AsCheckItem('if necessary tools are installed', limit_os: 'Darwin', level: 997)]
+public function checkCliTools(): ?CheckResult
+{
+    $missing = [];
+    foreach (self::REQUIRED_COMMANDS as $cmd) {
+        if ($this->findCommand($cmd) === null) {
+            $missing[] = $cmd;
+        }
+    }
+    if (!empty($missing)) {
+        return CheckResult::fail('missing system commands: ' . implode(', ', $missing), 'build-tools', [$missing]);
+    }
+    return CheckResult::ok();
+}

属性的第一个参数就是检查项目的名称,后面的 limit_os 参数是限制了该检查项仅在指定的系统下触发,level 是执行该检查项的优先级,数字越大,优先级越高。

里面用到的 $this->findCommand() 方法为 SPC\builder\traits\UnixSystemUtilTrait 的方法,用途是查找系统命令所在位置,找不到时返回 NULL。

每个检查项的方法都应该返回一个 SPC\doctor\CheckResult

  • 在返回 CheckResult::fail() 时,第一个参数用于输出终端的错误提示,第二个参数是在这个检查项可自动修复时的修复项目名称。
  • 在返回 CheckResult::ok() 时,表明检查通过。你也可以传递一个参数,用于返回检查结果,例如:CheckResult::ok('OS supported')
  • 在返回 CheckResult::fail() 时,如果包含了第三个参数,第三个参数的数组将被当作 AsFixItem 的参数。

下面是这个检查项对应的自动修复项的方法:

php
#[AsFixItem('build-tools')]
+public function fixBuildTools(array $missing): bool
+{
+    foreach ($missing as $cmd) {
+        try {
+            shell(true)->exec('brew install ' . escapeshellarg($cmd));
+        } catch (RuntimeException) {
+            return false;
+        }
+    }
+    return true;
+}

#[AsFixItem()] 属性传入的参数即修复项的名称,该方法必须返回 True 或 False。当返回 False 时,表明自动修复失败,需要手动处理。

此处的代码中 shell()->exec() 是项目的执行命令的方法,用于替代 exec()system(),同时提供了 debug、获取执行状态、进入目录等特性。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/develop/index.html b/zh/develop/index.html new file mode 100644 index 00000000..4d614ab7 --- /dev/null +++ b/zh/develop/index.html @@ -0,0 +1,24 @@ + + + + + + 开发简介 | static-php-cli + + + + + + + + + + + + + +
Skip to content

开发简介

开发本项目需要安装部署 PHP 环境,以及一些 PHP 项目常用的扩展和 Composer。

项目的开发环境和运行环境几乎完全一致,你可以参照 指南-本地构建 部分安装系统 PHP 或使用本项目预构建的静态 PHP 作为环境,这里不再赘述。

抛开用途,本项目本身其实就是一个 php-cli 程序,你可以将它当作一个正常的 PHP 项目进行编辑和开发,同时你需要了解不同系统的 Shell 命令行。

本项目目前的目的就是为了编译静态编译的独立 PHP,但主体部分也包含编译很多依赖库的静态版本,所以你可以复用这套编译逻辑,用于构建其他程序的独立二进制版本,例如 Nginx 等。

环境准备

开发本项目需要 PHP 环境。你可以使用系统自带的 PHP,也可以使用本项目构建的静态 PHP。

无论是使用哪种 PHP,在开发环境,你需要安装这些扩展:

curl,dom,filter,mbstring,openssl,pcntl,phar,posix,sodium,tokenizer,xml,xmlwriter

static-php-cli 项目本身不需要这么多扩展,但在开发过程中,你会用到 Composer、PHPUnit 等工具,它们需要这些扩展。

对于 static-php-cli 自身构建的 micro 自执行二进制,仅需要 pcntl,posix,mbstring,tokenizer,phar

开始开发

继续向下查看项目结构的文档,你可以从中了解 static-php-cli 是如何运作的。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/develop/source-module.html b/zh/develop/source-module.html new file mode 100644 index 00000000..e8dc5186 --- /dev/null +++ b/zh/develop/source-module.html @@ -0,0 +1,128 @@ + + + + + + 资源模块 | static-php-cli + + + + + + + + + + + + + +
Skip to content

资源模块

static-php-cli 的下载资源模块是一个主要的功能,它包含了所依赖的库、外部扩展、PHP 源码的下载方式和资源解压方式。 下载的配置文件主要涉及 source.jsonpkg.json 文件,这个文件记录了所有可下载的资源的下载方式。

下载功能主要涉及的命令有 bin/spc downloadbin/spc extract。其中 download 命令是一个下载器,它会根据配置文件下载资源; extract 命令是一个解压器,它会根据配置文件解压资源。

一般来说,下载资源可能会比较慢,因为这些资源来源于各个官网、GitHub 等不同位置,同时它们也占用了较大空间,所以你可以在一次下载资源后,可重复使用。

下载器的配置文件是 source.json,它包含了所有资源的下载方式,你可以在其中添加你需要的资源下载方式,也可以修改已有的资源下载方式。

每个资源的下载配置结构如下,下面是 libevent 扩展对应的资源下载配置:

json
{
+  "libevent": {
+    "type": "ghrel",
+    "repo": "libevent/libevent",
+    "match": "libevent.+\\.tar\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

这里最主要的字段是 type,目前它支持的类型有:

  • url: 直接使用 URL 下载,例如:https://download.libsodium.org/libsodium/releases/libsodium-1.0.18.tar.gz
  • ghrel: 使用 GitHub Release API 下载,即从 GitHub 项目发布的最新版本中上传的附件下载。
  • ghtar: 使用 GitHub Release API 下载,与 ghrel 不同的是,ghtar 是从项目的最新 Release 中找 source code (tar.gz) 下载的。
  • ghtagtar: 使用 GitHub Release API 下载,与 ghtar 相比,ghtagtar 可以从 tags 列表找最新的,并下载 tar.gz 格式的源码(因为有些项目只使用了 tag 发布版本)。
  • bitbuckettag: 使用 BitBucket API 下载,基本和 ghtagtar 相同,只是这个适用于 BitBucket。
  • git: 直接从一个 Git 地址克隆项目来下载资源,适用于任何公开 Git 仓库。
  • filelist: 使用爬虫爬取提供文件索引的 Web 下载站点,并获取最新版本的文件名并下载。
  • custom: 如果以上下载方式都不能满足,你可以编写 custom 后,在 src/SPC/store/source/ 下新建一个类,并继承 CustomSourceBase,自己编写下载脚本。

source.json 通用参数

source.json 中每个源文件拥有以下字段:

  • license: 源代码的开源许可证,见下方 开源许可证 章节
  • type: 必须为上面提到的类型之一
  • path(可选): 释放源码到指定目录而非 source/{name}

TIP

source.json 中的 path 参数可指定相对路径或绝对路径。当指定为相对路径时,路径基于 source/

下载类型 - url

url 类型的资源指的是从 URL 直接下载文件。

包含的参数有:

  • url: 文件的下载地址,如 https://example.com/file.tgz
  • filename(可选): 保存到本地的文件名,如不指定,则使用 url 的文件名

例子(下载 imagick 扩展,并解压缩到 php 源码的扩展存放路径):

json
{
+  "ext-imagick": {
+    "type": "url",
+    "url": "https://pecl.php.net/get/imagick",
+    "path": "php-src/ext/imagick",
+    "filename": "imagick.tgz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghrel

ghrel 会从 GitHub Release 中上传的 Assets 下载文件。首先使用 GitHub Release API 获取最新版本,然后根据正则匹配方式下载相应的文件。

包含的参数有:

  • repo: GitHub 仓库名称
  • match: 匹配 Assets 文件的正则表达式
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(下载 libsodium 库,匹配 Release 中的 libsodium-x.y.tar.gz 文件):

json
{
+  "libsodium": {
+    "type": "ghrel",
+    "repo": "jedisct1/libsodium",
+    "match": "libsodium-\\d+(\\.\\d+)*\\.tar\\.gz",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghtar

ghtar 会从 GitHub Release Tag 下载文件,与 ghrel 不同的是,ghtar 是从项目的最新 Release 中找 source code (tar.gz) 下载的。

包含的参数有:

  • repo: GitHub 仓库名称
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(brotli 库):

json
{
+  "brotli": {
+    "type": "ghtar",
+    "repo": "google/brotli",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - ghtagtar

使用 GitHub Release API 下载,与 ghtar 相比,ghtagtar 可以从 tags 列表找最新的,并下载 tar.gz 格式的源码(因为有些项目只使用了 tag 发布版本)。

包含的参数有:

  • repo: GitHub 仓库名称
  • prefer-stable: 是否优先下载稳定版本(默认为 false

例子(gmp 库):

json
{
+  "gmp": {
+    "type": "ghtagtar",
+    "repo": "alisw/GMP",
+    "license": {
+      "type": "text",
+      "text": "EXAMPLE LICENSE"
+    }
+  }
+}

下载类型 - bitbuckettag

使用 BitBucket API 下载,基本和 ghtagtar 相同,只是这个适用于 BitBucket。

包含的参数有:

  • repo: BitBucket 仓库名称

下载类型 - git

直接从一个 Git 地址克隆项目来下载资源,适用于任何公开 Git 仓库。

包含的参数有:

  • url: Git 链接(仅限 HTTPS)
  • rev: 分支名称
json
{
+  "imap": {
+    "type": "git",
+    "url": "https://github.com/static-php/imap.git",
+    "rev": "master",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

下载类型 - filelist

使用爬虫爬取提供文件索引的 Web 下载站点,并获取最新版本的文件名并下载。

注意,该方法仅限于镜像站、GNU 官网等具有页面 index 功能的静态站点使用。

包含的参数有:

  • url: 要爬取文件最新版本的页面 URL
  • regex: 匹配文件名及下载链接的正则表达式

例子(从 GNU 官网下载 libiconv 库):

json
{
+  "libiconv": {
+    "type": "filelist",
+    "url": "https://ftp.gnu.org/gnu/libiconv/",
+    "regex": "/href=\"(?<file>libiconv-(?<version>[^\"]+)\\.tar\\.gz)\"/",
+    "license": {
+      "type": "file",
+      "path": "COPYING"
+    }
+  }
+}

下载类型 - custom

如果以上下载方式都不能满足,你可以编写 custom 后,在 src/SPC/store/source/ 下新建一个类,并继承 CustomSourceBase,自己编写下载脚本。

这里不再赘述,你可以查看 src/SPC/store/source/PhpSource.phpsrc/SPC/store/source/PostgreSQLSource.php 作为例子。

pkg.json 通用参数

pkg.json 存放的是非源码类型的文件资源,例如 musl-toolchain、UPX 等预编译的工具。它的使用包含:

  • type: 与 source.json 相同的类型及不同种类的参数。
  • extract(可选): 下载后解压缩的路径,默认为 pkgroot/{pkg_name}
  • extract-files(可选): 下载后仅解压指定的文件到指定位置。

需要注意的是,pkg.json 不涉及源代码的编译和修改分发,所以没有 license 开源许可证字段。并且你不能同时使用 extractextract-files 参数。

例子(下载 nasm 到本地,并只提取程序文件到 PHP SDK):

json
{
+  "nasm-x86_64-win": {
+    "type": "url",
+    "url": "https://www.nasm.us/pub/nasm/releasebuilds/2.16.01/win64/nasm-2.16.01-win64.zip",
+    "extract-files": {
+      "nasm-2.16.01/nasm.exe": "{php_sdk_path}/bin/nasm.exe",
+      "nasm-2.16.01/ndisasm.exe": "{php_sdk_path}/bin/ndisasm.exe"
+    }
+  }
+}

extract-files 中的键名为源文件夹下的文件,键值为存放的路径。存放的路径可以使用以下变量:

  • {php_sdk_path}: (仅限 Windows)PHP SDK 路径
  • {pkg_root_path}: pkgroot/
  • {working_dir}: 当前工作目录
  • {download_path}: 下载目录
  • {source_path}: 源码解压缩目录

extract-files 不使用变量且为相对路径时,相对路径的目录为 {working_dir}

开源许可证

对于 source.json 而言,每个源文件都应包含开源许可证。license 字段存放了开源许可证的信息。

每个 license 包含的参数有:

  • type: filetext
  • path: 源代码目录中的许可证文件(当 typefile 时,此项必填)
  • text: 许可证文本(当 typetext 时,此项必填)

例子(yaml 扩展的源代码中带有 LICENSE 文件):

json
{
+  "yaml": {
+    "type": "git",
+    "path": "php-src/ext/yaml",
+    "rev": "php7",
+    "url": "https://github.com/php/pecl-file_formats-yaml",
+    "license": {
+      "type": "file",
+      "path": "LICENSE"
+    }
+  }
+}

当开源项目拥有多个许可证时,可指定多个文件:

json
{
+  "libuv": {
+    "type": "ghtar",
+    "repo": "libuv/libuv",
+    "license": [
+      {
+        "type": "file",
+        "path": "LICENSE"
+      },
+      {
+        "type": "file",
+        "path": "LICENSE-extra"
+      }
+    ]
+  }
+}

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/develop/structure.html b/zh/develop/structure.html new file mode 100644 index 00000000..ba9eefe8 --- /dev/null +++ b/zh/develop/structure.html @@ -0,0 +1,33 @@ + + + + + + 项目结构简介 | static-php-cli + + + + + + + + + + + + + +
Skip to content

项目结构简介

static-php-cli 主要包含三种逻辑组件:资源、依赖库、扩展。这三种组件四个配置文件:source.jsonlib.jsonext.jsonpkg.json

一个完整的构建静态 PHP 流程是:

  1. 使用资源下载模块 Downloader 下载指定或所有资源,这些资源包含 PHP 源码、依赖库源码、扩展源码。
  2. 使用资源解压模块 SourceExtractor 解压下载的资源到编译目录。
  3. 使用依赖工具计算出当前加入的扩展的依赖扩展、依赖库,然后对每个需要编译的依赖库进行编译,按照依赖顺序。
  4. 使用对应操作系统下的 Builder 构建每个依赖库后,将其安装到 buildroot 目录。
  5. 如果包含外部扩展(源码没有包含在 PHP 内的扩展),将外部扩展拷贝到 source/php-src/ext/ 目录。
  6. 使用 Builder 构建 PHP 源码,将其安装到 buildroot 目录。

项目主要分为几个文件夹:

  • bin/: 用于存放程序入口文件,包含 bin/spcbin/spc-alpine-dockerbin/setup-runtime
  • config/: 包含了所有项目支持的扩展、依赖库以及这些资源下载的地址、下载方式等,分为四个文件:lib.jsonext.jsonsource.jsonpkg.json
  • src/SPC/: 项目的核心代码,包含了整个框架以及编译各种扩展和库的命令。
  • src/globals/: 项目的全局方法和常量、运行时需要的测试文件(例如:扩展的可用性检查代码)。
  • vendor/: Composer 依赖的目录,你无需对它做出任何修改。

其中运行原理就是启动一个 symfony/consoleConsoleApplication,然后解析用户在终端输入的命令。

基本命令行结构

bin/spc 是一个 PHP 代码入口文件,包含了 Unix 通用的 #!/usr/bin/env php 用来让系统自动以系统安装好的 PHP 解释器执行。 在项目执行了 new ConsoleApplication() 后,框架会自动使用反射的方式,解析 src/SPC/command 目录下的所有类,并将其注册成为命令。

项目并没有直接使用 Symfony 推荐的 Command 注册方式和命令执行方式,这里做出了一点小变动:

  1. 每个命令都使用 #[AsCommand()] Attribute 来注册名称和简介。
  2. execute() 抽象化,让所有命令基于 BaseCommand(它基于 Symfony\Component\Console\Command\Command),每个命令本身的执行代码写到了 handle() 方法中。
  3. BaseCommand 添加了变量 $no_motd,用于是否在该命令执行时显示 Figlet 欢迎词。
  4. BaseCommandInputInterfaceOutputInterface 保存为成员变量,你可以在命令的类内使用 $this->input$this->output

基本源码结构

项目的源码位于 src/SPC 目录,支持 PSR-4 标准的自动加载,包含以下子目录和类:

  • src/SPC/builder/: 用于不同操作系统下构建依赖库、PHP 及相关扩展的核心编译命令代码,还包含了一些编译的系统工具方法。
  • src/SPC/command/: 项目的所有命令都在这里。
  • src/SPC/doctor/: Doctor 模块,它是一个较为独立的用于检查系统环境的模块,可使用命令 bin/spc doctor 进入。
  • src/SPC/exception/: 异常类。
  • src/SPC/store/: 有关存储、文件和资源的类都在这里。
  • src/SPC/util/: 一些可以复用的工具方法都在这里。
  • src/SPC/ConsoleApplication.php: 命令行程序入口文件。

如果你阅读过源码,你可能会发现还有一个 src/globals/ 目录,它是用于存放一些全局变量、全局方法、构建过程中依赖的非 PSR-4 标准的 PHP 源码,例如测试扩展代码等。

Phar 应用目录问题

和其他 php-cli 项目一样,spc 自身对路径有额外的考虑。 因为 spc 可以在 php-cli directlymicro SAPIphp-cli with Pharvendor with Phar 等多种模式下运行,各类根目录存在歧义。这里会进行一个完整的说明。 此问题一般常见于 PHP 项目中存取文件的基类路径选择问题,尤其是在配合 micro.sfx 使用时容易出现路径问题。

注意,此处仅对你在开发 Phar 项目或 PHP 框架时可能有用。

接下来我们都将 static-php-cli(也就是 spc)当作一个普通的 php 命令行程序来看,你可以将 spc 理解为你自己的任何 php-cli 应用以参考。

下面主要有三个基本的常量理论值,我们建议你在编写 php 项目时引入这三种常量:

  • WORKING_DIR:执行 PHP 脚本时的工作目录
  • SOURCE_ROOT_DIRROOT_DIR:项目文件夹的根目录,一般为 composer.json 所在目录
  • FRAMEWORK_ROOT_DIR:使用框架的根目录,自行开发的框架可能会用到,一般框架目录为只读

你可以在你的框架或者 cli 应用程序入口中定义这些常量,以方便在你的项目中使用路径。

下面是 PHP 内置的常量值,在 PHP 解释器内部已被定义:

  • __DIR__:当前执行脚本的文件所在目录
  • __FILE__:当前执行脚本的文件路径

Git 项目模式(source)

Git 项目模式指的是一个框架或程序本身在当前文件夹以纯文本形式存放,运行通过 php path/to/entry.php 方式。

假设你的项目存放在 /home/example/static-php-cli/ 目录下,或你的项目就是框架本身,里面包含 composer.json 等项目文件:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

我们假设从 src/App/MyCommand.php 中获取以上常量:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIR/home/example/static-php-cli
FRAMEWORK_ROOT_DIR/home/example/static-php-cli
__DIR__/home/example/static-php-cli/src/App
__FILE__/home/example/static-php-cli/src/App/MyCommand.php

这种情况下,WORKING_DIRSOURCE_ROOT_DIRFRAMEWORK_ROOT_DIR 的值是完全一致的:/home/example/static-php-cli。 框架的源码和应用的源码都在当前路径下。

Vendor 库模式(vendor)

Vendor 库模式一般是指你的项目为框架类或者被其他应用作为 composer 依赖项安装到项目中,存放位置在 vendor/author/XXX 目录。

假设你的项目是 crazywhalecc/static-php-cli,你或其他人在另一个项目使用 composer require 安装了这个项目。

我们假设 static-php-cli 中包含同 Git 模式 的除 vendor 目录外的所有文件,并从 src/App/MyCommand 中获取常量值, 目录常量应该是:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIR/home/example/another-app
FRAMEWORK_ROOT_DIR/home/example/another-app/vendor/crazywhalecc/static-php-cli
__DIR__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App
__FILE__/home/example/another-app/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

这里的 SOURCE_ROOT_DIR 就指的是使用 static-php-cli 的项目的根目录。

Git 项目 Phar 模式(source-phar)

Git 项目 Phar 模式指的是将 Git 项目模式的项目目录打包为一个 phar 文件的模式。我们假设 /home/example/static-php-cli 将打包为一个 Phar 文件,目录有以下文件:

composer.json
+src/App/MyCommand.app
+vendor/*
+bin/entry.php

打包为 app.phar 并存放到 /home/example/static-php-cli 目录下时,此时执行 app.phar,假设执行了 src/App/MyCommand 代码,常量在该文件内获取:

ConstantValue
WORKING_DIR/home/example/static-php-cli
SOURCE_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/static-php-cli/app.phar/
__DIR__phar:///home/example/static-php-cli/app.phar/src/App
__FILE__phar:///home/example/static-php-cli/app.phar/src/App/MyCommand.php

因为在 phar 内读取自身 phar 的文件需要 phar:// 协议进行,所以项目根目录和框架目录将会和 WORKING_DIR 不同。

Vendor 库 Phar 模式(vendor-phar)

Vendor 库 Phar 模式指的是你的项目作为框架安装在其他项目内,存储于 vendor 目录下。

我们假设你的项目目录结构如下:

composer.json # 当前项目的 Composer 配置文件
+box.json # 打包 Phar 的配置文件
+another-app.php # 另一个项目的入口文件
+vendor/crazywhalecc/static-php-cli/* # 你的项目被作为依赖库

将该目录 /home/example/another-app/ 下的这些文件打包为 app.phar 时,对于你的项目而言,下面常量的值应为:

ConstantValue
WORKING_DIR/home/example/another-app
SOURCE_ROOT_DIRphar:///home/example/another-app/app.phar/
FRAMEWORK_ROOT_DIRphar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli
__DIR__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App
__FILE__phar:///home/example/another-app/app.phar/vendor/crazywhalecc/static-php-cli/src/App/MyCommand.php

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/develop/system-build-tools.html b/zh/develop/system-build-tools.html new file mode 100644 index 00000000..1611e45d --- /dev/null +++ b/zh/develop/system-build-tools.html @@ -0,0 +1,90 @@ + + + + + + 系统编译工具 | static-php-cli + + + + + + + + + + + + + +
Skip to content

系统编译工具

static-php-cli 在构建静态 PHP 时使用了许多系统编译工具,这些工具主要包括:

  • autoconf: 用于生成 configure 脚本。
  • make: 用于执行 Makefile
  • cmake: 用于执行 CMakeLists.txt
  • pkg-config: 用于查找依赖库的安装路径。
  • gcc: 用于在 Linux 下编译 C/C++ 语言代码。
  • clang: 用于在 macOS 下编译 C/C++ 语言代码。

对于 Linux 和 macOS 操作系统,这些工具通常可以通过包管理安装,这部分在 doctor 模块中编写了。 理论上我们也可以通过编译和手动下载这些工具,但这样会增加编译的复杂度,所以我们不推荐这样做。

Linux 环境编译工具

对于 Linux 系统来说,不同发行版的编译工具安装方式不同。而且对于静态编译来说,某些发行版的包管理无法安装用于纯静态编译的库和工具, 所以对于 Linux 平台及其不同发行版,我们目前提供了多种编译环境的部署措施。

glibc 环境

glibc 环境指的是系统底层的 libc 库(即所有 C 语言编写的程序动态链接的 C 标准库)使用的是 glibc,这是大多数发行版的默认环境。 例如:Ubuntu、Debian、CentOS、RHEL、openSUSE、Arch Linux 等。

而 glibc 环境下,我们使用的包管理、编译器都是默认指向 glibc 的,glibc 不能被良好地静态链接。它不能被静态链接的原因之一是它的网络库 nss 无法静态编译。

对于 glibc 环境,在 2.0 RC8 及以后的 static-php-cli 及 spc 中,你可以选择两种方式来构建静态 PHP:

  1. 使用 Docker 构建,这是最简单的方式,你可以使用 bin/spc-alpine-docker 来构建,它会在 Alpine Linux 环境下构建。
  2. 使用 bin/spc doctor 安装 musl-wrapper 和 musl-cross-make 套件,然后直接正常构建。(相关源码

一般来说,这两种构建方式的构建结果是一致的,你可以根据实际需求选择。

在 doctor 模块中,static-php-cli 会先检测当前的 Linux 发行版。如果当前发行版是 glibc 环境,会提示需要安装 musl-wrapper 和 musl-cross-make 套件。

在 glibc 环境下安装 musl-wrapper 的过程如下:

  1. 从 musl 官网下载特定版本的 musl-wrapper 源码
  2. 使用从包管理安装的 gcc 编译 musl-wrapper 源码,生成 musl-libc 等库:./configure --disable-gcc-wrapper && make -j && sudo make install
  3. musl-wrapper 相关库将被安装在 /usr/local/musl 目录。

在 glibc 环境下安装 musl-cross-make 的过程如下:

  1. 从 dl.static-php.dev 下载预编译好的 musl-cross-make 压缩包。
  2. 解压到 /usr/local/musl 目录。

TIP

在 glibc 环境下,静态编译可以通过直接安装 musl-wrapper 来实现,但是 musl-wrapper 仅包含了 musl-gcc,而没有 musl-g++,这也就意味着无法编译 C++ 代码。 所以我们需要 musl-cross-make 来提供 musl-g++

而 musl-cross-make 套件无法在本地直接编译的原因是它的编译环境要求比较高(需要 36GB 以上内存,Alpine Linux 下编译),所以我们提供了预编译好的二进制包,可用于所有 Linux 发行版。

同时,部分发行版的包管理提供了 musl-wrapper,但 musl-cross-make 需要匹配对应的 musl-wrapper 版本,所以我们不使用包管理安装 musl-wrapper。

对于如何编译 musl-cross-make,将在本章节内的 编译 musl-cross-make 小节中介绍。

musl 环境

musl 环境指的是系统底层的 libc 库使用的是 musl,这是一种轻量级的 C 标准库,它的特点是可以被良好地静态链接。

对于目前流行的 Linux 发行版,Alpine Linux 使用的就是 musl 环境,所以 static-php-cli 在 Alpine Linux 下可以直接构建静态 PHP,仅需直接从包管理安装基础编译工具(如 gcc、cmake 等)即可。

对于其他发行版,如果你的发行版使用的是 musl 环境,那么你也可以在安装必要的编译工具后直接使用 static-php-cli 构建静态 PHP。

TIP

在 musl 环境下,static-php-cli 会自动跳过 musl-wrapper 和 musl-cross-make 的安装。

Docker 环境

Docker 环境指的是使用 Docker 容器来构建静态 PHP,你可以使用 bin/spc-alpine-docker 来构建。 执行这个命令前需要先安装 Docker,然后在项目根目录执行 bin/spc-alpine-docker 即可。

在执行 bin/spc-alpine-docker 后,static-php-cli 会自动下载 Alpine Linux 镜像,然后构建一个 cwcc-spc-x86_64cwcc-spc-aarch64 的镜像。 然后一切的构建都在这个镜像内进行,相当于在 Alpine Linux 内编译。总的来说,Docker 环境就是 musl 环境。

musl-cross-make 工具链编译

在 Linux 中,尽管你不需要手动编译 musl-cross-make 工具,但是如果你想了解它的编译过程,可以参考这里。 还有一个重要的原因就是,这个可能无法使用 CI、Actions 等自动化工具编译,因为现有的 CI 服务编译环境不满足 musl-cross-make 的编译要求,满足要求的配置价格太高。

musl-cross-make 的编译过程如下:

准备一个 Alpine Linux 环境(直接安装或使用 Docker 均可),编译的过程需要 36GB 以上内存,所以你需要在内存较大的机器上编译。如果没有这么多内存,可能会导致编译失败。

然后将以下内容写入 config.mak 文件内:

makefile
STAT = -static --static
+FLAG = -g0 -Os -Wno-error
+
+ifneq ($(NATIVE),)
+COMMON_CONFIG += CC="$(HOST)-gcc ${STAT}" CXX="$(HOST)-g++ ${STAT}"
+else
+COMMON_CONFIG += CC="gcc ${STAT}" CXX="g++ ${STAT}"
+endif
+
+COMMON_CONFIG += CFLAGS="${FLAG}" CXXFLAGS="${FLAG}" LDFLAGS="${STAT}"
+
+BINUTILS_CONFIG += --enable-gold=yes --enable-gprofng=no
+GCC_CONFIG += --enable-static-pie --disable-cet --enable-default-pie  
+#--enable-default-pie
+
+CONFIG_SUB_REV = 888c8e3d5f7b
+GCC_VER = 13.2.0
+BINUTILS_VER = 2.40
+MUSL_VER = 1.2.4
+GMP_VER = 6.2.1
+MPC_VER = 1.2.1
+MPFR_VER = 4.2.0
+LINUX_VER = 6.1.36

同时,你需要新建一个 gcc-13.2.0.tar.xz.sha1 文件,文件内容如下:

5f95b6d042fb37d45c6cbebfc91decfbc4fb493c  gcc-13.2.0.tar.xz

如果你使用的是 Docker 构建,新建一个 Dockerfile 文件,写入以下内容:

dockerfile
FROM alpine:edge
+
+RUN apk add --no-cache \
+gcc g++ git make curl perl \
+rsync patch wget libtool \
+texinfo autoconf automake \
+bison tar xz bzip2 zlib \
+file binutils flex \
+linux-headers libintl \
+gettext gettext-dev icu-libs pkgconf \
+pkgconfig icu-dev bash \
+ccache libarchive-tools zip
+
+WORKDIR /opt
+
+RUN git clone https://git.zv.io/toolchains/musl-cross-make.git
+WORKDIR /opt/musl-cross-make
+COPY config.mak /opt/musl-cross-make
+COPY gcc-13.2.0.tar.xz.sha1 /opt/musl-cross-make/hashes
+
+RUN make TARGET=x86_64-linux-musl -j || :
+RUN sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+RUN make TARGET=x86_64-linux-musl -j
+RUN make TARGET=x86_64-linux-musl install -j
+RUN tar cvzf x86_64-musl-toolchain.tgz output/*

如果你使用的是非 Docker 环境的 Alpine Linux,可以直接执行 Dockerfile 中的命令,例如:

bash
apk add --no-cache \
+gcc g++ git make curl perl \
+rsync patch wget libtool \
+texinfo autoconf automake \
+bison tar xz bzip2 zlib \
+file binutils flex \
+linux-headers libintl \
+gettext gettext-dev icu-libs pkgconf \
+pkgconfig icu-dev bash \
+ccache libarchive-tools zip
+
+git clone https://git.zv.io/toolchains/musl-cross-make.git
+# 将 config.mak 拷贝到 musl-cross-make 的工作目录内,你需要将 /path/to/config.mak 替换为你的 config.mak 文件路径
+cp /path/to/config.mak musl-cross-make/
+cp /path/to/gcc-13.2.0.tar.xz.sha1 musl-cross-make/hashes
+
+make TARGET=x86_64-linux-musl -j || :
+sed -i 's/poison calloc/poison/g' ./gcc-13.2.0/gcc/system.h
+make TARGET=x86_64-linux-musl -j
+make TARGET=x86_64-linux-musl install -j
+tar cvzf x86_64-musl-toolchain.tgz output/*

TIP

以上所有脚本都适用于 x86_64 架构的 Linux。如果你需要构建 ARM 环境的 musl-cross-make,只需要将上方所有 x86_64 替换为 aarch64 即可。

这个编译过程可能会因为内存不足、网络问题等原因导致编译失败,你可以多尝试几次,或者使用更大内存的机器来编译。 如果遇到了问题,或者你有更好的改进方案,可以在 讨论 中提出。

macOS 环境编译工具

对于 macOS 系统来说,我们使用的编译工具主要是 clang,它是 macOS 系统默认的编译器,同时也是 Xcode 的编译器。

在 macOS 下编译,主要依赖于 Xcode 或 Xcode Command Line Tools,你可以在 App Store 下载 Xcode,或者在终端执行 xcode-select --install 来安装 Xcode Command Line Tools。

此外,在 doctor 环境检查模块中,static-php-cli 会检查 macOS 系统是否安装了 Homebrew、编译工具等,如果没有,会提示你安装,这里不再赘述。

FreeBSD 环境编译工具

FreeBSD 也是 Unix 系统,它的编译工具和 macOS 类似,你可以直接使用包管理 pkg 安装 clang 等编译工具,通过 doctor 命令。

pkg-config 编译

如果你在使用 static-php-cli 构建静态 PHP 时仔细观察编译的日志,你会发现无论编译什么,都会先编译 pkg-config,这是因为 pkg-config 是一个用于查找依赖库的工具。 在早期的 static-php-cli 版本中,我们直接使用了包管理安装的 pkg-config 工具,但是这样会导致一些问题,例如:

  • 即使指定了 PKG_CONFIG_PATHpkg-config 也会尝试从系统路径中查找依赖包。
  • 由于 pkg-config 会从系统路径中查找依赖包,所以如果系统中存在同名的依赖包,可能会导致编译失败。

为了避免以上问题,我们将 pkg-config 编译到用户态的 buildroot/bin 内并使用,使用了 --without-sysroot 等参数来避免从系统路径中查找依赖包。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/faq/index.html b/zh/faq/index.html new file mode 100644 index 00000000..53696c43 --- /dev/null +++ b/zh/faq/index.html @@ -0,0 +1,25 @@ + + + + + + 常见问题 | static-php-cli + + + + + + + + + + + + + +
Skip to content

常见问题

这里将会编写一些你容易遇到的问题。目前有很多,但是我需要花时间来整理一下。

静态编译的 PHP 可以安装扩展吗

因为传统架构下的 PHP 安装扩展的原理是使用 .so 类型的动态链接的库方式安装新扩展,而使用本项目编译的静态链接的 PHP 无法直接使用动态链接库安装新扩展。

对于 macOS 平台来说,macOS 下的几乎所有二进制文件都无法真正纯静态链接,几乎所有二进制文件都会链接 macOS 的系统库:/usr/lib/libresolv.9.dylib/usr/lib/libSystem.B.dylib。 所以在 macOS 系统下,在特定的编译条件下可以使用静态编译的 php 二进制文件,同时使用动态链接的扩展:

  1. 使用 --no-strip 参数,将不会对二进制文件去除调试符号等信息,以供使用 Xdebug 等外部 Zend 扩展。
  2. 如果要编译某些 Zend 扩展,使用 Homebrew、MacPorts、源码编译的形式,在所在的操作系统安装一个普通版本的 PHP。
  3. 使用 phpize && ./configure && make 命令编译想要使用的扩展。
  4. 将扩展文件 xxxx.so 拷贝到外部,使用静态编译的 PHP 二进制,例如使用 Xdebug 扩展:cd buildroot/bin/ && ./php -d "zend_extension=/path/to/xdebug.so"
bash
# 构建静态 php-cli
+bin/spc build ffi --build-cli --no-strip

对于 Linux 平台来说,目前的编译结果为纯静态链接的二进制文件,无法使用动态链接库安装新扩展。

可以支持 Oracle 数据库扩展吗

部分依赖库闭源的扩展,如 oci8sourceguardian 等,它们没有提供纯静态编译的依赖库文件(.a),仅提供了动态依赖库文件(.so), 这些扩展无法使用源码的形式编译到 static-php-cli 中,所以本项目可能永远也不会支持这些扩展。不过,理论上你可以根据上面的问题在 macOS 下接入和使用这类扩展。

如果你对此类扩展有需求,或者大部分人都对这些闭源扩展使用有需求, 可以看看有关 standalone-php-cli 的讨论。欢迎留言。

支持 Windows 吗

该项目目前已支持 Windows,但支持的扩展数量较少,Windows 的支持并不完美,主要有以下几个问题:

  1. Windows 的编译流程与 *nix 不同,使用的工具链也不同,编译各个扩展的依赖库使用的编译工具也几乎完全不同。
  2. Windows 版本的需求也会根据所有使用本项目的人的需求推进,如果有很多人需要,我会尽快支持相关扩展。

使用 micro 可以保护我的源码吗

不可以。micro.sfx 本质上是将 php 和 php 代码结合为一个文件,没有 PHP 代码编译或加密的过程。 首先 php-src 是 PHP 代码的官方解释器,而且现在市面上还没有一个能兼容主流分支的 PHP 编译器。 之前我在网上看到有一个项目是 BPC(Binary PHP Compiler?)可以把 PHP 编译为二进制,但是限制也是很多很多。

加密保护代码的方向和编译也不是一回事,编译过后也可以通过逆向工程等方式拿到代码,真正保护还是通过加壳、加密代码等手段进行。

所以本项目(static-php-cli)、相关项目(lwmbs、swoole-cli)都是提供一个对 php-src 源码的便捷编译工具, 本项目和相关项目引用的 phpmicro 也仅仅是 PHP 的 sapi 接口封装,而不是 PHP 代码的编译工具。 PHP 代码的编译器是完全不同的项目,因此不会考虑额外的情况。如果你对加密感兴趣,可以考虑使用现有的加密技术,如 Swoole Compiler、Source Guardian 等。

无法使用 ssl

使用 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 文件,也可以使用系统自带的证书文件。 有关不同发行版的证书位置,可参考 Go 标准库

INI 配置 openssl.cafile 不可以使用 ini_set() 函数动态设置,因为 openssl.cafile 是一个 PHP_INI_SYSTEM 类型的配置,只能在 php.ini 文件中设置。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/action-build.html b/zh/guide/action-build.html new file mode 100644 index 00000000..49b70083 --- /dev/null +++ b/zh/guide/action-build.html @@ -0,0 +1,24 @@ + + + + + + Action 构建 | static-php-cli + + + + + + + + + + + + + +
Skip to content

Action 构建

Action 构建指的是直接使用 GitHub Action 进行编译。

如果你不想自行编译,可以从本项目现有的 Action 下载 Artifact,也可以从自托管的服务器下载:进入

自托管的二进制也是由 Action 构建而来,项目仓库地址

构建方法

使用 GitHub Action 可以方便地构建一个静态编译的 PHP 和 phpmicro,同时可以自行定义要编译的扩展。

  1. Fork 本项目。
  2. 进入项目的 Actions,选择 CI 开头的 Workflow(根据你需要的操作系统选择)。
  3. 选择 Run workflow,填入你要编译的 PHP 版本、目标类型、扩展列表。(扩展列表使用英文逗号分割,例如 bcmath,curl,mbstring
  4. 等待大约一段时间后,进入对应的任务中,获取 Artifacts

如果你选择了 debug,则会在构建时输出所有日志,包括编译的日志,以供排查错误。

如果你需要在其他环境构建,可以使用 手动构建

扩展选择

你可以到 扩展列表 中查看目前你需要的扩展是否均支持, 然后到 在线命令生成 中选择你需要编译的扩展,复制扩展字符串到 Action 的 extensions 中,编译即可。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/build-on-windows.html b/zh/guide/build-on-windows.html new file mode 100644 index 00000000..e1e7282b --- /dev/null +++ b/zh/guide/build-on-windows.html @@ -0,0 +1,45 @@ + + + + + + 在 Windows 上构建 | static-php-cli + + + + + + + + + + + + + +
Skip to content

在 Windows 上构建

因为 Windows 系统是 NT 内核,与类 Unix 的操作系统使用的编译工具及操作系统接口几乎完全不同,所以在 Windows 上的构建流程会与 Unix 系统有些许不同。

GitHub Actions 构建

现在已支持从 Actions 构建 Windows 版本的 static-php 了。 和 Linux、macOS 一样,你需要先 Fork static-php-cli 仓库到你的 GitHub 账户中,然后你可以进入 扩展列表 选择要编译的扩展,然后进入自己仓库的 CI on Windows 选择 PHP 版本、填入扩展列表(逗号分割),点击 Run 即可。

如果你要在本地开发或构建,请继续向下阅读。

环境准备

在 Windows 上构建静态 PHP 所需要的工具与 PHP 官方的 Windows 构建工具是相同的。你可以阅读 官方文档

总结下来,你需要以下环境及工具:

  • Windows 10(需要 build 17063 或以后的更新)
  • Visual Studio 2019/2022(推荐 2022)
  • Visual Studio 的 C++ 桌面开发
  • Git for Windows
  • static-php-cli 仓库
  • PHP 和 Composer(static-php-cli 需要它们,可使用 bin/setup-runtime 自动安装)
  • php-sdk-binary-tools(可使用 doctor 自动安装)
  • strawberry-perl(可使用 doctor 自动安装)
  • nasm(可使用 doctor 自动安装)

TIP

static-php-cli 在 Windows 上的构建指的是使用 MSVC 构建 PHP,不基于 MinGW、Cygwin、WSL 等环境。

如果你更倾向使用 WSL,请参考在 Linux 上构建的章节。

在安装 Visual Studio 后,选择 C++ 桌面开发的工作负荷后,可能会下载 8GB 左右的编译工具,下载速度取决于你的网络状况。

安装 Git

Git for Windows 可以从 这里 下载并安装 Standalone Installer 64-bit 版本,安装在默认位置(C:\Program Files\Git\)。 如果不想手动下载和安装,你也可以使用 Visual Studio Installer,在单个组件的选择列表中,勾选 Git。

准备 static-php-cli

static-php-cli 项目的下载方式很简单,只需要使用 git clone 即可。推荐将项目放在 C:\spc-build\ 或类似目录,路径最好不要有空格。

shell
mkdir "C:\spc-build"
+cd C:\spc-build
+git clone https://github.com/crazywhalecc/static-php-cli.git
+cd static-php-cli

static-php-cli 自身需要 PHP 环境,是有点奇怪,但现在可以通过脚本快速安装 PHP 环境。 一般你的电脑不会安装 Windows 版本的 PHP,所以我们建议你在下载 static-php-cli 后,直接使用 bin/setup-runtime,在当前目录安装 PHP 和 Composer。

shell
# 安装 PHP 和 Composer 到 ./runtime/ 目录
+bin/setup-runtime
+
+# 安装后,如需在全局命令中使用 PHP 和 Composer,使用下面的命令将 runtime/ 目录添加到 PATH
+bin/setup-runtime -action add-path
+# 删除 PATH 中的 runtime/ 目录
+bin/setup-runtime -action remove-path

在准备好 PHP 和 Composer 环境后,使用 composer 安装 static-php-cli 的依赖:

shell
cd C:\spc-build\static-php-cli
+runtime/composer install --no-dev

自动安装其他依赖

对于 php-sdk-binary-toolsstrawberry-perlnasm,我们更建议你直接使用命令 bin/spc doctor --auto-fix 检查并安装。

如果 doctor 成功自动安装,请跳过下方手动安装上述工具的步骤。

如果自动安装无法成功的话,再参考下方手动安装的方式。

手动安装 php-sdk-binary-tools

bat
cd C:\spc-build\static-php-cli
+git clone https://github.com/php/php-sdk-binary-tools.git

你也可以在 Windows 设置中设置全局变量 PHP_SDK_PATH,并将该项目克隆至变量对应的路径。一般情况下,默认即可。

手动安装 strawberry-perl

如果你不需要编译 openssl 扩展,可不安装 perl。

  1. GitHub 下载 strawberry-perl 最新版。
  2. 安装到 C:\spc-build\static-php-cli\pkgroot\perl\ 目录。

你可以下载 -portable 版本,并直接解压到上述目录。 最后的 perl.exe 应该位于 C:\spc-build\static-php-cli\pkgroot\perl\perl\bin\perl.exe

手动安装 nasm

如果你不需要编译 openssl 扩展,可不安装 nasm。

  1. 官网 下载 nasm 工具(x64)。
  2. nasm.exendisasm.exe 放在 C:\spc-build\static-php-cli\php-sdk-binary-tools\bin\ 目录。

下载源码

本地构建 - download

编译 PHP

使用 build 命令可以开始构建静态 php 二进制,在执行 bin/spc build 命令前,务必先使用 download 命令下载资源,建议使用 doctor 检查环境。

基本用法

你需要先到 扩展列表命令生成器 选择你要加入的扩展,然后使用命令 bin/spc build 进行编译。你需要指定编译目标,从如下参数中选择:

  • --build-cli: 构建一个 cli sapi(命令行界面,可在命令行执行 PHP 代码)
  • --build-micro: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
shell
# 编译 PHP,附带 bcmath,openssl,zlib 扩展,编译目标为 cli
+bin/spc build "bcmath,openssl,zlib" --build-cli
+
+# 编译 PHP,附带 bcmath,openssl,zlib 扩展,编译目标为 micro 和 cli
+bin/spc build "bcmath,openssl,zlib" --build-micro --build-cli

WARNING

在Windows中,最好使用双引号包裹包含逗号的参数,例如 "bcmath,openssl,mbstring"

调试

如果你在编译过程中遇到了问题,或者想查看每个执行的 shell 命令,可以使用 --debug 开启 debug 模式,查看所有终端日志:

shell
bin/spc build "openssl" --build-cli --debug

编译运行选项

在编译过程中,有些特殊情况需要对编译器、编译目录的内容进行干预,可以尝试使用以下命令:

  • --with-clean: 编译 PHP 前先清理旧的 make 产生的文件
  • --enable-zts: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
  • --with-libs=XXX,YYY: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能
  • -I xxx=yyy: 编译前将 INI 选项硬编译到 PHP 内(支持多个选项,别名是 --with-hardcoded-ini
  • --with-micro-fake-cli: 在编译 micro 时,让 micro 的 SAPI 伪装为 cli(用于兼容一些检查 PHP_SAPI 的程序)
  • --disable-opcache-jit: 禁用 opcache jit(默认启用)
  • --without-micro-ext-test: 在构建 micro.sfx 后,禁用测试不同扩展在 micro.sfx 的运行结果
  • --with-suggested-exts: 编译时将 ext-suggests 也作为编译依赖加入
  • --with-suggested-libs: 编译时将 lib-suggests 也作为编译依赖加入
  • --with-upx-pack: 编译后使用 UPX 减小二进制文件体积(需先使用 bin/spc install-pkg upx 安装 upx)
  • --with-micro-logo=XXX.ico: 自定义 micro 构建组合后的 exe 可执行文件的图标(格式为 .ico

有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 memory_limit,并且禁用 system 函数:

shell
bin/spc build "bcmath,openssl" --build-cli -I "memory_limit=4G" -I "disable_functions=system"

另一个例子:自定义 micro 构建后的 exe 程序图标:

shell
bin/spc build "ffi,bcmath" --build-micro --with-micro-logo=mylogo.ico --debug
+bin/spc micro:combine hello.php
+# Then we got `my-app.exe` with custom logo!
+my-app.exe

使用 php.exe

php.exe 编译后位于 buildroot\bin\ 目录,你可以将其拷贝到任意位置使用。

shell
.\php -v

使用 micro

phpmicro 是一个提供自执行二进制 PHP 的项目,本项目依赖 phpmicro 进行编译自执行二进制。详见 dixyes/phpmicro

最后编译结果会输出一个 ./micro.sfx 的文件,此文件需要配合你的 PHP 源码使用。 该文件编译后会存放在 buildroot/bin/ 目录中。

使用时应准备好你的项目源码文件,可以是单个 PHP 文件,也可以是 Phar 文件。

如果要结合 phar 文件,编译时必须包含 phar 扩展!

shell
# code.php "<?php echo 'Hello world' . PHP_EOL;"
+bin/spc micro:combine code.php -O my-app.exe
+# Run it!!! Copy it to another computer!!!
+./my-app.exe

如果打包 PHAR 文件,仅需把 code.php 更换为 phar 文件路径即可。 你可以使用 box-project/box 将你的 CLI 项目打包为 Phar, 然后将它与 phpmicro 结合,生成独立可执行的二进制文件。

有关 micro:combine 命令的更多细节,请参考 Unix 系统上的 命令

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/cli-generator.html b/zh/guide/cli-generator.html new file mode 100644 index 00000000..166d9672 --- /dev/null +++ b/zh/guide/cli-generator.html @@ -0,0 +1,25 @@ + + + + + + CLI 编译命令生成器 | static-php-cli + + + + + + + + + + + + + + +
Skip to content

CLI 编译命令生成器

TIP

下面选择扩展可能包含所选操作系统不支持的扩展,这可能导致编译失败。请先查阅 支持的扩展

选择操作系统

选择扩展

选择常用扩展
全部取消选择
要构建的库

TIP

选择扩展后,不可选中的项目为必需的依赖,编译的依赖库列表中可选的为现有扩展和依赖库的可选依赖。选择可选依赖后,将生成 --with-libs 参数。

选择编译目标

编译选项

编译环境
下载 PHP 版本
是否开启调试输出
是否编译线程安全版
是否展示仅下载对应扩展依赖的命令
是否开启 UPX 压缩(减小二进制体积,但很少见的情况下 micro SAPI 无法使用)

硬编码 INI 选项

结果展示

只下载对应扩展的依赖包命令
bin/spc download --with-php=8.2 --for-extensions ""
编译命令
bin/spc build --build-cli ""

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/env-vars.html b/zh/guide/env-vars.html new file mode 100644 index 00000000..cb15fd19 --- /dev/null +++ b/zh/guide/env-vars.html @@ -0,0 +1,29 @@ + + + + + + 环境变量列表 | static-php-cli + + + + + + + + + + + + + +
Skip to content

环境变量列表

本页面的环境变量列表中所提到的所有环境变量都具有默认值,除非另有说明。你可以通过设置这些环境变量来覆盖默认值。

一般情况下,你不需要修改任何以下环境变量,因为它们已经被设置为最佳值。 但是,如果你有特殊需求,你可以通过设置这些环境变量来满足你的需求(比如你需要调试不同编译参数下的 PHP 性能表现)。

如需使用自定义环境变量,你可以在终端中使用 export 命令或者在命令前直接设置环境变量,例如:

shell
# export 方式
+export SPC_CONCURRENCY=4
+bin/spc build mbstring,pcntl --build-cli
+
+# 直接设置方式
+SPC_CONCURRENCY=4 bin/spc build mbstring,pcntl --build-cli

通用环境变量

通用环境变量是所有构建目标都可以使用的环境变量。

var namedefault valuecomment
BUILD_ROOT_PATH{pwd}/buildroot编译目标的根目录
BUILD_LIB_PATH{pwd}/buildroot/lib编译依赖库的根目录
BUILD_INCLUDE_PATH{pwd}/buildroot/include编译依赖库的头文件目录
BUILD_BIN_PATH{pwd}/buildroot/bin编译依赖库的二进制文件目录
PKG_ROOT_PATH{pwd}/pkgroot闭源或预编译工具下载后安装的目录
SOURCE_PATH{pwd}/source编译项目的源码解压缩目录
DOWNLOAD_PATH{pwd}/downloads下载的文件存放目录
SPC_CONCURRENCY取决于当前 CPU 核心数量并行编译的数量
SPC_SKIP_PHP_VERSION_CHECK设置为 yes 时,跳过扩展对 PHP 版本的检查

系统特定变量

这些环境变量是特定于系统的,它们只在特定的系统上才会生效。

Windows

var namedefault valuecomment
PHP_SDK_PATH{pwd}\php-sdk-binary-toolsPHP SDK 工具的安装目录
UPX_EXEC$PKG_ROOT_PATH\bin\upx.exeUPX 压缩工具的路径

macOS

var namedefault valuecomment
CCclangC 编译器
CXXclang++C++ 编译器
SPC_DEFAULT_C_FLAGS--target=arm64-apple-darwin--target=x86_64-apple-darwin默认 C 编译标志(与 CFLAGS 不同)
SPC_DEFAULT_CXX_FLAGS--target=arm64-apple-darwin--target=x86_64-apple-darwin默认 C++ 编译标志(与 CXXFLAGS 不同)
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --force编译 PHP buildconf 命令前缀
SPC_CMD_PREFIX_PHP_CONFIGURE./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbg编译 PHP configure 命令前缀
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCY编译 PHP make 命令前缀
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGS -Werror=unknown-warning-optionPHP configure 命令的 CFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHPHP configure 命令的 CPPFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHPHP configure 命令的 LDFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os-g -O0(当使用 --no-strip 时为后者)PHP make 命令的 EXTRA_CFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBS-lresolvPHP make 命令的额外 EXTRA_LIBS 变量

Linux

var namedefault valuecomment
UPX_EXEC$PKG_ROOT_PATH/bin/upxUPX 压缩工具的路径
GNU_ARCHx86_64aarch64当前环境的 CPU 架构
CCAlpine: gcc, Other: $GNU_ARCH-linux-musl-gccC 编译器
CXXAlpine: g++, Other: $GNU_ARCH-linux-musl-g++C++ 编译器
ARAlpine: ar, Other: $GNU_ARCH-linux-musl-ar静态库工具
LDld.gold链接器
PATH/usr/local/musl/bin:/usr/local/musl/$GNU_ARCH-linux-musl/bin:$PATH系统 PATH
SPC_DEFAULT_C_FLAGSempty默认 C 编译标志
SPC_DEFAULT_CXX_FLAGSempty默认 C++ 编译标志
SPC_CMD_PREFIX_PHP_BUILDCONF./buildconf --force编译 PHP buildconf 命令前缀
SPC_CMD_PREFIX_PHP_CONFIGURELD_LIBRARY_PATH={ld_lib_path} ./configure --prefix= --with-valgrind=no --enable-shared=no --enable-static=yes --disable-all --disable-cgi --disable-phpdbg编译 PHP configure 命令前缀
SPC_CMD_PREFIX_PHP_MAKEmake -j$SPC_CONCURRENCY编译 PHP make 命令前缀
SPC_CMD_VAR_PHP_CONFIGURE_CFLAGS$SPC_DEFAULT_C_FLAGSPHP configure 命令的 CFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_CPPFLAGS-I$BUILD_INCLUDE_PATHPHP configure 命令的 CPPFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LDFLAGS-L$BUILD_LIB_PATHPHP configure 命令的 LDFLAGS 变量
SPC_CMD_VAR_PHP_CONFIGURE_LIBS-ldl -lpthreadPHP configure 命令的 LIBS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_CFLAGS-g0 -Os -fno-ident -fPIE-g -O0 -fno-ident -fPIE(当使用 --no-strip 时为后者)PHP make 命令的 EXTRA_CFLAGS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LIBSemptyPHP make 命令的额外 EXTRA_LIBS 变量
SPC_CMD_VAR_PHP_MAKE_EXTRA_LDFLAGS_PROGRAM-all-static(当使用 clang 时:-Xcompiler -fuse-ld=lld -all-staticmake 命令的额外 LDFLAGS 变量(用于编译程序)
SPC_NO_MUSL_PATHempty是否不插入 musl 工具链的 PATH(值为 yes 时不插入)

{ld_lib_path} 值为 /usr/local/musl/$GNU_ARCH-linux-musl/lib

FreeBSD

因 FreeBSD 系统的用户较少,我们暂时不提供 FreeBSD 系统的环境变量。

Unix

对于 macOS、Linux、FreeBSD 等 Unix 系统,以下环境变量是通用的。

var namedefault valuecomment
PATH$BUILD_BIN_PATH:$PATH系统 PATH
PKG_CONFIG_PATH$BUILD_LIB_PATH/pkgconfigpkg-config 的搜索路径
PKG_CONFIG$BUILD_BIN_PATH/pkg-configpkg-config 命令路径

编译依赖库的环境变量(仅限 Unix 系统)

从 2.2.0 开始,static-php-cli 对所有 macOS、Linux、FreeBSD 等 Unix 系统的编译依赖库的命令均支持自定义环境变量。

这样你就可以随时通过环境变量来调整编译依赖库的行为。例如你可以通过 xxx_CFLAGS=-O0 来设置编译 xxx 库的优化参数。

当然,不是每个依赖库都支持注入环境变量,我们目前提供了三个通配的环境变量,后缀分别为:

  • _CFLAGS: C 编译器的参数
  • _LDFLAGS: 链接器的参数
  • _LIBS: 额外的链接库

前缀为依赖库的名称,具体依赖库的名称以 lib.json 为准。其中,带有 - 的依赖库名称需要将 - 替换为 _

下面是一个替换 openssl 库编译的优化选项示例:

shell
openssl_CFLAGS="-O0"

库名称使用同 lib.json 中列举的名称,区分大小写。

TIP

当未指定相关环境变量时,除以下变量外,其余值均默认为空:

var namevar default value
pkg_config_CFLAGSmacOS: $SPC_DEFAULT_C_FLAGS -Wimplicit-function-declaration -Wno-int-conversion, Other: empty
pkg_config_LDFLAGSLinux: --static, Other: empty
imagemagick_LDFLAGSLinux: -static, Other: empty
imagemagick_LIBSmacOS: -liconv, Other: empty
ldap_LDFLAGS-L$BUILD_LIB_PATH
openssl_CFLAGSLinux: $SPC_DEFAULT_C_FLAGS, Other: empty
others...empty

下表是支持自定义以上三种变量的依赖库名称列表:

lib name
brotli
bzip
curl
freetype
gettext
gmp
imagemagick
ldap
libargon2
libavif
libcares
libevent
openssl

TIP

因为给每个库适配自定义环境变量是一项特别繁琐的工作,且大部分情况下你都不需要这些库的自定义环境变量,所以我们目前只支持了部分库的自定义环境变量。

如果你需要自定义环境变量的库不在上方列表,可以通过 GitHub Issue 来提出需求。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/extension-notes.html b/zh/guide/extension-notes.html new file mode 100644 index 00000000..dd6d03c2 --- /dev/null +++ b/zh/guide/extension-notes.html @@ -0,0 +1,24 @@ + + + + + + 扩展注意事项 | static-php-cli + + + + + + + + + + + + + +
Skip to content

扩展注意事项

因为是静态编译,扩展不会 100% 完美编译,而且不同扩展对 PHP、环境都有不同的要求,这里将一一列举。

curl

使用 curl 请求 HTTPS 时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

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 扩展,启用 swooleswoole-hook-pgsql 即可。 该扩展包含了 pdo_pgsql 的协程环境的实现。

在 macOS 系统,pdo_pgsql 可能无法正常连接到 postgresql 服务器,请谨慎使用。

swoole-hook-mysql

swoole-hook-mysql 不是一个扩展,而是 Swoole 的 Hook 特性。 如果你在编译时添加了 swoole,swoole-hook-mysql,你将启用 Swoole 的 mysqlndpdo_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 扩展,启用 swooleswoole-hook-sqlite 即可。 该扩展包含了 pdo_sqlite 的协程环境的实现。

swow

  1. swow 仅支持 PHP >= 8.0 版本。

imap

  1. 该扩展目前不支持 Kerberos。
  2. 由于底层的 c-client、ext-imap 不是线程安全的。 无法在 --enable-zts 构建中使用它。
  3. 由于该扩展可能会从未来的 PHP 中删除,因此我们建议您寻找替代实现,例如 Webklex/php-imap

gd

  1. gd 扩展依赖了较多的额外图形库,默认情况下,直接使用 bin/spc build gd 不会引入和支持部分图形库,例如 libjpeglibavif 等, 需要使用 --with-libs 参数补全。目前支持 freetype,libjpeg,libavif,libwebp 四个库的支持,所以这里可以使用以下命令来让 gd 库引入它们:
bash
bin/spc build gd --with-libs=freetype,libjpeg,libavif,libwebp --build-cli

mcrypt

  1. 目前未支持,未来也不计划支持此扩展。#32

oci8

  1. oci8 是 Oracle 数据库的扩展,因为 Oracle 提供的扩展所依赖的库未提供静态编译版本(.a)或源代码,无法使用静态链接的方式将此扩展编译到 php 内,故无法支持。

xdebug

  1. Xdebug 是一个 Zend 扩展,Xdebug 的功能依赖于 PHP 的 Zend 引擎和底层代码,如果要将其静态编译到 PHP 中,可能需要巨量的 patch 代码,这是不可行的。
  2. macOS 平台可以通过在相同平台编译的 PHP 下编译一个 xdebug 扩展,并提取其中的 xdebug.so 文件,再在 static-php-cli 中使用 --no-strip 参数保留调试符号表,同时加入 ffi 扩展。 编译的 ./php 二进制可以通过指定 INI 配置并运行,例如./php -d 'zend_extension=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 不兼容。相关链接:

pgsql 16.2 修复了这个 Bug,现在正常工作了。

在 pgsql 使用 SSL 连接时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

openssl

使用基于 openssl 的扩展(如 curl、pgsql 等网络库)时,可能存在 error:80000002:system library::No such file or directory 错误, 解决办法详见 FAQ - 无法使用 ssl

password-argon2

  1. password-argon2不是一个标准的扩展,它是 password_hash 函数的额外算法。
  2. 在Linux系统,password-argon2 的依赖库 libargon2libsodium 库冲突。

ffi

  1. 因为 Linux 系统的限制,虽然可以成功编译 ffi 扩展,但无法使用它加载其他 so 扩展。Linux 支持加载 so 扩展的前提是非静态编译,但动态编译和本项目的目的冲突。
  2. macOS 支持 ffi 扩展,但是部分内核下不包含调试符号时会出现错误。
  3. Windows 支持 ffi 扩展。

xhprof

xhprof 扩展包含三部分:xhprof_extensionxhprof_htmlxhprof_libs。编译的二进制中只包含 xhprof_extension。 如果需要使用 xhprof,请到 pecl.php.net/package/xhprof 下载源码,指定 xhprof_libsxhprof_html 路径来使用。

event

event 扩展在 macOS 系统下编译后暂无法使用 openpty 特性。相关 Issue:

parallel

parallel 扩展只支持 PHP 8.0 及以上版本,并只支持 ZTS 构建(--enable-zts)。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/extensions.html b/zh/guide/extensions.html new file mode 100644 index 00000000..def47080 --- /dev/null +++ b/zh/guide/extensions.html @@ -0,0 +1,24 @@ + + + + + + 扩展列表 | static-php-cli + + + + + + + + + + + + + +
Skip to content

扩展列表

  • yes: 已支持
  • 空白: 目前还不支持,或正在支持中
  • no with issue link: 确定不支持或无法支持
  • partial with issue link: 已支持,但是无法完美工作
Extension NameLinuxmacOSFreeBSDWindows
amqpyesyesyes
apcuyesyesyesyes
bcmathyesyesyesyes
bz2yesyesyesyes
calendaryesyesyesyes
ctypeyesyesyesyes
curlyesyesyesyes
dbayesyesyesyes
domyesyesyes
dsyesyesyesyes
enchant
eventyesyes
exifyesyesyesyes
ffinoyesyes
fileinfoyesyesyesyes
filteryesyesyesyes
ftpyesyesyesyes
gdyesyesyes
gettextyesyes
glfwnoyesno
gmpyesyes
iconvyesyesyes
igbinaryyesyes
imagickyesyes
imapyesyes
inotifyyesnono
intlyesyesno
ldapyesyes
libxmlyesyesyes
mbregexyesyesyesyes
mbstringyesyesyesyes
mcryptnononono
memcacheyesyes
memcachednoyes
mongodbyesyes
mysqliyesyesyesyes
mysqlndyesyesyesyes
oci8nonono
opcacheyesyesyesyes
opensslyesyesyesyes
parallelyesyesyes
password-argon2yesyes
pcntlyesyesyesno
pdoyesyesyesyes
pdo_mysqlyesyesyesyes
pdo_pgsqlyesyes
pdo_sqliteyesyesyes
pdo_sqlsrvyesyesyes
pgsqlyesyes
pharyesyesyesyes
posixyesyesyesno
protobufyesyes
raryespartialyes
readlineyesyes
redisyesyes
sessionyesyesyesyes
shmopyesyesyesyes
simdjsonyesyesyesyes
simplexmlyesyesyes
snappyyesyes
soapyesyesyes
socketsyesyesyesyes
sodiumyesyes
sqlite3yesyesyes
sqlsrvyesyesyes
ssh2yesyesyes
swooleyesyesno
swoole-hook-mysqlyesyesno
swoole-hook-pgsqlyespartialno
swoole-hook-sqliteyesyesno
swowyesyesyes
sysvmsgyesyesno
sysvsemyesyesno
sysvshmyesyesyes
tidyyesyes
tokenizeryesyesyesyes
uuidyesyes
uvyesyes
xdebugnonono
xhprofyesyes
xlswriteryesyes
xmlyesyesyes
xmlreaderyesyesyes
xmlwriteryesyesyes
xslyesyes
yacyesyesyes
yamlyesyesyes
zipyesyesyes
zlibyesyesyesyes
zstdyesyes

TIP

如果缺少您需要的扩展,您可以创建 功能请求

有些扩展或扩展依赖的库会有一些可选的特性,例如 gd 库可选支持 libwebp、freetype 等。 如果你只使用 bin/spc build gd --build-cli 是不会包含它们(static-php-cli 默认为最小依赖原则)。

你可以在编译时使用 --with-libs= 加入这些库,当本次编译的依赖库中包含它们,gd 会自动依赖它们启用这些特性。 (如:bin/spc build gd --with-libs=libwebp,freetype --build-cli

或者你也可以使用 --with-suggested-exts--with-suggested-libs 启用这些扩展和库所有可选的依赖。 (如:bin/spc build gd --with-suggested-libs --build-cli

如果你不知道某个扩展是否有可选特性,可以通过查看 spc 配置文件 或使用命令 bin/spc dev:extensions 查看(库依赖为 lib-suggests,扩展依赖为 ext-suggests)。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/index.html b/zh/guide/index.html new file mode 100644 index 00000000..bd9a50eb --- /dev/null +++ b/zh/guide/index.html @@ -0,0 +1,24 @@ + + + + + + 指南 | static-php-cli + + + + + + + + + + + + + +
Skip to content

指南

static-php-cli 是一个用于构建静态编译的 PHP 二进制的工具,目前支持 Linux 和 macOS 系统。

在指南章节中,你将了解到如何使用 static-php-cli 构建独立的 php 程序。

编译环境

下面是架构支持情况,⚙️ 代表支持 GitHub Action 构建,💻 代表支持本地构建,空 代表暂不支持。

x86_64aarch64
macOS⚙️ 💻⚙️ 💻
Linux⚙️ 💻⚙️ 💻
Windows⚙️ 💻
FreeBSD💻💻

其中,Linux 目前仅在 Ubuntu、Debian、Alpine 发行版测试通过,其他发行版未进行测试,不能保证编译成功。 对于未经过测试的发行版,可以使用 Docker 等方式本地编译,避免环境导致的问题。

macOS 下支持 x86_64 和 Arm 两种架构,但在其中一个架构上编译的二进制无法直接在另一个架构上使用。 Rosetta 2 不能保证 Arm 架构编译的程序可以完全运行在 x86_64 环境下。

Windows 目前只支持 x86_64 架构,不支持 32 位 x86、不支持 arm64 架构。

PHP 支持版本

目前,static-php-cli 对 PHP 7.4 ~ 8.3 版本是支持的,对于 PHP 7.4 及更早版本理论上支持,只需下载时选择早期版本即可。 但由于部分扩展和特殊组件已对早期版本的 PHP 停止了支持,所以 static-php-cli 不会明确支持早期版本。 我们推荐你编译尽可能新的 PHP 版本,以获得更好的体验。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/manual-build.html b/zh/guide/manual-build.html new file mode 100644 index 00000000..3d8b6d8c --- /dev/null +++ b/zh/guide/manual-build.html @@ -0,0 +1,157 @@ + + + + + + 本地构建(Linux、macOS、FreeBSD) | static-php-cli + + + + + + + + + + + + + +
Skip to content

本地构建(Linux、macOS、FreeBSD)

本章节为 Linux、macOS、FreeBSD 的构建过程,如果你要在 Windows 上构建,请到 在 Windows 上构建

手动构建(使用 SPC 二进制)(推荐)

本项目提供了一个 static-php-cli 的二进制文件,你可以直接下载对应平台的二进制文件,然后使用它来构建静态的 PHP。目前 spc 二进制支持的平台有 Linux 和 macOS。

使用以下命令从自托管服务器下载:

bash
# Download from self-hosted nightly builds (sync with main branch)
+# For Linux x86_64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-x86_64
+# For Linux aarch64
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-linux-aarch64
+# macOS x86_64 (Intel)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-x86_64
+# macOS aarch64 (Apple)
+curl -o spc https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-macos-aarch64
+# Windows (x86_64, win10 build 17063 or later)
+curl.exe -o spc.exe https://dl.static-php.dev/static-php-cli/spc-bin/nightly/spc-windows-x64.exe
+
+# Add execute perm (Linux and macOS only)
+chmod +x ./spc
+
+# Run (Linux and macOS)
+./spc --version
+# Run (Windows powershell)
+.\spc.exe --version

如果你使用的是打包好的 spc 二进制,你需要将下面所有命令中 bin/spc 开头替换为 ./spc

手动构建(使用源码)

如果使用 spc 二进制出现问题,或你有修改 static-php-cli 源码需求,请从源码下载 static-php-cli。

目前支持在 macOS、Linux 上构建,macOS 支持最新版操作系统和两种架构,Linux 支持 Debian、RHEL 及衍生发行版、Alpine Linux 等。

因为本项目本身采用 PHP 开发,所以在编译时也需要系统安装 PHP。本项目本身也提供了适用于本项目的静态二进制 php,可以根据实际情况自行选择使用。

下载本项目

bash
git clone https://github.com/crazywhalecc/static-php-cli.git --depth=1
+cd static-php-cli
+
+# 你需要先安装 PHP 环境后再运行 Composer 和本项目,安装方式可参考下面。
+composer update

使用系统 PHP 环境

下面是系统安装 PHP、Composer 的一些示例命令。具体安装方式建议自行搜索或询问 AI 搜索引擎获取答案,这里不多赘述。

bash
# [macOS], 需要先安装 Homebrew. See https://brew.sh/
+# Remember change your composer executable path. For M1/M2 Chip mac, "/opt/homebrew/bin/", for Intel mac, "/usr/local/bin/". Or add it to your own path.
+brew install php wget
+wget https://getcomposer.org/download/latest-stable/composer.phar -O /path/to/your/bin/composer && chmod +x /path/to/your/bin/composer
+
+# [Debian], you need to make sure your php version >= 8.1 and composer >= 2.0
+sudo apt install php-cli composer php-tokenizer
+
+# [Alpine]
+apk add bash file wget xz php81 php81-common php81-pcntl php81-tokenizer php81-phar php81-posix php81-xml composer

TIP

目前 Ubuntu 部分版本的 apt 安装的 php 版本较旧,故不提供安装命令。如有需要,建议先添加 ppa 等软件源后,安装最新版的 PHP 以及 tokenizer、xml、phar 扩展。

较老版本的 Debian 默认安装的可能为旧版本(<= 7.4)版本的 PHP,建议先升级 Debian。

使用 Docker 环境

如果你不愿意在系统安装 PHP 和 Composer 运行环境,可以使用内置的 Docker 环境构建脚本。

bash
# 直接使用,将所有使用的命令中 `bin/spc` 替换为 `bin/spc-alpine-docker` 即可
+bin/spc-alpine-docker

首次执行命令会使用 docker build 构建一个 Docker 镜像,默认构建的 Docker 镜像为 x86_64 架构,镜像名称为 cwcc-spc-x86_64

如果你想在 x86_64 环境下构建 aarch64 的 static-php-cli,可以使用 qemu 模拟 arm 镜像运行 Docker,但速度会非常慢。使用参数:SPC_USE_ARCH=aarch64 bin/spc-alpine-docker

如果运行后提示需要 sudo 才能运行,执行一次以下命令可授予 static-php-cli 执行 sudo 的权限:

bash
export SPC_USE_SUDO=yes

使用预编译静态 PHP 二进制

如果你不想使用 Docker、在系统内安装 PHP,可以直接下载本项目自身编译好的 php 二进制 cli 程序。使用流程如下:

使用命令部署环境,此脚本会从 自托管的服务器 下载一个当前操作系统的 php-cli 包, 并从 getcomposerAliyun(镜像) 下载 Composer。

TIP

使用预编译静态 PHP 二进制目前仅支持 Linux 和 macOS。FreeBSD 环境因为缺少自动化构建环境,所以暂不支持。

bash
bin/setup-runtime
+
+# 对于中国大陆地区等网络环境特殊的用户,可使用镜像站加快下载速度
+bin/setup-runtime --mirror china

此脚本总共会下载两个文件:bin/phpbin/composer,下载完成后,有两种使用方式:

  1. bin/ 目录添加到 PATH 路径中:export PATH="/path/to/your/static-php-cli/bin:$PATH",添加路径后,相当于系统安装了 PHP,可直接使用 composerphp -v 等命令,也可以直接使用 bin/spc
  2. 直接调用,比如执行 static-php-cli 命令:bin/php bin/spc --help,执行 Composer:bin/php bin/composer update

命令 download - 下载依赖包

使用命令 bin/spc download 可以下载编译需要的源代码,包括 php-src 以及依赖的各种库的源码。

bash
# 仅下载要编译的扩展及依赖库(使用扩展名,包含可选库)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl,zstd
+
+# 仅下载要编译的扩展及依赖库(使用扩展名,不包含可选库)
+bin/spc download --for-extensions=openssl,swoole,zip,pcntl --without-suggestions
+
+# 仅下载要编译的库(包括其依赖,使用库名,包含可选库,可以和 --for-extensions 组合使用)
+bin/spc download --for-libs=liblz4,libevent --for-extensions=pcntl,rar,xml
+
+# 仅下载要编译的库(包括其依赖,使用库名,不包含可选库)
+bin/spc download --for-libs=liblz4,libevent --without-suggestions
+
+# 下载资源时,忽略部分资源的缓存,强制下载(如切换 PHP 版本)
+bin/spc download --for-extensions=curl,pcntl,xml --ignore-cache-sources=php-src --with-php=8.3
+
+# 下载所有依赖包
+bin/spc download --all
+
+# 下载所有依赖包,并指定下载的 PHP 主版本,可选:7.3,7.4,8.0,8.1,8.2,8.3。
+bin/spc download --all --with-php=8.2
+
+# 下载时显示下载进度条(curl)
+bin/spc download --all --debug
+
+# 删除旧的下载数据
+bin/spc download --clean
+
+# 仅下载指定的资源(使用资源名)
+bin/spc download php-src,micro,zstd,ext-zstd
+
+# 设置重试次数
+bin/spc download --all --retry=2

如果你所在地区的网络不好,或者下载依赖包速度过于缓慢,可以从 GitHub Action 下载每周定时打包的 download.zip,并使用命令直接使用 zip 压缩包作为依赖。 依赖包可以从 Action 下载到本地。 进入 Action 并选择一个最新成功运行的 Workflow,下载 download-files-x.y 即可。

bash
bin/spc download --from-zip=/path/to/your/download.zip

如果某个 source 始终无法下载,或者你需要下载一些特定版本的包,例如下载测试版 PHP、旧版本库等,可以使用参数 -U--custom-url 重写下载链接, 让下载器强制使用你指定的链接下载此 source 的包。使用方法为 {source-name}:{url} 即可,可同时重写多个库的下载地址。在使用 --for-extensions 选项下载时同样可用。

bash
# 例如:指定下载测试版的 PHP8.3
+bin/spc download --all -U "php-src:https://downloads.php.net/~eric/php-8.3.0beta1.tar.gz"
+
+# 指定下载旧版本的 curl 库
+bin/spc download --all -U "curl:https://curl.se/download/curl-7.88.1.tar.gz"

命令 doctor - 环境检查

如果你可以正常运行 bin/spc 但无法正常编译静态的 PHP 或依赖库,可以先运行 bin/spc doctor 检查系统自身是否缺少依赖。

bash
# 快速检查
+bin/spc doctor
+
+# 快速检查,并在可以自动修复的时候修复(使用包管理安装依赖包,仅支持上述提到的操作系统及发行版)
+bin/spc doctor --auto-fix

命令 build - 编译 PHP

使用 build 命令可以开始构建静态 php 二进制,在执行 bin/spc build 命令前,务必先使用 download 命令下载资源,建议使用 doctor 检查环境。

基本用法

你需要先到 扩展列表命令生成器 选择你要加入的扩展,然后使用命令 bin/spc build 进行编译。你需要指定一个编译目标,从如下参数中选择:

  • --build-cli: 构建一个 cli sapi(命令行界面,可在命令行执行 PHP 代码)
  • --build-fpm: 构建一个 fpm sapi(php-fpm,用于和其他传统的 fpm 架构的软件如 nginx 配合使用)
  • --build-micro: 构建一个 micro sapi(用于构建一个包含 PHP 代码的独立可执行二进制)
  • --build-embed: 构建一个 embed sapi(用于嵌入到其他 C 语言程序中)
  • --build-all: 构建以上所有 sapi
bash
# 编译 PHP,附带 bcmath,curl,openssl,ftp,posix,pcntl 扩展,编译目标为 cli
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+
+# 编译 PHP,附带 phar,curl,posix,pcntl,tokenizer 扩展,编译目标为 micro
+bin/spc build phar,curl,posix,pcntl,tokenizer --build-micro

TIP

如果你需要重复构建、调试,你可以删除 buildroot/source/ 两个目录,这样你可以从已下载的源码压缩包重新解压并构建:

shell
# remove
+rm -rf buildroot source
+# build again
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

TIP

如果你想构建多个版本的 PHP,且不想每次都重复构建其他依赖库,可以使用 switch-php-version 在编译好一个版本后快速切换至另一个版本并编译:

shell
# switch to 8.3
+bin/spc switch-php-version 8.3
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli
+# switch to 8.0
+bin/spc switch-php-version 8.0
+# build
+bin/spc build bcmath,curl,openssl,ftp,posix,pcntl --build-cli

调试

如果你在编译过程中遇到了问题,或者想查看每个执行的 shell 命令,可以使用 --debug 开启 debug 模式,查看所有终端日志:

bash
bin/spc build mysqlnd,pdo_mysql --build-all --debug

编译运行选项

在编译过程中,有些特殊情况需要对编译器、编译目录的内容进行干预,可以尝试使用以下命令:

  • --cc=XXX: 指定 C 语言编译器的执行命令(Linux 默认 musl-gccgcc,macOS 默认 clang
  • --cxx=XXX: 指定 C++ 语言编译器的执行命令(Linux 默认 g++,macOS 默认 clang++
  • --with-clean: 编译 PHP 前先清理旧的 make 产生的文件
  • --enable-zts: 让编译的 PHP 为线程安全版本(默认为 NTS 版本)
  • --no-strip: 编译 PHP 库后不运行 strip 裁剪二进制文件缩小体积(不裁剪的 macOS 二进制文件可使用动态链接的第三方扩展)
  • --with-libs=XXX,YYY: 编译 PHP 前先编译指定的依赖库,激活部分扩展的可选功能(例如 gd 库的 libavif 等)
  • -I xxx=yyy: 编译前将 INI 选项硬编译到 PHP 内(支持多个选项,别名是 --with-hardcoded-ini
  • --with-micro-fake-cli: 在编译 micro 时,让 micro 的 SAPI 伪装为 cli(用于兼容一些检查 PHP_SAPI 的程序)
  • --disable-opcache-jit: 禁用 opcache jit(默认启用)
  • -P xxx.php: 在 static-php-cli 编译过程中注入外部脚本(详见下方 注入外部脚本
  • --without-micro-ext-test: 在构建 micro.sfx 后,禁用测试不同扩展在 micro.sfx 的运行结果
  • --with-suggested-exts: 编译时将 ext-suggests 也作为编译依赖加入
  • --with-suggested-libs: 编译时将 lib-suggests 也作为编译依赖加入
  • --with-upx-pack: 编译后使用 UPX 减小二进制文件体积(需先使用 bin/spc install-pkg upx 安装 upx)

硬编码 INI 选项适用于 cli、micro、embed。有关硬编码 INI 选项,下面是一个简单的例子,我们预设一个更大的 memory_limit,并且禁用 system 函数:

bash
bin/spc build bcmath,pcntl,posix --build-all -I "memory_limit=4G" -I "disable_functions=system"

命令 micro:combine - 打包 micro 二进制

使用 micro:combine 命令可以将上面编译好的 micro.sfx 和你的代码(.php.phar 文件)构建为一个可执行二进制。 你也可以使用该命令直接构建一个注入了 ini 配置的 micro 自执行二进制文件。

TIP

注入 ini 配置指的是,在将 micro.sfx 和 PHP 源码结合前,在 micro.sfx 后追加一段特殊的结构用于保存 ini 配置项。

micro.sfx 可通过特殊的字节来标识 INI 文件头,通过 INI 文件头可以实现 micro 带 INI 启动。

此特性的原说明地址在 phpmicro - Wiki,这个特性也有可能在未来发生变化。

下面是常规用法,直接打包 php 源码到一个文件中:

bash
# 在做打包流程前,你应该先使用 `build --build-micro` 编译好 micro.sfx
+echo "<?php echo 'hello';" > a.php
+bin/spc micro:combine a.php
+
+# 使用
+./my-app

你可以使用以下参数指定要输出的文件名,你也可以指定其他路径的 micro.sfx 进行打包。

bash
# 指定输出文件名
+bin/spc micro:combine a.php --output=custom-bin
+# 使用绝对路径,也可以使用简化参数名
+bin/spc micro:combine a.php -O /tmp/my-custom-app
+
+# 指定其他位置的 micro.sfx 进行打包
+bin/spc micro:combine a.app --with-micro=/path/to/your/micro.sfx

如果想注入 ini 配置项,可以使用下面的参数,从文件或命令行选项添加 ini 到可执行文件中。

bash
# 使用命令行选项指定(-I 是 --with-ini-set 的简写)
+bin/spc micro:combine a.php -I "a=b" -I "foo=bar"
+
+# 使用 ini 文件指定(-N 是 --with-ini-file 的简写)
+bin/spc micro:combine a.php -N /path/to/your/custom.ini

WARNING

注意,请不要直接使用 PHP 源码或系统安装的 PHP 中的 php.ini 文件,最好手动编写一个自己需要的参数配置文件,例如:

ini
; custom.ini
+curl.cainfo=/path/to/your/cafile.pem
+memory_limit=1G

该命令的注入 ini 是通过在 micro.sfx 后追加一段特殊的结构来实现的,和编译时插入硬编码 INI 的功能不同。

如果要打包 phar,只需要将 a.php 替换为打包好的 phar 文件即可。但要注意,phar 下的 micro.sfx 需要额外注意路径问题,见 Developing - Phar 路径问题

命令 extract - 手动解压某个库

使用命令 bin/spc extract 可以解包和拷贝编译需要的源代码,包括 php-src 以及依赖的各种库的源码(需要自己指定要解包的库名)。

例如,我们在下载好资源后,想分布执行构建流程,手动解包和拷贝包到指定位置,可以使用命令。

bash
# 解压 php-src 和 libxml2 的下载压缩包,解压的源码存放在 source 目录
+bin/spc extract php-src,libxml2

调试命令 dev - 调试命令集合

调试命令指的是你在使用 static-php-cli 构建 PHP 或改造、增强 static-php-cli 项目本身的时候,可以辅助输出一些信息的命令集合。

  • dev:extensions: 输出目前所有支持的扩展信息,或者输出指定的扩展信息
  • dev:php-version: 输出当前编译的 PHP 版本(通过读取 php_version.h 实现)
  • dev:sort-config: 对 config/ 目录下的配置文件的列表按照字母表排序
  • dev:lib-ver <lib-name>: 从依赖库的源码中读取版本(仅特定依赖库可用)
  • dev:ext-ver <ext-name>: 从扩展的源码中读取对应版本(仅特定扩展可用)
bash
# 输出所有扩展
+bin/spc dev:extensions
+
+# 输出指定扩展的信息
+bin/spc dev:extensions mongodb,curl,openssl
+
+# 输出指定列,可选:lib-depends, lib-suggests, ext-depends, ext-suggests, unix-only, type
+bin/spc dev:extensions --columns=lib-depends,type,ext-depends
+
+# 输出当前编译的 PHP 版本(需要先将下载好的 PHP 源码解压到 source 目录,你可以使用 `bin/spc extract php-src` 单独解压缩源码)
+bin/spc dev:php-version
+
+# 排序配置文件 ext.json(也可以排序 lib、source)
+bin/spc dev:sort-config ext

命令 install-pkg - 下载二进制包

使用命令 bin/spc install-pkg 可以下载一些预编译或闭源的工具,并将其安装到 pkgroot 目录中。

bin/spc doctor 自动修复 Windows 环境时会下载 nasm、perl 等工具,使用的也是 install-pkg 的安装过程。

下面是安装工具的示例:

  • 下载安装 UPX(仅限 Linux 和 Windows): bin/spc install-pkg upx

命令 del-download - 删除已下载的资源

一些情况下,你需要删除单个或多个指定的下载源文件,并重新下载他们,例如切换 PHP 版本,2.1.0-beta.4 版本后提供了 bin/spc del-download 命令,可以删除指定源文件。

删除已下载的源文件包含预编译的包以及源代码,名称是 source.jsonpkg.json 中的键名。下面是一些例子:

  • 删除 PHP 8.2 源码并切换下载为 8.3 版本: bin/spc del-download php-src && bin/spc download php-src --with-php=8.3
  • 删除 redis 扩展的下载文件: bin/spc del-download redis
  • 删除下载好的 musl-toolchain x86_64: bin/spc del-download musl-toolchain-x86_64-linux

注入外部脚本

注入外部脚本指的是在 static-php-cli 编译过程中插入一个或多个脚本,用于更灵活地支持不同环境下的参数修改、源代码补丁。

一般情况下,该功能主要解决使用 spc 二进制进行编译时无法通过修改 static-php-cli 代码来实现修改补丁的功能。 还有一种情况:你的项目直接依赖了 crazywhalecc/static-php-cli 仓库并同步,但因为项目特性需要做出一些专有的修改,而这些特性并不适合合并到主分支。

鉴于以上情况,在 2.0.1 正式版本中,static-php-cli 加入了多个事件的触发点,你可以通过编写外部的 xx.php 脚本,并通过命令行参数 -P 传入并执行。

在编写注入外部脚本时,你一定会用到的方法是 builder()patch_point()。其中,patch_point() 获取的是当前正在执行的事件名称,builder() 获取的是 BuilderBase 对象。

因为传入的注入点不区分事件,所以你必须将你要执行的代码写在 if(patch_point() === 'your_event_name') 中,否则会重复在其他事件中执行。

下面是支持的 patch_point 事件名称及对应位置:

事件名称事件描述
before-libs-extract在编译的依赖库解压前触发
after-libs-extract在编译的依赖库解压后触发
before-php-extract在 PHP 源码解压前触发
after-php-extract在 PHP 源码解压后触发
before-micro-extract在 phpmicro 解压前触发
after-micro-extract在 phpmicro 解压后触发
before-exts-extract在要编译的扩展解压到 PHP 源码目录前触发
after-exts-extract在要编译的扩展解压到 PHP 源码目录后触发
before-library[name]-build在名称为 name 的库编译前触发(如 before-library[postgresql]-build
after-library[name]-build在名称为 name 的库编译后触发
before-php-buildconf在编译 PHP 命令 ./buildconf 前触发
before-php-configure在编译 PHP 命令 ./configure 前触发
before-php-make在编译 PHP 命令 make 前触发
before-sanity-check在编译 PHP 后,运行扩展检查前触发

下面是一个简单的临时修改 PHP 源码的例子,开启 CLI 下在当前工作目录查找 php.ini 配置的功能:

php
// a.php
+<?php
+if (patch_point() === 'before-php-buildconf') {
+    // replace php source code
+    \SPC\store\FileSystem::replaceFileStr(
+        SOURCE_PATH . '/php-src/sapi/cli/php_cli.c',
+        'sapi_module->php_ini_ignore_cwd = 1;',
+        'sapi_module->php_ini_ignore_cwd = 0;'
+    );
+}
bash
bin/spc build mbstring --build-cli -P a.php
+echo 'memory_limit=8G' > ./php.ini
$ buildroot/bin/php -i | grep Loaded
+Loaded Configuration File => /Users/jerry/project/git-project/static-php-cli/php.ini
+
+$ buildroot/bin/php -i | grep memory
+memory_limit => 8G => 8G

对于 static-php-cli 支持的对象、方法及接口,可以阅读源码,大部分的方法和对象都有相应的注释。

一般使用 -P 功能常用的对象及函数有:

  • SPC\store\FileSystem: 文件管理类
    • ::replaceFileStr(string $filename, string $search, $replace): 替换文件字符串内容
    • ::replaceFileStr(string $filename, string $pattern, $replace): 正则替换文件内容
    • ::replaceFileUser(string $filename, $callback): 用户自定义函数替换文件内容
    • ::copyDir(string $from, string $to): 递归拷贝某个目录到另一个位置
    • ::convertPath(string $path): 转换路径的分隔符为当前系统分隔符
    • ::scanDirFiles(string $dir, bool $recursive = true, bool|string $relative = false, bool $include_dir = false): 遍历目录文件
  • SPC\builder\BuilderBase: 构建对象
    • ->getPatchPoint(): 获取当前的注入点名称
    • ->getOption(string $key, $default = null): 获取命令行和编译时的选项
    • ->getPHPVersionID(): 获取当前编译的 PHP 版本 ID
    • ->getPHPVersion(): 获取当前编译的 PHP 版本号
    • ->setOption(string $key, $value): 设定选项
    • ->setOptionIfNotExists(string $key, $value): 如果选项不存在则设定选项

TIP

static-php-cli 开放的方法非常多,文档中无法一一列举,但只要是 public function 并且不被标注为 @internal,均可调用。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/guide/troubleshooting.html b/zh/guide/troubleshooting.html new file mode 100644 index 00000000..79e5c3e2 --- /dev/null +++ b/zh/guide/troubleshooting.html @@ -0,0 +1,24 @@ + + + + + + 故障排除 | static-php-cli + + + + + + + + + + + + + +
Skip to content

故障排除

使用 static-php-cli 过程中可能会碰到各种各样的故障,这里将讲述如何自行查看错误并反馈 Issue。

下载失败问题

下载资源问题是 spc 最常见的问题之一。主要是由于 spc 下载资源使用的地址一般均为对应项目的官方网站或 GitHub 等,而这些网站可能偶尔会宕机、屏蔽 IP 地址。 目前 2.0.0 版本还没有加入自动重试机制,所以在遇到下载失败后,可以多次尝试调用下载命令。如果确认地址确实无法正常访问,可以提交 Issue 或 PR 更新地址。

doctor 无法修复

在绝大部分情况下,doctor 模块都可以对缺失的系统环境进行自动修复和安装,但也存在特殊的环境无法正常使用自动修复功能。

部分项目由于系统局限(如 Windows 下无法自动安装 Visual Studio 等软件),无法使用自动修复功能。 在遇到无法自动修复功能时,如果遇到 Some check items can not be fixed 字样,则表明无法自动修复,请根据终端显示的方法提交 Issue 或自行修复环境。

编译错误

遇到编译错误时,如果没有开启 --debug 日志,请先开启调试日志,然后确定报错的命令。 报错的终端输出对于修复编译错误非常重要,请在提交 Issue 时一并将终端日志的最后报错片段(或整个终端日志输出)上传,并且包含使用的 spc 命令和参数。

Released under the MIT License.

+ + + + \ No newline at end of file diff --git a/zh/index.html b/zh/index.html new file mode 100644 index 00000000..f8336a34 --- /dev/null +++ b/zh/index.html @@ -0,0 +1,24 @@ + + + + + + static-php-cli + + + + + + + + + + + + + +
Skip to content

static-php-cli

在 Linux、macOS、FreeBSD、Windows 上与 PHP 项目一起构建独立的 PHP 二进制文件,并包含流行的扩展。

Released under the MIT License.

+ + + + \ No newline at end of file