xmake-io / xmake

🔥 A cross-platform build utility based on Lua

Home Page:https://xmake.io

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

依赖循环有什么好办法

zeromake opened this issue · comments

Xmake 版本

v2.7.2+master.52dd7982a

操作系统版本和架构

Windows 10 version 19043.2130

描述问题

现在有两个库用 freetypeharfbuzz 举例(类似于这个两个库的依赖关系)。

之前两个库是直接用 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

@waruqi

自己手动改了 xmake 去支持了,不过代码比较乱

commit

一、体验效果

# 更新到我的分支代码> 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 替换回去,再一次编译。
commented

这个问题不太好搞,等后面有空了,我再看下。

相关 issues: #3163

commented

我觉得这个只能在 包里尽可能去避免这个循环依赖,xmake 是没法完全处理的。。

就算 load packages 里面通过判断之前加载过,打破了死循环加载了,但是还有很多其他问题的。。

比如加载过了,处理掉了死循环,目前顺序:freetype -> harfbuzz

  1. on_test 就是其一,最后一层的包 harfbuzz,on_test 肯定是失败了,没 freetype 信息,因为 freetype 还没安装
  2. on_install 里面,最后一层的包内部编译可能会失败,harfbuzz 依赖 freetype 里面的 includes 和 links ,但是 freetype 还没安装
  3. 还有最终提供给用户的 links order 也是个问题,这种得放进 whole-archive 才行

所以从xmake内部处理循环,目前不管怎么处理,都是会有各种问题的,我只能改进加载阶段,检测到循环依赖后,不让它死循环,然后提示警告或者错误信息,让用户在包层面去打破这种死循环。

所以 xmake-repo 仓库的 freetype 默认配置没开 harfbuzz,不会有这个问题,即使通过 add_configs 开了,也不会死循环,因为 harfbuzz 里面的 add_deps("freetype") 没开 harfbuzz。。

commented

我 dev 加了个循环依赖检测,至少避免卡死。。

error: circular dependency(freetype.harfbuzz.freetype) detected in package(freetype)!

@waruqi
后面自己测试有些小问题修改,现在单独放 一个分支 ,暂时使用下来是没什么问题了,都能正常编译 freetype 和 harfbuzz,可以参考一下,不过实际上碰到这种问题很少的。

除了 on_test 不知道为啥一直不能加载依赖库以外都还行,我有空看看这个是我的分支改出的问题还是说 xmake 主分支上也有。

commented

除了 on_test 不知道为啥一直不能加载依赖库以外都还行,我有空看看这个是我的分支改出的问题还是说 xmake 主分支上也有。

就是我刚说的问题,on_test 跑通的前提是 deps 必须要全部安装完成,才能 Fetch 到它们的 includedirs/links 。。但是循环依赖,不管怎么搞,都是不行的。

还有 on_install 里面也一样,foo/bar 两个库,相互依赖,编译安装 foo 需要 bar 里面的 头文件,编译安装 bar 需要 foo 里面的头文件,前提必须有一个完成安装,才能获取到,但是相互依赖导致,谁也没法通过编译。怎么解。。

@waruqi 我说的不是依赖循环的情况下出现的,而且我的依赖循环解决方案是

  1. 先进行一个 freetype(), harfbuzz(freetype) 编译。
  2. 再次重新进行一个 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 的相同包上。

commented

这种感觉也不需要 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 包,啥也没改,就是这个流程,也不会死循环

@waruqi

试了一下确实能编译完成,但是却引用了两次 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 可以先放一下。

commented

为啥要搞两次,我上面贴的使用 xmake-repo 包,也是一次过。还有为啥不用 xmake-repo 仓库的包。

我印象里记得 xrepo 用 xmake 编译时会新的一个 xmake 命令

项目工程中包安装不用 xrepo 命令,不会有单独新xmake进程

感觉还是 add_requireconfs 不能覆盖导致哪里有一个 harfbuzz 依赖

覆盖什么?

还有就是这用起来是真的麻烦,能不能先把覆盖 add_requires 选项先整一个,有了这个我就不用想哪个包依赖哪个了,例如

看不懂你要搞啥。add_require_globalconfs 和 add_requireconfs 的区别是什么

@waruqi

为啥要搞两次,我上面贴的使用 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 需要指定父级包名。

commented

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")

@waruqi
好的我自己写个 option 把选项传递过去吧,add_requireconfs 有匹配模式也可以了

commented

那先这样了,回头有问题再开