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.
First, we can use the xmake create command to quickly create an empty static or dynamic library project.
$ 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.
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:
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.
-- 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"})
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:
Run the xmake package command (or the full command xmake package -f local) in the root directory of the library project to package it.
$ 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.
We copy the generated build/packages directory to any location, or use it directly. Then configure it in the consumer project's xmake.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.::: tip Repository vs Package We need to distinguish between Repository and Package:
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.
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:
We run xmake package -f remote to generate a configuration template for the remote package.
$ 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.
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.
package("foo")
set_description("The foo package")
set_license("Apache-2.0")
-- Set private git repository address
add_urls("[email protected]: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)
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.
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
In the consumer project, we need to add this private repository.
add_rules("mode.debug", "mode.release")
-- Add private Git repository
add_repositories("my-repo [email protected]: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.
Xmake also supports distributing C++ Modules libraries.
If it is a pure module library, we recommend configuring set_kind("moduleonly") in the project xmake.lua and exporting module files like .mpp.
target("foo")
set_kind("moduleonly")
add_files("src/*.mpp")
In the package description, we need to set set_kind("library", {moduleonly = true}). This tells Xmake to treat it as a pure module package, which does not require conventional library linking and handles module dependencies better.
package("foo")
set_kind("library", {moduleonly = true})
set_sourcedir(path.join(os.scriptdir(), "src"))
on_install(function(package)
import("package.tools.xmake").install(package, {})
end)
When using module packages, we need to enable C++20 module build support.
add_repositories("my-repo [email protected]:mycompany/my-repo.git")
add_requires("foo")
target("bar")
set_kind("binary")
set_languages("c++20")
add_packages("foo")
-- Enable C++ module build policy
set_policy("build.c++.modules", true)
For more complete examples, please refer to: C++ Modules Package Distribution Example.
Whether it is a remote package or a local package, we can flexibly choose how to manage the package repository.
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):
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.
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:
add_repositories("my-repo [email protected]: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.
# Add globally
$ xrepo add-repo my-repo [email protected]:mycompany/my-repo.git
# Add only for the current project
$ xmake repo --add my-repo [email protected]:mycompany/my-repo.git
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.
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.
$ 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:
$ 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).
If we are developing a library that needs to be used by other non-xmake projects, we can integrate it in the following ways.
If it is a CMake project, we can use xrepo-cmake to directly integrate packages managed by Xmake.
# 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.
We can also configure utils.install.pkgconfig_importfiles and utils.install.cmake_importfiles rules to generate import files while installing the library.
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
find_package(foo REQUIRED)
target_link_libraries(demo PRIVATE foo::foo)
Pkg-config
pkg-config --cflags --libs foo
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.
nsis, wix, zip, targzdeb, rpm, srpm, runself (shell self-extracting script), targz, srczip, appimagedmg, zip, targz, runselfWe can add an xpack configuration block in xmake.lua to define packaging rules.
For example, to configure generating an NSIS installer:
-- 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:
$ 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.