Intro
xmake jest wieloplatrofmowym narzędziem do budowania projektów napisanych w różnych językach oprogramowania o składni opartej na
https://www.lua.org/. Podobnie jak
CMake generuje narzędzia do budowania, z
tą różnicą, że ma bardziej ludzką składnię. Ponadto przy pomocy xmake'a można zaimportować projekt CMake'a i innych popularnych systemów budowania. Kolejną zaletą narzędzia jest to, że posiada on
wbudowany manager pakietów. Na swojej oficjalnej stronie chwalą się
szybkością jak manager budowania Ninja, który słynie z szybkości.
W nieniejszym artykule skupię się na użyciu narzędzia xmake przez programistę C++. Mimo iż xmake jest wieloplatformowe tak bardzo, że działa nawet na mało popularnych systemach, to jednak pisząc ten artykuł używałem systemu Linux (dystrybucji opartej na Ubuntu).Narzędzie to ma bardzo dopracowaną dokumentację, bardzo długi i obszerny
tutorial jak zacząć, jak również
przykłady. Mimo iż nie jest to tak popularne jak CMake, to zawiera bardzo dużo możliwości, których nie jestem w stanie przedstawić w artykule, skupiając się jedynie na wybranych pod ogólnego programistę C++.
Narzędzie ma bardzo duże możliwości, nie tylko budowania, ale też uruchamiania zbudowanego programu, jak również uruchamiania testów i znacznie, znacznie, znacznie więcej możliwości.
Instalacja
Narzędzie to instaluje się zależnie od platformy. Instrukcja znajduje się w
oficjalnym tutorialu. Przykładowo dla dystrybucji opartej na Ubuntu instalacja odbywa się tak:
sudo add-apt-repository ppa:xmake-io/xmake
sudo apt update
sudo apt install xmake
Z kolei na dystrybucjach opartych na Archu:
sudo pacman -S xmake
Aktualizacja
Zaletą tego narzędzie, jest fakt, że jest ono samo-aktualizujące przy pomocy wbudowanej komendy, przykładowo:
xmake update
# można też podać konkretną wersję:
xmake update 2.7.1
Integracja z IDE
Narzędzie posiada wtyczki do integracji z
CLion,
VScode,
QT Creatora, czy
edytor vim i innymi.
Używanie narzędzia xmake
Obecnie narzędzie wspiera projekty korzystające z następujących języków programowania:
Narzędzie jest dość łatwe w użyciu. W swoim projekcie możemy dodać plik
xmake.lua
, lub skorzystać z możliwości wygenerowania projektu.
Ciekawą funkcjonalnością xmake'a jest możliwość budowania projektów posiadających jedynie pliki źródłowe (nie zawierających pliku konfiguracyjnego xmake, oraz też innych plików konfiguracyjnych). Wystarczy, że w katalogu zawołamy
xmake
, po czym zostaniemy spytani czy na pewno. Wg twórców ten mechanizm sobie radzi. Po szczegóły odsyłam do
dokumentacji.
Jego kolejną ciekawą funkcjonalnością jest możliwość wołania innych systemów do budowania, nie tylko CMake'a (
szczegóły), ale również:
Generowanie projektu
Aby utworzyć projekt należy użyć komendy:
xmake create -l c++ -t console "Pierwszy"
# alternatywnie do powyższego:
xmake create --language=c++ --template=console "Pierwszy"
Dla powyższej komendy został wygenerowany następujący plik
xmake.lua
:
add_rules("mode.debug", "mode.release")
target("Pierwszy")
set_kind("binary")
add_files("src/*.cpp")
-- oraz wiele innych komentarzy z przydatnymi komendami xmake
Możliwe jest wiele różnych szablonów projektów, m.in.:
Jak widać
xmake
umożliwia generowanie projektów z wsparciem biblioteki QT (jest znacznie więcej opcji generacji), jak również do pewnych narzędzi programistycznych.
Listę szablonów można znaleźć wpisując:
xmake create --help
Przykładowe używanie xmake
Załużmy, że mamy już gotowy projekt (może być wygenerowany powyższymi komendami), teraz czas na przykładowe komendy:
Budowanie projektu z xmake
Odbywa się to przez zawołanie po prostu:
xmake
# alternatywnie:
xmake build
# a jeśli chcemy tylko nasz wygenerowany wcześniej projekt, a nie wszystkie targety:
xmake build Pierwszy
Domyślnie pliki wynikowe zostaną umieszczone w katalogu
./build/
Jeśli potrzebujemy przebudować projekt możemy zawołać:
xmake --rebuild
# alternatywnie:
xmake -r
Uruchomienie programu przez xmake
Aby uruchomić jeden z celów (ang. target) wystarczy napisać:
xmake run
a, jeśli mamy więcej projektów możemy łatwo wskazać docelowy:
xmake run Pierwszy
Mamy też możliwość uruchomienia wszystkich celów:
xmake run --all
# alternatywnie:
xmake run -a
# alternatywnie zamiast całego run:
xmake r -a
Kolejną wygodą jest możliwość przekazania argumentów uruchomienia, które podajemy za nazwą targetu:
xmake r Pierwszy argumen1 argument2 "setka argumentow"
Czasami programista musi skorzystać z możliwości debugowania, co jest też możliwe przez narzędzie
xmake
:
xmake run --debug Pierwszy
# alternatywnie:
xmake run -d Pierwszy argument1 argument2
Czyszczenie zbudowanych plików
xmake clean
# lub krócej:
xmake c
Łatwość w konfiguracji platformy
Łatwo możemy zmienić konfiguracje do budowania. Przykładowo aby zmienić tryb między debug/release piszemy:
xmake config --mode=debug
# można też krócej:
xmake f --mode=debug
# lub jeszcze krócej:
xmake f -m debug
Po zmianie konfiguracji po prostu budujemy ponownie projekt.
Możemy też w łatwy sposób skompilować projekt pod inną platformę, w tym celu zmieniamy konfigurację:
xmake config --plat=windows --arch=x86 --mode=debug
# lub krócej:
xmake f -p windows -a x86 -m debug
Więcej opcji konfiguracji możemy znaleźć w standardowym
--help
dla podkomendy
config
czyli:
xmake config --help
ale mamy też możliwość wejścia w konfiguracyjne menu, przez zawołanie:
xmake config --menu
Jego możliwości widać na obrazku:
Warto wspomnieć, że można tworzyć własne opcje (ang. option), które można ustawiać z poziomu menu.
Wbudowane typy projektów
Oczywiście można również tworzyć własne typy.
Obsługiwane platformy dla xmake
Mamy możliwość skonfigurowania targetów na popularne platformy (
pełna lista platform i możliwych konfiguracji):
Również na różne architektury np.:
x86
, czy
arm64
.
Możemy sprawdzić listę dostępnych narzędzi programistycznych (ang. toolchains):
$ xmake show -l toolchains
xcode Xcode IDE
vs VisualStudio IDE
yasm The Yasm Modular Assembler
clang A C language family frontend for LLVM
go Go Programming Language Compiler
dlang D Programming Language Compiler
gfortran GNU Fortran Programming Language Compiler
zig Zig Programming Language Compiler
sdcc Small Device C Compiler
cuda CUDA Toolkit
ndk Android NDK
rust Rust Programming Language Compiler
llvm A collection of modular and reusable compiler and toolchain technologies
cross Common cross compilation toolchain
nasm NASM Assembler
gcc GNU Compiler Collection
mingw Minimalist GNU for Windows
gnu-rm GNU Arm Embedded Toolchain
envs Environment variables toolchain
fasm Flat Assembler
tinycc Tiny C Compiler
emcc A toolchain for compiling to asm.js and WebAssembly
icc Intel C/C++ Compiler
ifort Intel Fortran Compiler
muslcc The musl-based cross-compilation toolchain
fpc Free Pascal Programming Language Compiler
wasi WASI-enabled WebAssembly C/C++ toolchain
nim Nim Programming Language Compiler
circle A new C++20 compiler
armcc ARM Compiler Version 5 of Keil MDK
armclang ARM Compiler Version 6 of Keil MDK
Mamy też możliwość utworzyć własnego toolchaina, czy napisać składowe istniejącego (np. kompilator).
Zawartość pliku xmake.lua
Najprostrza zawartość pliku
xmake.lua
wygląda w następujący sposób:
target("Pierwszy")
set_kind("binary")
add_files("src/*.cpp")
Dodatkowe możliwości konfiguracyjne
Dodajemy je pod źródłem np.:
target("Pierwszy")
set_kind("binary")
-- tutaj dodajemy
Dodawanie plików po wzorcu
add_files("src/*.cpp")
Dodawanie stałych czasu kompilacji (define)
add_defines("DEBUG")
Dodawanie ścieżki z nagłówkami
add_includedirs("exe")
Instrukcje warunkowe
if is_mode("debug") then
-- co robimy przy spelnionym warunku
end
Dodawanie katalogu zawierającego pliki źródłowe do pod-projektu
includes("src")
Można też wskazać plik podprojektu:
includes("src/xmake.lua")
Można też rekursywnie:
includes("**/xmake.lua")
Włączenie wbudowanych funkcji, przykładowo:
includes("@builtin/qt")
includes("@builtin/check")
-- alternatywnie możemy dodać tylko konkretny plik:
includes("@builtin/check/check_cfuncs.lua")
Biblioteki do linkera
add_links("pthread", "m", "dl")
Aby podać ścieżki do linkera:
add_linkdirs("$(buildir)/lib")
Systemowe flagi linkera
add_syslinks("pthread")
Flagi dla linkera
add_ldflags("-static", {force = true})
Opcja
force
jest po to, aby flaga nie została zignorowana. Na ogół o zignorowaniu flagi informuje nas xmake w formie warningu.
Flagi dla kompilatora C++
add_cxflags("-O0")
Dodatkowy kod podczas instalacji targetu
on_install("modules.test.install")
W powyższym kodzie obok pliku
xmake.lua
potrzebujemy plik
modules/test/install.lua
, który będzie zawierał kod w lua.
Dodatkowy kod podczas/po/przed_budowaniem/instalacją/uruchamianiem
Możliwości można łączyć:
on/after/before_build/install/package/run_build/install/run
Ustawianie opcji konfiguracyjnych dla projektów
option("test1", {default = "hello", showmenu = true, description = "test1 option"})
option("test2")
set_default(true)
set_showmenu(true)
set_description("test1 option")
option("test3")
set_default(true)
set_showmeu(true)
option("test4")
set_default("hello")
Jest bardzo dużo możliwości (szczegóły w
dokumentacji), tylko pierwszy argument opcji jest obowiązkowy, resztę można pominąć.
Opcja
showmenu
służy do tego, żeby dana opcja była wyświetlona w menu konfiguracyjnym projektu.
Możliwość konfiguracji dla konkretnego systemu
if is_plat("linux", "macosx") then
add_cxflags("-O0")
end
W powyższym przykładzie sprawdzamy czy mamy do czynienia z Linuxem lub MacOS, wtedy dodawana jest dodatkowa flaga
Możliwość iterowania po liście
for _, name in ipairs({"pthread", "m", "dl"}) do
add_links(name)
end
Wykonywanie skryptu po zakończonej kompilacji
target("Pierwszy")
after_build(function (target)
print("hello: %s", target:name())
os.exec("echo %s", target:targetfile())
end)
Dodawanie zależności do projektu (o tym później)
add_packages("tbox", "libuv", "vcpkg::ffmpeg", "brew::pcre2/libpcre2-8", "openssl")
Projekty zależne od siebie
W ramach jednego pliku
xmake.lua
jest możliwe dodanie wielu projektów/targetów. Mamy możliwość połączyć je zależnościami:
add_deps("projekt1", "projekt2")
Łączenie projektu w archiwum
set_policy("build.merge_archive", true)
Wyłączenie użycie ccache
set_policy("build.ccache", false)
jest ono domyślnie używane.
Oczywiście można też użyć komendy zamiast zmieniać pliku konfiguracyjnego:
xmake f --ccache=n
Zmiana ustawień ostrzeżeń kompilacji (ang. warning)
Domyślnie
nie są one wyświetlane, możemy je włączyć w ustawieniach:
set_policy("build.warning", true)
set_warnings("all", "extra")
a także komendą:
xmake -w
Możliwość wskazania zestawu narzędzi (ang. toolchain)
set_toolchains("llvm@llvm-10")
Możliwość tworzenia tasków
Czyli dodatkowych "komend", które można wywołać, które mogą mieć różne argumenty wykonywania. Coś na podobnym poziomie jak target, czy option. Jest to zagadnienie bardziej skomplikowane, dlatego odsyłam do
dokumentacji.
Inne zależności zależne od typu projektu
Dla różnych typów projektu istnieją specjalne opcje, ułatwiające konfiguracje konkretnego typu projektu np. projektu używającego QT. Jest ich bardzo dużo, nie będę ich tutaj przytaczał.
Dodanie zależności
Załóżmy, że budujemy bibilitekę, w której potrzebujemy
#include <rapidjson/*>
, której nie mamy w ogóle w naszym systemie. Dzięki
xmake
wystarczy zmodyfikować nasz
xmake.lua
i wszystkie zależności zostaną automatycznie pobrane:
add_requires("rapidjson")
target("Pierwszy")
set_kind("binary")
add_files("src/*.cpp")
add_packages("rapidjson")
Oczywiście po zawołaniu komendy budującej zostaniemy zapytani czy pobrać zależności. Jeśli z góry wiemy, że chcemy zainstalować wszystko można się pokusić o użycie flagi "zawsze na tak":
xmake --yes
# albo krócej:
xmake -y
Powyżej podawaliśmy zależności jako obowiązkowe, możemy ich użyć w sposób opcjonalny:
add_requires("libharu", {optional=true})
Możemy też sprecyzować wersje pakietu, a nawet alias na nazwę pakietu:
add_requires("tbox >1.6.1", {alias="tbox"})
add_requires("libuv master")
Dużym udogodnieniem jest, że można
wszystkie zależności podać w jednej komendzie np.:
add_requires("tbox >1.6.1", "libuv master", "ffmpeg", "pcre2/libpcre2-8")
Instalowanie zależności przy użyciu wskazanych managerów pakietów typu vcpkg/conan/bref
Jest to możliwe, wystarczy poprzedzić nazwę zależności odpowiednią nazwą managera pakietów np.:
add_requires("vcpkg::ffmpeg", "conan::openssl/1.1.1g", "brew::pcre2/libpcre2-8")
Oczywiście te zależności należy dodać do projektu, przykładowy plik
xmake.lua
z
dokumentacji:
add_requires("tbox >1.6.1", "libuv master", "vcpkg::ffmpeg", "brew::pcre2/libpcre2-8")
add_requires("conan::openssl/1.1.1g", {alias = "openssl", optional = true, debug = true})
target("test")
set_kind("binary")
add_files("src/*.c")
add_packages("tbox", "libuv", "vcpkg::ffmpeg", "brew::pcre2/libpcre2-8", "openssl")
Zależności te widać na obrazku ze
strony dokumentacji xmake:
Instalowanie pakietów przy użyciu xmake
Pożądane pakiety możemy również zainstalować komendą, np.:
xrepo install zlib tbox
Alternatywnie możemy podać wersję:
xrepo install "zlib 1.2.x"
xrepo install "zlib >=1.2.0"
Ponadto mamy możliwość wybrania odpowiedniej wersji biblioteki (debug, shared):
xrepo install -m debug zlib
xrepo install -k shared zlib
Wreszcie mamy możliwość sprecyzowania konfiguracji pod pakiet:
xrepo install -f "vs_runtime='MD'" zlib
xrepo install -f "regex=true,thread=true" boost
Nie zapomnijmy też, że mamy możliwość wskazania konkretnego managera pakietów (który musi być zainstalowany w systemie):
xrepo install brew::zlib
xrepo install vcpkg::zlib
xrepo install conan::zlib/1.2.11
Olbrzymią zaletą instalacji zależności przy pomocy xrepo jest możliwość wyświetlenia flag linkowania/kompilacji:
xrepo fetch pcre2
{
{
linkdirs = {
"/usr/local/Cellar/pcre2/10.33/lib"
},
links = {
"pcre2-8"
},
defines = {
"PCRE2_CODE_UNIT_WIDTH=8"
},
includedirs = "/usr/local/Cellar/pcre2/10.33/include"
}
}
Jeśli potrzebujemy tylko jedno z tych możemy odpytać bezpośrednio:
xrepo fetch --ldflags openssl
xrepo fetch --cflags openssl
xrepo fetch --cflags --ldflags conan::zlib/1.2.11
Możliwe źródła zależności
Jest ich bardzo wiele, nawet te wbudowane w daną dystrybucję linuxa:
Usuwanie pakietu
xmake require --uninstall tbox
Informacje o zainstalowanym pakiecie
xmake require --info tbox
Listowanie aktualnie zainstalowanych pakietów
xmake require --list
Dodanie pakietu z repozytorium github
Jest możliwe zarówno prywatne jak i publiczne repozytorium (
szczegóły):
xmake repo --add myrepo git@github.com:myrepo/xmake-repo.git
Można też w pliku
xmake.lua
dodać własne repozytoria:
add_repositories("my-repo git@github.com:myrepo/xmake-repo.git")
add_repositories("my-repo git@github.com:myrepo/xmake-repo.git dev")
Możliwość wysłania własnego pakietu do oficjalnego repozytoium
Często managery pakietów nie mają wszystkiego, na szczęście każdy może zgłosić własny pakiet. Wymaga to kilka kroków, ale jest szczegółowo opisane w
oficjalnym tutorialu.
Pakiety lokalne
Czasami zależności znajdują się w repozytorium projektu, mogą to być zależności xmake'a, które są włączane w specjalny sposób (np. są w repozytorium). Szczegóły w
dokumentacji.
Używanie funkcji CMake'a z wnętrza xmake'a
Twórcy xmake zdają sobie sprawę, że CMake stanowi pewien standard, dlatego udostępniają wiele funkcjonalności korzystających z tego standardu.
Możliwość znalezienia pakietu zainstalowanego w systemie przy użyciu CMake'a
xmake l find_package cmake::ZLIB
Dodawanie zależności korzystających z CMake'a
Wskazywanie komponentów:
add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"}}))
Domyślne przełączyniki pakietów:
add_requires("cmake::Boost", {system = true, configs = {components = {"regex", "system"},
presets = {Boost_USE_STATIC_LIB = true}}})
Zmienne środowiskowe:
add_requires("cmake::OpenCV", {system = true, configs = {envs = {CMAKE_PREFIX_PATH = "xxx"}}})
Możliwość używania pakietów z xmake'a w CMake'u
Sprowadza się do tego, że instalujemy odpowiedni pakiet do CMake'a, przy pomocy którego ładujemy zależności do pliku CMake. Jest to opisane w
oficjalnym tutorialu.
Inne możliwości xmake'a
Tryb cichy logowania
xmake --quiet
# alternatywnie:
xmake -q
Tryb wylewny logowania (ang. verbose)
xmake --verbose
# alternatywnie:
xmake -v
Środowisko izolowane
W temacie zależności - możliwe jest utworzenie wirtualnego środowiska izolowanego dla zależności (jak
python venv):
xrepo env shell
.
Aby wyjść ze środowiska izolowanego:
xrepo env quit
.
Generowanie konkurencyjnych plików konfigracyjnych dla projektów
Xmake wspiera również generowanie m.in. CMake, Ninja, komendy kompilacji, VisualStudio z projektu używającego xmake'u:
Integracja z IDE
Narzędzie
xmake
posiada wtyczki dla popularnych środowisk programistycznych:
Wykrywanie ustawień systemu i architektury
Zaletą CMake'a jest możliwość sprawdzenia nie tylko zależności, ale również m.in. typów, nagłówków, czy funkcjonalności kompilatora itp.
xmake
również ma taką możliwość, po włączeniu odpowiedniego modułu np.
includes("check_ctypes.lua")
target("test")
set_kind("binary")
add_files("*.c")
add_configfiles("config.h.in")
configvar_check_ctypes("HAS_WCHAR", "wchar_t")
configvar_check_ctypes("HAS_WCHAR_AND_FLOAT", {"wchar_t", "float"})
configvar_check_cincludes("HAS_STRING_H", "string.h")
configvar_check_cincludes("HAS_STRING_AND_STDIO_H", {"string.h", "stdio.h"})
configvar_check_csnippets("HAS_STATIC_ASSERT", "_Static_assert(1, \"\");")
configvar_check_csnippets("HAS_LONG_8", "return (sizeof(long) == 8)? 0: -1;", {tryrun = true})
configvar_check_features("HAS_CONSEXPR_AND_STATIC_ASSERT", {"cxx_constexpr", "c_static_assert"}, {languages = "c++11"})
Więcej informacji na
stronie dokumentacji.
Rodzaje targetów
Czyli to co ustawiamy przez komendę
set_kind("...")
(
więcej szczegółów):
Zdalne, rozproszone budowanie, unity building
Narzędzie jest wyposażone też w funkcje:
zdalnnego budowania,
rozproszonego budowania,
unity building.
Generowanie dokumentacji doxygen
W tym celu wystarczy zawołać:
xmake doxygen
Różne style graficzne
Ciekawostką jest możliwość zmiany szablonów działania
w konsoli narzędzia
xmake
:
xmake g --theme=ninja $ źródło: https://www.youtube.com/watch?v=Z1YsG3TpuS0
xmake g --theme=emoji
xmake g --theme=dark
xmake g --theme=light
xmake g --theme=plain
xmake g --theme=powershell
xmake g --theme=default # alternatywnie: xmake g -c
Pozostałe ustawienia
Jest też bardzo dużo innych, mniej popularnych ustawień np.
tworzenie własnych reguł dla plików o określonych rozszerzeniach,
ustawienie zestawów narzędzi (ang. toolchain),
ustawienia pakietów i zależności między nimi. Poza tym również jest możliwe pobieranie informacji o
pakietach,
targetach,
opcjach,
modułach wbudowanych i
rozszerzeniach modułów.
Płatne wsparcie
Narzędzie xmake jest bezpłatne, natomiast można skorzystać z
płatnego wsparcia.
API ściąga (ang. cheetsheet)
Czyli nazwy niewymienione wcześniej, które mogą się przydać.
Wbudowane predykaty
Czyli funkcje zwracające prawdę lub fałsz, na których podstawie można dokonać ustawień warunkowych, przykładowo:
Funkcje ustawiające projekt
Poniżej jest opis wybranych funkcji globalnych ustawiających projekt (
całość w dokumentacji):
Dodatkowe ustawienia targetu
Robimy to pod targetem, czyli:
target("demo")
-- tutaj ustawiamy
Poniżej wymieniłem wybrane ustawienia (
pełna lista znajduje się w dokumentacji, ustawień tych jest bardzo dużo):
Dla ułatwienia kolejność wywołania funkcji przy budowaniu targetu:
on_load
->
after_load
->
on_config
->
before_build
->
on_build
->
after_build
->
on_link
Mamy też:
before_run
->
on_run
->
after_run
Wbudowane zmienne
Poza zmiennymi wbudowanymi można w łatwy sposób tworzyć własne:
xmake f --var=val
taką zmienną odczytujemy w następujący sposób:
target("test")
add_defines("-DTEST=$(var)")
Bibliografia
Autorzy artykułu