依赖循环有什么好办法
zeromake opened this issue · comments
Xmake 版本
v2.7.2+master.52dd7982a
操作系统版本和架构
Windows 10 version 19043.2130
描述问题
现在有两个库用 freetype
和 harfbuzz
举例(类似于这个两个库的依赖关系)。
之前两个库是直接用 xmake.lua 的 target 的方式走静态链接,直接把两个库源码互相 include,由于静态库是不需要检查依赖的库的方法所以顺便先编译哪个都行。
后来把这个两个库转为 packages 就发现了会互相依赖,而且发现如果我在最上层声明了依赖 configs 依赖里的相同库还是用自己的 configs,如果直接在 packages 里去强制依赖会直接死循环。
除了依赖循环的问题所在,还有着依赖的 packages 里的依赖的 configs 被丢弃,无法通过最上层的 add_requires("", {configs={xxx=true}})
来覆盖。
期待的结果
看了一下 xmake 里的代码 import("package.tools.xmake").install
是一个新进程,要么用环境变量要么用文件来同步信息了。
c/c++ 是没办法搞什么一个库引入两个版本的操作,是否可以考虑想办法压平依赖树的方式,然后子依赖编译时应当继承顶层的 configs 声明(也就是用同一份)。
不过这个只能解决依赖的 configs 问题,也就是类似 sdl_ttf
库依赖 freetype
但是 freetype
有大把依赖才能开启的特性。
现在没有太好解决方案,我先把两库放一个里编译吧。
工程配置
我专门做了一个这样的 repo
xmake repo -a local https://github.com/zeromake/cycle-xrepo
在任意有 xmake.lua 的项目下执行以下命令就会看到 freetype 依赖一个 harfbuzz 方法并不存在但是如果我在 harfbuzz 里强制依赖 freetype,就会达成无限循环。
xmake require -f -y -vD --build --extra="{configs={harfbuzz=true}}" freetype
附加信息和错误日志
No response
自己手动改了 xmake 去支持了,不过代码比较乱
一、体验效果
# 更新到我的分支代码
⋊> xmake update -s git@github.com:zeromake/xmake.git#master
# 创建一个目录来测试
⋊> mkdir demo && cd demo
# 添加 xmake.lua 和 c 代码
⋊> cat>xmake.lua<<EOF
add_rules("mode.debug", "mode.release")
add_requires("freetype", {configs={harfbuzz=true}, lazy_options={configs={harfbuzz=false}}})
add_requires("harfbuzz", {configs={freetype=true}})
target("demo")
add_files("main.c")
add_packages("freetype", "harfbuzz")
EOF
⋊> cat>main.c<<EOF
#include <ft.h>
int main() {
ft_font_create();
return 0;
}
EOF
# 添加有依赖循环的 repo
⋊> xmake repo -a local https://github.com/zeromake/cycle-xrepo
# 然后安装 require
⋊> xmake require -f -y -vD --build
# 摘抄一些日志
xmake f --diagnosis --verbose --yes -y -c --use-harfbuzz=n --plat=macosx --arch=x86_64 --mode=release --kind=static --cxflags=-fPIC --buildir=build_b6eb879f
patching /Users/zero/.xmake/packages/f/freetype/2.12.1/b6eb879fe6b242249cca33632327b448/lib/pkgconfig/freetype.pc ..
=> install freetype 2.12.1 .. ok
xmake f --diagnosis --verbose --yes -y -c --use-freetype=y --plat=macosx --arch=x86_64 --mode=release --kind=static --cxflags=-fPIC --buildir=build_c297384b
patching /Users/zero/.xmake/packages/h/harfbuzz/5.3.1/c297384bf19f440e807dd60936cbcab1/lib/pkgconfig/harfbuzz.pc ..
=> install harfbuzz 5.3.1 .. ok
xmake f --diagnosis --verbose --yes -y -c --use-harfbuzz=y --plat=macosx --arch=x86_64 --mode=release --kind=static --cxflags=-fPIC --buildir=build_b49f1a35
patching /Users/zero/.xmake/packages/f/freetype/2.12.1/b49f1a356eae427ca7ad0ddf8af1b12b/lib/pkgconfig/freetype.pc ..
=> install freetype#1 2.12.1 .. ok
# 上面的 freetype 编译了两次一次 --use-harfbuzz=n 一次 --use-harfbuzz=y。
# 最后就正常 xmake build
⋊> xmake build -vD demo
[ 25%]: cache compiling.release main.c
"/usr/bin/xcrun -sdk macosx clang" -c -Qunused-arguments -target x86_64-apple-macos12.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -fvisibility=hidden -O3 -isystem /Users/zero/.xmake/packages/f/freetype/2.12.1/b49f1a356eae427ca7ad0ddf8af1b12b/include -isystem /Users/zero/.xmake/packages/h/harfbuzz/5.3.1/c297384bf19f440e807dd60936cbcab1/include -DNDEBUG -o build/.objs/demo/macosx/x86_64/release/main.c.o main.c
[ 50%]: linking.release demo
"/usr/bin/xcrun -sdk macosx clang++" -o build/macosx/x86_64/release/demo build/.objs/demo/macosx/x86_64/release/main.c.o -target x86_64-apple-macos12.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -stdlib=libc++ -L/Users/zero/.xmake/packages/f/freetype/2.12.1/b49f1a356eae427ca7ad0ddf8af1b12b/lib -L/Users/zero/.xmake/packages/h/harfbuzz/5.3.1/c297384bf19f440e807dd60936cbcab1/lib -Wl,-x -lfreetype -lharfbuzz -lz
# 可以看到 freetype 正确的使用了 b49f1a356eae427ca7ad0ddf8af1b12b 的版本
# 运行可以看到依赖的库的代码被执行了
⋊> xmake run demo
ft_font_create call
hb_ft_font_create call
二、on_test 问题。
上面已经能够正常处理依赖循环,不过还是有些问题的,比如 freetype 是可选依赖 harfbuzz 我打开了 harfbuzz 支持但是在 packge 里的 on_test 里不会引用 harfbuzz 的头文件和库目录,尝试过手动添加 has_cfuncs 的 config 里的依赖方式是能正常的,现在只能把 freetype 的 on_test 关掉。
# freetype 开启 harfbuzz 选项时的 on_test 错误,只有 freetype 的 include
patching /Users/zero/.xmake/packages/f/freetype/2.12.1/b49f1a356eae427ca7ad0ddf8af1b12b/lib/pkgconfig/freetype.pc ..
> "/usr/bin/xcrun -sdk macosx clang" -c -Qunused-arguments -target x86_64-apple-macos12.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -isystem /Users/zero/.xmake/packages/f/freetype/2.12.1/b49f1a356eae427ca7ad0ddf8af1b12b/include -o /var/folders/d8/53_r4vqx76953rk1ync3fg8c0000gn/T/.xmake501/221130/_EC0A8EEAA11F4920855474D29D1BA950.o /var/folders/d8/53_r4vqx76953rk1ync3fg8c0000gn/T/.xmake501/221130/_C97FAB220D604A32A6C366CE6F19EC6A.c
> checking for c includes(ft.h, hb.h)
> checking for c funcs(ft_font_create)
> checking for c links(freetype)
> checking for c snippet(has_cfuncs)
checkinfo: ...amdir/core/sandbox/modules/import/core/tool/compiler.lua:84: @programdir/modules/core/tools/gcc.lua:704: /var/folders/d8/53_r4vqx76953rk1ync3fg8c0000gn/T/.xmake501/221130/_C97FAB220D604A32A6C366CE6F19EC6A.c:3:10: fatal error: 'hb.h' file not found
#include <hb.h>
三、代码修改思路
先处理依赖的依赖的 requires_extra 不能被顶层的 add_requires 覆盖
- xmake/modules/private/action/require/install.lua 里通过环境变量强制替换 requires_extra
- xmake/modules/package/tools/xmake.lua 里把这个环境变量传递给新的 xmake
处理 _load_packages 依赖循环会卡住,当然是指默认依赖选项都开启会卡住。
- xmake/modules/private/action/require/impl/package.lua 里的 _load_packages 创建一个表
- 把当前的包全部放进去,然后检查 dep 里是否有已经处理过的,总之就是想办法让 dep 去找上层是否有过该包。
当然光是这样会出现 freetype 等 harfbuzz,harfbuzz 等 freetype 的情况
- xmake/modules/private/action/require/install.lua 里发现有的包的 requires_extra 有 lazy_options 字段,就先用 lazy_options 来替换 requires_extra 。
- 并在这次之后再把 requires_extra 替换回去,再一次编译。
我觉得这个只能在 包里尽可能去避免这个循环依赖,xmake 是没法完全处理的。。
就算 load packages 里面通过判断之前加载过,打破了死循环加载了,但是还有很多其他问题的。。
比如加载过了,处理掉了死循环,目前顺序:freetype -> harfbuzz
- on_test 就是其一,最后一层的包 harfbuzz,on_test 肯定是失败了,没 freetype 信息,因为 freetype 还没安装
- on_install 里面,最后一层的包内部编译可能会失败,harfbuzz 依赖 freetype 里面的 includes 和 links ,但是 freetype 还没安装
- 还有最终提供给用户的 links order 也是个问题,这种得放进 whole-archive 才行
所以从xmake内部处理循环,目前不管怎么处理,都是会有各种问题的,我只能改进加载阶段,检测到循环依赖后,不让它死循环,然后提示警告或者错误信息,让用户在包层面去打破这种死循环。
所以 xmake-repo 仓库的 freetype 默认配置没开 harfbuzz,不会有这个问题,即使通过 add_configs 开了,也不会死循环,因为 harfbuzz 里面的 add_deps("freetype") 没开 harfbuzz。。
我 dev 加了个循环依赖检测,至少避免卡死。。
error: circular dependency(freetype.harfbuzz.freetype) detected in package(freetype)!
除了 on_test
不知道为啥一直不能加载依赖库以外都还行,我有空看看这个是我的分支改出的问题还是说 xmake 主分支上也有。
除了
on_test
不知道为啥一直不能加载依赖库以外都还行,我有空看看这个是我的分支改出的问题还是说 xmake 主分支上也有。
就是我刚说的问题,on_test 跑通的前提是 deps 必须要全部安装完成,才能 Fetch 到它们的 includedirs/links 。。但是循环依赖,不管怎么搞,都是不行的。
还有 on_install 里面也一样,foo/bar 两个库,相互依赖,编译安装 foo 需要 bar 里面的 头文件,编译安装 bar 需要 foo 里面的头文件,前提必须有一个完成安装,才能获取到,但是相互依赖导致,谁也没法通过编译。怎么解。。
@waruqi 我说的不是依赖循环的情况下出现的,而且我的依赖循环解决方案是
- 先进行一个 freetype(), harfbuzz(freetype) 编译。
- 再次重新进行一个 freetype(harfbuzz) 编译由于这个时候的 harfbuzz(freetype) 已经编译完成虽然是依赖循环但是并不会重新编译 harfbuzz(freetype) (因为 configs 没有变,还好 xmake 不会检查 harfbuzz 里的依赖是否变化)。
相当于我一次用安装好
add_requires("freetype")
add_requires("harfbuzz", {configs={freetype=true}})
然后再用
add_requires("freetype", {configs={harfbuzz=true}})
add_requires("harfbuzz", {configs={freetype=true}})
上面的情况必须先实现在根项目的 add_requires 里的 opt 可以传递到 repo 里的 add_requires 的相同包上。
这种感觉也不需要 xmake 改啥,还是调整包本身配置。。现有 xmake-repo 仓库的 freetype/harfbuzz 包。目前就是你这个流程,也不会死循环么
add_requires("freetype", {system = false, configs = {harfbuzz = true}})
add_requireconfs("freetype.harfbuzz", {system = false})
add_requireconfs("harfbuzz.freetype", {system = false})
in xmake-repo:
-> python 3.10.6 [binary, from:ninja,meson]
-> ninja 1.11.0 [binary, from:meson]
-> meson 0.62.1 [from:harfbuzz]
-> freetype#1 2.12.1 [from:harfbuzz] -> 先编译 不带 harfbuzz 的 freetype
-> harfbuzz 3.1.1 [from:freetype] -> 再编译 harfbuzz(freetype)
-> freetype 2.12.1 [harfbuzz:y] -> 再编译 freetype(harfbuzz)
please input: y (y/n/m)
线上 xmake 版本和 xmake-repo 包,啥也没改,就是这个流程,也不会死循环
试了一下确实能编译完成,但是却引用了两次 freetype
包。
先把 xmake 重置了
brew reinstall xmake
xmake update -s dev
xrepo add-repo local https://github.com/zeromake/xrepo.git
第一次先在 xmake.lua 里使用如下 add_requires
add_requires("freetype", {system = false})
add_requires("harfbuzz", {system = false, configs = {freetype = true}})
add_requireconfs("freetype.harfbuzz", {system = false, configs = {freetype = true}})
add_requireconfs("harfbuzz.freetype", {system = false})
⋊> ~/D/p/demo xmake f -m debug -c 16:24:42
checking for platform ... macosx
checking for architecture ... x86_64
checking for Xcode directory ... /Applications/Xcode.app
checking for Codesign Identity of Xcode ... Apple Development: 390720046@qq.com (52L3LRH9JV)
checking for SDK version of Xcode for macosx (x86_64) ... 12.3
checking for Minimal target version of Xcode for macosx (x86_64) ... 12.3
note: install or modify (m) these packages (pass -y to skip confirm)?
in local:
-> freetype 2.12.1 [from:harfbuzz]
-> harfbuzz 5.3.1 [freetype:y]
please input: y (y/n/m)
y
=> install freetype 2.12.1 .. ok
=> install harfbuzz 5.3.1 .. ok
freetype(): 85ac3b35516943b781c80cb07c09a7aa
harfbuzz(freetype): e93a19f8f43d424fa87792d9ae11a3ad
第二次
add_requires("freetype", {system = false, configs = {harfbuzz = true}})
add_requires("harfbuzz", {system = false, configs = {freetype = true}})
add_requireconfs("freetype.harfbuzz", {system = false, configs = {freetype = true}})
add_requireconfs("harfbuzz.freetype", {system = false, configs = {harfbuzz = true}})
⋊> ~/D/p/demo xmake f -m debug -c 16:25:29
checking for platform ... macosx
checking for architecture ... x86_64
checking for Xcode directory ... /Applications/Xcode.app
checking for Codesign Identity of Xcode ... Apple Development: 390720046@qq.com (52L3LRH9JV)
checking for SDK version of Xcode for macosx (x86_64) ... 12.3
checking for Minimal target version of Xcode for macosx (x86_64) ... 12.3
note: install or modify (m) these packages (pass -y to skip confirm)?
in local:
-> freetype 2.12.1 [harfbuzz:y]
please input: y (y/n/m)
y
=> install freetype 2.12.1 .. ok
freetype(harfbuzz): 8fc32770f0f046cd93b679693b6bd118
harfbuzz(): 7b0f2332ef27421fa6aeb506bd796fb7 (这个有些问题不应该编译出来的吧,感觉还是 add_requireconfs 不能覆盖导致哪里有一个 harfbuzz 依赖,我印象里记得 xrepo 用 xmake 编译时会新的一个 xmake 命令,应该是编译 freetype 时里面的 xmake.lua, add_requires("harfbuzz") 导致的)
⋊> ~/D/p/demo xmake build -rvD demo
"/usr/bin/xcrun -sdk macosx clang" -c -Qunused-arguments -target x86_64-apple-macos12.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -g -O0 -std=c++17 -framework OpenGL -framework CoreVideo -framework CoreAudio -framework AudioToolbox -framework Carbon -framework CoreGraphics -framework ForceFeedback -framework Metal -framework AppKit -framework IOKit -framework CoreFoundation -framework Foundation -framework CoreHaptics -framework GameController -isystem /Users/zero/.xmake/packages/s/sdl2/2.26.1/53828060ac714d94a8a4ef29b0f6cd00/include -isystem /Users/zero/.xmake/packages/f/freetype/2.12.1/8fc32770f0f046cd93b679693b6bd118/include -isystem /Users/zero/.xmake/packages/h/harfbuzz/5.3.1/e93a19f8f43d424fa87792d9ae11a3ad/include -isystem /Users/zero/.xmake/packages/f/freetype/2.12.1/85ac3b35516943b781c80cb07c09a7aa/include -o build/.objs/demo/macosx/x86_64/debug/main.cpp.o main.cpp
"/usr/bin/xcrun -sdk macosx clang++" -o build/macosx/x86_64/debug/demo build/.objs/demo/macosx/x86_64/debug/main.cpp.o build/.objs/demo/macosx/x86_64/debug/ren-font.cpp.o -target x86_64-apple-macos12.3 -isysroot /Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX12.3.sdk -stdlib=libc++ -L/Users/zero/.xmake/packages/s/sdl2/2.26.1/53828060ac714d94a8a4ef29b0f6cd00/lib -L/Users/zero/.xmake/packages/f/freetype/2.12.1/8fc32770f0f046cd93b679693b6bd118/lib -L/Users/zero/.xmake/packages/h/harfbuzz/5.3.1/e93a19f8f43d424fa87792d9ae11a3ad/lib -L/Users/zero/.xmake/packages/f/freetype/2.12.1/85ac3b35516943b781c80cb07c09a7aa/lib -lsdl2 -lharfbuzz -lfreetype -framework OpenGL -framework CoreVideo -framework CoreAudio -framework AudioToolbox -framework Carbon -framework CoreGraphics -framework ForceFeedback -framework Metal -framework AppKit -framework IOKit -framework CoreFoundation -framework Foundation -framework CoreHaptics -framework GameController -liconv -lz
下面两个库都被引用了,顺序倒是对了。
freetype(harfbuzz): 8fc32770f0f046cd93b679693b6bd118
freetype(): 85ac3b35516943b781c80cb07c09a7aa
还有就是这用起来是真的麻烦,能不能先把覆盖 add_requires 选项先整一个,有了这个我就不用想哪个包依赖哪个了,例如
-- 第一次编译
add_requires("freetype", "harfbuzz")
add_require_globalconfs("freetype", {system = false})
add_require_globalconfs("harfbuzz", {system = false, configs={freetype = true}})
-- 第二次编译
add_requires("freetype", "harfbuzz")
add_require_globalconfs("freetype", {system = false, configs={harfbuzz = true}})
add_require_globalconfs("harfbuzz", {system = false, configs={freetype = true}})
package 里的 xmake.lua 里的 add_requires 可以先放一下。
为啥要搞两次,我上面贴的使用 xmake-repo 包,也是一次过。还有为啥不用 xmake-repo 仓库的包。
我印象里记得 xrepo 用 xmake 编译时会新的一个 xmake 命令
项目工程中包安装不用 xrepo 命令,不会有单独新xmake进程
感觉还是 add_requireconfs 不能覆盖导致哪里有一个 harfbuzz 依赖
覆盖什么?
还有就是这用起来是真的麻烦,能不能先把覆盖 add_requires 选项先整一个,有了这个我就不用想哪个包依赖哪个了,例如
看不懂你要搞啥。add_require_globalconfs 和 add_requireconfs 的区别是什么
为啥要搞两次,我上面贴的使用 xmake-repo 包,也是一次过。还有为啥不用 xmake-repo 仓库的包。
因为我自己的库不是 freetype 和 harfbuzz,只是情况类似罢了,如果全是 xmake 维护的就会这样。
项目工程中包安装不用 xrepo 命令,不会有单独新xmake进程
import("package.tools.xmake").install(package, configs)
package 里 on_install 掉起 xmake 实际是一个子进程,我说的就是这个 xmake f -c 的时候 xmake.lua 里的 add_requires 和父进程里我已经声明过的配置选项是不一致的。
覆盖什么?
比如 curl
依赖一个 http2
然后 http2
可选依赖 zlib
。
add_requires("curl", {system = false, configs={http2=true}})
add_requires("http2", {system = false, configs={zlib=true}})
但是依旧会编译一个 http2() 并连接它,我顶层已经有一个 http2(zlib) 了。
关键代码位置
这个时候 requires_extra 我没法直接继承顶层的。
我知道 add_requireconfs("curl.http2", {system = false, configs={zlib=true}}) 可以搞定,但是如果项目里有另一个依赖了 http2
那我是不是还要再加个 add_requireconfs("xxx.http2", {system = false, configs={zlib=true}}).
看不懂你要搞啥。add_require_globalconfs 和 add_requireconfs 的区别是什么
就是为了省去 add_requireconfs
需要指定父级包名。
package 里 on_install 掉起 xmake 实际是一个子进程,我说的就是这个 xmake f -c 的时候 xmake.lua 里的 add_requires 和父进程里我已经声明过的配置选项是不一致的。
目前包内部 xmake.lua 维护的是独立的工程,用户的 add_requireconfs 和包的 add_deps 是影响不到它的,你得自己透传进去。。比如通过 option,或者 patch
但是依旧会编译一个 http2() 并连接它,我顶层已经有一个 http2(zlib) 了。
这个时候 requires_extra 我没法直接继承顶层的。
这是两个独立的包,独立的链路,互不影响的,这个目前设计如此,你只能通过 add_requireconfs 修改 curl.http2 的配置,跟顶层保持一致
就是为了省去 add_requireconfs 需要指定父级包名。
可以模式匹配的,单层父包,add_requireconfs("*.http2")
,多层父包:add_requireconfs("**.http2")
那先这样了,回头有问题再开