Skip to content

Adding Dependencies and Auto-detection Mechanism

xmake encapsulates dependency libraries, dependency headers, dependency types, and dependency interfaces uniformly using the option mechanism, and further introduces a package mechanism at a higher level, making adding and detecting dependencies more modular and simpler.

Let's look at how xmake's package mechanism works through a specific example.

Suppose your project already has two packages: zlib.pkg and polarssl.pkg (how to build packages will be explained in detail later; for now, you can refer to the examples of existing packages in TBOX dependencies). Your project directory structure is as follows:

demo
 - xmake.lua
 - src
   main.c
 - pkg
   zlib.pkg
   polarssl.pkg

You can modify xmake.lua to use the two dependency packages above:

lua
-- Add dependency package directory, all required packages will be searched from this directory
add_packagedirs("pkg")

-- Add target
target("demo")

    -- Set program type to binary executable
    set_kind("binary")

    -- Add source files
    add_files("src/*.c") 

    -- Add polarssl and zlib packages through option mechanism, if detection passes, they will be automatically linked
    -- The first time you run xmake config or xmake build, it will automatically detect them and cache the configuration
    -- To re-detect, you can run xmake config -c to clear the original configuration and reconfigure everything
    add_options("polarssl", "zlib")

    -- Set auto-generated configuration header file, if mysql detection passes, it will generate CONFIG_PACKAGE_HAVE_MYSQL switch
    set_config_h("$(buildir)/config.h")

    -- Set config.h macro switch prefix: CONFIG_xxxx
    set_config_h_prefix("CONFIG")

    -- Add header file search directory, here to search for config.h
    add_includedirs("$(buildir)")

Next, here's how to use it in your code:

c
#include <stdio.h>

// Include auto-generated config.h header file
// Search path is set in ./build directory
#include "config.h"

// If zlib exists on current platform, use it
#ifdef CONFIG_PACKAGE_HAVE_ZLIB
#   include "zlib/zlib.h"
#endif

// If polarssl exists on current platform, use it
#ifdef CONFIG_PACKAGE_HAVE_POLARSSL
#   include "polarssl/polarssl.h"
#endif

int main(int argc, char** argv)
{
    printf("hello world!\n");
    return 0;
}

The above is the simplest example of package usage. Now let's see how this zlib.pkg is generated:

If this package is developed by your own project xxx, you only need to run xmake p to package it, and it will automatically generate an xxx.pkg package in the ./build directory, which you can directly use in other projects.

If it's a third-party library, you need to build it yourself, but it's also very convenient. If you can't, you can refer to some packages in the existing TBOX dependencies and modify them.

A pkg package directory structure:

zlib.pkg
    - inc (header file directory, optional)
       - zlib/zlib.h
    - lib (link library directory, optional)
       - linux/i386/libz.a
       - windows/i386/zlib.lib
    - xmake.lua (package description file)

Among them, inc and lib are optional. The specific logic is described in xmake.lua. The default package logic generated by xmake will first detect whether there are available libraries and header files in the zlib.pkg directory for the current platform. If the detection fails, it will then detect the system platform.

Of course, you can also modify the detection logic yourself. You don't have to do it this way. You only need to describe the xxx.pkg/xmake.lua file according to your needs.

Let's look at the description logic of zlib.pkg/xmake.lua I provided here:

lua
-- Add a zlib package auto-configuration option
option("zlib")

    -- Set whether to display in xmake f -h configuration menu
    -- If you want your package to allow users to manually disable it in the project, then enable this
    set_showmenu(true)

    -- Display related description information in xmake f -h
    set_description("The mysql package")

    -- If detection passes, define macro switch to config.h
    add_defines_h_if_ok("$(prefix)_PACKAGE_HAVE_ZLIB")

    -- Detect linking
    add_links("z")

    -- Add detected link library directory, here set to first detect if link library exists in zlib.pkg/lib/ for related platform, then detect system
    -- If this is not set, xmake can only detect link libraries in some system directories, such as: /usr/lib, /usr/local/lib
    -- If it cannot be detected in common system directories, but you have installed this library, you can set the search directory for detection yourself
    add_linkdirs("lib/$(plat)/$(arch)")

    -- Detect if #include "zlib/zlib.h" can compile successfully
    add_cincludes("zlib/zlib.h")

    -- Add some detected header file directories, by default it will search in zlib.pkg/inc, of course you can also specify other directories
    add_includedirs("inc/$(plat)", "inc")

As long as you describe xxx.pkg/xmake.lua well, a package can be used by xmake and automatically detected. It uses xmake's option mechanism. Of course, in the package, you can not only detect dependency libraries and header files, but also detect whether certain required interfaces, type definitions, etc. exist.

And the detection mechanism completely uses lua syntax, supports if conditional logic, you can do some special processing for some specific platforms, making your package more universal.

For example, the description of this base package base.pkg:

lua
-- Base package base.pkg
option("base")
    
    -- If current platform is windows, detect ws2_32 link library dependency
    if os("windows") then add_links("ws2_32") 
    -- If it's other platforms, detect -lm, -ldl, -lpthread dependencies (since they are all system libraries, no search directory is set here)
    else add_links("m", "dl", "pthread") end

If your package is only described through xmake.lua without other file directories, you can also embed your package xmake.lua description content directly into the project description file xmake.lua. These two are originally universal. In other words, the mechanism of add_packagedirs("pkg") is to call the project description API: add_subdirs("pkg/*") to add sub-projects. And xxx.pkg is just a sub-project description file.

If you want to add interface detection in your package detection, you only need to use:

  • add_cfuncs
  • add_cxxfuncs
  • add_ctypes
  • add_cxxtypes

So using the package mechanism can maximize the reuse of your dependency environment across different projects. It's a very useful feature.