PHP 编译器 BPC 编译实战: tinyfilemanager (一个非常好的编译入门示例)

2023-10-20 15:44:38 +08:00
 heguangyu5

昨天看到 peachpie 的 issue 里有一个 can not work woth tinyfilemanager , 就点进去看了下这个 tinyfilemanager.

Tiny File Manager 是一个 PHP 实现的单文件版的文件管理器, 4300 行代码, 实现了文件/目录管理,上传本地/网络文件,文件搜索,多语言/主题切换,压缩/解压 zip 文件等功能.

在线 Demo 在这里: https://tinyfilemanager.github.io/demo/

于是就想着拿 BPC 来编译一下,继而发现这是一个很好的 BPC 编译示例:

  1. 文件个数少,最多 3 个文件,容易说清楚
  2. BPC 编译时的常见问题都有
  3. 编译结果可以真的拿来用,不是个 hello world

接下来就分享下编译过程,如果你能成功编译这个项目,那么大多数的其它 PHP 项目都能自己编译了.

1.安装 BPC 编译器

编译第 1 步当然是要安装 BPC 编译器,如果你是 ubuntu 18.04 系统,可以参考 Manual Installation 手动安装,使用起来也方便些.

考虑到不少网友应该不是 ubuntu 18.04 的系统,这里采用 docker 安装:

root@hgydebian12:~# docker run -it heguangyu5/bpc-compiler
...
Status: Downloaded newer image for heguangyu5/bpc-compiler:latest
root@1fb84d5cb51d:/bpc-workspace# bpc
bpc/6.5.0
Usage: bpc [options] <input-files> [-- script args]
see bpc -h for help with command line options

root@1fb84d5cb51d:/bpc-workspace# exit

安装好之后退出 docker.

2. 编译 tinyfilemanager

要编译 tinyfilemanager, 需要对其源码做一些修改,具体修改了哪些地方稍后再说,现在先来编译运行.

root@hgydebian12:~# git clone https://github.com/heguangyu5/tinyfilemanager.git
Cloning into 'tinyfilemanager'...
...
root@hgydebian12:~# cd tinyfilemanager/
root@hgydebian12:~# docker run -v `pwd`:/bpc-workspace -it heguangyu5/bpc-compiler
root@d7107d2fabef:/bpc-workspace# ls

注意,这里多加了参数

-v `pwd`:/bpc-workspace

将本机的 tinyfilemanager 目录映射到了 docker image 里的 /bpc-workspace,接下来执行一下 make 就编译好了.

root@d7107d2fabef:/bpc-workspace# make
...
generate code
  [1/2] /bpc-workspace/tinyfilemanager.php
  [2/2] /bpc-workspace/translation.json
output prologue
copy althttpd.c althttpd.h althttpd-bpc.scm
generate build.ninja
run ninja
[7/7] link ../tinyfilemanager (statically linked)
root@d7107d2fabef:/bpc-workspace# exit

退出 docker, 编译好的 tinyfilemanager 就在当前目录下:

root@hgydebian12:~/tinyfilemanager# ls -lh tinyfilemanager
-rwxr-xr-x 1 root root 19M Oct 20 12:22 tinyfilemanager

3. 运行 tinyfilemanager

ldd 一下会发现缺少一些类库,当然可能在你的机器上不缺或者缺少的不一样:

root@hgydebian12:~/tinyfilemanager# ldd tinyfilemanager
	...
	libgmodule-2.0.so.0 => not found
	libgio-2.0.so.0 => not found
	libgobject-2.0.so.0 => not found
	libglib-2.0.so.0 => not found
	...
	libcurl.so.4 => not found
	...

在 ubuntu 18.04 / 20.04 / 22.04 以及 Debian 12 上,补上缺少的类库 tinyfilemanager 就能正常运行了, 运行命令是在当前目录执行:

root@hgydebian12:~/tinyfilemanager# mkdir -p /tmp/tinyfilemanager/tmp && mkdir -p /tmp/tinyfilemanager/public && chown -R www-data:www-data /tmp/tinyfilemanager && ./tinyfilemanager -project-name bpc-workspace -port 7878 -home-page tinyfilemanager.php -root /tmp/tinyfilemanager/public -user www-data
Listening for HTTP requests on 0.0.0.0:7878

在其它 linux 发行版上,或者不想安装缺少的类库,也可以使用 docker 来运行:

root@hgydebian12:~/tinyfilemanager# make run-docker-docker-build
...
Status: Downloaded newer image for heguangyu5/bpc-base:latest
Listening for HTTP requests on 0.0.0.0:7878

此时访问 http://localhost:7878 或者 http://IP:7878 就能看到登录界面了.

tinyfilemanager 默认有两个用户:

admin admin@123
user 12345

4. 对 tinyfilemanager.php 做了哪些修改?

@see git commit

  1. FM_Config::save()

    tinyfilemanager.php 的原本逻辑是将设置参数保存在 $CONFIG 变量中, 当参数变化时,直接修改 tinyfilemanager.php 文件自身来完成保存.

    BPC 编译后,只有一个二进制文件,显然这个逻辑需要调整,我们增加了一个 const CONFIG_FILE = '../config.json'; 将设置参数保存到 ../config.json 文件中.

    然后在一开始读取这个文件来初始化 $CONFIG.

  2. __DIR__.'/config.php'

    tinyfilemanager.php 支持使用 config.php 来覆盖默认选项,通过is_readable($config_file) 来判断是否 include 进来.

    BPC 可以通过调用内置函数 include_silent($config_file) 来实现同样的目的.

    我们知道 PHP include 一个文件时,如果这个文件不存在,会报一个 warning,然后继续执行,BPC 的这个include_silent() 函数如果遇到文件不存在,不会报 warning,然后继续执行.

  3. if (defined('__BPC__')) { /* BPC code*/ } else { /* PHP code */ }

    这个结构在 BPC 编译时只会保留下 BPC code, if/elsePHP code 直接丢掉了,类似于 C 语言的#ifdef #else #endif.

    当你需要在 PHP 环境下和在 BPC 编译时执行不同代码时,就可以用它.

  4. $_SESSION['token'] = bin2hex(openssl_random_pseudo_bytes(32));

    我们不想 link openssl 扩展,因为整个 tinyfilemanager.php 里只有这里用到.

  5. [] => array()

    BPC 自身不支持[]这样的数组写法,如果不想改代码,可以通过 phptobpc 进行转换.

  6. $use_curl = true;

    BPC 的copy不支持 copy url,所以从 URL 上传文件功能要用 curl 实现.

  7. FM_Zipper_Tar

    BPC 尚未实现 class PharData, 所以不支持压缩/解压tar文件.

    这里使用 if (defined('__BPC__')) 在 BPC 编译时消掉$tar = new FM_Zipper_Tar();代码是因为在 BPC 编译时,如果一个 class 从未声明过,那么它不能出现在代码里.

  8. translation.json

    BPC 支持将资源文件编译进二进制,默认后缀是.php,.inc,.phtml的文件被识别为代码文件,其它文件都认为是资源文件.编译时可以通过参数 --php-exts 来设置后缀.

    资源文件被 url 请求到时会返回其内容,如果要在 PHP 代码中获取资源文件,可以使用 BPC 内置函数resource_get_contents($resource_file) 来获取.

  9. if ($iswin && class_exists("COM")) {

    BPC 不支持 windows,也没有class COM,所以new COM()不能出现在代码里.

5. 关于 bpc.conf

@see git commit

bpc.conf 定义了一个项目用到的扩展和static link时需要额外添加的 link 参数.

BPC 6.5 支持的所有扩展列表如下:

(extensions php-std
            php-posix
            php-date
            php-pcre
            php-mbstring
            php-json
            php-fileinfo
            php-curl
            php-sysvsem
            php-zlib
            php-session
            php-filter
            php-pdo
            php-pdo_mysql
            php-pdo_sqlite
            php-openssl
            php-ctype
            php-pcntl
            php-tinycdb
            php-hash
            php-scws
            php-xml
            php-iconv
            php-gd
            php-zip
            php-event
            php-mysqli
            php-gmp
        )

tinyfilemanager.php 只用到了一部分,把没用到的扩展去掉,在 static link 时可以减小最终生成的二进制文件大小.

6. 关于 Makefile

@see git commit

tinyfilemanager:
	bpc -v                                          \
		--static                                    \
		--althttpd                                  \
		-c bpc.conf                                 \
		-d max_execution_time=30                    \
		-d upload_max_filesize=50M                  \
		-d post_max_size=60M                        \
		-d memory_limit=512M                        \
		-d log_errors=on                            \
		-d date.timezone=Asia/Shanghai              \
		-d sys_temp_dir=/tmp/tinyfilemanager/tmp    \
		-d session.gc_maxlifetime=604800            \
		-d session.cookie_httponly=1                \
		tinyfilemanager.php                         \
		translation.json
clean:
	@rm -rf .bpc-build-* md5.map

编译过程中生成的 scheme 代码 .scm.bpc-build-PID 目录里, 编译过程中会将 php 文件名,函数名进行 md5, md5 对照表保存在 md5.map 里.

run-docker-docker-build:
	mkdir -p /tmp/tinyfilemanager/tmp && mkdir -p /tmp/tinyfilemanager/public && chown -R www-data:www-data /tmp/tinyfilemanager && docker run -v `pwd`:/bpc-app -v /tmp/tinyfilemanager:/tmp/tinyfilemanager -p 7878:7878 -it heguangyu5/bpc-base ./tinyfilemanager -project-name bpc-workspace -port 7878 -home-page tinyfilemanager.php -root /tmp/tinyfilemanager/public -user www-data

注意前边编译时设定了 php.ini 选项 sys_temp_dir=/tmp/tinyfilemanager/tmp, 这个目录用于保存临时文件和 session 文件.

之所以要设定一个 tmp 目录有两个原因:

  1. tinyfilemanager 处理 URL 上传时,会生成一个临时文件,然后再把临时文件rename public 目录,当以 docker 方式运行时,从 docker 内部 rename 显然会失败.
  2. 登录时会生成 session,session 文件当没有指定 session.save_path 时会保存到 sys_tem_dir 中,这样我们在 docker 外部就能看到这些 session 文件.
./tinyfilemanager -project-name bpc-workspace -port 7878 -home-page tinyfilemanager.php -root /tmp/tinyfilemanager/public -user www-data
868 次点击
所在节点    PHP
0 条回复

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/983826

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX