build-static.sh 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. #!/bin/bash
  2. set -o errexit
  3. set -x
  4. if ! type "git" >/dev/null 2>&1; then
  5. echo "The \"git\" command must be installed."
  6. exit 1
  7. fi
  8. arch="$(uname -m)"
  9. os="$(uname -s | tr '[:upper:]' '[:lower:]')"
  10. # FIXME: re-enable PHP errors when SPC will be compatible with PHP 8.4
  11. spcCommand="php -ddisplay_errors=Off ./bin/spc"
  12. md5binary="md5sum"
  13. if [ "${os}" = "darwin" ]; then
  14. os="mac"
  15. md5binary="md5 -q"
  16. fi
  17. if [ "${os}" = "linux" ] && ! type "cmake" >/dev/null 2>&1; then
  18. echo "The \"cmake\" command must be installed."
  19. exit 1
  20. fi
  21. if [ "${os}" = "linux" ] && { [[ "${arch}" =~ "aarch" ]] || [[ "${arch}" =~ "arm" ]]; }; then
  22. fpic="-fPIC"
  23. fpie="-fPIE"
  24. if [ -z "${DEBUG_SYMBOLS}" ]; then
  25. export SPC_PHP_DEFAULT_OPTIMIZE_CFLAGS="-g -fstack-protector-strong -fPIC -fPIE -Os -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64"
  26. fi
  27. else
  28. fpic="-fpic"
  29. fpie="-fpie"
  30. fi
  31. if [ -z "${PHP_EXTENSIONS}" ]; then
  32. if [ -n "${EMBED}" ] && [ -f "${EMBED}/composer.json" ]; then
  33. cd "${EMBED}"
  34. # read the composer.json file and extract the required PHP extensions
  35. # remove internal extensions from the list: https://github.com/crazywhalecc/static-php-cli/blob/4b16631d45a57370b4747df15c8f105130e96d03/src/globals/defines.php#L26-L34
  36. PHP_EXTENSIONS="$(composer check-platform-reqs --no-dev 2>/dev/null | grep ^ext | sed -e 's/^ext-core//' -e 's/^ext-hash//' -e 's/^ext-json//' -e 's/^ext-pcre//' -e 's/^ext-reflection//' -e 's/^ext-spl//' -e 's/^ext-standard//' -e 's/^ext-//' -e 's/ .*//' | xargs | tr ' ' ',')"
  37. export PHP_EXTENSIONS
  38. cd -
  39. else
  40. export PHP_EXTENSIONS="apcu,bcmath,bz2,calendar,ctype,curl,dba,dom,exif,fileinfo,filter,ftp,gd,gmp,gettext,iconv,igbinary,imagick,intl,ldap,mbregex,mbstring,mysqli,mysqlnd,opcache,openssl,parallel,pcntl,pdo,pdo_mysql,pdo_pgsql,pdo_sqlite,pgsql,phar,posix,protobuf,readline,redis,session,shmop,simplexml,soap,sockets,sodium,sqlite3,ssh2,sysvmsg,sysvsem,sysvshm,tidy,tokenizer,xlswriter,xml,xmlreader,xmlwriter,zip,zlib,yaml,zstd"
  41. fi
  42. fi
  43. if [ -z "${PHP_EXTENSION_LIBS}" ]; then
  44. export PHP_EXTENSION_LIBS="bzip2,freetype,libavif,libjpeg,liblz4,libwebp,libzip,nghttp2"
  45. fi
  46. # The Brotli library must always be built as it is required by http://github.com/dunglas/caddy-cbrotli
  47. if ! echo "${PHP_EXTENSION_LIBS}" | grep -q "\bbrotli\b"; then
  48. export PHP_EXTENSION_LIBS="${PHP_EXTENSION_LIBS},brotli"
  49. fi
  50. if [ -z "${PHP_VERSION}" ]; then
  51. export PHP_VERSION="8.4"
  52. fi
  53. if [ -z "${FRANKENPHP_VERSION}" ]; then
  54. FRANKENPHP_VERSION="$(git rev-parse --verify HEAD)"
  55. export FRANKENPHP_VERSION
  56. elif [ -d ".git/" ]; then
  57. CURRENT_REF="$(git rev-parse --abbrev-ref HEAD)"
  58. export CURRENT_REF
  59. if echo "${FRANKENPHP_VERSION}" | grep -F -q "."; then
  60. # Tag
  61. # Trim "v" prefix if any
  62. FRANKENPHP_VERSION=${FRANKENPHP_VERSION#v}
  63. export FRANKENPHP_VERSION
  64. git checkout "v${FRANKENPHP_VERSION}"
  65. else
  66. git checkout "${FRANKENPHP_VERSION}"
  67. fi
  68. fi
  69. bin="frankenphp-${os}-${arch}"
  70. if [ -n "${CLEAN}" ]; then
  71. rm -Rf dist/
  72. go clean -cache
  73. fi
  74. cache_key="${PHP_VERSION}-${PHP_EXTENSIONS}-${PHP_EXTENSION_LIBS}"
  75. # Build libphp if necessary
  76. if [ -f dist/cache_key ] && [ "$(cat dist/cache_key)" = "${cache_key}" ] && [ -f "dist/static-php-cli/buildroot/lib/libphp.a" ]; then
  77. cd dist/static-php-cli
  78. else
  79. mkdir -p dist/
  80. cd dist/
  81. echo -n "${cache_key}" >cache_key
  82. if [ -d "static-php-cli/" ]; then
  83. cd static-php-cli/
  84. git pull
  85. else
  86. git clone --depth 1 https://github.com/crazywhalecc/static-php-cli
  87. cd static-php-cli/
  88. fi
  89. if type "brew" >/dev/null 2>&1; then
  90. if ! type "composer" >/dev/null; then
  91. packages="composer"
  92. fi
  93. if ! type "go" >/dev/null 2>&1; then
  94. packages="${packages} go"
  95. fi
  96. if [ -n "${RELEASE}" ] && ! type "gh" >/dev/null 2>&1; then
  97. packages="${packages} gh"
  98. fi
  99. if [ -n "${packages}" ]; then
  100. # shellcheck disable=SC2086
  101. brew install --formula --quiet ${packages}
  102. fi
  103. fi
  104. composer install --no-dev -a
  105. if [ "${os}" = "linux" ]; then
  106. extraOpts="--disable-opcache-jit"
  107. fi
  108. if [ -n "${DEBUG_SYMBOLS}" ]; then
  109. extraOpts="${extraOpts} --no-strip"
  110. fi
  111. ${spcCommand} doctor --auto-fix
  112. ${spcCommand} download --with-php="${PHP_VERSION}" --for-extensions="${PHP_EXTENSIONS}" --for-libs="${PHP_EXTENSION_LIBS}" --ignore-cache-sources=php-src --prefer-pre-built
  113. # shellcheck disable=SC2086
  114. ${spcCommand} build --debug --enable-zts --build-embed ${extraOpts} "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}"
  115. fi
  116. if ! type "go" >/dev/null 2>&1; then
  117. echo "The \"go\" command must be installed."
  118. exit 1
  119. fi
  120. XCADDY_COMMAND="xcaddy"
  121. if ! type "$XCADDY_COMMAND" >/dev/null 2>&1; then
  122. go install github.com/caddyserver/xcaddy/cmd/xcaddy@latest
  123. XCADDY_COMMAND="$(go env GOPATH)/bin/xcaddy"
  124. fi
  125. curlGitHubHeaders=(--header "X-GitHub-Api-Version: 2022-11-28")
  126. if [ "${GITHUB_TOKEN}" ]; then
  127. curlGitHubHeaders+=(--header "Authorization: Bearer ${GITHUB_TOKEN}")
  128. fi
  129. # Compile e-dant/watcher as a static library
  130. mkdir -p watcher
  131. cd watcher
  132. curl -f --retry 5 "${curlGitHubHeaders[@]}" https://api.github.com/repos/e-dant/watcher/releases/latest |
  133. grep tarball_url |
  134. awk '{ print $2 }' |
  135. sed 's/,$//' |
  136. sed 's/"//g' |
  137. xargs curl -fL --retry 5 "${curlGitHubHeaders[@]}" |
  138. tar xz --strip-components 1
  139. cd watcher-c
  140. cc -c -o libwatcher-c.o ./src/watcher-c.cpp -I ./include -I ../include -std=c++17 -Wall -Wextra "${fpic}"
  141. ar rcs libwatcher-c.a libwatcher-c.o
  142. cp libwatcher-c.a ../../buildroot/lib/libwatcher-c.a
  143. mkdir -p ../../buildroot/include/wtr
  144. cp -R include/wtr/watcher-c.h ../../buildroot/include/wtr/watcher-c.h
  145. cd ../../
  146. # See https://github.com/docker-library/php/blob/master/8.3/alpine3.20/zts/Dockerfile#L53-L55
  147. CGO_CFLAGS="-DFRANKENPHP_VERSION=${FRANKENPHP_VERSION} -I${PWD}/buildroot/include/ $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --includes)"
  148. if [ -n "${DEBUG_SYMBOLS}" ]; then
  149. CGO_CFLAGS="-g ${CGO_CFLAGS}"
  150. else
  151. CGO_CFLAGS="-fstack-protector-strong ${fpic} ${fpie} -O2 -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 ${CGO_CFLAGS}"
  152. fi
  153. export CGO_CFLAGS
  154. export CGO_CPPFLAGS="${CGO_CFLAGS}"
  155. if [ "${os}" = "mac" ]; then
  156. export CGO_LDFLAGS="-framework CoreFoundation -framework SystemConfiguration"
  157. elif [ "${os}" = "linux" ] && [ -z "${DEBUG_SYMBOLS}" ]; then
  158. CGO_LDFLAGS="-Wl,-O1 -pie"
  159. fi
  160. CGO_LDFLAGS="${CGO_LDFLAGS} ${PWD}/buildroot/lib/libbrotlicommon.a ${PWD}/buildroot/lib/libbrotlienc.a ${PWD}/buildroot/lib/libbrotlidec.a ${PWD}/buildroot/lib/libwatcher-c.a $(${spcCommand} spc-config "${PHP_EXTENSIONS}" --with-libs="${PHP_EXTENSION_LIBS}" --libs)"
  161. if [ "${os}" = "linux" ]; then
  162. if echo "${PHP_EXTENSIONS}" | grep -qE "\b(intl|imagick|grpc|v8js|protobuf|mongodb|tbb)\b"; then
  163. CGO_LDFLAGS="${CGO_LDFLAGS} -lstdc++"
  164. fi
  165. fi
  166. export CGO_LDFLAGS
  167. LIBPHP_VERSION="$(./buildroot/bin/php-config --version)"
  168. cd ../
  169. if [ "${os}" = "linux" ]; then
  170. if [ -n "${MIMALLOC}" ]; then
  171. # Replace musl's mallocng by mimalloc
  172. # The default musl allocator is slow, especially when used by multi-threaded apps,
  173. # and triggers weird bugs
  174. # Adapted from https://www.tweag.io/blog/2023-08-10-rust-static-link-with-mimalloc/
  175. echo 'The USE_MIMALLOC environment variable is EXPERIMENTAL.'
  176. echo 'This option can be removed or its behavior modified at any time.'
  177. if [ ! -f "mimalloc/out/libmimalloc.a" ]; then
  178. if [ -d "mimalloc" ]; then
  179. cd mimalloc/
  180. git reset --hard
  181. git clean -xdf
  182. git fetch --tags
  183. else
  184. git clone https://github.com/microsoft/mimalloc.git
  185. cd mimalloc/
  186. fi
  187. git checkout "$(git describe --tags "$(git rev-list --tags --max-count=1 || true)" || true)"
  188. curl -fL --retry 5 https://raw.githubusercontent.com/tweag/rust-alpine-mimalloc/1a756444a5c1484d26af9cd39187752728416ba8/mimalloc.diff -o mimalloc.diff
  189. patch -p1 <mimalloc.diff
  190. mkdir -p out/
  191. cd out/
  192. if [ -n "${DEBUG_SYMBOLS}" ]; then
  193. cmake \
  194. -DCMAKE_BUILD_TYPE=Debug \
  195. -DMI_BUILD_SHARED=OFF \
  196. -DMI_BUILD_OBJECT=OFF \
  197. -DMI_BUILD_TESTS=OFF \
  198. ../
  199. else
  200. cmake \
  201. -DCMAKE_BUILD_TYPE=Release \
  202. -DMI_BUILD_SHARED=OFF \
  203. -DMI_BUILD_OBJECT=OFF \
  204. -DMI_BUILD_TESTS=OFF \
  205. ../
  206. fi
  207. make -j"$(nproc || true)"
  208. cd ../../
  209. fi
  210. if [ -n "${DEBUG_SYMBOLS}" ]; then
  211. libmimalloc_path=mimalloc/out/libmimalloc-debug.a
  212. else
  213. libmimalloc_path=mimalloc/out/libmimalloc.a
  214. fi
  215. # Patch musl library to use mimalloc
  216. for libc_path in "/usr/local/musl/lib/libc.a" "/usr/local/musl/$(uname -m)-linux-musl/lib/libc.a" "/usr/lib/libc.a"; do
  217. if [ ! -f "${libc_path}" ] || [ -f "${libc_path}.unpatched" ]; then
  218. continue
  219. fi
  220. {
  221. echo "CREATE libc.a"
  222. echo "ADDLIB ${libc_path}"
  223. echo "DELETE aligned_alloc.lo calloc.lo donate.lo free.lo libc_calloc.lo lite_malloc.lo malloc.lo malloc_usable_size.lo memalign.lo posix_memalign.lo realloc.lo reallocarray.lo valloc.lo"
  224. echo "ADDLIB ${libmimalloc_path}"
  225. echo "SAVE"
  226. } | ar -M
  227. mv "${libc_path}" "${libc_path}.unpatched"
  228. mv libc.a "${libc_path}"
  229. done
  230. fi
  231. # Increase the default stack size to prevents issues with code including many files such as Symfony containers
  232. extraExtldflags="-Wl,-z,stack-size=0x80000"
  233. fi
  234. if [ -z "${DEBUG_SYMBOLS}" ]; then
  235. extraLdflags="-w -s"
  236. fi
  237. cd ../
  238. # Embed PHP app, if any
  239. if [ -n "${EMBED}" ] && [ -d "${EMBED}" ]; then
  240. tar -cf app.tar -C "${EMBED}" .
  241. ${md5binary} app.tar | awk '{printf $1}' >app_checksum.txt
  242. fi
  243. if [ -z "${XCADDY_ARGS}" ]; then
  244. XCADDY_ARGS="--with github.com/dunglas/caddy-cbrotli --with github.com/dunglas/mercure/caddy --with github.com/dunglas/vulcain/caddy"
  245. fi
  246. XCADDY_DEBUG=0
  247. if [ -n "${DEBUG_SYMBOLS}" ]; then
  248. XCADDY_DEBUG=1
  249. fi
  250. go env
  251. cd caddy/
  252. # shellcheck disable=SC2086
  253. CGO_ENABLED=1 \
  254. XCADDY_GO_BUILD_FLAGS="-buildmode=pie -tags cgo,netgo,osusergo,static_build,nobadger,nomysql,nopgx -ldflags \"-linkmode=external -extldflags '-static-pie ${extraExtldflags}' ${extraLdflags} -X 'github.com/caddyserver/caddy/v2.CustomVersion=FrankenPHP ${FRANKENPHP_VERSION} PHP ${LIBPHP_VERSION} Caddy'\"" \
  255. XCADDY_DEBUG="${XCADDY_DEBUG}" \
  256. ${XCADDY_COMMAND} build \
  257. --output "../dist/${bin}" \
  258. ${XCADDY_ARGS} \
  259. --with github.com/dunglas/frankenphp=.. \
  260. --with github.com/dunglas/frankenphp/caddy=.
  261. cd ..
  262. if [ -d "${EMBED}" ]; then
  263. truncate -s 0 app.tar
  264. truncate -s 0 app_checksum.txt
  265. fi
  266. if type "upx" >/dev/null 2>&1 && [ -z "${DEBUG_SYMBOLS}" ] && [ -z "${NO_COMPRESS}" ]; then
  267. upx --best "dist/${bin}"
  268. fi
  269. "dist/${bin}" version
  270. if [ -n "${RELEASE}" ]; then
  271. gh release upload "v${FRANKENPHP_VERSION}" "dist/${bin}" --repo dunglas/frankenphp --clobber
  272. fi
  273. if [ -n "${CURRENT_REF}" ]; then
  274. git checkout "${CURRENT_REF}"
  275. fi