mirror of
https://github.com/cemu-project/vcpkg.git
synced 2024-11-27 05:00:42 +00:00
[vcpkg metrics] Allow someone to opt out after build (#11542)
* [vcpkg metrics] start using json library Additionally, add floats to the JSON library since they're required. * [vcpkg metrics] allow users to disable metrics after the build Additionally, as a drive by, fix UUID generation * fix metrics data * code review
This commit is contained in:
parent
a64dc07690
commit
09319cd79e
@ -80,6 +80,10 @@ Code licensed under the [MIT License](LICENSE.txt).
|
||||
|
||||
## Telemetry
|
||||
|
||||
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by running `bootstrap-vcpkg.bat` or `bootstrap-vcpkg.sh` with `-disableMetrics`.
|
||||
vcpkg collects usage data in order to help us improve your experience.
|
||||
The data collected by Microsoft is anonymous.
|
||||
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
|
||||
passing --disable-metrics to vcpkg on the command line,
|
||||
or by setting the VCPKG_DISABLE_METRICS environment variable.
|
||||
|
||||
Read more about vcpkg telemetry at docs/about/privacy.md
|
||||
|
@ -1,7 +1,7 @@
|
||||
# Vcpkg telemetry and privacy
|
||||
|
||||
vcpkg collects telemetry data to understand usage issues, such as failing packages, and to guide tool improvements. The collected data is anonymous.
|
||||
For more information about how Microsoft protects your privacy, see https://privacy.microsoft.com/en-US/privacystatement#mainenterprisedeveloperproductsmodule
|
||||
For more information about how Microsoft protects your privacy, see https://privacy.microsoft.com/en-US/privacystatement#mainenterprisedeveloperproductsmodule
|
||||
|
||||
## Scope
|
||||
|
||||
@ -22,7 +22,11 @@ vcpkg displays text similar to the following when you build vcpkg. This is how M
|
||||
```
|
||||
Telemetry
|
||||
---------
|
||||
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics.
|
||||
vcpkg collects usage data in order to help us improve your experience.
|
||||
The data collected by Microsoft is anonymous.
|
||||
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
|
||||
passing --disable-metrics to vcpkg on the command line,
|
||||
or by setting the VCPKG_DISABLE_METRICS environment variable.
|
||||
|
||||
Read more about vcpkg telemetry at docs/about/privacy.md
|
||||
```
|
||||
@ -39,7 +43,7 @@ You can see the telemetry events any command by appending `--printmetrics` after
|
||||
|
||||
In the source code (included in `toolsrc\`), you can search for calls to the functions `track_property()` and `track_metric()` to see every specific data point we collect.
|
||||
|
||||
## Avoid inadvertent disclosure information
|
||||
## Avoid inadvertent disclosure information
|
||||
|
||||
vcpkg contributors and anyone else running a version of vcpkg that they built themselves should consider the path to their source code. If a crash occurs when using vcpkg, the file path from the build machine is collected as part of the stack trace and isn't hashed.
|
||||
Because of this, builds of vcpkg shouldn't be located in directories whose path names expose personal or sensitive information.
|
||||
|
@ -341,7 +341,7 @@ if ($disableMetrics)
|
||||
$platform = "x86"
|
||||
$vcpkgReleaseDir = "$vcpkgSourcesPath\msbuild.x86.release"
|
||||
if($PSVersionTable.PSVersion.Major -le 2)
|
||||
{
|
||||
{
|
||||
$architecture=(Get-WmiObject win32_operatingsystem | Select-Object osarchitecture).osarchitecture
|
||||
}
|
||||
else
|
||||
@ -417,9 +417,13 @@ if (-not $disableMetrics)
|
||||
Write-Host @"
|
||||
Telemetry
|
||||
---------
|
||||
vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running bootstrap-vcpkg.bat with -disableMetrics.
|
||||
Read more about vcpkg telemetry at docs/about/privacy.md
|
||||
vcpkg collects usage data in order to help us improve your experience.
|
||||
The data collected by Microsoft is anonymous.
|
||||
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
|
||||
passing --disable-metrics to vcpkg on the command line,
|
||||
or by setting the VCPKG_DISABLE_METRICS environment variable.
|
||||
|
||||
Read more about vcpkg telemetry at docs/about/privacy.md
|
||||
"@
|
||||
}
|
||||
|
||||
|
@ -256,9 +256,15 @@ rm -rf "$vcpkgRootDir/vcpkg"
|
||||
cp "$buildDir/vcpkg" "$vcpkgRootDir/"
|
||||
|
||||
if ! [ "$vcpkgDisableMetrics" = "ON" ]; then
|
||||
echo "Telemetry"
|
||||
echo "---------"
|
||||
echo "vcpkg collects usage data in order to help us improve your experience. The data collected by Microsoft is anonymous. You can opt-out of telemetry by re-running bootstrap-vcpkg.sh with -disableMetrics"
|
||||
echo "Read more about vcpkg telemetry at docs/about/privacy.md"
|
||||
echo ""
|
||||
cat <<EOF
|
||||
Telemetry
|
||||
---------
|
||||
vcpkg collects usage data in order to help us improve your experience.
|
||||
The data collected by Microsoft is anonymous.
|
||||
You can opt-out of telemetry by re-running the bootstrap-vcpkg script with -disableMetrics,
|
||||
passing --disable-metrics to vcpkg on the command line,
|
||||
or by setting the VCPKG_DISABLE_METRICS environment variable.
|
||||
|
||||
Read more about vcpkg telemetry at docs/about/privacy.md
|
||||
EOF
|
||||
fi
|
||||
|
@ -32,3 +32,6 @@ ForEachMacros: [TEST_CASE, SECTION]
|
||||
PenaltyReturnTypeOnItsOwnLine: 1000
|
||||
SpaceAfterTemplateKeyword: false
|
||||
SpaceBeforeCpp11BracedList: false
|
||||
|
||||
IncludeBlocks: Preserve
|
||||
SortIncludes: false
|
||||
|
@ -9,6 +9,12 @@
|
||||
#include <winhttp.h>
|
||||
#endif
|
||||
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@ -16,10 +22,6 @@
|
||||
#include <cctype>
|
||||
#include <chrono>
|
||||
#include <codecvt>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
|
||||
#if VCPKG_USE_STD_FILESYSTEM
|
||||
#include <filesystem>
|
||||
|
@ -70,6 +70,7 @@ namespace vcpkg::Json
|
||||
{
|
||||
Null,
|
||||
Boolean,
|
||||
Integer,
|
||||
Number,
|
||||
String,
|
||||
Array,
|
||||
@ -84,10 +85,19 @@ namespace vcpkg::Json
|
||||
|
||||
struct Value
|
||||
{
|
||||
Value() noexcept; // equivalent to Value::null()
|
||||
Value(Value&&) noexcept;
|
||||
Value& operator=(Value&&) noexcept;
|
||||
~Value();
|
||||
|
||||
Value clone() const noexcept;
|
||||
|
||||
ValueKind kind() const noexcept;
|
||||
|
||||
bool is_null() const noexcept;
|
||||
bool is_boolean() const noexcept;
|
||||
bool is_integer() const noexcept;
|
||||
// either integer _or_ number
|
||||
bool is_number() const noexcept;
|
||||
bool is_string() const noexcept;
|
||||
bool is_array() const noexcept;
|
||||
@ -95,7 +105,8 @@ namespace vcpkg::Json
|
||||
|
||||
// a.x() asserts when !a.is_x()
|
||||
bool boolean() const noexcept;
|
||||
int64_t number() const noexcept;
|
||||
int64_t integer() const noexcept;
|
||||
double number() const noexcept;
|
||||
StringView string() const noexcept;
|
||||
|
||||
const Array& array() const noexcept;
|
||||
@ -104,18 +115,13 @@ namespace vcpkg::Json
|
||||
const Object& object() const noexcept;
|
||||
Object& object() noexcept;
|
||||
|
||||
Value(Value&&) noexcept;
|
||||
Value& operator=(Value&&) noexcept;
|
||||
~Value();
|
||||
|
||||
Value() noexcept; // equivalent to Value::null()
|
||||
static Value null(std::nullptr_t) noexcept;
|
||||
static Value boolean(bool) noexcept;
|
||||
static Value number(int64_t i) noexcept;
|
||||
static Value integer(int64_t i) noexcept;
|
||||
static Value number(double d) noexcept;
|
||||
static Value string(StringView) noexcept;
|
||||
static Value array(Array&&) noexcept;
|
||||
static Value object(Object&&) noexcept;
|
||||
Value clone() const noexcept;
|
||||
|
||||
private:
|
||||
friend struct impl::ValueImpl;
|
||||
@ -128,11 +134,24 @@ namespace vcpkg::Json
|
||||
using underlying_t = std::vector<Value>;
|
||||
|
||||
public:
|
||||
Array() = default;
|
||||
Array(Array const&) = delete;
|
||||
Array(Array&&) = default;
|
||||
Array& operator=(Array const&) = delete;
|
||||
Array& operator=(Array&&) = default;
|
||||
~Array() = default;
|
||||
|
||||
Array clone() const noexcept;
|
||||
|
||||
using iterator = underlying_t::iterator;
|
||||
using const_iterator = underlying_t::const_iterator;
|
||||
|
||||
void push_back(Value&& value) { this->underlying_.push_back(std::move(value)); }
|
||||
void insert_before(iterator it, Value&& value) { this->underlying_.insert(it, std::move(value)); }
|
||||
Value& push_back(Value&& value);
|
||||
Object& push_back(Object&& value);
|
||||
Array& push_back(Array&& value);
|
||||
Value& insert_before(iterator it, Value&& value);
|
||||
Object& insert_before(iterator it, Object&& value);
|
||||
Array& insert_before(iterator it, Array&& value);
|
||||
|
||||
std::size_t size() const noexcept { return this->underlying_.size(); }
|
||||
|
||||
@ -148,8 +167,6 @@ namespace vcpkg::Json
|
||||
return this->underlying_[idx];
|
||||
}
|
||||
|
||||
Array clone() const noexcept;
|
||||
|
||||
iterator begin() { return underlying_.begin(); }
|
||||
iterator end() { return underlying_.end(); }
|
||||
const_iterator begin() const { return cbegin(); }
|
||||
@ -160,7 +177,6 @@ namespace vcpkg::Json
|
||||
private:
|
||||
underlying_t underlying_;
|
||||
};
|
||||
|
||||
struct Object
|
||||
{
|
||||
private:
|
||||
@ -169,12 +185,26 @@ namespace vcpkg::Json
|
||||
underlying_t::const_iterator internal_find_key(StringView key) const noexcept;
|
||||
|
||||
public:
|
||||
// these are here for better diagnostics
|
||||
Object() = default;
|
||||
Object(Object const&) = delete;
|
||||
Object(Object&&) = default;
|
||||
Object& operator=(Object const&) = delete;
|
||||
Object& operator=(Object&&) = default;
|
||||
~Object() = default;
|
||||
|
||||
Object clone() const noexcept;
|
||||
|
||||
// asserts if the key is found
|
||||
void insert(std::string key, Value value) noexcept;
|
||||
Value& insert(std::string key, Value&& value);
|
||||
Object& insert(std::string key, Object&& value);
|
||||
Array& insert(std::string key, Array&& value);
|
||||
|
||||
// replaces the value if the key is found, otherwise inserts a new
|
||||
// value.
|
||||
void insert_or_replace(std::string key, Value value) noexcept;
|
||||
Value& insert_or_replace(std::string key, Value&& value);
|
||||
Object& insert_or_replace(std::string key, Object&& value);
|
||||
Array& insert_or_replace(std::string key, Array&& value);
|
||||
|
||||
// returns whether the key existed
|
||||
bool remove(StringView key) noexcept;
|
||||
@ -200,8 +230,6 @@ namespace vcpkg::Json
|
||||
|
||||
std::size_t size() const noexcept { return this->underlying_.size(); }
|
||||
|
||||
Object clone() const noexcept;
|
||||
|
||||
struct const_iterator
|
||||
{
|
||||
using value_type = std::pair<StringView, const Value&>;
|
||||
@ -245,6 +273,9 @@ namespace vcpkg::Json
|
||||
const Files::Filesystem&, const fs::path&, std::error_code& ec) noexcept;
|
||||
ExpectedT<std::pair<Value, JsonStyle>, std::unique_ptr<Parse::IParseError>> parse(
|
||||
StringView text, const fs::path& filepath = "") noexcept;
|
||||
std::string stringify(const Value&, JsonStyle style) noexcept;
|
||||
|
||||
std::string stringify(const Value&, JsonStyle style);
|
||||
std::string stringify(const Object&, JsonStyle style);
|
||||
std::string stringify(const Array&, JsonStyle style);
|
||||
|
||||
}
|
||||
|
@ -10,6 +10,7 @@ namespace vcpkg::Metrics
|
||||
{
|
||||
void set_send_metrics(bool should_send_metrics);
|
||||
void set_print_metrics(bool should_print_metrics);
|
||||
void set_disabled(bool disabled);
|
||||
void set_user_information(const std::string& user_id, const std::string& first_use_time);
|
||||
static void init_user_information(std::string& user_id, std::string& first_use_time);
|
||||
|
||||
@ -17,6 +18,8 @@ namespace vcpkg::Metrics
|
||||
void track_buildtime(const std::string& name, double value);
|
||||
void track_property(const std::string& name, const std::string& value);
|
||||
|
||||
bool metrics_enabled();
|
||||
|
||||
void upload(const std::string& payload);
|
||||
void flush();
|
||||
};
|
||||
@ -24,5 +27,4 @@ namespace vcpkg::Metrics
|
||||
extern Util::LockGuarded<Metrics> g_metrics;
|
||||
|
||||
std::string get_MAC_user();
|
||||
bool get_compiled_metrics_enabled();
|
||||
}
|
||||
|
@ -94,6 +94,8 @@ namespace vcpkg
|
||||
std::vector<std::string> binarysources;
|
||||
Optional<bool> debug = nullopt;
|
||||
Optional<bool> sendmetrics = nullopt;
|
||||
// fully disable metrics -- both printing and sending
|
||||
Optional<bool> disable_metrics = nullopt;
|
||||
Optional<bool> printmetrics = nullopt;
|
||||
|
||||
// feature flags
|
||||
|
@ -4,6 +4,8 @@
|
||||
#include <vcpkg/base/json.h>
|
||||
#include <vcpkg/base/unicode.h>
|
||||
|
||||
#include "math.h"
|
||||
|
||||
// TODO: remove this once we switch to C++20 completely
|
||||
// This is the worst, but we also can't really deal with it any other way.
|
||||
#if __cpp_char8_t
|
||||
@ -57,9 +59,7 @@ TEST_CASE ("JSON parse strings", "[json]")
|
||||
REQUIRE(res.get()->first.is_string());
|
||||
REQUIRE(res.get()->first.string() == "\xED\xA0\x80");
|
||||
|
||||
const auto make_json_string = [] (vcpkg::StringView sv) {
|
||||
return '"' + sv.to_string() + '"';
|
||||
};
|
||||
const auto make_json_string = [](vcpkg::StringView sv) { return '"' + sv.to_string() + '"'; };
|
||||
const vcpkg::StringView radical = U8_STR("⎷");
|
||||
const vcpkg::StringView grin = U8_STR("😁");
|
||||
|
||||
@ -79,28 +79,51 @@ TEST_CASE ("JSON parse strings", "[json]")
|
||||
REQUIRE(res.get()->first.string() == grin);
|
||||
}
|
||||
|
||||
TEST_CASE ("JSON parse numbers", "[json]")
|
||||
TEST_CASE ("JSON parse integers", "[json]")
|
||||
{
|
||||
auto res = Json::parse("0");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == 0);
|
||||
REQUIRE(res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.integer() == 0);
|
||||
res = Json::parse("12345");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == 12345);
|
||||
REQUIRE(res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.integer() == 12345);
|
||||
res = Json::parse("-12345");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == -12345);
|
||||
REQUIRE(res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.integer() == -12345);
|
||||
res = Json::parse("9223372036854775807"); // INT64_MAX
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == 9223372036854775807);
|
||||
REQUIRE(res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.integer() == 9223372036854775807);
|
||||
res = Json::parse("-9223372036854775808");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.integer() == (-9223372036854775807 - 1)); // INT64_MIN (C++'s parser is fun)
|
||||
}
|
||||
|
||||
TEST_CASE ("JSON parse floats", "[json]")
|
||||
{
|
||||
auto res = Json::parse("0.0");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == (-9223372036854775807 - 1)); // INT64_MIN (C++'s parser is fun)
|
||||
REQUIRE(!res.get()->first.is_integer());
|
||||
REQUIRE(res.get()->first.number() == 0.0);
|
||||
REQUIRE(!signbit(res.get()->first.number()));
|
||||
res = Json::parse("-0.0");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE(res.get()->first.number() == 0.0);
|
||||
REQUIRE(signbit(res.get()->first.number()));
|
||||
res = Json::parse("12345.6789");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE_THAT(res.get()->first.number(), Catch::WithinULP(12345.6789, 3));
|
||||
res = Json::parse("-12345.6789");
|
||||
REQUIRE(res);
|
||||
REQUIRE(res.get()->first.is_number());
|
||||
REQUIRE_THAT(res.get()->first.number(), Catch::WithinULP(-12345.6789, 3));
|
||||
}
|
||||
|
||||
TEST_CASE ("JSON parse arrays", "[json]")
|
||||
@ -116,18 +139,18 @@ TEST_CASE ("JSON parse arrays", "[json]")
|
||||
val = std::move(res.get()->first);
|
||||
REQUIRE(val.is_array());
|
||||
REQUIRE(val.array().size() == 1);
|
||||
REQUIRE(val.array()[0].is_number());
|
||||
REQUIRE(val.array()[0].number() == 123);
|
||||
REQUIRE(val.array()[0].is_integer());
|
||||
REQUIRE(val.array()[0].integer() == 123);
|
||||
|
||||
res = Json::parse("[123, 456]");
|
||||
REQUIRE(res);
|
||||
val = std::move(res.get()->first);
|
||||
REQUIRE(val.is_array());
|
||||
REQUIRE(val.array().size() == 2);
|
||||
REQUIRE(val.array()[0].is_number());
|
||||
REQUIRE(val.array()[0].number() == 123);
|
||||
REQUIRE(val.array()[1].is_number());
|
||||
REQUIRE(val.array()[1].number() == 456);
|
||||
REQUIRE(val.array()[0].is_integer());
|
||||
REQUIRE(val.array()[0].integer() == 123);
|
||||
REQUIRE(val.array()[1].is_integer());
|
||||
REQUIRE(val.array()[1].integer() == 456);
|
||||
|
||||
res = Json::parse("[123, 456, [null]]");
|
||||
REQUIRE(res);
|
||||
@ -155,5 +178,8 @@ TEST_CASE ("JSON parse full file", "[json]")
|
||||
;
|
||||
|
||||
auto res = Json::parse(json);
|
||||
if (!res) {
|
||||
std::cerr << res.error()->format() << '\n';
|
||||
}
|
||||
REQUIRE(res);
|
||||
}
|
||||
|
@ -333,6 +333,11 @@ int main(const int argc, const char* const* const argv)
|
||||
auto flags = Strings::split(*v, ',');
|
||||
if (std::find(flags.begin(), flags.end(), "binarycaching") != flags.end()) GlobalState::g_binary_caching = true;
|
||||
}
|
||||
const auto vcpkg_disable_metrics_env = System::get_environment_variable("VCPKG_DISABLE_METRICS");
|
||||
if (vcpkg_disable_metrics_env.has_value())
|
||||
{
|
||||
Metrics::g_metrics.lock()->set_disabled(true);
|
||||
}
|
||||
|
||||
const VcpkgCmdArguments args = VcpkgCmdArguments::create_from_command_line(argc, argv);
|
||||
|
||||
@ -340,8 +345,20 @@ int main(const int argc, const char* const* const argv)
|
||||
|
||||
if (const auto p = args.printmetrics.get()) Metrics::g_metrics.lock()->set_print_metrics(*p);
|
||||
if (const auto p = args.sendmetrics.get()) Metrics::g_metrics.lock()->set_send_metrics(*p);
|
||||
if (const auto p = args.disable_metrics.get()) Metrics::g_metrics.lock()->set_disabled(*p);
|
||||
if (const auto p = args.debug.get()) Debug::g_debugging = *p;
|
||||
|
||||
if (args.sendmetrics.has_value() && !Metrics::g_metrics.lock()->metrics_enabled())
|
||||
{
|
||||
System::print2(System::Color::warning,
|
||||
"Warning: passed either --sendmetrics or --no-sendmetrics, but metrics are disabled.\n");
|
||||
}
|
||||
if (args.printmetrics.has_value() && !Metrics::g_metrics.lock()->metrics_enabled())
|
||||
{
|
||||
System::print2(System::Color::warning,
|
||||
"Warning: passed either --printmetrics or --no-printmetrics, but metrics are disabled.\n");
|
||||
}
|
||||
|
||||
if (Debug::g_debugging)
|
||||
{
|
||||
inner(args);
|
||||
|
@ -5,6 +5,8 @@
|
||||
#include <vcpkg/base/system.debug.h>
|
||||
#include <vcpkg/base/unicode.h>
|
||||
|
||||
#include <inttypes.h>
|
||||
|
||||
namespace vcpkg::Json
|
||||
{
|
||||
using VK = ValueKind;
|
||||
@ -23,7 +25,8 @@ namespace vcpkg::Json
|
||||
{
|
||||
std::nullptr_t null;
|
||||
bool boolean;
|
||||
int64_t number;
|
||||
int64_t integer;
|
||||
double number;
|
||||
std::string string;
|
||||
Array array;
|
||||
Object object;
|
||||
@ -31,7 +34,8 @@ namespace vcpkg::Json
|
||||
|
||||
ValueImpl(ValueKindConstant<VK::Null> vk, std::nullptr_t) : tag(vk), null() { }
|
||||
ValueImpl(ValueKindConstant<VK::Boolean> vk, bool b) : tag(vk), boolean(b) { }
|
||||
ValueImpl(ValueKindConstant<VK::Number> vk, int64_t i) : tag(vk), number(i) { }
|
||||
ValueImpl(ValueKindConstant<VK::Integer> vk, int64_t i) : tag(vk), integer(i) { }
|
||||
ValueImpl(ValueKindConstant<VK::Number> vk, double d) : tag(vk), number(d) { }
|
||||
ValueImpl(ValueKindConstant<VK::String> vk, std::string&& s) : tag(vk), string(std::move(s)) { }
|
||||
ValueImpl(ValueKindConstant<VK::Array> vk, Array&& arr) : tag(vk), array(std::move(arr)) { }
|
||||
ValueImpl(ValueKindConstant<VK::Object> vk, Object&& obj) : tag(vk), object(std::move(obj)) { }
|
||||
@ -42,6 +46,7 @@ namespace vcpkg::Json
|
||||
{
|
||||
case VK::Null: return internal_assign(VK::Null, &ValueImpl::null, other);
|
||||
case VK::Boolean: return internal_assign(VK::Boolean, &ValueImpl::boolean, other);
|
||||
case VK::Integer: return internal_assign(VK::Integer, &ValueImpl::integer, other);
|
||||
case VK::Number: return internal_assign(VK::Number, &ValueImpl::number, other);
|
||||
case VK::String: return internal_assign(VK::String, &ValueImpl::string, other);
|
||||
case VK::Array: return internal_assign(VK::Array, &ValueImpl::array, other);
|
||||
@ -101,7 +106,12 @@ namespace vcpkg::Json
|
||||
|
||||
bool Value::is_null() const noexcept { return kind() == VK::Null; }
|
||||
bool Value::is_boolean() const noexcept { return kind() == VK::Boolean; }
|
||||
bool Value::is_number() const noexcept { return kind() == VK::Number; }
|
||||
bool Value::is_integer() const noexcept { return kind() == VK::Integer; }
|
||||
bool Value::is_number() const noexcept
|
||||
{
|
||||
auto k = kind();
|
||||
return k == VK::Integer || k == VK::Number;
|
||||
}
|
||||
bool Value::is_string() const noexcept { return kind() == VK::String; }
|
||||
bool Value::is_array() const noexcept { return kind() == VK::Array; }
|
||||
bool Value::is_object() const noexcept { return kind() == VK::Object; }
|
||||
@ -111,10 +121,22 @@ namespace vcpkg::Json
|
||||
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_boolean());
|
||||
return underlying_->boolean;
|
||||
}
|
||||
int64_t Value::number() const noexcept
|
||||
int64_t Value::integer() const noexcept
|
||||
{
|
||||
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_number());
|
||||
return underlying_->number;
|
||||
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, is_integer());
|
||||
return underlying_->integer;
|
||||
}
|
||||
double Value::number() const noexcept
|
||||
{
|
||||
auto k = kind();
|
||||
if (k == VK::Number)
|
||||
{
|
||||
return underlying_->number;
|
||||
}
|
||||
else
|
||||
{
|
||||
return static_cast<double>(integer());
|
||||
}
|
||||
}
|
||||
StringView Value::string() const noexcept
|
||||
{
|
||||
@ -155,6 +177,7 @@ namespace vcpkg::Json
|
||||
{
|
||||
case ValueKind::Null: return Value::null(nullptr);
|
||||
case ValueKind::Boolean: return Value::boolean(boolean());
|
||||
case ValueKind::Integer: return Value::integer(integer());
|
||||
case ValueKind::Number: return Value::number(number());
|
||||
case ValueKind::String: return Value::string(string());
|
||||
case ValueKind::Array: return Value::array(array().clone());
|
||||
@ -170,10 +193,17 @@ namespace vcpkg::Json
|
||||
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Boolean>(), b);
|
||||
return val;
|
||||
}
|
||||
Value Value::number(int64_t i) noexcept
|
||||
Value Value::integer(int64_t i) noexcept
|
||||
{
|
||||
Value val;
|
||||
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Number>(), i);
|
||||
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Integer>(), i);
|
||||
return val;
|
||||
}
|
||||
Value Value::number(double d) noexcept
|
||||
{
|
||||
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, isfinite(d));
|
||||
Value val;
|
||||
val.underlying_ = std::make_unique<ValueImpl>(ValueKindConstant<VK::Number>(), d);
|
||||
return val;
|
||||
}
|
||||
Value Value::string(StringView sv) noexcept
|
||||
@ -211,25 +241,66 @@ namespace vcpkg::Json
|
||||
}
|
||||
return arr;
|
||||
}
|
||||
|
||||
Value& Array::push_back(Value&& value)
|
||||
{
|
||||
underlying_.push_back(std::move(value));
|
||||
return underlying_.back();
|
||||
}
|
||||
Object& Array::push_back(Object&& obj) { return push_back(Value::object(std::move(obj))).object(); }
|
||||
Array& Array::push_back(Array&& arr) { return push_back(Value::array(std::move(arr))).array(); }
|
||||
Value& Array::insert_before(iterator it, Value&& value)
|
||||
{
|
||||
size_t index = it - underlying_.begin();
|
||||
underlying_.insert(it, std::move(value));
|
||||
return underlying_[index];
|
||||
}
|
||||
Object& Array::insert_before(iterator it, Object&& obj)
|
||||
{
|
||||
return insert_before(it, Value::object(std::move(obj))).object();
|
||||
}
|
||||
Array& Array::insert_before(iterator it, Array&& arr)
|
||||
{
|
||||
return insert_before(it, Value::array(std::move(arr))).array();
|
||||
}
|
||||
// } struct Array
|
||||
// struct Object {
|
||||
void Object::insert(std::string key, Value value) noexcept
|
||||
Value& Object::insert(std::string key, Value&& value)
|
||||
{
|
||||
vcpkg::Checks::check_exit(VCPKG_LINE_INFO, !contains(key));
|
||||
underlying_.push_back(std::make_pair(std::move(key), std::move(value)));
|
||||
underlying_.push_back({std::move(key), std::move(value)});
|
||||
return underlying_.back().second;
|
||||
}
|
||||
void Object::insert_or_replace(std::string key, Value value) noexcept
|
||||
Array& Object::insert(std::string key, Array&& value)
|
||||
{
|
||||
return insert(std::move(key), Value::array(std::move(value))).array();
|
||||
}
|
||||
Object& Object::insert(std::string key, Object&& value)
|
||||
{
|
||||
return insert(std::move(key), Value::object(std::move(value))).object();
|
||||
}
|
||||
Value& Object::insert_or_replace(std::string key, Value&& value)
|
||||
{
|
||||
auto v = get(key);
|
||||
if (v)
|
||||
{
|
||||
*v = std::move(value);
|
||||
return *v;
|
||||
}
|
||||
else
|
||||
{
|
||||
underlying_.push_back(std::make_pair(std::move(key), std::move(value)));
|
||||
underlying_.push_back({std::move(key), std::move(value)});
|
||||
return underlying_.back().second;
|
||||
}
|
||||
}
|
||||
Array& Object::insert_or_replace(std::string key, Array&& value)
|
||||
{
|
||||
return insert_or_replace(std::move(key), Value::array(std::move(value))).array();
|
||||
}
|
||||
Object& Object::insert_or_replace(std::string key, Object&& value)
|
||||
{
|
||||
return insert_or_replace(std::move(key), Value::object(std::move(value))).object();
|
||||
}
|
||||
|
||||
auto Object::internal_find_key(StringView key) const noexcept -> underlying_t::const_iterator
|
||||
{
|
||||
@ -463,11 +534,15 @@ namespace vcpkg::Json
|
||||
Value parse_number() noexcept
|
||||
{
|
||||
Checks::check_exit(VCPKG_LINE_INFO, is_number_start(cur()));
|
||||
bool negative = false;
|
||||
|
||||
bool floating = false;
|
||||
bool negative = false; // negative & 0 -> floating, so keep track of it
|
||||
std::string number_to_parse;
|
||||
|
||||
char32_t current = cur();
|
||||
if (cur() == '-')
|
||||
{
|
||||
number_to_parse.push_back('-');
|
||||
negative = true;
|
||||
current = next();
|
||||
if (current == Unicode::end_of_file)
|
||||
@ -480,54 +555,81 @@ namespace vcpkg::Json
|
||||
if (current == '0')
|
||||
{
|
||||
current = next();
|
||||
if (current != Unicode::end_of_file)
|
||||
if (current == '.')
|
||||
{
|
||||
if (is_digit(current))
|
||||
{
|
||||
add_error("Unexpected digits after a leading zero");
|
||||
}
|
||||
if (current == '.')
|
||||
{
|
||||
add_error("Found a `.` -- this JSON implementation does not support floating point");
|
||||
}
|
||||
number_to_parse.append("0.");
|
||||
floating = true;
|
||||
current = next();
|
||||
}
|
||||
return Value::number(0);
|
||||
}
|
||||
|
||||
// parse as negative so that someone can write INT64_MIN; otherwise, they'd only be able to get
|
||||
// -INT64_MAX = INT64_MIN + 1
|
||||
constexpr auto min_value = std::numeric_limits<int64_t>::min();
|
||||
int64_t result = 0;
|
||||
while (current != Unicode::end_of_file && is_digit(current))
|
||||
{
|
||||
const int digit = current - '0';
|
||||
// result * 10 - digit < min_value : remember that result < 0
|
||||
if (result < (min_value + digit) / 10)
|
||||
else if (is_digit(current))
|
||||
{
|
||||
add_error("Number is too big for an int64_t");
|
||||
add_error("Unexpected digits after a leading zero");
|
||||
return Value();
|
||||
}
|
||||
result *= 10;
|
||||
result -= digit;
|
||||
else
|
||||
{
|
||||
if (negative)
|
||||
{
|
||||
return Value::number(-0.0);
|
||||
}
|
||||
else
|
||||
{
|
||||
return Value::integer(0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while (is_digit(current))
|
||||
{
|
||||
number_to_parse.push_back(static_cast<char>(current));
|
||||
current = next();
|
||||
}
|
||||
if (current == '.')
|
||||
if (!floating && current == '.')
|
||||
{
|
||||
add_error("Found a `.` -- this JSON implementation doesn't support floating point");
|
||||
return Value();
|
||||
}
|
||||
|
||||
if (!negative)
|
||||
{
|
||||
if (result == min_value)
|
||||
floating = true;
|
||||
number_to_parse.push_back('.');
|
||||
current = next();
|
||||
if (!is_digit(current))
|
||||
{
|
||||
add_error("Number is too big for a uint64_t");
|
||||
add_error("Expected digits after the decimal point");
|
||||
return Value();
|
||||
}
|
||||
result = -result;
|
||||
while (is_digit(current))
|
||||
{
|
||||
number_to_parse.push_back(static_cast<char>(current));
|
||||
current = next();
|
||||
}
|
||||
}
|
||||
|
||||
return Value::number(result);
|
||||
#ifdef _MSC_VER
|
||||
#define SCANF sscanf_s
|
||||
#else
|
||||
#define SCANF sscanf
|
||||
#endif
|
||||
|
||||
// TODO: switch to `from_chars` once we are able to remove support for old compilers
|
||||
if (floating)
|
||||
{
|
||||
double res;
|
||||
if (SCANF(number_to_parse.c_str(), "%lf", &res) != 1)
|
||||
{
|
||||
add_error(Strings::format("Invalid floating point constant: %s", number_to_parse));
|
||||
return Value();
|
||||
}
|
||||
return Value::number(res);
|
||||
}
|
||||
else
|
||||
{
|
||||
int64_t res;
|
||||
if (SCANF(number_to_parse.c_str(), "%" SCNd64, &res) != 1)
|
||||
{
|
||||
add_error(Strings::format("Invalid integer constant: %s", number_to_parse));
|
||||
return Value();
|
||||
}
|
||||
return Value::integer(res);
|
||||
}
|
||||
|
||||
#undef SCANF
|
||||
}
|
||||
|
||||
Value parse_keyword() noexcept
|
||||
@ -630,7 +732,7 @@ namespace vcpkg::Json
|
||||
|
||||
auto current = cur();
|
||||
|
||||
auto res = std::make_pair(std::string(""), Value());
|
||||
std::pair<std::string, Value> res = {std::string(""), Value()};
|
||||
|
||||
if (current == Unicode::end_of_file)
|
||||
{
|
||||
@ -804,118 +906,120 @@ namespace vcpkg::Json
|
||||
}
|
||||
// } auto parse()
|
||||
|
||||
// auto stringify() {
|
||||
static std::string& append_unicode_escape(std::string& s, char16_t code_unit)
|
||||
namespace
|
||||
{
|
||||
s.append("\\u");
|
||||
|
||||
// AFAIK, there's no standard way of doing this?
|
||||
constexpr const char hex_digit[16] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
s.push_back(hex_digit[(code_unit >> 12) & 0x0F]);
|
||||
s.push_back(hex_digit[(code_unit >> 8) & 0x0F]);
|
||||
s.push_back(hex_digit[(code_unit >> 4) & 0x0F]);
|
||||
s.push_back(hex_digit[(code_unit >> 0) & 0x0F]);
|
||||
|
||||
return s;
|
||||
}
|
||||
|
||||
// taken from the ECMAScript 2020 standard, 24.5.2.2: Runtime Semantics: QuoteJSONString
|
||||
static std::string& append_quoted_json_string(std::string& product, StringView sv)
|
||||
{
|
||||
// Table 66: JSON Single Character Escape Sequences
|
||||
constexpr static std::array<std::pair<char32_t, const char*>, 7> escape_sequences = {
|
||||
std::make_pair(0x0008, R"(\b)"), // BACKSPACE
|
||||
std::make_pair(0x0009, R"(\t)"), // CHARACTER TABULATION
|
||||
std::make_pair(0x000A, R"(\n)"), // LINE FEED (LF)
|
||||
std::make_pair(0x000C, R"(\f)"), // FORM FEED (FF)
|
||||
std::make_pair(0x000D, R"(\r)"), // CARRIAGE RETURN (CR)
|
||||
std::make_pair(0x0022, R"(\")"), // QUOTATION MARK
|
||||
std::make_pair(0x005C, R"(\\)") // REVERSE SOLIDUS
|
||||
};
|
||||
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
|
||||
product.push_back('"');
|
||||
|
||||
// 2. For each code point C in ! UTF16DecodeString(value), do
|
||||
// (note that we use utf8 instead of utf16)
|
||||
for (auto code_point : Unicode::Utf8Decoder(sv.begin(), sv.end()))
|
||||
struct Stringifier
|
||||
{
|
||||
bool matched = false; // early exit boolean
|
||||
// a. If C is listed in the "Code Point" column of Table 66, then
|
||||
for (auto pr : escape_sequences)
|
||||
JsonStyle style;
|
||||
std::string& buffer;
|
||||
|
||||
void append_indent(int indent)
|
||||
{
|
||||
// i. Set product to the string-concatenation of product and the escape sequence for C as specified in
|
||||
// the "Escape Sequence" column of the corresponding row.
|
||||
if (code_point == pr.first)
|
||||
if (style.use_tabs())
|
||||
{
|
||||
product.append(pr.second);
|
||||
matched = true;
|
||||
break;
|
||||
buffer.append(indent, '\t');
|
||||
}
|
||||
}
|
||||
if (matched) break;
|
||||
else
|
||||
{
|
||||
buffer.append(indent * style.spaces(), ' ');
|
||||
}
|
||||
};
|
||||
|
||||
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as a
|
||||
// leading surrogate or trailing surrogate, then
|
||||
if (code_point < 0x0020 || Unicode::utf16_is_surrogate_code_point(code_point))
|
||||
void append_unicode_escape(char16_t code_unit)
|
||||
{
|
||||
// i. Let unit be the code unit whose numeric value is that of C.
|
||||
// ii. Set product to the string-concatenation of product and UnicodeEscape(unit).
|
||||
append_unicode_escape(product, static_cast<char16_t>(code_point));
|
||||
break;
|
||||
buffer.append("\\u");
|
||||
|
||||
// AFAIK, there's no standard way of doing this?
|
||||
constexpr const char hex_digit[16] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
|
||||
|
||||
buffer.push_back(hex_digit[(code_unit >> 12) & 0x0F]);
|
||||
buffer.push_back(hex_digit[(code_unit >> 8) & 0x0F]);
|
||||
buffer.push_back(hex_digit[(code_unit >> 4) & 0x0F]);
|
||||
buffer.push_back(hex_digit[(code_unit >> 0) & 0x0F]);
|
||||
}
|
||||
|
||||
// c. Else,
|
||||
// i. Set product to the string-concatenation of product and the UTF16Encoding of C.
|
||||
// (again, we use utf-8 here instead)
|
||||
Unicode::utf8_append_code_point(product, code_point);
|
||||
}
|
||||
// taken from the ECMAScript 2020 standard, 24.5.2.2: Runtime Semantics: QuoteJSONString
|
||||
void append_quoted_json_string(StringView sv)
|
||||
{
|
||||
// Table 66: JSON Single Character Escape Sequences
|
||||
constexpr static std::array<std::pair<char32_t, const char*>, 7> escape_sequences = {{
|
||||
{0x0008, R"(\b)"}, // BACKSPACE
|
||||
{0x0009, R"(\t)"}, // CHARACTER TABULATION
|
||||
{0x000A, R"(\n)"}, // LINE FEED (LF)
|
||||
{0x000C, R"(\f)"}, // FORM FEED (FF)
|
||||
{0x000D, R"(\r)"}, // CARRIAGE RETURN (CR)
|
||||
{0x0022, R"(\")"}, // QUOTATION MARK
|
||||
{0x005C, R"(\\)"} // REVERSE SOLIDUS
|
||||
}};
|
||||
// 1. Let product be the String value consisting solely of the code unit 0x0022 (QUOTATION MARK).
|
||||
buffer.push_back('"');
|
||||
|
||||
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
|
||||
product.push_back('"');
|
||||
// 2. For each code point C in ! UTF16DecodeString(value), do
|
||||
// (note that we use utf8 instead of utf16)
|
||||
for (auto code_point : Unicode::Utf8Decoder(sv.begin(), sv.end()))
|
||||
{
|
||||
// a. If C is listed in the "Code Point" column of Table 66, then
|
||||
const auto match = std::find_if(begin(escape_sequences), end(escape_sequences), [code_point](const std::pair<char32_t, const char*>& attempt) {
|
||||
return attempt.first == code_point;
|
||||
});
|
||||
// i. Set product to the string-concatenation of product and the escape sequence for C as
|
||||
// specified in the "Escape Sequence" column of the corresponding row.
|
||||
if (match != end(escape_sequences)) {
|
||||
buffer.append(match->second);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 4. Return product.
|
||||
return product;
|
||||
}
|
||||
// b. Else if C has a numeric value less than 0x0020 (SPACE), or if C has the same numeric value as
|
||||
// a leading surrogate or trailing surrogate, then
|
||||
if (code_point < 0x0020 || Unicode::utf16_is_surrogate_code_point(code_point))
|
||||
{
|
||||
// i. Let unit be the code unit whose numeric value is that of C.
|
||||
// ii. Set product to the string-concatenation of product and UnicodeEscape(unit).
|
||||
append_unicode_escape(static_cast<char16_t>(code_point));
|
||||
break;
|
||||
}
|
||||
|
||||
static std::string quote_json_string(StringView sv)
|
||||
{
|
||||
std::string product;
|
||||
append_quoted_json_string(product, sv);
|
||||
return product;
|
||||
}
|
||||
// c. Else,
|
||||
// i. Set product to the string-concatenation of product and the UTF16Encoding of C.
|
||||
// (again, we use utf-8 here instead)
|
||||
Unicode::utf8_append_code_point(buffer, code_point);
|
||||
}
|
||||
|
||||
static void internal_stringify(const Value& value, JsonStyle style, std::string& buffer, int current_indent)
|
||||
{
|
||||
const auto append_indent = [&](int indent) {
|
||||
if (style.use_tabs())
|
||||
{
|
||||
buffer.append(indent, '\t');
|
||||
// 3. Set product to the string-concatenation of product and the code unit 0x0022 (QUOTATION MARK).
|
||||
buffer.push_back('"');
|
||||
}
|
||||
else
|
||||
|
||||
void stringify_object(const Object& obj, int current_indent)
|
||||
{
|
||||
buffer.append(indent * style.spaces(), ' ');
|
||||
buffer.push_back('{');
|
||||
if (obj.size() != 0)
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
for (const auto& el : obj)
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
buffer.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent + 1);
|
||||
|
||||
append_quoted_json_string(el.first);
|
||||
buffer.append(": ");
|
||||
stringify(el.second, current_indent + 1);
|
||||
}
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent);
|
||||
}
|
||||
buffer.push_back('}');
|
||||
}
|
||||
};
|
||||
switch (value.kind())
|
||||
{
|
||||
case VK::Null: buffer.append("null"); break;
|
||||
case VK::Boolean:
|
||||
|
||||
void stringify_array(const Array& arr, int current_indent)
|
||||
{
|
||||
auto v = value.boolean();
|
||||
buffer.append(v ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
case VK::Number: buffer.append(std::to_string(value.number())); break;
|
||||
case VK::String:
|
||||
{
|
||||
append_quoted_json_string(buffer, value.string());
|
||||
break;
|
||||
}
|
||||
case VK::Array:
|
||||
{
|
||||
const auto& arr = value.array();
|
||||
buffer.push_back('[');
|
||||
if (arr.size() == 0)
|
||||
{
|
||||
@ -936,51 +1040,64 @@ namespace vcpkg::Json
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent + 1);
|
||||
|
||||
internal_stringify(el, style, buffer, current_indent + 1);
|
||||
stringify(el, current_indent + 1);
|
||||
}
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent);
|
||||
buffer.push_back(']');
|
||||
}
|
||||
break;
|
||||
}
|
||||
case VK::Object:
|
||||
|
||||
void stringify(const Value& value, int current_indent)
|
||||
{
|
||||
const auto& obj = value.object();
|
||||
buffer.push_back('{');
|
||||
if (obj.size() != 0)
|
||||
switch (value.kind())
|
||||
{
|
||||
bool first = true;
|
||||
|
||||
for (const auto& el : obj)
|
||||
case VK::Null: buffer.append("null"); break;
|
||||
case VK::Boolean:
|
||||
{
|
||||
if (!first)
|
||||
{
|
||||
buffer.push_back(',');
|
||||
}
|
||||
first = false;
|
||||
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent + 1);
|
||||
|
||||
auto key = quote_json_string(el.first);
|
||||
buffer.append(key.begin(), key.end());
|
||||
buffer.append(": ");
|
||||
internal_stringify(el.second, style, buffer, current_indent + 1);
|
||||
auto v = value.boolean();
|
||||
buffer.append(v ? "true" : "false");
|
||||
break;
|
||||
}
|
||||
// TODO: switch to `to_chars` once we are able to remove support for old compilers
|
||||
case VK::Integer: buffer.append(std::to_string(value.integer())); break;
|
||||
case VK::Number: buffer.append(std::to_string(value.number())); break;
|
||||
case VK::String:
|
||||
{
|
||||
append_quoted_json_string(value.string());
|
||||
break;
|
||||
}
|
||||
case VK::Array:
|
||||
{
|
||||
stringify_array(value.array(), current_indent);
|
||||
break;
|
||||
}
|
||||
case VK::Object:
|
||||
{
|
||||
stringify_object(value.object(), current_indent);
|
||||
break;
|
||||
}
|
||||
buffer.append(style.newline());
|
||||
append_indent(current_indent);
|
||||
}
|
||||
buffer.push_back('}');
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
std::string stringify(const Value& value, JsonStyle style) noexcept
|
||||
std::string stringify(const Value& value, JsonStyle style)
|
||||
{
|
||||
std::string res;
|
||||
internal_stringify(value, style, res, 0);
|
||||
Stringifier{style, res}.stringify(value, 0);
|
||||
return res;
|
||||
}
|
||||
std::string stringify(const Object& obj, JsonStyle style)
|
||||
{
|
||||
std::string res;
|
||||
Stringifier{style, res}.stringify_object(obj, 0);
|
||||
return res;
|
||||
}
|
||||
std::string stringify(const Array& arr, JsonStyle style)
|
||||
{
|
||||
std::string res;
|
||||
Stringifier{style, res}.stringify_array(arr, 0);
|
||||
return res;
|
||||
}
|
||||
// } auto stringify()
|
||||
|
@ -237,6 +237,7 @@ namespace vcpkg
|
||||
L"USERDOMAIN_ROAMINGPROFILE",
|
||||
L"USERNAME",
|
||||
L"USERPROFILE",
|
||||
L"VCPKG_DISABLE_METRICS",
|
||||
L"windir",
|
||||
// Enables proxy information to be passed to Curl, the underlying download library in cmake.exe
|
||||
L"http_proxy",
|
||||
@ -558,6 +559,7 @@ VCPKG_MSVC_WARNING(suppress : 6335) // Leaking process information handle 'proce
|
||||
(void)env;
|
||||
Debug::print("system(", cmd_line, ")\n");
|
||||
fflush(nullptr);
|
||||
|
||||
int exit_code = system(cmd_line.c_str());
|
||||
Debug::print(
|
||||
"system() returned ", exit_code, " after ", static_cast<unsigned int>(timer.microseconds()), " us\n");
|
||||
@ -617,6 +619,7 @@ VCPKG_MSVC_WARNING(suppress : 6335) // Leaking process information handle 'proce
|
||||
Debug::print("popen(", actual_cmd_line, ")\n");
|
||||
// Flush stdout before launching external process
|
||||
fflush(stdout);
|
||||
|
||||
const auto pipe = popen(actual_cmd_line.c_str(), "r");
|
||||
if (pipe == nullptr)
|
||||
{
|
||||
|
@ -32,7 +32,7 @@ namespace vcpkg::Commands::Version
|
||||
#ifndef NDEBUG
|
||||
+ std::string("-debug")
|
||||
#endif
|
||||
+ std::string(Metrics::get_compiled_metrics_enabled() ? "" : "-external");
|
||||
;
|
||||
return S_VERSION;
|
||||
}
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
#include <vcpkg/base/chrono.h>
|
||||
#include <vcpkg/base/files.h>
|
||||
#include <vcpkg/base/hash.h>
|
||||
#include <vcpkg/base/json.h>
|
||||
#include <vcpkg/base/strings.h>
|
||||
#include <vcpkg/base/system.process.h>
|
||||
|
||||
@ -29,51 +30,73 @@ namespace vcpkg::Metrics
|
||||
return "";
|
||||
}
|
||||
|
||||
static std::string generate_random_UUID()
|
||||
// note: this ignores the bits of these numbers that would be where format and variant go
|
||||
static std::string uuid_of_integers(uint64_t top, uint64_t bottom)
|
||||
{
|
||||
int part_sizes[] = {8, 4, 4, 4, 12};
|
||||
char uuid[37];
|
||||
memset(uuid, 0, sizeof(uuid));
|
||||
int num;
|
||||
srand(static_cast<int>(time(nullptr)));
|
||||
int index = 0;
|
||||
for (int part = 0; part < 5; part++)
|
||||
// uuid_field_size in bytes, not hex characters
|
||||
constexpr size_t uuid_top_field_size[] = {4, 2, 2};
|
||||
constexpr size_t uuid_bottom_field_size[] = {2, 6};
|
||||
|
||||
// uuid_field_size in hex characters, not bytes
|
||||
constexpr size_t uuid_size = 8 + 1 + 4 + 1 + 4 + 1 + 4 + 1 + 12;
|
||||
|
||||
constexpr static char hex[17] = "0123456789abcdef";
|
||||
constexpr static auto write_byte = [](std::string& res, std::uint8_t bits) {
|
||||
res.push_back(hex[(bits >> 4) & 0x0F]);
|
||||
res.push_back(hex[(bits >> 0) & 0x0F]);
|
||||
};
|
||||
|
||||
// set the version bits to 4
|
||||
top &= 0xFFFF'FFFF'FFFF'0FFFULL;
|
||||
top |= 0x0000'0000'0000'4000ULL;
|
||||
|
||||
// set the variant bits to 2 (variant one)
|
||||
bottom &= 0x3FFF'FFFF'FFFF'FFFFULL;
|
||||
bottom |= 0x8000'0000'0000'0000ULL;
|
||||
|
||||
std::string res;
|
||||
res.reserve(uuid_size);
|
||||
|
||||
bool first = true;
|
||||
size_t start_byte = 0;
|
||||
for (auto field_size : uuid_top_field_size)
|
||||
{
|
||||
if (part > 0)
|
||||
if (!first)
|
||||
{
|
||||
uuid[index] = '-';
|
||||
index++;
|
||||
res.push_back('-');
|
||||
}
|
||||
|
||||
// Generating UUID format version 4
|
||||
// http://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
for (int i = 0; i < part_sizes[part]; i++, index++)
|
||||
first = false;
|
||||
for (size_t i = start_byte; i < start_byte + field_size; ++i)
|
||||
{
|
||||
if (part == 2 && i == 0)
|
||||
{
|
||||
num = 4;
|
||||
}
|
||||
else if (part == 4 && i == 0)
|
||||
{
|
||||
num = (rand() % 4) + 8;
|
||||
}
|
||||
else
|
||||
{
|
||||
num = rand() % 16;
|
||||
}
|
||||
|
||||
if (num < 10)
|
||||
{
|
||||
uuid[index] = static_cast<char>('0' + num);
|
||||
}
|
||||
else
|
||||
{
|
||||
uuid[index] = static_cast<char>('a' + (num - 10));
|
||||
}
|
||||
auto shift = 64 - (i + 1) * 8;
|
||||
write_byte(res, (top >> shift) & 0xFF);
|
||||
}
|
||||
start_byte += field_size;
|
||||
}
|
||||
|
||||
return uuid;
|
||||
start_byte = 0;
|
||||
for (auto field_size : uuid_bottom_field_size)
|
||||
{
|
||||
res.push_back('-');
|
||||
for (size_t i = start_byte; i < start_byte + field_size; ++i)
|
||||
{
|
||||
auto shift = 64 - (i + 1) * 8;
|
||||
write_byte(res, (bottom >> shift) & 0xFF);
|
||||
}
|
||||
start_byte += field_size;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
// UUID format version 4, variant 1
|
||||
// http://en.wikipedia.org/wiki/Universally_unique_identifier
|
||||
// [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}
|
||||
static std::string generate_random_UUID()
|
||||
{
|
||||
std::random_device rnd{};
|
||||
std::uniform_int_distribution<std::uint64_t> uid{};
|
||||
return uuid_of_integers(uid(rnd), uid(rnd));
|
||||
}
|
||||
|
||||
static const std::string& get_session_id()
|
||||
@ -82,37 +105,6 @@ namespace vcpkg::Metrics
|
||||
return ID;
|
||||
}
|
||||
|
||||
static std::string to_json_string(const std::string& str)
|
||||
{
|
||||
std::string encoded = "\"";
|
||||
for (auto&& ch : str)
|
||||
{
|
||||
if (ch == '\\')
|
||||
{
|
||||
encoded.append("\\\\");
|
||||
}
|
||||
else if (ch == '"')
|
||||
{
|
||||
encoded.append("\\\"");
|
||||
}
|
||||
else if (ch < 0x20 || static_cast<unsigned char>(ch) >= 0x80)
|
||||
{
|
||||
// Note: this treats incoming Strings as Latin-1
|
||||
static constexpr const char HEX[16] = {
|
||||
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
|
||||
encoded.append("\\u00");
|
||||
encoded.push_back(HEX[ch / 16]);
|
||||
encoded.push_back(HEX[ch % 16]);
|
||||
}
|
||||
else
|
||||
{
|
||||
encoded.push_back(ch);
|
||||
}
|
||||
}
|
||||
encoded.push_back('"');
|
||||
return encoded;
|
||||
}
|
||||
|
||||
static std::string get_os_version_string()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
@ -150,91 +142,89 @@ namespace vcpkg::Metrics
|
||||
std::string user_id = generate_random_UUID();
|
||||
std::string user_timestamp;
|
||||
std::string timestamp = get_current_date_time();
|
||||
std::string properties;
|
||||
std::string measurements;
|
||||
|
||||
std::vector<std::string> buildtime_names;
|
||||
std::vector<std::string> buildtime_times;
|
||||
Json::Object properties;
|
||||
Json::Object measurements;
|
||||
|
||||
Json::Array buildtime_names;
|
||||
Json::Array buildtime_times;
|
||||
|
||||
void track_property(const std::string& name, const std::string& value)
|
||||
{
|
||||
if (properties.size() != 0) properties.push_back(',');
|
||||
properties.append(to_json_string(name));
|
||||
properties.push_back(':');
|
||||
properties.append(to_json_string(value));
|
||||
properties.insert_or_replace(name, Json::Value::string(value));
|
||||
}
|
||||
|
||||
void track_metric(const std::string& name, double value)
|
||||
{
|
||||
if (measurements.size() != 0) measurements.push_back(',');
|
||||
measurements.append(to_json_string(name));
|
||||
measurements.push_back(':');
|
||||
measurements.append(std::to_string(value));
|
||||
measurements.insert_or_replace(name, Json::Value::number(value));
|
||||
}
|
||||
|
||||
void track_buildtime(const std::string& name, double value)
|
||||
{
|
||||
buildtime_names.push_back(name);
|
||||
buildtime_times.push_back(std::to_string(value));
|
||||
buildtime_names.push_back(Json::Value::string(name));
|
||||
buildtime_times.push_back(Json::Value::number(value));
|
||||
}
|
||||
|
||||
std::string format_event_data_template() const
|
||||
{
|
||||
auto props_plus_buildtimes = properties;
|
||||
auto props_plus_buildtimes = properties.clone();
|
||||
if (buildtime_names.size() > 0)
|
||||
{
|
||||
if (props_plus_buildtimes.size() > 0) props_plus_buildtimes.push_back(',');
|
||||
props_plus_buildtimes.append(Strings::format(R"("buildnames_1": [%s], "buildtimes": [%s])",
|
||||
Strings::join(",", buildtime_names, to_json_string),
|
||||
Strings::join(",", buildtime_times)));
|
||||
props_plus_buildtimes.insert("buildnames_1", Json::Value::array(buildtime_names.clone()));
|
||||
props_plus_buildtimes.insert("buildtimes", Json::Value::array(buildtime_times.clone()));
|
||||
}
|
||||
|
||||
const std::string& session_id = get_session_id();
|
||||
return Strings::format(R"([{
|
||||
"ver": 1,
|
||||
"name": "Microsoft.ApplicationInsights.Event",
|
||||
"time": "%s",
|
||||
"sampleRate": 100.000000,
|
||||
"seq": "0:0",
|
||||
"iKey": "b4e88960-4393-4dd9-ab8e-97e8fe6d7603",
|
||||
"flags": 0.000000,
|
||||
"tags": {
|
||||
"ai.device.os": "Other",
|
||||
"ai.device.osVersion": "%s-%s",
|
||||
"ai.session.id": "%s",
|
||||
"ai.user.id": "%s",
|
||||
"ai.user.accountAcquisitionDate": "%s"
|
||||
},
|
||||
"data": {
|
||||
"baseType": "EventData",
|
||||
"baseData": {
|
||||
"ver": 2,
|
||||
"name": "commandline_test7",
|
||||
"properties": { %s },
|
||||
"measurements": { %s }
|
||||
}
|
||||
}
|
||||
}])",
|
||||
timestamp,
|
||||
Json::Array arr = Json::Array();
|
||||
Json::Object& obj = arr.push_back(Json::Object());
|
||||
|
||||
obj.insert("ver", Json::Value::integer(1));
|
||||
obj.insert("name", Json::Value::string("Microsoft.ApplicationInsights.Event"));
|
||||
obj.insert("time", Json::Value::string(timestamp));
|
||||
obj.insert("sampleRate", Json::Value::number(100.0));
|
||||
obj.insert("seq", Json::Value::string("0:0"));
|
||||
obj.insert("iKey", Json::Value::string("b4e88960-4393-4dd9-ab8e-97e8fe6d7603"));
|
||||
obj.insert("flags", Json::Value::integer(0));
|
||||
|
||||
{
|
||||
Json::Object& tags = obj.insert("tags", Json::Object());
|
||||
|
||||
tags.insert("ai.device.os", Json::Value::string("Other"));
|
||||
|
||||
const char* os_name =
|
||||
#if defined(_WIN32)
|
||||
"Windows",
|
||||
"Windows";
|
||||
#elif defined(__APPLE__)
|
||||
"OSX",
|
||||
"OSX";
|
||||
#elif defined(__linux__)
|
||||
"Linux",
|
||||
"Linux";
|
||||
#elif defined(__FreeBSD__)
|
||||
"FreeBSD",
|
||||
"FreeBSD";
|
||||
#elif defined(__unix__)
|
||||
"Unix",
|
||||
"Unix";
|
||||
#else
|
||||
"Other",
|
||||
"Other";
|
||||
#endif
|
||||
get_os_version_string(),
|
||||
session_id,
|
||||
user_id,
|
||||
user_timestamp,
|
||||
props_plus_buildtimes,
|
||||
measurements);
|
||||
|
||||
tags.insert("ai.device.osVersion",
|
||||
Json::Value::string(Strings::format("%s-%s", os_name, get_os_version_string())));
|
||||
tags.insert("ai.session.id", Json::Value::string(get_session_id()));
|
||||
tags.insert("ai.user.id", Json::Value::string(user_id));
|
||||
tags.insert("ai.user.accountAcquisitionDate", Json::Value::string(user_timestamp));
|
||||
}
|
||||
|
||||
{
|
||||
Json::Object& data = obj.insert("data", Json::Object());
|
||||
|
||||
data.insert("baseType", Json::Value::string("EventData"));
|
||||
Json::Object& base_data = data.insert("baseData", Json::Object());
|
||||
|
||||
base_data.insert("ver", Json::Value::integer(2));
|
||||
base_data.insert("name", Json::Value::string("commandline_test7"));
|
||||
base_data.insert("properties", Json::Value::object(std::move(props_plus_buildtimes)));
|
||||
base_data.insert("measurements", Json::Value::object(measurements.clone()));
|
||||
}
|
||||
|
||||
return Json::stringify(arr, vcpkg::Json::JsonStyle());
|
||||
}
|
||||
};
|
||||
|
||||
@ -247,12 +237,39 @@ namespace vcpkg::Metrics
|
||||
#endif
|
||||
;
|
||||
static bool g_should_print_metrics = false;
|
||||
static bool g_metrics_disabled =
|
||||
#if VCPKG_DISABLE_METRICS
|
||||
true
|
||||
#else
|
||||
false
|
||||
#endif
|
||||
;
|
||||
|
||||
bool get_compiled_metrics_enabled() { return !VCPKG_DISABLE_METRICS; }
|
||||
// for child vcpkg processes, we also want to disable metrics
|
||||
static void set_vcpkg_disable_metrics_environment_variable(bool disabled)
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
SetEnvironmentVariableW(L"VCPKG_DISABLE_METRICS", disabled ? L"1" : nullptr);
|
||||
#else
|
||||
if (disabled)
|
||||
{
|
||||
setenv("VCPKG_DISABLE_METRICS", "1", true);
|
||||
}
|
||||
else
|
||||
{
|
||||
unsetenv("VCPKG_DISABLE_METRICS");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
std::string get_MAC_user()
|
||||
{
|
||||
#if defined(_WIN32)
|
||||
if (!g_metrics.lock()->metrics_enabled())
|
||||
{
|
||||
return "{}";
|
||||
}
|
||||
|
||||
auto getmac = System::cmd_execute_and_capture_output("getmac");
|
||||
|
||||
if (getmac.exit_code != 0) return "0";
|
||||
@ -293,20 +310,55 @@ namespace vcpkg::Metrics
|
||||
|
||||
void Metrics::set_print_metrics(bool should_print_metrics) { g_should_print_metrics = should_print_metrics; }
|
||||
|
||||
void Metrics::track_metric(const std::string& name, double value) { g_metricmessage.track_metric(name, value); }
|
||||
void Metrics::set_disabled(bool disabled)
|
||||
{
|
||||
set_vcpkg_disable_metrics_environment_variable(disabled);
|
||||
g_metrics_disabled = disabled;
|
||||
}
|
||||
|
||||
bool Metrics::metrics_enabled()
|
||||
{
|
||||
#if VCPKG_DISABLE_METRICS
|
||||
return false;
|
||||
#else
|
||||
return !g_metrics_disabled;
|
||||
#endif
|
||||
}
|
||||
|
||||
void Metrics::track_metric(const std::string& name, double value)
|
||||
{
|
||||
if (!metrics_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
g_metricmessage.track_metric(name, value);
|
||||
}
|
||||
|
||||
void Metrics::track_buildtime(const std::string& name, double value)
|
||||
{
|
||||
if (!metrics_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
g_metricmessage.track_buildtime(name, value);
|
||||
}
|
||||
|
||||
void Metrics::track_property(const std::string& name, const std::string& value)
|
||||
{
|
||||
if (!metrics_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
g_metricmessage.track_property(name, value);
|
||||
}
|
||||
|
||||
void Metrics::upload(const std::string& payload)
|
||||
{
|
||||
if (!metrics_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
#if !defined(_WIN32)
|
||||
Util::unused(payload);
|
||||
#else
|
||||
@ -402,6 +454,11 @@ namespace vcpkg::Metrics
|
||||
|
||||
void Metrics::flush()
|
||||
{
|
||||
if (!metrics_enabled())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const std::string payload = g_metricmessage.format_event_data_template();
|
||||
if (g_should_print_metrics) std::cerr << payload << "\n";
|
||||
if (!g_should_send_metrics) return;
|
||||
|
@ -212,6 +212,11 @@ namespace vcpkg
|
||||
parse_switch(true, "printmetrics", args.printmetrics);
|
||||
continue;
|
||||
}
|
||||
if (arg == "--disable-metrics")
|
||||
{
|
||||
parse_switch(true, "printmetrics", args.disable_metrics);
|
||||
continue;
|
||||
}
|
||||
if (arg == "--no-sendmetrics")
|
||||
{
|
||||
parse_switch(false, "sendmetrics", args.sendmetrics);
|
||||
@ -222,6 +227,11 @@ namespace vcpkg
|
||||
parse_switch(false, "printmetrics", args.printmetrics);
|
||||
continue;
|
||||
}
|
||||
if (arg == "--no-disable-metrics")
|
||||
{
|
||||
parse_switch(false, "printmetrics", args.disable_metrics);
|
||||
continue;
|
||||
}
|
||||
if (arg == "--featurepackages")
|
||||
{
|
||||
parse_switch(true, "featurepackages", args.featurepackages);
|
||||
|
Loading…
Reference in New Issue
Block a user