From bba4b2463e70389d6d1fd952120824f154e539aa Mon Sep 17 00:00:00 2001 From: "Zed A. Shaw" Date: Tue, 21 Jan 2025 06:46:18 -0500 Subject: [PATCH] Initial commit of a small project to keep a record of various things in C++ that confuses me, other people, or are just down right dumb and should be avoided. This will be where I link people who tell me something is fast or better. --- .gdbinit | 10 +++ .gitignore | 29 +++++++ .vimrc_proj | 1 + Makefile | 29 +++++++ TracyClient.cpp | 61 +++++++++++++++ dbc.cpp | 44 +++++++++++ dbc.hpp | 29 +++++++ emplace_move/test.cpp | 100 ++++++++++++++++++++++++ meson.build | 51 ++++++++++++ meson.options | 1 + rvo/test.cpp | 156 +++++++++++++++++++++++++++++++++++++ scripts/coverage_reset.ps1 | 7 ++ scripts/coverage_reset.sh | 11 +++ scripts/reset_build.ps1 | 7 ++ scripts/reset_build.sh | 11 +++ stats.cpp | 10 +++ stats.hpp | 45 +++++++++++ tests/base.cpp | 9 +++ wraps/catch2.wrap | 11 +++ wraps/fmt.wrap | 13 ++++ wraps/nlohmann_json.wrap | 11 +++ wraps/tracy.wrap | 7 ++ 22 files changed, 653 insertions(+) create mode 100644 .gdbinit create mode 100644 .gitignore create mode 100644 .vimrc_proj create mode 100644 Makefile create mode 100644 TracyClient.cpp create mode 100644 dbc.cpp create mode 100644 dbc.hpp create mode 100644 emplace_move/test.cpp create mode 100644 meson.build create mode 100644 meson.options create mode 100644 rvo/test.cpp create mode 100644 scripts/coverage_reset.ps1 create mode 100644 scripts/coverage_reset.sh create mode 100644 scripts/reset_build.ps1 create mode 100644 scripts/reset_build.sh create mode 100644 stats.cpp create mode 100644 stats.hpp create mode 100644 tests/base.cpp create mode 100644 wraps/catch2.wrap create mode 100644 wraps/fmt.wrap create mode 100644 wraps/nlohmann_json.wrap create mode 100644 wraps/tracy.wrap diff --git a/.gdbinit b/.gdbinit new file mode 100644 index 0000000..d86a368 --- /dev/null +++ b/.gdbinit @@ -0,0 +1,10 @@ +set confirm off +set breakpoint pending on +set logging on +set logging overwrite on +set print pretty on +set pagination off +break abort +#break _invalid_parameter_noinfo +#break _invalid_parameter +catch throw diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..20a7cf2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +# ---> Vim +# Swap +[._]*.s[a-v][a-z] +!*.svg # comment out if you don't need vector files +[._]*.sw[a-p] +[._]s[a-rt-v][a-z] +[._]ss[a-gi-z] +[._]sw[a-p] + +# Session +Session.vim +Sessionx.vim + +# Temporary +.netrwhist +*~ +# Auto-generated tag files +tags +# Persistent undo +[._]*.un~ + +subprojects +builddir +ttassets +backup +*.exe +*.dll +*.world +coverage diff --git a/.vimrc_proj b/.vimrc_proj new file mode 100644 index 0000000..2b745b4 --- /dev/null +++ b/.vimrc_proj @@ -0,0 +1 @@ +set makeprg=meson\ compile\ -C\ . diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..156120a --- /dev/null +++ b/Makefile @@ -0,0 +1,29 @@ +all: build test + +reset: + powershell -executionpolicy bypass .\scripts\reset_build.ps1 + +%.cpp : %.rl + ragel -o $@ $< + +build: + meson compile -j 10 -C builddir + +release_build: + meson --wipe builddir -Db_ndebug=true --buildtype release + meson compile -j 10 -C builddir + +debug_build: + meson setup --wipe builddir --buildtype debug + meson compile -j 10 -C builddir + +tracy_build: + meson setup --wipe builddir --buildtype debugoptimized -Dtracy_enable=true -Dtracy:on_demand=true + meson compile -j 10 -C builddir + +test: build + ./builddir/emplace_test.exe + ./builddir/rvo_test.exe + +clean: + meson compile --clean -C builddir diff --git a/TracyClient.cpp b/TracyClient.cpp new file mode 100644 index 0000000..6224f48 --- /dev/null +++ b/TracyClient.cpp @@ -0,0 +1,61 @@ +// +// Tracy profiler +// ---------------- +// +// For fast integration, compile and +// link with this source file (and none +// other) in your executable (or in the +// main DLL / shared object on multi-DLL +// projects). +// + +// Define TRACY_ENABLE to enable profiler. + +#include "common/TracySystem.cpp" + +#ifdef TRACY_ENABLE + +#ifdef _MSC_VER +# pragma warning(push, 0) +#endif + +#include "common/tracy_lz4.cpp" +#include "client/TracyProfiler.cpp" +#include "client/TracyCallstack.cpp" +#include "client/TracySysPower.cpp" +#include "client/TracySysTime.cpp" +#include "client/TracySysTrace.cpp" +#include "common/TracySocket.cpp" +#include "client/tracy_rpmalloc.cpp" +#include "client/TracyDxt1.cpp" +#include "client/TracyAlloc.cpp" +#include "client/TracyOverride.cpp" +#include "client/TracyKCore.cpp" + +#if defined(TRACY_HAS_CALLSTACK) +# if TRACY_HAS_CALLSTACK == 2 || TRACY_HAS_CALLSTACK == 3 || TRACY_HAS_CALLSTACK == 4 || TRACY_HAS_CALLSTACK == 6 +# include "libbacktrace/alloc.cpp" +# include "libbacktrace/dwarf.cpp" +# include "libbacktrace/fileline.cpp" +# include "libbacktrace/mmapio.cpp" +# include "libbacktrace/posix.cpp" +# include "libbacktrace/sort.cpp" +# include "libbacktrace/state.cpp" +# if TRACY_HAS_CALLSTACK == 4 +# include "libbacktrace/macho.cpp" +# else +# include "libbacktrace/elf.cpp" +# endif +# include "common/TracyStackFrames.cpp" +# endif +#endif + +#ifdef _MSC_VER +# pragma comment(lib, "ws2_32.lib") +# pragma comment(lib, "dbghelp.lib") +# pragma comment(lib, "advapi32.lib") +# pragma comment(lib, "user32.lib") +# pragma warning(pop) +#endif + +#endif diff --git a/dbc.cpp b/dbc.cpp new file mode 100644 index 0000000..13bafde --- /dev/null +++ b/dbc.cpp @@ -0,0 +1,44 @@ +#include "dbc.hpp" +#include + +void dbc::log(const string &message) { + std::cerr << "!!!!!!!!!!" << message << std::endl; +} + +void dbc::sentinel(const string &message) { + string err = fmt::format("[SENTINEL!] {}", message); + dbc::log(err); + throw dbc::SentinelError{err}; +} + +void dbc::pre(const string &message, bool test) { + if(!test) { + string err = fmt::format("[PRE!] {}", message); + dbc::log(err); + throw dbc::PreCondError{err}; + } +} + +void dbc::pre(const string &message, std::function tester) { + dbc::pre(message, tester()); +} + +void dbc::post(const string &message, bool test) { + if(!test) { + string err = fmt::format("[POST!] {}", message); + dbc::log(err); + throw dbc::PostCondError{err}; + } +} + +void dbc::post(const string &message, std::function tester) { + dbc::post(message, tester()); +} + +void dbc::check(bool test, const string &message) { + if(!test) { + string err = fmt::format("[CHECK!] {}\n", message); + dbc::log(err); + throw dbc::CheckError{err}; + } +} diff --git a/dbc.hpp b/dbc.hpp new file mode 100644 index 0000000..919d729 --- /dev/null +++ b/dbc.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include + +using std::string; + +namespace dbc { + class Error { + public: + const string message; + Error(string m) : message{m} {} + Error(const char *m) : message{m} {} + }; + + class CheckError : public Error {}; + class SentinelError : public Error {}; + class PreCondError : public Error {}; + class PostCondError : public Error {}; + + void log(const string &message); + void sentinel(const string &message); + void pre(const string &message, bool test); + void pre(const string &message, std::function tester); + void post(const string &message, bool test); + void post(const string &message, std::function tester); + void check(bool test, const string &message); +} diff --git a/emplace_move/test.cpp b/emplace_move/test.cpp new file mode 100644 index 0000000..dee87ae --- /dev/null +++ b/emplace_move/test.cpp @@ -0,0 +1,100 @@ +#include +#include +#include +#include +#include +#include + +using namespace fmt; + +// Some heavy object +struct A { + A() = default; + + A(int val) : val(val) { + std::cout << "called constructor with: " << val << std::endl; + } + + A(A const& other) { + std::cout << "calling copy(A const& other): " << val << std::endl;; + } + A(A&& other) { + std::cout << "calling move(A&& other): " << val << std::endl;; + } + + A& operator=(A&& other) { + std::cout << "calling move(A&& other)=: " << val << std::endl;; + return *this; + } + + A& operator=(A const& other) { + std::cout << "calling copy(A const& other)=: " << val << std::endl;; + return *this; + } + + ~A() = default; + + int val{}; +}; + + +TEST_CASE("emplace move tests", "[emplace]") { + A a{10}; + std::vector test; + test.reserve(10); // to prevent vector resizing and creating new objects on the way + std::cout << "===== Emplacing Start =====\n"; + // pass by l-value and will call copy constructor since it'll match + // A(A const&) + std::cout << "== 1. Emplace: Calling copy constructor \n"; + test.emplace_back(a); + + // pass by r-value and will call move constructor since it'll match + // A(A&&) + std::cout << "== 2. Emplace: Calling move constructor \n"; + test.emplace_back(std::move(a)); + + std::cout << "== 3. Emplace: Calling move constructor without direct constructor\n"; + test.emplace_back(100); + + // pass by pr-value (pure r-value which is those value that has not come to an existance yet) + // and will call move constructor since it'll match A(A&&). + // "copy-elision" could be applied here but I don't know why compilers + // refused to do it. Maybe how vectors are implemented and you need + // to put the value in a slot so the compiler had to call the move constructor + std::cout << "== 4. Emplace: Calling move constructor\n"; + test.emplace_back(A{}); +} + +TEST_CASE("pushback move tests", "[push_back]") { + std::cout << "\n\n!!!!!!!!!! PUSH BACK alternative !!!!!!!!!!\n"; + + A a{20}; + std::vector test; + test.reserve(10); // to prevent vector resizing and creating new objects on the way + std::cout << "===== Push Back Start =====\n"; + // pass by l-value and will call copy constructor since it'll match + // A(A const&) + std::cout << "== 1. Push Back: Calling copy constructor \n"; + test.push_back(a); + + // pass by r-value and will call move constructor since it'll match + // A(A&&) + std::cout << "== 2. Push Back: Calling move constructor \n"; + test.push_back(std::move(a)); + + std::cout << "== 3. Push Back: Calling move constructor without direct constructor\n"; + test.push_back(200); + + // pass by pr-value (pure r-value which is those value that has not come to an existance yet) + // and will call move constructor since it'll match A(A&&). + // "copy-elision" could be applied here but I don't know why compilers + // refused to do it. Maybe how vectors are implemented and you need + // to put the value in a slot so the compiler had to call the move constructor + std::cout << "== 4. Push Back: Calling move constructor\n"; + test.push_back(A{}); +} + + +TEST_CASE("proof you don't need emplace or std::move", "[proof]") { + +} diff --git a/meson.build b/meson.build new file mode 100644 index 0000000..80ce1c4 --- /dev/null +++ b/meson.build @@ -0,0 +1,51 @@ +project('c-plus-plus-dafuq', 'cpp', + version: '0.1.0', + default_options: [ + 'cpp_std=c++20', + 'cpp_args=-DTRACY_ENABLE=1 -D_GLIBCXX_DEBUG=1 -D_GLIBCXX_DEBUG_PEDANTIC=1', + 'cpp_args=-DTRACY_ENABLE=1', + ]) + +exe_defaults = [] + +cc = meson.get_compiler('cpp') + +tracy = dependency('tracy', static: true) +catch2 = dependency('catch2-with-main') +fmt = dependency('fmt', allow_fallback: true) +json = dependency('nlohmann_json') + +# not sure if tracy needs these +opengl32 = cc.find_library('opengl32', required: true) +winmm = cc.find_library('winmm', required: true) +gdi32 = cc.find_library('gdi32', required: true) + +# i know for sure tracy needs these +ws2_32 = cc.find_library('ws2_32', required: true) +dbghelp = cc.find_library('dbghelp', required: true) + +if get_option('tracy_enable') and get_option('buildtype') != 'debugoptimized' + warning('Profiling builds should set --buildtype=debugoptimized') +endif + +dependencies = [ + tracy, catch2, fmt, json, opengl32, + ws2_32, dbghelp +] + +# use this for common options only for our executables +cpp_args=[ ] + +executable('emplace_test', [ + 'TracyClient.cpp', + 'emplace_move/test.cpp' + ], + override_options: exe_defaults, + dependencies: dependencies) + +executable('rvo_test', [ + 'TracyClient.cpp', + 'rvo/test.cpp' + ], + override_options: exe_defaults, + dependencies: dependencies) diff --git a/meson.options b/meson.options new file mode 100644 index 0000000..906ac3a --- /dev/null +++ b/meson.options @@ -0,0 +1 @@ +option('tracy_enable', type: 'boolean', value: false, description: 'Enable profiling') diff --git a/rvo/test.cpp b/rvo/test.cpp new file mode 100644 index 0000000..098168f --- /dev/null +++ b/rvo/test.cpp @@ -0,0 +1,156 @@ +#include +#include +#include +#include +#include +#include + +using namespace fmt; + + +// Some heavy object +struct A { + A() = default; + + A(int val) : val(val) { + std::cout << "called constructor with: " << val << std::endl; + } + + A(A const& other) { + std::cout << "calling copy(A const& other): " << val << std::endl;; + } + A(A&& other) { + std::cout << "calling move(A&& other): " << val << std::endl;; + } + + A& operator=(A&& other) { + std::cout << "calling move(A&& other)=: " << val << std::endl;; + return *this; + } + + A& operator=(A const& other) { + std::cout << "calling copy(A const& other)=: " << val << std::endl;; + return *this; + } + + ~A() = default; + + int val{}; +}; + + +template +void print_helper(std::string_view var, T&&) { + std::string name = typeid(T).name(); + if constexpr (std::is_const_v) { + name += " const"; + } + if constexpr (std::is_lvalue_reference_v) { + name += "&"; + } else { + name += "&&"; + } + + std::cout << "Type of '" << var << "': '" << name <<"'\n"; +} + +#define print_t(VAR) print_helper(#VAR, VAR) + +A rvo_impl_1() { + A a; + std::cout << "rvo_impl_1\n"; + a.val = rand() % 2; + return a; +} + +// calls the move constructor or it might optimize away it in -O3 +std::string rvo_impl_2(int v) { + std::string res; + std::cout << "rvo_impl_2\n"; + res = std::to_string(v); + return res; +} + +// It'll call copy constructors +std::string rvo_not_work_complex(int v) { + if (v % 5 == 0) return "multiple of five"; + if (v % 2 == 0) return "multiple of 2"; + return std::to_string(v); +} + +// call move constructor +A rvo_not_work_impl(int v) { + std::cout << "rvo_not_work_impl: "; + A a; + if (v % 2) { + a.val = v % 2; + } else { + // returning early + // you might think who would do that let me show you an example + return {}; + } + return a; +} + +// copy-elision/RVO does not call any constructor since they get optimized away +A copy_elision() { + std::cout << "copy_elision\n"; + return A(); // pr-value +} + +// RVO(return value optimization) is a part of copy-elision, but "copy-elision" is +// a much wider concept that applies to other parts of the program. It removes +// copy/move constuctors + +// What is l-value? +// l-values are references to the objects. These are objects you can hold onto +// like constructed objects, variables or references. +// example: +// 1. int& +// 2. int const& +// 3. int a = 4; foo(a <-- this will be a l-value) + +// What is r-value? +// r-values are the values that is temporary. +// example: +// 1. literals like "hello", 1, 2.5 +// 2. call to object constructor like A(), std::string("Hello"), ... +// 3. A&&, int&&, ... these are r-values that you've seen inside the move constructors + +// There are more value that you can look into "pr-value", ... +// https://en.cppreference.com/w/cpp/language/value_category + +// You may be thinking why i'm talking about them. The reason being these concept +// allows you understand when to use "std::move" and when it's useless. For that +// let me implement my version of move constructor + +template +// decltype(auto) means forward the return type or whatever I'm returning infer the type with +// "const&", "&" or "&&" +decltype(auto) my_move(T&& val) { + // here T&& is not a r-value. It's perfect forwarding. Whenever you see "&&" you see + // with template it means perfect forwading. I know cpp has fucked-up syntax. + using type = std::decay_t; + // all this does is cast l-value to r-value + return static_cast(val); +} + +TEST_CASE("RVO tests", "[base]") { + A a{10}; + int argc; + std::cout << "\n\n######### RVO TESTS ###############\n"; + auto t0 = rvo_impl_1(); // no constructor will be called because of return value optimization + auto t1 = rvo_not_work_impl(argc); + auto t2 = copy_elision(); + auto t3 = rvo_not_work_complex(argc); + auto t4 = rvo_impl_2(argc); + + print_t(a); // pass by l-value | output: Type of 'a': 'A&' + print_t(std::move(a)); // pass by r-value | output: Type of 'std::move(a)': 'A&&' + print_t(my_move(a)); // pass by r-value | output: Type of 'my_move(a)': 'A&&' + print_t(A{}); // pass by r-value | output: Type of 'A{}': 'A&&' + + static_assert(!std::is_rvalue_reference_v, "'a' is l-value"); + // you can remove the negation and see it failing + static_assert(!std::is_lvalue_reference_v, "This should pass if I've implement it correctly"); +} diff --git a/scripts/coverage_reset.ps1 b/scripts/coverage_reset.ps1 new file mode 100644 index 0000000..4799ae6 --- /dev/null +++ b/scripts/coverage_reset.ps1 @@ -0,0 +1,7 @@ +mv .\subprojects\packagecache . +rm -recurse -force .\subprojects\,.\builddir\ +mkdir subprojects +mv .\packagecache .\subprojects\ +mkdir builddir +cp wraps\*.wrap subprojects\ +meson setup --default-library=static --prefer-static -Db_coverage=true builddir diff --git a/scripts/coverage_reset.sh b/scripts/coverage_reset.sh new file mode 100644 index 0000000..9970738 --- /dev/null +++ b/scripts/coverage_reset.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +mv -f ./subprojects/packagecache . +rm -rf subprojects builddir +mkdir subprojects +mv packagecache ./subprojects/ +mkdir builddir +cp wraps/*.wrap subprojects/ +# on OSX you can't do this with static +meson setup -Db_coverage=true builddir diff --git a/scripts/reset_build.ps1 b/scripts/reset_build.ps1 new file mode 100644 index 0000000..975852d --- /dev/null +++ b/scripts/reset_build.ps1 @@ -0,0 +1,7 @@ +mv .\subprojects\packagecache . +rm -recurse -force .\subprojects\,.\builddir\ +mkdir subprojects +mv .\packagecache .\subprojects\ +mkdir builddir +cp wraps\*.wrap subprojects\ +meson setup --default-library=static --prefer-static builddir diff --git a/scripts/reset_build.sh b/scripts/reset_build.sh new file mode 100644 index 0000000..9d56a7f --- /dev/null +++ b/scripts/reset_build.sh @@ -0,0 +1,11 @@ +#!/usr/bin/env bash +set -e + +mv -f ./subprojects/packagecache . +rm -rf subprojects builddir +mkdir subprojects +mv packagecache ./subprojects/ +mkdir builddir +cp wraps/*.wrap subprojects/ +# on OSX you can't do this with static +meson setup builddir diff --git a/stats.cpp b/stats.cpp new file mode 100644 index 0000000..0508b85 --- /dev/null +++ b/stats.cpp @@ -0,0 +1,10 @@ +#include "stats.hpp" +#include + +void Stats::dump() +{ + fmt::println("sum: {}, sumsq: {}, n: {}, " + "min: {}, max: {}, mean: {}, stddev: {}", + sum, sumsq, n, min, max, mean(), + stddev()); +} diff --git a/stats.hpp b/stats.hpp new file mode 100644 index 0000000..3be96d2 --- /dev/null +++ b/stats.hpp @@ -0,0 +1,45 @@ +#pragma once +#include + +struct Stats { + double sum = 0.0; + double sumsq = 0.0; + unsigned long n = 0L; + double min = 0.0; + double max = 0.0; + + + inline void reset() { + sum = 0; + sumsq = 0; + n = 0L; + min = 0; + max = 0; + } + + inline double mean() { + return sum / n; + } + + inline double stddev() { + return std::sqrt((sumsq - (sum * sum / n)) / + (n - 1)); + } + + inline void sample(double s) { + sum += s; + sumsq += s * s; + + if (n == 0) { + min = s; + max = s; + } else { + if (min > s) min = s; + if (max < s) max = s; + } + + n += 1; + } + + void dump(); +}; diff --git a/tests/base.cpp b/tests/base.cpp new file mode 100644 index 0000000..4097258 --- /dev/null +++ b/tests/base.cpp @@ -0,0 +1,9 @@ +#include +#include +#include + +using namespace fmt; + +TEST_CASE("base test", "[base]") { + REQUIRE(1 == 1); +} diff --git a/wraps/catch2.wrap b/wraps/catch2.wrap new file mode 100644 index 0000000..f9bf436 --- /dev/null +++ b/wraps/catch2.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = Catch2-3.7.1 +source_url = https://github.com/catchorg/Catch2/archive/v3.7.1.tar.gz +source_filename = Catch2-3.7.1.tar.gz +source_hash = c991b247a1a0d7bb9c39aa35faf0fe9e19764213f28ffba3109388e62ee0269c +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/catch2_3.7.1-1/Catch2-3.7.1.tar.gz +wrapdb_version = 3.7.1-1 + +[provide] +catch2 = catch2_dep +catch2-with-main = catch2_with_main_dep diff --git a/wraps/fmt.wrap b/wraps/fmt.wrap new file mode 100644 index 0000000..fd50847 --- /dev/null +++ b/wraps/fmt.wrap @@ -0,0 +1,13 @@ +[wrap-file] +directory = fmt-11.0.2 +source_url = https://github.com/fmtlib/fmt/archive/11.0.2.tar.gz +source_filename = fmt-11.0.2.tar.gz +source_hash = 6cb1e6d37bdcb756dbbe59be438790db409cdb4868c66e888d5df9f13f7c027f +patch_filename = fmt_11.0.2-1_patch.zip +patch_url = https://wrapdb.mesonbuild.com/v2/fmt_11.0.2-1/get_patch +patch_hash = 90c9e3b8e8f29713d40ca949f6f93ad115d78d7fb921064112bc6179e6427c5e +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/fmt_11.0.2-1/fmt-11.0.2.tar.gz +wrapdb_version = 11.0.2-1 + +[provide] +fmt = fmt_dep diff --git a/wraps/nlohmann_json.wrap b/wraps/nlohmann_json.wrap new file mode 100644 index 0000000..8c46676 --- /dev/null +++ b/wraps/nlohmann_json.wrap @@ -0,0 +1,11 @@ +[wrap-file] +directory = nlohmann_json-3.11.3 +lead_directory_missing = true +source_url = https://github.com/nlohmann/json/releases/download/v3.11.3/include.zip +source_filename = nlohmann_json-3.11.3.zip +source_hash = a22461d13119ac5c78f205d3df1db13403e58ce1bb1794edc9313677313f4a9d +source_fallback_url = https://github.com/mesonbuild/wrapdb/releases/download/nlohmann_json_3.11.3-1/nlohmann_json-3.11.3.zip +wrapdb_version = 3.11.3-1 + +[provide] +nlohmann_json = nlohmann_json_dep diff --git a/wraps/tracy.wrap b/wraps/tracy.wrap new file mode 100644 index 0000000..3e1bda5 --- /dev/null +++ b/wraps/tracy.wrap @@ -0,0 +1,7 @@ +[wrap-git] +url=https://github.com/wolfpld/tracy.git +revision=v0.11.1 +depth=1 + +[provide] +tracy = tracy_dep