IMakeCore 漫谈¶
一个新的包管理系统¶
在新兴编程语言领域,包管理 服务已是一个常态。java 有 maven, node.js 有 npm, python 能够 pip install 来安装三方库,go 语言有 go pkg,rust 有 cargo ,等等不一而足。
在C++构建工具中,比较突出的是cmake,已经成为默认的事实标注。qmake 在GUI领域也有很重要的地位,但是 qmake 也被局限于GUI编程领域。xmake 作为国人开发的构建工具,颇受好评,市场接受度也越来越高。其他的也有Bazel 和 Meson等等。
c++ 是一个古老而生机勃勃的语言,在对于库的支持上面因其起步较早,编译语言ABI不兼容的特性,在包管理机制上面落后于其他的语言。各家也提出了一系列的编译方案和包管理的方案,比如 vcpkg, conda, xmake 等等。这些方案各有千秋,发展迅速。其中 vcpkg 被广泛应用于软件开发。
这些c++包管理工具的工作原理有的是将二进制的包封装起来给用户直接使用,有的是将源代码下载下来经过编译之后形成二进制库文件集成到软件接下来的编译中去,也有直接将库封装为 header-only 类型,通过inline的方式编译到程序当中。
针对于上述问题,我封装了一个新的库管理程序,可以无缝的集成在 qmake和cmake编译系统的基础上。新的库管理程序相对于以前的库管理程序而言,是基于源代码直接分发的,减少了一个提前编译的过程。
优点不止如此,不管用户是在 qmake 上管理代码还是在cmake 上管理编译代码,该库管理都可以无缝集成,之后还会考虑更多的构建系统的集成,比如 xmake 等。用户只需要在构建文件上添加几行代码就可以使用该包管理系统,用户只需要在包配置文件中添加一个包的信息,该包就会自动添加到用户的构建当中。
IMakeCore¶
我在写 IWebCore的过程中,随着需求增加,模块也逐渐增多,外部依赖的库也增多,如何发布 IWebCore就成了一个问题。IWebCore由于静态对初始化象的使用,不能够使用 header-only 库,也不能使用动态链接库和静态链接库,使用源码分发就成了唯一的选择。
但是如果将所有的包直接使用源码分发,那我就需要分发多个源码包,比如提供 Http 服务的包,提供 命令行 服务的包,提供 WebSocket 服务的包, 等等,这样的分发方式显然不合理,而且没有办法管理依赖。
我在此看了cmake 的包分发,和 xmake 的包分发,发现都没有解决我遇到的问题,所以我开始思考如何解决这个问题。我就想到,既然我需要源码分发,我自己写一个简单,够用的基于源码的包管理系统,于是我开始写了一个名为 IMakeCore 的包管理系统。
慢慢的,这个包管理系统不仅能够集成包到项目中去,我还写了 IPubCore 和 IPubCmd 这两个项目,来支持包管理系统。
IPubCore 的作用类似于 npm, maven 的网站,它提供了包上传,搜索的功能,让用户能够查找到有哪些包,这些包如何使用,如何集成到 IMakeCore 项目中去。
IPubCmd 则是提供了一个命令行工具,让用户能够管理自己的包,比如安装,卸载,更新,列出自己的包,等等, 用户使用 ipc 命令就可以管理自己的包。
总结¶
IMakeCore 不是想代替 cmake,也不是想代替 qmake,而是想提供一个更加简单,更加方便的包管理系统,让用户多一个选择而已。它只是提供了一个集成到 cmake 和 qmake 编译系统的包管理系统。
IMakeCore 包管理系统不是想解决所有的 c++ 包管理问题。它第一个想解决的内容是 IWebCore 的包需要的问题。在 IWebCore 的开发过程中,比如用户想集成一个 基于 cache 的 session 管理的包,想找一个 基于 IHttp 的 压缩功能,ranges功能,crossregion 的功能, 那么这个包就可以在 IPubCore 中找到,在 IMakeCore 中集成到项目中。
其次就是一些代码量较少,可以跨平台编译的库。这些库一方面是打成包比较简单,没有复杂的各种依赖,也没有复杂的编译条件,直接放源码就能编译。另外一方面,由于代码量较少,这些库和我们的项目一起编译也不会对编译时间造成太大的影响。
还有一个优点就是,如果直接以源码的形式参与编译,可以让编译器有更充分的空间进行优化,也可以相对于动态链接库使用,减少更多的库。
但是如果用户想使用 opencv, vtk, openssl 这样的大型库,IMakeCore 就不能够直接提供。之前用户怎样使用这些库,之后还是如何使用,还是在 cmake 项目中, find_package, 在 qmake 项目中,编写各种的 pri 依赖。
最后¶
在我面试找工作的过程中,有一些面试官提了一些问题,我觉得挺有意义的,这里记录一下。
为什么还要一个新的包管理系统?¶
-
IMakeCore 不是一个像 cmake, qmake这样集项目管理,项目构建于一体的工具,而只是一个包管理工具,但是他可以集成在 cmake 和 qmake 之上,在未来还可以支持任何如 xmake 这样的管理构建系统。所以在任何一个qmake/cmake项目中都可以集成 IMakeCore 包管理系统,来引入相关的包。这样的话就实现了
跨项目的包管理。 -
IMakeCore 是基于源代码的包管理系统。目前c++包管理系统更常见的作法是将代码编译为二进制的形式再集成到项目中。IMakeCore则是直接管理源代码,将包的源代码和项目一起编译。直接管理源代码,不用预先编译,可以直接使用,可以跨平台编译。在目前诸如go/rust/python 等都是源码分发。在c++中这个问题更加的明显,由于C++ ABI不兼容,导致不同编译器编译出来的二进制文件不兼容,所以源码分相对而言是最好的选择。当然这个也不是没有代价的,就是在重新全量编译程序的时候会增加编译时间。而这一点则实现了
跨平台的包管理。
综合前面两个原因,IMakeCore 是一个跨项目跨平台的包管理系统。
包依赖如何解决?¶
-
由于c++包管理的长期缺失,c++的每个包/库都相对十分独立。c++现有的库不像 node 库一样有很深的依赖,他们基本上不依赖其他的额外的库。一般这些包都会依赖 std 库进行开发,而不会过多的依赖其他的库。所以我们在目前的包中,基本上不存在依赖的可能,如果真的有依赖也是很简单的依赖关系。这个是c++包的一个很有意思的特点。
-
用户可以在包的定义中使用 dependencies 字段来指定依赖的包,IMakeCore 会自动检测这些包是否存在,如果不存在就会报错。
-
如果真的有特殊的包依赖,用户页可以自己封装依赖到的包,进行管理即可。这里鼓励用户上传到 IPubCore 网站分享给大家使用。
包冲突怎么解决?¶
这个确实是一个很麻烦的事情。包冲突有可以分为两个问题。
-
第一种情况是一个包和另外一个包中的内容一致导致了冲突。这里内容一致的意思比如他们的头文件的相对路径一致了,在引用头文件的时候不知道该怎么区分他们。他们的定义的内容一致了。比如同一个名称的数据或者函数在不同的包中都会被定义,那么程序在编译的链接阶段就会报错。
-
这种冲突也有可能是宏冲突。
-
针对于类的问题,如果真发生这种冲突的问题了,用户可需要自己解决。用户需要将这些包编译为动态库或者静态库再选择导出具体的头文件。
-
如果是用户现在编写的包,那么这个针对于这个问题强烈建议用户使用 namespace 进行区分。java 一个最好的实践就是使用包名作为命名空间。
-
还有一种东西就是头文件冲突。他们的定义位置一致了,导致的冲突。
-
对于已经存在的包,用户需要自己处理这些冲突。处理的方法可以是封装为动态静态库,修改源代码,或者换一个库也可以,或者将代码从新打包成一个.h,或者cpp文件等手段。
-
对于现在创建的库,建议用户以自己的namespace作为嵌套文件夹名称来限定头文件。
总之,对于以后写的库,我们可以有更多的限定来防止冲突,对于以前的库,用户自己需要做一定的处理来防止冲突。
一切就是这么简单!