Merge branch 'master' into bg_bindings
This commit is contained in:
commit
a82845af43
|
@ -9,7 +9,9 @@ bindings/java/foundationdb-tests*.jar
|
|||
bindings/java/fdb-java-*-sources.jar
|
||||
packaging/msi/FDBInstaller.msi
|
||||
build/
|
||||
cmake-build-debug*
|
||||
cmake-build-debug/
|
||||
cmake-build-release/
|
||||
|
||||
# Generated source, build, and packaging files
|
||||
*.g.cpp
|
||||
*.g.h
|
||||
|
@ -105,3 +107,4 @@ temp/
|
|||
/compile_commands.json
|
||||
/.ccls-cache
|
||||
.clangd/
|
||||
.stignore
|
||||
|
|
|
@ -85,11 +85,20 @@ message(STATUS "Current git version ${CURRENT_GIT_VERSION}")
|
|||
################################################################################
|
||||
# Version information
|
||||
################################################################################
|
||||
|
||||
set(FDB_PACKAGE_NAME "${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}")
|
||||
set(FDB_VERSION ${PROJECT_VERSION})
|
||||
if (FDB_RELEASE_CANDIDATE)
|
||||
set(FDB_RELEASE_CANDIDATE_VERSION 1 CACHE STRING "release candidate version")
|
||||
set(FDB_VERSION ${PROJECT_VERSION}-rc${FDB_RELEASE_CANDIDATE_VERSION})
|
||||
else()
|
||||
set(FDB_VERSION ${PROJECT_VERSION})
|
||||
endif()
|
||||
set(FDB_VERSION_PLAIN ${FDB_VERSION})
|
||||
configure_file(${CMAKE_SOURCE_DIR}/versions.target.cmake ${CMAKE_SOURCE_DIR}/versions.target)
|
||||
string(REPLACE "." ";" FDB_VERSION_LIST ${FDB_VERSION_PLAIN})
|
||||
list(GET FDB_VERSION_LIST 0 FDB_MAJOR)
|
||||
list(GET FDB_VERSION_LIST 1 FDB_MINOR)
|
||||
list(GET FDB_VERSION_LIST 2 FDB_PATCH)
|
||||
set(FDB_PACKAGE_NAME "${FDB_MAJOR}.${FDB_MINOR}")
|
||||
configure_file(${CMAKE_SOURCE_DIR}/versions.target.cmake ${CMAKE_CURRENT_BINARY_DIR}/versions.target)
|
||||
file(WRITE ${CMAKE_BINARY_DIR}/version.txt ${FDB_VERSION})
|
||||
|
||||
message(STATUS "FDB version is ${FDB_VERSION}")
|
||||
message(STATUS "FDB package name is ${FDB_PACKAGE_NAME}")
|
||||
|
@ -198,7 +207,6 @@ configure_file(${CMAKE_CURRENT_SOURCE_DIR}/fdbclient/BuildFlags.h.in ${CMAKE_CUR
|
|||
################################################################################
|
||||
# process compile commands for IDE
|
||||
################################################################################
|
||||
|
||||
if (CMAKE_EXPORT_COMPILE_COMMANDS AND WITH_PYTHON)
|
||||
add_custom_command(
|
||||
OUTPUT ${CMAKE_CURRENT_SOURCE_DIR}/compile_commands.json
|
||||
|
|
|
@ -117,8 +117,8 @@ should simulate it using an anonymous transaction. Remember that set and clear
|
|||
operations must immediately commit (with appropriate retry behavior!).
|
||||
|
||||
Any error that bubbles out of these operations must be caught. In the event of
|
||||
an error, you must push the packed tuple of the string `"ERROR"` and the error
|
||||
code (as a string, not an integer).
|
||||
an error, you must push the packed tuple of the byte string `"ERROR"` and the
|
||||
error code (as a byte string, not an integer).
|
||||
|
||||
Some operations may allow you to push future values onto the stack. When popping
|
||||
objects from the stack, the future MUST BE waited on and errors caught before
|
||||
|
|
|
@ -7,18 +7,26 @@ file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/foundationdb)
|
|||
|
||||
set(asm_file ${CMAKE_CURRENT_BINARY_DIR}/fdb_c.g.S)
|
||||
|
||||
set(platform "linux")
|
||||
set(os "linux")
|
||||
set(cpu "intel")
|
||||
if(APPLE)
|
||||
set(platform "osx")
|
||||
set(os "osx")
|
||||
elseif(WIN32)
|
||||
set(platform "windows")
|
||||
set(os "windows")
|
||||
set(asm_file ${CMAKE_CURRENT_BINARY_DIR}/fdb_c.g.asm)
|
||||
elseif(CMAKE_SYSTEM_NAME MATCHES "Linux" AND CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
||||
set(platform "linux-aarch64")
|
||||
endif()
|
||||
|
||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64" OR CMAKE_SYSTEM_PROCESSOR MATCHES "arm64")
|
||||
set(cpu "aarch64")
|
||||
endif()
|
||||
|
||||
set(IS_ARM_MAC NO)
|
||||
if(APPLE AND CMAKE_SYSTEM_PROCESSOR STREQUAL "arm64")
|
||||
set(IS_ARM_MAC YES)
|
||||
endif()
|
||||
|
||||
add_custom_command(OUTPUT ${asm_file} ${CMAKE_CURRENT_BINARY_DIR}/fdb_c_function_pointers.g.h
|
||||
COMMAND $<TARGET_FILE:Python::Interpreter> ${CMAKE_CURRENT_SOURCE_DIR}/generate_asm.py ${platform}
|
||||
COMMAND $<TARGET_FILE:Python::Interpreter> ${CMAKE_CURRENT_SOURCE_DIR}/generate_asm.py ${os} ${cpu}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/fdb_c.cpp
|
||||
${asm_file}
|
||||
${CMAKE_CURRENT_BINARY_DIR}/fdb_c_function_pointers.g.h
|
||||
|
@ -66,8 +74,10 @@ if(WIN32)
|
|||
set_property(SOURCE ${asm_file} PROPERTY LANGUAGE ASM_MASM)
|
||||
endif()
|
||||
|
||||
# The tests don't build on windows
|
||||
if(NOT WIN32)
|
||||
# The tests don't build on windows and ARM macs
|
||||
# doctest doesn't seem to compile on ARM macs, we should
|
||||
# check later whether this works
|
||||
if(NOT WIN32 AND NOT IS_ARM_MAC)
|
||||
set(MAKO_SRCS
|
||||
test/mako/mako.c
|
||||
test/mako/mako.h
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
import re
|
||||
import sys
|
||||
|
||||
(platform, source, asm, h) = sys.argv[1:]
|
||||
(os, cpu, source, asm, h) = sys.argv[1:]
|
||||
|
||||
functions = {}
|
||||
|
||||
|
@ -59,17 +59,18 @@ def write_windows_asm(asmfile, functions):
|
|||
|
||||
|
||||
def write_unix_asm(asmfile, functions, prefix):
|
||||
if platform != "linux-aarch64":
|
||||
if cpu != "aarch64":
|
||||
asmfile.write(".intel_syntax noprefix\n")
|
||||
|
||||
if platform.startswith('linux') or platform == "freebsd":
|
||||
if cpu == 'aarch64' or os == 'linux' or os == 'freebsd':
|
||||
asmfile.write("\n.data\n")
|
||||
for f in functions:
|
||||
asmfile.write("\t.extern fdb_api_ptr_%s\n" % f)
|
||||
|
||||
asmfile.write("\n.text\n")
|
||||
for f in functions:
|
||||
asmfile.write("\t.global %s\n\t.type %s, @function\n" % (f, f))
|
||||
|
||||
if os == 'linux' or os == 'freebsd':
|
||||
asmfile.write("\n.text\n")
|
||||
for f in functions:
|
||||
asmfile.write("\t.global %s\n\t.type %s, @function\n" % (f, f))
|
||||
|
||||
for f in functions:
|
||||
asmfile.write("\n.globl %s%s\n" % (prefix, f))
|
||||
|
@ -104,10 +105,16 @@ def write_unix_asm(asmfile, functions, prefix):
|
|||
# .size g, .-g
|
||||
# .ident "GCC: (GNU) 8.3.1 20190311 (Red Hat 8.3.1-3)"
|
||||
|
||||
if platform == "linux-aarch64":
|
||||
asmfile.write("\tadrp x8, :got:fdb_api_ptr_%s\n" % (f))
|
||||
asmfile.write("\tldr x8, [x8, #:got_lo12:fdb_api_ptr_%s]\n" % (f))
|
||||
asmfile.write("\tldr x8, [x8]\n")
|
||||
p = ''
|
||||
if os == 'osx':
|
||||
p = '_'
|
||||
if cpu == "aarch64":
|
||||
asmfile.write("\tldr x16, =%sfdb_api_ptr_%s\n" % (p, f))
|
||||
if os == 'osx':
|
||||
asmfile.write("\tldr x16, [x16]\n")
|
||||
else:
|
||||
asmfile.write("\tldr x8, [x8, #:got_lo12:fdb_api_ptr_%s]\n" % (f))
|
||||
asmfile.write("\tldr x8, [x8]\n")
|
||||
asmfile.write("\tbr x8\n")
|
||||
else:
|
||||
asmfile.write(
|
||||
|
@ -123,15 +130,15 @@ with open(asm, 'w') as asmfile:
|
|||
hfile.write(
|
||||
"void fdb_api_ptr_removed() { fprintf(stderr, \"REMOVED FDB API FUNCTION\\n\"); abort(); }\n\n")
|
||||
|
||||
if platform.startswith('linux'):
|
||||
if os == 'linux':
|
||||
write_unix_asm(asmfile, functions, '')
|
||||
elif platform == "osx":
|
||||
elif os == "osx":
|
||||
write_unix_asm(asmfile, functions, '_')
|
||||
elif platform == "windows":
|
||||
elif os == "windows":
|
||||
write_windows_asm(asmfile, functions)
|
||||
|
||||
for f in functions:
|
||||
if platform == "windows":
|
||||
if os == "windows":
|
||||
hfile.write("extern \"C\" ")
|
||||
hfile.write("void* fdb_api_ptr_%s = (void*)&fdb_api_ptr_unimpl;\n" % f)
|
||||
for v in functions[f]:
|
||||
|
|
|
@ -1085,7 +1085,7 @@ int run_workload(FDBTransaction* transaction,
|
|||
if (tracetimer == dotrace) {
|
||||
fdb_error_t err;
|
||||
tracetimer = 0;
|
||||
snprintf(traceid, 32, "makotrace%019lld", total_xacts);
|
||||
snprintf(traceid, 32, "makotrace%019ld", total_xacts);
|
||||
fprintf(debugme, "DEBUG: txn tracing %s\n", traceid);
|
||||
err = fdb_transaction_set_option(transaction,
|
||||
FDB_TR_OPTION_DEBUG_TRANSACTION_IDENTIFIER,
|
||||
|
@ -1246,7 +1246,7 @@ void* worker_thread(void* thread_args) {
|
|||
}
|
||||
|
||||
fprintf(debugme,
|
||||
"DEBUG: worker_id:%d (%d) thread_id:%d (%d) database_index:%d (tid:%lld)\n",
|
||||
"DEBUG: worker_id:%d (%d) thread_id:%d (%d) database_index:%lu (tid:%lu)\n",
|
||||
worker_id,
|
||||
args->num_processes,
|
||||
thread_id,
|
||||
|
@ -1446,7 +1446,7 @@ int worker_process_main(mako_args_t* args, int worker_id, mako_shmhdr_t* shm, pi
|
|||
if (err) {
|
||||
fprintf(stderr,
|
||||
"ERROR: fdb_network_set_option (FDB_NET_OPTION_CLIENT_THREADS_PER_VERSION) (%d): %s\n",
|
||||
(uint8_t*)&args->client_threads_per_version,
|
||||
args->client_threads_per_version,
|
||||
fdb_get_error(err));
|
||||
// let's exit here since we do not want to confuse users
|
||||
// that mako is running with multi-threaded client enabled
|
||||
|
@ -2198,9 +2198,9 @@ void print_stats(mako_args_t* args, mako_stats_t* stats, struct timespec* now, s
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0) {
|
||||
uint64_t ops_total_diff = ops_total[op] - ops_total_prev[op];
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", ops_total_diff);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", ops_total_diff);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), ops_total_diff);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), ops_total_diff);
|
||||
}
|
||||
errors_diff[op] = errors_total[op] - errors_total_prev[op];
|
||||
print_err = (errors_diff[op] > 0);
|
||||
|
@ -2228,7 +2228,7 @@ void print_stats(mako_args_t* args, mako_stats_t* stats, struct timespec* now, s
|
|||
printf("%" STR(STATS_TITLE_WIDTH) "s ", "Errors");
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", errors_diff[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", errors_diff[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"errors\": %.2f", conflicts_diff);
|
||||
}
|
||||
|
@ -2373,10 +2373,10 @@ void print_report(mako_args_t* args,
|
|||
break;
|
||||
}
|
||||
}
|
||||
printf("Total Xacts: %8lld\n", totalxacts);
|
||||
printf("Total Conflicts: %8lld\n", conflicts);
|
||||
printf("Total Errors: %8lld\n", totalerrors);
|
||||
printf("Overall TPS: %8lld\n\n", totalxacts * 1000000000 / duration_nsec);
|
||||
printf("Total Xacts: %8lu\n", totalxacts);
|
||||
printf("Total Conflicts: %8lu\n", conflicts);
|
||||
printf("Total Errors: %8lu\n", totalerrors);
|
||||
printf("Overall TPS: %8lu\n\n", totalxacts * 1000000000 / duration_nsec);
|
||||
|
||||
if (fp) {
|
||||
fprintf(fp, "\"results\": {");
|
||||
|
@ -2384,10 +2384,10 @@ void print_report(mako_args_t* args,
|
|||
fprintf(fp, "\"totalProcesses\": %d,", args->num_processes);
|
||||
fprintf(fp, "\"totalThreads\": %d,", args->num_threads);
|
||||
fprintf(fp, "\"targetTPS\": %d,", args->tpsmax);
|
||||
fprintf(fp, "\"totalXacts\": %lld,", totalxacts);
|
||||
fprintf(fp, "\"totalConflicts\": %lld,", conflicts);
|
||||
fprintf(fp, "\"totalErrors\": %lld,", totalerrors);
|
||||
fprintf(fp, "\"overallTPS\": %lld,", totalxacts * 1000000000 / duration_nsec);
|
||||
fprintf(fp, "\"totalXacts\": %lu,", totalxacts);
|
||||
fprintf(fp, "\"totalConflicts\": %lu,", conflicts);
|
||||
fprintf(fp, "\"totalErrors\": %lu,", totalerrors);
|
||||
fprintf(fp, "\"overallTPS\": %lu,", totalxacts * 1000000000 / duration_nsec);
|
||||
}
|
||||
|
||||
/* per-op stats */
|
||||
|
@ -2400,9 +2400,9 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if ((args->txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) || op == OP_COMMIT) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", ops_total[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", ops_total[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), ops_total[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), ops_total[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2423,9 +2423,9 @@ void print_report(mako_args_t* args,
|
|||
printf("%-" STR(STATS_TITLE_WIDTH) "s ", "Errors");
|
||||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 && op != OP_TRANSACTION) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", errors_total[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", errors_total[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), errors_total[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), errors_total[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2442,12 +2442,12 @@ void print_report(mako_args_t* args,
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 || op == OP_TRANSACTION || op == OP_COMMIT) {
|
||||
if (lat_total[op]) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", lat_samples[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_samples[op]);
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
}
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), lat_samples[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), lat_samples[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2463,9 +2463,9 @@ void print_report(mako_args_t* args,
|
|||
if (lat_min[op] == -1) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", lat_min[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_min[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), lat_min[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), lat_min[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2480,9 +2480,9 @@ void print_report(mako_args_t* args,
|
|||
for (op = 0; op < MAX_OP; op++) {
|
||||
if (args->txnspec.ops[op][OP_COUNT] > 0 || op == OP_TRANSACTION || op == OP_COMMIT) {
|
||||
if (lat_total[op]) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", lat_total[op] / lat_samples[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_total[op] / lat_samples[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), lat_total[op] / lat_samples[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), lat_total[op] / lat_samples[op]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2501,9 +2501,9 @@ void print_report(mako_args_t* args,
|
|||
if (lat_max[op] == 0) {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", lat_max[op]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", lat_max[op]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), lat_max[op]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), lat_max[op]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2553,9 +2553,9 @@ void print_report(mako_args_t* args,
|
|||
} else {
|
||||
median = (dataPoints[op][num_points[op] / 2] + dataPoints[op][num_points[op] / 2 - 1]) >> 1;
|
||||
}
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", median);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", median);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), median);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), median);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2577,9 +2577,9 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_95pct = ((float)(num_points[op]) * 0.95) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", dataPoints[op][point_95pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_95pct]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), dataPoints[op][point_95pct]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), dataPoints[op][point_95pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2601,9 +2601,9 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_99pct = ((float)(num_points[op]) * 0.99) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", dataPoints[op][point_99pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_99pct]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), dataPoints[op][point_99pct]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), dataPoints[op][point_99pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2625,9 +2625,9 @@ void print_report(mako_args_t* args,
|
|||
}
|
||||
if (lat_total[op]) {
|
||||
point_99_9pct = ((float)(num_points[op]) * 0.999) - 1;
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lld ", dataPoints[op][point_99_9pct]);
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "lu ", dataPoints[op][point_99_9pct]);
|
||||
if (fp) {
|
||||
fprintf(fp, "\"%s\": %lld,", get_ops_name(op), dataPoints[op][point_99_9pct]);
|
||||
fprintf(fp, "\"%s\": %lu,", get_ops_name(op), dataPoints[op][point_99_9pct]);
|
||||
}
|
||||
} else {
|
||||
printf("%" STR(STATS_FIELD_WIDTH) "s ", "N/A");
|
||||
|
@ -2689,7 +2689,7 @@ int stats_process_main(mako_args_t* args,
|
|||
fprintf(fp, "\"value_length\": %d,", args->value_length);
|
||||
fprintf(fp, "\"commit_get\": %d,", args->commit_get);
|
||||
fprintf(fp, "\"verbose\": %d,", args->verbose);
|
||||
fprintf(fp, "\"cluster_files\": \"%s\",", args->cluster_files);
|
||||
fprintf(fp, "\"cluster_files\": \"%s\",", args->cluster_files[0]);
|
||||
fprintf(fp, "\"log_group\": \"%s\",", args->log_group);
|
||||
fprintf(fp, "\"prefixpadding\": %d,", args->prefixpadding);
|
||||
fprintf(fp, "\"trace\": %d,", args->trace);
|
||||
|
@ -2978,6 +2978,7 @@ failExit:
|
|||
if (shmfd) {
|
||||
close(shmfd);
|
||||
shm_unlink(shmpath);
|
||||
unlink(shmpath);
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
|
|
@ -67,25 +67,25 @@ void runTests(struct ResultSet* rs) {
|
|||
fdb_transaction_set(tr, keys[i], KEY_SIZE, valueStr, VALUE_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %u\n", i, sizes[i]);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_set(tr, keys[i], KEY_SIZE, valueStr, VALUE_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %u\n", i, sizes[i]);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_clear(tr, keys[i], KEY_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %u\n", i, sizes[i]);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
fdb_transaction_clear_range(tr, keys[i], KEY_SIZE, keys[i + 1], KEY_SIZE);
|
||||
e = getSize(rs, tr, sizes + i);
|
||||
checkError(e, "transaction get size", rs);
|
||||
printf("size %d: %u\n", i, sizes[i]);
|
||||
printf("size %d: %ld\n", i, sizes[i]);
|
||||
i++;
|
||||
|
||||
for (j = 0; j + 1 < i; j++) {
|
||||
|
|
|
@ -252,7 +252,8 @@ public:
|
|||
fdb_bool_t snapshot,
|
||||
fdb_bool_t reverse);
|
||||
|
||||
// Returns a future which will be set to an FDBKeyValue array.
|
||||
// WARNING: This feature is considered experimental at this time. It is only allowed when using snapshot isolation
|
||||
// AND disabling read-your-writes. Returns a future which will be set to an FDBKeyValue array.
|
||||
KeyValueArrayFuture get_range_and_flat_map(const uint8_t* begin_key_name,
|
||||
int begin_key_name_length,
|
||||
fdb_bool_t begin_or_equal,
|
||||
|
|
|
@ -876,17 +876,17 @@ TEST_CASE("fdb_transaction_set_read_version future_version") {
|
|||
const std::string EMPTY = Tuple().pack().toString();
|
||||
const KeyRef RECORD = "RECORD"_sr;
|
||||
const KeyRef INDEX = "INDEX"_sr;
|
||||
static KeyRef primaryKey(const int i) {
|
||||
return KeyRef(format("primary-key-of-record-%08d", i));
|
||||
static Key primaryKey(const int i) {
|
||||
return Key(format("primary-key-of-record-%08d", i));
|
||||
}
|
||||
static KeyRef indexKey(const int i) {
|
||||
return KeyRef(format("index-key-of-record-%08d", i));
|
||||
static Key indexKey(const int i) {
|
||||
return Key(format("index-key-of-record-%08d", i));
|
||||
}
|
||||
static ValueRef dataOfRecord(const int i) {
|
||||
return KeyRef(format("data-of-record-%08d", i));
|
||||
static Value dataOfRecord(const int i) {
|
||||
return Value(format("data-of-record-%08d", i));
|
||||
}
|
||||
static std::string indexEntryKey(const int i) {
|
||||
return Tuple().append(prefix).append(INDEX).append(indexKey(i)).append(primaryKey(i)).pack().toString();
|
||||
return Tuple().append(StringRef(prefix)).append(INDEX).append(indexKey(i)).append(primaryKey(i)).pack().toString();
|
||||
}
|
||||
static std::string recordKey(const int i) {
|
||||
return Tuple().append(prefix).append(RECORD).append(primaryKey(i)).pack().toString();
|
||||
|
@ -922,7 +922,7 @@ TEST_CASE("fdb_transaction_get_range_and_flat_map") {
|
|||
/* target_bytes */ 0,
|
||||
/* FDBStreamingMode */ FDB_STREAMING_MODE_WANT_ALL,
|
||||
/* iteration */ 0,
|
||||
/* snapshot */ false,
|
||||
/* snapshot */ true,
|
||||
/* reverse */ 0);
|
||||
|
||||
if (result.err) {
|
||||
|
@ -953,6 +953,44 @@ TEST_CASE("fdb_transaction_get_range_and_flat_map") {
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE("fdb_transaction_get_range_and_flat_map_restricted_to_snapshot") {
|
||||
std::string mapper = Tuple().append(prefix).append(RECORD).append("{K[3]}"_sr).pack().toString();
|
||||
fdb::Transaction tr(db);
|
||||
fdb_check(tr.set_option(FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE, nullptr, 0));
|
||||
auto result = get_range_and_flat_map(
|
||||
tr,
|
||||
FDB_KEYSEL_FIRST_GREATER_OR_EQUAL((const uint8_t*)indexEntryKey(0).c_str(), indexEntryKey(0).size()),
|
||||
FDB_KEYSEL_FIRST_GREATER_THAN((const uint8_t*)indexEntryKey(1).c_str(), indexEntryKey(1).size()),
|
||||
(const uint8_t*)mapper.c_str(),
|
||||
mapper.size(),
|
||||
/* limit */ 0,
|
||||
/* target_bytes */ 0,
|
||||
/* FDBStreamingMode */ FDB_STREAMING_MODE_WANT_ALL,
|
||||
/* iteration */ 0,
|
||||
/* snapshot */ false, // Set snapshot to false
|
||||
/* reverse */ 0);
|
||||
ASSERT(result.err == error_code_client_invalid_operation);
|
||||
}
|
||||
|
||||
TEST_CASE("fdb_transaction_get_range_and_flat_map_restricted_to_ryw_disable") {
|
||||
std::string mapper = Tuple().append(prefix).append(RECORD).append("{K[3]}"_sr).pack().toString();
|
||||
fdb::Transaction tr(db);
|
||||
// Not set FDB_TR_OPTION_READ_YOUR_WRITES_DISABLE.
|
||||
auto result = get_range_and_flat_map(
|
||||
tr,
|
||||
FDB_KEYSEL_FIRST_GREATER_OR_EQUAL((const uint8_t*)indexEntryKey(0).c_str(), indexEntryKey(0).size()),
|
||||
FDB_KEYSEL_FIRST_GREATER_THAN((const uint8_t*)indexEntryKey(1).c_str(), indexEntryKey(1).size()),
|
||||
(const uint8_t*)mapper.c_str(),
|
||||
mapper.size(),
|
||||
/* limit */ 0,
|
||||
/* target_bytes */ 0,
|
||||
/* FDBStreamingMode */ FDB_STREAMING_MODE_WANT_ALL,
|
||||
/* iteration */ 0,
|
||||
/* snapshot */ true,
|
||||
/* reverse */ 0);
|
||||
ASSERT(result.err == error_code_client_invalid_operation);
|
||||
}
|
||||
|
||||
TEST_CASE("fdb_transaction_get_range reverse") {
|
||||
std::map<std::string, std::string> data = create_data({ { "a", "1" }, { "b", "2" }, { "c", "3" }, { "d", "4" } });
|
||||
insert_data(db, data);
|
||||
|
|
|
@ -36,15 +36,15 @@ if(NOT OPEN_FOR_IDE)
|
|||
endforeach()
|
||||
|
||||
if(NOT FDB_RELEASE)
|
||||
set(prerelease_string "-PRERELEASE")
|
||||
set(not_fdb_release_string "-SNAPSHOT")
|
||||
else()
|
||||
set(prerelease_string "")
|
||||
set(not_fdb_release_string "")
|
||||
endif()
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/packages)
|
||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/packages)
|
||||
set(package_dir ${CMAKE_CURRENT_BINARY_DIR}/packages/fdb-flow-${CMAKE_PROJECT_VERSION})
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/fdb-flow-${CMAKE_PROJECT_VERSION}${prerelease_string}-x86_64.tar.gz)
|
||||
set(package_dir ${CMAKE_CURRENT_BINARY_DIR}/packages/fdb-flow-${FDB_VERSION})
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/fdb-flow-${FDB_VERSION}${not_fdb_release_string}-x86_64.tar.gz)
|
||||
add_custom_command(OUTPUT ${tar_file}
|
||||
COMMAND
|
||||
${CMAKE_COMMAND} -E make_directory ${package_dir} &&
|
||||
|
|
|
@ -24,6 +24,7 @@
|
|||
#include <stdio.h>
|
||||
#include <cinttypes>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "flow/SystemMonitor.h"
|
||||
#include "flow/TLSConfig.actor.h"
|
||||
|
@ -44,7 +45,7 @@ ACTOR Future<Void> _test() {
|
|||
// tr->setVersion(1);
|
||||
|
||||
Version ver = wait(tr->getReadVersion());
|
||||
printf("%" PRId64 "\n", ver);
|
||||
fmt::print("{}\n", ver);
|
||||
|
||||
state std::vector<Future<Version>> versions;
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ set(MANIFEST_FILE ${CMAKE_CURRENT_BINARY_DIR}/resources/META-INF/MANIFEST.MF)
|
|||
file(WRITE ${MANIFEST_FILE} ${MANIFEST_TEXT})
|
||||
|
||||
add_jar(fdb-java ${JAVA_BINDING_SRCS} ${GENERATED_JAVA_FILES} ${CMAKE_SOURCE_DIR}/LICENSE
|
||||
OUTPUT_DIR ${PROJECT_BINARY_DIR}/lib VERSION ${CMAKE_PROJECT_VERSION} MANIFEST ${MANIFEST_FILE})
|
||||
OUTPUT_DIR ${PROJECT_BINARY_DIR}/lib VERSION ${FDB_VERSION} MANIFEST ${MANIFEST_FILE})
|
||||
add_dependencies(fdb-java fdb_java_options fdb_java)
|
||||
|
||||
if(NOT OPEN_FOR_IDE)
|
||||
|
@ -238,11 +238,11 @@ if(NOT OPEN_FOR_IDE)
|
|||
add_custom_target(copy_lib DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/lib_copied)
|
||||
add_dependencies(copy_lib unpack_jar)
|
||||
if(NOT FDB_RELEASE)
|
||||
set(prerelease_string "-PRERELEASE")
|
||||
set(not_fdb_release_string "-SNAPSHOT")
|
||||
else()
|
||||
set(prerelease_string "")
|
||||
set(not_fdb_release_string "")
|
||||
endif()
|
||||
set(target_jar ${jar_destination}/fdb-java-${CMAKE_PROJECT_VERSION}${prerelease_string}.jar)
|
||||
set(target_jar ${jar_destination}/fdb-java-${FDB_VERSION}${not_fdb_release_string}.jar)
|
||||
add_custom_command(OUTPUT ${target_jar}
|
||||
COMMAND ${Java_JAR_EXECUTABLE} cfm ${target_jar} ${unpack_dir}/META-INF/MANIFEST.MF .
|
||||
WORKING_DIRECTORY ${unpack_dir}
|
||||
|
@ -250,7 +250,7 @@ if(NOT OPEN_FOR_IDE)
|
|||
COMMENT "Build ${target_jar}")
|
||||
add_jar(foundationdb-tests SOURCES ${JAVA_TESTS_SRCS} INCLUDE_JARS fdb-java)
|
||||
add_dependencies(foundationdb-tests fdb_java_options)
|
||||
set(tests_jar ${jar_destination}/fdb-java-${CMAKE_PROJECT_VERSION}${prerelease_string}-tests.jar)
|
||||
set(tests_jar ${jar_destination}/fdb-java-${FDB_VERSION}${not_fdb_release_string}-tests.jar)
|
||||
add_custom_command(OUTPUT ${tests_jar}
|
||||
COMMAND ${CMAKE_COMMAND} -E copy foundationdb-tests.jar "${tests_jar}"
|
||||
WORKING_DIRECTORY .
|
||||
|
|
|
@ -180,9 +180,10 @@ class RangeAndFlatMapQueryIntegrationTest {
|
|||
try {
|
||||
tr.options().setReadYourWritesDisable();
|
||||
List<KeyValue> kvs =
|
||||
tr.getRangeAndFlatMap(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)),
|
||||
KeySelector.firstGreaterOrEqual(indexEntryKey(end)), MAPPER,
|
||||
ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL)
|
||||
tr.snapshot()
|
||||
.getRangeAndFlatMap(KeySelector.firstGreaterOrEqual(indexEntryKey(begin)),
|
||||
KeySelector.firstGreaterOrEqual(indexEntryKey(end)), MAPPER,
|
||||
ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL)
|
||||
.asList()
|
||||
.get();
|
||||
Assertions.assertEquals(end - begin, kvs.size());
|
||||
|
@ -219,10 +220,12 @@ class RangeAndFlatMapQueryIntegrationTest {
|
|||
// getRangeAndFlatMap is only support without RYW. This is a must!!!
|
||||
tr.options().setReadYourWritesDisable();
|
||||
|
||||
// getRangeAndFlatMap is only supported with snapshot.
|
||||
Iterator<KeyValue> kvs =
|
||||
tr.getRangeAndFlatMap(KeySelector.firstGreaterOrEqual(indexEntryKey(0)),
|
||||
KeySelector.firstGreaterThan(indexEntryKey(1)), MAPPER,
|
||||
ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL)
|
||||
tr.snapshot()
|
||||
.getRangeAndFlatMap(KeySelector.firstGreaterOrEqual(indexEntryKey(0)),
|
||||
KeySelector.firstGreaterThan(indexEntryKey(1)), MAPPER,
|
||||
ReadTransaction.ROW_LIMIT_UNLIMITED, false, StreamingMode.WANT_ALL)
|
||||
.iterator();
|
||||
Iterator<byte[]> expected_data_of_records_iter = expected_data_of_records.iterator();
|
||||
while (expected_data_of_records_iter.hasNext()) {
|
||||
|
|
|
@ -350,10 +350,7 @@ class FDBTransaction extends NativeObjectWrapper implements Transaction, OptionC
|
|||
@Override
|
||||
public AsyncIterable<KeyValue> getRangeAndFlatMap(KeySelector begin, KeySelector end, byte[] mapper, int limit,
|
||||
boolean reverse, StreamingMode mode) {
|
||||
if (mapper == null) {
|
||||
throw new IllegalArgumentException("Mapper must be non-null");
|
||||
}
|
||||
return new RangeQuery(this, false, begin, end, mapper, limit, reverse, mode, eventKeeper);
|
||||
throw new UnsupportedOperationException("getRangeAndFlatMap is only supported in snapshot");
|
||||
}
|
||||
|
||||
///////////////////
|
||||
|
|
|
@ -425,9 +425,8 @@ public interface ReadTransaction extends ReadTransactionContext {
|
|||
int limit, boolean reverse, StreamingMode mode);
|
||||
|
||||
/**
|
||||
* Gets an ordered range of keys and values from the database. The begin
|
||||
* and end keys are specified by {@code KeySelector}s, with the begin
|
||||
* {@code KeySelector} inclusive and the end {@code KeySelector} exclusive.
|
||||
* WARNING: This feature is considered experimental at this time. It is only allowed when using snapshot isolation
|
||||
* AND disabling read-your-writes.
|
||||
*
|
||||
* @see KeySelector
|
||||
* @see AsyncIterator
|
||||
|
|
|
@ -60,12 +60,12 @@ else()
|
|||
add_custom_target(fdb_python_check COMMAND ${CMAKE_COMMAND} -E echo "Skipped Python style check! Missing: pycodestyle")
|
||||
endif()
|
||||
if(NOT FDB_RELEASE)
|
||||
set(prerelease_string "a1")
|
||||
set(not_fdb_release_string "a1")
|
||||
else()
|
||||
set(prerelease_string "")
|
||||
set(not_fdb_release_string "")
|
||||
endif()
|
||||
set(setup_file_name foundationdb-${FDB_VERSION}.tar.gz)
|
||||
set(package_file ${CMAKE_BINARY_DIR}/packages/foundationdb-${FDB_VERSION}${prerelease_string}.tar.gz)
|
||||
set(package_file ${CMAKE_BINARY_DIR}/packages/foundationdb-${FDB_VERSION}${not_fdb_release_string}.tar.gz)
|
||||
add_custom_command(OUTPUT ${package_file}
|
||||
COMMAND $<TARGET_FILE:Python::Interpreter> setup.py sdist --formats=gztar &&
|
||||
${CMAKE_COMMAND} -E copy dist/${setup_file_name} ${package_file}
|
||||
|
|
|
@ -5,15 +5,15 @@ vexillographer_compile(TARGET ruby_options LANG ruby
|
|||
configure_file(fdb.gemspec.cmake fdb.gemspec)
|
||||
|
||||
if(NOT FDB_RELEASE)
|
||||
set(prerelease_string "PRERELEASE")
|
||||
set(not_fdb_release_string "SNAPSHOT")
|
||||
else()
|
||||
set(prerelease_string "")
|
||||
set(not_fdb_release_string "")
|
||||
endif()
|
||||
|
||||
set(setup_file fdb-${FDB_VERSION}.gem)
|
||||
set(gem_target ${CMAKE_BINARY_DIR}/packages/fdb-${FDB_VERSION}${prerelease_string}.gem)
|
||||
set(gem_target ${CMAKE_BINARY_DIR}/packages/fdb-${FDB_VERSION}${not_fdb_release_string}.gem)
|
||||
add_custom_command(OUTPUT ${gem_target}
|
||||
COMMAND ${GEM_COMMAND} build fdb.gemspec &&
|
||||
COMMAND ${GEM_COMMAND} build fdb.gemspec --output ${setup_file} &&
|
||||
${CMAKE_COMMAND} -E copy ${setup_file} ${gem_target}
|
||||
WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
|
||||
COMMENT "Building ruby gem")
|
||||
|
|
|
@ -250,7 +250,7 @@ function(create_correctness_package)
|
|||
endif()
|
||||
set(out_dir "${CMAKE_BINARY_DIR}/correctness")
|
||||
stage_correctness_package(OUT_DIR ${out_dir} CONTEXT "correctness" OUT_FILES package_files)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/correctness-${CMAKE_PROJECT_VERSION}.tar.gz)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/correctness-${FDB_VERSION}.tar.gz)
|
||||
add_custom_command(
|
||||
OUTPUT ${tar_file}
|
||||
DEPENDS ${package_files}
|
||||
|
@ -285,7 +285,7 @@ function(create_valgrind_correctness_package)
|
|||
if(USE_VALGRIND)
|
||||
set(out_dir "${CMAKE_BINARY_DIR}/valgrind_correctness")
|
||||
stage_correctness_package(OUT_DIR ${out_dir} CONTEXT "valgrind correctness" OUT_FILES package_files)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/valgrind-${CMAKE_PROJECT_VERSION}.tar.gz)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/valgrind-${FDB_VERSION}.tar.gz)
|
||||
add_custom_command(
|
||||
OUTPUT ${tar_file}
|
||||
DEPENDS ${package_files}
|
||||
|
@ -355,14 +355,14 @@ function(package_bindingtester)
|
|||
set(generated_binding_files python/fdb/fdboptions.py)
|
||||
if(WITH_JAVA_BINDING)
|
||||
if(NOT FDB_RELEASE)
|
||||
set(prerelease_string "-PRERELEASE")
|
||||
set(not_fdb_release_string "-SNAPSHOT")
|
||||
else()
|
||||
set(prerelease_string "")
|
||||
set(not_fdb_release_string "")
|
||||
endif()
|
||||
add_custom_command(
|
||||
TARGET copy_binding_output_files
|
||||
COMMAND ${CMAKE_COMMAND} -E copy
|
||||
${CMAKE_BINARY_DIR}/packages/fdb-java-${CMAKE_PROJECT_VERSION}${prerelease_string}.jar
|
||||
${CMAKE_BINARY_DIR}/packages/fdb-java-${FDB_VERSION}${not_fdb_release_string}.jar
|
||||
${bdir}/tests/java/foundationdb-client.jar
|
||||
COMMENT "Copy Java bindings for bindingtester")
|
||||
add_dependencies(copy_binding_output_files fat-jar)
|
||||
|
@ -391,7 +391,7 @@ function(package_bindingtester)
|
|||
add_custom_target(copy_bindingtester_binaries
|
||||
DEPENDS ${outfiles} "${CMAKE_BINARY_DIR}/bindingtester.touch" copy_binding_output_files)
|
||||
add_dependencies(copy_bindingtester_binaries strip_only_fdbserver strip_only_fdbcli strip_only_fdb_c)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/bindingtester-${CMAKE_PROJECT_VERSION}.tar.gz)
|
||||
set(tar_file ${CMAKE_BINARY_DIR}/packages/bindingtester-${FDB_VERSION}.tar.gz)
|
||||
add_custom_command(
|
||||
OUTPUT ${tar_file}
|
||||
COMMAND ${CMAKE_COMMAND} -E tar czf ${tar_file} *
|
||||
|
|
|
@ -49,8 +49,8 @@ function(compile_boost)
|
|||
include(ExternalProject)
|
||||
set(BOOST_INSTALL_DIR "${CMAKE_BINARY_DIR}/boost_install")
|
||||
ExternalProject_add("${COMPILE_BOOST_TARGET}Project"
|
||||
URL "https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.bz2"
|
||||
URL_HASH SHA256=59c9b274bc451cf91a9ba1dd2c7fdcaf5d60b1b3aa83f2c9fa143417cc660722
|
||||
URL "https://boostorg.jfrog.io/artifactory/main/release/1.77.0/source/boost_1_77_0.tar.bz2"
|
||||
URL_HASH SHA256=fc9f85fc030e233142908241af7a846e60630aa7388de9a5fafb1f3a26840854
|
||||
CONFIGURE_COMMAND ${BOOTSTRAP_COMMAND} ${BOOTSTRAP_ARGS} --with-libraries=${BOOTSTRAP_LIBRARIES} --with-toolset=${BOOST_TOOLSET}
|
||||
BUILD_COMMAND ${B2_COMMAND} link=static ${COMPILE_BOOST_BUILD_ARGS} --prefix=${BOOST_INSTALL_DIR} ${USER_CONFIG_FLAG} install
|
||||
BUILD_IN_SOURCE ON
|
||||
|
@ -113,7 +113,7 @@ if(WIN32)
|
|||
return()
|
||||
endif()
|
||||
|
||||
find_package(Boost 1.72.0 EXACT QUIET COMPONENTS context CONFIG PATHS ${BOOST_HINT_PATHS})
|
||||
find_package(Boost 1.77.0 EXACT QUIET COMPONENTS context CONFIG PATHS ${BOOST_HINT_PATHS})
|
||||
set(FORCE_BOOST_BUILD OFF CACHE BOOL "Forces cmake to build boost and ignores any installed boost")
|
||||
|
||||
if(Boost_FOUND AND NOT FORCE_BOOST_BUILD)
|
||||
|
|
|
@ -10,6 +10,7 @@ env_set(USE_GCOV OFF BOOL "Compile with gcov instrumentation")
|
|||
env_set(USE_MSAN OFF BOOL "Compile with memory sanitizer. To avoid false positives you need to dynamically link to a msan-instrumented libc++ and libc++abi, which you must compile separately. See https://github.com/google/sanitizers/wiki/MemorySanitizerLibcxxHowTo#instrumented-libc.")
|
||||
env_set(USE_TSAN OFF BOOL "Compile with thread sanitizer. It is recommended to dynamically link to a tsan-instrumented libc++ and libc++abi, which you can compile separately.")
|
||||
env_set(USE_UBSAN OFF BOOL "Compile with undefined behavior sanitizer")
|
||||
env_set(FDB_RELEASE_CANDIDATE OFF BOOL "This is a building of a release candidate")
|
||||
env_set(FDB_RELEASE OFF BOOL "This is a building of a final release")
|
||||
env_set(USE_CCACHE OFF BOOL "Use ccache for compilation if available")
|
||||
env_set(RELATIVE_DEBUG_PATHS OFF BOOL "Use relative file paths in debug info")
|
||||
|
@ -283,11 +284,11 @@ else()
|
|||
-Wshift-sign-overflow
|
||||
# Here's the current set of warnings we need to explicitly disable to compile warning-free with clang 11
|
||||
-Wno-delete-non-virtual-dtor
|
||||
-Wno-format
|
||||
-Wno-sign-compare
|
||||
-Wno-undefined-var-template
|
||||
-Wno-unknown-warning-option
|
||||
-Wno-unused-parameter
|
||||
-Wno-constant-logical-operand
|
||||
)
|
||||
if (USE_CCACHE)
|
||||
add_compile_options(
|
||||
|
|
|
@ -202,6 +202,21 @@ else()
|
|||
target_include_directories(toml11_target SYSTEM INTERFACE ${CMAKE_CURRENT_BINARY_DIR}/toml11/include)
|
||||
endif()
|
||||
|
||||
################################################################################
|
||||
# Coroutine implementation
|
||||
################################################################################
|
||||
|
||||
set(DEFAULT_COROUTINE_IMPL boost)
|
||||
if(WIN32)
|
||||
# boost coroutine not available in windows build environment for now.
|
||||
set(DEFAULT_COROUTINE_IMPL libcoro)
|
||||
elseif(NOT APPLE AND CMAKE_HOST_SYSTEM_PROCESSOR MATCHES "^x86")
|
||||
# revert to libcoro for x86 linux while we investigate a performance regression
|
||||
set(DEFAULT_COROUTINE_IMPL libcoro)
|
||||
endif()
|
||||
|
||||
set(COROUTINE_IMPL ${DEFAULT_COROUTINE_IMPL} CACHE STRING "Which coroutine implementation to use. Options are boost and libcoro")
|
||||
|
||||
################################################################################
|
||||
|
||||
file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/packages)
|
||||
|
|
|
@ -105,7 +105,7 @@ install_destinations(TGZ
|
|||
ETC etc/foundationdb
|
||||
LOG log/foundationdb
|
||||
DATA lib/foundationdb)
|
||||
copy_install_destinations(TGZ VERSIONED PREFIX "usr/lib/foundationdb-${PROJECT_VERSION}/")
|
||||
copy_install_destinations(TGZ VERSIONED PREFIX "usr/lib/foundationdb-${FDB_VERSION}/")
|
||||
install_destinations(DEB
|
||||
BIN usr/bin
|
||||
SBIN usr/sbin
|
||||
|
@ -141,21 +141,12 @@ set(CPACK_PROJECT_CONFIG_FILE "${CMAKE_BINARY_DIR}/packaging/CPackConfig.cmake")
|
|||
|
||||
set(GENERATE_DEBUG_PACKAGES "${FDB_RELEASE}" CACHE BOOL "Build debug rpm/deb packages (default: only ON for FDB_RELEASE)")
|
||||
|
||||
################################################################################
|
||||
# Version information
|
||||
################################################################################
|
||||
|
||||
string(REPLACE "." ";" FDB_VERSION_LIST ${FDB_VERSION_PLAIN})
|
||||
list(GET FDB_VERSION_LIST 0 FDB_MAJOR)
|
||||
list(GET FDB_VERSION_LIST 1 FDB_MINOR)
|
||||
list(GET FDB_VERSION_LIST 2 FDB_PATCH)
|
||||
|
||||
|
||||
################################################################################
|
||||
# Alternatives config
|
||||
################################################################################
|
||||
|
||||
set(mv_packaging_dir ${PROJECT_SOURCE_DIR}/packaging/multiversion)
|
||||
# USE PROJECT_VERSION_* HERE AS THEY ARE GUARANTEED TO BE INTEGERS ONLY
|
||||
math(EXPR ALTERNATIVES_PRIORITY "(${PROJECT_VERSION_MAJOR} * 1000) + (${PROJECT_VERSION_MINOR} * 100) + ${PROJECT_VERSION_PATCH}")
|
||||
set(script_dir "${PROJECT_BINARY_DIR}/packaging/multiversion/")
|
||||
file(MAKE_DIRECTORY "${script_dir}/server" "${script_dir}/clients")
|
||||
|
@ -185,12 +176,6 @@ install(DIRECTORY "${script_dir}/clients/usr/lib/cmake"
|
|||
DESTINATION usr/lib
|
||||
COMPONENT clients-versioned)
|
||||
|
||||
################################################################################
|
||||
# Move Docker Setup
|
||||
################################################################################
|
||||
|
||||
file(COPY "${PROJECT_SOURCE_DIR}/packaging/docker" DESTINATION "${PROJECT_BINARY_DIR}/packages/")
|
||||
|
||||
################################################################################
|
||||
# General CPack configuration
|
||||
################################################################################
|
||||
|
@ -215,20 +200,20 @@ set(CPACK_COMPONENT_SERVER-DEB_DEPENDS clients-deb)
|
|||
set(CPACK_COMPONENT_SERVER-TGZ_DEPENDS clients-tgz)
|
||||
set(CPACK_COMPONENT_SERVER-VERSIONED_DEPENDS clients-versioned)
|
||||
set(CPACK_RPM_SERVER-VERSIONED_PACKAGE_REQUIRES
|
||||
"foundationdb${PROJECT_VERSION}-clients")
|
||||
"foundationdb${FDB_VERSION}-clients")
|
||||
|
||||
set(CPACK_COMPONENT_SERVER-EL7_DISPLAY_NAME "foundationdb-server")
|
||||
set(CPACK_COMPONENT_SERVER-DEB_DISPLAY_NAME "foundationdb-server")
|
||||
set(CPACK_COMPONENT_SERVER-TGZ_DISPLAY_NAME "foundationdb-server")
|
||||
set(CPACK_COMPONENT_SERVER-VERSIONED_DISPLAY_NAME "foundationdb${PROJECT_VERSION}-server")
|
||||
set(CPACK_COMPONENT_SERVER-VERSIONED_DISPLAY_NAME "foundationdb${FDB_VERSION}-server")
|
||||
|
||||
set(CPACK_COMPONENT_CLIENTS-EL7_DISPLAY_NAME "foundationdb-clients")
|
||||
set(CPACK_COMPONENT_CLIENTS-DEB_DISPLAY_NAME "foundationdb-clients")
|
||||
set(CPACK_COMPONENT_CLIENTS-TGZ_DISPLAY_NAME "foundationdb-clients")
|
||||
set(CPACK_COMPONENT_CLIENTS-VERSIONED_DISPLAY_NAME "foundationdb${PROJECT_VERSION}-clients")
|
||||
set(CPACK_COMPONENT_CLIENTS-VERSIONED_DISPLAY_NAME "foundationdb${FDB_VERSION}-clients")
|
||||
|
||||
|
||||
# MacOS needs a file exiension for the LICENSE file
|
||||
# MacOS needs a file extension for the LICENSE file
|
||||
configure_file(${CMAKE_SOURCE_DIR}/LICENSE ${CMAKE_BINARY_DIR}/License.txt COPYONLY)
|
||||
|
||||
################################################################################
|
||||
|
@ -240,27 +225,27 @@ if(NOT FDB_RELEASE)
|
|||
set(git_string ".${CURRENT_GIT_VERSION}")
|
||||
endif()
|
||||
set(CPACK_RPM_PACKAGE_RELEASE 0)
|
||||
set(prerelease_string "-0${git_string}.PRERELEASE")
|
||||
set(not_fdb_release_string "-0${git_string}.SNAPSHOT")
|
||||
else()
|
||||
set(CPACK_RPM_PACKAGE_RELEASE 1)
|
||||
set(prerelease_string "-1")
|
||||
set(not_fdb_release_string "-1")
|
||||
endif()
|
||||
|
||||
#############
|
||||
# Filenames #
|
||||
#############
|
||||
set(unversioned_postfix "${PROJECT_VERSION}${prerelease_string}")
|
||||
set(unversioned_postfix "${FDB_VERSION}${not_fdb_release_string}")
|
||||
# RPM filenames
|
||||
set(rpm-clients-filename "foundationdb-clients-${unversioned_postfix}")
|
||||
set(rpm-server-filename "foundationdb-server-${unversioned_postfix}")
|
||||
set(rpm-clients-versioned-filename "foundationdb${PROJECT_VERSION}-clients${prerelease_string}")
|
||||
set(rpm-server-versioned-filename "foundationdb${PROJECT_VERSION}-server${prerelease_string}")
|
||||
set(rpm-clients-versioned-filename "foundationdb${FDB_VERSION}-clients${prerelease_string}")
|
||||
set(rpm-server-versioned-filename "foundationdb${FDB_VERSION}-server${prerelease_string}")
|
||||
|
||||
# Deb filenames
|
||||
set(deb-clients-filename "foundationdb-clients_${unversioned_postfix}")
|
||||
set(deb-server-filename "foundationdb-server_${unversioned_postfix}")
|
||||
set(deb-clients-versioned-filename "foundationdb${PROJECT_VERSION}-clients${prerelease_string}")
|
||||
set(deb-server-versioned-filename "foundationdb${PROJECT_VERSION}-server${prerelease_string}")
|
||||
set(deb-clients-versioned-filename "foundationdb${FDB_VERSION}-clients${prerelease_string}")
|
||||
set(deb-server-versioned-filename "foundationdb${FDB_VERSION}-server${prerelease_string}")
|
||||
|
||||
################################################################################
|
||||
# Configuration for RPM
|
||||
|
@ -271,8 +256,8 @@ set(CPACK_RPM_PACKAGE_LICENSE "Apache 2.0")
|
|||
set(CPACK_RPM_PACKAGE_NAME "foundationdb")
|
||||
set(CPACK_RPM_CLIENTS-EL7_PACKAGE_NAME "foundationdb-clients")
|
||||
set(CPACK_RPM_SERVER-EL7_PACKAGE_NAME "foundationdb-server")
|
||||
set(CPACK_RPM_SERVER-VERSIONED_PACKAGE_NAME "foundationdb${PROJECT_VERSION}-server")
|
||||
set(CPACK_RPM_CLIENTS-VERSIONED_PACKAGE_NAME "foundationdb${PROJECT_VERSION}-clients")
|
||||
set(CPACK_RPM_SERVER-VERSIONED_PACKAGE_NAME "foundationdb${FDB_VERSION}-server")
|
||||
set(CPACK_RPM_CLIENTS-VERSIONED_PACKAGE_NAME "foundationdb${FDB_VERSION}-clients")
|
||||
|
||||
set(CPACK_RPM_CLIENTS-EL7_FILE_NAME "${rpm-clients-filename}.el7.${CMAKE_SYSTEM_PROCESSOR}.rpm")
|
||||
set(CPACK_RPM_CLIENTS-VERSIONED_FILE_NAME "${rpm-clients-versioned-filename}.versioned.${CMAKE_SYSTEM_PROCESSOR}.rpm")
|
||||
|
@ -370,8 +355,8 @@ set(CPACK_DEBIAN_ENABLE_COMPONENT_DEPENDS ON)
|
|||
|
||||
set(CPACK_DEBIAN_SERVER-DEB_PACKAGE_NAME "foundationdb-server")
|
||||
set(CPACK_DEBIAN_CLIENTS-DEB_PACKAGE_NAME "foundationdb-clients")
|
||||
set(CPACK_DEBIAN_SERVER-VERSIONED_PACKAGE_NAME "foundationdb${PROJECT_VERSION}-server")
|
||||
set(CPACK_DEBIAN_CLIENTS-VERSIONED_PACKAGE_NAME "foundationdb${PROJECT_VERSION}-clients")
|
||||
set(CPACK_DEBIAN_SERVER-VERSIONED_PACKAGE_NAME "foundationdb${FDB_VERSION}-server")
|
||||
set(CPACK_DEBIAN_CLIENTS-VERSIONED_PACKAGE_NAME "foundationdb${FDB_VERSION}-clients")
|
||||
|
||||
set(CPACK_DEBIAN_SERVER-DEB_PACKAGE_DEPENDS "adduser, libc6 (>= 2.12), foundationdb-clients (= ${FDB_VERSION})")
|
||||
set(CPACK_DEBIAN_SERVER-DEB_PACKAGE_RECOMMENDS "python (>= 2.6)")
|
||||
|
@ -425,10 +410,10 @@ if(NOT WIN32)
|
|||
RENAME "foundationdb"
|
||||
COMPONENT server-deb)
|
||||
install(FILES ${CMAKE_SOURCE_DIR}/packaging/rpm/foundationdb.service
|
||||
DESTINATION "usr/lib/foundationdb-${PROJECT_VERSION}/lib/systemd/system"
|
||||
DESTINATION "usr/lib/foundationdb-${FDB_VERSION}/lib/systemd/system"
|
||||
COMPONENT server-versioned)
|
||||
install(PROGRAMS ${CMAKE_SOURCE_DIR}/packaging/deb/foundationdb-init
|
||||
DESTINATION "usr/lib/foundationdb-${PROJECT_VERSION}/etc/init.d"
|
||||
DESTINATION "usr/lib/foundationdb-${FDB_VERSION}/etc/init.d"
|
||||
RENAME "foundationdb"
|
||||
COMPONENT server-versioned)
|
||||
endif()
|
||||
|
|
|
@ -3,7 +3,7 @@ includedir=/@INCLUDE_DIR@
|
|||
|
||||
Name: foundationdb-client
|
||||
Description: FoundationDB c client
|
||||
Version: @PROJECT_VERSION@
|
||||
Version: @FDB_VERSION@
|
||||
|
||||
Libs: -L${libdir} -lfdb_c
|
||||
Cflags: -I${includedir}
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
add_subdirectory(fmt-8.0.1)
|
||||
if(NOT WIN32)
|
||||
add_subdirectory(monitoring)
|
||||
add_subdirectory(TraceLogHelper)
|
||||
|
|
|
@ -1636,8 +1636,8 @@ namespace SummarizeTest
|
|||
{
|
||||
Console.WriteLine("Version: 1.02");
|
||||
|
||||
Console.WriteLine("FDB Project Ver: " + "${CMAKE_PROJECT_VERSION}");
|
||||
Console.WriteLine("FDB Version: " + "${CMAKE_PROJECT_VERSION_MAJOR}" + "." + "${CMAKE_PROJECT_VERSION_MINOR}");
|
||||
Console.WriteLine("FDB Project Ver: " + "${FDB_VERSION}");
|
||||
Console.WriteLine("FDB Version: " + "${FDB_VERSION_MAJOR}" + "." + "${FDB_VERSION_MINOR}");
|
||||
Console.WriteLine("Source Version: " + "${CURRENT_GIT_VERSION}");
|
||||
return 1;
|
||||
}
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
DisableFormat: true
|
|
@ -0,0 +1,410 @@
|
|||
cmake_minimum_required(VERSION 3.1...3.18)
|
||||
|
||||
# Fallback for using newer policies on CMake <3.12.
|
||||
if(${CMAKE_VERSION} VERSION_LESS 3.12)
|
||||
cmake_policy(VERSION ${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION})
|
||||
endif()
|
||||
|
||||
# Determine if fmt is built as a subproject (using add_subdirectory)
|
||||
# or if it is the master project.
|
||||
if (NOT DEFINED FMT_MASTER_PROJECT)
|
||||
set(FMT_MASTER_PROJECT OFF)
|
||||
if (CMAKE_CURRENT_SOURCE_DIR STREQUAL CMAKE_SOURCE_DIR)
|
||||
set(FMT_MASTER_PROJECT ON)
|
||||
message(STATUS "CMake version: ${CMAKE_VERSION}")
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
# Joins arguments and places the results in ${result_var}.
|
||||
function(join result_var)
|
||||
set(result "")
|
||||
foreach (arg ${ARGN})
|
||||
set(result "${result}${arg}")
|
||||
endforeach ()
|
||||
set(${result_var} "${result}" PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
function(enable_module target)
|
||||
if (MSVC)
|
||||
set(BMI ${CMAKE_CURRENT_BINARY_DIR}/${target}.ifc)
|
||||
target_compile_options(${target}
|
||||
PRIVATE /interface /ifcOutput ${BMI}
|
||||
INTERFACE /reference fmt=${BMI})
|
||||
endif ()
|
||||
set_target_properties(${target} PROPERTIES ADDITIONAL_CLEAN_FILES ${BMI})
|
||||
set_source_files_properties(${BMI} PROPERTIES GENERATED ON)
|
||||
endfunction()
|
||||
|
||||
include(CMakeParseArguments)
|
||||
|
||||
# Sets a cache variable with a docstring joined from multiple arguments:
|
||||
# set(<variable> <value>... CACHE <type> <docstring>...)
|
||||
# This allows splitting a long docstring for readability.
|
||||
function(set_verbose)
|
||||
# cmake_parse_arguments is broken in CMake 3.4 (cannot parse CACHE) so use
|
||||
# list instead.
|
||||
list(GET ARGN 0 var)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(GET ARGN 0 val)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
list(GET ARGN 0 type)
|
||||
list(REMOVE_AT ARGN 0)
|
||||
join(doc ${ARGN})
|
||||
set(${var} ${val} CACHE ${type} ${doc})
|
||||
endfunction()
|
||||
|
||||
# Set the default CMAKE_BUILD_TYPE to Release.
|
||||
# This should be done before the project command since the latter can set
|
||||
# CMAKE_BUILD_TYPE itself (it does so for nmake).
|
||||
if (FMT_MASTER_PROJECT AND NOT CMAKE_BUILD_TYPE)
|
||||
set_verbose(CMAKE_BUILD_TYPE Release CACHE STRING
|
||||
"Choose the type of build, options are: None(CMAKE_CXX_FLAGS or "
|
||||
"CMAKE_C_FLAGS used) Debug Release RelWithDebInfo MinSizeRel.")
|
||||
endif ()
|
||||
|
||||
project(FMT CXX)
|
||||
include(GNUInstallDirs)
|
||||
set_verbose(FMT_INC_DIR ${CMAKE_INSTALL_INCLUDEDIR} CACHE STRING
|
||||
"Installation directory for include files, a relative path that "
|
||||
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
option(FMT_PEDANTIC "Enable extra warnings and expensive tests." OFF)
|
||||
option(FMT_WERROR "Halt the compilation with an error on compiler warnings."
|
||||
OFF)
|
||||
|
||||
# Options that control generation of various targets.
|
||||
option(FMT_DOC "Generate the doc target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_INSTALL "Generate the install target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_TEST "Generate the test target." ${FMT_MASTER_PROJECT})
|
||||
option(FMT_FUZZ "Generate the fuzz target." OFF)
|
||||
option(FMT_CUDA_TEST "Generate the cuda-test target." OFF)
|
||||
option(FMT_OS "Include core requiring OS (Windows/Posix) " ON)
|
||||
option(FMT_MODULE "Build a module instead of a traditional library." OFF)
|
||||
|
||||
set(FMT_CAN_MODULE OFF)
|
||||
if (CMAKE_CXX_STANDARD GREATER 17 AND
|
||||
# msvc 16.10-pre4
|
||||
MSVC AND CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 19.29.30035)
|
||||
set(FMT_CAN_MODULE ON)
|
||||
endif ()
|
||||
if (NOT FMT_CAN_MODULE)
|
||||
set(FMT_MODULE OFF)
|
||||
message(STATUS "Module support is disabled.")
|
||||
endif ()
|
||||
if (FMT_TEST AND FMT_MODULE)
|
||||
# The tests require {fmt} to be compiled as traditional library
|
||||
message(STATUS "Testing is incompatible with build mode 'module'.")
|
||||
endif ()
|
||||
|
||||
# Get version from core.h
|
||||
file(READ include/fmt/core.h core_h)
|
||||
if (NOT core_h MATCHES "FMT_VERSION ([0-9]+)([0-9][0-9])([0-9][0-9])")
|
||||
message(FATAL_ERROR "Cannot get FMT_VERSION from core.h.")
|
||||
endif ()
|
||||
# Use math to skip leading zeros if any.
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MAJOR ${CMAKE_MATCH_1})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_MINOR ${CMAKE_MATCH_2})
|
||||
math(EXPR CPACK_PACKAGE_VERSION_PATCH ${CMAKE_MATCH_3})
|
||||
join(FMT_VERSION ${CPACK_PACKAGE_VERSION_MAJOR}.${CPACK_PACKAGE_VERSION_MINOR}.
|
||||
${CPACK_PACKAGE_VERSION_PATCH})
|
||||
message(STATUS "Version: ${FMT_VERSION}")
|
||||
|
||||
message(STATUS "Build type: ${CMAKE_BUILD_TYPE}")
|
||||
|
||||
if (NOT CMAKE_RUNTIME_OUTPUT_DIRECTORY)
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH}
|
||||
"${CMAKE_CURRENT_SOURCE_DIR}/support/cmake")
|
||||
|
||||
include(cxx14)
|
||||
include(CheckCXXCompilerFlag)
|
||||
include(JoinPaths)
|
||||
|
||||
list(FIND CMAKE_CXX_COMPILE_FEATURES "cxx_variadic_templates" index)
|
||||
if (${index} GREATER -1)
|
||||
# Use cxx_variadic_templates instead of more appropriate cxx_std_11 for
|
||||
# compatibility with older CMake versions.
|
||||
set(FMT_REQUIRED_FEATURES cxx_variadic_templates)
|
||||
endif ()
|
||||
message(STATUS "Required features: ${FMT_REQUIRED_FEATURES}")
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_CXX_VISIBILITY_PRESET)
|
||||
set_verbose(CMAKE_CXX_VISIBILITY_PRESET hidden CACHE STRING
|
||||
"Preset for the export of private symbols")
|
||||
set_property(CACHE CMAKE_CXX_VISIBILITY_PRESET PROPERTY STRINGS
|
||||
hidden default)
|
||||
endif ()
|
||||
|
||||
if (FMT_MASTER_PROJECT AND NOT DEFINED CMAKE_VISIBILITY_INLINES_HIDDEN)
|
||||
set_verbose(CMAKE_VISIBILITY_INLINES_HIDDEN ON CACHE BOOL
|
||||
"Whether to add a compile flag to hide symbols of inline functions")
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "GNU")
|
||||
set(PEDANTIC_COMPILE_FLAGS -pedantic-errors -Wall -Wextra -pedantic
|
||||
-Wold-style-cast -Wundef
|
||||
-Wredundant-decls -Wwrite-strings -Wpointer-arith
|
||||
-Wcast-qual -Wformat=2 -Wmissing-include-dirs
|
||||
-Wcast-align
|
||||
-Wctor-dtor-privacy -Wdisabled-optimization
|
||||
-Winvalid-pch -Woverloaded-virtual
|
||||
-Wconversion -Wswitch-enum -Wundef
|
||||
-Wno-ctor-dtor-privacy -Wno-format-nonliteral)
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 4.6)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
-Wno-dangling-else -Wno-unused-local-typedefs)
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 5.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wdouble-promotion
|
||||
-Wtrampolines -Wzero-as-null-pointer-constant -Wuseless-cast
|
||||
-Wvector-operation-performance -Wsized-deallocation -Wshadow)
|
||||
endif ()
|
||||
if (NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS 6.0)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS} -Wshift-overflow=2
|
||||
-Wnull-dereference -Wduplicated-cond)
|
||||
endif ()
|
||||
set(WERROR_FLAG -Werror)
|
||||
endif ()
|
||||
|
||||
if (CMAKE_CXX_COMPILER_ID MATCHES "Clang")
|
||||
set(PEDANTIC_COMPILE_FLAGS -Wall -Wextra -pedantic -Wconversion -Wundef
|
||||
-Wdeprecated -Wweak-vtables -Wshadow
|
||||
-Wno-gnu-zero-variadic-macro-arguments)
|
||||
check_cxx_compiler_flag(-Wzero-as-null-pointer-constant HAS_NULLPTR_WARNING)
|
||||
if (HAS_NULLPTR_WARNING)
|
||||
set(PEDANTIC_COMPILE_FLAGS ${PEDANTIC_COMPILE_FLAGS}
|
||||
-Wzero-as-null-pointer-constant)
|
||||
endif ()
|
||||
set(WERROR_FLAG -Werror)
|
||||
endif ()
|
||||
|
||||
if (MSVC)
|
||||
set(PEDANTIC_COMPILE_FLAGS /W3)
|
||||
set(WERROR_FLAG /WX)
|
||||
endif ()
|
||||
|
||||
if (FMT_MASTER_PROJECT AND CMAKE_GENERATOR MATCHES "Visual Studio")
|
||||
# If Microsoft SDK is installed create script run-msbuild.bat that
|
||||
# calls SetEnv.cmd to set up build environment and runs msbuild.
|
||||
# It is useful when building Visual Studio projects with the SDK
|
||||
# toolchain rather than Visual Studio.
|
||||
include(FindSetEnv)
|
||||
if (WINSDK_SETENV)
|
||||
set(MSBUILD_SETUP "call \"${WINSDK_SETENV}\"")
|
||||
endif ()
|
||||
# Set FrameworkPathOverride to get rid of MSB3644 warnings.
|
||||
join(netfxpath
|
||||
"C:\\Program Files\\Reference Assemblies\\Microsoft\\Framework\\"
|
||||
".NETFramework\\v4.0")
|
||||
file(WRITE run-msbuild.bat "
|
||||
${MSBUILD_SETUP}
|
||||
${CMAKE_MAKE_PROGRAM} -p:FrameworkPathOverride=\"${netfxpath}\" %*")
|
||||
endif ()
|
||||
|
||||
set(strtod_l_headers stdlib.h)
|
||||
if (APPLE)
|
||||
set(strtod_l_headers ${strtod_l_headers} xlocale.h)
|
||||
endif ()
|
||||
|
||||
include(CheckSymbolExists)
|
||||
if (WIN32)
|
||||
check_symbol_exists(_strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
else ()
|
||||
check_symbol_exists(strtod_l "${strtod_l_headers}" HAVE_STRTOD_L)
|
||||
endif ()
|
||||
|
||||
function(add_headers VAR)
|
||||
set(headers ${${VAR}})
|
||||
foreach (header ${ARGN})
|
||||
set(headers ${headers} include/fmt/${header})
|
||||
endforeach()
|
||||
set(${VAR} ${headers} PARENT_SCOPE)
|
||||
endfunction()
|
||||
|
||||
# Define the fmt library, its includes and the needed defines.
|
||||
add_headers(FMT_HEADERS args.h chrono.h color.h compile.h core.h format.h
|
||||
format-inl.h locale.h os.h ostream.h printf.h ranges.h
|
||||
xchar.h)
|
||||
if (FMT_MODULE)
|
||||
set(FMT_SOURCES src/fmt.cc)
|
||||
elseif (FMT_OS)
|
||||
set(FMT_SOURCES src/format.cc src/os.cc)
|
||||
else()
|
||||
set(FMT_SOURCES src/format.cc)
|
||||
endif ()
|
||||
|
||||
add_library(fmt ${FMT_SOURCES} ${FMT_HEADERS})
|
||||
add_library(fmt::fmt ALIAS fmt)
|
||||
|
||||
if (HAVE_STRTOD_L)
|
||||
target_compile_definitions(fmt PUBLIC FMT_LOCALE)
|
||||
endif ()
|
||||
|
||||
if (MINGW)
|
||||
check_cxx_compiler_flag("Wa,-mbig-obj" FMT_HAS_MBIG_OBJ)
|
||||
if (${FMT_HAS_MBIG_OBJ})
|
||||
target_compile_options(fmt PUBLIC "-Wa,-mbig-obj")
|
||||
endif()
|
||||
endif ()
|
||||
|
||||
if (FMT_WERROR)
|
||||
target_compile_options(fmt PRIVATE ${WERROR_FLAG})
|
||||
endif ()
|
||||
if (FMT_PEDANTIC)
|
||||
target_compile_options(fmt PRIVATE ${PEDANTIC_COMPILE_FLAGS})
|
||||
endif ()
|
||||
if (FMT_MODULE)
|
||||
enable_module(fmt)
|
||||
endif ()
|
||||
|
||||
target_compile_features(fmt INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt PUBLIC
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
set(FMT_DEBUG_POSTFIX d CACHE STRING "Debug library postfix.")
|
||||
|
||||
set_target_properties(fmt PROPERTIES
|
||||
VERSION ${FMT_VERSION} SOVERSION ${CPACK_PACKAGE_VERSION_MAJOR}
|
||||
DEBUG_POSTFIX "${FMT_DEBUG_POSTFIX}")
|
||||
|
||||
# Set FMT_LIB_NAME for pkg-config fmt.pc. We cannot use the OUTPUT_NAME target
|
||||
# property because it's not set by default.
|
||||
set(FMT_LIB_NAME fmt)
|
||||
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
|
||||
set(FMT_LIB_NAME ${FMT_LIB_NAME}${FMT_DEBUG_POSTFIX})
|
||||
endif ()
|
||||
|
||||
if (BUILD_SHARED_LIBS)
|
||||
if (UNIX AND NOT APPLE AND NOT ${CMAKE_SYSTEM_NAME} MATCHES "SunOS" AND
|
||||
NOT EMSCRIPTEN)
|
||||
# Fix rpmlint warning:
|
||||
# unused-direct-shlib-dependency /usr/lib/libformat.so.1.1.0 /lib/libm.so.6.
|
||||
target_link_libraries(fmt -Wl,--as-needed)
|
||||
endif ()
|
||||
target_compile_definitions(fmt PRIVATE FMT_EXPORT INTERFACE FMT_SHARED)
|
||||
endif ()
|
||||
if (FMT_SAFE_DURATION_CAST)
|
||||
target_compile_definitions(fmt PUBLIC FMT_SAFE_DURATION_CAST)
|
||||
endif()
|
||||
|
||||
add_library(fmt-header-only INTERFACE)
|
||||
add_library(fmt::fmt-header-only ALIAS fmt-header-only)
|
||||
|
||||
target_compile_definitions(fmt-header-only INTERFACE FMT_HEADER_ONLY=1)
|
||||
target_compile_features(fmt-header-only INTERFACE ${FMT_REQUIRED_FEATURES})
|
||||
|
||||
target_include_directories(fmt-header-only INTERFACE
|
||||
$<BUILD_INTERFACE:${PROJECT_SOURCE_DIR}/include>
|
||||
$<INSTALL_INTERFACE:${FMT_INC_DIR}>)
|
||||
|
||||
# Install targets.
|
||||
if (FMT_INSTALL)
|
||||
include(CMakePackageConfigHelpers)
|
||||
set_verbose(FMT_CMAKE_DIR ${CMAKE_INSTALL_LIBDIR}/cmake/fmt CACHE STRING
|
||||
"Installation directory for cmake files, a relative path that "
|
||||
"will be joined with ${CMAKE_INSTALL_PREFIX} or an absolute "
|
||||
"path.")
|
||||
set(version_config ${PROJECT_BINARY_DIR}/fmt-config-version.cmake)
|
||||
set(project_config ${PROJECT_BINARY_DIR}/fmt-config.cmake)
|
||||
set(pkgconfig ${PROJECT_BINARY_DIR}/fmt.pc)
|
||||
set(targets_export_name fmt-targets)
|
||||
|
||||
set_verbose(FMT_LIB_DIR ${CMAKE_INSTALL_LIBDIR} CACHE STRING
|
||||
"Installation directory for libraries, a relative path that "
|
||||
"will be joined to ${CMAKE_INSTALL_PREFIX} or an absolute path.")
|
||||
|
||||
set_verbose(FMT_PKGCONFIG_DIR ${CMAKE_INSTALL_LIBDIR}/pkgconfig CACHE PATH
|
||||
"Installation directory for pkgconfig (.pc) files, a relative "
|
||||
"path that will be joined with ${CMAKE_INSTALL_PREFIX} or an "
|
||||
"absolute path.")
|
||||
|
||||
# Generate the version, config and target files into the build directory.
|
||||
write_basic_package_version_file(
|
||||
${version_config}
|
||||
VERSION ${FMT_VERSION}
|
||||
COMPATIBILITY AnyNewerVersion)
|
||||
|
||||
join_paths(libdir_for_pc_file "\${exec_prefix}" "${FMT_LIB_DIR}")
|
||||
join_paths(includedir_for_pc_file "\${prefix}" "${FMT_INC_DIR}")
|
||||
|
||||
configure_file(
|
||||
"${PROJECT_SOURCE_DIR}/support/cmake/fmt.pc.in"
|
||||
"${pkgconfig}"
|
||||
@ONLY)
|
||||
configure_package_config_file(
|
||||
${PROJECT_SOURCE_DIR}/support/cmake/fmt-config.cmake.in
|
||||
${project_config}
|
||||
INSTALL_DESTINATION ${FMT_CMAKE_DIR})
|
||||
|
||||
set(INSTALL_TARGETS fmt fmt-header-only)
|
||||
|
||||
# Install the library and headers.
|
||||
install(TARGETS ${INSTALL_TARGETS} EXPORT ${targets_export_name}
|
||||
LIBRARY DESTINATION ${FMT_LIB_DIR}
|
||||
ARCHIVE DESTINATION ${FMT_LIB_DIR}
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
|
||||
# Use a namespace because CMake provides better diagnostics for namespaced
|
||||
# imported targets.
|
||||
export(TARGETS ${INSTALL_TARGETS} NAMESPACE fmt::
|
||||
FILE ${PROJECT_BINARY_DIR}/${targets_export_name}.cmake)
|
||||
|
||||
# Install version, config and target files.
|
||||
install(
|
||||
FILES ${project_config} ${version_config}
|
||||
DESTINATION ${FMT_CMAKE_DIR})
|
||||
install(EXPORT ${targets_export_name} DESTINATION ${FMT_CMAKE_DIR}
|
||||
NAMESPACE fmt::)
|
||||
|
||||
install(FILES $<TARGET_PDB_FILE:${INSTALL_TARGETS}>
|
||||
DESTINATION ${FMT_LIB_DIR} OPTIONAL)
|
||||
install(FILES ${FMT_HEADERS} DESTINATION "${FMT_INC_DIR}/fmt")
|
||||
install(FILES "${pkgconfig}" DESTINATION "${FMT_PKGCONFIG_DIR}")
|
||||
endif ()
|
||||
|
||||
if (FMT_DOC)
|
||||
add_subdirectory(doc)
|
||||
endif ()
|
||||
|
||||
if (FMT_TEST)
|
||||
enable_testing()
|
||||
add_subdirectory(test)
|
||||
endif ()
|
||||
|
||||
# Control fuzzing independent of the unit tests.
|
||||
if (FMT_FUZZ)
|
||||
add_subdirectory(test/fuzzing)
|
||||
|
||||
# The FMT_FUZZ macro is used to prevent resource exhaustion in fuzzing
|
||||
# mode and make fuzzing practically possible. It is similar to
|
||||
# FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION but uses a different name to
|
||||
# avoid interfering with fuzzing of projects that use {fmt}.
|
||||
# See also https://llvm.org/docs/LibFuzzer.html#fuzzer-friendly-build-mode.
|
||||
target_compile_definitions(fmt PUBLIC FMT_FUZZ)
|
||||
endif ()
|
||||
|
||||
set(gitignore ${PROJECT_SOURCE_DIR}/.gitignore)
|
||||
if (FMT_MASTER_PROJECT AND EXISTS ${gitignore})
|
||||
# Get the list of ignored files from .gitignore.
|
||||
file (STRINGS ${gitignore} lines)
|
||||
list(REMOVE_ITEM lines /doc/html)
|
||||
foreach (line ${lines})
|
||||
string(REPLACE "." "[.]" line "${line}")
|
||||
string(REPLACE "*" ".*" line "${line}")
|
||||
set(ignored_files ${ignored_files} "${line}$" "${line}/")
|
||||
endforeach ()
|
||||
set(ignored_files ${ignored_files}
|
||||
/.git /breathe /format-benchmark sphinx/ .buildinfo .doctrees)
|
||||
|
||||
set(CPACK_SOURCE_GENERATOR ZIP)
|
||||
set(CPACK_SOURCE_IGNORE_FILES ${ignored_files})
|
||||
set(CPACK_SOURCE_PACKAGE_FILE_NAME fmt-${FMT_VERSION})
|
||||
set(CPACK_PACKAGE_NAME fmt)
|
||||
set(CPACK_RESOURCE_FILE_README ${PROJECT_SOURCE_DIR}/README.rst)
|
||||
include(CPack)
|
||||
endif ()
|
|
@ -0,0 +1,27 @@
|
|||
Copyright (c) 2012 - present, Victor Zverovich
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining
|
||||
a copy of this software and associated documentation files (the
|
||||
"Software"), to deal in the Software without restriction, including
|
||||
without limitation the rights to use, copy, modify, merge, publish,
|
||||
distribute, sublicense, and/or sell copies of the Software, and to
|
||||
permit persons to whom the Software is furnished to do so, subject to
|
||||
the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be
|
||||
included in all copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||
|
||||
--- Optional exception to the license ---
|
||||
|
||||
As an exception, if, as a result of your compiling your source code, portions
|
||||
of this Software are embedded into a machine-executable object form of such
|
||||
source code, you may redistribute such embedded portions in such object form
|
||||
without including the above copyright and permission notices.
|
|
@ -0,0 +1,232 @@
|
|||
// Formatting library for C++ - dynamic format arguments
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_ARGS_H_
|
||||
#define FMT_ARGS_H_
|
||||
|
||||
#include <functional> // std::reference_wrapper
|
||||
#include <memory> // std::unique_ptr
|
||||
#include <vector>
|
||||
|
||||
#include "core.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename T> struct is_reference_wrapper : std::false_type {};
|
||||
template <typename T>
|
||||
struct is_reference_wrapper<std::reference_wrapper<T>> : std::true_type {};
|
||||
|
||||
template <typename T> const T& unwrap(const T& v) { return v; }
|
||||
template <typename T> const T& unwrap(const std::reference_wrapper<T>& v) {
|
||||
return static_cast<const T&>(v);
|
||||
}
|
||||
|
||||
class dynamic_arg_list {
|
||||
// Workaround for clang's -Wweak-vtables. Unlike for regular classes, for
|
||||
// templates it doesn't complain about inability to deduce single translation
|
||||
// unit for placing vtable. So storage_node_base is made a fake template.
|
||||
template <typename = void> struct node {
|
||||
virtual ~node() = default;
|
||||
std::unique_ptr<node<>> next;
|
||||
};
|
||||
|
||||
template <typename T> struct typed_node : node<> {
|
||||
T value;
|
||||
|
||||
template <typename Arg>
|
||||
FMT_CONSTEXPR typed_node(const Arg& arg) : value(arg) {}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR typed_node(const basic_string_view<Char>& arg)
|
||||
: value(arg.data(), arg.size()) {}
|
||||
};
|
||||
|
||||
std::unique_ptr<node<>> head_;
|
||||
|
||||
public:
|
||||
template <typename T, typename Arg> const T& push(const Arg& arg) {
|
||||
auto new_node = std::unique_ptr<typed_node<T>>(new typed_node<T>(arg));
|
||||
auto& value = new_node->value;
|
||||
new_node->next = std::move(head_);
|
||||
head_ = std::move(new_node);
|
||||
return value;
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
/**
|
||||
\rst
|
||||
A dynamic version of `fmt::format_arg_store`.
|
||||
It's equipped with a storage to potentially temporary objects which lifetimes
|
||||
could be shorter than the format arguments object.
|
||||
|
||||
It can be implicitly converted into `~fmt::basic_format_args` for passing
|
||||
into type-erased formatting functions such as `~fmt::vformat`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename Context>
|
||||
class dynamic_format_arg_store
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround a GCC template argument substitution bug.
|
||||
: public basic_format_args<Context>
|
||||
#endif
|
||||
{
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
template <typename T> struct need_copy {
|
||||
static constexpr detail::type mapped_type =
|
||||
detail::mapped_type_constant<T, Context>::value;
|
||||
|
||||
enum {
|
||||
value = !(detail::is_reference_wrapper<T>::value ||
|
||||
std::is_same<T, basic_string_view<char_type>>::value ||
|
||||
std::is_same<T, detail::std_string_view<char_type>>::value ||
|
||||
(mapped_type != detail::type::cstring_type &&
|
||||
mapped_type != detail::type::string_type &&
|
||||
mapped_type != detail::type::custom_type))
|
||||
};
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
using stored_type = conditional_t<detail::is_string<T>::value &&
|
||||
!has_formatter<T, Context>::value &&
|
||||
!detail::is_reference_wrapper<T>::value,
|
||||
std::basic_string<char_type>, T>;
|
||||
|
||||
// Storage of basic_format_arg must be contiguous.
|
||||
std::vector<basic_format_arg<Context>> data_;
|
||||
std::vector<detail::named_arg_info<char_type>> named_info_;
|
||||
|
||||
// Storage of arguments not fitting into basic_format_arg must grow
|
||||
// without relocation because items in data_ refer to it.
|
||||
detail::dynamic_arg_list dynamic_args_;
|
||||
|
||||
friend class basic_format_args<Context>;
|
||||
|
||||
unsigned long long get_types() const {
|
||||
return detail::is_unpacked_bit | data_.size() |
|
||||
(named_info_.empty()
|
||||
? 0ULL
|
||||
: static_cast<unsigned long long>(detail::has_named_args_bit));
|
||||
}
|
||||
|
||||
const basic_format_arg<Context>* data() const {
|
||||
return named_info_.empty() ? data_.data() : data_.data() + 1;
|
||||
}
|
||||
|
||||
template <typename T> void emplace_arg(const T& arg) {
|
||||
data_.emplace_back(detail::make_arg<Context>(arg));
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
void emplace_arg(const detail::named_arg<char_type, T>& arg) {
|
||||
if (named_info_.empty()) {
|
||||
constexpr const detail::named_arg_info<char_type>* zero_ptr{nullptr};
|
||||
data_.insert(data_.begin(), {zero_ptr, 0});
|
||||
}
|
||||
data_.emplace_back(detail::make_arg<Context>(detail::unwrap(arg.value)));
|
||||
auto pop_one = [](std::vector<basic_format_arg<Context>>* data) {
|
||||
data->pop_back();
|
||||
};
|
||||
std::unique_ptr<std::vector<basic_format_arg<Context>>, decltype(pop_one)>
|
||||
guard{&data_, pop_one};
|
||||
named_info_.push_back({arg.name, static_cast<int>(data_.size() - 2u)});
|
||||
data_[0].value_.named_args = {named_info_.data(), named_info_.size()};
|
||||
guard.release();
|
||||
}
|
||||
|
||||
public:
|
||||
/**
|
||||
\rst
|
||||
Adds an argument into the dynamic store for later passing to a formatting
|
||||
function.
|
||||
|
||||
Note that custom types and string types (but not string views) are copied
|
||||
into the store dynamically allocating memory if necessary.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
store.push_back(42);
|
||||
store.push_back("abc");
|
||||
store.push_back(1.5f);
|
||||
std::string result = fmt::vformat("{} and {} and {}", store);
|
||||
\endrst
|
||||
*/
|
||||
template <typename T> void push_back(const T& arg) {
|
||||
if (detail::const_check(need_copy<T>::value))
|
||||
emplace_arg(dynamic_args_.push<stored_type<T>>(arg));
|
||||
else
|
||||
emplace_arg(detail::unwrap(arg));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Adds a reference to the argument into the dynamic store for later passing to
|
||||
a formatting function.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::dynamic_format_arg_store<fmt::format_context> store;
|
||||
char band[] = "Rolling Stones";
|
||||
store.push_back(std::cref(band));
|
||||
band[9] = 'c'; // Changing str affects the output.
|
||||
std::string result = fmt::vformat("{}", store);
|
||||
// result == "Rolling Scones"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T> void push_back(std::reference_wrapper<T> arg) {
|
||||
static_assert(
|
||||
need_copy<T>::value,
|
||||
"objects of built-in types and string views are always copied");
|
||||
emplace_arg(arg.get());
|
||||
}
|
||||
|
||||
/**
|
||||
Adds named argument into the dynamic store for later passing to a formatting
|
||||
function. ``std::reference_wrapper`` is supported to avoid copying of the
|
||||
argument. The name is always copied into the store.
|
||||
*/
|
||||
template <typename T>
|
||||
void push_back(const detail::named_arg<char_type, T>& arg) {
|
||||
const char_type* arg_name =
|
||||
dynamic_args_.push<std::basic_string<char_type>>(arg.name).c_str();
|
||||
if (detail::const_check(need_copy<T>::value)) {
|
||||
emplace_arg(
|
||||
fmt::arg(arg_name, dynamic_args_.push<stored_type<T>>(arg.value)));
|
||||
} else {
|
||||
emplace_arg(fmt::arg(arg_name, arg.value));
|
||||
}
|
||||
}
|
||||
|
||||
/** Erase all elements from the store */
|
||||
void clear() {
|
||||
data_.clear();
|
||||
named_info_.clear();
|
||||
dynamic_args_ = detail::dynamic_arg_list();
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Reserves space to store at least *new_cap* arguments including
|
||||
*new_cap_named* named arguments.
|
||||
\endrst
|
||||
*/
|
||||
void reserve(size_t new_cap, size_t new_cap_named) {
|
||||
FMT_ASSERT(new_cap >= new_cap_named,
|
||||
"Set of arguments includes set of named arguments");
|
||||
data_.reserve(new_cap);
|
||||
named_info_.reserve(new_cap_named);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_ARGS_H_
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,627 @@
|
|||
// Formatting library for C++ - color support
|
||||
//
|
||||
// Copyright (c) 2018 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COLOR_H_
|
||||
#define FMT_COLOR_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// __declspec(deprecated) is broken in some MSVC versions.
|
||||
#if FMT_MSC_VER
|
||||
# define FMT_DEPRECATED_NONMSVC
|
||||
#else
|
||||
# define FMT_DEPRECATED_NONMSVC FMT_DEPRECATED
|
||||
#endif
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
enum class color : uint32_t {
|
||||
alice_blue = 0xF0F8FF, // rgb(240,248,255)
|
||||
antique_white = 0xFAEBD7, // rgb(250,235,215)
|
||||
aqua = 0x00FFFF, // rgb(0,255,255)
|
||||
aquamarine = 0x7FFFD4, // rgb(127,255,212)
|
||||
azure = 0xF0FFFF, // rgb(240,255,255)
|
||||
beige = 0xF5F5DC, // rgb(245,245,220)
|
||||
bisque = 0xFFE4C4, // rgb(255,228,196)
|
||||
black = 0x000000, // rgb(0,0,0)
|
||||
blanched_almond = 0xFFEBCD, // rgb(255,235,205)
|
||||
blue = 0x0000FF, // rgb(0,0,255)
|
||||
blue_violet = 0x8A2BE2, // rgb(138,43,226)
|
||||
brown = 0xA52A2A, // rgb(165,42,42)
|
||||
burly_wood = 0xDEB887, // rgb(222,184,135)
|
||||
cadet_blue = 0x5F9EA0, // rgb(95,158,160)
|
||||
chartreuse = 0x7FFF00, // rgb(127,255,0)
|
||||
chocolate = 0xD2691E, // rgb(210,105,30)
|
||||
coral = 0xFF7F50, // rgb(255,127,80)
|
||||
cornflower_blue = 0x6495ED, // rgb(100,149,237)
|
||||
cornsilk = 0xFFF8DC, // rgb(255,248,220)
|
||||
crimson = 0xDC143C, // rgb(220,20,60)
|
||||
cyan = 0x00FFFF, // rgb(0,255,255)
|
||||
dark_blue = 0x00008B, // rgb(0,0,139)
|
||||
dark_cyan = 0x008B8B, // rgb(0,139,139)
|
||||
dark_golden_rod = 0xB8860B, // rgb(184,134,11)
|
||||
dark_gray = 0xA9A9A9, // rgb(169,169,169)
|
||||
dark_green = 0x006400, // rgb(0,100,0)
|
||||
dark_khaki = 0xBDB76B, // rgb(189,183,107)
|
||||
dark_magenta = 0x8B008B, // rgb(139,0,139)
|
||||
dark_olive_green = 0x556B2F, // rgb(85,107,47)
|
||||
dark_orange = 0xFF8C00, // rgb(255,140,0)
|
||||
dark_orchid = 0x9932CC, // rgb(153,50,204)
|
||||
dark_red = 0x8B0000, // rgb(139,0,0)
|
||||
dark_salmon = 0xE9967A, // rgb(233,150,122)
|
||||
dark_sea_green = 0x8FBC8F, // rgb(143,188,143)
|
||||
dark_slate_blue = 0x483D8B, // rgb(72,61,139)
|
||||
dark_slate_gray = 0x2F4F4F, // rgb(47,79,79)
|
||||
dark_turquoise = 0x00CED1, // rgb(0,206,209)
|
||||
dark_violet = 0x9400D3, // rgb(148,0,211)
|
||||
deep_pink = 0xFF1493, // rgb(255,20,147)
|
||||
deep_sky_blue = 0x00BFFF, // rgb(0,191,255)
|
||||
dim_gray = 0x696969, // rgb(105,105,105)
|
||||
dodger_blue = 0x1E90FF, // rgb(30,144,255)
|
||||
fire_brick = 0xB22222, // rgb(178,34,34)
|
||||
floral_white = 0xFFFAF0, // rgb(255,250,240)
|
||||
forest_green = 0x228B22, // rgb(34,139,34)
|
||||
fuchsia = 0xFF00FF, // rgb(255,0,255)
|
||||
gainsboro = 0xDCDCDC, // rgb(220,220,220)
|
||||
ghost_white = 0xF8F8FF, // rgb(248,248,255)
|
||||
gold = 0xFFD700, // rgb(255,215,0)
|
||||
golden_rod = 0xDAA520, // rgb(218,165,32)
|
||||
gray = 0x808080, // rgb(128,128,128)
|
||||
green = 0x008000, // rgb(0,128,0)
|
||||
green_yellow = 0xADFF2F, // rgb(173,255,47)
|
||||
honey_dew = 0xF0FFF0, // rgb(240,255,240)
|
||||
hot_pink = 0xFF69B4, // rgb(255,105,180)
|
||||
indian_red = 0xCD5C5C, // rgb(205,92,92)
|
||||
indigo = 0x4B0082, // rgb(75,0,130)
|
||||
ivory = 0xFFFFF0, // rgb(255,255,240)
|
||||
khaki = 0xF0E68C, // rgb(240,230,140)
|
||||
lavender = 0xE6E6FA, // rgb(230,230,250)
|
||||
lavender_blush = 0xFFF0F5, // rgb(255,240,245)
|
||||
lawn_green = 0x7CFC00, // rgb(124,252,0)
|
||||
lemon_chiffon = 0xFFFACD, // rgb(255,250,205)
|
||||
light_blue = 0xADD8E6, // rgb(173,216,230)
|
||||
light_coral = 0xF08080, // rgb(240,128,128)
|
||||
light_cyan = 0xE0FFFF, // rgb(224,255,255)
|
||||
light_golden_rod_yellow = 0xFAFAD2, // rgb(250,250,210)
|
||||
light_gray = 0xD3D3D3, // rgb(211,211,211)
|
||||
light_green = 0x90EE90, // rgb(144,238,144)
|
||||
light_pink = 0xFFB6C1, // rgb(255,182,193)
|
||||
light_salmon = 0xFFA07A, // rgb(255,160,122)
|
||||
light_sea_green = 0x20B2AA, // rgb(32,178,170)
|
||||
light_sky_blue = 0x87CEFA, // rgb(135,206,250)
|
||||
light_slate_gray = 0x778899, // rgb(119,136,153)
|
||||
light_steel_blue = 0xB0C4DE, // rgb(176,196,222)
|
||||
light_yellow = 0xFFFFE0, // rgb(255,255,224)
|
||||
lime = 0x00FF00, // rgb(0,255,0)
|
||||
lime_green = 0x32CD32, // rgb(50,205,50)
|
||||
linen = 0xFAF0E6, // rgb(250,240,230)
|
||||
magenta = 0xFF00FF, // rgb(255,0,255)
|
||||
maroon = 0x800000, // rgb(128,0,0)
|
||||
medium_aquamarine = 0x66CDAA, // rgb(102,205,170)
|
||||
medium_blue = 0x0000CD, // rgb(0,0,205)
|
||||
medium_orchid = 0xBA55D3, // rgb(186,85,211)
|
||||
medium_purple = 0x9370DB, // rgb(147,112,219)
|
||||
medium_sea_green = 0x3CB371, // rgb(60,179,113)
|
||||
medium_slate_blue = 0x7B68EE, // rgb(123,104,238)
|
||||
medium_spring_green = 0x00FA9A, // rgb(0,250,154)
|
||||
medium_turquoise = 0x48D1CC, // rgb(72,209,204)
|
||||
medium_violet_red = 0xC71585, // rgb(199,21,133)
|
||||
midnight_blue = 0x191970, // rgb(25,25,112)
|
||||
mint_cream = 0xF5FFFA, // rgb(245,255,250)
|
||||
misty_rose = 0xFFE4E1, // rgb(255,228,225)
|
||||
moccasin = 0xFFE4B5, // rgb(255,228,181)
|
||||
navajo_white = 0xFFDEAD, // rgb(255,222,173)
|
||||
navy = 0x000080, // rgb(0,0,128)
|
||||
old_lace = 0xFDF5E6, // rgb(253,245,230)
|
||||
olive = 0x808000, // rgb(128,128,0)
|
||||
olive_drab = 0x6B8E23, // rgb(107,142,35)
|
||||
orange = 0xFFA500, // rgb(255,165,0)
|
||||
orange_red = 0xFF4500, // rgb(255,69,0)
|
||||
orchid = 0xDA70D6, // rgb(218,112,214)
|
||||
pale_golden_rod = 0xEEE8AA, // rgb(238,232,170)
|
||||
pale_green = 0x98FB98, // rgb(152,251,152)
|
||||
pale_turquoise = 0xAFEEEE, // rgb(175,238,238)
|
||||
pale_violet_red = 0xDB7093, // rgb(219,112,147)
|
||||
papaya_whip = 0xFFEFD5, // rgb(255,239,213)
|
||||
peach_puff = 0xFFDAB9, // rgb(255,218,185)
|
||||
peru = 0xCD853F, // rgb(205,133,63)
|
||||
pink = 0xFFC0CB, // rgb(255,192,203)
|
||||
plum = 0xDDA0DD, // rgb(221,160,221)
|
||||
powder_blue = 0xB0E0E6, // rgb(176,224,230)
|
||||
purple = 0x800080, // rgb(128,0,128)
|
||||
rebecca_purple = 0x663399, // rgb(102,51,153)
|
||||
red = 0xFF0000, // rgb(255,0,0)
|
||||
rosy_brown = 0xBC8F8F, // rgb(188,143,143)
|
||||
royal_blue = 0x4169E1, // rgb(65,105,225)
|
||||
saddle_brown = 0x8B4513, // rgb(139,69,19)
|
||||
salmon = 0xFA8072, // rgb(250,128,114)
|
||||
sandy_brown = 0xF4A460, // rgb(244,164,96)
|
||||
sea_green = 0x2E8B57, // rgb(46,139,87)
|
||||
sea_shell = 0xFFF5EE, // rgb(255,245,238)
|
||||
sienna = 0xA0522D, // rgb(160,82,45)
|
||||
silver = 0xC0C0C0, // rgb(192,192,192)
|
||||
sky_blue = 0x87CEEB, // rgb(135,206,235)
|
||||
slate_blue = 0x6A5ACD, // rgb(106,90,205)
|
||||
slate_gray = 0x708090, // rgb(112,128,144)
|
||||
snow = 0xFFFAFA, // rgb(255,250,250)
|
||||
spring_green = 0x00FF7F, // rgb(0,255,127)
|
||||
steel_blue = 0x4682B4, // rgb(70,130,180)
|
||||
tan = 0xD2B48C, // rgb(210,180,140)
|
||||
teal = 0x008080, // rgb(0,128,128)
|
||||
thistle = 0xD8BFD8, // rgb(216,191,216)
|
||||
tomato = 0xFF6347, // rgb(255,99,71)
|
||||
turquoise = 0x40E0D0, // rgb(64,224,208)
|
||||
violet = 0xEE82EE, // rgb(238,130,238)
|
||||
wheat = 0xF5DEB3, // rgb(245,222,179)
|
||||
white = 0xFFFFFF, // rgb(255,255,255)
|
||||
white_smoke = 0xF5F5F5, // rgb(245,245,245)
|
||||
yellow = 0xFFFF00, // rgb(255,255,0)
|
||||
yellow_green = 0x9ACD32 // rgb(154,205,50)
|
||||
}; // enum class color
|
||||
|
||||
enum class terminal_color : uint8_t {
|
||||
black = 30,
|
||||
red,
|
||||
green,
|
||||
yellow,
|
||||
blue,
|
||||
magenta,
|
||||
cyan,
|
||||
white,
|
||||
bright_black = 90,
|
||||
bright_red,
|
||||
bright_green,
|
||||
bright_yellow,
|
||||
bright_blue,
|
||||
bright_magenta,
|
||||
bright_cyan,
|
||||
bright_white
|
||||
};
|
||||
|
||||
enum class emphasis : uint8_t {
|
||||
bold = 1,
|
||||
italic = 1 << 1,
|
||||
underline = 1 << 2,
|
||||
strikethrough = 1 << 3
|
||||
};
|
||||
|
||||
// rgb is a struct for red, green and blue colors.
|
||||
// Using the name "rgb" makes some editors show the color in a tooltip.
|
||||
struct rgb {
|
||||
FMT_CONSTEXPR rgb() : r(0), g(0), b(0) {}
|
||||
FMT_CONSTEXPR rgb(uint8_t r_, uint8_t g_, uint8_t b_) : r(r_), g(g_), b(b_) {}
|
||||
FMT_CONSTEXPR rgb(uint32_t hex)
|
||||
: r((hex >> 16) & 0xFF), g((hex >> 8) & 0xFF), b(hex & 0xFF) {}
|
||||
FMT_CONSTEXPR rgb(color hex)
|
||||
: r((uint32_t(hex) >> 16) & 0xFF),
|
||||
g((uint32_t(hex) >> 8) & 0xFF),
|
||||
b(uint32_t(hex) & 0xFF) {}
|
||||
uint8_t r;
|
||||
uint8_t g;
|
||||
uint8_t b;
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
// color is a struct of either a rgb color or a terminal color.
|
||||
struct color_type {
|
||||
FMT_CONSTEXPR color_type() FMT_NOEXCEPT : is_rgb(), value{} {}
|
||||
FMT_CONSTEXPR color_type(color rgb_color) FMT_NOEXCEPT : is_rgb(true),
|
||||
value{} {
|
||||
value.rgb_color = static_cast<uint32_t>(rgb_color);
|
||||
}
|
||||
FMT_CONSTEXPR color_type(rgb rgb_color) FMT_NOEXCEPT : is_rgb(true), value{} {
|
||||
value.rgb_color = (static_cast<uint32_t>(rgb_color.r) << 16) |
|
||||
(static_cast<uint32_t>(rgb_color.g) << 8) | rgb_color.b;
|
||||
}
|
||||
FMT_CONSTEXPR color_type(terminal_color term_color) FMT_NOEXCEPT : is_rgb(),
|
||||
value{} {
|
||||
value.term_color = static_cast<uint8_t>(term_color);
|
||||
}
|
||||
bool is_rgb;
|
||||
union color_union {
|
||||
uint8_t term_color;
|
||||
uint32_t rgb_color;
|
||||
} value;
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
/** A text style consisting of foreground and background colors and emphasis. */
|
||||
class text_style {
|
||||
public:
|
||||
FMT_CONSTEXPR text_style(emphasis em = emphasis()) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems(em) {}
|
||||
|
||||
FMT_CONSTEXPR text_style& operator|=(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
foreground_color.value.rgb_color |= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't OR a terminal color"));
|
||||
background_color.value.rgb_color |= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) |
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR text_style operator|(text_style lhs,
|
||||
const text_style& rhs) {
|
||||
return lhs |= rhs;
|
||||
}
|
||||
|
||||
FMT_DEPRECATED_NONMSVC FMT_CONSTEXPR text_style& operator&=(
|
||||
const text_style& rhs) {
|
||||
return and_assign(rhs);
|
||||
}
|
||||
|
||||
FMT_DEPRECATED_NONMSVC friend FMT_CONSTEXPR text_style
|
||||
operator&(text_style lhs, const text_style& rhs) {
|
||||
return lhs.and_assign(rhs);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR bool has_foreground() const FMT_NOEXCEPT {
|
||||
return set_foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_background() const FMT_NOEXCEPT {
|
||||
return set_background_color;
|
||||
}
|
||||
FMT_CONSTEXPR bool has_emphasis() const FMT_NOEXCEPT {
|
||||
return static_cast<uint8_t>(ems) != 0;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_foreground() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_foreground(), "no foreground specified for this style");
|
||||
return foreground_color;
|
||||
}
|
||||
FMT_CONSTEXPR detail::color_type get_background() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_background(), "no background specified for this style");
|
||||
return background_color;
|
||||
}
|
||||
FMT_CONSTEXPR emphasis get_emphasis() const FMT_NOEXCEPT {
|
||||
FMT_ASSERT(has_emphasis(), "no emphasis specified for this style");
|
||||
return ems;
|
||||
}
|
||||
|
||||
private:
|
||||
FMT_CONSTEXPR text_style(bool is_foreground,
|
||||
detail::color_type text_color) FMT_NOEXCEPT
|
||||
: set_foreground_color(),
|
||||
set_background_color(),
|
||||
ems() {
|
||||
if (is_foreground) {
|
||||
foreground_color = text_color;
|
||||
set_foreground_color = true;
|
||||
} else {
|
||||
background_color = text_color;
|
||||
set_background_color = true;
|
||||
}
|
||||
}
|
||||
|
||||
// DEPRECATED!
|
||||
FMT_CONSTEXPR text_style& and_assign(const text_style& rhs) {
|
||||
if (!set_foreground_color) {
|
||||
set_foreground_color = rhs.set_foreground_color;
|
||||
foreground_color = rhs.foreground_color;
|
||||
} else if (rhs.set_foreground_color) {
|
||||
if (!foreground_color.is_rgb || !rhs.foreground_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
foreground_color.value.rgb_color &= rhs.foreground_color.value.rgb_color;
|
||||
}
|
||||
|
||||
if (!set_background_color) {
|
||||
set_background_color = rhs.set_background_color;
|
||||
background_color = rhs.background_color;
|
||||
} else if (rhs.set_background_color) {
|
||||
if (!background_color.is_rgb || !rhs.background_color.is_rgb)
|
||||
FMT_THROW(format_error("can't AND a terminal color"));
|
||||
background_color.value.rgb_color &= rhs.background_color.value.rgb_color;
|
||||
}
|
||||
|
||||
ems = static_cast<emphasis>(static_cast<uint8_t>(ems) &
|
||||
static_cast<uint8_t>(rhs.ems));
|
||||
return *this;
|
||||
}
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style fg(detail::color_type foreground)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
friend FMT_CONSTEXPR_DECL text_style bg(detail::color_type background)
|
||||
FMT_NOEXCEPT;
|
||||
|
||||
detail::color_type foreground_color;
|
||||
detail::color_type background_color;
|
||||
bool set_foreground_color;
|
||||
bool set_background_color;
|
||||
emphasis ems;
|
||||
};
|
||||
|
||||
/** Creates a text style from the foreground (text) color. */
|
||||
FMT_CONSTEXPR inline text_style fg(detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return text_style(true, foreground);
|
||||
}
|
||||
|
||||
/** Creates a text style from the background color. */
|
||||
FMT_CONSTEXPR inline text_style bg(detail::color_type background) FMT_NOEXCEPT {
|
||||
return text_style(false, background);
|
||||
}
|
||||
|
||||
FMT_CONSTEXPR inline text_style operator|(emphasis lhs,
|
||||
emphasis rhs) FMT_NOEXCEPT {
|
||||
return text_style(lhs) | rhs;
|
||||
}
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
template <typename Char> struct ansi_color_escape {
|
||||
FMT_CONSTEXPR ansi_color_escape(detail::color_type text_color,
|
||||
const char* esc) FMT_NOEXCEPT {
|
||||
// If we have a terminal color, we need to output another escape code
|
||||
// sequence.
|
||||
if (!text_color.is_rgb) {
|
||||
bool is_background = esc == string_view("\x1b[48;2;");
|
||||
uint32_t value = text_color.value.term_color;
|
||||
// Background ASCII codes are the same as the foreground ones but with
|
||||
// 10 more.
|
||||
if (is_background) value += 10u;
|
||||
|
||||
size_t index = 0;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
|
||||
if (value >= 100u) {
|
||||
buffer[index++] = static_cast<Char>('1');
|
||||
value %= 100u;
|
||||
}
|
||||
buffer[index++] = static_cast<Char>('0' + value / 10u);
|
||||
buffer[index++] = static_cast<Char>('0' + value % 10u);
|
||||
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
buffer[index++] = static_cast<Char>('\0');
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < 7; i++) {
|
||||
buffer[i] = static_cast<Char>(esc[i]);
|
||||
}
|
||||
rgb color(text_color.value.rgb_color);
|
||||
to_esc(color.r, buffer + 7, ';');
|
||||
to_esc(color.g, buffer + 11, ';');
|
||||
to_esc(color.b, buffer + 15, 'm');
|
||||
buffer[19] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR ansi_color_escape(emphasis em) FMT_NOEXCEPT {
|
||||
uint8_t em_codes[4] = {};
|
||||
uint8_t em_bits = static_cast<uint8_t>(em);
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::bold)) em_codes[0] = 1;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::italic)) em_codes[1] = 3;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::underline)) em_codes[2] = 4;
|
||||
if (em_bits & static_cast<uint8_t>(emphasis::strikethrough))
|
||||
em_codes[3] = 9;
|
||||
|
||||
size_t index = 0;
|
||||
for (int i = 0; i < 4; ++i) {
|
||||
if (!em_codes[i]) continue;
|
||||
buffer[index++] = static_cast<Char>('\x1b');
|
||||
buffer[index++] = static_cast<Char>('[');
|
||||
buffer[index++] = static_cast<Char>('0' + em_codes[i]);
|
||||
buffer[index++] = static_cast<Char>('m');
|
||||
}
|
||||
buffer[index++] = static_cast<Char>(0);
|
||||
}
|
||||
FMT_CONSTEXPR operator const Char*() const FMT_NOEXCEPT { return buffer; }
|
||||
|
||||
FMT_CONSTEXPR const Char* begin() const FMT_NOEXCEPT { return buffer; }
|
||||
FMT_CONSTEXPR_CHAR_TRAITS const Char* end() const FMT_NOEXCEPT {
|
||||
return buffer + std::char_traits<Char>::length(buffer);
|
||||
}
|
||||
|
||||
private:
|
||||
Char buffer[7u + 3u * 4u + 1u];
|
||||
|
||||
static FMT_CONSTEXPR void to_esc(uint8_t c, Char* out,
|
||||
char delimiter) FMT_NOEXCEPT {
|
||||
out[0] = static_cast<Char>('0' + c / 100);
|
||||
out[1] = static_cast<Char>('0' + c / 10 % 10);
|
||||
out[2] = static_cast<Char>('0' + c % 10);
|
||||
out[3] = static_cast<Char>(delimiter);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_foreground_color(
|
||||
detail::color_type foreground) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(foreground, "\x1b[38;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_background_color(
|
||||
detail::color_type background) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(background, "\x1b[48;2;");
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
FMT_CONSTEXPR ansi_color_escape<Char> make_emphasis(emphasis em) FMT_NOEXCEPT {
|
||||
return ansi_color_escape<Char>(em);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void fputs(const Char* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputs(chars, stream);
|
||||
}
|
||||
|
||||
template <>
|
||||
inline void fputs<wchar_t>(const wchar_t* chars, FILE* stream) FMT_NOEXCEPT {
|
||||
std::fputws(chars, stream);
|
||||
}
|
||||
|
||||
template <typename Char> inline void reset_color(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs("\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <> inline void reset_color<wchar_t>(FILE* stream) FMT_NOEXCEPT {
|
||||
fputs(L"\x1b[0m", stream);
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
inline void reset_color(buffer<Char>& buffer) FMT_NOEXCEPT {
|
||||
auto reset_color = string_view("\x1b[0m");
|
||||
buffer.append(reset_color.begin(), reset_color.end());
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
void vformat_to(buffer<Char>& buf, const text_style& ts,
|
||||
basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
bool has_style = false;
|
||||
if (ts.has_emphasis()) {
|
||||
has_style = true;
|
||||
auto emphasis = detail::make_emphasis<Char>(ts.get_emphasis());
|
||||
buf.append(emphasis.begin(), emphasis.end());
|
||||
}
|
||||
if (ts.has_foreground()) {
|
||||
has_style = true;
|
||||
auto foreground = detail::make_foreground_color<Char>(ts.get_foreground());
|
||||
buf.append(foreground.begin(), foreground.end());
|
||||
}
|
||||
if (ts.has_background()) {
|
||||
has_style = true;
|
||||
auto background = detail::make_background_color<Char>(ts.get_background());
|
||||
buf.append(background.begin(), background.end());
|
||||
}
|
||||
detail::vformat_to(buf, format_str, args, {});
|
||||
if (has_style) detail::reset_color<Char>(buf);
|
||||
}
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
void vprint(std::FILE* f, const text_style& ts, const S& format,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format), args);
|
||||
buf.push_back(Char(0));
|
||||
detail::fputs(buf.data(), f);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to the specified file stream using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(std::FILE* f, const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
vprint(f, ts, format_str,
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats a string and prints it to stdout using ANSI escape sequences to
|
||||
specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"Elapsed time: {0:.2f} seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
void print(const text_style& ts, const S& format_str, const Args&... args) {
|
||||
return print(stdout, ts, format_str, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> vformat(
|
||||
const text_style& ts, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buf;
|
||||
detail::vformat_to(buf, ts, to_string_view(format_str), args);
|
||||
return fmt::to_string(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string using ANSI
|
||||
escape sequences to specify text formatting.
|
||||
|
||||
**Example**::
|
||||
|
||||
#include <fmt/color.h>
|
||||
std::string message = fmt::format(fmt::emphasis::bold | fg(fmt::color::red),
|
||||
"The answer is {}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
inline std::basic_string<Char> format(const text_style& ts, const S& format_str,
|
||||
const Args&... args) {
|
||||
return fmt::vformat(ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Formats a string with the given text_style and writes the output to ``out``.
|
||||
*/
|
||||
template <typename OutputIt, typename Char,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value)>
|
||||
OutputIt vformat_to(
|
||||
OutputIt out, const text_style& ts, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, ts, format_str, args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments with the given text_style, writes the result to the output
|
||||
iterator ``out`` and returns the iterator past the end of the output range.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::vector<char> out;
|
||||
fmt::format_to(std::back_inserter(out),
|
||||
fmt::emphasis::bold | fg(fmt::color::red), "{}", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
bool enable = detail::is_output_iterator<OutputIt, char_t<S>>::value&&
|
||||
detail::is_string<S>::value>
|
||||
inline auto format_to(OutputIt out, const text_style& ts, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
return vformat_to(out, ts, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COLOR_H_
|
|
@ -0,0 +1,639 @@
|
|||
// Formatting library for C++ - experimental format string compilation
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich and fmt contributors
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_COMPILE_H_
|
||||
#define FMT_COMPILE_H_
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
// An output iterator that counts the number of objects written to it and
|
||||
// discards them.
|
||||
class counting_iterator {
|
||||
private:
|
||||
size_t count_;
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
using _Unchecked_type = counting_iterator; // Mark iterator as checked.
|
||||
|
||||
struct value_type {
|
||||
template <typename T> void operator=(const T&) {}
|
||||
};
|
||||
|
||||
counting_iterator() : count_(0) {}
|
||||
|
||||
size_t count() const { return count_; }
|
||||
|
||||
counting_iterator& operator++() {
|
||||
++count_;
|
||||
return *this;
|
||||
}
|
||||
counting_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
friend counting_iterator operator+(counting_iterator it, difference_type n) {
|
||||
it.count_ += static_cast<size_t>(n);
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type operator*() const { return {}; }
|
||||
};
|
||||
|
||||
template <typename Char, typename InputIt>
|
||||
inline counting_iterator copy_str(InputIt begin, InputIt end,
|
||||
counting_iterator it) {
|
||||
return it + (end - begin);
|
||||
}
|
||||
|
||||
template <typename OutputIt> class truncating_iterator_base {
|
||||
protected:
|
||||
OutputIt out_;
|
||||
size_t limit_;
|
||||
size_t count_ = 0;
|
||||
|
||||
truncating_iterator_base() : out_(), limit_(0) {}
|
||||
|
||||
truncating_iterator_base(OutputIt out, size_t limit)
|
||||
: out_(out), limit_(limit) {}
|
||||
|
||||
public:
|
||||
using iterator_category = std::output_iterator_tag;
|
||||
using value_type = typename std::iterator_traits<OutputIt>::value_type;
|
||||
using difference_type = std::ptrdiff_t;
|
||||
using pointer = void;
|
||||
using reference = void;
|
||||
using _Unchecked_type =
|
||||
truncating_iterator_base; // Mark iterator as checked.
|
||||
|
||||
OutputIt base() const { return out_; }
|
||||
size_t count() const { return count_; }
|
||||
};
|
||||
|
||||
// An output iterator that truncates the output and counts the number of objects
|
||||
// written to it.
|
||||
template <typename OutputIt,
|
||||
typename Enable = typename std::is_void<
|
||||
typename std::iterator_traits<OutputIt>::value_type>::type>
|
||||
class truncating_iterator;
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::false_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
mutable typename truncating_iterator_base<OutputIt>::value_type blackhole_;
|
||||
|
||||
public:
|
||||
using value_type = typename truncating_iterator_base<OutputIt>::value_type;
|
||||
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
truncating_iterator& operator++() {
|
||||
if (this->count_++ < this->limit_) ++this->out_;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator operator++(int) {
|
||||
auto it = *this;
|
||||
++*this;
|
||||
return it;
|
||||
}
|
||||
|
||||
value_type& operator*() const {
|
||||
return this->count_ < this->limit_ ? *this->out_ : blackhole_;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename OutputIt>
|
||||
class truncating_iterator<OutputIt, std::true_type>
|
||||
: public truncating_iterator_base<OutputIt> {
|
||||
public:
|
||||
truncating_iterator() = default;
|
||||
|
||||
truncating_iterator(OutputIt out, size_t limit)
|
||||
: truncating_iterator_base<OutputIt>(out, limit) {}
|
||||
|
||||
template <typename T> truncating_iterator& operator=(T val) {
|
||||
if (this->count_++ < this->limit_) *this->out_++ = val;
|
||||
return *this;
|
||||
}
|
||||
|
||||
truncating_iterator& operator++() { return *this; }
|
||||
truncating_iterator& operator++(int) { return *this; }
|
||||
truncating_iterator& operator*() { return *this; }
|
||||
};
|
||||
|
||||
// A compile-time string which is compiled into fast formatting code.
|
||||
class compiled_string {};
|
||||
|
||||
template <typename S>
|
||||
struct is_compiled_string : std::is_base_of<compiled_string, S> {};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Converts a string literal *s* into a format string that will be parsed at
|
||||
compile time and converted into efficient formatting code. Requires C++17
|
||||
``constexpr if`` compiler support.
|
||||
|
||||
**Example**::
|
||||
|
||||
// Converts 42 into std::string using the most efficient method and no
|
||||
// runtime format string processing.
|
||||
std::string s = fmt::format(FMT_COMPILE("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
#ifdef __cpp_if_constexpr
|
||||
# define FMT_COMPILE(s) \
|
||||
FMT_STRING_IMPL(s, fmt::detail::compiled_string, explicit)
|
||||
#else
|
||||
# define FMT_COMPILE(s) FMT_STRING(s)
|
||||
#endif
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
template <typename Char, size_t N,
|
||||
fmt::detail_exported::fixed_string<Char, N> Str>
|
||||
struct udl_compiled_string : compiled_string {
|
||||
using char_type = Char;
|
||||
constexpr operator basic_string_view<char_type>() const {
|
||||
return {Str.data, N - 1};
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
||||
template <typename T, typename... Tail>
|
||||
const T& first(const T& value, const Tail&...) {
|
||||
return value;
|
||||
}
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
template <typename... Args> struct type_list {};
|
||||
|
||||
// Returns a reference to the argument at index N from [first, rest...].
|
||||
template <int N, typename T, typename... Args>
|
||||
constexpr const auto& get([[maybe_unused]] const T& first,
|
||||
[[maybe_unused]] const Args&... rest) {
|
||||
static_assert(N < 1 + sizeof...(Args), "index is out of bounds");
|
||||
if constexpr (N == 0)
|
||||
return first;
|
||||
else
|
||||
return get<N - 1>(rest...);
|
||||
}
|
||||
|
||||
template <typename Char, typename... Args>
|
||||
constexpr int get_arg_index_by_name(basic_string_view<Char> name,
|
||||
type_list<Args...>) {
|
||||
return get_arg_index_by_name<Args...>(name);
|
||||
}
|
||||
|
||||
template <int N, typename> struct get_type_impl;
|
||||
|
||||
template <int N, typename... Args> struct get_type_impl<N, type_list<Args...>> {
|
||||
using type = remove_cvref_t<decltype(get<N>(std::declval<Args>()...))>;
|
||||
};
|
||||
|
||||
template <int N, typename T>
|
||||
using get_type = typename get_type_impl<N, T>::type;
|
||||
|
||||
template <typename T> struct is_compiled_format : std::false_type {};
|
||||
|
||||
template <typename Char> struct text {
|
||||
basic_string_view<Char> data;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, data);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<text<Char>> : std::true_type {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr text<Char> make_text(basic_string_view<Char> s, size_t pos,
|
||||
size_t size) {
|
||||
return {{&s[pos], size}};
|
||||
}
|
||||
|
||||
template <typename Char> struct code_unit {
|
||||
Char value;
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&...) const {
|
||||
return write<Char>(out, value);
|
||||
}
|
||||
};
|
||||
|
||||
// This ensures that the argument type is convertible to `const T&`.
|
||||
template <typename T, int N, typename... Args>
|
||||
constexpr const T& get_arg_checked(const Args&... args) {
|
||||
const auto& arg = get<N>(args...);
|
||||
if constexpr (detail::is_named_arg<remove_cvref_t<decltype(arg)>>()) {
|
||||
return arg.value;
|
||||
} else {
|
||||
return arg;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<code_unit<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N.
|
||||
template <typename Char, typename T, int N> struct field {
|
||||
using char_type = Char;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
return write<Char>(out, get_arg_checked<T, N>(args...));
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<field<Char, T, N>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument with name.
|
||||
template <typename Char> struct runtime_named_field {
|
||||
using char_type = Char;
|
||||
basic_string_view<Char> name;
|
||||
|
||||
template <typename OutputIt, typename T>
|
||||
constexpr static bool try_format_argument(
|
||||
OutputIt& out,
|
||||
// [[maybe_unused]] due to unused-but-set-parameter warning in GCC 7,8,9
|
||||
[[maybe_unused]] basic_string_view<Char> arg_name, const T& arg) {
|
||||
if constexpr (is_named_arg<typename std::remove_cv<T>::type>::value) {
|
||||
if (arg_name == arg.name) {
|
||||
out = write<Char>(out, arg.value);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
bool found = (try_format_argument(out, name, args) || ...);
|
||||
if (!found) {
|
||||
throw format_error("argument with specified name is not found");
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_compiled_format<runtime_named_field<Char>> : std::true_type {};
|
||||
|
||||
// A replacement field that refers to argument N and has format specifiers.
|
||||
template <typename Char, typename T, int N> struct spec_field {
|
||||
using char_type = Char;
|
||||
formatter<T, Char> fmt;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr FMT_INLINE OutputIt format(OutputIt out,
|
||||
const Args&... args) const {
|
||||
const auto& vargs =
|
||||
fmt::make_format_args<basic_format_context<OutputIt, Char>>(args...);
|
||||
basic_format_context<OutputIt, Char> ctx(out, vargs);
|
||||
return fmt.format(get_arg_checked<T, N>(args...), ctx);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename T, int N>
|
||||
struct is_compiled_format<spec_field<Char, T, N>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R> struct concat {
|
||||
L lhs;
|
||||
R rhs;
|
||||
using char_type = typename L::char_type;
|
||||
|
||||
template <typename OutputIt, typename... Args>
|
||||
constexpr OutputIt format(OutputIt out, const Args&... args) const {
|
||||
out = lhs.format(out, args...);
|
||||
return rhs.format(out, args...);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename L, typename R>
|
||||
struct is_compiled_format<concat<L, R>> : std::true_type {};
|
||||
|
||||
template <typename L, typename R>
|
||||
constexpr concat<L, R> make_concat(L lhs, R rhs) {
|
||||
return {lhs, rhs};
|
||||
}
|
||||
|
||||
struct unknown_format {};
|
||||
|
||||
template <typename Char>
|
||||
constexpr size_t parse_text(basic_string_view<Char> str, size_t pos) {
|
||||
for (size_t size = str.size(); pos != size; ++pos) {
|
||||
if (str[pos] == '{' || str[pos] == '}') break;
|
||||
}
|
||||
return pos;
|
||||
}
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str);
|
||||
|
||||
template <typename Args, size_t POS, int ID, typename T, typename S>
|
||||
constexpr auto parse_tail(T head, S format_str) {
|
||||
if constexpr (POS !=
|
||||
basic_string_view<typename S::char_type>(format_str).size()) {
|
||||
constexpr auto tail = compile_format_string<Args, POS, ID>(format_str);
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(tail)>,
|
||||
unknown_format>())
|
||||
return tail;
|
||||
else
|
||||
return make_concat(head, tail);
|
||||
} else {
|
||||
return head;
|
||||
}
|
||||
}
|
||||
|
||||
template <typename T, typename Char> struct parse_specs_result {
|
||||
formatter<T, Char> fmt;
|
||||
size_t end;
|
||||
int next_arg_id;
|
||||
};
|
||||
|
||||
constexpr int manual_indexing_id = -1;
|
||||
|
||||
template <typename T, typename Char>
|
||||
constexpr parse_specs_result<T, Char> parse_specs(basic_string_view<Char> str,
|
||||
size_t pos, int next_arg_id) {
|
||||
str.remove_prefix(pos);
|
||||
auto ctx = basic_format_parse_context<Char>(str, {}, next_arg_id);
|
||||
auto f = formatter<T, Char>();
|
||||
auto end = f.parse(ctx);
|
||||
return {f, pos + fmt::detail::to_unsigned(end - str.data()) + 1,
|
||||
next_arg_id == 0 ? manual_indexing_id : ctx.next_arg_id()};
|
||||
}
|
||||
|
||||
template <typename Char> struct arg_id_handler {
|
||||
arg_ref<Char> arg_id;
|
||||
|
||||
constexpr int operator()() {
|
||||
FMT_ASSERT(false, "handler cannot be used with automatic indexing");
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(int id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
constexpr int operator()(basic_string_view<Char> id) {
|
||||
arg_id = arg_ref<Char>(id);
|
||||
return 0;
|
||||
}
|
||||
|
||||
constexpr void on_error(const char* message) { throw format_error(message); }
|
||||
};
|
||||
|
||||
template <typename Char> struct parse_arg_id_result {
|
||||
arg_ref<Char> arg_id;
|
||||
const Char* arg_id_end;
|
||||
};
|
||||
|
||||
template <int ID, typename Char>
|
||||
constexpr auto parse_arg_id(const Char* begin, const Char* end) {
|
||||
auto handler = arg_id_handler<Char>{arg_ref<Char>{}};
|
||||
auto arg_id_end = parse_arg_id(begin, end, handler);
|
||||
return parse_arg_id_result<Char>{handler.arg_id, arg_id_end};
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void> struct field_type {
|
||||
using type = remove_cvref_t<T>;
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct field_type<T, enable_if_t<detail::is_named_arg<T>::value>> {
|
||||
using type = remove_cvref_t<decltype(T::value)>;
|
||||
};
|
||||
|
||||
template <typename T, typename Args, size_t END_POS, int ARG_INDEX, int NEXT_ID,
|
||||
typename S>
|
||||
constexpr auto parse_replacement_field_then_tail(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||
constexpr char_type c = END_POS != str.size() ? str[END_POS] : char_type();
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, END_POS + 1, NEXT_ID>(
|
||||
field<char_type, typename field_type<T>::type, ARG_INDEX>(),
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
constexpr auto result = parse_specs<typename field_type<T>::type>(
|
||||
str, END_POS + 1, NEXT_ID == manual_indexing_id ? 0 : NEXT_ID);
|
||||
return parse_tail<Args, result.end, result.next_arg_id>(
|
||||
spec_field<char_type, typename field_type<T>::type, ARG_INDEX>{
|
||||
result.fmt},
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
|
||||
// Compiles a non-empty format string and returns the compiled representation
|
||||
// or unknown_format() on unrecognized input.
|
||||
template <typename Args, size_t POS, int ID, typename S>
|
||||
constexpr auto compile_format_string(S format_str) {
|
||||
using char_type = typename S::char_type;
|
||||
constexpr auto str = basic_string_view<char_type>(format_str);
|
||||
if constexpr (str[POS] == '{') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '{' in format string");
|
||||
if constexpr (str[POS + 1] == '{') {
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else if constexpr (str[POS + 1] == '}' || str[POS + 1] == ':') {
|
||||
static_assert(ID != manual_indexing_id,
|
||||
"cannot switch from manual to automatic argument indexing");
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<get_type<ID, Args>, Args,
|
||||
POS + 1, ID, next_id>(
|
||||
format_str);
|
||||
} else {
|
||||
constexpr auto arg_id_result =
|
||||
parse_arg_id<ID>(str.data() + POS + 1, str.data() + str.size());
|
||||
constexpr auto arg_id_end_pos = arg_id_result.arg_id_end - str.data();
|
||||
constexpr char_type c =
|
||||
arg_id_end_pos != str.size() ? str[arg_id_end_pos] : char_type();
|
||||
static_assert(c == '}' || c == ':', "missing '}' in format string");
|
||||
if constexpr (arg_id_result.arg_id.kind == arg_id_kind::index) {
|
||||
static_assert(
|
||||
ID == manual_indexing_id || ID == 0,
|
||||
"cannot switch from automatic to manual argument indexing");
|
||||
constexpr auto arg_index = arg_id_result.arg_id.val.index;
|
||||
return parse_replacement_field_then_tail<get_type<arg_index, Args>,
|
||||
Args, arg_id_end_pos,
|
||||
arg_index, manual_indexing_id>(
|
||||
format_str);
|
||||
} else if constexpr (arg_id_result.arg_id.kind == arg_id_kind::name) {
|
||||
constexpr auto arg_index =
|
||||
get_arg_index_by_name(arg_id_result.arg_id.val.name, Args{});
|
||||
if constexpr (arg_index != invalid_arg_index) {
|
||||
constexpr auto next_id =
|
||||
ID != manual_indexing_id ? ID + 1 : manual_indexing_id;
|
||||
return parse_replacement_field_then_tail<
|
||||
decltype(get_type<arg_index, Args>::value), Args, arg_id_end_pos,
|
||||
arg_index, next_id>(format_str);
|
||||
} else {
|
||||
if constexpr (c == '}') {
|
||||
return parse_tail<Args, arg_id_end_pos + 1, ID>(
|
||||
runtime_named_field<char_type>{arg_id_result.arg_id.val.name},
|
||||
format_str);
|
||||
} else if constexpr (c == ':') {
|
||||
return unknown_format(); // no type info for specs parsing
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if constexpr (str[POS] == '}') {
|
||||
if constexpr (POS + 1 == str.size())
|
||||
throw format_error("unmatched '}' in format string");
|
||||
return parse_tail<Args, POS + 2, ID>(make_text(str, POS, 1), format_str);
|
||||
} else {
|
||||
constexpr auto end = parse_text(str, POS + 1);
|
||||
if constexpr (end - POS > 1) {
|
||||
return parse_tail<Args, end, ID>(make_text(str, POS, end - POS),
|
||||
format_str);
|
||||
} else {
|
||||
return parse_tail<Args, end, ID>(code_unit<char_type>{str[POS]},
|
||||
format_str);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename... Args, typename S,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
constexpr auto compile(S format_str) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(format_str);
|
||||
if constexpr (str.size() == 0) {
|
||||
return detail::make_text(str, 0, 0);
|
||||
} else {
|
||||
constexpr auto result =
|
||||
detail::compile_format_string<detail::type_list<Args...>, 0, 0>(
|
||||
format_str);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
#endif // __cpp_if_constexpr
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
#ifdef __cpp_if_constexpr
|
||||
|
||||
template <typename CompiledFormat, typename... Args,
|
||||
typename Char = typename CompiledFormat::char_type,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
FMT_INLINE std::basic_string<Char> format(const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
auto s = std::basic_string<Char>();
|
||||
cf.format(std::back_inserter(s), args...);
|
||||
return s;
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename CompiledFormat, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_format<CompiledFormat>::value)>
|
||||
constexpr FMT_INLINE OutputIt format_to(OutputIt out, const CompiledFormat& cf,
|
||||
const Args&... args) {
|
||||
return cf.format(out, args...);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_INLINE std::basic_string<typename S::char_type> format(const S&,
|
||||
Args&&... args) {
|
||||
if constexpr (std::is_same<typename S::char_type, char>::value) {
|
||||
constexpr auto str = basic_string_view<typename S::char_type>(S());
|
||||
if constexpr (str.size() == 2 && str[0] == '{' && str[1] == '}') {
|
||||
const auto& first = detail::first(args...);
|
||||
if constexpr (detail::is_named_arg<
|
||||
remove_cvref_t<decltype(first)>>::value) {
|
||||
return fmt::to_string(first.value);
|
||||
} else {
|
||||
return fmt::to_string(first);
|
||||
}
|
||||
}
|
||||
}
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return format(static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
} else {
|
||||
return format(compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
FMT_CONSTEXPR OutputIt format_to(OutputIt out, const S&, Args&&... args) {
|
||||
constexpr auto compiled = detail::compile<Args...>(S());
|
||||
if constexpr (std::is_same<remove_cvref_t<decltype(compiled)>,
|
||||
detail::unknown_format>()) {
|
||||
return format_to(out,
|
||||
static_cast<basic_string_view<typename S::char_type>>(S()),
|
||||
std::forward<Args>(args)...);
|
||||
} else {
|
||||
return format_to(out, compiled, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
format_to_n_result<OutputIt> format_to_n(OutputIt out, size_t n,
|
||||
const S& format_str, Args&&... args) {
|
||||
auto it = format_to(detail::truncating_iterator<OutputIt>(out, n), format_str,
|
||||
std::forward<Args>(args)...);
|
||||
return {it.base(), it.count()};
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
size_t formatted_size(const S& format_str, const Args&... args) {
|
||||
return format_to(detail::counting_iterator(), format_str, args...).count();
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(std::FILE* f, const S& format_str, const Args&... args) {
|
||||
memory_buffer buffer;
|
||||
format_to(std::back_inserter(buffer), format_str, args...);
|
||||
detail::print(f, {buffer.data(), buffer.size()});
|
||||
}
|
||||
|
||||
template <typename S, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_compiled_string<S>::value)>
|
||||
void print(const S& format_str, const Args&... args) {
|
||||
print(stdout, format_str, args...);
|
||||
}
|
||||
|
||||
#if FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
inline namespace literals {
|
||||
template <detail_exported::fixed_string Str>
|
||||
constexpr detail::udl_compiled_string<
|
||||
remove_cvref_t<decltype(Str.data[0])>,
|
||||
sizeof(Str.data) / sizeof(decltype(Str.data[0])), Str>
|
||||
operator""_cf() {
|
||||
return {};
|
||||
}
|
||||
} // namespace literals
|
||||
#endif
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_COMPILE_H_
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,2 @@
|
|||
#include "xchar.h"
|
||||
#warning fmt/locale.h is deprecated, include fmt/format.h or fmt/xchar.h instead
|
|
@ -0,0 +1,515 @@
|
|||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OS_H_
|
||||
#define FMT_OS_H_
|
||||
|
||||
#include <cerrno>
|
||||
#include <clocale> // locale_t
|
||||
#include <cstddef>
|
||||
#include <cstdio>
|
||||
#include <cstdlib> // strtod_l
|
||||
#include <system_error> // std::system_error
|
||||
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h> // for LC_NUMERIC_MASK on OS X
|
||||
#endif
|
||||
|
||||
#include "format.h"
|
||||
|
||||
// UWP doesn't provide _pipe.
|
||||
#if FMT_HAS_INCLUDE("winapifamily.h")
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (FMT_HAS_INCLUDE(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h> // for O_RDONLY
|
||||
# define FMT_USE_FCNTL 1
|
||||
#else
|
||||
# define FMT_USE_FCNTL 0
|
||||
#endif
|
||||
|
||||
#ifndef FMT_POSIX
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX(call) _##call
|
||||
# else
|
||||
# define FMT_POSIX(call) call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Calls to system functions are wrapped in FMT_SYSTEM for testability.
|
||||
#ifdef FMT_SYSTEM
|
||||
# define FMT_POSIX_CALL(call) FMT_SYSTEM(call)
|
||||
#else
|
||||
# define FMT_SYSTEM(call) ::call
|
||||
# ifdef _WIN32
|
||||
// Fix warnings about deprecated symbols.
|
||||
# define FMT_POSIX_CALL(call) ::_##call
|
||||
# else
|
||||
# define FMT_POSIX_CALL(call) ::call
|
||||
# endif
|
||||
#endif
|
||||
|
||||
// Retries the expression while it evaluates to error_result and errno
|
||||
// equals to EINTR.
|
||||
#ifndef _WIN32
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) \
|
||||
do { \
|
||||
(result) = (expression); \
|
||||
} while ((result) == (error_result) && errno == EINTR)
|
||||
#else
|
||||
# define FMT_RETRY_VAL(result, expression, error_result) result = (expression)
|
||||
#endif
|
||||
|
||||
#define FMT_RETRY(result, expression) FMT_RETRY_VAL(result, expression, -1)
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
A reference to a null-terminated string. It can be constructed from a C
|
||||
string or ``std::string``.
|
||||
|
||||
You can use one of the following type aliases for common character types:
|
||||
|
||||
+---------------+-----------------------------+
|
||||
| Type | Definition |
|
||||
+===============+=============================+
|
||||
| cstring_view | basic_cstring_view<char> |
|
||||
+---------------+-----------------------------+
|
||||
| wcstring_view | basic_cstring_view<wchar_t> |
|
||||
+---------------+-----------------------------+
|
||||
|
||||
This class is most useful as a parameter type to allow passing
|
||||
different types of strings to a function, for example::
|
||||
|
||||
template <typename... Args>
|
||||
std::string format(cstring_view format_str, const Args & ... args);
|
||||
|
||||
format("{}", 42);
|
||||
format(std::string("{}"), 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename Char> class basic_cstring_view {
|
||||
private:
|
||||
const Char* data_;
|
||||
|
||||
public:
|
||||
/** Constructs a string reference object from a C string. */
|
||||
basic_cstring_view(const Char* s) : data_(s) {}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a string reference from an ``std::string`` object.
|
||||
\endrst
|
||||
*/
|
||||
basic_cstring_view(const std::basic_string<Char>& s) : data_(s.c_str()) {}
|
||||
|
||||
/** Returns the pointer to a C string. */
|
||||
const Char* c_str() const { return data_; }
|
||||
};
|
||||
|
||||
using cstring_view = basic_cstring_view<char>;
|
||||
using wcstring_view = basic_cstring_view<wchar_t>;
|
||||
|
||||
template <typename Char> struct formatter<std::error_code, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
FMT_CONSTEXPR auto format(const std::error_code& ec, FormatContext& ctx) const
|
||||
-> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
out = detail::write_bytes(out, ec.category().name(),
|
||||
basic_format_specs<Char>());
|
||||
out = detail::write<Char>(out, Char(':'));
|
||||
out = detail::write<Char>(out, ec.value());
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
#ifdef _WIN32
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT;
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
// A converter from UTF-16 to UTF-8.
|
||||
// It is only provided for Windows since other systems support UTF-8 natively.
|
||||
class utf16_to_utf8 {
|
||||
private:
|
||||
memory_buffer buffer_;
|
||||
|
||||
public:
|
||||
utf16_to_utf8() {}
|
||||
FMT_API explicit utf16_to_utf8(basic_string_view<wchar_t> s);
|
||||
operator string_view() const { return string_view(&buffer_[0], size()); }
|
||||
size_t size() const { return buffer_.size() - 1; }
|
||||
const char* c_str() const { return &buffer_[0]; }
|
||||
std::string str() const { return std::string(&buffer_[0], size()); }
|
||||
|
||||
// Performs conversion returning a system error code instead of
|
||||
// throwing exception on conversion error. This method may still throw
|
||||
// in case of memory allocation error.
|
||||
FMT_API int convert(basic_string_view<wchar_t> s);
|
||||
};
|
||||
|
||||
FMT_API void format_windows_error(buffer<char>& out, int error_code,
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
FMT_API std::system_error vwindows_error(int error_code, string_view format_str,
|
||||
format_args args);
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a :class:`std::system_error` object with the description
|
||||
of the form
|
||||
|
||||
.. parsed-literal::
|
||||
*<message>*: *<system-message>*
|
||||
|
||||
where *<message>* is the formatted message and *<system-message>* is the
|
||||
system message corresponding to the error code.
|
||||
*error_code* is a Windows error code as given by ``GetLastError``.
|
||||
If *error_code* is not a valid error code such as -1, the system message
|
||||
will look like "error -1".
|
||||
|
||||
**Example**::
|
||||
|
||||
// This throws a system_error with the description
|
||||
// cannot open file 'madeup': The system cannot find the file specified.
|
||||
// or similar (system message may vary).
|
||||
const char *filename = "madeup";
|
||||
LPOFSTRUCT of = LPOFSTRUCT();
|
||||
HFILE file = OpenFile(filename, &of, OF_READ);
|
||||
if (file == HFILE_ERROR) {
|
||||
throw fmt::windows_error(GetLastError(),
|
||||
"cannot open file '{}'", filename);
|
||||
}
|
||||
\endrst
|
||||
*/
|
||||
template <typename... Args>
|
||||
std::system_error windows_error(int error_code, string_view message,
|
||||
const Args&... args) {
|
||||
return vwindows_error(error_code, message, fmt::make_format_args(args...));
|
||||
}
|
||||
|
||||
// Reports a Windows error without throwing an exception.
|
||||
// Can be used to report errors from destructors.
|
||||
FMT_API void report_windows_error(int error_code,
|
||||
const char* message) FMT_NOEXCEPT;
|
||||
#else
|
||||
inline const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
return std::system_category();
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
// std::system is not available on some platforms such as iOS (#2248).
|
||||
#ifdef __OSX__
|
||||
template <typename S, typename... Args, typename Char = char_t<S>>
|
||||
void say(const S& format_str, Args&&... args) {
|
||||
std::system(format("say \"{}\"", format(format_str, args...)).c_str());
|
||||
}
|
||||
#endif
|
||||
|
||||
// A buffered file.
|
||||
class buffered_file {
|
||||
private:
|
||||
FILE* file_;
|
||||
|
||||
friend class file;
|
||||
|
||||
explicit buffered_file(FILE* f) : file_(f) {}
|
||||
|
||||
public:
|
||||
buffered_file(const buffered_file&) = delete;
|
||||
void operator=(const buffered_file&) = delete;
|
||||
|
||||
// Constructs a buffered_file object which doesn't represent any file.
|
||||
buffered_file() FMT_NOEXCEPT : file_(nullptr) {}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~buffered_file() FMT_NOEXCEPT;
|
||||
|
||||
public:
|
||||
buffered_file(buffered_file&& other) FMT_NOEXCEPT : file_(other.file_) {
|
||||
other.file_ = nullptr;
|
||||
}
|
||||
|
||||
buffered_file& operator=(buffered_file&& other) {
|
||||
close();
|
||||
file_ = other.file_;
|
||||
other.file_ = nullptr;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Opens a file.
|
||||
FMT_API buffered_file(cstring_view filename, cstring_view mode);
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the pointer to a FILE object representing this file.
|
||||
FILE* get() const FMT_NOEXCEPT { return file_; }
|
||||
|
||||
// We place parentheses around fileno to workaround a bug in some versions
|
||||
// of MinGW that define fileno as a macro.
|
||||
FMT_API int(fileno)() const;
|
||||
|
||||
void vprint(string_view format_str, format_args args) {
|
||||
fmt::vprint(file_, format_str, args);
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
inline void print(string_view format_str, const Args&... args) {
|
||||
vprint(format_str, fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
// A file. Closed file is represented by a file object with descriptor -1.
|
||||
// Methods that are not declared with FMT_NOEXCEPT may throw
|
||||
// fmt::system_error in case of failure. Note that some errors such as
|
||||
// closing the file multiple times will cause a crash on Windows rather
|
||||
// than an exception. You can get standard behavior by overriding the
|
||||
// invalid parameter handler with _set_invalid_parameter_handler.
|
||||
class file {
|
||||
private:
|
||||
int fd_; // File descriptor.
|
||||
|
||||
// Constructs a file object with a given descriptor.
|
||||
explicit file(int fd) : fd_(fd) {}
|
||||
|
||||
public:
|
||||
// Possible values for the oflag argument to the constructor.
|
||||
enum {
|
||||
RDONLY = FMT_POSIX(O_RDONLY), // Open for reading only.
|
||||
WRONLY = FMT_POSIX(O_WRONLY), // Open for writing only.
|
||||
RDWR = FMT_POSIX(O_RDWR), // Open for reading and writing.
|
||||
CREATE = FMT_POSIX(O_CREAT), // Create if the file doesn't exist.
|
||||
APPEND = FMT_POSIX(O_APPEND), // Open in append mode.
|
||||
TRUNC = FMT_POSIX(O_TRUNC) // Truncate the content of the file.
|
||||
};
|
||||
|
||||
// Constructs a file object which doesn't represent any file.
|
||||
file() FMT_NOEXCEPT : fd_(-1) {}
|
||||
|
||||
// Opens a file and constructs a file object representing this file.
|
||||
FMT_API file(cstring_view path, int oflag);
|
||||
|
||||
public:
|
||||
file(const file&) = delete;
|
||||
void operator=(const file&) = delete;
|
||||
|
||||
file(file&& other) FMT_NOEXCEPT : fd_(other.fd_) { other.fd_ = -1; }
|
||||
|
||||
// Move assignment is not noexcept because close may throw.
|
||||
file& operator=(file&& other) {
|
||||
close();
|
||||
fd_ = other.fd_;
|
||||
other.fd_ = -1;
|
||||
return *this;
|
||||
}
|
||||
|
||||
// Destroys the object closing the file it represents if any.
|
||||
FMT_API ~file() FMT_NOEXCEPT;
|
||||
|
||||
// Returns the file descriptor.
|
||||
int descriptor() const FMT_NOEXCEPT { return fd_; }
|
||||
|
||||
// Closes the file.
|
||||
FMT_API void close();
|
||||
|
||||
// Returns the file size. The size has signed type for consistency with
|
||||
// stat::st_size.
|
||||
FMT_API long long size() const;
|
||||
|
||||
// Attempts to read count bytes from the file into the specified buffer.
|
||||
FMT_API size_t read(void* buffer, size_t count);
|
||||
|
||||
// Attempts to write count bytes from the specified buffer to the file.
|
||||
FMT_API size_t write(const void* buffer, size_t count);
|
||||
|
||||
// Duplicates a file descriptor with the dup function and returns
|
||||
// the duplicate as a file object.
|
||||
FMT_API static file dup(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd);
|
||||
|
||||
// Makes fd be the copy of this file descriptor, closing fd first if
|
||||
// necessary.
|
||||
FMT_API void dup2(int fd, std::error_code& ec) FMT_NOEXCEPT;
|
||||
|
||||
// Creates a pipe setting up read_end and write_end file objects for reading
|
||||
// and writing respectively.
|
||||
FMT_API static void pipe(file& read_end, file& write_end);
|
||||
|
||||
// Creates a buffered_file object associated with this file and detaches
|
||||
// this file object from the file.
|
||||
FMT_API buffered_file fdopen(const char* mode);
|
||||
};
|
||||
|
||||
// Returns the memory page size.
|
||||
long getpagesize();
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
struct buffer_size {
|
||||
buffer_size() = default;
|
||||
size_t value = 0;
|
||||
buffer_size operator=(size_t val) const {
|
||||
auto bs = buffer_size();
|
||||
bs.value = val;
|
||||
return bs;
|
||||
}
|
||||
};
|
||||
|
||||
struct ostream_params {
|
||||
int oflag = file::WRONLY | file::CREATE | file::TRUNC;
|
||||
size_t buffer_size = BUFSIZ > 32768 ? BUFSIZ : 32768;
|
||||
|
||||
ostream_params() {}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, int new_oflag) : ostream_params(params...) {
|
||||
oflag = new_oflag;
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
ostream_params(T... params, detail::buffer_size bs)
|
||||
: ostream_params(params...) {
|
||||
this->buffer_size = bs.value;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
constexpr detail::buffer_size buffer_size;
|
||||
|
||||
/** A fast output stream which is not thread-safe. */
|
||||
class FMT_API ostream final : private detail::buffer<char> {
|
||||
private:
|
||||
file file_;
|
||||
|
||||
void flush() {
|
||||
if (size() == 0) return;
|
||||
file_.write(data(), size());
|
||||
clear();
|
||||
}
|
||||
|
||||
void grow(size_t) override;
|
||||
|
||||
ostream(cstring_view path, const detail::ostream_params& params)
|
||||
: file_(path, params.oflag) {
|
||||
set(new char[params.buffer_size], params.buffer_size);
|
||||
}
|
||||
|
||||
public:
|
||||
ostream(ostream&& other)
|
||||
: detail::buffer<char>(other.data(), other.size(), other.capacity()),
|
||||
file_(std::move(other.file_)) {
|
||||
other.clear();
|
||||
other.set(nullptr, 0);
|
||||
}
|
||||
~ostream() {
|
||||
flush();
|
||||
delete[] data();
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
friend ostream output_file(cstring_view path, T... params);
|
||||
|
||||
void close() {
|
||||
flush();
|
||||
file_.close();
|
||||
}
|
||||
|
||||
/**
|
||||
Formats ``args`` according to specifications in ``fmt`` and writes the
|
||||
output to the file.
|
||||
*/
|
||||
template <typename... T> void print(format_string<T...> fmt, T&&... args) {
|
||||
vformat_to(detail::buffer_appender<char>(*this), fmt,
|
||||
fmt::make_format_args(args...));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
\rst
|
||||
Opens a file for writing. Supported parameters passed in *params*:
|
||||
|
||||
* ``<integer>``: Flags passed to `open
|
||||
<https://pubs.opengroup.org/onlinepubs/007904875/functions/open.html>`_
|
||||
(``file::WRONLY | file::CREATE`` by default)
|
||||
* ``buffer_size=<integer>``: Output buffer size
|
||||
|
||||
**Example**::
|
||||
|
||||
auto out = fmt::output_file("guide.txt");
|
||||
out.print("Don't {}", "Panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline ostream output_file(cstring_view path, T... params) {
|
||||
return {path, detail::ostream_params(params...)};
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef FMT_LOCALE
|
||||
// A "C" numeric locale.
|
||||
class locale {
|
||||
private:
|
||||
# ifdef _WIN32
|
||||
using locale_t = _locale_t;
|
||||
|
||||
static void freelocale(locale_t loc) { _free_locale(loc); }
|
||||
|
||||
static double strtod_l(const char* nptr, char** endptr, _locale_t loc) {
|
||||
return _strtod_l(nptr, endptr, loc);
|
||||
}
|
||||
# endif
|
||||
|
||||
locale_t locale_;
|
||||
|
||||
public:
|
||||
using type = locale_t;
|
||||
locale(const locale&) = delete;
|
||||
void operator=(const locale&) = delete;
|
||||
|
||||
locale() {
|
||||
# ifndef _WIN32
|
||||
locale_ = FMT_SYSTEM(newlocale(LC_NUMERIC_MASK, "C", nullptr));
|
||||
# else
|
||||
locale_ = _create_locale(LC_NUMERIC, "C");
|
||||
# endif
|
||||
if (!locale_) FMT_THROW(system_error(errno, "cannot create locale"));
|
||||
}
|
||||
~locale() { freelocale(locale_); }
|
||||
|
||||
type get() const { return locale_; }
|
||||
|
||||
// Converts string to floating-point number and advances str past the end
|
||||
// of the parsed input.
|
||||
double strtod(const char*& str) const {
|
||||
char* end = nullptr;
|
||||
double result = strtod_l(str, &end, locale_);
|
||||
str = end;
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using Locale FMT_DEPRECATED_ALIAS = locale;
|
||||
#endif // FMT_LOCALE
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OS_H_
|
|
@ -0,0 +1,181 @@
|
|||
// Formatting library for C++ - std::ostream support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_OSTREAM_H_
|
||||
#define FMT_OSTREAM_H_
|
||||
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char> class basic_printf_parse_context;
|
||||
template <typename OutputIt, typename Char> class basic_printf_context;
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Char> class formatbuf : public std::basic_streambuf<Char> {
|
||||
private:
|
||||
using int_type = typename std::basic_streambuf<Char>::int_type;
|
||||
using traits_type = typename std::basic_streambuf<Char>::traits_type;
|
||||
|
||||
buffer<Char>& buffer_;
|
||||
|
||||
public:
|
||||
formatbuf(buffer<Char>& buf) : buffer_(buf) {}
|
||||
|
||||
protected:
|
||||
// The put-area is actually always empty. This makes the implementation
|
||||
// simpler and has the advantage that the streambuf and the buffer are always
|
||||
// in sync and sputc never writes into uninitialized memory. The obvious
|
||||
// disadvantage is that each call to sputc always results in a (virtual) call
|
||||
// to overflow. There is no disadvantage here for sputn since this always
|
||||
// results in a call to xsputn.
|
||||
|
||||
int_type overflow(int_type ch = traits_type::eof()) FMT_OVERRIDE {
|
||||
if (!traits_type::eq_int_type(ch, traits_type::eof()))
|
||||
buffer_.push_back(static_cast<Char>(ch));
|
||||
return ch;
|
||||
}
|
||||
|
||||
std::streamsize xsputn(const Char* s, std::streamsize count) FMT_OVERRIDE {
|
||||
buffer_.append(s, s + count);
|
||||
return count;
|
||||
}
|
||||
};
|
||||
|
||||
struct converter {
|
||||
template <typename T, FMT_ENABLE_IF(is_integral<T>::value)> converter(T);
|
||||
};
|
||||
|
||||
template <typename Char> struct test_stream : std::basic_ostream<Char> {
|
||||
private:
|
||||
void_t<> operator<<(converter);
|
||||
};
|
||||
|
||||
// Hide insertion operators for built-in types.
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, Char);
|
||||
template <typename Char, typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<Char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, signed char);
|
||||
template <typename Traits>
|
||||
void_t<> operator<<(std::basic_ostream<char, Traits>&, unsigned char);
|
||||
|
||||
// Checks if T has a user-defined operator<< (e.g. not a member of
|
||||
// std::ostream).
|
||||
template <typename T, typename Char> class is_streamable {
|
||||
private:
|
||||
template <typename U>
|
||||
static bool_constant<!std::is_same<decltype(std::declval<test_stream<Char>&>()
|
||||
<< std::declval<U>()),
|
||||
void_t<>>::value>
|
||||
test(int);
|
||||
|
||||
template <typename> static std::false_type test(...);
|
||||
|
||||
using result = decltype(test<T>(0));
|
||||
|
||||
public:
|
||||
is_streamable() = default;
|
||||
|
||||
static const bool value = result::value;
|
||||
};
|
||||
|
||||
// Write the content of buf to os.
|
||||
template <typename Char>
|
||||
void write_buffer(std::basic_ostream<Char>& os, buffer<Char>& buf) {
|
||||
const Char* buf_data = buf.data();
|
||||
using unsigned_streamsize = std::make_unsigned<std::streamsize>::type;
|
||||
unsigned_streamsize size = buf.size();
|
||||
unsigned_streamsize max_size = to_unsigned(max_value<std::streamsize>());
|
||||
do {
|
||||
unsigned_streamsize n = size <= max_size ? size : max_size;
|
||||
os.write(buf_data, static_cast<std::streamsize>(n));
|
||||
buf_data += n;
|
||||
size -= n;
|
||||
} while (size != 0);
|
||||
}
|
||||
|
||||
template <typename Char, typename T>
|
||||
void format_value(buffer<Char>& buf, const T& value,
|
||||
locale_ref loc = locale_ref()) {
|
||||
formatbuf<Char> format_buf(buf);
|
||||
std::basic_ostream<Char> output(&format_buf);
|
||||
#if !defined(FMT_STATIC_THOUSANDS_SEPARATOR)
|
||||
if (loc) output.imbue(loc.get<std::locale>());
|
||||
#endif
|
||||
output << value;
|
||||
output.exceptions(std::ios_base::failbit | std::ios_base::badbit);
|
||||
buf.try_resize(buf.size());
|
||||
}
|
||||
|
||||
// Formats an object of type T that has an overloaded ostream operator<<.
|
||||
template <typename T, typename Char>
|
||||
struct fallback_formatter<T, Char, enable_if_t<is_streamable<T, Char>::value>>
|
||||
: private formatter<basic_string_view<Char>, Char> {
|
||||
FMT_CONSTEXPR auto parse(basic_format_parse_context<Char>& ctx)
|
||||
-> decltype(ctx.begin()) {
|
||||
return formatter<basic_string_view<Char>, Char>::parse(ctx);
|
||||
}
|
||||
template <typename ParseCtx,
|
||||
FMT_ENABLE_IF(std::is_same<
|
||||
ParseCtx, basic_printf_parse_context<Char>>::value)>
|
||||
auto parse(ParseCtx& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_format_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
basic_string_view<Char> str(buffer.data(), buffer.size());
|
||||
return formatter<basic_string_view<Char>, Char>::format(str, ctx);
|
||||
}
|
||||
template <typename OutputIt>
|
||||
auto format(const T& value, basic_printf_context<OutputIt, Char>& ctx)
|
||||
-> OutputIt {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
format_value(buffer, value, ctx.locale());
|
||||
return std::copy(buffer.begin(), buffer.end(), ctx.out());
|
||||
}
|
||||
};
|
||||
} // namespace detail
|
||||
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename Char>
|
||||
void vprint(std::basic_ostream<Char>& os, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
detail::write_buffer(os, buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the stream *os*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print(cerr, "Don't {}!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
FMT_MODULE_EXPORT
|
||||
template <typename S, typename... Args,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
void print(std::basic_ostream<Char>& os, const S& format_str, Args&&... args) {
|
||||
vprint(os, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_OSTREAM_H_
|
|
@ -0,0 +1,652 @@
|
|||
// Formatting library for C++ - legacy printf implementation
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_PRINTF_H_
|
||||
#define FMT_PRINTF_H_
|
||||
|
||||
#include <algorithm> // std::max
|
||||
#include <limits> // std::numeric_limits
|
||||
#include <ostream>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
template <typename T> struct printf_formatter { printf_formatter() = delete; };
|
||||
|
||||
template <typename Char>
|
||||
class basic_printf_parse_context : public basic_format_parse_context<Char> {
|
||||
using basic_format_parse_context<Char>::basic_format_parse_context;
|
||||
};
|
||||
|
||||
template <typename OutputIt, typename Char> class basic_printf_context {
|
||||
private:
|
||||
OutputIt out_;
|
||||
basic_format_args<basic_printf_context> args_;
|
||||
|
||||
public:
|
||||
using char_type = Char;
|
||||
using format_arg = basic_format_arg<basic_printf_context>;
|
||||
using parse_context_type = basic_printf_parse_context<Char>;
|
||||
template <typename T> using formatter_type = printf_formatter<T>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs a ``printf_context`` object. References to the arguments are
|
||||
stored in the context object so make sure they have appropriate lifetimes.
|
||||
\endrst
|
||||
*/
|
||||
basic_printf_context(OutputIt out,
|
||||
basic_format_args<basic_printf_context> args)
|
||||
: out_(out), args_(args) {}
|
||||
|
||||
OutputIt out() { return out_; }
|
||||
void advance_to(OutputIt it) { out_ = it; }
|
||||
|
||||
detail::locale_ref locale() { return {}; }
|
||||
|
||||
format_arg arg(int id) const { return args_.get(id); }
|
||||
|
||||
FMT_CONSTEXPR void on_error(const char* message) {
|
||||
detail::error_handler().on_error(message);
|
||||
}
|
||||
};
|
||||
|
||||
FMT_BEGIN_DETAIL_NAMESPACE
|
||||
|
||||
// Checks if a value fits in int - used to avoid warnings about comparing
|
||||
// signed and unsigned integers.
|
||||
template <bool IsSigned> struct int_checker {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
unsigned max = max_value<int>();
|
||||
return value <= max;
|
||||
}
|
||||
static bool fits_in_int(bool) { return true; }
|
||||
};
|
||||
|
||||
template <> struct int_checker<true> {
|
||||
template <typename T> static bool fits_in_int(T value) {
|
||||
return value >= (std::numeric_limits<int>::min)() &&
|
||||
value <= max_value<int>();
|
||||
}
|
||||
static bool fits_in_int(int) { return true; }
|
||||
};
|
||||
|
||||
class printf_precision_handler {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
int operator()(T value) {
|
||||
if (!int_checker<std::numeric_limits<T>::is_signed>::fits_in_int(value))
|
||||
FMT_THROW(format_error("number is too big"));
|
||||
return (std::max)(static_cast<int>(value), 0);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
int operator()(T) {
|
||||
FMT_THROW(format_error("precision is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// An argument visitor that returns true iff arg is a zero integer.
|
||||
class is_zero_int {
|
||||
public:
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
bool operator()(T value) {
|
||||
return value == 0;
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
bool operator()(T) {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> struct make_unsigned_or_bool : std::make_unsigned<T> {};
|
||||
|
||||
template <> struct make_unsigned_or_bool<bool> { using type = bool; };
|
||||
|
||||
template <typename T, typename Context> class arg_converter {
|
||||
private:
|
||||
using char_type = typename Context::char_type;
|
||||
|
||||
basic_format_arg<Context>& arg_;
|
||||
char_type type_;
|
||||
|
||||
public:
|
||||
arg_converter(basic_format_arg<Context>& arg, char_type type)
|
||||
: arg_(arg), type_(type) {}
|
||||
|
||||
void operator()(bool value) {
|
||||
if (type_ != 's') operator()<bool>(value);
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(std::is_integral<U>::value)>
|
||||
void operator()(U value) {
|
||||
bool is_signed = type_ == 'd' || type_ == 'i';
|
||||
using target_type = conditional_t<std::is_same<T, void>::value, U, T>;
|
||||
if (const_check(sizeof(target_type) <= sizeof(int))) {
|
||||
// Extra casts are used to silence warnings.
|
||||
if (is_signed) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<int>(static_cast<target_type>(value)));
|
||||
} else {
|
||||
using unsigned_type = typename make_unsigned_or_bool<target_type>::type;
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<unsigned>(static_cast<unsigned_type>(value)));
|
||||
}
|
||||
} else {
|
||||
if (is_signed) {
|
||||
// glibc's printf doesn't sign extend arguments of smaller types:
|
||||
// std::printf("%lld", -42); // prints "4294967254"
|
||||
// but we don't have to do the same because it's a UB.
|
||||
arg_ = detail::make_arg<Context>(static_cast<long long>(value));
|
||||
} else {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename make_unsigned_or_bool<U>::type>(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename U, FMT_ENABLE_IF(!std::is_integral<U>::value)>
|
||||
void operator()(U) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// Converts an integer argument to T for printf, if T is an integral type.
|
||||
// If T is void, the argument is converted to corresponding signed or unsigned
|
||||
// type depending on the type specifier: 'd' and 'i' - signed, other -
|
||||
// unsigned).
|
||||
template <typename T, typename Context, typename Char>
|
||||
void convert_arg(basic_format_arg<Context>& arg, Char type) {
|
||||
visit_format_arg(arg_converter<T, Context>(arg, type), arg);
|
||||
}
|
||||
|
||||
// Converts an integer argument to char for printf.
|
||||
template <typename Context> class char_converter {
|
||||
private:
|
||||
basic_format_arg<Context>& arg_;
|
||||
|
||||
public:
|
||||
explicit char_converter(basic_format_arg<Context>& arg) : arg_(arg) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
void operator()(T value) {
|
||||
arg_ = detail::make_arg<Context>(
|
||||
static_cast<typename Context::char_type>(value));
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
void operator()(T) {} // No conversion needed for non-integral types.
|
||||
};
|
||||
|
||||
// An argument visitor that return a pointer to a C string if argument is a
|
||||
// string or null otherwise.
|
||||
template <typename Char> struct get_cstring {
|
||||
template <typename T> const Char* operator()(T) { return nullptr; }
|
||||
const Char* operator()(const Char* s) { return s; }
|
||||
};
|
||||
|
||||
// Checks if an argument is a valid printf width specifier and sets
|
||||
// left alignment if it is negative.
|
||||
template <typename Char> class printf_width_handler {
|
||||
private:
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
format_specs& specs_;
|
||||
|
||||
public:
|
||||
explicit printf_width_handler(format_specs& specs) : specs_(specs) {}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_integral<T>::value)>
|
||||
unsigned operator()(T value) {
|
||||
auto width = static_cast<uint32_or_64_or_128_t<T>>(value);
|
||||
if (detail::is_negative(value)) {
|
||||
specs_.align = align::left;
|
||||
width = 0 - width;
|
||||
}
|
||||
unsigned int_max = max_value<int>();
|
||||
if (width > int_max) FMT_THROW(format_error("number is too big"));
|
||||
return static_cast<unsigned>(width);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(!std::is_integral<T>::value)>
|
||||
unsigned operator()(T) {
|
||||
FMT_THROW(format_error("width is not integer"));
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
// The ``printf`` argument formatter.
|
||||
template <typename OutputIt, typename Char>
|
||||
class printf_arg_formatter : public arg_formatter<Char> {
|
||||
private:
|
||||
using base = arg_formatter<Char>;
|
||||
using context_type = basic_printf_context<OutputIt, Char>;
|
||||
using format_specs = basic_format_specs<Char>;
|
||||
|
||||
context_type& context_;
|
||||
|
||||
OutputIt write_null_pointer(bool is_string = false) {
|
||||
auto s = this->specs;
|
||||
s.type = 0;
|
||||
return write_bytes(this->out, is_string ? "(null)" : "(nil)", s);
|
||||
}
|
||||
|
||||
public:
|
||||
printf_arg_formatter(OutputIt iter, format_specs& s, context_type& ctx)
|
||||
: base{iter, s, locale_ref()}, context_(ctx) {}
|
||||
|
||||
OutputIt operator()(monostate value) { return base::operator()(value); }
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(detail::is_integral<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
// MSVC2013 fails to compile separate overloads for bool and Char so use
|
||||
// std::is_same instead.
|
||||
if (std::is_same<T, Char>::value) {
|
||||
format_specs fmt_specs = this->specs;
|
||||
if (fmt_specs.type && fmt_specs.type != 'c')
|
||||
return (*this)(static_cast<int>(value));
|
||||
fmt_specs.sign = sign::none;
|
||||
fmt_specs.alt = false;
|
||||
fmt_specs.fill[0] = ' '; // Ignore '0' flag for char types.
|
||||
// align::numeric needs to be overwritten here since the '0' flag is
|
||||
// ignored for non-numeric types
|
||||
if (fmt_specs.align == align::none || fmt_specs.align == align::numeric)
|
||||
fmt_specs.align = align::right;
|
||||
return write<Char>(this->out, static_cast<Char>(value), fmt_specs);
|
||||
}
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
template <typename T, FMT_ENABLE_IF(std::is_floating_point<T>::value)>
|
||||
OutputIt operator()(T value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a null-terminated C string. */
|
||||
OutputIt operator()(const char* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
}
|
||||
|
||||
/** Formats a null-terminated wide C string. */
|
||||
OutputIt operator()(const wchar_t* value) {
|
||||
if (value) return base::operator()(value);
|
||||
return write_null_pointer(this->specs.type != 'p');
|
||||
}
|
||||
|
||||
OutputIt operator()(basic_string_view<Char> value) {
|
||||
return base::operator()(value);
|
||||
}
|
||||
|
||||
/** Formats a pointer. */
|
||||
OutputIt operator()(const void* value) {
|
||||
return value ? base::operator()(value) : write_null_pointer();
|
||||
}
|
||||
|
||||
/** Formats an argument of a custom (user-defined) type. */
|
||||
OutputIt operator()(typename basic_format_arg<context_type>::handle handle) {
|
||||
auto parse_ctx =
|
||||
basic_printf_parse_context<Char>(basic_string_view<Char>());
|
||||
handle.format(parse_ctx, context_);
|
||||
return this->out;
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
void parse_flags(basic_format_specs<Char>& specs, const Char*& it,
|
||||
const Char* end) {
|
||||
for (; it != end; ++it) {
|
||||
switch (*it) {
|
||||
case '-':
|
||||
specs.align = align::left;
|
||||
break;
|
||||
case '+':
|
||||
specs.sign = sign::plus;
|
||||
break;
|
||||
case '0':
|
||||
specs.fill[0] = '0';
|
||||
break;
|
||||
case ' ':
|
||||
if (specs.sign != sign::plus) {
|
||||
specs.sign = sign::space;
|
||||
}
|
||||
break;
|
||||
case '#':
|
||||
specs.alt = true;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
template <typename Char, typename GetArg>
|
||||
int parse_header(const Char*& it, const Char* end,
|
||||
basic_format_specs<Char>& specs, GetArg get_arg) {
|
||||
int arg_index = -1;
|
||||
Char c = *it;
|
||||
if (c >= '0' && c <= '9') {
|
||||
// Parse an argument index (if followed by '$') or a width possibly
|
||||
// preceded with '0' flag(s).
|
||||
int value = parse_nonnegative_int(it, end, -1);
|
||||
if (it != end && *it == '$') { // value is an argument index
|
||||
++it;
|
||||
arg_index = value != -1 ? value : max_value<int>();
|
||||
} else {
|
||||
if (c == '0') specs.fill[0] = '0';
|
||||
if (value != 0) {
|
||||
// Nonzero value means that we parsed width and don't need to
|
||||
// parse it or flags again, so return now.
|
||||
if (value == -1) FMT_THROW(format_error("number is too big"));
|
||||
specs.width = value;
|
||||
return arg_index;
|
||||
}
|
||||
}
|
||||
}
|
||||
parse_flags(specs, it, end);
|
||||
// Parse width.
|
||||
if (it != end) {
|
||||
if (*it >= '0' && *it <= '9') {
|
||||
specs.width = parse_nonnegative_int(it, end, -1);
|
||||
if (specs.width == -1) FMT_THROW(format_error("number is too big"));
|
||||
} else if (*it == '*') {
|
||||
++it;
|
||||
specs.width = static_cast<int>(visit_format_arg(
|
||||
detail::printf_width_handler<Char>(specs), get_arg(-1)));
|
||||
}
|
||||
}
|
||||
return arg_index;
|
||||
}
|
||||
|
||||
template <typename Char, typename Context>
|
||||
void vprintf(buffer<Char>& buf, basic_string_view<Char> format,
|
||||
basic_format_args<Context> args) {
|
||||
using OutputIt = buffer_appender<Char>;
|
||||
auto out = OutputIt(buf);
|
||||
auto context = basic_printf_context<OutputIt, Char>(out, args);
|
||||
auto parse_ctx = basic_printf_parse_context<Char>(format);
|
||||
|
||||
// Returns the argument with specified index or, if arg_index is -1, the next
|
||||
// argument.
|
||||
auto get_arg = [&](int arg_index) {
|
||||
if (arg_index < 0)
|
||||
arg_index = parse_ctx.next_arg_id();
|
||||
else
|
||||
parse_ctx.check_arg_id(--arg_index);
|
||||
return detail::get_arg(context, arg_index);
|
||||
};
|
||||
|
||||
const Char* start = parse_ctx.begin();
|
||||
const Char* end = parse_ctx.end();
|
||||
auto it = start;
|
||||
while (it != end) {
|
||||
if (!detail::find<false, Char>(it, end, '%', it)) {
|
||||
it = end; // detail::find leaves it == nullptr if it doesn't find '%'
|
||||
break;
|
||||
}
|
||||
Char c = *it++;
|
||||
if (it != end && *it == c) {
|
||||
out = detail::write(
|
||||
out, basic_string_view<Char>(start, detail::to_unsigned(it - start)));
|
||||
start = ++it;
|
||||
continue;
|
||||
}
|
||||
out = detail::write(out, basic_string_view<Char>(
|
||||
start, detail::to_unsigned(it - 1 - start)));
|
||||
|
||||
basic_format_specs<Char> specs;
|
||||
specs.align = align::right;
|
||||
|
||||
// Parse argument index, flags and width.
|
||||
int arg_index = parse_header(it, end, specs, get_arg);
|
||||
if (arg_index == 0) parse_ctx.on_error("argument not found");
|
||||
|
||||
// Parse precision.
|
||||
if (it != end && *it == '.') {
|
||||
++it;
|
||||
c = it != end ? *it : 0;
|
||||
if ('0' <= c && c <= '9') {
|
||||
specs.precision = parse_nonnegative_int(it, end, 0);
|
||||
} else if (c == '*') {
|
||||
++it;
|
||||
specs.precision = static_cast<int>(
|
||||
visit_format_arg(detail::printf_precision_handler(), get_arg(-1)));
|
||||
} else {
|
||||
specs.precision = 0;
|
||||
}
|
||||
}
|
||||
|
||||
auto arg = get_arg(arg_index);
|
||||
// For d, i, o, u, x, and X conversion specifiers, if a precision is
|
||||
// specified, the '0' flag is ignored
|
||||
if (specs.precision >= 0 && arg.is_integral())
|
||||
specs.fill[0] =
|
||||
' '; // Ignore '0' flag for non-numeric types or if '-' present.
|
||||
if (specs.precision >= 0 && arg.type() == detail::type::cstring_type) {
|
||||
auto str = visit_format_arg(detail::get_cstring<Char>(), arg);
|
||||
auto str_end = str + specs.precision;
|
||||
auto nul = std::find(str, str_end, Char());
|
||||
arg = detail::make_arg<basic_printf_context<OutputIt, Char>>(
|
||||
basic_string_view<Char>(
|
||||
str, detail::to_unsigned(nul != str_end ? nul - str
|
||||
: specs.precision)));
|
||||
}
|
||||
if (specs.alt && visit_format_arg(detail::is_zero_int(), arg))
|
||||
specs.alt = false;
|
||||
if (specs.fill[0] == '0') {
|
||||
if (arg.is_arithmetic() && specs.align != align::left)
|
||||
specs.align = align::numeric;
|
||||
else
|
||||
specs.fill[0] = ' '; // Ignore '0' flag for non-numeric types or if '-'
|
||||
// flag is also present.
|
||||
}
|
||||
|
||||
// Parse length and convert the argument to the required type.
|
||||
c = it != end ? *it++ : 0;
|
||||
Char t = it != end ? *it : 0;
|
||||
using detail::convert_arg;
|
||||
switch (c) {
|
||||
case 'h':
|
||||
if (t == 'h') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<signed char>(arg, t);
|
||||
} else {
|
||||
convert_arg<short>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'l':
|
||||
if (t == 'l') {
|
||||
++it;
|
||||
t = it != end ? *it : 0;
|
||||
convert_arg<long long>(arg, t);
|
||||
} else {
|
||||
convert_arg<long>(arg, t);
|
||||
}
|
||||
break;
|
||||
case 'j':
|
||||
convert_arg<intmax_t>(arg, t);
|
||||
break;
|
||||
case 'z':
|
||||
convert_arg<size_t>(arg, t);
|
||||
break;
|
||||
case 't':
|
||||
convert_arg<std::ptrdiff_t>(arg, t);
|
||||
break;
|
||||
case 'L':
|
||||
// printf produces garbage when 'L' is omitted for long double, no
|
||||
// need to do the same.
|
||||
break;
|
||||
default:
|
||||
--it;
|
||||
convert_arg<void>(arg, c);
|
||||
}
|
||||
|
||||
// Parse type.
|
||||
if (it == end) FMT_THROW(format_error("invalid format string"));
|
||||
specs.type = static_cast<char>(*it++);
|
||||
if (arg.is_integral()) {
|
||||
// Normalize type.
|
||||
switch (specs.type) {
|
||||
case 'i':
|
||||
case 'u':
|
||||
specs.type = 'd';
|
||||
break;
|
||||
case 'c':
|
||||
visit_format_arg(
|
||||
detail::char_converter<basic_printf_context<OutputIt, Char>>(arg),
|
||||
arg);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
start = it;
|
||||
|
||||
// Format argument.
|
||||
out = visit_format_arg(
|
||||
detail::printf_arg_formatter<OutputIt, Char>(out, specs, context), arg);
|
||||
}
|
||||
detail::write(out, basic_string_view<Char>(start, to_unsigned(it - start)));
|
||||
}
|
||||
FMT_END_DETAIL_NAMESPACE
|
||||
|
||||
template <typename Char>
|
||||
using basic_printf_context_t =
|
||||
basic_printf_context<detail::buffer_appender<Char>, Char>;
|
||||
|
||||
using printf_context = basic_printf_context_t<char>;
|
||||
using wprintf_context = basic_printf_context_t<wchar_t>;
|
||||
|
||||
using printf_args = basic_format_args<printf_context>;
|
||||
using wprintf_args = basic_format_args<wprintf_context>;
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::printf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto make_printf_args(const T&... args)
|
||||
-> format_arg_store<printf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Constructs an `~fmt::format_arg_store` object that contains references to
|
||||
arguments and can be implicitly converted to `~fmt::wprintf_args`.
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
inline auto make_wprintf_args(const T&... args)
|
||||
-> format_arg_store<wprintf_context, T...> {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vsprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Formats arguments and returns the result as a string.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::string message = fmt::sprintf("The answer is %d", 42);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T,
|
||||
typename Char = enable_if_t<detail::is_string<S>::value, char_t<S>>>
|
||||
inline auto sprintf(const S& fmt, const T&... args) -> std::basic_string<Char> {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vsprintf(to_string_view(fmt), fmt::make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vfprintf(
|
||||
std::FILE* f, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
size_t size = buffer.size();
|
||||
return std::fwrite(buffer.data(), sizeof(Char), size, f) < size
|
||||
? -1
|
||||
: static_cast<int>(size);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to the file *f*.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::fprintf(stderr, "Don't %s!", "panic");
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
inline auto fprintf(std::FILE* f, const S& fmt, const T&... args) -> int {
|
||||
using context = basic_printf_context_t<Char>;
|
||||
return vfprintf(f, to_string_view(fmt),
|
||||
fmt::make_format_args<context>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
inline auto vprintf(
|
||||
const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
return vfprintf(stdout, to_string_view(fmt), args);
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Prints formatted data to ``stdout``.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::printf("Elapsed time: %.2f seconds", 1.23);
|
||||
\endrst
|
||||
*/
|
||||
template <typename S, typename... T, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
inline auto printf(const S& fmt, const T&... args) -> int {
|
||||
return vprintf(
|
||||
to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<char_t<S>>>(args...));
|
||||
}
|
||||
|
||||
template <typename S, typename Char = char_t<S>>
|
||||
FMT_DEPRECATED auto vfprintf(
|
||||
std::basic_ostream<Char>& os, const S& fmt,
|
||||
basic_format_args<basic_printf_context_t<type_identity_t<Char>>> args)
|
||||
-> int {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
vprintf(buffer, to_string_view(fmt), args);
|
||||
os.write(buffer.data(), static_cast<std::streamsize>(buffer.size()));
|
||||
return static_cast<int>(buffer.size());
|
||||
}
|
||||
template <typename S, typename... T, typename Char = char_t<S>>
|
||||
FMT_DEPRECATED auto fprintf(std::basic_ostream<Char>& os, const S& fmt,
|
||||
const T&... args) -> int {
|
||||
return vfprintf(os, to_string_view(fmt),
|
||||
fmt::make_format_args<basic_printf_context_t<Char>>(args...));
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_PRINTF_H_
|
|
@ -0,0 +1,468 @@
|
|||
// Formatting library for C++ - experimental range support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
//
|
||||
// Copyright (c) 2018 - present, Remotion (Igor Schulz)
|
||||
// All Rights Reserved
|
||||
// {fmt} support for ranges, containers and types tuple interface.
|
||||
|
||||
#ifndef FMT_RANGES_H_
|
||||
#define FMT_RANGES_H_
|
||||
|
||||
#include <initializer_list>
|
||||
#include <type_traits>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_range {
|
||||
#ifdef FMT_DEPRECATED_BRACED_RANGES
|
||||
Char prefix = '{';
|
||||
Char postfix = '}';
|
||||
#else
|
||||
Char prefix = '[';
|
||||
Char postfix = ']';
|
||||
#endif
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename Enable = void> struct formatting_tuple {
|
||||
Char prefix = '(';
|
||||
Char postfix = ')';
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
};
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <typename RangeT, typename OutputIterator>
|
||||
OutputIterator copy(const RangeT& range, OutputIterator out) {
|
||||
for (auto it = range.begin(), end = range.end(); it != end; ++it)
|
||||
*out++ = *it;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(const char* str, OutputIterator out) {
|
||||
while (*str) *out++ = *str++;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(char ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename OutputIterator>
|
||||
OutputIterator copy(wchar_t ch, OutputIterator out) {
|
||||
*out++ = ch;
|
||||
return out;
|
||||
}
|
||||
|
||||
/// Return true value if T has std::string interface, like std::string_view.
|
||||
template <typename T> class is_std_string_like {
|
||||
template <typename U>
|
||||
static auto check(U* p)
|
||||
-> decltype((void)p->find('a'), p->length(), (void)p->data(), int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
is_string<T>::value || !std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
template <typename Char>
|
||||
struct is_std_string_like<fmt::basic_string_view<Char>> : std::true_type {};
|
||||
|
||||
template <typename... Ts> struct conditional_helper {};
|
||||
|
||||
template <typename T, typename _ = void> struct is_range_ : std::false_type {};
|
||||
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER > 1800
|
||||
|
||||
# define FMT_DECLTYPE_RETURN(val) \
|
||||
->decltype(val) { return val; } \
|
||||
static_assert( \
|
||||
true, "") // This makes it so that a semicolon is required after the
|
||||
// macro, which helps clang-format handle the formatting.
|
||||
|
||||
// C array overload
|
||||
template <typename T, std::size_t N>
|
||||
auto range_begin(const T (&arr)[N]) -> const T* {
|
||||
return arr;
|
||||
}
|
||||
template <typename T, std::size_t N>
|
||||
auto range_end(const T (&arr)[N]) -> const T* {
|
||||
return arr + N;
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_member_fn_begin_end_t : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_member_fn_begin_end_t<T, void_t<decltype(std::declval<T>().begin()),
|
||||
decltype(std::declval<T>().end())>>
|
||||
: std::true_type {};
|
||||
|
||||
// Member function overload
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).begin());
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) FMT_DECLTYPE_RETURN(static_cast<T&&>(rng).end());
|
||||
|
||||
// ADL overload. Only participates in overload resolution if member functions
|
||||
// are not found.
|
||||
template <typename T>
|
||||
auto range_begin(T&& rng)
|
||||
-> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(begin(static_cast<T&&>(rng)))> {
|
||||
return begin(static_cast<T&&>(rng));
|
||||
}
|
||||
template <typename T>
|
||||
auto range_end(T&& rng) -> enable_if_t<!has_member_fn_begin_end_t<T&&>::value,
|
||||
decltype(end(static_cast<T&&>(rng)))> {
|
||||
return end(static_cast<T&&>(rng));
|
||||
}
|
||||
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_const_begin_end : std::false_type {};
|
||||
template <typename T, typename Enable = void>
|
||||
struct has_mutable_begin_end : std::false_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_const_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(
|
||||
std::declval<const remove_cvref_t<T>&>())),
|
||||
decltype(detail::range_begin(
|
||||
std::declval<const remove_cvref_t<T>&>()))>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct has_mutable_begin_end<
|
||||
T, void_t<decltype(detail::range_begin(std::declval<T>())),
|
||||
decltype(detail::range_begin(std::declval<T>())),
|
||||
enable_if_t<std::is_copy_constructible<T>::value>>>
|
||||
: std::true_type {};
|
||||
|
||||
template <typename T>
|
||||
struct is_range_<T, void>
|
||||
: std::integral_constant<bool, (has_const_begin_end<T>::value ||
|
||||
has_mutable_begin_end<T>::value)> {};
|
||||
|
||||
template <typename T, typename Enable = void> struct range_to_view;
|
||||
template <typename T>
|
||||
struct range_to_view<T, enable_if_t<has_const_begin_end<T>::value>> {
|
||||
struct view_t {
|
||||
const T* m_range_ptr;
|
||||
|
||||
auto begin() const FMT_DECLTYPE_RETURN(detail::range_begin(*m_range_ptr));
|
||||
auto end() const FMT_DECLTYPE_RETURN(detail::range_end(*m_range_ptr));
|
||||
};
|
||||
static auto view(const T& range) -> view_t { return {&range}; }
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
struct range_to_view<T, enable_if_t<!has_const_begin_end<T>::value &&
|
||||
has_mutable_begin_end<T>::value>> {
|
||||
struct view_t {
|
||||
T m_range_copy;
|
||||
|
||||
auto begin() FMT_DECLTYPE_RETURN(detail::range_begin(m_range_copy));
|
||||
auto end() FMT_DECLTYPE_RETURN(detail::range_end(m_range_copy));
|
||||
};
|
||||
static auto view(const T& range) -> view_t { return {range}; }
|
||||
};
|
||||
# undef FMT_DECLTYPE_RETURN
|
||||
#endif
|
||||
|
||||
/// tuple_size and tuple_element check.
|
||||
template <typename T> class is_tuple_like_ {
|
||||
template <typename U>
|
||||
static auto check(U* p) -> decltype(std::tuple_size<U>::value, int());
|
||||
template <typename> static void check(...);
|
||||
|
||||
public:
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
!std::is_void<decltype(check<T>(nullptr))>::value;
|
||||
};
|
||||
|
||||
// Check for integer_sequence
|
||||
#if defined(__cpp_lib_integer_sequence) || FMT_MSC_VER >= 1900
|
||||
template <typename T, T... N>
|
||||
using integer_sequence = std::integer_sequence<T, N...>;
|
||||
template <size_t... N> using index_sequence = std::index_sequence<N...>;
|
||||
template <size_t N> using make_index_sequence = std::make_index_sequence<N>;
|
||||
#else
|
||||
template <typename T, T... N> struct integer_sequence {
|
||||
using value_type = T;
|
||||
|
||||
static FMT_CONSTEXPR size_t size() { return sizeof...(N); }
|
||||
};
|
||||
|
||||
template <size_t... N> using index_sequence = integer_sequence<size_t, N...>;
|
||||
|
||||
template <typename T, size_t N, T... Ns>
|
||||
struct make_integer_sequence : make_integer_sequence<T, N - 1, N - 1, Ns...> {};
|
||||
template <typename T, T... Ns>
|
||||
struct make_integer_sequence<T, 0, Ns...> : integer_sequence<T, Ns...> {};
|
||||
|
||||
template <size_t N>
|
||||
using make_index_sequence = make_integer_sequence<size_t, N>;
|
||||
#endif
|
||||
|
||||
template <class Tuple, class F, size_t... Is>
|
||||
void for_each(index_sequence<Is...>, Tuple&& tup, F&& f) FMT_NOEXCEPT {
|
||||
using std::get;
|
||||
// using free function get<I>(T) now.
|
||||
const int _[] = {0, ((void)f(get<Is>(tup)), 0)...};
|
||||
(void)_; // blocks warnings
|
||||
}
|
||||
|
||||
template <class T>
|
||||
FMT_CONSTEXPR make_index_sequence<std::tuple_size<T>::value> get_indexes(
|
||||
T const&) {
|
||||
return {};
|
||||
}
|
||||
|
||||
template <class Tuple, class F> void for_each(Tuple&& tup, F&& f) {
|
||||
const auto indexes = get_indexes(tup);
|
||||
for_each(indexes, std::forward<Tuple>(tup), std::forward<F>(f));
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
using value_type =
|
||||
remove_cvref_t<decltype(*detail::range_begin(std::declval<Range>()))>;
|
||||
|
||||
template <typename OutputIt> OutputIt write_delimiter(OutputIt out) {
|
||||
*out++ = ',';
|
||||
*out++ = ' ';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(is_std_string_like<typename std::decay<Arg>::type>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
*out++ = '"';
|
||||
out = write<Char>(out, v);
|
||||
*out++ = '"';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg v) {
|
||||
*out++ = '\'';
|
||||
*out++ = v;
|
||||
*out++ = '\'';
|
||||
return out;
|
||||
}
|
||||
|
||||
template <
|
||||
typename Char, typename OutputIt, typename Arg,
|
||||
FMT_ENABLE_IF(!is_std_string_like<typename std::decay<Arg>::type>::value &&
|
||||
!std::is_same<Arg, Char>::value)>
|
||||
OutputIt write_range_entry(OutputIt out, const Arg& v) {
|
||||
return write<Char>(out, v);
|
||||
}
|
||||
|
||||
} // namespace detail
|
||||
|
||||
template <typename T> struct is_tuple_like {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_tuple_like_<T>::value && !detail::is_range_<T>::value;
|
||||
};
|
||||
|
||||
template <typename TupleT, typename Char>
|
||||
struct formatter<TupleT, Char, enable_if_t<fmt::is_tuple_like<TupleT>::value>> {
|
||||
private:
|
||||
// C++11 generic lambda for format()
|
||||
template <typename FormatContext> struct format_each {
|
||||
template <typename T> void operator()(const T& v) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, v);
|
||||
++i;
|
||||
}
|
||||
formatting_tuple<Char>& formatting;
|
||||
size_t& i;
|
||||
typename std::add_lvalue_reference<
|
||||
decltype(std::declval<FormatContext>().out())>::type out;
|
||||
};
|
||||
|
||||
public:
|
||||
formatting_tuple<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext = format_context>
|
||||
auto format(const TupleT& values, FormatContext& ctx) -> decltype(ctx.out()) {
|
||||
auto out = ctx.out();
|
||||
size_t i = 0;
|
||||
|
||||
detail::copy(formatting.prefix, out);
|
||||
detail::for_each(values, format_each<FormatContext>{formatting, i, out});
|
||||
detail::copy(formatting.postfix, out);
|
||||
|
||||
return ctx.out();
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T, typename Char> struct is_range {
|
||||
static FMT_CONSTEXPR_DECL const bool value =
|
||||
detail::is_range_<T>::value && !detail::is_std_string_like<T>::value &&
|
||||
!std::is_convertible<T, std::basic_string<Char>>::value &&
|
||||
!std::is_constructible<detail::std_string_view<Char>, T>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Char>
|
||||
struct formatter<
|
||||
T, Char,
|
||||
enable_if_t<
|
||||
fmt::is_range<T, Char>::value
|
||||
// Workaround a bug in MSVC 2017 and earlier.
|
||||
#if !FMT_MSC_VER || FMT_MSC_VER >= 1927
|
||||
&& (has_formatter<detail::value_type<T>, format_context>::value ||
|
||||
detail::has_fallback_formatter<detail::value_type<T>, Char>::value)
|
||||
#endif
|
||||
>> {
|
||||
formatting_range<Char> formatting;
|
||||
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return formatting.parse(ctx);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
typename FormatContext::iterator format(const T& values, FormatContext& ctx) {
|
||||
auto out = detail::copy(formatting.prefix, ctx.out());
|
||||
size_t i = 0;
|
||||
auto view = detail::range_to_view<T>::view(values);
|
||||
auto it = view.begin();
|
||||
auto end = view.end();
|
||||
for (; it != end; ++it) {
|
||||
if (i > 0) out = detail::write_delimiter(out);
|
||||
out = detail::write_range_entry<Char>(out, *it);
|
||||
++i;
|
||||
}
|
||||
return detail::copy(formatting.postfix, out);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T> struct tuple_join_view : detail::view {
|
||||
const std::tuple<T...>& tuple;
|
||||
basic_string_view<Char> sep;
|
||||
|
||||
tuple_join_view(const std::tuple<T...>& t, basic_string_view<Char> s)
|
||||
: tuple(t), sep{s} {}
|
||||
};
|
||||
|
||||
template <typename Char, typename... T>
|
||||
using tuple_arg_join = tuple_join_view<Char, T...>;
|
||||
|
||||
template <typename Char, typename... T>
|
||||
struct formatter<tuple_join_view<Char, T...>, Char> {
|
||||
template <typename ParseContext>
|
||||
FMT_CONSTEXPR auto parse(ParseContext& ctx) -> decltype(ctx.begin()) {
|
||||
return ctx.begin();
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx) ->
|
||||
typename FormatContext::iterator {
|
||||
return format(value, ctx, detail::make_index_sequence<sizeof...(T)>{});
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename FormatContext, size_t... N>
|
||||
auto format(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
detail::index_sequence<N...>) ->
|
||||
typename FormatContext::iterator {
|
||||
using std::get;
|
||||
return format_args(value, ctx, get<N>(value.tuple)...);
|
||||
}
|
||||
|
||||
template <typename FormatContext>
|
||||
auto format_args(const tuple_join_view<Char, T...>&, FormatContext& ctx) ->
|
||||
typename FormatContext::iterator {
|
||||
// NOTE: for compilers that support C++17, this empty function instantiation
|
||||
// can be replaced with a constexpr branch in the variadic overload.
|
||||
return ctx.out();
|
||||
}
|
||||
|
||||
template <typename FormatContext, typename Arg, typename... Args>
|
||||
auto format_args(const tuple_join_view<Char, T...>& value, FormatContext& ctx,
|
||||
const Arg& arg, const Args&... args) ->
|
||||
typename FormatContext::iterator {
|
||||
using base = formatter<typename std::decay<Arg>::type, Char>;
|
||||
auto out = base().format(arg, ctx);
|
||||
if (sizeof...(Args) > 0) {
|
||||
out = std::copy(value.sep.begin(), value.sep.end(), out);
|
||||
ctx.advance_to(out);
|
||||
return format_args(value, ctx, args...);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
};
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `tuple` with elements separated by `sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
std::tuple<int, char> t = {1, 'a'};
|
||||
fmt::print("{}", fmt::join(t, ", "));
|
||||
// Output: "1, a"
|
||||
\endrst
|
||||
*/
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple, string_view sep)
|
||||
-> tuple_join_view<char, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
FMT_CONSTEXPR auto join(const std::tuple<T...>& tuple,
|
||||
basic_string_view<wchar_t> sep)
|
||||
-> tuple_join_view<wchar_t, T...> {
|
||||
return {tuple, sep};
|
||||
}
|
||||
|
||||
/**
|
||||
\rst
|
||||
Returns an object that formats `initializer_list` with elements separated by
|
||||
`sep`.
|
||||
|
||||
**Example**::
|
||||
|
||||
fmt::print("{}", fmt::join({1, 2, 3}, ", "));
|
||||
// Output: "1, 2, 3"
|
||||
\endrst
|
||||
*/
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, string_view sep)
|
||||
-> join_view<const T*, const T*> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_RANGES_H_
|
|
@ -0,0 +1,236 @@
|
|||
// Formatting library for C++ - optional wchar_t and exotic character support
|
||||
//
|
||||
// Copyright (c) 2012 - present, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#ifndef FMT_WCHAR_H_
|
||||
#define FMT_WCHAR_H_
|
||||
|
||||
#include <cwchar>
|
||||
#include <tuple>
|
||||
|
||||
#include "format.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
template <typename T>
|
||||
using is_exotic_char = bool_constant<!std::is_same<T, char>::value>;
|
||||
}
|
||||
|
||||
FMT_MODULE_EXPORT_BEGIN
|
||||
|
||||
using wstring_view = basic_string_view<wchar_t>;
|
||||
using wformat_parse_context = basic_format_parse_context<wchar_t>;
|
||||
using wformat_context = buffer_context<wchar_t>;
|
||||
using wformat_args = basic_format_args<wformat_context>;
|
||||
using wmemory_buffer = basic_memory_buffer<wchar_t>;
|
||||
|
||||
#if FMT_GCC_VERSION && FMT_GCC_VERSION < 409
|
||||
// Workaround broken conversion on older gcc.
|
||||
template <typename... Args> using wformat_string = wstring_view;
|
||||
#else
|
||||
template <typename... Args>
|
||||
using wformat_string = basic_format_string<wchar_t, type_identity_t<Args>...>;
|
||||
#endif
|
||||
|
||||
template <> struct is_char<wchar_t> : std::true_type {};
|
||||
template <> struct is_char<detail::char8_type> : std::true_type {};
|
||||
template <> struct is_char<char16_t> : std::true_type {};
|
||||
template <> struct is_char<char32_t> : std::true_type {};
|
||||
|
||||
template <typename... Args>
|
||||
constexpr format_arg_store<wformat_context, Args...> make_wformat_args(
|
||||
const Args&... args) {
|
||||
return {args...};
|
||||
}
|
||||
|
||||
inline namespace literals {
|
||||
constexpr auto operator"" _format(const wchar_t* s, size_t n)
|
||||
-> detail::udl_formatter<wchar_t> {
|
||||
return {{s, n}};
|
||||
}
|
||||
|
||||
#if FMT_USE_USER_DEFINED_LITERALS && !FMT_USE_NONTYPE_TEMPLATE_PARAMETERS
|
||||
constexpr detail::udl_arg<wchar_t> operator"" _a(const wchar_t* s, size_t) {
|
||||
return {s};
|
||||
}
|
||||
#endif
|
||||
} // namespace literals
|
||||
|
||||
template <typename It, typename Sentinel>
|
||||
auto join(It begin, Sentinel end, wstring_view sep)
|
||||
-> join_view<It, Sentinel, wchar_t> {
|
||||
return {begin, end, sep};
|
||||
}
|
||||
|
||||
template <typename Range>
|
||||
auto join(Range&& range, wstring_view sep)
|
||||
-> join_view<detail::iterator_t<Range>, detail::sentinel_t<Range>,
|
||||
wchar_t> {
|
||||
return join(std::begin(range), std::end(range), sep);
|
||||
}
|
||||
|
||||
template <typename T>
|
||||
auto join(std::initializer_list<T> list, wstring_view sep)
|
||||
-> join_view<const T*, const T*, wchar_t> {
|
||||
return join(std::begin(list), std::end(list), sep);
|
||||
}
|
||||
|
||||
template <typename Char, FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto vformat(basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
basic_memory_buffer<Char> buffer;
|
||||
detail::vformat_to(buffer, format_str, args);
|
||||
return to_string(buffer);
|
||||
}
|
||||
|
||||
// Pass char_t as a default template parameter instead of using
|
||||
// std::basic_string<char_t<S>> to reduce the symbol size.
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(!std::is_same<Char, char>::value)>
|
||||
auto format(const S& format_str, Args&&... args) -> std::basic_string<Char> {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat(to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat(
|
||||
const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, to_string_view(format_str), args);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format(const Locale& loc, const S& format_str, Args&&... args)
|
||||
-> std::basic_string<Char> {
|
||||
return detail::vformat(loc, to_string_view(format_str),
|
||||
fmt::make_args_checked<Args...>(format_str, args...));
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
auto vformat_to(OutputIt out, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
detail::vformat_to(buf, to_string_view(format_str), args);
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to(OutputIt out, const S& fmt, Args&&... args) -> OutputIt {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to(out, to_string_view(fmt), vargs);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char, size_t SIZE,
|
||||
typename Allocator, FMT_ENABLE_IF(detail::is_string<S>::value)>
|
||||
FMT_DEPRECATED auto format_to(basic_memory_buffer<Char, SIZE, Allocator>& buf,
|
||||
const S& format_str, Args&&... args) ->
|
||||
typename buffer_context<Char>::iterator {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
detail::vformat_to(buf, to_string_view(format_str), vargs, {});
|
||||
return detail::buffer_appender<Char>(buf);
|
||||
}
|
||||
|
||||
template <typename Locale, typename S, typename OutputIt, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to(
|
||||
OutputIt out, const Locale& loc, const S& format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args) -> OutputIt {
|
||||
auto&& buf = detail::get_buffer<Char>(out);
|
||||
vformat_to(buf, to_string_view(format_str), args, detail::locale_ref(loc));
|
||||
return detail::get_iterator(buf);
|
||||
}
|
||||
|
||||
template <
|
||||
typename OutputIt, typename Locale, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
bool enable = detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_locale<Locale>::value&& detail::is_exotic_char<Char>::value>
|
||||
inline auto format_to(OutputIt out, const Locale& loc, const S& format_str,
|
||||
Args&&... args) ->
|
||||
typename std::enable_if<enable, OutputIt>::type {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(format_str, args...);
|
||||
return vformat_to(out, loc, to_string_view(format_str), vargs);
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename Char, typename... Args,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto vformat_to_n(
|
||||
OutputIt out, size_t n, basic_string_view<Char> format_str,
|
||||
basic_format_args<buffer_context<type_identity_t<Char>>> args)
|
||||
-> format_to_n_result<OutputIt> {
|
||||
detail::iterator_buffer<OutputIt, Char, detail::fixed_buffer_traits> buf(out,
|
||||
n);
|
||||
detail::vformat_to(buf, format_str, args);
|
||||
return {buf.out(), buf.count()};
|
||||
}
|
||||
|
||||
template <typename OutputIt, typename S, typename... Args,
|
||||
typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_output_iterator<OutputIt, Char>::value&&
|
||||
detail::is_exotic_char<Char>::value)>
|
||||
inline auto format_to_n(OutputIt out, size_t n, const S& fmt,
|
||||
const Args&... args) -> format_to_n_result<OutputIt> {
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
return vformat_to_n(out, n, to_string_view(fmt), vargs);
|
||||
}
|
||||
|
||||
template <typename S, typename... Args, typename Char = char_t<S>,
|
||||
FMT_ENABLE_IF(detail::is_exotic_char<Char>::value)>
|
||||
inline auto formatted_size(const S& fmt, Args&&... args) -> size_t {
|
||||
detail::counting_buffer<Char> buf;
|
||||
const auto& vargs = fmt::make_args_checked<Args...>(fmt, args...);
|
||||
detail::vformat_to(buf, to_string_view(fmt), vargs);
|
||||
return buf.count();
|
||||
}
|
||||
|
||||
inline void vprint(std::FILE* f, wstring_view fmt, wformat_args args) {
|
||||
wmemory_buffer buffer;
|
||||
detail::vformat_to(buffer, fmt, args);
|
||||
buffer.push_back(L'\0');
|
||||
if (std::fputws(buffer.data(), f) == -1)
|
||||
FMT_THROW(system_error(errno, FMT_STRING("cannot write to file")));
|
||||
}
|
||||
|
||||
inline void vprint(wstring_view fmt, wformat_args args) {
|
||||
vprint(stdout, fmt, args);
|
||||
}
|
||||
|
||||
template <typename... T>
|
||||
void print(std::FILE* f, wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(f, wstring_view(fmt), make_wformat_args(args...));
|
||||
}
|
||||
|
||||
template <typename... T> void print(wformat_string<T...> fmt, T&&... args) {
|
||||
return vprint(wstring_view(fmt), make_wformat_args(args...));
|
||||
}
|
||||
|
||||
/**
|
||||
Converts *value* to ``std::wstring`` using the default format for type *T*.
|
||||
*/
|
||||
template <typename T> inline auto to_wstring(const T& value) -> std::wstring {
|
||||
return format(FMT_STRING(L"{}"), value);
|
||||
}
|
||||
FMT_MODULE_EXPORT_END
|
||||
FMT_END_NAMESPACE
|
||||
|
||||
#endif // FMT_WCHAR_H_
|
|
@ -0,0 +1,100 @@
|
|||
module;
|
||||
#ifndef __cpp_modules
|
||||
# error Module not supported.
|
||||
#endif
|
||||
|
||||
// put all implementation-provided headers into the global module fragment
|
||||
// to prevent attachment to this module
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
#if !defined(WIN32_LEAN_AND_MEAN) && defined(_WIN32)
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <cctype>
|
||||
#include <cerrno>
|
||||
#include <chrono>
|
||||
#include <climits>
|
||||
#include <clocale>
|
||||
#include <cmath>
|
||||
#include <cstdarg>
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <ctime>
|
||||
#include <cwchar>
|
||||
#include <exception>
|
||||
#include <functional>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <locale>
|
||||
#include <memory>
|
||||
#include <ostream>
|
||||
#include <sstream>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <system_error>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#if _MSC_VER
|
||||
# include <intrin.h>
|
||||
#endif
|
||||
#if defined __APPLE__ || defined(__FreeBSD__)
|
||||
# include <xlocale.h>
|
||||
#endif
|
||||
#if __has_include(<winapifamily.h>)
|
||||
# include <winapifamily.h>
|
||||
#endif
|
||||
#if (__has_include(<fcntl.h>) || defined(__APPLE__) || \
|
||||
defined(__linux__)) && \
|
||||
(!defined(WINAPI_FAMILY) || (WINAPI_FAMILY == WINAPI_FAMILY_DESKTOP_APP))
|
||||
# include <fcntl.h>
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# include <io.h>
|
||||
# endif
|
||||
#endif
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
export module fmt;
|
||||
|
||||
#define FMT_MODULE_EXPORT export
|
||||
#define FMT_MODULE_EXPORT_BEGIN export {
|
||||
#define FMT_MODULE_EXPORT_END }
|
||||
#define FMT_BEGIN_DETAIL_NAMESPACE \
|
||||
} \
|
||||
namespace detail {
|
||||
#define FMT_END_DETAIL_NAMESPACE \
|
||||
} \
|
||||
export {
|
||||
|
||||
// all library-provided declarations and definitions
|
||||
// must be in the module purview to be exported
|
||||
#include "fmt/args.h"
|
||||
#include "fmt/chrono.h"
|
||||
#include "fmt/color.h"
|
||||
#include "fmt/compile.h"
|
||||
#include "fmt/format.h"
|
||||
#include "fmt/os.h"
|
||||
#include "fmt/printf.h"
|
||||
#include "fmt/xchar.h"
|
||||
|
||||
// gcc doesn't yet implement private module fragments
|
||||
#if !FMT_GCC_VERSION
|
||||
module : private;
|
||||
#endif
|
||||
|
||||
#include "format.cc"
|
||||
#include "os.cc"
|
|
@ -0,0 +1,78 @@
|
|||
// Formatting library for C++
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
#include "fmt/format-inl.h"
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
namespace detail {
|
||||
|
||||
template <typename T>
|
||||
int format_float(char* buf, std::size_t size, const char* format, int precision,
|
||||
T value) {
|
||||
#ifdef FMT_FUZZ
|
||||
if (precision > 100000)
|
||||
throw std::runtime_error(
|
||||
"fuzz mode - avoid large allocation inside snprintf");
|
||||
#endif
|
||||
// Suppress the warning about nonliteral format string.
|
||||
int (*snprintf_ptr)(char*, size_t, const char*, ...) = FMT_SNPRINTF;
|
||||
return precision < 0 ? snprintf_ptr(buf, size, format, value)
|
||||
: snprintf_ptr(buf, size, format, precision, value);
|
||||
}
|
||||
|
||||
template FMT_API dragonbox::decimal_fp<float> dragonbox::to_decimal(float x)
|
||||
FMT_NOEXCEPT;
|
||||
template FMT_API dragonbox::decimal_fp<double> dragonbox::to_decimal(double x)
|
||||
FMT_NOEXCEPT;
|
||||
} // namespace detail
|
||||
|
||||
// Workaround a bug in MSVC2013 that prevents instantiation of format_float.
|
||||
int (*instantiate_format_float)(double, int, detail::float_specs,
|
||||
detail::buffer<char>&) = detail::format_float;
|
||||
|
||||
#ifndef FMT_STATIC_THOUSANDS_SEPARATOR
|
||||
template FMT_API detail::locale_ref::locale_ref(const std::locale& loc);
|
||||
template FMT_API std::locale detail::locale_ref::get<std::locale>() const;
|
||||
#endif
|
||||
|
||||
// Explicit instantiations for char.
|
||||
|
||||
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<char>;
|
||||
template FMT_API char detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<char>::append(const char*, const char*);
|
||||
|
||||
// DEPRECATED!
|
||||
// There is no correspondent extern template in format.h because of
|
||||
// incompatibility between clang and gcc (#2377).
|
||||
template FMT_API void detail::vformat_to(
|
||||
detail::buffer<char>&, string_view,
|
||||
basic_format_args<FMT_BUFFER_CONTEXT(char)>, detail::locale_ref);
|
||||
|
||||
template FMT_API int detail::snprintf_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::snprintf_float(long double, int,
|
||||
detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
template FMT_API int detail::format_float(long double, int, detail::float_specs,
|
||||
detail::buffer<char>&);
|
||||
|
||||
// Explicit instantiations for wchar_t.
|
||||
|
||||
template FMT_API auto detail::thousands_sep_impl(locale_ref)
|
||||
-> thousands_sep_result<wchar_t>;
|
||||
template FMT_API wchar_t detail::decimal_point_impl(locale_ref);
|
||||
|
||||
template FMT_API void detail::buffer<wchar_t>::append(const wchar_t*,
|
||||
const wchar_t*);
|
||||
|
||||
template struct detail::basic_data<void>;
|
||||
|
||||
FMT_END_NAMESPACE
|
|
@ -0,0 +1,360 @@
|
|||
// Formatting library for C++ - optional OS-specific functionality
|
||||
//
|
||||
// Copyright (c) 2012 - 2016, Victor Zverovich
|
||||
// All rights reserved.
|
||||
//
|
||||
// For the license information refer to format.h.
|
||||
|
||||
// Disable bogus MSVC warnings.
|
||||
#if !defined(_CRT_SECURE_NO_WARNINGS) && defined(_MSC_VER)
|
||||
# define _CRT_SECURE_NO_WARNINGS
|
||||
#endif
|
||||
|
||||
#include "fmt/os.h"
|
||||
|
||||
#include <climits>
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
# include <sys/stat.h>
|
||||
# include <sys/types.h>
|
||||
|
||||
# ifndef _WIN32
|
||||
# include <unistd.h>
|
||||
# else
|
||||
# ifndef WIN32_LEAN_AND_MEAN
|
||||
# define WIN32_LEAN_AND_MEAN
|
||||
# endif
|
||||
# include <io.h>
|
||||
|
||||
# define O_CREAT _O_CREAT
|
||||
# define O_TRUNC _O_TRUNC
|
||||
|
||||
# ifndef S_IRUSR
|
||||
# define S_IRUSR _S_IREAD
|
||||
# endif
|
||||
|
||||
# ifndef S_IWUSR
|
||||
# define S_IWUSR _S_IWRITE
|
||||
# endif
|
||||
|
||||
# ifdef __MINGW32__
|
||||
# define _SH_DENYNO 0x40
|
||||
# endif
|
||||
# endif // _WIN32
|
||||
#endif // FMT_USE_FCNTL
|
||||
|
||||
#ifdef _WIN32
|
||||
# include <windows.h>
|
||||
#endif
|
||||
|
||||
#ifdef fileno
|
||||
# undef fileno
|
||||
#endif
|
||||
|
||||
namespace {
|
||||
#ifdef _WIN32
|
||||
// Return type of read and write functions.
|
||||
using rwresult = int;
|
||||
|
||||
// On Windows the count argument to read and write is unsigned, so convert
|
||||
// it from size_t preventing integer overflow.
|
||||
inline unsigned convert_rwcount(std::size_t count) {
|
||||
return count <= UINT_MAX ? static_cast<unsigned>(count) : UINT_MAX;
|
||||
}
|
||||
#elif FMT_USE_FCNTL
|
||||
// Return type of read and write functions.
|
||||
using rwresult = ssize_t;
|
||||
|
||||
inline std::size_t convert_rwcount(std::size_t count) { return count; }
|
||||
#endif
|
||||
} // namespace
|
||||
|
||||
FMT_BEGIN_NAMESPACE
|
||||
|
||||
#ifdef _WIN32
|
||||
detail::utf16_to_utf8::utf16_to_utf8(basic_string_view<wchar_t> s) {
|
||||
if (int error_code = convert(s)) {
|
||||
FMT_THROW(windows_error(error_code,
|
||||
"cannot convert string from UTF-16 to UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
int detail::utf16_to_utf8::convert(basic_string_view<wchar_t> s) {
|
||||
if (s.size() > INT_MAX) return ERROR_INVALID_PARAMETER;
|
||||
int s_size = static_cast<int>(s.size());
|
||||
if (s_size == 0) {
|
||||
// WideCharToMultiByte does not support zero length, handle separately.
|
||||
buffer_.resize(1);
|
||||
buffer_[0] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, nullptr, 0,
|
||||
nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_.resize(length + 1);
|
||||
length = WideCharToMultiByte(CP_UTF8, 0, s.data(), s_size, &buffer_[0],
|
||||
length, nullptr, nullptr);
|
||||
if (length == 0) return GetLastError();
|
||||
buffer_[length] = 0;
|
||||
return 0;
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
class system_message {
|
||||
system_message(const system_message&) = delete;
|
||||
void operator=(const system_message&) = delete;
|
||||
|
||||
unsigned long result_;
|
||||
wchar_t* message_;
|
||||
|
||||
static bool is_whitespace(wchar_t c) FMT_NOEXCEPT {
|
||||
return c == L' ' || c == L'\n' || c == L'\r' || c == L'\t' || c == L'\0';
|
||||
}
|
||||
|
||||
public:
|
||||
explicit system_message(unsigned long error_code)
|
||||
: result_(0), message_(nullptr) {
|
||||
result_ = FormatMessageW(
|
||||
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM |
|
||||
FORMAT_MESSAGE_IGNORE_INSERTS,
|
||||
nullptr, error_code, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
|
||||
reinterpret_cast<wchar_t*>(&message_), 0, nullptr);
|
||||
if (result_ != 0) {
|
||||
while (result_ != 0 && is_whitespace(message_[result_ - 1])) {
|
||||
--result_;
|
||||
}
|
||||
}
|
||||
}
|
||||
~system_message() { LocalFree(message_); }
|
||||
explicit operator bool() const FMT_NOEXCEPT { return result_ != 0; }
|
||||
operator basic_string_view<wchar_t>() const FMT_NOEXCEPT {
|
||||
return basic_string_view<wchar_t>(message_, result_);
|
||||
}
|
||||
};
|
||||
|
||||
class utf8_system_category final : public std::error_category {
|
||||
public:
|
||||
const char* name() const FMT_NOEXCEPT override { return "system"; }
|
||||
std::string message(int error_code) const override {
|
||||
system_message msg(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
return utf8_message.str();
|
||||
}
|
||||
}
|
||||
return "unknown error";
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace detail
|
||||
|
||||
FMT_API const std::error_category& system_category() FMT_NOEXCEPT {
|
||||
static const detail::utf8_system_category category;
|
||||
return category;
|
||||
}
|
||||
|
||||
std::system_error vwindows_error(int err_code, string_view format_str,
|
||||
format_args args) {
|
||||
auto ec = std::error_code(err_code, system_category());
|
||||
return std::system_error(ec, vformat(format_str, args));
|
||||
}
|
||||
|
||||
void detail::format_windows_error(detail::buffer<char>& out, int error_code,
|
||||
const char* message) FMT_NOEXCEPT {
|
||||
FMT_TRY {
|
||||
system_message msg(error_code);
|
||||
if (msg) {
|
||||
utf16_to_utf8 utf8_message;
|
||||
if (utf8_message.convert(msg) == ERROR_SUCCESS) {
|
||||
format_to(buffer_appender<char>(out), "{}: {}", message, utf8_message);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
FMT_CATCH(...) {}
|
||||
format_error_code(out, error_code, message);
|
||||
}
|
||||
|
||||
void report_windows_error(int error_code, const char* message) FMT_NOEXCEPT {
|
||||
report_error(detail::format_windows_error, error_code, message);
|
||||
}
|
||||
#endif // _WIN32
|
||||
|
||||
buffered_file::~buffered_file() FMT_NOEXCEPT {
|
||||
if (file_ && FMT_SYSTEM(fclose(file_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
buffered_file::buffered_file(cstring_view filename, cstring_view mode) {
|
||||
FMT_RETRY_VAL(file_, FMT_SYSTEM(fopen(filename.c_str(), mode.c_str())),
|
||||
nullptr);
|
||||
if (!file_)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", filename.c_str()));
|
||||
}
|
||||
|
||||
void buffered_file::close() {
|
||||
if (!file_) return;
|
||||
int result = FMT_SYSTEM(fclose(file_));
|
||||
file_ = nullptr;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
// A macro used to prevent expansion of fileno on broken versions of MinGW.
|
||||
#define FMT_ARGS
|
||||
|
||||
int buffered_file::fileno() const {
|
||||
int fd = FMT_POSIX_CALL(fileno FMT_ARGS(file_));
|
||||
if (fd == -1) FMT_THROW(system_error(errno, "cannot get file descriptor"));
|
||||
return fd;
|
||||
}
|
||||
|
||||
#if FMT_USE_FCNTL
|
||||
file::file(cstring_view path, int oflag) {
|
||||
int mode = S_IRUSR | S_IWUSR;
|
||||
# if defined(_WIN32) && !defined(__MINGW32__)
|
||||
fd_ = -1;
|
||||
FMT_POSIX_CALL(sopen_s(&fd_, path.c_str(), oflag, _SH_DENYNO, mode));
|
||||
# else
|
||||
FMT_RETRY(fd_, FMT_POSIX_CALL(open(path.c_str(), oflag, mode)));
|
||||
# endif
|
||||
if (fd_ == -1)
|
||||
FMT_THROW(system_error(errno, "cannot open file {}", path.c_str()));
|
||||
}
|
||||
|
||||
file::~file() FMT_NOEXCEPT {
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
if (fd_ != -1 && FMT_POSIX_CALL(close(fd_)) != 0)
|
||||
report_system_error(errno, "cannot close file");
|
||||
}
|
||||
|
||||
void file::close() {
|
||||
if (fd_ == -1) return;
|
||||
// Don't retry close in case of EINTR!
|
||||
// See http://linux.derkeiler.com/Mailing-Lists/Kernel/2005-09/3000.html
|
||||
int result = FMT_POSIX_CALL(close(fd_));
|
||||
fd_ = -1;
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot close file"));
|
||||
}
|
||||
|
||||
long long file::size() const {
|
||||
# ifdef _WIN32
|
||||
// Use GetFileSize instead of GetFileSizeEx for the case when _WIN32_WINNT
|
||||
// is less than 0x0500 as is the case with some default MinGW builds.
|
||||
// Both functions support large file sizes.
|
||||
DWORD size_upper = 0;
|
||||
HANDLE handle = reinterpret_cast<HANDLE>(_get_osfhandle(fd_));
|
||||
DWORD size_lower = FMT_SYSTEM(GetFileSize(handle, &size_upper));
|
||||
if (size_lower == INVALID_FILE_SIZE) {
|
||||
DWORD error = GetLastError();
|
||||
if (error != NO_ERROR)
|
||||
FMT_THROW(windows_error(GetLastError(), "cannot get file size"));
|
||||
}
|
||||
unsigned long long long_size = size_upper;
|
||||
return (long_size << sizeof(DWORD) * CHAR_BIT) | size_lower;
|
||||
# else
|
||||
using Stat = struct stat;
|
||||
Stat file_stat = Stat();
|
||||
if (FMT_POSIX_CALL(fstat(fd_, &file_stat)) == -1)
|
||||
FMT_THROW(system_error(errno, "cannot get file attributes"));
|
||||
static_assert(sizeof(long long) >= sizeof(file_stat.st_size),
|
||||
"return type of file::size is not large enough");
|
||||
return file_stat.st_size;
|
||||
# endif
|
||||
}
|
||||
|
||||
std::size_t file::read(void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(read(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot read from file"));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
std::size_t file::write(const void* buffer, std::size_t count) {
|
||||
rwresult result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(write(fd_, buffer, convert_rwcount(count))));
|
||||
if (result < 0) FMT_THROW(system_error(errno, "cannot write to file"));
|
||||
return detail::to_unsigned(result);
|
||||
}
|
||||
|
||||
file file::dup(int fd) {
|
||||
// Don't retry as dup doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009695399/functions/dup.html
|
||||
int new_fd = FMT_POSIX_CALL(dup(fd));
|
||||
if (new_fd == -1)
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {}", fd));
|
||||
return file(new_fd);
|
||||
}
|
||||
|
||||
void file::dup2(int fd) {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) {
|
||||
FMT_THROW(system_error(errno, "cannot duplicate file descriptor {} to {}",
|
||||
fd_, fd));
|
||||
}
|
||||
}
|
||||
|
||||
void file::dup2(int fd, std::error_code& ec) FMT_NOEXCEPT {
|
||||
int result = 0;
|
||||
FMT_RETRY(result, FMT_POSIX_CALL(dup2(fd_, fd)));
|
||||
if (result == -1) ec = std::error_code(errno, std::generic_category());
|
||||
}
|
||||
|
||||
void file::pipe(file& read_end, file& write_end) {
|
||||
// Close the descriptors first to make sure that assignments don't throw
|
||||
// and there are no leaks.
|
||||
read_end.close();
|
||||
write_end.close();
|
||||
int fds[2] = {};
|
||||
# ifdef _WIN32
|
||||
// Make the default pipe capacity same as on Linux 2.6.11+.
|
||||
enum { DEFAULT_CAPACITY = 65536 };
|
||||
int result = FMT_POSIX_CALL(pipe(fds, DEFAULT_CAPACITY, _O_BINARY));
|
||||
# else
|
||||
// Don't retry as the pipe function doesn't return EINTR.
|
||||
// http://pubs.opengroup.org/onlinepubs/009696799/functions/pipe.html
|
||||
int result = FMT_POSIX_CALL(pipe(fds));
|
||||
# endif
|
||||
if (result != 0) FMT_THROW(system_error(errno, "cannot create pipe"));
|
||||
// The following assignments don't throw because read_fd and write_fd
|
||||
// are closed.
|
||||
read_end = file(fds[0]);
|
||||
write_end = file(fds[1]);
|
||||
}
|
||||
|
||||
buffered_file file::fdopen(const char* mode) {
|
||||
// Don't retry as fdopen doesn't return EINTR.
|
||||
# if defined(__MINGW32__) && defined(_POSIX_)
|
||||
FILE* f = ::fdopen(fd_, mode);
|
||||
# else
|
||||
FILE* f = FMT_POSIX_CALL(fdopen(fd_, mode));
|
||||
# endif
|
||||
if (!f)
|
||||
FMT_THROW(
|
||||
system_error(errno, "cannot associate stream with file descriptor"));
|
||||
buffered_file bf(f);
|
||||
fd_ = -1;
|
||||
return bf;
|
||||
}
|
||||
|
||||
long getpagesize() {
|
||||
# ifdef _WIN32
|
||||
SYSTEM_INFO si;
|
||||
GetSystemInfo(&si);
|
||||
return si.dwPageSize;
|
||||
# else
|
||||
long size = FMT_POSIX_CALL(sysconf(_SC_PAGESIZE));
|
||||
if (size < 0) FMT_THROW(system_error(errno, "cannot get memory page size"));
|
||||
return size;
|
||||
# endif
|
||||
}
|
||||
|
||||
FMT_API void ostream::grow(size_t) {
|
||||
if (this->size() == this->capacity()) flush();
|
||||
}
|
||||
#endif // FMT_USE_FCNTL
|
||||
FMT_END_NAMESPACE
|
|
@ -0,0 +1,7 @@
|
|||
# A CMake script to find SetEnv.cmd.
|
||||
|
||||
find_program(WINSDK_SETENV NAMES SetEnv.cmd
|
||||
PATHS "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Microsoft\\Microsoft SDKs\\Windows;CurrentInstallFolder]/bin")
|
||||
if (WINSDK_SETENV AND PRINT_PATH)
|
||||
execute_process(COMMAND ${CMAKE_COMMAND} -E echo "${WINSDK_SETENV}")
|
||||
endif ()
|
|
@ -0,0 +1,26 @@
|
|||
# This module provides function for joining paths
|
||||
# known from from most languages
|
||||
#
|
||||
# Original license:
|
||||
# SPDX-License-Identifier: (MIT OR CC0-1.0)
|
||||
# Explicit permission given to distribute this module under
|
||||
# the terms of the project as described in /LICENSE.rst.
|
||||
# Copyright 2020 Jan Tojnar
|
||||
# https://github.com/jtojnar/cmake-snips
|
||||
#
|
||||
# Modelled after Python’s os.path.join
|
||||
# https://docs.python.org/3.7/library/os.path.html#os.path.join
|
||||
# Windows not supported
|
||||
function(join_paths joined_path first_path_segment)
|
||||
set(temp_path "${first_path_segment}")
|
||||
foreach(current_segment IN LISTS ARGN)
|
||||
if(NOT ("${current_segment}" STREQUAL ""))
|
||||
if(IS_ABSOLUTE "${current_segment}")
|
||||
set(temp_path "${current_segment}")
|
||||
else()
|
||||
set(temp_path "${temp_path}/${current_segment}")
|
||||
endif()
|
||||
endif()
|
||||
endforeach()
|
||||
set(${joined_path} "${temp_path}" PARENT_SCOPE)
|
||||
endfunction()
|
|
@ -0,0 +1,70 @@
|
|||
# C++14 feature support detection
|
||||
|
||||
include(CheckCXXSourceCompiles)
|
||||
include(CheckCXXCompilerFlag)
|
||||
|
||||
if (NOT CMAKE_CXX_STANDARD)
|
||||
set(CMAKE_CXX_STANDARD 11)
|
||||
endif()
|
||||
message(STATUS "CXX_STANDARD: ${CMAKE_CXX_STANDARD}")
|
||||
|
||||
if (CMAKE_CXX_STANDARD EQUAL 20)
|
||||
check_cxx_compiler_flag(-std=c++20 has_std_20_flag)
|
||||
check_cxx_compiler_flag(-std=c++2a has_std_2a_flag)
|
||||
|
||||
if (has_std_20_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++20)
|
||||
elseif (has_std_2a_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++2a)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 17)
|
||||
check_cxx_compiler_flag(-std=c++17 has_std_17_flag)
|
||||
check_cxx_compiler_flag(-std=c++1z has_std_1z_flag)
|
||||
|
||||
if (has_std_17_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++17)
|
||||
elseif (has_std_1z_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1z)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 14)
|
||||
check_cxx_compiler_flag(-std=c++14 has_std_14_flag)
|
||||
check_cxx_compiler_flag(-std=c++1y has_std_1y_flag)
|
||||
|
||||
if (has_std_14_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++14)
|
||||
elseif (has_std_1y_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++1y)
|
||||
endif ()
|
||||
elseif (CMAKE_CXX_STANDARD EQUAL 11)
|
||||
check_cxx_compiler_flag(-std=c++11 has_std_11_flag)
|
||||
check_cxx_compiler_flag(-std=c++0x has_std_0x_flag)
|
||||
|
||||
if (has_std_11_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++11)
|
||||
elseif (has_std_0x_flag)
|
||||
set(CXX_STANDARD_FLAG -std=c++0x)
|
||||
endif ()
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS ${CXX_STANDARD_FLAG})
|
||||
|
||||
# Check if user-defined literals are available
|
||||
check_cxx_source_compiles("
|
||||
void operator\"\" _udl(long double);
|
||||
int main() {}"
|
||||
SUPPORTS_USER_DEFINED_LITERALS)
|
||||
if (NOT SUPPORTS_USER_DEFINED_LITERALS)
|
||||
set (SUPPORTS_USER_DEFINED_LITERALS OFF)
|
||||
endif ()
|
||||
|
||||
# Check if <variant> is available
|
||||
set(CMAKE_REQUIRED_FLAGS -std=c++1z)
|
||||
check_cxx_source_compiles("
|
||||
#include <variant>
|
||||
int main() {}"
|
||||
FMT_HAS_VARIANT)
|
||||
if (NOT FMT_HAS_VARIANT)
|
||||
set (FMT_HAS_VARIANT OFF)
|
||||
endif ()
|
||||
|
||||
set(CMAKE_REQUIRED_FLAGS )
|
|
@ -0,0 +1,4 @@
|
|||
@PACKAGE_INIT@
|
||||
|
||||
include(${CMAKE_CURRENT_LIST_DIR}/@targets_export_name@.cmake)
|
||||
check_required_components(fmt)
|
|
@ -0,0 +1,11 @@
|
|||
prefix=@CMAKE_INSTALL_PREFIX@
|
||||
exec_prefix=@CMAKE_INSTALL_PREFIX@
|
||||
libdir=@libdir_for_pc_file@
|
||||
includedir=@includedir_for_pc_file@
|
||||
|
||||
Name: fmt
|
||||
Description: A modern formatting library
|
||||
Version: @FMT_VERSION@
|
||||
Libs: -L${libdir} -l@FMT_LIB_NAME@
|
||||
Cflags: -I${includedir}
|
||||
|
|
@ -53,7 +53,7 @@ copyright = u'2013-2021 Apple, Inc and the FoundationDB project authors'
|
|||
|
||||
# Load the version information from 'versions.target'
|
||||
import xml.etree.ElementTree as ET
|
||||
version_path = os.path.join(os.path.dirname(__file__), '..', '..', 'versions.target')
|
||||
version_path = os.path.join(os.path.dirname(sys.executable), '..', '..', '..', 'versions.target')
|
||||
tree = ET.parse(version_path)
|
||||
root = tree.getroot()
|
||||
|
||||
|
|
|
@ -700,7 +700,7 @@
|
|||
"ssd",
|
||||
"ssd-1",
|
||||
"ssd-2",
|
||||
"ssd-redwood-experimental",
|
||||
"ssd-redwood-1-experimental",
|
||||
"ssd-rocksdb-experimental",
|
||||
"memory",
|
||||
"memory-1",
|
||||
|
@ -713,7 +713,7 @@
|
|||
"ssd",
|
||||
"ssd-1",
|
||||
"ssd-2",
|
||||
"ssd-redwood-experimental",
|
||||
"ssd-redwood-1-experimental",
|
||||
"ssd-rocksdb-experimental",
|
||||
"memory",
|
||||
"memory-1",
|
||||
|
|
|
@ -31,7 +31,7 @@ Because TSS recruitment only pairs *new* storage processes, you must add process
|
|||
Example commands
|
||||
----------------
|
||||
|
||||
Set the desired TSS processes count to 4, using the redwood storage engine: ``configure tss ssd-redwood-experimental count=4``.
|
||||
Set the desired TSS processes count to 4, using the redwood storage engine: ``configure tss ssd-redwood-1-experimental count=4``.
|
||||
|
||||
Change the desired TSS process count to 2: ``configure tss count=2``.
|
||||
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbbackup/BackupTLSConfig.h"
|
||||
#include "fdbclient/JsonBuilder.h"
|
||||
#include "flow/Arena.h"
|
||||
|
@ -2309,7 +2310,7 @@ ACTOR Future<Void> runRestore(Database db,
|
|||
|
||||
origDb = Database::createDatabase(originalClusterFile, Database::API_VERSION_LATEST);
|
||||
Version v = wait(timeKeeperVersionFromDatetime(targetTimestamp, origDb.get()));
|
||||
printf("Timestamp '%s' resolves to version %" PRId64 "\n", targetTimestamp.c_str(), v);
|
||||
fmt::print("Timestamp '{0}' resolves to version {1}\n", targetTimestamp, v);
|
||||
targetVersion = v;
|
||||
}
|
||||
|
||||
|
@ -2336,8 +2337,9 @@ ACTOR Future<Void> runRestore(Database db,
|
|||
throw restore_error();
|
||||
}
|
||||
|
||||
if (verbose)
|
||||
printf("Using target restore version %" PRId64 "\n", targetVersion);
|
||||
if (verbose) {
|
||||
fmt::print("Ussing target restore version {}\n", targetVersion);
|
||||
}
|
||||
}
|
||||
|
||||
if (performRestore) {
|
||||
|
@ -2359,19 +2361,19 @@ ACTOR Future<Void> runRestore(Database db,
|
|||
|
||||
if (waitForDone && verbose) {
|
||||
// If restore is now complete then report version restored
|
||||
printf("Restored to version %" PRId64 "\n", restoredVersion);
|
||||
fmt::print("Restored to version {}\n", restoredVersion);
|
||||
}
|
||||
} else {
|
||||
state Optional<RestorableFileSet> rset = wait(bc->getRestoreSet(targetVersion, ranges));
|
||||
|
||||
if (!rset.present()) {
|
||||
fprintf(stderr,
|
||||
"Insufficient data to restore to version %" PRId64 ". Describe backup for more information.\n",
|
||||
targetVersion);
|
||||
fmt::print(stderr,
|
||||
"Insufficient data to restore to version {}. Describe backup for more information.\n",
|
||||
targetVersion);
|
||||
throw restore_invalid_version();
|
||||
}
|
||||
|
||||
printf("Backup can be used to restore to version %" PRId64 "\n", targetVersion);
|
||||
fmt::print("Backup can be used to restore to version {}\n", targetVersion);
|
||||
}
|
||||
|
||||
} catch (Error& e) {
|
||||
|
@ -2472,20 +2474,20 @@ ACTOR Future<Void> runFastRestoreTool(Database db,
|
|||
|
||||
state Optional<RestorableFileSet> rset = wait(bc->getRestoreSet(restoreVersion));
|
||||
if (!rset.present()) {
|
||||
fprintf(stderr, "Insufficient data to restore to version %" PRId64 "\n", restoreVersion);
|
||||
fmt::print(stderr, "Insufficient data to restore to version {}\n", restoreVersion);
|
||||
throw restore_invalid_version();
|
||||
}
|
||||
|
||||
// Display the restore information, if requested
|
||||
if (verbose) {
|
||||
printf("[DRY RUN] Restoring backup to version: %" PRId64 "\n", restoreVersion);
|
||||
printf("%s\n", description.toString().c_str());
|
||||
fmt::print("[DRY RUN] Restoring backup to version: {}\n", restoreVersion);
|
||||
fmt::print("{}\n", description.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (waitForDone && verbose) {
|
||||
// If restore completed then report version restored
|
||||
printf("Restored to version %" PRId64 "%s\n", restoreVersion, (performRestore) ? "" : " (DRY RUN)");
|
||||
fmt::print("Restored to version {0}{1}\n", restoreVersion, (performRestore) ? "" : " (DRY RUN)");
|
||||
}
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled)
|
||||
|
@ -2520,7 +2522,7 @@ ACTOR Future<Void> dumpBackupData(const char* name,
|
|||
}
|
||||
}
|
||||
|
||||
printf("Scanning version range %" PRId64 " to %" PRId64 "\n", beginVersion, endVersion);
|
||||
fmt::print("Scanning version range {0} to {1}\n", beginVersion, endVersion);
|
||||
BackupFileList files = wait(c->dumpFileList(beginVersion, endVersion));
|
||||
files.toStream(stdout);
|
||||
|
||||
|
@ -2579,12 +2581,12 @@ ACTOR Future<Void> expireBackupData(const char* name,
|
|||
printf("\r%s%s\n", p.c_str(), (spaces > 0 ? std::string(spaces, ' ').c_str() : ""));
|
||||
|
||||
if (endVersion < 0)
|
||||
printf("All data before %" PRId64 " versions (%" PRId64
|
||||
" days) prior to latest backup log has been deleted.\n",
|
||||
-endVersion,
|
||||
-endVersion / ((int64_t)24 * 3600 * CLIENT_KNOBS->CORE_VERSIONSPERSECOND));
|
||||
fmt::print("All data before {0} versions ({1}"
|
||||
" days) prior to latest backup log has been deleted.\n",
|
||||
-endVersion,
|
||||
-endVersion / ((int64_t)24 * 3600 * CLIENT_KNOBS->CORE_VERSIONSPERSECOND));
|
||||
else
|
||||
printf("All data before version %" PRId64 " has been deleted.\n", endVersion);
|
||||
fmt::print("All data before version {} has been deleted.\n", endVersion);
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled)
|
||||
throw;
|
||||
|
@ -3762,35 +3764,6 @@ int main(int argc, char* argv[]) {
|
|||
}
|
||||
}
|
||||
|
||||
IKnobCollection::setGlobalKnobCollection(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False);
|
||||
auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection();
|
||||
for (const auto& [knobName, knobValueString] : knobs) {
|
||||
try {
|
||||
auto knobValue = g_knobs.parseKnobValue(knobName, knobValueString);
|
||||
g_knobs.setKnob(knobName, knobValue);
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_invalid_option_value) {
|
||||
fprintf(stderr,
|
||||
"WARNING: Invalid value '%s' for knob option '%s'\n",
|
||||
knobValueString.c_str(),
|
||||
knobName.c_str());
|
||||
TraceEvent(SevWarnAlways, "InvalidKnobValue")
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString));
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to set knob option '%s': %s\n", knobName.c_str(), e.what());
|
||||
TraceEvent(SevError, "FailedToSetKnob")
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString))
|
||||
.error(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reinitialize knobs in order to update knobs that are dependent on explicitly set knobs
|
||||
g_knobs.initialize(Randomize::False, IsSimulated::False);
|
||||
|
||||
if (trace) {
|
||||
if (!traceLogGroup.empty())
|
||||
setNetworkOption(FDBNetworkOptions::TRACE_LOG_GROUP, StringRef(traceLogGroup));
|
||||
|
@ -3831,6 +3804,34 @@ int main(int argc, char* argv[]) {
|
|||
return FDB_EXIT_ERROR;
|
||||
}
|
||||
|
||||
auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection();
|
||||
for (const auto& [knobName, knobValueString] : knobs) {
|
||||
try {
|
||||
auto knobValue = g_knobs.parseKnobValue(knobName, knobValueString);
|
||||
g_knobs.setKnob(knobName, knobValue);
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_invalid_option_value) {
|
||||
fprintf(stderr,
|
||||
"WARNING: Invalid value '%s' for knob option '%s'\n",
|
||||
knobValueString.c_str(),
|
||||
knobName.c_str());
|
||||
TraceEvent(SevWarnAlways, "InvalidKnobValue")
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString));
|
||||
} else {
|
||||
fprintf(stderr, "ERROR: Failed to set knob option '%s': %s\n", knobName.c_str(), e.what());
|
||||
TraceEvent(SevError, "FailedToSetKnob")
|
||||
.detail("Knob", printable(knobName))
|
||||
.detail("Value", printable(knobValueString))
|
||||
.error(e);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reinitialize knobs in order to update knobs that are dependent on explicitly set knobs
|
||||
g_knobs.initialize(Randomize::False, IsSimulated::False);
|
||||
|
||||
TraceEvent("ProgramStart")
|
||||
.setMaxEventLength(12000)
|
||||
.detail("SourceVersion", getSourceVersion())
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
#include "fdbclient/FDBOptions.g.h"
|
||||
|
@ -62,6 +64,17 @@ ACTOR Future<Void> changeFeedList(Database db) {
|
|||
|
||||
namespace fdb_cli {
|
||||
|
||||
ACTOR Future<Void> requestVersionUpdate(Database localDb, Reference<ChangeFeedData> feedData) {
|
||||
loop {
|
||||
wait(delay(5.0));
|
||||
Transaction tr(localDb);
|
||||
state Version ver = wait(tr.getReadVersion());
|
||||
fmt::print("Requesting version {}\n", ver);
|
||||
wait(feedData->whenAtLeast(ver));
|
||||
fmt::print("Feed at version {}\n", ver);
|
||||
}
|
||||
}
|
||||
|
||||
ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRef> tokens, Future<Void> warn) {
|
||||
if (tokens.size() == 1) {
|
||||
printUsage(tokens[0]);
|
||||
|
@ -109,7 +122,7 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
|
|||
}
|
||||
if (tokens.size() > 4) {
|
||||
int n = 0;
|
||||
if (sscanf(tokens[4].toString().c_str(), "%ld%n", &end, &n) != 1 || n != tokens[4].size()) {
|
||||
if (sscanf(tokens[4].toString().c_str(), "%" PRId64 "%n", &end, &n) != 1 || n != tokens[4].size()) {
|
||||
printUsage(tokens[0]);
|
||||
return false;
|
||||
}
|
||||
|
@ -117,24 +130,26 @@ ACTOR Future<bool> changeFeedCommandActor(Database localDb, std::vector<StringRe
|
|||
if (warn.isValid()) {
|
||||
warn.cancel();
|
||||
}
|
||||
state PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>> feedResults;
|
||||
state Future<Void> feed = localDb->getChangeFeedStream(feedResults, tokens[2], begin, end);
|
||||
state Reference<ChangeFeedData> feedData = makeReference<ChangeFeedData>();
|
||||
state Future<Void> feed = localDb->getChangeFeedStream(feedData, tokens[2], begin, end);
|
||||
state Future<Void> versionUpdates = requestVersionUpdate(localDb, feedData);
|
||||
printf("\n");
|
||||
try {
|
||||
state Future<Void> feedInterrupt = LineNoise::onKeyboardInterrupt();
|
||||
loop {
|
||||
choose {
|
||||
when(Standalone<VectorRef<MutationsAndVersionRef>> res = waitNext(feedResults.getFuture())) {
|
||||
when(Standalone<VectorRef<MutationsAndVersionRef>> res =
|
||||
waitNext(feedData->mutations.getFuture())) {
|
||||
for (auto& it : res) {
|
||||
for (auto& it2 : it.mutations) {
|
||||
printf("%lld %s\n", it.version, it2.toString().c_str());
|
||||
fmt::print("{0} {1}\n", it.version, it2.toString());
|
||||
}
|
||||
}
|
||||
}
|
||||
when(wait(feedInterrupt)) {
|
||||
feedInterrupt = Future<Void>();
|
||||
feed.cancel();
|
||||
feedResults = PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>>();
|
||||
feedData = makeReference<ChangeFeedData>();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <cinttypes>
|
||||
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
|
@ -55,7 +56,7 @@ ACTOR Future<Void> printHealthyZone(Reference<IDatabase> db) {
|
|||
} else {
|
||||
std::string zoneId = res[0].key.removePrefix(fdb_cli::maintenanceSpecialKeyRange.begin).toString();
|
||||
int64_t seconds = static_cast<int64_t>(boost::lexical_cast<double>(res[0].value.toString()));
|
||||
printf("Maintenance for zone %s will continue for %" PRId64 " seconds.\n", zoneId.c_str(), seconds);
|
||||
fmt::print("Maintenance for zone {0} will continue for {1} seconds.\n", zoneId, seconds);
|
||||
}
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
|
|
|
@ -18,6 +18,8 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbcli/fdbcli.actor.h"
|
||||
|
||||
#include "fdbclient/FDBOptions.g.h"
|
||||
|
@ -48,7 +50,7 @@ ACTOR Future<Void> printProcessClass(Reference<IDatabase> db) {
|
|||
ASSERT(processSourceList.size() == processTypeList.size());
|
||||
if (!processTypeList.size())
|
||||
printf("No processes are registered in the database.\n");
|
||||
printf("There are currently %zu processes in the database:\n", processTypeList.size());
|
||||
fmt::print("There are currently {} processes in the database:\n", processTypeList.size());
|
||||
for (int index = 0; index < processTypeList.size(); index++) {
|
||||
std::string address =
|
||||
processTypeList[index].key.removePrefix(fdb_cli::processClassTypeSpecialKeyRange.begin).toString();
|
||||
|
|
|
@ -19,6 +19,7 @@
|
|||
*/
|
||||
|
||||
#include "boost/lexical_cast.hpp"
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbclient/ClusterConnectionFile.h"
|
||||
#include "fdbclient/NativeAPI.actor.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
|
@ -631,9 +632,9 @@ ACTOR Future<Void> commitTransaction(Reference<ITransaction> tr) {
|
|||
wait(makeInterruptable(safeThreadFutureToFuture(tr->commit())));
|
||||
auto ver = tr->getCommittedVersion();
|
||||
if (ver != invalidVersion)
|
||||
printf("Committed (%" PRId64 ")\n", ver);
|
||||
fmt::print("Committed ({})\n", ver);
|
||||
else
|
||||
printf("Nothing to commit\n");
|
||||
fmt::print("Nothing to commit\n");
|
||||
return Void();
|
||||
}
|
||||
|
||||
|
@ -1402,7 +1403,9 @@ struct CLIOptions {
|
|||
exit_code = FDB_EXIT_ERROR;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void setupKnobs() {
|
||||
auto& g_knobs = IKnobCollection::getMutableGlobalKnobCollection();
|
||||
for (const auto& [knobName, knobValueString] : knobs) {
|
||||
try {
|
||||
|
@ -2432,8 +2435,6 @@ int main(int argc, char** argv) {
|
|||
|
||||
registerCrashHandler();
|
||||
|
||||
IKnobCollection::setGlobalKnobCollection(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False);
|
||||
|
||||
#ifdef __unixish__
|
||||
struct sigaction act;
|
||||
|
||||
|
@ -2534,6 +2535,10 @@ int main(int argc, char** argv) {
|
|||
try {
|
||||
API->selectApiVersion(opt.api_version);
|
||||
API->setupNetwork();
|
||||
opt.setupKnobs();
|
||||
if (opt.exit_code != -1) {
|
||||
return opt.exit_code;
|
||||
}
|
||||
Future<int> cliFuture = runCli(opt);
|
||||
Future<Void> timeoutFuture = opt.exit_timeout ? timeExit(opt.exit_timeout) : Never();
|
||||
auto f = stopNetworkAfter(success(cliFuture) || timeoutFuture);
|
||||
|
@ -2548,4 +2553,4 @@ int main(int argc, char** argv) {
|
|||
fprintf(stderr, "ERROR: %s (%d)\n", e.what(), e.code());
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <ostream>
|
||||
|
||||
// FIXME: Trim this down
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "flow/Platform.actor.h"
|
||||
#include "fdbclient/AsyncTaskThread.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
|
@ -75,13 +76,13 @@ std::string IBackupContainer::ExpireProgress::toString() const {
|
|||
|
||||
void BackupFileList::toStream(FILE* fout) const {
|
||||
for (const RangeFile& f : ranges) {
|
||||
fprintf(fout, "range %" PRId64 " %s\n", f.fileSize, f.fileName.c_str());
|
||||
fmt::print(fout, "range {0} {1}\n", f.fileSize, f.fileName);
|
||||
}
|
||||
for (const LogFile& f : logs) {
|
||||
fprintf(fout, "log %" PRId64 " %s\n", f.fileSize, f.fileName.c_str());
|
||||
fmt::print(fout, "log {0} {1}\n", f.fileSize, f.fileName);
|
||||
}
|
||||
for (const KeyspaceSnapshotFile& f : snapshots) {
|
||||
fprintf(fout, "snapshotManifest %" PRId64 " %s\n", f.totalSize, f.fileName.c_str());
|
||||
fmt::print(fout, "snapshotManifest {0} {1}\n", f.totalSize, f.fileName);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -1757,7 +1757,7 @@ ACTOR Future<Void> testBackupContainer(std::string url, Optional<std::string> en
|
|||
state Version expireVersion = listing.snapshots[i].endVersion;
|
||||
|
||||
// Expire everything up to but not including the snapshot end version
|
||||
printf("EXPIRE TO %" PRId64 "\n", expireVersion);
|
||||
fmt::print("EXPIRE TO {}\n", expireVersion);
|
||||
state Future<Void> f = c->expireData(expireVersion);
|
||||
wait(ready(f));
|
||||
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#define FDBCLIENT_BACKUP_CONTAINER_FILESYSTEM_H
|
||||
#pragma once
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "flow/Trace.h"
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "flow/serialize.h"
|
||||
#include "fdbclient/BlobGranuleFiles.h"
|
||||
#include "fdbclient/SystemData.h" // for allKeys unit test - could remove
|
||||
|
@ -70,10 +71,10 @@ static Arena loadSnapshotFile(const StringRef& snapshotData,
|
|||
}
|
||||
}*/
|
||||
if (BG_READ_DEBUG) {
|
||||
printf("Started with %d rows from snapshot after pruning to [%s - %s)\n",
|
||||
dataMap.size(),
|
||||
keyRange.begin.printable().c_str(),
|
||||
keyRange.end.printable().c_str());
|
||||
fmt::print("Started with {0} rows from snapshot after pruning to [{1} - {2})\n",
|
||||
dataMap.size(),
|
||||
keyRange.begin.printable(),
|
||||
keyRange.end.printable());
|
||||
}
|
||||
|
||||
return parseArena;
|
||||
|
@ -150,14 +151,14 @@ static Arena loadDeltaFile(StringRef deltaData,
|
|||
reader.deserialize(FileIdentifierFor<GranuleDeltas>::value, deltas, parseArena);
|
||||
|
||||
if (BG_READ_DEBUG) {
|
||||
printf("Parsed %d deltas from file\n", deltas.size());
|
||||
fmt::print("Parsed {}} deltas from file\n", deltas.size());
|
||||
}
|
||||
|
||||
// TODO REMOVE sanity check
|
||||
for (int i = 0; i < deltas.size() - 1; i++) {
|
||||
if (deltas[i].version > deltas[i + 1].version) {
|
||||
printf(
|
||||
"BG VERSION ORDER VIOLATION IN DELTA FILE: '%lld', '%lld'\n", deltas[i].version, deltas[i + 1].version);
|
||||
fmt::print(
|
||||
"BG VERSION ORDER VIOLATION IN DELTA FILE: '{0}', '{1}'\n", deltas[i].version, deltas[i + 1].version);
|
||||
}
|
||||
ASSERT(deltas[i].version <= deltas[i + 1].version);
|
||||
}
|
||||
|
@ -191,14 +192,14 @@ RangeResult materializeBlobGranule(const BlobGranuleChunkRef& chunk,
|
|||
}
|
||||
|
||||
if (BG_READ_DEBUG) {
|
||||
printf("Applying %d delta files\n", chunk.deltaFiles.size());
|
||||
fmt::print("Applying {} delta files\n", chunk.deltaFiles.size());
|
||||
}
|
||||
for (int deltaIdx = 0; deltaIdx < chunk.deltaFiles.size(); deltaIdx++) {
|
||||
Arena deltaArena = loadDeltaFile(deltaFileData[deltaIdx], keyRange, readVersion, lastFileEndVersion, dataMap);
|
||||
arena.dependsOn(deltaArena);
|
||||
}
|
||||
if (BG_READ_DEBUG) {
|
||||
printf("Applying %d memory deltas\n", chunk.newDeltas.size());
|
||||
fmt::print("Applying {} memory deltas\n", chunk.newDeltas.size());
|
||||
}
|
||||
applyDeltas(chunk.newDeltas, keyRange, readVersion, lastFileEndVersion, dataMap);
|
||||
|
||||
|
|
|
@ -21,6 +21,7 @@
|
|||
#include <map>
|
||||
#include <vector>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbclient/AsyncFileS3BlobStore.actor.h"
|
||||
#include "fdbclient/BlobGranuleCommon.h"
|
||||
#include "fdbclient/BlobGranuleFiles.h"
|
||||
|
|
|
@ -100,6 +100,7 @@ void ClientKnobs::initialize(Randomize randomize) {
|
|||
init( RANGESTREAM_FRAGMENT_SIZE, 1e6 );
|
||||
init( RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT, 20 );
|
||||
init( QUARANTINE_TSS_ON_MISMATCH, true ); if( randomize && BUGGIFY ) QUARANTINE_TSS_ON_MISMATCH = false; // if true, a tss mismatch will put the offending tss in quarantine. If false, it will just be killed
|
||||
init( CHANGE_FEED_EMPTY_BATCH_TIME, 0.005 );
|
||||
|
||||
//KeyRangeMap
|
||||
init( KRM_GET_RANGE_LIMIT, 1e5 ); if( randomize && BUGGIFY ) KRM_GET_RANGE_LIMIT = 10;
|
||||
|
|
|
@ -100,6 +100,7 @@ public:
|
|||
int64_t RANGESTREAM_FRAGMENT_SIZE;
|
||||
int RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT;
|
||||
bool QUARANTINE_TSS_ON_MISMATCH;
|
||||
double CHANGE_FEED_EMPTY_BATCH_TIME;
|
||||
|
||||
// KeyRangeMap
|
||||
int KRM_GET_RANGE_LIMIT;
|
||||
|
|
|
@ -31,8 +31,10 @@
|
|||
// A ClientWorkerInterface is embedded as the first element of a WorkerInterface.
|
||||
struct ClientWorkerInterface {
|
||||
constexpr static FileIdentifier file_identifier = 12418152;
|
||||
|
||||
RequestStream<struct RebootRequest> reboot;
|
||||
RequestStream<struct ProfilerRequest> profiler;
|
||||
RequestStream<struct SetFailureInjection> setFailureInjection;
|
||||
|
||||
bool operator==(ClientWorkerInterface const& r) const { return id() == r.id(); }
|
||||
bool operator!=(ClientWorkerInterface const& r) const { return id() != r.id(); }
|
||||
|
@ -43,7 +45,7 @@ struct ClientWorkerInterface {
|
|||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, reboot, profiler);
|
||||
serializer(ar, reboot, profiler, setFailureInjection);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -88,4 +90,39 @@ struct ProfilerRequest {
|
|||
}
|
||||
};
|
||||
|
||||
struct SetFailureInjection {
|
||||
constexpr static FileIdentifier file_identifier = 15439864;
|
||||
ReplyPromise<Void> reply;
|
||||
struct DiskFailureCommand {
|
||||
// how often should the disk be stalled (0 meaning once, 10 meaning every 10 secs)
|
||||
double stallInterval;
|
||||
// Period of time disk stalls will be injected for
|
||||
double stallPeriod;
|
||||
// Period of time the disk will be slowed down for
|
||||
double throttlePeriod;
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, stallInterval, stallPeriod, throttlePeriod);
|
||||
}
|
||||
};
|
||||
|
||||
struct FlipBitsCommand {
|
||||
// percent of bits to flip in the given file
|
||||
double percentBitFlips;
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, percentBitFlips);
|
||||
}
|
||||
};
|
||||
|
||||
Optional<DiskFailureCommand> diskFailure;
|
||||
Optional<FlipBitsCommand> flipBits;
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, reply, diskFailure, flipBits);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -39,6 +39,7 @@ struct ClusterInterface {
|
|||
RequestStream<struct ForceRecoveryRequest> forceRecovery;
|
||||
RequestStream<struct MoveShardRequest> moveShard;
|
||||
RequestStream<struct RepairSystemDataRequest> repairSystemData;
|
||||
RequestStream<struct SplitShardRequest> splitShard;
|
||||
|
||||
bool operator==(ClusterInterface const& r) const { return id() == r.id(); }
|
||||
bool operator!=(ClusterInterface const& r) const { return id() != r.id(); }
|
||||
|
@ -49,7 +50,8 @@ struct ClusterInterface {
|
|||
return openDatabase.getFuture().isReady() || failureMonitoring.getFuture().isReady() ||
|
||||
databaseStatus.getFuture().isReady() || ping.getFuture().isReady() ||
|
||||
getClientWorkers.getFuture().isReady() || forceRecovery.getFuture().isReady() ||
|
||||
moveShard.getFuture().isReady() || repairSystemData.getFuture().isReady();
|
||||
moveShard.getFuture().isReady() || repairSystemData.getFuture().isReady() ||
|
||||
splitShard.getFuture().isReady();
|
||||
}
|
||||
|
||||
void initEndpoints() {
|
||||
|
@ -61,6 +63,7 @@ struct ClusterInterface {
|
|||
forceRecovery.getEndpoint(TaskPriority::ClusterController);
|
||||
moveShard.getEndpoint(TaskPriority::ClusterController);
|
||||
repairSystemData.getEndpoint(TaskPriority::ClusterController);
|
||||
splitShard.getEndpoint(TaskPriority::ClusterController);
|
||||
}
|
||||
|
||||
template <class Ar>
|
||||
|
@ -73,7 +76,8 @@ struct ClusterInterface {
|
|||
getClientWorkers,
|
||||
forceRecovery,
|
||||
moveShard,
|
||||
repairSystemData);
|
||||
repairSystemData,
|
||||
splitShard);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -288,4 +292,35 @@ struct RepairSystemDataRequest {
|
|||
serializer(ar, reply);
|
||||
}
|
||||
};
|
||||
|
||||
// Returns the actual shards generated by the SplitShardRequest.
|
||||
struct SplitShardReply {
|
||||
constexpr static FileIdentifier file_identifier = 1384440;
|
||||
std::vector<KeyRange> shards;
|
||||
|
||||
SplitShardReply() {}
|
||||
explicit SplitShardReply(std::vector<KeyRange> shards) : shards{ std::move(shards) } {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, shards);
|
||||
}
|
||||
};
|
||||
|
||||
// Split keyrange [shard.begin, shard.end) into num shards.
|
||||
// Split points are chosen as the arithmeticlly equal division points of the given range.
|
||||
struct SplitShardRequest {
|
||||
constexpr static FileIdentifier file_identifier = 1384443;
|
||||
KeyRange shard;
|
||||
int num;
|
||||
ReplyPromise<SplitShardReply> reply;
|
||||
|
||||
SplitShardRequest() : num(0) {}
|
||||
SplitShardRequest(KeyRange shard, int num) : shard{ std::move(shard) }, num(num) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, shard, num, reply);
|
||||
}
|
||||
};
|
||||
#endif
|
||||
|
|
|
@ -20,6 +20,9 @@
|
|||
|
||||
#include "fdbclient/DatabaseConfiguration.h"
|
||||
#include "fdbclient/SystemData.h"
|
||||
#include "flow/ITrace.h"
|
||||
#include "flow/Trace.h"
|
||||
#include "flow/genericactors.actor.h"
|
||||
|
||||
DatabaseConfiguration::DatabaseConfiguration() {
|
||||
resetInternal();
|
||||
|
@ -49,6 +52,10 @@ void DatabaseConfiguration::resetInternal() {
|
|||
storageMigrationType = StorageMigrationType::DEFAULT;
|
||||
}
|
||||
|
||||
int toInt(ValueRef const& v) {
|
||||
return atoi(v.toString().c_str());
|
||||
}
|
||||
|
||||
void parse(int* i, ValueRef const& v) {
|
||||
// FIXME: Sanity checking
|
||||
*i = atoi(v.toString().c_str());
|
||||
|
@ -288,7 +295,7 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const {
|
|||
result["storage_engine"] = "ssd-2";
|
||||
} else if (tLogDataStoreType == KeyValueStoreType::SSD_BTREE_V2 &&
|
||||
storageServerStoreType == KeyValueStoreType::SSD_REDWOOD_V1) {
|
||||
result["storage_engine"] = "ssd-redwood-experimental";
|
||||
result["storage_engine"] = "ssd-redwood-1-experimental";
|
||||
} else if (tLogDataStoreType == KeyValueStoreType::SSD_BTREE_V2 &&
|
||||
storageServerStoreType == KeyValueStoreType::SSD_ROCKSDB_V1) {
|
||||
result["storage_engine"] = "ssd-rocksdb-experimental";
|
||||
|
@ -311,7 +318,7 @@ StatusObject DatabaseConfiguration::toJSON(bool noPolicies) const {
|
|||
} else if (testingStorageServerStoreType == KeyValueStoreType::SSD_BTREE_V2) {
|
||||
result["tss_storage_engine"] = "ssd-2";
|
||||
} else if (testingStorageServerStoreType == KeyValueStoreType::SSD_REDWOOD_V1) {
|
||||
result["tss_storage_engine"] = "ssd-redwood-experimental";
|
||||
result["tss_storage_engine"] = "ssd-redwood-1-experimental";
|
||||
} else if (testingStorageServerStoreType == KeyValueStoreType::SSD_ROCKSDB_V1) {
|
||||
result["tss_storage_engine"] = "ssd-rocksdb-experimental";
|
||||
} else if (testingStorageServerStoreType == KeyValueStoreType::MEMORY_RADIXTREE) {
|
||||
|
@ -466,6 +473,64 @@ std::string DatabaseConfiguration::toString() const {
|
|||
return json_spirit::write_string(json_spirit::mValue(toJSON()), json_spirit::Output_options::none);
|
||||
}
|
||||
|
||||
Key getKeyWithPrefix(std::string const& k) {
|
||||
return StringRef(k).withPrefix(configKeysPrefix);
|
||||
}
|
||||
|
||||
void DatabaseConfiguration::overwriteProxiesCount() {
|
||||
Key commitProxiesKey = getKeyWithPrefix("commit_proxies");
|
||||
Key grvProxiesKey = getKeyWithPrefix("grv_proxies");
|
||||
Key proxiesKey = getKeyWithPrefix("proxies");
|
||||
Optional<ValueRef> optCommitProxies = DatabaseConfiguration::get(commitProxiesKey);
|
||||
Optional<ValueRef> optGrvProxies = DatabaseConfiguration::get(grvProxiesKey);
|
||||
Optional<ValueRef> optProxies = DatabaseConfiguration::get(proxiesKey);
|
||||
|
||||
const int mutableGrvProxyCount = optGrvProxies.present() ? toInt(optGrvProxies.get()) : 0;
|
||||
const int mutableCommitProxyCount = optCommitProxies.present() ? toInt(optCommitProxies.get()) : 0;
|
||||
const int mutableProxiesCount = optProxies.present() ? toInt(optProxies.get()) : 0;
|
||||
|
||||
if (mutableProxiesCount > 1) {
|
||||
TraceEvent(SevDebug, "OverwriteProxiesCount")
|
||||
.detail("CPCount", commitProxyCount)
|
||||
.detail("MutableCPCount", mutableCommitProxyCount)
|
||||
.detail("GrvCount", grvProxyCount)
|
||||
.detail("MutableGrvCPCount", mutableGrvProxyCount)
|
||||
.detail("MutableProxiesCount", mutableProxiesCount);
|
||||
|
||||
if (grvProxyCount == -1 && commitProxyCount > 0) {
|
||||
if (mutableProxiesCount > commitProxyCount) {
|
||||
grvProxyCount = mutableProxiesCount - commitProxyCount;
|
||||
} else {
|
||||
// invalid configuration; provision min GrvProxies
|
||||
grvProxyCount = 1;
|
||||
commitProxyCount = mutableProxiesCount - 1;
|
||||
}
|
||||
} else if (grvProxyCount > 0 && commitProxyCount == -1) {
|
||||
if (mutableProxiesCount > grvProxyCount) {
|
||||
commitProxyCount = mutableProxiesCount - grvProxyCount;
|
||||
} else {
|
||||
// invalid configuration; provision min CommitProxies
|
||||
commitProxyCount = 1;
|
||||
grvProxyCount = mutableProxiesCount - 1;
|
||||
}
|
||||
} else if (grvProxyCount == -1 && commitProxyCount == -1) {
|
||||
// Use DEFAULT_COMMIT_GRV_PROXIES_RATIO to split proxies between Grv & Commit proxies
|
||||
const int derivedGrvProxyCount =
|
||||
std::max(1,
|
||||
std::min(CLIENT_KNOBS->DEFAULT_MAX_GRV_PROXIES,
|
||||
mutableProxiesCount / (CLIENT_KNOBS->DEFAULT_COMMIT_GRV_PROXIES_RATIO + 1)));
|
||||
|
||||
grvProxyCount = derivedGrvProxyCount;
|
||||
commitProxyCount = mutableProxiesCount - grvProxyCount;
|
||||
}
|
||||
|
||||
TraceEvent(SevDebug, "OverwriteProxiesCountResult")
|
||||
.detail("CommitProxyCount", commitProxyCount)
|
||||
.detail("GrvProxyCount", grvProxyCount)
|
||||
.detail("ProxyCount", mutableProxiesCount);
|
||||
}
|
||||
}
|
||||
|
||||
bool DatabaseConfiguration::setInternal(KeyRef key, ValueRef value) {
|
||||
KeyRef ck = key.removePrefix(configKeysPrefix);
|
||||
int type;
|
||||
|
@ -473,9 +538,13 @@ bool DatabaseConfiguration::setInternal(KeyRef key, ValueRef value) {
|
|||
if (ck == LiteralStringRef("initialized")) {
|
||||
initialized = true;
|
||||
} else if (ck == LiteralStringRef("commit_proxies")) {
|
||||
parse(&commitProxyCount, value);
|
||||
commitProxyCount = toInt(value);
|
||||
if (commitProxyCount == -1)
|
||||
overwriteProxiesCount();
|
||||
} else if (ck == LiteralStringRef("grv_proxies")) {
|
||||
parse(&grvProxyCount, value);
|
||||
grvProxyCount = toInt(value);
|
||||
if (grvProxyCount == -1)
|
||||
overwriteProxiesCount();
|
||||
} else if (ck == LiteralStringRef("resolvers")) {
|
||||
parse(&resolverCount, value);
|
||||
} else if (ck == LiteralStringRef("logs")) {
|
||||
|
@ -557,21 +626,7 @@ bool DatabaseConfiguration::setInternal(KeyRef key, ValueRef value) {
|
|||
parse((&type), value);
|
||||
storageMigrationType = (StorageMigrationType::MigrationType)type;
|
||||
} else if (ck == LiteralStringRef("proxies")) {
|
||||
int proxiesCount;
|
||||
parse(&proxiesCount, value);
|
||||
if (proxiesCount > 1) {
|
||||
int derivedGrvProxyCount =
|
||||
std::max(1,
|
||||
std::min(CLIENT_KNOBS->DEFAULT_MAX_GRV_PROXIES,
|
||||
proxiesCount / (CLIENT_KNOBS->DEFAULT_COMMIT_GRV_PROXIES_RATIO + 1)));
|
||||
int derivedCommitProxyCount = proxiesCount - derivedGrvProxyCount;
|
||||
if (grvProxyCount == -1) {
|
||||
grvProxyCount = derivedGrvProxyCount;
|
||||
}
|
||||
if (commitProxyCount == -1) {
|
||||
commitProxyCount = derivedCommitProxyCount;
|
||||
}
|
||||
}
|
||||
overwriteProxiesCount();
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
|
|
@ -333,6 +333,8 @@ private:
|
|||
|
||||
/// Check if the key is overridden by either mutableConfiguration or rawConfiguration
|
||||
bool isOverridden(std::string key) const;
|
||||
// Overwrite commitProxyCount and/or grvProxyCount if set to -1
|
||||
void overwriteProxiesCount();
|
||||
};
|
||||
|
||||
#endif
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
|
||||
#ifndef DatabaseContext_h
|
||||
#define DatabaseContext_h
|
||||
#include "fdbclient/Notified.h"
|
||||
#include "flow/FastAlloc.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "fdbclient/StorageServerInterface.h"
|
||||
|
@ -146,6 +147,37 @@ public:
|
|||
WatchMetadata(Key key, Optional<Value> value, Version version, TransactionInfo info, TagSet tags);
|
||||
};
|
||||
|
||||
struct MutationAndVersionStream {
|
||||
Standalone<MutationsAndVersionRef> next;
|
||||
PromiseStream<Standalone<MutationsAndVersionRef>> results;
|
||||
bool operator<(MutationAndVersionStream const& rhs) const { return next.version > rhs.next.version; }
|
||||
};
|
||||
|
||||
struct ChangeFeedStorageData : ReferenceCounted<ChangeFeedStorageData> {
|
||||
UID id;
|
||||
Future<Void> updater;
|
||||
NotifiedVersion version;
|
||||
NotifiedVersion desired;
|
||||
Promise<Void> destroyed;
|
||||
|
||||
~ChangeFeedStorageData() { destroyed.send(Void()); }
|
||||
};
|
||||
|
||||
struct ChangeFeedData : ReferenceCounted<ChangeFeedData> {
|
||||
PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>> mutations;
|
||||
std::vector<ReplyPromiseStream<ChangeFeedStreamReply>> streams;
|
||||
|
||||
Version getVersion();
|
||||
Future<Void> whenAtLeast(Version version);
|
||||
|
||||
NotifiedVersion lastReturnedVersion;
|
||||
std::vector<Reference<ChangeFeedStorageData>> storageData;
|
||||
AsyncVar<int> notAtLatest;
|
||||
Promise<Void> refresh;
|
||||
|
||||
ChangeFeedData() : notAtLatest(1) {}
|
||||
};
|
||||
|
||||
class DatabaseContext : public ReferenceCounted<DatabaseContext>, public FastAllocated<DatabaseContext>, NonCopyable {
|
||||
public:
|
||||
static DatabaseContext* allocateOnForeignThread() {
|
||||
|
@ -253,7 +285,7 @@ public:
|
|||
// Management API, create snapshot
|
||||
Future<Void> createSnapshot(StringRef uid, StringRef snapshot_command);
|
||||
|
||||
Future<Void> getChangeFeedStream(const PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>>& results,
|
||||
Future<Void> getChangeFeedStream(Reference<ChangeFeedData> results,
|
||||
Key rangeID,
|
||||
Version begin = 0,
|
||||
Version end = std::numeric_limits<Version>::max(),
|
||||
|
@ -341,6 +373,9 @@ public:
|
|||
std::unordered_map<UID, Reference<TSSMetrics>> tssMetrics;
|
||||
// map from changeFeedId -> changeFeedRange
|
||||
std::unordered_map<Key, KeyRange> changeFeedCache;
|
||||
std::unordered_map<UID, Reference<ChangeFeedStorageData>> changeFeedUpdaters;
|
||||
|
||||
Reference<ChangeFeedStorageData> getStorageData(StorageServerInterface interf);
|
||||
|
||||
UID dbId;
|
||||
IsInternal internal; // Only contexts created through the C client and fdbcli are non-internal
|
||||
|
@ -460,6 +495,8 @@ public:
|
|||
using TransactionT = ReadYourWritesTransaction;
|
||||
Reference<TransactionT> createTransaction();
|
||||
|
||||
EventCacheHolder connectToDatabaseEventCacheHolder;
|
||||
|
||||
private:
|
||||
std::unordered_map<KeyRef, Reference<WatchMetadata>> watchMap;
|
||||
};
|
||||
|
|
|
@ -706,7 +706,7 @@ struct KeyValueStoreType {
|
|||
case SSD_BTREE_V2:
|
||||
return "ssd-2";
|
||||
case SSD_REDWOOD_V1:
|
||||
return "ssd-redwood-experimental";
|
||||
return "ssd-redwood-1-experimental";
|
||||
case SSD_ROCKSDB_V1:
|
||||
return "ssd-rocksdb-experimental";
|
||||
case MEMORY:
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbclient/BackupAgent.actor.h"
|
||||
#include "fdbclient/BackupContainer.h"
|
||||
#include "fdbclient/DatabaseContext.h"
|
||||
|
@ -5342,10 +5343,7 @@ public:
|
|||
.detail("BackupContainer", bc->getURL())
|
||||
.detail("BeginVersion", beginVersion)
|
||||
.detail("TargetVersion", targetVersion);
|
||||
fprintf(stderr,
|
||||
"ERROR: Restore version %" PRId64 " is not possible from %s\n",
|
||||
targetVersion,
|
||||
bc->getURL().c_str());
|
||||
fmt::print(stderr, "ERROR: Restore version {0} is not possible from {1}\n", targetVersion, bc->getURL());
|
||||
throw restore_invalid_version();
|
||||
}
|
||||
|
||||
|
|
|
@ -73,20 +73,26 @@ KnobValue IKnobCollection::parseKnobValue(std::string const& knobName, std::stri
|
|||
UNSTOPPABLE_ASSERT(false);
|
||||
}
|
||||
|
||||
std::unique_ptr<IKnobCollection> IKnobCollection::globalKnobCollection =
|
||||
IKnobCollection::create(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False);
|
||||
std::unique_ptr<IKnobCollection>& IKnobCollection::globalKnobCollection() {
|
||||
static std::unique_ptr<IKnobCollection> res;
|
||||
if (!res) {
|
||||
res = IKnobCollection::create(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void IKnobCollection::setGlobalKnobCollection(Type type, Randomize randomize, IsSimulated isSimulated) {
|
||||
globalKnobCollection = create(type, randomize, isSimulated);
|
||||
FLOW_KNOBS = &globalKnobCollection->getFlowKnobs();
|
||||
globalKnobCollection() = create(type, randomize, isSimulated);
|
||||
ASSERT(FLOW_KNOBS == &bootstrapGlobalFlowKnobs);
|
||||
FLOW_KNOBS = &globalKnobCollection()->getFlowKnobs();
|
||||
}
|
||||
|
||||
IKnobCollection const& IKnobCollection::getGlobalKnobCollection() {
|
||||
return *globalKnobCollection;
|
||||
return *globalKnobCollection();
|
||||
}
|
||||
|
||||
IKnobCollection& IKnobCollection::getMutableGlobalKnobCollection() {
|
||||
return *globalKnobCollection;
|
||||
return *globalKnobCollection();
|
||||
}
|
||||
|
||||
ConfigMutationRef IKnobCollection::createSetMutation(Arena arena, KeyRef key, ValueRef value) {
|
||||
|
|
|
@ -36,7 +36,7 @@
|
|||
* - TestKnobs
|
||||
*/
|
||||
class IKnobCollection {
|
||||
static std::unique_ptr<IKnobCollection> globalKnobCollection;
|
||||
static std::unique_ptr<IKnobCollection>& globalKnobCollection();
|
||||
|
||||
public:
|
||||
virtual ~IKnobCollection() = default;
|
||||
|
|
|
@ -22,6 +22,7 @@
|
|||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
#include "fdbclient/Knobs.h"
|
||||
#include "flow/Arena.h"
|
||||
#include "fdbclient/ClusterConnectionMemoryRecord.h"
|
||||
|
@ -184,7 +185,7 @@ std::map<std::string, std::string> configForToken(std::string const& mode) {
|
|||
} else if (mode == "ssd" || mode == "ssd-2") {
|
||||
logType = KeyValueStoreType::SSD_BTREE_V2;
|
||||
storeType = KeyValueStoreType::SSD_BTREE_V2;
|
||||
} else if (mode == "ssd-redwood-experimental") {
|
||||
} else if (mode == "ssd-redwood-1-experimental") {
|
||||
logType = KeyValueStoreType::SSD_BTREE_V2;
|
||||
storeType = KeyValueStoreType::SSD_REDWOOD_V1;
|
||||
} else if (mode == "ssd-rocksdb-experimental") {
|
||||
|
@ -1677,9 +1678,9 @@ ACTOR Future<Void> printHealthyZone(Database cx) {
|
|||
printf("No ongoing maintenance.\n");
|
||||
} else {
|
||||
auto healthyZone = decodeHealthyZoneValue(val.get());
|
||||
printf("Maintenance for zone %s will continue for %" PRId64 " seconds.\n",
|
||||
healthyZone.first.toString().c_str(),
|
||||
(healthyZone.second - tr.getReadVersion().get()) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND);
|
||||
fmt::print("Maintenance for zone {0} will continue for {1} seconds.\n",
|
||||
healthyZone.first.toString(),
|
||||
(healthyZone.second - tr.getReadVersion().get()) / CLIENT_KNOBS->CORE_VERSIONSPERSECOND);
|
||||
}
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
|
|
|
@ -481,7 +481,7 @@ ThreadFuture<ProtocolVersion> DLDatabase::getServerProtocol(Optional<ProtocolVer
|
|||
// requireFunction - Determines the behavior if the function is not present. If true, an error is thrown. If false,
|
||||
// the function pointer will be set to nullptr.
|
||||
template <class T>
|
||||
void loadClientFunction(T* fp, void* lib, std::string libPath, const char* functionName, bool requireFunction = true) {
|
||||
void loadClientFunction(T* fp, void* lib, std::string libPath, const char* functionName, bool requireFunction) {
|
||||
*(void**)(fp) = loadFunction(lib, functionName);
|
||||
if (*fp == nullptr && requireFunction) {
|
||||
TraceEvent(SevError, "ErrorLoadingFunction").detail("LibraryPath", libPath).detail("Function", functionName);
|
||||
|
@ -511,16 +511,17 @@ void DLApi::init() {
|
|||
}
|
||||
}
|
||||
|
||||
loadClientFunction(&api->selectApiVersion, lib, fdbCPath, "fdb_select_api_version_impl");
|
||||
loadClientFunction(&api->selectApiVersion, lib, fdbCPath, "fdb_select_api_version_impl", headerVersion >= 0);
|
||||
loadClientFunction(&api->getClientVersion, lib, fdbCPath, "fdb_get_client_version", headerVersion >= 410);
|
||||
loadClientFunction(&api->setNetworkOption, lib, fdbCPath, "fdb_network_set_option");
|
||||
loadClientFunction(&api->setupNetwork, lib, fdbCPath, "fdb_setup_network");
|
||||
loadClientFunction(&api->runNetwork, lib, fdbCPath, "fdb_run_network");
|
||||
loadClientFunction(&api->stopNetwork, lib, fdbCPath, "fdb_stop_network");
|
||||
loadClientFunction(&api->setNetworkOption, lib, fdbCPath, "fdb_network_set_option", headerVersion >= 0);
|
||||
loadClientFunction(&api->setupNetwork, lib, fdbCPath, "fdb_setup_network", headerVersion >= 0);
|
||||
loadClientFunction(&api->runNetwork, lib, fdbCPath, "fdb_run_network", headerVersion >= 0);
|
||||
loadClientFunction(&api->stopNetwork, lib, fdbCPath, "fdb_stop_network", headerVersion >= 0);
|
||||
loadClientFunction(&api->createDatabase, lib, fdbCPath, "fdb_create_database", headerVersion >= 610);
|
||||
|
||||
loadClientFunction(&api->databaseCreateTransaction, lib, fdbCPath, "fdb_database_create_transaction");
|
||||
loadClientFunction(&api->databaseSetOption, lib, fdbCPath, "fdb_database_set_option");
|
||||
loadClientFunction(
|
||||
&api->databaseCreateTransaction, lib, fdbCPath, "fdb_database_create_transaction", headerVersion >= 0);
|
||||
loadClientFunction(&api->databaseSetOption, lib, fdbCPath, "fdb_database_set_option", headerVersion >= 0);
|
||||
loadClientFunction(&api->databaseGetMainThreadBusyness,
|
||||
lib,
|
||||
fdbCPath,
|
||||
|
@ -528,7 +529,7 @@ void DLApi::init() {
|
|||
headerVersion >= 700);
|
||||
loadClientFunction(
|
||||
&api->databaseGetServerProtocol, lib, fdbCPath, "fdb_database_get_server_protocol", headerVersion >= 700);
|
||||
loadClientFunction(&api->databaseDestroy, lib, fdbCPath, "fdb_database_destroy");
|
||||
loadClientFunction(&api->databaseDestroy, lib, fdbCPath, "fdb_database_destroy", headerVersion >= 0);
|
||||
loadClientFunction(&api->databaseRebootWorker, lib, fdbCPath, "fdb_database_reboot_worker", headerVersion >= 700);
|
||||
loadClientFunction(&api->databaseForceRecoveryWithDataLoss,
|
||||
lib,
|
||||
|
@ -538,33 +539,48 @@ void DLApi::init() {
|
|||
loadClientFunction(
|
||||
&api->databaseCreateSnapshot, lib, fdbCPath, "fdb_database_create_snapshot", headerVersion >= 700);
|
||||
|
||||
loadClientFunction(&api->transactionSetOption, lib, fdbCPath, "fdb_transaction_set_option");
|
||||
loadClientFunction(&api->transactionDestroy, lib, fdbCPath, "fdb_transaction_destroy");
|
||||
loadClientFunction(&api->transactionSetReadVersion, lib, fdbCPath, "fdb_transaction_set_read_version");
|
||||
loadClientFunction(&api->transactionGetReadVersion, lib, fdbCPath, "fdb_transaction_get_read_version");
|
||||
loadClientFunction(&api->transactionGet, lib, fdbCPath, "fdb_transaction_get");
|
||||
loadClientFunction(&api->transactionGetKey, lib, fdbCPath, "fdb_transaction_get_key");
|
||||
loadClientFunction(&api->transactionGetAddressesForKey, lib, fdbCPath, "fdb_transaction_get_addresses_for_key");
|
||||
loadClientFunction(&api->transactionGetRange, lib, fdbCPath, "fdb_transaction_get_range");
|
||||
loadClientFunction(&api->transactionGetRangeAndFlatMap, lib, fdbCPath, "fdb_transaction_get_range_and_flat_map");
|
||||
loadClientFunction(&api->transactionSetOption, lib, fdbCPath, "fdb_transaction_set_option", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionDestroy, lib, fdbCPath, "fdb_transaction_destroy", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->transactionSetReadVersion, lib, fdbCPath, "fdb_transaction_set_read_version", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->transactionGetReadVersion, lib, fdbCPath, "fdb_transaction_get_read_version", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGet, lib, fdbCPath, "fdb_transaction_get", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetKey, lib, fdbCPath, "fdb_transaction_get_key", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetAddressesForKey,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_transaction_get_addresses_for_key",
|
||||
headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetRange, lib, fdbCPath, "fdb_transaction_get_range", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetRangeAndFlatMap,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_transaction_get_range_and_flat_map",
|
||||
headerVersion >= 700);
|
||||
loadClientFunction(
|
||||
&api->transactionGetVersionstamp, lib, fdbCPath, "fdb_transaction_get_versionstamp", headerVersion >= 410);
|
||||
loadClientFunction(&api->transactionSet, lib, fdbCPath, "fdb_transaction_set");
|
||||
loadClientFunction(&api->transactionClear, lib, fdbCPath, "fdb_transaction_clear");
|
||||
loadClientFunction(&api->transactionClearRange, lib, fdbCPath, "fdb_transaction_clear_range");
|
||||
loadClientFunction(&api->transactionAtomicOp, lib, fdbCPath, "fdb_transaction_atomic_op");
|
||||
loadClientFunction(&api->transactionCommit, lib, fdbCPath, "fdb_transaction_commit");
|
||||
loadClientFunction(&api->transactionGetCommittedVersion, lib, fdbCPath, "fdb_transaction_get_committed_version");
|
||||
loadClientFunction(&api->transactionSet, lib, fdbCPath, "fdb_transaction_set", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionClear, lib, fdbCPath, "fdb_transaction_clear", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionClearRange, lib, fdbCPath, "fdb_transaction_clear_range", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionAtomicOp, lib, fdbCPath, "fdb_transaction_atomic_op", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionCommit, lib, fdbCPath, "fdb_transaction_commit", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetCommittedVersion,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_transaction_get_committed_version",
|
||||
headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetApproximateSize,
|
||||
lib,
|
||||
fdbCPath,
|
||||
"fdb_transaction_get_approximate_size",
|
||||
headerVersion >= 620);
|
||||
loadClientFunction(&api->transactionWatch, lib, fdbCPath, "fdb_transaction_watch");
|
||||
loadClientFunction(&api->transactionOnError, lib, fdbCPath, "fdb_transaction_on_error");
|
||||
loadClientFunction(&api->transactionReset, lib, fdbCPath, "fdb_transaction_reset");
|
||||
loadClientFunction(&api->transactionCancel, lib, fdbCPath, "fdb_transaction_cancel");
|
||||
loadClientFunction(&api->transactionAddConflictRange, lib, fdbCPath, "fdb_transaction_add_conflict_range");
|
||||
loadClientFunction(&api->transactionWatch, lib, fdbCPath, "fdb_transaction_watch", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionOnError, lib, fdbCPath, "fdb_transaction_on_error", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionReset, lib, fdbCPath, "fdb_transaction_reset", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionCancel, lib, fdbCPath, "fdb_transaction_cancel", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->transactionAddConflictRange, lib, fdbCPath, "fdb_transaction_add_conflict_range", headerVersion >= 0);
|
||||
loadClientFunction(&api->transactionGetEstimatedRangeSizeBytes,
|
||||
lib,
|
||||
fdbCPath,
|
||||
|
@ -583,20 +599,24 @@ void DLApi::init() {
|
|||
headerVersion >= 710);
|
||||
loadClientFunction(
|
||||
&api->transactionReadBlobGranules, lib, fdbCPath, "fdb_transaction_read_blob_granules", headerVersion >= 710);
|
||||
loadClientFunction(
|
||||
&api->futureGetInt64, lib, fdbCPath, headerVersion >= 620 ? "fdb_future_get_int64" : "fdb_future_get_version");
|
||||
loadClientFunction(&api->futureGetInt64,
|
||||
lib,
|
||||
fdbCPath,
|
||||
headerVersion >= 620 ? "fdb_future_get_int64" : "fdb_future_get_version",
|
||||
headerVersion >= 0);
|
||||
loadClientFunction(&api->futureGetUInt64, lib, fdbCPath, "fdb_future_get_uint64", headerVersion >= 700);
|
||||
loadClientFunction(&api->futureGetError, lib, fdbCPath, "fdb_future_get_error");
|
||||
loadClientFunction(&api->futureGetKey, lib, fdbCPath, "fdb_future_get_key");
|
||||
loadClientFunction(&api->futureGetValue, lib, fdbCPath, "fdb_future_get_value");
|
||||
loadClientFunction(&api->futureGetStringArray, lib, fdbCPath, "fdb_future_get_string_array");
|
||||
loadClientFunction(&api->futureGetError, lib, fdbCPath, "fdb_future_get_error", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureGetKey, lib, fdbCPath, "fdb_future_get_key", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureGetValue, lib, fdbCPath, "fdb_future_get_value", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureGetStringArray, lib, fdbCPath, "fdb_future_get_string_array", headerVersion >= 0);
|
||||
loadClientFunction(
|
||||
&api->futureGetKeyRangeArray, lib, fdbCPath, "fdb_future_get_keyrange_array", headerVersion >= 710);
|
||||
loadClientFunction(&api->futureGetKeyArray, lib, fdbCPath, "fdb_future_get_key_array", headerVersion >= 700);
|
||||
loadClientFunction(&api->futureGetKeyValueArray, lib, fdbCPath, "fdb_future_get_keyvalue_array");
|
||||
loadClientFunction(&api->futureSetCallback, lib, fdbCPath, "fdb_future_set_callback");
|
||||
loadClientFunction(&api->futureCancel, lib, fdbCPath, "fdb_future_cancel");
|
||||
loadClientFunction(&api->futureDestroy, lib, fdbCPath, "fdb_future_destroy");
|
||||
loadClientFunction(
|
||||
&api->futureGetKeyValueArray, lib, fdbCPath, "fdb_future_get_keyvalue_array", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureSetCallback, lib, fdbCPath, "fdb_future_set_callback", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureCancel, lib, fdbCPath, "fdb_future_cancel", headerVersion >= 0);
|
||||
loadClientFunction(&api->futureDestroy, lib, fdbCPath, "fdb_future_destroy", headerVersion >= 0);
|
||||
|
||||
loadClientFunction(
|
||||
&api->resultGetKeyValueArray, lib, fdbCPath, "fdb_result_get_keyvalue_array", headerVersion >= 710);
|
||||
|
@ -1839,6 +1859,8 @@ void MultiVersionApi::setNetworkOption(FDBNetworkOptions::Option option, Optiona
|
|||
}
|
||||
|
||||
void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option, Optional<StringRef> value) {
|
||||
bool forwardOption = false;
|
||||
|
||||
auto itr = FDBNetworkOptions::optionInfo.find(option);
|
||||
if (itr != FDBNetworkOptions::optionInfo.end()) {
|
||||
TraceEvent("SetNetworkOption").detail("Option", itr->second.name);
|
||||
|
@ -1870,20 +1892,24 @@ void MultiVersionApi::setNetworkOptionInternal(FDBNetworkOptions::Option option,
|
|||
ASSERT(!value.present() && !networkStartSetup);
|
||||
externalClient = true;
|
||||
bypassMultiClientApi = true;
|
||||
forwardOption = true;
|
||||
} else if (option == FDBNetworkOptions::CLIENT_THREADS_PER_VERSION) {
|
||||
MutexHolder holder(lock);
|
||||
validateOption(value, true, false, false);
|
||||
ASSERT(!networkStartSetup);
|
||||
if (networkStartSetup) {
|
||||
throw invalid_option();
|
||||
}
|
||||
#if defined(__unixish__)
|
||||
threadCount = extractIntOption(value, 1, 1024);
|
||||
#else
|
||||
// multiple client threads are not supported on windows.
|
||||
threadCount = extractIntOption(value, 1, 1);
|
||||
#endif
|
||||
if (threadCount > 1) {
|
||||
disableLocalClient();
|
||||
}
|
||||
} else {
|
||||
forwardOption = true;
|
||||
}
|
||||
|
||||
if (forwardOption) {
|
||||
MutexHolder holder(lock);
|
||||
localClient->api->setNetworkOption(option, value);
|
||||
|
||||
|
@ -1910,6 +1936,10 @@ void MultiVersionApi::setupNetwork() {
|
|||
throw network_already_setup();
|
||||
}
|
||||
|
||||
if (threadCount > 1) {
|
||||
disableLocalClient();
|
||||
}
|
||||
|
||||
for (auto i : externalClientDescriptions) {
|
||||
std::string path = i.second.libPath;
|
||||
std::string filename = basename(path);
|
||||
|
@ -1953,13 +1983,13 @@ void MultiVersionApi::setupNetwork() {
|
|||
localClient->api->setupNetwork();
|
||||
}
|
||||
|
||||
localClient->loadProtocolVersion();
|
||||
localClient->loadVersion();
|
||||
|
||||
if (!bypassMultiClientApi) {
|
||||
runOnExternalClientsAllThreads([this](Reference<ClientInfo> client) {
|
||||
TraceEvent("InitializingExternalClient").detail("LibraryPath", client->libPath);
|
||||
client->api->selectApiVersion(apiVersion);
|
||||
client->loadProtocolVersion();
|
||||
client->loadVersion();
|
||||
});
|
||||
|
||||
MutexHolder holder(lock);
|
||||
|
@ -2007,11 +2037,21 @@ void MultiVersionApi::runNetwork() {
|
|||
|
||||
std::vector<THREAD_HANDLE> handles;
|
||||
if (!bypassMultiClientApi) {
|
||||
runOnExternalClientsAllThreads([&handles](Reference<ClientInfo> client) {
|
||||
if (client->external) {
|
||||
handles.push_back(g_network->startThread(&runNetworkThread, client.getPtr()));
|
||||
}
|
||||
});
|
||||
for (int threadNum = 0; threadNum < threadCount; threadNum++) {
|
||||
runOnExternalClients(threadNum, [&handles, threadNum](Reference<ClientInfo> client) {
|
||||
if (client->external) {
|
||||
std::string threadName = format("fdb-%s-%d", client->releaseVersion.c_str(), threadNum);
|
||||
if (threadName.size() > 15) {
|
||||
threadName = format("fdb-%s", client->releaseVersion.c_str());
|
||||
if (threadName.size() > 15) {
|
||||
threadName = "fdb-external";
|
||||
}
|
||||
}
|
||||
handles.push_back(
|
||||
g_network->startThread(&runNetworkThread, client.getPtr(), 0, threadName.c_str()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
localClient->api->runNetwork();
|
||||
|
@ -2215,19 +2255,24 @@ MultiVersionApi::MultiVersionApi()
|
|||
MultiVersionApi* MultiVersionApi::api = new MultiVersionApi();
|
||||
|
||||
// ClientInfo
|
||||
void ClientInfo::loadProtocolVersion() {
|
||||
void ClientInfo::loadVersion() {
|
||||
std::string version = api->getClientVersion();
|
||||
if (version == "unknown") {
|
||||
protocolVersion = ProtocolVersion(0);
|
||||
releaseVersion = "unknown";
|
||||
return;
|
||||
}
|
||||
|
||||
Standalone<ClientVersionRef> clientVersion = ClientVersionRef(StringRef(version));
|
||||
|
||||
char* next;
|
||||
std::string protocolVersionStr = ClientVersionRef(StringRef(version)).protocolVersion.toString();
|
||||
std::string protocolVersionStr = clientVersion.protocolVersion.toString();
|
||||
protocolVersion = ProtocolVersion(strtoull(protocolVersionStr.c_str(), &next, 16));
|
||||
|
||||
ASSERT(protocolVersion.version() != 0 && protocolVersion.version() != ULLONG_MAX);
|
||||
ASSERT_EQ(next, &protocolVersionStr[protocolVersionStr.length()]);
|
||||
|
||||
releaseVersion = clientVersion.clientVersion.toString();
|
||||
}
|
||||
|
||||
bool ClientInfo::canReplace(Reference<ClientInfo> other) const {
|
||||
|
|
|
@ -521,6 +521,7 @@ struct ClientDesc {
|
|||
|
||||
struct ClientInfo : ClientDesc, ThreadSafeReferenceCounted<ClientInfo> {
|
||||
ProtocolVersion protocolVersion;
|
||||
std::string releaseVersion = "unknown";
|
||||
IClientApi* api;
|
||||
bool failed;
|
||||
std::atomic_bool initialized;
|
||||
|
@ -533,7 +534,7 @@ struct ClientInfo : ClientDesc, ThreadSafeReferenceCounted<ClientInfo> {
|
|||
ClientInfo(IClientApi* api, std::string libPath)
|
||||
: ClientDesc(libPath, true), protocolVersion(0), api(api), failed(false), initialized(false) {}
|
||||
|
||||
void loadProtocolVersion();
|
||||
void loadVersion();
|
||||
bool canReplace(Reference<ClientInfo> other) const;
|
||||
};
|
||||
|
||||
|
|
|
@ -28,6 +28,8 @@
|
|||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "contrib/fmt-8.0.1/include/fmt/format.h"
|
||||
|
||||
#include "fdbclient/FDBTypes.h"
|
||||
#include "fdbrpc/FailureMonitor.h"
|
||||
#include "fdbrpc/MultiInterface.h"
|
||||
|
@ -65,6 +67,7 @@
|
|||
#include "flow/ActorCollection.h"
|
||||
#include "flow/DeterministicRandom.h"
|
||||
#include "flow/Error.h"
|
||||
#include "flow/FastRef.h"
|
||||
#include "flow/IRandom.h"
|
||||
#include "flow/flow.h"
|
||||
#include "flow/genericactors.actor.h"
|
||||
|
@ -124,8 +127,8 @@ TLSConfig tlsConfig(TLSEndpointType::CLIENT);
|
|||
NetworkOptions::NetworkOptions()
|
||||
: traceRollSize(TRACE_DEFAULT_ROLL_SIZE), traceMaxLogsSize(TRACE_DEFAULT_MAX_LOGS_SIZE), traceLogGroup("default"),
|
||||
traceFormat("xml"), traceClockSource("now"),
|
||||
supportedVersions(new ReferencedObject<Standalone<VectorRef<ClientVersionRef>>>()), runLoopProfilingEnabled(false) {
|
||||
}
|
||||
supportedVersions(new ReferencedObject<Standalone<VectorRef<ClientVersionRef>>>()), runLoopProfilingEnabled(false),
|
||||
primaryClient(true) {}
|
||||
|
||||
static const Key CLIENT_LATENCY_INFO_PREFIX = LiteralStringRef("client_latency/");
|
||||
static const Key CLIENT_LATENCY_INFO_CTR_PREFIX = LiteralStringRef("client_latency_counter/");
|
||||
|
@ -1226,7 +1229,8 @@ DatabaseContext::DatabaseContext(Reference<AsyncVar<Reference<IClusterConnection
|
|||
clientInfo(clientInfo), clientInfoMonitor(clientInfoMonitor), coordinator(coordinator), apiVersion(apiVersion),
|
||||
mvCacheInsertLocation(0), healthMetricsLastUpdated(0), detailedHealthMetricsLastUpdated(0),
|
||||
smoothMidShardSize(CLIENT_KNOBS->SHARD_STAT_SMOOTH_AMOUNT),
|
||||
specialKeySpace(std::make_unique<SpecialKeySpace>(specialKeys.begin, specialKeys.end, /* test */ false)) {
|
||||
specialKeySpace(std::make_unique<SpecialKeySpace>(specialKeys.begin, specialKeys.end, /* test */ false)),
|
||||
connectToDatabaseEventCacheHolder(format("ConnectToDatabase/%s", dbId.toString().c_str())) {
|
||||
dbId = deterministicRandom()->randomUniqueID();
|
||||
connected = (clientInfo->get().commitProxies.size() && clientInfo->get().grvProxies.size())
|
||||
? Void()
|
||||
|
@ -1478,7 +1482,8 @@ DatabaseContext::DatabaseContext(const Error& err)
|
|||
transactionsExpensiveClearCostEstCount("ExpensiveClearCostEstCount", cc),
|
||||
transactionGrvFullBatches("NumGrvFullBatches", cc), transactionGrvTimedOutBatches("NumGrvTimedOutBatches", cc),
|
||||
latencies(1000), readLatencies(1000), commitLatencies(1000), GRVLatencies(1000), mutationsPerCommit(1000),
|
||||
bytesPerCommit(1000), transactionTracingSample(false), smoothMidShardSize(CLIENT_KNOBS->SHARD_STAT_SMOOTH_AMOUNT) {}
|
||||
bytesPerCommit(1000), transactionTracingSample(false), smoothMidShardSize(CLIENT_KNOBS->SHARD_STAT_SMOOTH_AMOUNT),
|
||||
connectToDatabaseEventCacheHolder(format("ConnectToDatabase/%s", dbId.toString().c_str())) {}
|
||||
|
||||
// Static constructor used by server processes to create a DatabaseContext
|
||||
// For internal (fdbserver) use only
|
||||
|
@ -1792,6 +1797,8 @@ Database Database::createDatabase(Reference<IClusterConnectionRecord> connRecord
|
|||
if (!g_network)
|
||||
throw network_not_setup();
|
||||
|
||||
platform::ImageInfo imageInfo = platform::getImageInfo();
|
||||
|
||||
if (connRecord) {
|
||||
if (networkOptions.traceDirectory.present() && !traceFileIsOpen()) {
|
||||
g_network->initMetrics();
|
||||
|
@ -1814,11 +1821,11 @@ Database Database::createDatabase(Reference<IClusterConnectionRecord> connRecord
|
|||
.detail("SourceVersion", getSourceVersion())
|
||||
.detail("Version", FDB_VT_VERSION)
|
||||
.detail("PackageName", FDB_VT_PACKAGE_NAME)
|
||||
.detail("ClusterFile", connRecord->toString())
|
||||
.detail("ConnectionString", connRecord->getConnectionString().toString())
|
||||
.detailf("ActualTime", "%lld", DEBUG_DETERMINISM ? 0 : time(nullptr))
|
||||
.detail("ApiVersion", apiVersion)
|
||||
.detailf("ImageOffset", "%p", platform::getImageOffset())
|
||||
.detail("ClientLibrary", imageInfo.fileName)
|
||||
.detailf("ImageOffset", "%p", imageInfo.offset)
|
||||
.detail("Primary", networkOptions.primaryClient)
|
||||
.trackLatest("ClientStart");
|
||||
|
||||
initializeSystemMonitorMachineState(SystemMonitorMachineState(IPAddress(publicIP)));
|
||||
|
@ -1872,6 +1879,16 @@ Database Database::createDatabase(Reference<IClusterConnectionRecord> connRecord
|
|||
database, Reference<AsyncVar<ClientDBInfo> const>(clientInfo), std::addressof(clientInfo->get()));
|
||||
GlobalConfig::globalConfig().trigger(samplingFrequency, samplingProfilerUpdateFrequency);
|
||||
GlobalConfig::globalConfig().trigger(samplingWindow, samplingProfilerUpdateWindow);
|
||||
|
||||
TraceEvent("ConnectToDatabase", database->dbId)
|
||||
.detail("Version", FDB_VT_VERSION)
|
||||
.detail("ClusterFile", connRecord ? connRecord->toString() : "None")
|
||||
.detail("ConnectionString", connRecord ? connRecord->getConnectionString().toString() : "None")
|
||||
.detail("ClientLibrary", imageInfo.fileName)
|
||||
.detail("Primary", networkOptions.primaryClient)
|
||||
.detail("Internal", internal)
|
||||
.trackLatest(database->connectToDatabaseEventCacheHolder.trackingKey);
|
||||
|
||||
return database;
|
||||
}
|
||||
|
||||
|
@ -1989,7 +2006,11 @@ void setNetworkOption(FDBNetworkOptions::Option option, Optional<StringRef> valu
|
|||
|
||||
try {
|
||||
auto knobValue = IKnobCollection::parseKnobValue(knobName, knobValueString, IKnobCollection::Type::CLIENT);
|
||||
IKnobCollection::getMutableGlobalKnobCollection().setKnob(knobName, knobValue);
|
||||
if (g_network) {
|
||||
IKnobCollection::getMutableGlobalKnobCollection().setKnob(knobName, knobValue);
|
||||
} else {
|
||||
networkOptions.knobs[knobName] = knobValue;
|
||||
}
|
||||
} catch (Error& e) {
|
||||
TraceEvent(SevWarnAlways, "UnrecognizedKnob").detail("Knob", knobName.c_str());
|
||||
fprintf(stderr, "FoundationDB client ignoring unrecognized knob option '%s'\n", knobName.c_str());
|
||||
|
@ -2090,6 +2111,9 @@ void setNetworkOption(FDBNetworkOptions::Option option, Optional<StringRef> valu
|
|||
}
|
||||
break;
|
||||
}
|
||||
case FDBNetworkOptions::EXTERNAL_CLIENT:
|
||||
networkOptions.primaryClient = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
@ -2135,6 +2159,13 @@ ACTOR Future<Void> monitorNetworkBusyness() {
|
|||
}
|
||||
}
|
||||
|
||||
static void setupGlobalKnobs() {
|
||||
IKnobCollection::setGlobalKnobCollection(IKnobCollection::Type::CLIENT, Randomize::False, IsSimulated::False);
|
||||
for (const auto& [knobName, knobValue] : networkOptions.knobs) {
|
||||
IKnobCollection::getMutableGlobalKnobCollection().setKnob(knobName, knobValue);
|
||||
}
|
||||
}
|
||||
|
||||
// Setup g_network and start monitoring for network busyness
|
||||
void setupNetwork(uint64_t transportId, UseMetrics useMetrics) {
|
||||
if (g_network)
|
||||
|
@ -2143,6 +2174,7 @@ void setupNetwork(uint64_t transportId, UseMetrics useMetrics) {
|
|||
if (!networkOptions.logClientInfo.present())
|
||||
networkOptions.logClientInfo = true;
|
||||
|
||||
setupGlobalKnobs();
|
||||
TLS::DisableOpenSSLAtExitHandler();
|
||||
g_network = newNet2(tlsConfig, false, useMetrics || networkOptions.traceDirectory.present());
|
||||
g_network->addStopCallback(Net2FileSystem::stop);
|
||||
|
@ -4125,7 +4157,6 @@ ACTOR Future<Void> getRangeStream(PromiseStream<RangeResult> _results,
|
|||
Reverse reverse,
|
||||
TransactionInfo info,
|
||||
TagSet tags) {
|
||||
|
||||
state ParallelStream<RangeResult> results(_results, CLIENT_KNOBS->RANGESTREAM_BUFFERED_FRAGMENTS_LIMIT);
|
||||
|
||||
// FIXME: better handling to disable row limits
|
||||
|
@ -4596,7 +4627,6 @@ Future<RangeResult> Transaction::getRangeAndFlatMap(const KeySelector& begin,
|
|||
GetRangeLimits limits,
|
||||
Snapshot snapshot,
|
||||
Reverse reverse) {
|
||||
|
||||
return getRangeInternal<GetKeyValuesAndFlatMapRequest, GetKeyValuesAndFlatMapReply>(
|
||||
begin, end, mapper, limits, snapshot, reverse);
|
||||
}
|
||||
|
@ -6014,7 +6044,6 @@ ACTOR Future<ProtocolVersion> getCoordinatorProtocol(NetworkAddressList coordina
|
|||
ACTOR Future<Optional<ProtocolVersion>> getCoordinatorProtocolFromConnectPacket(
|
||||
NetworkAddress coordinatorAddress,
|
||||
Optional<ProtocolVersion> expectedVersion) {
|
||||
|
||||
state Reference<AsyncVar<Optional<ProtocolVersion>> const> protocolVersion =
|
||||
FlowTransport::transport().getPeerProtocolAsyncVar(coordinatorAddress);
|
||||
|
||||
|
@ -6042,7 +6071,6 @@ ACTOR Future<Optional<ProtocolVersion>> getCoordinatorProtocolFromConnectPacket(
|
|||
ACTOR Future<ProtocolVersion> getClusterProtocolImpl(
|
||||
Reference<AsyncVar<Optional<ClientLeaderRegInterface>> const> coordinator,
|
||||
Optional<ProtocolVersion> expectedVersion) {
|
||||
|
||||
state bool needToConnect = true;
|
||||
state Future<ProtocolVersion> protocolVersion = Never();
|
||||
|
||||
|
@ -6521,193 +6549,6 @@ struct BWLocationInfo : MultiInterface<ReferencedInterface<BlobWorkerInterface>>
|
|||
explicit BWLocationInfo(const std::vector<Reference<ReferencedInterface<BlobWorkerInterface>>>& v) : Locations(v) {}
|
||||
};
|
||||
|
||||
/*
|
||||
ACTOR Future<Void> readBlobGranulesStreamActor(Reference<DatabaseContext> db,
|
||||
PromiseStream<Standalone<BlobGranuleChunkRef>> results,
|
||||
KeyRange range,
|
||||
Version begin,
|
||||
Optional<Version> read) { // end not present is just latest
|
||||
state Database cx(db);
|
||||
state Reference<ReadYourWritesTransaction> tr = makeReference<ReadYourWritesTransaction>(cx);
|
||||
state RangeResult blobGranuleMapping;
|
||||
state Version readVersion;
|
||||
state Key granuleStartKey;
|
||||
state Key granuleEndKey;
|
||||
state KeyRange keyRange = range;
|
||||
state int i, loopCounter = 0;
|
||||
state UID workerId;
|
||||
loop {
|
||||
try {
|
||||
// FIXME: Use streaming parallelism?
|
||||
// Read mapping and worker interfaces from DB
|
||||
loopCounter++;
|
||||
loop {
|
||||
try {
|
||||
tr->reset();
|
||||
tr->setOption(FDBTransactionOptions::ACCESS_SYSTEM_KEYS);
|
||||
if (loopCounter == 1) {
|
||||
// if retrying, use new version for mapping but original version for read version
|
||||
if (read.present()) {
|
||||
readVersion = read.get();
|
||||
} else {
|
||||
Version _end = wait(tr->getReadVersion());
|
||||
readVersion = _end;
|
||||
}
|
||||
}
|
||||
|
||||
// Right now just read whole blob range assignments from DB
|
||||
// FIXME: eventually we probably want to cache this and invalidate similarly to storage servers.
|
||||
// Cache misses could still read from the DB, or we could add it to the Transaction State Store and
|
||||
// have proxies serve it from memory.
|
||||
RangeResult _bgMapping = wait(krmGetRanges(
|
||||
tr, blobGranuleMappingKeys.begin, keyRange, 1000, GetRangeLimits::BYTE_LIMIT_UNLIMITED));
|
||||
blobGranuleMapping = _bgMapping;
|
||||
if (blobGranuleMapping.more) {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("BG Mapping for [%s - %s) too large!\n");
|
||||
}
|
||||
throw unsupported_operation();
|
||||
}
|
||||
ASSERT(!blobGranuleMapping.more && blobGranuleMapping.size() < CLIENT_KNOBS->TOO_MANY);
|
||||
|
||||
if (blobGranuleMapping.size() == 0) {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("no blob worker assignments yet \n");
|
||||
}
|
||||
throw transaction_too_old();
|
||||
}
|
||||
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("Doing blob granule request @ %lld\n", readVersion);
|
||||
printf("blob worker assignments:\n");
|
||||
}
|
||||
|
||||
for (i = 0; i < blobGranuleMapping.size() - 1; i++) {
|
||||
granuleStartKey = blobGranuleMapping[i].key;
|
||||
granuleEndKey = blobGranuleMapping[i + 1].key;
|
||||
if (!blobGranuleMapping[i].value.size()) {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("Key range [%s - %s) missing worker assignment!\n",
|
||||
granuleStartKey.printable().c_str(),
|
||||
granuleEndKey.printable().c_str());
|
||||
// TODO probably new exception type instead
|
||||
}
|
||||
throw transaction_too_old();
|
||||
}
|
||||
|
||||
workerId = decodeBlobGranuleMappingValue(blobGranuleMapping[i].value);
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf(" [%s - %s): %s\n",
|
||||
granuleStartKey.printable().c_str(),
|
||||
granuleEndKey.printable().c_str(),
|
||||
workerId.toString().c_str());
|
||||
}
|
||||
|
||||
if (!cx->blobWorker_interf.count(workerId)) {
|
||||
Optional<Value> workerInterface = wait(tr->get(blobWorkerListKeyFor(workerId)));
|
||||
ASSERT(workerInterface.present());
|
||||
cx->blobWorker_interf[workerId] = decodeBlobWorkerListValue(workerInterface.get());
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf(" decoded worker interface for %s\n", workerId.toString().c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
} catch (Error& e) {
|
||||
wait(tr->onError(e));
|
||||
}
|
||||
}
|
||||
|
||||
// Make request for each granule
|
||||
for (i = 0; i < blobGranuleMapping.size() - 1; i++) {
|
||||
granuleStartKey = blobGranuleMapping[i].key;
|
||||
granuleEndKey = blobGranuleMapping[i + 1].key;
|
||||
// if this was a time travel and the request returned larger bounds, skip this chunk
|
||||
if (granuleEndKey <= keyRange.begin) {
|
||||
continue;
|
||||
}
|
||||
workerId = decodeBlobGranuleMappingValue(blobGranuleMapping[i].value);
|
||||
// prune first/last granules to requested range
|
||||
if (keyRange.begin > granuleStartKey) {
|
||||
granuleStartKey = keyRange.begin;
|
||||
}
|
||||
if (keyRange.end < granuleEndKey) {
|
||||
granuleEndKey = keyRange.end;
|
||||
}
|
||||
|
||||
state BlobGranuleFileRequest req;
|
||||
req.keyRange = KeyRangeRef(StringRef(req.arena, granuleStartKey), StringRef(req.arena, granuleEndKey));
|
||||
req.beginVersion = begin;
|
||||
req.readVersion = readVersion;
|
||||
|
||||
std::vector<Reference<ReferencedInterface<BlobWorkerInterface>>> v;
|
||||
v.push_back(makeReference<ReferencedInterface<BlobWorkerInterface>>(cx->blobWorker_interf[workerId]));
|
||||
state Reference<MultiInterface<ReferencedInterface<BlobWorkerInterface>>> location =
|
||||
makeReference<BWLocationInfo>(v);
|
||||
// use load balance with one option for now for retry and error handling
|
||||
BlobGranuleFileReply rep = wait(loadBalance(location,
|
||||
&BlobWorkerInterface::blobGranuleFileRequest,
|
||||
req,
|
||||
TaskPriority::DefaultPromiseEndpoint,
|
||||
AtMostOnce::False,
|
||||
nullptr));
|
||||
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("Blob granule request for [%s - %s) @ %lld - %lld got reply from %s:\n",
|
||||
granuleStartKey.printable().c_str(),
|
||||
granuleEndKey.printable().c_str(),
|
||||
begin,
|
||||
readVersion,
|
||||
workerId.toString().c_str());
|
||||
}
|
||||
for (auto& chunk : rep.chunks) {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("[%s - %s)\n",
|
||||
chunk.keyRange.begin.printable().c_str(),
|
||||
chunk.keyRange.end.printable().c_str());
|
||||
|
||||
printf(" SnapshotFile:\n %s\n",
|
||||
chunk.snapshotFile.present() ? chunk.snapshotFile.get().toString().c_str() : "<none>");
|
||||
printf(" DeltaFiles:\n");
|
||||
for (auto& df : chunk.deltaFiles) {
|
||||
printf(" %s\n", df.toString().c_str());
|
||||
}
|
||||
printf(" Deltas: (%d)", chunk.newDeltas.size());
|
||||
if (chunk.newDeltas.size() > 0) {
|
||||
printf(" with version [%lld - %lld]",
|
||||
chunk.newDeltas[0].version,
|
||||
chunk.newDeltas[chunk.newDeltas.size() - 1].version);
|
||||
}
|
||||
printf(" IncludedVersion: %lld\n", chunk.includedVersion);
|
||||
printf("\n\n");
|
||||
}
|
||||
Arena a;
|
||||
a.dependsOn(rep.arena);
|
||||
results.send(Standalone<BlobGranuleChunkRef>(chunk, a));
|
||||
keyRange = KeyRangeRef(std::min(chunk.keyRange.end, keyRange.end), keyRange.end);
|
||||
}
|
||||
}
|
||||
results.sendError(end_of_stream());
|
||||
return Void();
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled) {
|
||||
throw;
|
||||
}
|
||||
if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed ||
|
||||
e.code() == error_code_connection_failed) {
|
||||
// TODO would invalidate mapping cache here if we had it
|
||||
wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY));
|
||||
} else {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("blob granule file request got unexpected error %s\n", e.name());
|
||||
}
|
||||
results.sendError(e);
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
ACTOR Future<Standalone<VectorRef<BlobGranuleChunkRef>>> readBlobGranulesActor(
|
||||
Database cx,
|
||||
Transaction* self,
|
||||
|
@ -6756,8 +6597,8 @@ ACTOR Future<Standalone<VectorRef<BlobGranuleChunkRef>>> readBlobGranulesActor(
|
|||
}
|
||||
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("Doing blob granule request @ %lld\n", *readVersionOut);
|
||||
printf("blob worker assignments:\n");
|
||||
fmt::print("Doing blob granule request @ {}\n", endVersion);
|
||||
fmt::print("blob worker assignments:\n");
|
||||
}
|
||||
|
||||
for (i = 0; i < blobGranuleMapping.size() - 1; i++) {
|
||||
|
@ -6829,32 +6670,31 @@ ACTOR Future<Standalone<VectorRef<BlobGranuleChunkRef>>> readBlobGranulesActor(
|
|||
nullptr));
|
||||
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("Blob granule request for [%s - %s) @ %lld - %lld got reply from %s:\n",
|
||||
granuleStartKey.printable().c_str(),
|
||||
granuleEndKey.printable().c_str(),
|
||||
begin,
|
||||
*readVersionOut,
|
||||
workerId.toString().c_str());
|
||||
fmt::print("Blob granule request for [{0} - {1}) @ {2} - {3} got reply from {4}:\n",
|
||||
granuleStartKey.printable(),
|
||||
granuleEndKey.printable(),
|
||||
begin,
|
||||
*readVersionOut,
|
||||
workerId.toString());
|
||||
}
|
||||
results.arena().dependsOn(rep.arena);
|
||||
for (auto& chunk : rep.chunks) {
|
||||
if (BG_REQUEST_DEBUG) {
|
||||
printf("[%s - %s)\n", chunk.keyRange.begin.printable().c_str(), chunk.keyRange.end.printable().c_str());
|
||||
|
||||
printf(" SnapshotFile:\n %s\n",
|
||||
printf(" SnapshotFile:\n \n",
|
||||
chunk.snapshotFile.present() ? chunk.snapshotFile.get().toString().c_str() : "<none>");
|
||||
printf(" DeltaFiles:\n");
|
||||
for (auto& df : chunk.deltaFiles) {
|
||||
printf(" %s\n", df.toString().c_str());
|
||||
fmt::print(" {0}\n", df.toString());
|
||||
}
|
||||
printf(" Deltas: (%d)", chunk.newDeltas.size());
|
||||
fmt::print(" Deltas: ({0})", chunk.newDeltas.size());
|
||||
if (chunk.newDeltas.size() > 0) {
|
||||
printf(" with version [%lld - %lld]",
|
||||
chunk.newDeltas[0].version,
|
||||
chunk.newDeltas[chunk.newDeltas.size() - 1].version);
|
||||
fmt::print(" with version [{0} - {1}]",
|
||||
chunk.newDeltas[0].version,
|
||||
chunk.newDeltas[chunk.newDeltas.size() - 1].version);
|
||||
}
|
||||
printf(" IncludedVersion: %lld\n", chunk.includedVersion);
|
||||
printf("\n\n");
|
||||
fmt::print(" IncludedVersion: {}\n\n\n", chunk.includedVersion);
|
||||
}
|
||||
|
||||
results.push_back(results.arena(), chunk);
|
||||
|
@ -7173,70 +7013,210 @@ Future<Void> DatabaseContext::createSnapshot(StringRef uid, StringRef snapshot_c
|
|||
return createSnapshotActor(this, UID::fromString(uid_str), snapshot_command);
|
||||
}
|
||||
|
||||
ACTOR Future<Void> singleChangeFeedStream(StorageServerInterface interf,
|
||||
PromiseStream<Standalone<MutationsAndVersionRef>> results,
|
||||
Key rangeID,
|
||||
Version begin,
|
||||
Version end,
|
||||
KeyRange range) {
|
||||
ACTOR Future<Void> storageFeedVersionUpdater(StorageServerInterface interf, ChangeFeedStorageData* self) {
|
||||
state Promise<Void> destroyed = self->destroyed;
|
||||
loop {
|
||||
try {
|
||||
state Version lastEmpty = invalidVersion;
|
||||
state ChangeFeedStreamRequest req;
|
||||
req.rangeID = rangeID;
|
||||
req.begin = begin;
|
||||
req.end = end;
|
||||
req.range = range;
|
||||
|
||||
state ReplyPromiseStream<ChangeFeedStreamReply> replyStream = interf.changeFeedStream.getReplyStream(req);
|
||||
|
||||
loop {
|
||||
state ChangeFeedStreamReply rep = waitNext(replyStream.getFuture());
|
||||
begin = rep.mutations.back().version + 1;
|
||||
|
||||
state int resultLoc = 0;
|
||||
// FIXME: handle empty versions properly
|
||||
while (resultLoc < rep.mutations.size()) {
|
||||
if (rep.mutations[resultLoc].mutations.size() || rep.mutations[resultLoc].version + 1 == end ||
|
||||
(rep.mutations[resultLoc].mutations.empty() &&
|
||||
rep.mutations[resultLoc].version >= lastEmpty + 5000000)) {
|
||||
wait(results.onEmpty());
|
||||
results.send(rep.mutations[resultLoc]);
|
||||
}
|
||||
resultLoc++;
|
||||
}
|
||||
if (begin == end) {
|
||||
results.sendError(end_of_stream());
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled) {
|
||||
throw;
|
||||
}
|
||||
results.sendError(e);
|
||||
if (destroyed.isSet()) {
|
||||
return Void();
|
||||
}
|
||||
if (self->version.get() < self->desired.get()) {
|
||||
wait(delay(CLIENT_KNOBS->CHANGE_FEED_EMPTY_BATCH_TIME) || self->version.whenAtLeast(self->desired.get()));
|
||||
if (destroyed.isSet()) {
|
||||
return Void();
|
||||
}
|
||||
if (self->version.get() < self->desired.get()) {
|
||||
ChangeFeedVersionUpdateReply rep = wait(brokenPromiseToNever(
|
||||
interf.changeFeedVersionUpdate.getReply(ChangeFeedVersionUpdateRequest(self->desired.get()))));
|
||||
if (rep.version > self->version.get()) {
|
||||
self->version.set(rep.version);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
wait(self->desired.whenAtLeast(self->version.get() + 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct MutationAndVersionStream {
|
||||
Standalone<MutationsAndVersionRef> next;
|
||||
PromiseStream<Standalone<MutationsAndVersionRef>> results;
|
||||
bool operator<(MutationAndVersionStream const& rhs) const { return next.version > rhs.next.version; }
|
||||
};
|
||||
Reference<ChangeFeedStorageData> DatabaseContext::getStorageData(StorageServerInterface interf) {
|
||||
auto it = changeFeedUpdaters.find(interf.id());
|
||||
if (it == changeFeedUpdaters.end()) {
|
||||
Reference<ChangeFeedStorageData> newStorageUpdater = makeReference<ChangeFeedStorageData>();
|
||||
newStorageUpdater->id = interf.id();
|
||||
newStorageUpdater->updater = storageFeedVersionUpdater(interf, newStorageUpdater.getPtr());
|
||||
changeFeedUpdaters[interf.id()] = newStorageUpdater;
|
||||
return newStorageUpdater;
|
||||
}
|
||||
return it->second;
|
||||
}
|
||||
|
||||
ACTOR Future<Void> mergeChangeFeedStream(std::vector<std::pair<StorageServerInterface, KeyRange>> interfs,
|
||||
PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>> results,
|
||||
Version ChangeFeedData::getVersion() {
|
||||
if (notAtLatest.get() == 0 && mutations.isEmpty()) {
|
||||
Version v = storageData[0]->version.get();
|
||||
for (int i = 1; i < storageData.size(); i++) {
|
||||
if (storageData[i]->version.get() < v) {
|
||||
v = storageData[i]->version.get();
|
||||
}
|
||||
}
|
||||
return std::max(v, lastReturnedVersion.get());
|
||||
}
|
||||
return lastReturnedVersion.get();
|
||||
}
|
||||
|
||||
ACTOR Future<Void> changeFeedWhenAtLatest(ChangeFeedData* self, Version version) {
|
||||
state Future<Void> lastReturned = self->lastReturnedVersion.whenAtLeast(version);
|
||||
loop {
|
||||
if (self->notAtLatest.get() == 0) {
|
||||
std::vector<Future<Void>> allAtLeast;
|
||||
for (auto& it : self->storageData) {
|
||||
if (it->version.get() < version) {
|
||||
if (version > it->desired.get()) {
|
||||
it->desired.set(version);
|
||||
}
|
||||
allAtLeast.push_back(it->version.whenAtLeast(version));
|
||||
}
|
||||
}
|
||||
choose {
|
||||
when(wait(lastReturned)) { return Void(); }
|
||||
when(wait(waitForAll(allAtLeast))) {
|
||||
std::vector<Future<Void>> onEmpty;
|
||||
if (!self->mutations.isEmpty()) {
|
||||
onEmpty.push_back(self->mutations.onEmpty());
|
||||
}
|
||||
for (auto& it : self->streams) {
|
||||
if (!it.isEmpty()) {
|
||||
onEmpty.push_back(it.onEmpty());
|
||||
}
|
||||
}
|
||||
if (!onEmpty.size()) {
|
||||
return Void();
|
||||
}
|
||||
choose {
|
||||
when(wait(waitForAll(onEmpty))) {
|
||||
wait(delay(0));
|
||||
return Void();
|
||||
}
|
||||
when(wait(lastReturned)) { return Void(); }
|
||||
when(wait(self->refresh.getFuture())) {}
|
||||
when(wait(self->notAtLatest.onChange())) {}
|
||||
}
|
||||
}
|
||||
when(wait(self->refresh.getFuture())) {}
|
||||
when(wait(self->notAtLatest.onChange())) {}
|
||||
}
|
||||
} else {
|
||||
choose {
|
||||
when(wait(lastReturned)) { return Void(); }
|
||||
when(wait(self->notAtLatest.onChange())) {}
|
||||
when(wait(self->refresh.getFuture())) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Void> ChangeFeedData::whenAtLeast(Version version) {
|
||||
return changeFeedWhenAtLatest(this, version);
|
||||
}
|
||||
|
||||
ACTOR Future<Void> singleChangeFeedStream(StorageServerInterface interf,
|
||||
PromiseStream<Standalone<MutationsAndVersionRef>> results,
|
||||
ReplyPromiseStream<ChangeFeedStreamReply> replyStream,
|
||||
Version end,
|
||||
Reference<ChangeFeedData> feedData,
|
||||
Reference<ChangeFeedStorageData> storageData) {
|
||||
state bool atLatestVersion = false;
|
||||
state Version nextVersion = 0;
|
||||
try {
|
||||
loop {
|
||||
if (nextVersion >= end) {
|
||||
results.sendError(end_of_stream());
|
||||
return Void();
|
||||
}
|
||||
choose {
|
||||
when(state ChangeFeedStreamReply rep = waitNext(replyStream.getFuture())) {
|
||||
state int resultLoc = 0;
|
||||
while (resultLoc < rep.mutations.size()) {
|
||||
wait(results.onEmpty());
|
||||
if (rep.mutations[resultLoc].version >= nextVersion) {
|
||||
results.send(rep.mutations[resultLoc]);
|
||||
} else {
|
||||
ASSERT(rep.mutations[resultLoc].mutations.empty());
|
||||
}
|
||||
resultLoc++;
|
||||
}
|
||||
nextVersion = rep.mutations.back().version + 1;
|
||||
|
||||
if (!atLatestVersion && rep.atLatestVersion) {
|
||||
atLatestVersion = true;
|
||||
feedData->notAtLatest.set(feedData->notAtLatest.get() - 1);
|
||||
}
|
||||
if (rep.minStreamVersion > storageData->version.get()) {
|
||||
storageData->version.set(rep.minStreamVersion);
|
||||
}
|
||||
|
||||
for (auto& it : feedData->storageData) {
|
||||
if (rep.mutations.back().version > it->desired.get()) {
|
||||
it->desired.set(rep.mutations.back().version);
|
||||
}
|
||||
}
|
||||
}
|
||||
when(wait(atLatestVersion && replyStream.isEmpty() && results.isEmpty()
|
||||
? storageData->version.whenAtLeast(nextVersion)
|
||||
: Future<Void>(Never()))) {
|
||||
MutationsAndVersionRef empty;
|
||||
empty.version = storageData->version.get();
|
||||
results.send(empty);
|
||||
nextVersion = storageData->version.get() + 1;
|
||||
}
|
||||
when(wait(atLatestVersion && replyStream.isEmpty() && !results.isEmpty() ? results.onEmpty()
|
||||
: Future<Void>(Never()))) {}
|
||||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled) {
|
||||
throw;
|
||||
}
|
||||
results.sendError(e);
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
|
||||
ACTOR Future<Void> mergeChangeFeedStream(Reference<DatabaseContext> db,
|
||||
std::vector<std::pair<StorageServerInterface, KeyRange>> interfs,
|
||||
Reference<ChangeFeedData> results,
|
||||
Key rangeID,
|
||||
Version* begin,
|
||||
Version end) {
|
||||
state std::priority_queue<MutationAndVersionStream, std::vector<MutationAndVersionStream>> mutations;
|
||||
state std::vector<Future<Void>> fetchers(interfs.size());
|
||||
state std::vector<MutationAndVersionStream> streams(interfs.size());
|
||||
|
||||
results->streams.clear();
|
||||
for (auto& it : interfs) {
|
||||
ChangeFeedStreamRequest req;
|
||||
req.rangeID = rangeID;
|
||||
req.begin = *begin;
|
||||
req.end = end;
|
||||
req.range = it.second;
|
||||
results->streams.push_back(it.first.changeFeedStream.getReplyStream(req));
|
||||
}
|
||||
|
||||
for (auto& it : results->storageData) {
|
||||
if (it->debugGetReferenceCount() == 2) {
|
||||
db->changeFeedUpdaters.erase(it->id);
|
||||
}
|
||||
}
|
||||
results->storageData.clear();
|
||||
Promise<Void> refresh = results->refresh;
|
||||
results->refresh = Promise<Void>();
|
||||
for (int i = 0; i < interfs.size(); i++) {
|
||||
fetchers[i] =
|
||||
singleChangeFeedStream(interfs[i].first, streams[i].results, rangeID, *begin, end, interfs[i].second);
|
||||
results->storageData.push_back(db->getStorageData(interfs[i].first));
|
||||
}
|
||||
results->notAtLatest.set(interfs.size());
|
||||
refresh.send(Void());
|
||||
|
||||
for (int i = 0; i < interfs.size(); i++) {
|
||||
fetchers[i] = singleChangeFeedStream(
|
||||
interfs[i].first, streams[i].results, results->streams[i], end, results, results->storageData[i]);
|
||||
}
|
||||
state int interfNum = 0;
|
||||
while (interfNum < interfs.size()) {
|
||||
|
@ -7260,7 +7240,8 @@ ACTOR Future<Void> mergeChangeFeedStream(std::vector<std::pair<StorageServerInte
|
|||
if (nextStream.next.version != checkVersion) {
|
||||
if (nextOut.size()) {
|
||||
*begin = checkVersion + 1;
|
||||
results.send(nextOut);
|
||||
results->mutations.send(nextOut);
|
||||
results->lastReturnedVersion.set(nextOut.back().version);
|
||||
nextOut = Standalone<VectorRef<MutationsAndVersionRef>>();
|
||||
}
|
||||
checkVersion = nextStream.next.version;
|
||||
|
@ -7285,7 +7266,8 @@ ACTOR Future<Void> mergeChangeFeedStream(std::vector<std::pair<StorageServerInte
|
|||
}
|
||||
}
|
||||
if (nextOut.size()) {
|
||||
results.send(nextOut);
|
||||
results->mutations.send(nextOut);
|
||||
results->lastReturnedVersion.set(nextOut.back().version);
|
||||
}
|
||||
throw end_of_stream();
|
||||
}
|
||||
|
@ -7324,7 +7306,7 @@ ACTOR Future<KeyRange> getChangeFeedRange(Reference<DatabaseContext> db, Databas
|
|||
}
|
||||
|
||||
ACTOR Future<Void> getChangeFeedStreamActor(Reference<DatabaseContext> db,
|
||||
PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>> results,
|
||||
Reference<ChangeFeedData> results,
|
||||
Key rangeID,
|
||||
Version begin,
|
||||
Version end,
|
||||
|
@ -7397,34 +7379,65 @@ ACTOR Future<Void> getChangeFeedStreamActor(Reference<DatabaseContext> db,
|
|||
interfs.push_back(std::make_pair(locations[i].second->getInterface(chosenLocations[i]),
|
||||
locations[i].first & range));
|
||||
}
|
||||
wait(mergeChangeFeedStream(interfs, results, rangeID, &begin, end) || cx->connectionFileChanged());
|
||||
wait(mergeChangeFeedStream(db, interfs, results, rangeID, &begin, end) || cx->connectionFileChanged());
|
||||
} else {
|
||||
state ChangeFeedStreamRequest req;
|
||||
req.rangeID = rangeID;
|
||||
req.begin = begin;
|
||||
req.end = end;
|
||||
req.range = range;
|
||||
|
||||
StorageServerInterface interf = locations[0].second->getInterface(chosenLocations[0]);
|
||||
state ReplyPromiseStream<ChangeFeedStreamReply> replyStream =
|
||||
locations[0]
|
||||
.second->get(chosenLocations[0], &StorageServerInterface::changeFeedStream)
|
||||
.getReplyStream(req);
|
||||
|
||||
interf.changeFeedStream.getReplyStream(req);
|
||||
for (auto& it : results->storageData) {
|
||||
if (it->debugGetReferenceCount() == 2) {
|
||||
db->changeFeedUpdaters.erase(it->id);
|
||||
}
|
||||
}
|
||||
results->streams.clear();
|
||||
results->storageData.clear();
|
||||
results->storageData.push_back(db->getStorageData(interf));
|
||||
Promise<Void> refresh = results->refresh;
|
||||
results->refresh = Promise<Void>();
|
||||
results->notAtLatest.set(1);
|
||||
refresh.send(Void());
|
||||
state bool atLatest = false;
|
||||
loop {
|
||||
wait(results.onEmpty());
|
||||
wait(results->mutations.onEmpty());
|
||||
choose {
|
||||
when(wait(cx->connectionFileChanged())) { break; }
|
||||
when(ChangeFeedStreamReply rep = waitNext(replyStream.getFuture())) {
|
||||
begin = rep.mutations.back().version + 1;
|
||||
results.send(Standalone<VectorRef<MutationsAndVersionRef>>(rep.mutations, rep.arena));
|
||||
results->mutations.send(
|
||||
Standalone<VectorRef<MutationsAndVersionRef>>(rep.mutations, rep.arena));
|
||||
results->lastReturnedVersion.set(rep.mutations.back().version);
|
||||
if (!atLatest && rep.atLatestVersion) {
|
||||
atLatest = true;
|
||||
results->notAtLatest.set(0);
|
||||
}
|
||||
if (rep.minStreamVersion > results->storageData[0]->version.get()) {
|
||||
results->storageData[0]->version.set(rep.minStreamVersion);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Error& e) {
|
||||
if (e.code() == error_code_actor_cancelled) {
|
||||
for (auto& it : results->storageData) {
|
||||
if (it->debugGetReferenceCount() == 2) {
|
||||
db->changeFeedUpdaters.erase(it->id);
|
||||
}
|
||||
}
|
||||
results->streams.clear();
|
||||
results->storageData.clear();
|
||||
results->refresh.sendError(change_feed_cancelled());
|
||||
throw;
|
||||
}
|
||||
if (results->notAtLatest.get() == 0) {
|
||||
results->notAtLatest.set(1);
|
||||
}
|
||||
|
||||
if (e.code() == error_code_wrong_shard_server || e.code() == error_code_all_alternatives_failed ||
|
||||
e.code() == error_code_connection_failed || e.code() == error_code_unknown_change_feed ||
|
||||
e.code() == error_code_broken_promise) {
|
||||
|
@ -7432,19 +7445,26 @@ ACTOR Future<Void> getChangeFeedStreamActor(Reference<DatabaseContext> db,
|
|||
cx->invalidateCache(keys);
|
||||
wait(delay(CLIENT_KNOBS->WRONG_SHARD_SERVER_DELAY));
|
||||
} else {
|
||||
results.sendError(e);
|
||||
results->mutations.sendError(e);
|
||||
results->refresh.sendError(change_feed_cancelled());
|
||||
for (auto& it : results->storageData) {
|
||||
if (it->debugGetReferenceCount() == 2) {
|
||||
db->changeFeedUpdaters.erase(it->id);
|
||||
}
|
||||
}
|
||||
results->streams.clear();
|
||||
results->storageData.clear();
|
||||
return Void();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<Void> DatabaseContext::getChangeFeedStream(
|
||||
const PromiseStream<Standalone<VectorRef<MutationsAndVersionRef>>>& results,
|
||||
Key rangeID,
|
||||
Version begin,
|
||||
Version end,
|
||||
KeyRange range) {
|
||||
Future<Void> DatabaseContext::getChangeFeedStream(Reference<ChangeFeedData> results,
|
||||
Key rangeID,
|
||||
Version begin,
|
||||
Version end,
|
||||
KeyRange range) {
|
||||
return getChangeFeedStreamActor(Reference<DatabaseContext>::addRef(this), results, rangeID, begin, end, range);
|
||||
}
|
||||
|
||||
|
@ -7587,16 +7607,15 @@ ACTOR Future<Void> popChangeFeedMutationsActor(Reference<DatabaseContext> db, Ke
|
|||
return Void();
|
||||
}
|
||||
|
||||
// FIXME: lookup both the src and dest shards as of the pop version to ensure all locations are popped
|
||||
state std::vector<Future<Void>> popRequests;
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
for (int j = 0; j < locations[i].second->size(); j++) {
|
||||
popRequests.push_back(locations[i].second->getInterface(j).changeFeedPop.getReply(
|
||||
ChangeFeedPopRequest(rangeID, version, locations[i].first)));
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// FIXME: lookup both the src and dest shards as of the pop version to ensure all locations are popped
|
||||
std::vector<Future<Void>> popRequests;
|
||||
for (int i = 0; i < locations.size(); i++) {
|
||||
for (int j = 0; j < locations[i].second->size(); j++) {
|
||||
popRequests.push_back(locations[i].second->getInterface(j).changeFeedPop.getReply(
|
||||
ChangeFeedPopRequest(rangeID, version, locations[i].first)));
|
||||
}
|
||||
}
|
||||
choose {
|
||||
when(wait(waitForAll(popRequests))) {}
|
||||
when(wait(delay(CLIENT_KNOBS->CHANGE_FEED_POP_TIMEOUT))) {
|
||||
|
|
|
@ -72,6 +72,8 @@ struct NetworkOptions {
|
|||
Optional<bool> logClientInfo;
|
||||
Reference<ReferencedObject<Standalone<VectorRef<ClientVersionRef>>>> supportedVersions;
|
||||
bool runLoopProfilingEnabled;
|
||||
bool primaryClient;
|
||||
std::map<std::string, KnobValue> knobs;
|
||||
|
||||
NetworkOptions();
|
||||
};
|
||||
|
|
|
@ -397,11 +397,10 @@ public:
|
|||
static inline Future<typename Req::Result> readWithConflictRangeAndFlatMap(ReadYourWritesTransaction* ryw,
|
||||
Req const& req,
|
||||
Snapshot snapshot) {
|
||||
if (ryw->options.readYourWritesDisabled) {
|
||||
// For now, getRangeAndFlatMap is only supported if transaction use snapshot isolation AND read-your-writes is
|
||||
// disabled.
|
||||
if (snapshot && ryw->options.readYourWritesDisabled) {
|
||||
return readWithConflictRangeThroughAndFlatMap(ryw, req, snapshot);
|
||||
} else if (snapshot && ryw->options.snapshotRywEnabled <= 0) {
|
||||
TEST(true); // readWithConflictRangeSnapshot not supported for getRangeAndFlatMap
|
||||
throw client_invalid_operation();
|
||||
}
|
||||
TEST(true); // readWithConflictRangeRYW not supported for getRangeAndFlatMap
|
||||
throw client_invalid_operation();
|
||||
|
|
|
@ -728,7 +728,7 @@ const KeyRef JSONSchemas::statusSchema = LiteralStringRef(R"statusSchema(
|
|||
"ssd",
|
||||
"ssd-1",
|
||||
"ssd-2",
|
||||
"ssd-redwood-experimental",
|
||||
"ssd-redwood-1-experimental",
|
||||
"ssd-rocksdb-experimental",
|
||||
"memory",
|
||||
"memory-1",
|
||||
|
@ -741,7 +741,7 @@ const KeyRef JSONSchemas::statusSchema = LiteralStringRef(R"statusSchema(
|
|||
"ssd",
|
||||
"ssd-1",
|
||||
"ssd-2",
|
||||
"ssd-redwood-experimental",
|
||||
"ssd-redwood-1-experimental",
|
||||
"ssd-rocksdb-experimental",
|
||||
"memory",
|
||||
"memory-1",
|
||||
|
|
|
@ -63,7 +63,7 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( MAX_MESSAGE_SIZE, std::max<int>(LOG_SYSTEM_PUSHED_DATA_BLOCK_SIZE, 1e5 + 2e4 + 1) + 8 ); // VALUE_SIZE_LIMIT + SYSTEM_KEY_SIZE_LIMIT + 9 bytes (4 bytes for length, 4 bytes for sequence number, and 1 byte for mutation type)
|
||||
init( TLOG_MESSAGE_BLOCK_BYTES, 10e6 );
|
||||
init( TLOG_MESSAGE_BLOCK_OVERHEAD_FACTOR, double(TLOG_MESSAGE_BLOCK_BYTES) / (TLOG_MESSAGE_BLOCK_BYTES - MAX_MESSAGE_SIZE) ); //1.0121466709838096006362758832473
|
||||
init( PEEK_TRACKER_EXPIRATION_TIME, 600 ); if( randomize && BUGGIFY ) PEEK_TRACKER_EXPIRATION_TIME = deterministicRandom()->coinflip() ? 0.1 : 120;
|
||||
init( PEEK_TRACKER_EXPIRATION_TIME, 600 ); if( randomize && BUGGIFY ) PEEK_TRACKER_EXPIRATION_TIME = 120; // Cannot be buggified lower without changing the following assert in LogSystemPeekCursor.actor.cpp: ASSERT_WE_THINK(e.code() == error_code_operation_obsolete || SERVER_KNOBS->PEEK_TRACKER_EXPIRATION_TIME < 10);
|
||||
init( PEEK_USING_STREAMING, true ); if( randomize && BUGGIFY ) PEEK_USING_STREAMING = false;
|
||||
init( PARALLEL_GET_MORE_REQUESTS, 32 ); if( randomize && BUGGIFY ) PARALLEL_GET_MORE_REQUESTS = 2;
|
||||
init( MULTI_CURSOR_PRE_FETCH_LIMIT, 10 );
|
||||
|
@ -771,8 +771,10 @@ void ServerKnobs::initialize(Randomize randomize, ClientKnobs* clientKnobs, IsSi
|
|||
init( REDWOOD_LAZY_CLEAR_MAX_PAGES, 1e6 );
|
||||
init( REDWOOD_REMAP_CLEANUP_WINDOW, 50 );
|
||||
init( REDWOOD_REMAP_CLEANUP_LAG, 0.1 );
|
||||
init( REDWOOD_PAGEFILE_GROWTH_SIZE_PAGES, 20000 ); if( randomize && BUGGIFY ) { REDWOOD_PAGEFILE_GROWTH_SIZE_PAGES = deterministicRandom()->randomInt(200, 1000); }
|
||||
init( REDWOOD_METRICS_INTERVAL, 5.0 );
|
||||
init( REDWOOD_HISTOGRAM_INTERVAL, 30.0 );
|
||||
init( REDWOOD_EVICT_UPDATED_PAGES, true ); if( randomize && BUGGIFY ) { REDWOOD_EVICT_UPDATED_PAGES = false; }
|
||||
|
||||
// Server request latency measurement
|
||||
init( LATENCY_SAMPLE_SIZE, 100000 );
|
||||
|
|
|
@ -724,8 +724,10 @@ public:
|
|||
int64_t REDWOOD_REMAP_CLEANUP_WINDOW; // Remap remover lag interval in which to coalesce page writes
|
||||
double REDWOOD_REMAP_CLEANUP_LAG; // Maximum allowed remap remover lag behind the cleanup window as a multiple of
|
||||
// the window size
|
||||
int REDWOOD_PAGEFILE_GROWTH_SIZE_PAGES; // Number of pages to grow page file by
|
||||
double REDWOOD_METRICS_INTERVAL;
|
||||
double REDWOOD_HISTOGRAM_INTERVAL;
|
||||
bool REDWOOD_EVICT_UPDATED_PAGES; // Whether to prioritize eviction of updated pages from cache.
|
||||
|
||||
// Server request latency measurement
|
||||
int LATENCY_SAMPLE_SIZE;
|
||||
|
|
|
@ -83,6 +83,7 @@ struct StorageServerInterface {
|
|||
RequestStream<struct ChangeFeedStreamRequest> changeFeedStream;
|
||||
RequestStream<struct OverlappingChangeFeedsRequest> overlappingChangeFeeds;
|
||||
RequestStream<struct ChangeFeedPopRequest> changeFeedPop;
|
||||
RequestStream<struct ChangeFeedVersionUpdateRequest> changeFeedVersionUpdate;
|
||||
|
||||
explicit StorageServerInterface(UID uid) : uniqueID(uid) {}
|
||||
StorageServerInterface() : uniqueID(deterministicRandom()->randomUniqueID()) {}
|
||||
|
@ -125,14 +126,16 @@ struct StorageServerInterface {
|
|||
RequestStream<struct SplitRangeRequest>(getValue.getEndpoint().getAdjustedEndpoint(12));
|
||||
getKeyValuesStream =
|
||||
RequestStream<struct GetKeyValuesStreamRequest>(getValue.getEndpoint().getAdjustedEndpoint(13));
|
||||
changeFeedStream =
|
||||
RequestStream<struct ChangeFeedStreamRequest>(getValue.getEndpoint().getAdjustedEndpoint(14));
|
||||
overlappingChangeFeeds =
|
||||
RequestStream<struct OverlappingChangeFeedsRequest>(getValue.getEndpoint().getAdjustedEndpoint(15));
|
||||
changeFeedPop =
|
||||
RequestStream<struct ChangeFeedPopRequest>(getValue.getEndpoint().getAdjustedEndpoint(16));
|
||||
getKeyValuesAndFlatMap =
|
||||
RequestStream<struct GetKeyValuesAndFlatMapRequest>(getValue.getEndpoint().getAdjustedEndpoint(17));
|
||||
RequestStream<struct GetKeyValuesAndFlatMapRequest>(getValue.getEndpoint().getAdjustedEndpoint(14));
|
||||
changeFeedStream =
|
||||
RequestStream<struct ChangeFeedStreamRequest>(getValue.getEndpoint().getAdjustedEndpoint(15));
|
||||
overlappingChangeFeeds =
|
||||
RequestStream<struct OverlappingChangeFeedsRequest>(getValue.getEndpoint().getAdjustedEndpoint(16));
|
||||
changeFeedPop =
|
||||
RequestStream<struct ChangeFeedPopRequest>(getValue.getEndpoint().getAdjustedEndpoint(17));
|
||||
changeFeedVersionUpdate = RequestStream<struct ChangeFeedVersionUpdateRequest>(
|
||||
getValue.getEndpoint().getAdjustedEndpoint(18));
|
||||
}
|
||||
} else {
|
||||
ASSERT(Ar::isDeserializing);
|
||||
|
@ -175,10 +178,11 @@ struct StorageServerInterface {
|
|||
streams.push_back(getReadHotRanges.getReceiver());
|
||||
streams.push_back(getRangeSplitPoints.getReceiver());
|
||||
streams.push_back(getKeyValuesStream.getReceiver(TaskPriority::LoadBalancedEndpoint));
|
||||
streams.push_back(getKeyValuesAndFlatMap.getReceiver(TaskPriority::LoadBalancedEndpoint));
|
||||
streams.push_back(changeFeedStream.getReceiver());
|
||||
streams.push_back(overlappingChangeFeeds.getReceiver());
|
||||
streams.push_back(changeFeedPop.getReceiver());
|
||||
streams.push_back(getKeyValuesAndFlatMap.getReceiver(TaskPriority::LoadBalancedEndpoint));
|
||||
streams.push_back(changeFeedVersionUpdate.getReceiver());
|
||||
FlowTransport::transport().addEndpoints(streams);
|
||||
}
|
||||
};
|
||||
|
@ -684,6 +688,8 @@ struct ChangeFeedStreamReply : public ReplyPromiseStreamReply {
|
|||
constexpr static FileIdentifier file_identifier = 1783066;
|
||||
Arena arena;
|
||||
VectorRef<MutationsAndVersionRef> mutations;
|
||||
bool atLatestVersion = false;
|
||||
Version minStreamVersion = invalidVersion;
|
||||
|
||||
ChangeFeedStreamReply() {}
|
||||
|
||||
|
@ -691,7 +697,13 @@ struct ChangeFeedStreamReply : public ReplyPromiseStreamReply {
|
|||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, ReplyPromiseStreamReply::acknowledgeToken, ReplyPromiseStreamReply::sequence, mutations, arena);
|
||||
serializer(ar,
|
||||
ReplyPromiseStreamReply::acknowledgeToken,
|
||||
ReplyPromiseStreamReply::sequence,
|
||||
mutations,
|
||||
atLatestVersion,
|
||||
minStreamVersion,
|
||||
arena);
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -779,6 +791,33 @@ struct OverlappingChangeFeedsRequest {
|
|||
}
|
||||
};
|
||||
|
||||
struct ChangeFeedVersionUpdateReply {
|
||||
constexpr static FileIdentifier file_identifier = 11815134;
|
||||
Version version = 0;
|
||||
|
||||
ChangeFeedVersionUpdateReply() {}
|
||||
explicit ChangeFeedVersionUpdateReply(Version version) : version(version) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, version);
|
||||
}
|
||||
};
|
||||
|
||||
struct ChangeFeedVersionUpdateRequest {
|
||||
constexpr static FileIdentifier file_identifier = 6795746;
|
||||
Version minVersion;
|
||||
ReplyPromise<ChangeFeedVersionUpdateReply> reply;
|
||||
|
||||
ChangeFeedVersionUpdateRequest() {}
|
||||
explicit ChangeFeedVersionUpdateRequest(Version minVersion) : minVersion(minVersion) {}
|
||||
|
||||
template <class Ar>
|
||||
void serialize(Ar& ar) {
|
||||
serializer(ar, minVersion, reply);
|
||||
}
|
||||
};
|
||||
|
||||
struct GetStorageMetricsReply {
|
||||
constexpr static FileIdentifier file_identifier = 15491478;
|
||||
StorageMetrics load;
|
||||
|
|
|
@ -213,6 +213,8 @@ const KeyRangeRef writeConflictRangeKeysRange =
|
|||
KeyRangeRef(LiteralStringRef("\xff\xff/transaction/write_conflict_range/"),
|
||||
LiteralStringRef("\xff\xff/transaction/write_conflict_range/\xff\xff"));
|
||||
|
||||
const KeyRef clusterIdKey = LiteralStringRef("\xff/clusterId");
|
||||
|
||||
// "\xff/cacheServer/[[UID]] := StorageServerInterface"
|
||||
const KeyRangeRef storageCacheServerKeys(LiteralStringRef("\xff/cacheServer/"), LiteralStringRef("\xff/cacheServer0"));
|
||||
const KeyRef storageCacheServersPrefix = storageCacheServerKeys.begin;
|
||||
|
|
|
@ -67,6 +67,8 @@ void decodeKeyServersValue(std::map<Tag, UID> const& tag_uid,
|
|||
std::vector<UID>& src,
|
||||
std::vector<UID>& dest);
|
||||
|
||||
extern const KeyRef clusterIdKey;
|
||||
|
||||
// "\xff/storageCacheServer/[[UID]] := StorageServerInterface"
|
||||
// This will be added by the cache server on initialization and removed by DD
|
||||
// TODO[mpilman]: We will need a way to map uint16_t ids to UIDs in a future
|
||||
|
|
|
@ -15,7 +15,7 @@
|
|||
|
||||
//#define BOOST_SPIRIT_THREADSAFE // uncomment for multithreaded use, requires linking to boost.thread
|
||||
|
||||
#include <boost/bind.hpp>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#include <boost/function.hpp>
|
||||
#include <boost/version.hpp>
|
||||
|
||||
|
@ -366,18 +366,25 @@ public:
|
|||
typedef boost::function<void(boost::int64_t)> Int_action;
|
||||
typedef boost::function<void(boost::uint64_t)> Uint64_action;
|
||||
|
||||
Char_action begin_obj(boost::bind(&Semantic_actions_t::begin_obj, &self.actions_, _1));
|
||||
Char_action end_obj(boost::bind(&Semantic_actions_t::end_obj, &self.actions_, _1));
|
||||
Char_action begin_array(boost::bind(&Semantic_actions_t::begin_array, &self.actions_, _1));
|
||||
Char_action end_array(boost::bind(&Semantic_actions_t::end_array, &self.actions_, _1));
|
||||
Str_action new_name(boost::bind(&Semantic_actions_t::new_name, &self.actions_, _1, _2));
|
||||
Str_action new_str(boost::bind(&Semantic_actions_t::new_str, &self.actions_, _1, _2));
|
||||
Str_action new_true(boost::bind(&Semantic_actions_t::new_true, &self.actions_, _1, _2));
|
||||
Str_action new_false(boost::bind(&Semantic_actions_t::new_false, &self.actions_, _1, _2));
|
||||
Str_action new_null(boost::bind(&Semantic_actions_t::new_null, &self.actions_, _1, _2));
|
||||
Real_action new_real(boost::bind(&Semantic_actions_t::new_real, &self.actions_, _1));
|
||||
Int_action new_int(boost::bind(&Semantic_actions_t::new_int, &self.actions_, _1));
|
||||
Uint64_action new_uint64(boost::bind(&Semantic_actions_t::new_uint64, &self.actions_, _1));
|
||||
Char_action begin_obj(boost::bind(&Semantic_actions_t::begin_obj, &self.actions_, boost::placeholders::_1));
|
||||
Char_action end_obj(boost::bind(&Semantic_actions_t::end_obj, &self.actions_, boost::placeholders::_1));
|
||||
Char_action begin_array(
|
||||
boost::bind(&Semantic_actions_t::begin_array, &self.actions_, boost::placeholders::_1));
|
||||
Char_action end_array(boost::bind(&Semantic_actions_t::end_array, &self.actions_, boost::placeholders::_1));
|
||||
Str_action new_name(boost::bind(
|
||||
&Semantic_actions_t::new_name, &self.actions_, boost::placeholders::_1, boost::placeholders::_2));
|
||||
Str_action new_str(boost::bind(
|
||||
&Semantic_actions_t::new_str, &self.actions_, boost::placeholders::_1, boost::placeholders::_2));
|
||||
Str_action new_true(boost::bind(
|
||||
&Semantic_actions_t::new_true, &self.actions_, boost::placeholders::_1, boost::placeholders::_2));
|
||||
Str_action new_false(boost::bind(
|
||||
&Semantic_actions_t::new_false, &self.actions_, boost::placeholders::_1, boost::placeholders::_2));
|
||||
Str_action new_null(boost::bind(
|
||||
&Semantic_actions_t::new_null, &self.actions_, boost::placeholders::_1, boost::placeholders::_2));
|
||||
Real_action new_real(boost::bind(&Semantic_actions_t::new_real, &self.actions_, boost::placeholders::_1));
|
||||
Int_action new_int(boost::bind(&Semantic_actions_t::new_int, &self.actions_, boost::placeholders::_1));
|
||||
Uint64_action new_uint64(
|
||||
boost::bind(&Semantic_actions_t::new_uint64, &self.actions_, boost::placeholders::_1));
|
||||
|
||||
// actual grammer
|
||||
|
||||
|
|
|
@ -5,12 +5,12 @@ To test this, run the following commands from the root of the FoundationDB
|
|||
repository:
|
||||
|
||||
```bash
|
||||
docker build -t foundationdb/foundationdb-kubernetes:6.3.13-local --build-arg FDB_VERSION=6.3.13 --build-arg FDB_LIBRARY_VERSIONS="6.3.13 6.2.30 6.1.13" -f packaging/docker/kubernetes/Dockerfile .
|
||||
docker build -t foundationdb/foundationdb-kubernetes:6.3.15-local --build-arg FDB_VERSION=6.3.15 --build-arg FDB_LIBRARY_VERSIONS="6.3.15 6.2.30 6.1.13" -f packaging/docker/kubernetes/Dockerfile .
|
||||
docker build -t foundationdb/foundationdb-kubernetes:6.3.13-local --target fdb-kubernetes-monitor --build-arg FDB_VERSION=6.3.13 --build-arg FDB_LIBRARY_VERSIONS="6.3.13 6.2.30 6.1.13" -f packaging/docker/Dockerfile .
|
||||
docker build -t foundationdb/foundationdb-kubernetes:6.3.15-local --target fdb-kubernetes-monitor --build-arg FDB_VERSION=6.3.15 --build-arg FDB_LIBRARY_VERSIONS="6.3.15 6.2.30 6.1.13" -f packaging/docker/Dockerfile .
|
||||
kubectl apply -f packaging/docker/kubernetes/test_config.yaml
|
||||
# Wait for the pods to become ready
|
||||
ips=$(kubectl get pod -l app=fdb-kubernetes-example -o json | jq -j '[[.items|.[]|select(.status.podIP!="")]|limit(3;.[])|.status.podIP+":4501"]|join(",")')
|
||||
sed -e "s/fdb.cluster: \"\"/fdb.cluster: \"test:test@$ips\"/" -e "s/\"serverCount\": 0/\"serverCount\": 1/" packaging/docker/kubernetes/test_config.yaml | kubectl apply -f -
|
||||
sed -e "s/fdb.cluster: \"\"/fdb.cluster: \"test:test@$ips\"/" -e "s/\"runProcesses\": false/\"runProcesses\": true/" packaging/docker/kubernetes/test_config.yaml | kubectl apply -f -
|
||||
kubectl get pod -l app=fdb-kubernetes-example -o name | xargs -I {} kubectl annotate {} foundationdb.org/outdated-config-map-seen=$(date +%s) --overwrite
|
||||
# Watch the logs for the fdb-kubernetes-example pods to confirm that they have launched the fdbserver processes.
|
||||
kubectl exec -it sts/fdb-kubernetes-example -- fdbcli --exec "configure new double ssd"
|
||||
|
@ -21,7 +21,7 @@ This will set up a cluster in your Kubernetes environment using a statefulset, t
|
|||
You can then make changes to the data in the config map and update the fdbserver processes:
|
||||
|
||||
```bash
|
||||
sed -e "s/fdb.cluster: \"\"/fdb.cluster: \"test:test@$ips\"/" -e "s/\"serverCount\": 0/\"serverCount\": 1/" packaging/docker/kubernetes/test_config.yaml | kubectl apply -f -
|
||||
sed -e "s/fdb.cluster: \"\"/fdb.cluster: \"test:test@$ips\"/" -e "s/\"runProcesses\": false/\"runProcesses\": true/" packaging/docker/kubernetes/test_config.yaml | kubectl apply -f -
|
||||
|
||||
# You can apply an annotation to speed up the propagation of config
|
||||
kubectl get pod -l app=fdb-kubernetes-example -o name | xargs -I {} kubectl annotate {} foundationdb.org/outdated-config-map-seen=$(date +%s) --overwrite
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
{
|
||||
"version": "6.3.15",
|
||||
"serverCount": 1,
|
||||
"arguments": [
|
||||
{"value": "--cluster_file"},
|
||||
{"value": ".testdata/fdb.cluster"},
|
|
@ -17,7 +17,7 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
@ -31,8 +31,10 @@ type ProcessConfiguration struct {
|
|||
// Version provides the version of FoundationDB the process should run.
|
||||
Version string `json:"version"`
|
||||
|
||||
// ServerCount defines the number of processes to start.
|
||||
ServerCount int `json:"serverCount,omitempty"`
|
||||
// RunServers defines whether we should run the server processes.
|
||||
// This defaults to true, but you can set it to false to prevent starting
|
||||
// new fdbserver processes.
|
||||
RunServers *bool `json:"runServers,omitempty"`
|
||||
|
||||
// BinaryPath provides the path to the binary to launch.
|
||||
BinaryPath string `json:"-"`
|
|
@ -17,7 +17,7 @@
|
|||
// limitations under the License.
|
||||
//
|
||||
|
||||
package main
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
|
@ -24,8 +24,10 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"path"
|
||||
"strconv"
|
||||
|
||||
"github.com/apple/foundationdb/fdbkubernetesmonitor/api"
|
||||
"github.com/go-logr/logr"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
@ -67,7 +69,7 @@ type PodClient struct {
|
|||
}
|
||||
|
||||
// CreatePodClient creates a new client for working with the pod object.
|
||||
func CreatePodClient() (*PodClient, error) {
|
||||
func CreatePodClient(logger logr.Logger) (*PodClient, error) {
|
||||
config, err := rest.InClusterConfig()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -83,7 +85,7 @@ func CreatePodClient() (*PodClient, error) {
|
|||
return nil, err
|
||||
}
|
||||
|
||||
podClient := &PodClient{podApi: podApi, pod: pod, TimestampFeed: make(chan int64, 10)}
|
||||
podClient := &PodClient{podApi: podApi, pod: pod, TimestampFeed: make(chan int64, 10), Logger: logger}
|
||||
err = podClient.watchPod()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -94,7 +96,7 @@ func CreatePodClient() (*PodClient, error) {
|
|||
|
||||
// retrieveEnvironmentVariables extracts the environment variables we have for
|
||||
// an argument into a map.
|
||||
func retrieveEnvironmentVariables(argument Argument, target map[string]string) {
|
||||
func retrieveEnvironmentVariables(argument api.Argument, target map[string]string) {
|
||||
if argument.Source != "" {
|
||||
target[argument.Source] = os.Getenv(argument.Source)
|
||||
}
|
||||
|
@ -112,6 +114,7 @@ func (client *PodClient) UpdateAnnotations(monitor *Monitor) error {
|
|||
for _, argument := range monitor.ActiveConfiguration.Arguments {
|
||||
retrieveEnvironmentVariables(argument, environment)
|
||||
}
|
||||
environment["BINARY_DIR"] = path.Dir(monitor.ActiveConfiguration.BinaryPath)
|
||||
jsonEnvironment, err := json.Marshal(environment)
|
||||
if err != nil {
|
||||
return err
|
||||
|
@ -180,7 +183,7 @@ func (client *PodClient) processPodUpdate(pod *corev1.Pod) {
|
|||
}
|
||||
timestamp, err := strconv.ParseInt(annotation, 10, 64)
|
||||
if err != nil {
|
||||
client.Logger.Error(err, "Error parsing annotation", "key", OutdatedConfigMapAnnotation, "rawAnnotation", annotation, err)
|
||||
client.Logger.Error(err, "Error parsing annotation", "key", OutdatedConfigMapAnnotation, "rawAnnotation", annotation)
|
||||
return
|
||||
}
|
||||
|
||||
|
|
|
@ -51,6 +51,7 @@ var (
|
|||
mainContainerVersion string
|
||||
currentContainerVersion string
|
||||
additionalEnvFile string
|
||||
processCount int
|
||||
)
|
||||
|
||||
type executionMode string
|
||||
|
@ -78,6 +79,7 @@ func main() {
|
|||
pflag.StringArrayVar(&requiredCopyFiles, "require-not-empty", nil, "When copying this file, exit with an error if the file is empty")
|
||||
pflag.StringVar(&mainContainerVersion, "main-container-version", "", "For sidecar mode, this specifies the version of the main container. If this is equal to the current container version, no files will be copied")
|
||||
pflag.StringVar(&additionalEnvFile, "additional-env-file", "", "A file with additional environment variables to use when interpreting the monitor configuration")
|
||||
pflag.IntVar(&processCount, "process-count", 1, "The number of processes to start")
|
||||
pflag.Parse()
|
||||
|
||||
zapConfig := zap.NewProductionConfig()
|
||||
|
@ -110,7 +112,7 @@ func main() {
|
|||
logger.Error(err, "Error loading additional environment")
|
||||
os.Exit(1)
|
||||
}
|
||||
StartMonitor(logger, fmt.Sprintf("%s/%s", inputDir, monitorConfFile), customEnvironment)
|
||||
StartMonitor(logger, fmt.Sprintf("%s/%s", inputDir, monitorConfFile), customEnvironment, processCount)
|
||||
case executionModeInit:
|
||||
err = CopyFiles(logger, outputDir, copyDetails, requiredCopies)
|
||||
if err != nil {
|
||||
|
@ -124,9 +126,10 @@ func main() {
|
|||
logger.Error(err, "Error copying files")
|
||||
os.Exit(1)
|
||||
}
|
||||
done := make(chan bool)
|
||||
<-done
|
||||
}
|
||||
logger.Info("Waiting for process to be terminated")
|
||||
done := make(chan bool)
|
||||
<-done
|
||||
default:
|
||||
logger.Error(nil, "Unknown execution mode", "mode", mode)
|
||||
os.Exit(1)
|
||||
|
|
|
@ -32,6 +32,7 @@ import (
|
|||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/apple/foundationdb/fdbkubernetesmonitor/api"
|
||||
"github.com/fsnotify/fsnotify"
|
||||
"github.com/go-logr/logr"
|
||||
)
|
||||
|
@ -52,7 +53,7 @@ type Monitor struct {
|
|||
CustomEnvironment map[string]string
|
||||
|
||||
// ActiveConfiguration defines the active process configuration.
|
||||
ActiveConfiguration *ProcessConfiguration
|
||||
ActiveConfiguration *api.ProcessConfiguration
|
||||
|
||||
// ActiveConfigurationBytes defines the source data for the active process
|
||||
// configuration.
|
||||
|
@ -62,6 +63,9 @@ type Monitor struct {
|
|||
// configuration file.
|
||||
LastConfigurationTime time.Time
|
||||
|
||||
// ProcessCount defines how many processes the
|
||||
ProcessCount int
|
||||
|
||||
// ProcessIDs stores the PIDs of the processes that are running. A PID of
|
||||
// zero will indicate that a process does not have a run loop. A PID of -1
|
||||
// will indicate that a process has a run loop but is not currently running
|
||||
|
@ -82,8 +86,8 @@ type Monitor struct {
|
|||
}
|
||||
|
||||
// StartMonitor starts the monitor loop.
|
||||
func StartMonitor(logger logr.Logger, configFile string, customEnvironment map[string]string) {
|
||||
podClient, err := CreatePodClient()
|
||||
func StartMonitor(logger logr.Logger, configFile string, customEnvironment map[string]string, processCount int) {
|
||||
podClient, err := CreatePodClient(logger)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
@ -93,6 +97,7 @@ func StartMonitor(logger logr.Logger, configFile string, customEnvironment map[s
|
|||
PodClient: podClient,
|
||||
Logger: logger,
|
||||
CustomEnvironment: customEnvironment,
|
||||
ProcessCount: processCount,
|
||||
}
|
||||
|
||||
go func() { monitor.WatchPodTimestamps() }()
|
||||
|
@ -107,7 +112,7 @@ func (monitor *Monitor) LoadConfiguration() {
|
|||
return
|
||||
}
|
||||
defer file.Close()
|
||||
configuration := &ProcessConfiguration{}
|
||||
configuration := &api.ProcessConfiguration{}
|
||||
configurationBytes, err := io.ReadAll(file)
|
||||
if err != nil {
|
||||
monitor.Logger.Error(err, "Error reading monitor configuration", "monitorConfigPath", monitor.ConfigFile)
|
||||
|
@ -154,15 +159,15 @@ func checkOwnerExecutable(path string) error {
|
|||
|
||||
// acceptConfiguration is called when the monitor process parses and accepts
|
||||
// a configuration from the local config file.
|
||||
func (monitor *Monitor) acceptConfiguration(configuration *ProcessConfiguration, configurationBytes []byte) {
|
||||
func (monitor *Monitor) acceptConfiguration(configuration *api.ProcessConfiguration, configurationBytes []byte) {
|
||||
monitor.Mutex.Lock()
|
||||
defer monitor.Mutex.Unlock()
|
||||
monitor.Logger.Info("Received new configuration file", "configuration", configuration)
|
||||
|
||||
if monitor.ProcessIDs == nil {
|
||||
monitor.ProcessIDs = make([]int, configuration.ServerCount+1)
|
||||
monitor.ProcessIDs = make([]int, monitor.ProcessCount+1)
|
||||
} else {
|
||||
for len(monitor.ProcessIDs) <= configuration.ServerCount {
|
||||
for len(monitor.ProcessIDs) <= monitor.ProcessCount {
|
||||
monitor.ProcessIDs = append(monitor.ProcessIDs, 0)
|
||||
}
|
||||
}
|
||||
|
@ -171,7 +176,7 @@ func (monitor *Monitor) acceptConfiguration(configuration *ProcessConfiguration,
|
|||
monitor.ActiveConfigurationBytes = configurationBytes
|
||||
monitor.LastConfigurationTime = time.Now()
|
||||
|
||||
for processNumber := 1; processNumber <= configuration.ServerCount; processNumber++ {
|
||||
for processNumber := 1; processNumber <= monitor.ProcessCount; processNumber++ {
|
||||
if monitor.ProcessIDs[processNumber] == 0 {
|
||||
monitor.ProcessIDs[processNumber] = -1
|
||||
tempNumber := processNumber
|
||||
|
@ -284,7 +289,8 @@ func (monitor *Monitor) checkProcessRequired(processNumber int) bool {
|
|||
monitor.Mutex.Lock()
|
||||
defer monitor.Mutex.Unlock()
|
||||
logger := monitor.Logger.WithValues("processNumber", processNumber, "area", "checkProcessRequired")
|
||||
if monitor.ActiveConfiguration.ServerCount < processNumber {
|
||||
runProcesses := monitor.ActiveConfiguration.RunServers
|
||||
if monitor.ProcessCount < processNumber || (runProcesses != nil && !*runProcesses) {
|
||||
logger.Info("Terminating run loop")
|
||||
monitor.ProcessIDs[processNumber] = 0
|
||||
return false
|
||||
|
|
|
@ -0,0 +1,155 @@
|
|||
/*
|
||||
* AsyncFileChaos.actor.h
|
||||
*
|
||||
* This source file is part of the FoundationDB open source project
|
||||
*
|
||||
* Copyright 2013-2018 Apple Inc. and the FoundationDB project authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "flow/flow.h"
|
||||
#include "flow/serialize.h"
|
||||
#include "flow/genericactors.actor.h"
|
||||
#include "fdbrpc/IAsyncFile.h"
|
||||
#include "flow/network.h"
|
||||
#include "flow/ActorCollection.h"
|
||||
#include "flow/actorcompiler.h"
|
||||
|
||||
// template <class AsyncFileType>
|
||||
class AsyncFileChaos final : public IAsyncFile, public ReferenceCounted<AsyncFileChaos> {
|
||||
private:
|
||||
Reference<IAsyncFile> file;
|
||||
bool enabled;
|
||||
|
||||
public:
|
||||
explicit AsyncFileChaos(Reference<IAsyncFile> file) : file(file) {
|
||||
// We only allow chaos events on storage files
|
||||
enabled = (file->getFilename().find("storage-") != std::string::npos);
|
||||
}
|
||||
|
||||
void addref() override { ReferenceCounted<AsyncFileChaos>::addref(); }
|
||||
void delref() override { ReferenceCounted<AsyncFileChaos>::delref(); }
|
||||
|
||||
double getDelay() const {
|
||||
double delayFor = 0.0;
|
||||
if (!enabled)
|
||||
return delayFor;
|
||||
|
||||
auto res = g_network->global(INetwork::enDiskFailureInjector);
|
||||
if (res) {
|
||||
DiskFailureInjector* delayInjector = static_cast<DiskFailureInjector*>(res);
|
||||
delayFor = delayInjector->getDiskDelay();
|
||||
|
||||
// increment the metric for disk delays
|
||||
if (delayFor > 0.0) {
|
||||
auto res = g_network->global(INetwork::enChaosMetrics);
|
||||
if (res) {
|
||||
ChaosMetrics* chaosMetrics = static_cast<ChaosMetrics*>(res);
|
||||
chaosMetrics->diskDelays++;
|
||||
}
|
||||
}
|
||||
}
|
||||
return delayFor;
|
||||
}
|
||||
|
||||
Future<int> read(void* data, int length, int64_t offset) override {
|
||||
double diskDelay = getDelay();
|
||||
|
||||
if (diskDelay == 0.0)
|
||||
return file->read(data, length, offset);
|
||||
|
||||
// Wait for diskDelay before submitting the I/O
|
||||
// Template types are being provided explicitly because they can't be automatically deduced for some reason.
|
||||
return mapAsync<Void, std::function<Future<int>(Void)>, int>(
|
||||
delay(diskDelay), [=](Void _) -> Future<int> { return file->read(data, length, offset); });
|
||||
}
|
||||
|
||||
Future<Void> write(void const* data, int length, int64_t offset) override {
|
||||
Arena arena;
|
||||
char* pdata = nullptr;
|
||||
|
||||
// Check if a bit flip event was injected, if so, copy the buffer contents
|
||||
// with a random bit flipped in a new buffer and use that for the write
|
||||
auto res = g_network->global(INetwork::enBitFlipper);
|
||||
if (enabled && res) {
|
||||
auto bitFlipPercentage = static_cast<BitFlipper*>(res)->getBitFlipPercentage();
|
||||
if (bitFlipPercentage > 0.0) {
|
||||
auto bitFlipProb = bitFlipPercentage / 100;
|
||||
if (deterministicRandom()->random01() < bitFlipProb) {
|
||||
pdata = (char*)arena.allocate4kAlignedBuffer(length);
|
||||
memcpy(pdata, data, length);
|
||||
// flip a random bit in the copied buffer
|
||||
pdata[deterministicRandom()->randomInt(0, length)] ^= (1 << deterministicRandom()->randomInt(0, 8));
|
||||
|
||||
// increment the metric for bit flips
|
||||
auto res = g_network->global(INetwork::enChaosMetrics);
|
||||
if (res) {
|
||||
ChaosMetrics* chaosMetrics = static_cast<ChaosMetrics*>(res);
|
||||
chaosMetrics->bitFlips++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
double diskDelay = getDelay();
|
||||
if (diskDelay == 0.0) {
|
||||
if (pdata)
|
||||
return holdWhile(arena, file->write(pdata, length, offset));
|
||||
|
||||
return file->write(data, length, offset);
|
||||
}
|
||||
|
||||
// Wait for diskDelay before submitting the I/O
|
||||
return mapAsync<Void, std::function<Future<Void>(Void)>, Void>(delay(diskDelay), [=](Void _) -> Future<Void> {
|
||||
if (pdata)
|
||||
return holdWhile(arena, file->write(pdata, length, offset));
|
||||
|
||||
return file->write(data, length, offset);
|
||||
});
|
||||
}
|
||||
|
||||
Future<Void> truncate(int64_t size) override {
|
||||
double diskDelay = getDelay();
|
||||
if (diskDelay == 0.0)
|
||||
return file->truncate(size);
|
||||
|
||||
// Wait for diskDelay before submitting the I/O
|
||||
return mapAsync<Void, std::function<Future<Void>(Void)>, Void>(
|
||||
delay(diskDelay), [=](Void _) -> Future<Void> { return file->truncate(size); });
|
||||
}
|
||||
|
||||
Future<Void> sync() override {
|
||||
double diskDelay = getDelay();
|
||||
if (diskDelay == 0.0)
|
||||
return file->sync();
|
||||
|
||||
// Wait for diskDelay before submitting the I/O
|
||||
return mapAsync<Void, std::function<Future<Void>(Void)>, Void>(
|
||||
delay(diskDelay), [=](Void _) -> Future<Void> { return file->sync(); });
|
||||
}
|
||||
|
||||
Future<int64_t> size() const override {
|
||||
double diskDelay = getDelay();
|
||||
if (diskDelay == 0.0)
|
||||
return file->size();
|
||||
|
||||
// Wait for diskDelay before submitting the I/O
|
||||
return mapAsync<Void, std::function<Future<int64_t>(Void)>, int64_t>(
|
||||
delay(diskDelay), [=](Void _) -> Future<int64_t> { return file->size(); });
|
||||
}
|
||||
|
||||
int64_t debugFD() const override { return file->debugFD(); }
|
||||
|
||||
std::string getFilename() const override { return file->getFilename(); }
|
||||
};
|
|
@ -195,7 +195,6 @@ public:
|
|||
|
||||
void addref() override { ReferenceCounted<AsyncFileKAIO>::addref(); }
|
||||
void delref() override { ReferenceCounted<AsyncFileKAIO>::delref(); }
|
||||
|
||||
Future<int> read(void* data, int length, int64_t offset) override {
|
||||
++countFileLogicalReads;
|
||||
++countLogicalReads;
|
||||
|
|
|
@ -439,7 +439,6 @@ private:
|
|||
try {
|
||||
state int rep = wait(self->onRead(self, data, length, offset));
|
||||
wait(g_simulator.onProcess(currentProcess, currentTaskID));
|
||||
|
||||
return rep;
|
||||
} catch (Error& e) {
|
||||
state Error err = e;
|
||||
|
|
|
@ -33,6 +33,7 @@
|
|||
#define FLOW_ASYNCFILEWINASIO_ACTOR_H
|
||||
|
||||
#include <Windows.h>
|
||||
#include <boost/bind/bind.hpp>
|
||||
#undef min
|
||||
#undef max
|
||||
|
||||
|
|
|
@ -86,8 +86,23 @@ if(COMPILE_EIO)
|
|||
target_link_libraries(fdbrpc PRIVATE eio)
|
||||
target_link_libraries(fdbrpc_sampling PRIVATE eio)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_library(coro STATIC libcoroutine/Common.c libcoroutine/Coro.c)
|
||||
target_link_libraries(fdbrpc PRIVATE coro)
|
||||
target_link_libraries(fdbrpc_sampling PRIVATE coro)
|
||||
|
||||
set(CORO_SRCS libcoroutine/Common.c libcoroutine/Coro.c)
|
||||
if(APPLE)
|
||||
list(APPEND CORO_SRCS libcoroutine/asm.S)
|
||||
endif()
|
||||
if(NOT WIN32)
|
||||
list(APPEND CORO_SRCS libcoroutine/context.c)
|
||||
endif()
|
||||
add_library(coro STATIC ${CORO_SRCS})
|
||||
if(WIN32)
|
||||
target_compile_definitions(coro PRIVATE USE_FIBERS)
|
||||
else()
|
||||
target_compile_definitions(coro PRIVATE USE_UCONTEXT)
|
||||
target_compile_options(coro BEFORE PRIVATE -w) # disable warnings for third party
|
||||
endif()
|
||||
if(USE_VALGRIND)
|
||||
target_link_libraries(coro PUBLIC Valgrind)
|
||||
endif()
|
||||
target_link_libraries(fdbrpc PRIVATE coro)
|
||||
target_link_libraries(fdbrpc_sampling PRIVATE coro)
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue