Skip to content

Distribute Private Libraries

Xmake supports not only installing packages from the official repository but also creating and distributing private libraries. This is very useful for reusing private code libraries within a company.

We can package compiled static/dynamic libraries into local packages or remote packages for distribution, or install them directly to the system directory.

Create Library Project

First, we can use the xmake create command to quickly create an empty static or dynamic library project.

bash
$ xmake create -t static test
create test ...
  [+]: xmake.lua
  [+]: src/foo.cpp
  [+]: src/foo.h
  [+]: src/main.cpp
  [+]: .gitignore
create ok!

The default generated xmake.lua is quite simple. We need to modify it slightly to add add_headerfiles to export header files, so that the library headers can be installed together during installation.

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

target("foo")
    set_kind("static")
    add_files("src/foo.cpp")
    add_headerfiles("src/foo.h")

target("test")
    set_kind("binary")
    add_deps("foo")
    add_files("src/main.cpp")

By default, add_headerfiles installs header files directly to the include directory.

If you want to install header files to a subdirectory (e.g. include/foo/foo.h) to avoid filename conflicts, you can set prefixdir:

lua
add_headerfiles("src/foo.h", {prefixdir = "foo"})

For more details, please refer to: add_headerfiles documentation.

In addition to header files, we can also use add_installfiles to install any other files, such as documentation, scripts, resource files, etc.

lua
-- Install readme.md to share/doc/foo directory
add_installfiles("readme.md", {prefixdir = "share/doc/foo"})

-- Install all files under assets
add_installfiles("assets/**", {prefixdir = "share/foo/assets"})

Distribute as Local Package

If our library is only used within the local LAN or shared with others via a network drive, and does not need to be deployed to a remote git repository, we can use local package distribution.

The advantages of local packages are:

  • No need to set up a Git repository
  • Pre-compiled binaries are distributed directly, resulting in fast integration
  • Supports binary packages for multiple platforms, architectures, and build modes (Debug/Release)

Packaging

Run the xmake package command (or the full command xmake package -f local) in the root directory of the library project to package it.

bash
$ xmake package
checking for platform ... macosx 
checking for architecture ... x86_64 
checking for Xcode directory ... /Applications/Xcode.app 
checking for SDK version of Xcode for macosx (x86_64) ... 15.2 
checking for Minimal target version of Xcode for macosx (x86_64) ... 15.2 
[ 23%]: cache compiling.release src/main.cpp 
[ 23%]: cache compiling.release src/foo.cpp 
[ 35%]: archiving.release libfoo.a 
[ 71%]: linking.release test 
[100%]: build ok, spent 3.31s 
installing foo to build/packages/f/foo/macosx/x86_64/release .. 
package(foo): build/packages/f/foo generated 
installing test to build/packages/t/test/macosx/x86_64/release .. 
package(test): build/packages/t/test generated

By default, it will be generated in the build/packages directory.

build/packages/
├── f 
│   └── foo 
│       ├── macosx 
│       │   └── x86_64 
│       │       └── release 
│       │           ├── include 
│       │           │   └── foo.h 
│       │           └── lib 
│       │               └── libfoo.a 
│       └── xmake.lua 
└── t 
    └── test 
        ├── macosx 
        │   └── x86_64 
        │       └── release 
        │           └── bin 
        │               └── test 
        └── xmake.lua

Each package directory contains the generated binary library files (.a/.lib/.so/.dll) and header files.

Consume Local Package

We copy the generated build/packages directory to any location, or use it directly. Then configure it in the consumer project's xmake.lua:

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

-- Add local package repository directory, pointing to the packages directory
add_repositories("local-repo /path/to/test/build/packages")

-- Import foo package
add_requires("foo")

target("bar")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("foo")

Here we use several interfaces:

  • add_repositories: Add a custom package repository. The first argument local-repo is the repository name, and the second argument is the repository address (here it is a local path).
  • add_requires: Declare the packages that the project depends on.
  • add_packages: Link the specified packages to the current target.

Repository vs Package

We need to distinguish between Repository and Package:

  • Repository: A collection of package description files. It can maintain and manage multiple packages.
  • Package: A specific library or tool (e.g., foo, zlib).

A repository can contain multiple packages, and their organization structure is usually as follows (classified by the first letter):

repository/
└── packages/
    ├── f/
    │   ├── foo/
    │   │   └── xmake.lua
    │   └── bar/
    │       └── xmake.lua
    └── z/
        └── zlib/
            └── xmake.lua

The build/packages directory generated by xmake package mentioned above is actually a directory structure that conforms to the Xmake repository specification. Therefore, we can import it as a repository via add_repositories, and Xmake will automatically search for the foo package we need in it.

When executing xmake to build, Xmake will link the corresponding binary libraries directly from the local repository.

Distribute Remote Package

If we need to manage versions and distribute via a git repository, we can use the remote package mode. It supports distributing both source packages and binary packages.

The advantages of remote packages are:

  • Version control based on Git
  • Supports source distribution (automatic compilation) and binary distribution (direct installation)
  • Support for multi-version switching

Configure Remote Package

We run xmake package -f remote to generate a configuration template for the remote package.

bash
$ xmake package -f remote

It will generate the packages/f/foo/xmake.lua file. We can modify it as needed to support source distribution or binary distribution.

Source Distribution

This is the most common way. We only need to configure the git repository address, and Xmake will automatically download the source code and compile and install it.

lua
package("foo")
    set_description("The foo package")
    set_license("Apache-2.0")

    -- Set private git repository address
    add_urls("git@github.com:mycompany/foo.git")
    add_versions("1.0", "<commit-sha>")

    on_install(function (package)
        local configs = {}
        if package:config("shared") then
            configs.kind = "shared"
        end
        import("package.tools.xmake").install(package, configs)
    end)

Binary Distribution

If we don't want to leak source code, or want to speed up installation, we can pre-compile binary packages (e.g. .tar.gz), upload them to a server, and then configure the download address.

lua
package("foo")
    set_description("The foo package")

    -- Set pre-compiled binary package address
    add_urls("https://example.com/releases/foo-$(version).tar.gz")
    add_versions("1.0", "<shasum>")

    on_install(function (package)
        -- Install binary files directly
        os.cp("include", package:installdir())
        os.cp("lib", package:installdir())
    end)

For more detailed package configuration parameters, please refer to: Package Configuration and Package Dependency API.

In addition, we can also refer to the existing package configurations in the official repository xmake-repo.

We need to create a private git repository (e.g., my-repo) to store all package configurations. Push the generated packages directory to this repository.

The directory structure is similar to:

my-repo/
└── packages/
    └── f/
        └── foo/
            └── xmake.lua

Consume Remote Package

In the consumer project, we need to add this private repository.

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

-- Add private Git repository
add_repositories("my-repo git@github.com:mycompany/my-repo.git")

add_requires("foo")

target("bar")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("foo")

Xmake will automatically pull the package description from the my-repo repository, and then download the foo source code according to add_urls for compilation and installation.

Distribute C++ Modules Package

Xmake also supports distributing C++ Modules libraries. We only need to add {install = true} in add_files to package and distribute module files (.mpp, .ixx, etc.) together.

Usually, we need to define the package in an independent repository.

Configure Library Project

In the xmake.lua of the library project, we need to export the module files:

lua
target("foo")
    set_kind("static")
    add_files("src/*.cpp")
    -- Install and distribute module files
    add_files("src/*.mpp", {install = true})

Package Repository

In the private package repository (e.g., my-repo), add the package description file packages/f/foo/xmake.lua:

lua
package("foo")
    add_urls("git@github.com:mycompany/foo.git")
    add_versions("1.0", "<commit-sha>")
    on_install(function (package)
        import("package.tools.xmake").install(package)
    end)

Integration

When integrating on the consumer side, you only need to import the repository and enable the build.c++.modules policy:

lua
add_repositories("my-repo git@github.com:mycompany/my-repo.git")
add_requires("foo")

target("bar")
    set_kind("binary")
    set_languages("c++20")
    -- Enable C++ Modules support
    set_policy("build.c++.modules", true)
    add_packages("foo")

For more complete examples, please refer to: C++ Modules Package Distribution Example.

Manage Package Repositories

Whether it is a remote package or a local package, we can flexibly choose how to manage the package repository.

Project Internal Maintenance

If it is a private package within the project and does not need to be shared across projects, we can place the package repository directly under the project directory.

For example, we create a packages directory in the project root directory as a repository:

projectdir/
├── packages/
│   └── f/
│       └── foo/
│           └── xmake.lua
├── src/
│   └── main.cpp
└── xmake.lua

Then add this repository in xmake.lua via add_repositories (relative paths are supported):

lua
add_repositories("my-repo packages")
add_requires("foo")

target("test")
    set_kind("binary")
    add_files("src/*.cpp")
    add_packages("foo")

In this way, Xmake will automatically look for the foo package configuration in the packages directory under the current project.

Private Git Repository

If you need to share packages among multiple projects within the company, establishing an independent Git repository (such as GitHub private repository, GitLab, Gitee, or company intranet Git) to maintain package configurations is a better choice.

We can import the git repository address directly via add_repositories:

lua
add_repositories("my-repo git@github.com:mycompany/my-repo.git")

If the repository is private, please ensure that the local environment has configured SSH Key or has access permissions.

In addition, we can also use the xrepo add-repo or xmake repo --add command to add the repository address.

xrepo is a global independent command, while xmake repo can add package repositories only in the local project without affecting other projects.

bash
# Add globally
$ xrepo add-repo my-repo git@github.com:mycompany/my-repo.git

# Add only for the current project
$ xmake repo --add my-repo git@github.com:mycompany/my-repo.git

Official Repository

If your package is open source and you want to share it with all Xmake users, you can submit it to the official xmake-repo repository. For specific contribution guidelines, please refer to: Submit Packages to Official Repository.

Install to System

In addition to packaging and distribution, during the development and debugging stage, or if we want to install the library directly to the system directory for other projects to use, we can run xmake install directly.

bash
$ xmake install

By default, it will install to the system directory. If you want to install to a specific directory, you can specify the output directory:

bash
$ xmake install -o /tmp/output

After installation, the directory structure is roughly as follows:

/tmp/output
├── include
│   └── foo.h
├── lib
│   ├── libfoo.a
│   └── pkgconfig
│       └── foo.pc
└── share
    └── cmake
        └── modules
            └── foo-config.cmake

Since we configured the utils.install.pkgconfig_importfiles and utils.install.cmake_importfiles rules (see Use in Other Build Systems), foo.pc and foo-config.cmake files will be automatically generated during installation.

In this way, other non-xmake third-party projects (such as CMake projects) can also find and integrate it via find_package(foo).

Integrate with Other Build Systems

If we are developing a library that needs to be used by other non-xmake projects, we can integrate it in the following ways.

Use CMake (Xrepo)

If it is a CMake project, we can use xrepo-cmake to directly integrate packages managed by Xmake.

cmake
# Download xrepo.cmake
if(NOT EXISTS "${CMAKE_BINARY_DIR}/xrepo.cmake")
    file(DOWNLOAD "https://raw.githubusercontent.com/xmake-io/xrepo-cmake/main/xrepo.cmake"
                  "${CMAKE_BINARY_DIR}/xrepo.cmake" TLS_VERIFY ON)
endif()
include(${CMAKE_BINARY_DIR}/xrepo.cmake)

# Import package
xrepo_package("foo")

# Link package
target_link_libraries(demo PRIVATE foo)

For packages in private repositories, we need to ensure that the corresponding repository has been added locally (xrepo add-repo myrepo ...).

For more detailed instructions, please refer to: Using Xmake Packages in CMake.

Use CMake/Pkg-config

We can also configure utils.install.pkgconfig_importfiles and utils.install.cmake_importfiles rules to generate import files while installing the library.

lua
target("foo")
    set_kind("static")
    add_files("src/foo.cpp")
    add_headerfiles("src/foo.h")
    
    -- Export pkgconfig/cmake import files
    add_rules("utils.install.pkgconfig_importfiles")
    add_rules("utils.install.cmake_importfiles")

In this way, after users execute xmake install to install the library to the system, they can directly use standard methods to find and use the library.

CMake

cmake
find_package(foo REQUIRED)
target_link_libraries(demo PRIVATE foo::foo)

Pkg-config

bash
pkg-config --cflags --libs foo

Generate Installation Package (XPack)

Xmake also supports using the XPack plugin to generate installation packages in various formats, such as NSIS, Deb, RPM, Zip, etc.

This is very useful for distributing binary SDKs or deploying to production environments.

Supported Formats

  • Windows: nsis, wix, zip, targz
  • Linux: deb, rpm, srpm, runself (shell self-extracting script), targz, srczip, appimage
  • MacOS: dmg, zip, targz, runself

Configuration Example

We can add an xpack configuration block in xmake.lua to define packaging rules.

For example, to configure generating an NSIS installer:

lua
-- Include xpack plugin
includes("@builtin/xpack")

target("foo")
    set_kind("shared")
    add_files("src/*.cpp")
    add_headerfiles("src/*.h")

xpack("foo")
    set_formats("nsis")
    set_title("Foo Library")
    set_description("The foo library package")
    set_author("ruki")
    set_version("1.0.0")
    
    -- Add targets to package
    add_targets("foo")
    
    -- Add other files
    add_installfiles("doc/*.md", {prefixdir = "share/doc/foo"})

Then execute the packaging command:

bash
$ xmake pack

It will automatically download the NSIS tool and generate the installation package. The generated installation package can be installed by double-clicking, and it automatically configures environment variables such as PATH, making it convenient for users to use.

For more details, please see the documentation: XPack Packaging.