当前位置: 首页 > news >正文

Xmake实战---libjpeg 开源库移植

第三方代码库的移植编译

通过之前的实验,我们已经基本学习了解了 xmake.lua 的配置语法,也学习了如何通过这个文件配置构建 C/C++ 项目。因此,在本节实验中,我们通过实战的方式,以一个实际的第三方开源基础库 libjpeg 为例,讲解如何通过使用 xmake 去对它进行移植和编译。

libjpeg 是一个用于 jpeg 图片文件解码的开源基础库,其代码本身是完全跨平台的,但是其内部的构建系统却非常的混乱,有提供对于 Linux 系统的 autotools,也有对于 msvc 编译器的 bat 构建脚本。不同的平台,构建方式差异很大,大家如果想编译不同平台的库,都需要花上一定的时间去研究各种平台下对 libjpeg 的编译过程。

而使用 xmake 就可以进一步简化对于这个库的编译,并且还提供了如何去快速集成的方法。不过,在实际的讲解前,我们还需要先从官网下载 libjpeg 的源码,并通过 tar 命令解压到 jpeg-9d 目录下。

整个下载和解压过程如下。

cd ~/Code
wget http://www.ijg.org/files/jpegsrc.v9d.tar.gz
tar -xvf jpegsrc.v9d.tar.gz

使用 xmake 调用 autotools 直接编译

解压完成后,执行 ls 先看下这个项目的根目录下有哪些跟构建系统相关的文件,我们发现,libjpeg 其实存在 ./configure 配置脚本,这是 autotools 构建工具的主要配置工具。这就说明 libjpeg 其实可以直接用 autotools 来编译的,只不过 autotools 并不支持 windows 以及 msvc 编译器。

既然存在 ./configure,那么 xmake 也可以直接调用它来完成整个项目的构建,例如执行下面的命令。

$ cd jpeg-9d
$ xmake
note: configure found, try building it or you can run `xmake f --trybuild=` to set buildsystem (pass -y or --confirm=y/n/d to skip confirm)?
please input: y (y/n)

只需要执行一个 xmake 命令,xmake 就能自动探测当前的项目支持哪些构建系统,如果有 xmake.lua 文件,那就直接按 xmake 的配置编译,如果有 ./configure 那就提示用户是否使用 autotools 构建系统去常识性编译它(另外 xmake 还支持 cmake、meson 等构建系统文件的探测和尝试编译)。

看到下图提示后,我们可以按 y 或者默认回车,来告诉 xmake 请尝试直接调用 autotools 来编译它。

 

编译成功完成后,xmake 会把构建生成产物放置在 build/artifacts 目录下,实际输出内容,如下图。

 

自动生成 xmake.lua 移植编译 libjpeg

刚刚我们介绍了使用 xmake 直接去调用 autotools 来编译,但这种方式毕竟依赖第三方的构建系统,有很多局限性,比如 autotools 不支持 windows,那就无法做到跨平台编译了。

这个时候,我们可以考虑将 libjpeg 的所有源码移植到 xmake.lua 的配置文件中,然后利用 xmake 内部的构建系统直接去编译它,这样就能做到一次移植,多平台编译支持。

不过从现有的构建系统移植到 xmake.lua 还是比较费体力的,为此 xmake 提供了一个辅助功能,它可以扫描当前目录下的所有源代码,然后自动帮我们生成一个基本可用的 xmake.lua 出来,当然自动生成的配置文件,多少会存在一些小问题,但既然有个范本,我们在上面做些小改动调整,就能完成整个构建移植工作。相比起完全手动配置还是省事不少的。

为了启用 xmake 的自动扫描特性,首先得确保当前目录下没有其它的第三方构建系统文件,否则 xmake 检测到后,会优先走尝试调用第三方构建系统来编译的逻辑。

所以我们先执行下面的命令,删除 libjpeg 自带的 autotools 相关配置文件。

rm ./config*

删除后执行 xmake 命令,触发源文件的扫描提示,我们只需要按 y 后回车,确认生成即可。

$ xmake
note: xmake.lua not found, try generating it (pass -y or --confirm=y/n/d to skip confirm)?
please input: n (y/n)

提示内容如下图所示。

 

我们根据提示,按下 y 和回车后,xmake 就会开始自动扫描 libjpeg 库下的所有源文件,如图。

  

还会扫描用到 libjpeg 库的一些可执行程序,如下图。

 

然后继续编译,编译到一半的时候,触发了编译错误,看下图的错误信息,应该是编译 jmemmac.c 文件失败了。

毕竟,自动扫描再怎么智能,也不可能完全可靠,这里只是为了提高一些移植效率,如果编译发生错误,只需要分析下错误原因,对 xmake.lua 里面的配置稍作修改就行了。

看这里的错误,既然是 jmemmac.c 失败了,看文件名,应该是 mac 平台相关的,而我们现在是在 Linux 下,那说明这个源文件不应该参与编译。

因此,我们只需要在 xmake.lua 中删除对这个文件的配置就行了。

 

删除后,我们继续执行 xmake 编译 libjpeg 项目,这个时候我们又发现了其它类似的编译错误,这回是 jmemdos.c 文件,似乎也是跟 libjpeg 的内存分配相关代码。

 

连续碰到 jmemmac.c 和 jmemdos.c 的编译失败,我们大体能知道 libjpeg 提供了不同平台对应的 jmemxxx.c 内存分配实现。既然如此,我们再找下其它跟内存相关的平台代码文件,发现还有一个 jmemdosa.asm 也是,那么我们就一并注释掉这两个文件。

 

注释掉这三个不相关的文件后,继续执行 xmake 命令就能顺利通过编译了,如下图。

 

手动配置 xmake.lua 移植编译

刚刚我们通过自动生成 xmake.lua 文件的方式,也已经完成了 libjpeg 库的移植编译,但是生成的 xmake.lua 文件内容还比较冗余和粗糙,不够精简。

既然我们现在已经大概了解 libjpeg 库的源码结构,其 Linux 下实际仅仅使用了 jmemansi.c 文件参与编译,其它 jmem*.c 代码文件我们可以手动配置去排除它。

因此修改并精简 xmake.lua 配置如下。

add_rules("mode.debug", "mode.release")

target("jpeg")
    set_kind("static")
    add_files("*.c|jmem*|wrjpgcom.c|cjpeg.c|rdjpgcom.c|ckconfig.c|djpeg.c|jpegtran.c")
    add_files("jmemansi.c", "jmemmgr.c")

通过 add_files("*.c|jmem*") 的方式,我们就可以在添加匹配大部分代码文件的同时,忽略掉其它内存分配代码文件的编译。另外,我们也知道 wrjpgcom.c、cjpeg.c 和 rdjpgcom.c 这些文件,其实只是可执行程序的主文件,并不是 libjpeg 库的内部文件,编译静态库不需要参与进来,那么也可以一并排除掉,就如上面的配置所示。

手动精简后的配置仅仅只有简单的几行,就能做到相同的编译效果,我们重新执行下 xmake -r 命令,看看是否能够正常完成整个库的编译,如果一切正常,就会如下图所示。

 

远程包的依赖集成

通过手动移植的方式,我们虽然能够很好的完成第三方库的编译,但是集成起来还是比较繁琐的,如果把 libjpeg 库代码内置到项目中去,就会非常的臃肿。而如果仅仅提供编译后的库给其它项目链接使用,编译和版本维护又非常的麻烦。

然后我们已经完成了编译,就可以把整个编译移植过程记录下来,通过 package("libjpeg") 方式定义一个第三方包,内部描述整个库的编译过程。

而大家只需要在自己的项目中通过 add_requires("libjpeg") 引用进来,就可以实现远程库的自动下载、自动编译以及无缝集成。

下面,我们先来创建一个新的空工程,用于测试 jpeg 库的自动移植和集成。

cd ~/Code
xmake create jpegtest

创建完成后进入 jpegtest 目录,我们先来修改 src/main.cpp 加上对 jpeglib.h 库头文件的引用,以及对应 c 接口的调用。

#include "stdio.h"
#include "jpeglib.h"

int main(int argc, char** argv)
{
    jpeg_create_compress(0);
    return 0;
}

然后开始编辑 xmake.lua 文件,在里面定义一个 package("myjpeg") 的第三方包,内部使用 autotools 构建系统去构建安装 jpeg 库,然后通过 add_requires("myjpeg") 引入到工程中来,并且通过 add_packages("myjpeg") 的方式添加到对应的 jpegtest 目标程序中去进行集成和使用。

相关配置内容如下。

add_rules("mode.debug", "mode.release")

package("myjpeg")
    set_urls("http://www.ijg.org/files/jpegsrc.$(version).tar.gz")
    add_versions("v9d", "99cb50e48a4556bc571dadd27931955ff458aae32f68c4d9c39d624693f69c32")
    on_install(function (package)
        import("package.tools.autoconf").install(package)
    end)
package_end()

add_requires("myjpeg")

target("jpegtest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("myjpeg")

整个配置非常的简单,我们通过 add_versions 将 jpeg v9d 版本的对应源码包的 sha256 哈希值配置上,为了确保下载文件的完整性。

只需 sha256 的计算和获取,可以通过 xmake 提供的 xmake l hash.sha256 jpegsrc.v9d.tar.gz 命令来获取。

注:这里我们使用 add_requires("myjpeg") 而不是 libjpeg 或者 jpeg,是为了确保 xmake 能够使用我们定义的 package("myjpeg") 包,因为 libjpeg 包已经被收录到 xmake 的官方仓库 中,这会导致命名冲突。

而不用 jpeg 这个名称,是因为系统的 pkg-config 默认会检测到 jpeg 库,所以 add_requires("jpeg") 会优先使用系统安装的 jpeg 库。

最后,执行 xmake 的编译效果如下图,我们完全复用了整个 jpeg 库的移植过程,不用每次都去手动移植一遍,也可以快速集成。

 

另外,我们还可以改进下 package("mytest") 包定义,内部不使用 autotools 而是直接使用刚刚我们移植的 xmake.lua 来构建它,这样就能在不同平台下集成 jpeg 库了,修改 xmake.lua 如下。

add_rules("mode.debug", "mode.release")

package("myjpeg")
    set_urls("http://www.ijg.org/files/jpegsrc.$(version).tar.gz")
    add_versions("v9d", "99cb50e48a4556bc571dadd27931955ff458aae32f68c4d9c39d624693f69c32")
    on_install(function (package)
        io.writefile("xmake.lua", [[
            add_rules("mode.debug", "mode.release")

            target("jpeg")
                set_kind("static")
                set_kind("static")
                add_files("*.c|jmem*|wrjpgcom.c|cjpeg.c|rdjpgcom.c|ckconfig.c|djpeg.c|jpegtran.c")
                add_files("jmemansi.c", "jmemmgr.c")
                add_configfiles("jconfig.txt", {filename = "jconfig.h"})
                add_headerfiles("jerror.h", "jmorecfg.h", "jpeglib.h", "$(buildir)/jconfig.h")
        ]])
        import("package.tools.xmake").install(package)
    end)
    on_test(function (package)
        assert(package:has_cfuncs("jpeg_create_compress(0)", {includes = {"stdio.h", "jpeglib.h"}}))
    end)
package_end()

add_requires("myjpeg")

target("jpegtest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("myjpeg")

这次我们在 package("myjpeg") 的 on_install 里面,写入移植后的 xmake.lua 文件,然后通过 xmake 来编译它。

在重新执行 xmake 编译前,我们需要先执行下面的命令,移除之前已经安装的 myjpeg 包,这样才能重新发出 myjpeg 库的编译安装逻辑。

xmake require --uninstall myjpeg

移除掉之前安装的 myjpeg 包后,执行下面的命令重新触发 myjpeg 包的安装和编译集成。

xmake f -c
xmake -r

整个执行过程如下图,如果一切顺利,则会完成整个工程的编译。

 

配置文件的自动生成

在上面的 package("myjpeg") 包移植中,我们还额外加入了一行配置。

add_configfiles("jconfig.txt", {filename = "jconfig.h"})

这是由于 jpeg 库对外提供的头文件,需要有一个 jconfig.h 的配置头文件,但是其源码中默认是没有的,只有一个 jconfig.vc 的模板配置文件,在使用 autotools 编译时候,它会自动通过这个文件生成对应的 jconfig.h,而在我们移植后的 xmake.lua 中也需要自动去生成它。

这个时候,我们可以使用 add_configfiles 接口,配置上它就可以将其自动生成到 build/jconfig.h

然后,我们又通过下面的配置,将自动生成的 jconfig.h 以及其它头文件一起作为安装文件,在安装集成的时候导出到了 include 安装目录下。

add_headerfiles("jerror.h", "jmorecfg.h", "jpeglib.h", "$(buildir)/jconfig.h")

这样,我们在自己的工程里面就可以正常通过 #include "jpeglib.h" 使用 libjpeg 的头文件了。

直接集成使用第三方库

通过远程依赖集成的好处是第三方库的源码不需要内置到项目中去,当然如果像 libjpeg 这种小库,代码量不大,那么直接集成进去使用,也许会更加方便。

要直接进行代码内置集成,我们需要先将刚刚的 jpeg-9d 源码目录放置进来。

cd jpegtest
mv ../jpeg-9d .

然后修改 xmake.lua 文件,通过 includes 的方式引入刚刚在 jpeg-9d 下移植完成的 xmake.lua,例如。

add_rules("mode.debug", "mode.release")

target("jpegtest")
    set_kind("binary")
    add_files("src/*.cpp")
    add_deps("jpeg")

includes("jpeg-9d")

在上面的配置中,我们看到只需要通过 includes("jpeg-9d") 引入刚刚移植好的 jpeg-9d/xmake.lua 后,将其作为子工程,然后通过 add_deps("jpeg") 将 jpeg 库关联到指定目标程序即可。

执行 xmake -r 进行编译,如果一切顺利,那么整个项目都会完成编译,结果如下图。

 

实验总结

在本节实验中,我们通过一个完整的例子学习了如何使用 xmake 来移植编译 libjpeg,并且也学习了如何将移植后的 libjpeg 集成到项目工程中去。整个过程中,我们还学习到了 xmake 的自动扫描和生成模式以及 autotools 的基本概念和使用。

本实验的参考代码可以使用如下命令下载:

wget https://labfile.oss-internal.aliyuncs.com/courses/2764/code19.zip

相关文章:

  • 区块链安全应用------压力测试
  • BUUCTF---misc---[SWPU2019]我有一只马里奥
  • flutter类的细节
  • grafana报错This panel requires Angular (deprecated)
  • 面试八股——RabbitMQ
  • 代码随想录算法训练营第三十四天|1005.K次取反后最大化的数组和,134. 加油站,135. 分发糖果
  • 动态获取权限,文件管理器选择文件,I/O流
  • sql | leecode 1147 |即时事务配送II | sql 优化
  • [SUCTF 2019]EasySQL1 题目分析与详解
  • 微信小程序的医院体检预约管理系统springboot+uniapp+python
  • 使用uniapp实现小程序获取wifi并连接
  • 2024 值得推荐的免费开源 WAF
  • 基于划分的聚类分析——K-means(机器学习)
  • 10.17复习
  • Python正则表达式详解
  • Day08-尚品汇-分页器动态展示
  • 基于图像处理技术的印刷电路板缺陷检测技术分析
  • Hello Word你真的理解了么?今天教我的表弟,有些感悟
  • 【NNDL作业】图像锐化后,为什么“蒙上了一层灰色”?
  • JDBC——使用Java语言操作数据库
  • 将华为地图套件集成到HarmonyOs可穿戴设备应用中
  • 基于OpenAPI(Swagger3)使用AOP技术,进行日志记录
  • JAVAEE框架数据库技术之13_oracle 之PLSQL技术及存储过程和函数
  • 【PyTorch深度学习项目实战100例】—— 基于UNet实现血管瘤超声图像分割 | 第30例
  • 浅谈面向对象设计思想,以及在Linux内核中的体现
  • Mybatis——进阶
  • 简单上手_Kotlin,让开发更简洁
  • 机器学习——代价函数
  • 【百日刷题计划 第八天】——熟悉字符串 字符串基础题
  • python 处理阻尼正弦
  • 人工智能基础:人工智能云服务(Alaas)介绍
  • 【Leetcode】1092. Shortest Common Supersequence