Make FDB_USE_CSHARP_TOOLS authoritative and consistently honored across the build (#12615)

* Make FDB_USE_CSHARP_TOOLS authoritative across the build

Historically, FDB_USE_CSHARP_TOOLS acted as a preference hint, and parts of the build could still probe for or assume the presence of C# tooling even when it was disabled.

This change makes the option authoritative and consistently honored across the build system. C# tooling is now used only when explicitly enabled and available, and all downstream assumptions are gated accordingly.

The default configuration and tool preference order remain unchanged.

* cmake files changes

* WIP: tmp test

* Honor reviewer feedback on C# toolchain detection and actor comparison

- Stop assuming C# tooling availability on Windows; explicitly probe for
  .NET using find_program.
- Prefer .NET over mono on all platforms, with mono used only as a fallback.
- Fail explicitly when FDB_USE_CSHARP_TOOLS=ON but no C# toolchain is found.
- Preserve Python/C# actor output comparison when C# tooling is available,
  skipping it only when C# is explicitly disabled or unavailable.
- Simplify Python argument parsing and remove unnecessary textwrap usage.
This commit is contained in:
VXTLS
2026-01-06 23:55:33 -05:00
committed by GitHub
parent 2f91f6338c
commit 31d7eadd52
9 changed files with 662 additions and 114 deletions

View File

@@ -169,24 +169,68 @@ endif()
include(utils)
# Flow and other tools are written in C# - so we need that dependency
include(EnableCsharp)
# First thing we need is the actor compiler
option(
WITH_CSHARP
"Prefer C# build tools (actor compiler, coverage tool, vexillographer) when a toolchain is available"
ON)
set(FDB_USE_CSHARP_TOOLS_EXPLICIT FALSE)
if(DEFINED FDB_USE_CSHARP_TOOLS)
set(FDB_USE_CSHARP_TOOLS_EXPLICIT TRUE)
endif()
if(NOT DEFINED FDB_USE_CSHARP_TOOLS)
set(FDB_USE_CSHARP_TOOLS ${WITH_CSHARP})
else()
set(WITH_CSHARP ${FDB_USE_CSHARP_TOOLS})
endif()
set(CSHARP_TOOLCHAIN_FOUND FALSE)
set(COVERAGETOOL_AVAILABLE FALSE)
if(FDB_USE_CSHARP_TOOLS)
if(WIN32)
find_program(dotnet_EXECUTABLE NAMES dotnet dotnet.exe)
if(dotnet_EXECUTABLE)
set(CSHARP_TOOLCHAIN_FOUND TRUE)
endif()
else()
find_package(dotnet)
if(dotnet_FOUND)
set(CSHARP_TOOLCHAIN_FOUND TRUE)
endif()
endif()
if(NOT CSHARP_TOOLCHAIN_FOUND)
find_package(mono)
if(mono_FOUND)
set(CSHARP_TOOLCHAIN_FOUND TRUE)
set(CSHARP_USE_MONO TRUE)
endif()
endif()
endif()
if(FDB_USE_CSHARP_TOOLS_EXPLICIT AND FDB_USE_CSHARP_TOOLS
AND NOT CSHARP_TOOLCHAIN_FOUND)
message(
FATAL_ERROR
"FDB_USE_CSHARP_TOOLS is enabled, but CSHARP_TOOLCHAIN_FOUND is FALSE. Install .NET (dotnet) or Mono, or set WITH_CSHARP=OFF.")
endif()
# First thing we need is the actor compiler - and to compile and run the actor
# compiler, we need mono
include(CompileActorCompiler)
include(CompileCoverageTool)
if(FDB_USE_CSHARP_TOOLS AND CSHARP_TOOLCHAIN_FOUND)
include(CompileCoverageTool)
set(COVERAGETOOL_AVAILABLE TRUE)
else()
message(STATUS "C# tooling disabled or not found; skipping coverage tool build")
endif()
# Vexilographer generation is configured inside fdbclient
include(CompileVexillographer)
# with the actor compiler, we can now make the flow commands available
include(FlowCommands)
###############################################################################
# Vexilographer
###############################################################################
include(CompileVexillographer)
###############################################################################
# Generate config file
###############################################################################
@@ -266,7 +310,7 @@ if(WITH_PYTHON AND WITH_C_BINDING)
endif()
if(WITH_DOCUMENTATION)
add_subdirectory(documentation)
if(BUILD_JAVA_BINDING)
if(BUILD_JAVA_BINDING AND TARGET CopyJavadoc)
add_dependencies(html CopyJavadoc)
endif()
endif()

View File

@@ -225,29 +225,33 @@ function(stage_correctness_package)
endforeach()
endforeach()
list(APPEND package_files ${STAGE_OUT_DIR}/bin/fdbserver
${STAGE_OUT_DIR}/bin/coverage.fdbserver.xml
set(package_files ${STAGE_OUT_DIR}/bin/fdbserver
${STAGE_OUT_DIR}/CMakeCache.txt)
set(package_dependencies ${CMAKE_BINARY_DIR}/CMakeCache.txt
${CMAKE_BINARY_DIR}/packages/bin/fdbserver)
set(copy_sources ${CMAKE_BINARY_DIR}/packages/bin/fdbserver)
if(COVERAGETOOL_AVAILABLE)
list(APPEND package_files ${STAGE_OUT_DIR}/bin/coverage.fdbserver.xml
${STAGE_OUT_DIR}/bin/coverage.fdbclient.xml
${STAGE_OUT_DIR}/bin/coverage.fdbrpc.xml
${STAGE_OUT_DIR}/bin/coverage.flow.xml
${STAGE_OUT_DIR}/CMakeCache.txt
)
${STAGE_OUT_DIR}/bin/coverage.flow.xml)
list(APPEND package_dependencies ${CMAKE_BINARY_DIR}/bin/coverage.fdbserver.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbclient.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbrpc.xml
${CMAKE_BINARY_DIR}/lib/coverage.flow.xml)
list(APPEND copy_sources ${CMAKE_BINARY_DIR}/bin/coverage.fdbserver.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbclient.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbrpc.xml
${CMAKE_BINARY_DIR}/lib/coverage.flow.xml)
endif()
add_custom_command(
OUTPUT ${package_files}
DEPENDS ${CMAKE_BINARY_DIR}/CMakeCache.txt
${CMAKE_BINARY_DIR}/packages/bin/fdbserver
${CMAKE_BINARY_DIR}/bin/coverage.fdbserver.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbclient.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbrpc.xml
${CMAKE_BINARY_DIR}/lib/coverage.flow.xml
DEPENDS ${package_dependencies}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/CMakeCache.txt ${STAGE_OUT_DIR}
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_BINARY_DIR}/packages/bin/fdbserver
${CMAKE_BINARY_DIR}/bin/coverage.fdbserver.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbclient.xml
${CMAKE_BINARY_DIR}/lib/coverage.fdbrpc.xml
${CMAKE_BINARY_DIR}/lib/coverage.flow.xml
${STAGE_OUT_DIR}/bin
COMMAND ${CMAKE_COMMAND} -E copy ${copy_sources} ${STAGE_OUT_DIR}/bin
COMMENT "Copying files for ${STAGE_CONTEXT} package"
)

View File

@@ -1,7 +1,8 @@
find_package(Python3 REQUIRED COMPONENTS Interpreter)
find_program(MCS_EXECUTABLE mcs)
find_program(MONO_EXECUTABLE mono)
if(NOT DEFINED FDB_USE_CSHARP_TOOLS)
set(FDB_USE_CSHARP_TOOLS TRUE)
endif()
set(ACTORCOMPILER_PY_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler_py/__main__.py
@@ -12,7 +13,7 @@ set(ACTORCOMPILER_PY_SRCS
set(ACTORCOMPILER_CSPROJ
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/actorcompiler.csproj)
set(ACTORCOMPILER_SRCS
set(ACTORCOMPILER_LEGACY_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ActorCompiler.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ActorParser.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/actorcompiler/ParseTree.cs
@@ -25,8 +26,20 @@ set(ACTOR_COMPILER_REFERENCES
add_custom_target(actorcompiler_py DEPENDS ${ACTORCOMPILER_PY_SRCS})
if(WIN32)
add_executable(actorcompiler_csharp ${ACTORCOMPILER_SRCS})
set(ACTORCOMPILER_PY_COMMAND
${Python3_EXECUTABLE} -m flow.actorcompiler_py
CACHE INTERNAL "Command to run the Python actor compiler")
set(ACTORCOMPILER_CSHARP_COMMAND ""
CACHE INTERNAL "Command to run the C# actor compiler")
set(ACTORCOMPILER_COMMAND ${ACTORCOMPILER_PY_COMMAND}
CACHE INTERNAL "Command to run the actor compiler")
set(actorcompiler_dependencies actorcompiler_py)
if(FDB_USE_CSHARP_TOOLS AND CSHARP_TOOLCHAIN_FOUND)
if(WIN32)
add_executable(actorcompiler_csharp ${ACTORCOMPILER_LEGACY_SRCS})
target_compile_options(actorcompiler_csharp PRIVATE "/langversion:6")
set_property(
TARGET actorcompiler_csharp
@@ -40,14 +53,13 @@ if(WIN32)
"System.Xml")
set(ACTORCOMPILER_CSHARP_COMMAND $<TARGET_FILE:actorcompiler_csharp>
CACHE INTERNAL "Command to run the C# actor compiler")
add_custom_target(actorcompiler)
add_dependencies(actorcompiler actorcompiler_csharp actorcompiler_py)
elseif(CSHARP_USE_MONO)
list(APPEND actorcompiler_dependencies actorcompiler_csharp)
elseif(CSHARP_USE_MONO)
add_custom_command(
OUTPUT actorcompiler.exe
COMMAND ${CSHARP_COMPILER_EXECUTABLE} ARGS ${ACTOR_COMPILER_REFERENCES}
${ACTORCOMPILER_SRCS} "-target:exe" "-out:actorcompiler.exe"
DEPENDS ${ACTORCOMPILER_SRCS}
${ACTORCOMPILER_LEGACY_SRCS} "-target:exe" "-out:actorcompiler.exe"
DEPENDS ${ACTORCOMPILER_LEGACY_SRCS}
COMMENT "Compile actor compiler"
VERBATIM)
add_custom_target(actorcompiler_csharp
@@ -55,18 +67,22 @@ elseif(CSHARP_USE_MONO)
set(actor_exe "${CMAKE_CURRENT_BINARY_DIR}/actorcompiler.exe")
set(ACTORCOMPILER_CSHARP_COMMAND ${MONO_EXECUTABLE} ${actor_exe}
CACHE INTERNAL "Command to run the C# actor compiler")
add_custom_target(actorcompiler)
add_dependencies(actorcompiler actorcompiler_csharp actorcompiler_py)
else()
dotnet_build(${ACTORCOMPILER_CSPROJ} SOURCE ${ACTORCOMPILER_SRCS})
list(APPEND actorcompiler_dependencies actorcompiler_csharp)
else()
dotnet_build(${ACTORCOMPILER_CSPROJ} SOURCE ${ACTORCOMPILER_LEGACY_SRCS})
set(actor_exe "${actorcompiler_EXECUTABLE_PATH}")
message(STATUS "Actor compiler path: ${actor_exe}")
# dotnet_build already creates a target named 'actorcompiler', so we just add Python dependency
add_dependencies(actorcompiler actorcompiler_py)
set(ACTORCOMPILER_CSHARP_COMMAND ${actor_exe}
set(ACTORCOMPILER_CSHARP_COMMAND ${dotnet_EXECUTABLE} ${actor_exe}
CACHE INTERNAL "Command to run the C# actor compiler")
endif()
endif()
set(ACTORCOMPILER_COMMAND
${Python3_EXECUTABLE} -m flow.actorcompiler_py
CACHE INTERNAL "Command to run the actor compiler")
if(NOT TARGET actorcompiler)
add_custom_target(actorcompiler)
endif()
add_dependencies(actorcompiler ${actorcompiler_dependencies})
if(ACTORCOMPILER_CSHARP_COMMAND)
set(ACTORCOMPILER_COMMAND ${ACTORCOMPILER_CSHARP_COMMAND}
CACHE INTERNAL "Command to run the actor compiler" FORCE)
endif()

View File

@@ -4,6 +4,8 @@ set(COVERAGETOOL_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/flow/coveragetool/Program.cs
${CMAKE_CURRENT_SOURCE_DIR}/flow/coveragetool/Properties/AssemblyInfo.cs)
set(coveragetool_command "")
if(WIN32)
add_executable(coveragetool ${COVERAGETOOL_SRCS})
target_compile_options(coveragetool PRIVATE "/langversion:6")
@@ -32,7 +34,16 @@ elseif(CSHARP_USE_MONO)
add_custom_target(coveragetool
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/coveragetool.exe)
set(coveragetool_exe "${CMAKE_CURRENT_BINARY_DIR}/coveragetool.exe")
set(coveragetool_command ${MONO_EXECUTABLE} ${coveragetool_exe})
else()
dotnet_build(${COVERAGETOOL_CSPROJ} SOURCE ${COVERAGETOOL_SRCS})
set(coveragetool_exe "${coveragetool_EXECUTABLE_PATH}")
set(coveragetool_command ${dotnet_EXECUTABLE} ${coveragetool_exe})
endif()
if(NOT coveragetool_command)
set(coveragetool_command ${coveragetool_exe})
endif()
set(coveragetool_command
${coveragetool_command}
CACHE INTERNAL "Command to run coveragetool")

View File

@@ -7,14 +7,22 @@ set(VEXILLOGRAPHER_SRCS
${CMAKE_CURRENT_SOURCE_DIR}/fdbclient/vexillographer/python.cs
${CMAKE_CURRENT_SOURCE_DIR}/fdbclient/vexillographer/ruby.cs
${CMAKE_CURRENT_SOURCE_DIR}/fdbclient/vexillographer/vexillographer.cs)
set(VEXILLOGRAPHER_PY ${CMAKE_CURRENT_SOURCE_DIR}/fdbclient/vexillographer/vexillographer.py)
if(WIN32)
if(NOT DEFINED FDB_USE_CSHARP_TOOLS)
set(FDB_USE_CSHARP_TOOLS TRUE)
endif()
set(VEXILLOGRAPHER_COMMAND "")
if(WIN32 AND FDB_USE_CSHARP_TOOLS)
add_executable(vexillographer ${VEXILLOGRAPHER_SRCS})
target_compile_options(vexillographer PRIVATE "/langversion:6")
set_property(
TARGET vexillographer PROPERTY VS_DOTNET_REFERENCES "System" "System.Core"
"System.Data" "System.Xml" "System.Xml.Linq")
elseif(CSHARP_USE_MONO)
set(VEXILLOGRAPHER_DEPENDS vexillographer)
elseif(FDB_USE_CSHARP_TOOLS AND CSHARP_TOOLCHAIN_FOUND)
if(CSHARP_USE_MONO)
set(VEXILLOGRAPHER_REFERENCES
"-r:System,System.Core,System.Data,System.Xml,System.Xml.Linq")
set(VEXILLOGRAPHER_EXE "${CMAKE_CURRENT_BINARY_DIR}/vexillographer.exe")
@@ -25,10 +33,19 @@ elseif(CSHARP_USE_MONO)
DEPENDS ${VEXILLOGRAPHER_SRCS}
COMMENT "Compile Vexillographer")
add_custom_target(vexillographer DEPENDS ${VEXILLOGRAPHER_EXE})
else()
set(VEXILLOGRAPHER_DEPENDS vexillographer)
set(VEXILLOGRAPHER_COMMAND ${MONO_EXECUTABLE} ${VEXILLOGRAPHER_EXE})
else()
dotnet_build(${VEXILLOGRAPHER_CSPROJ} SOURCE ${VEXILLOGRAPHER_SRCS})
message(STATUS "Generated executable: ${vexillographer_EXECUTABLE_PATH}")
set(VEXILLOGRAPHER_EXE ${vexillographer_EXECUTABLE_PATH})
set(VEXILLOGRAPHER_COMMAND ${dotnet_EXECUTABLE} ${vexillographer_EXECUTABLE_PATH})
set(VEXILLOGRAPHER_DEPENDS ${vexillographer_EXECUTABLE_PATH})
endif()
else()
find_package(Python3 COMPONENTS Interpreter REQUIRED)
set(VEXILLOGRAPHER_COMMAND ${Python3_EXECUTABLE} ${VEXILLOGRAPHER_PY})
set(VEXILLOGRAPHER_DEPENDS ${VEXILLOGRAPHER_PY})
endif()
function(vexillographer_compile)
@@ -41,21 +58,19 @@ function(vexillographer_compile)
set(VX_OUTPUT ${VX_OUT})
endif()
if(WIN32)
if(WIN32 AND FDB_USE_CSHARP_TOOLS)
add_custom_command(
OUTPUT ${VX_OUTPUT}
COMMAND
$<TARGET_FILE:vexillographer>
COMMAND $<TARGET_FILE:vexillographer>
${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options ${VX_LANG}
${VX_OUT}
DEPENDS ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options
vexillographer
COMMENT "Generate FDBOptions ${VX_LANG} files")
elseif(CSHARP_USE_MONO)
elseif(FDB_USE_CSHARP_TOOLS AND CSHARP_TOOLCHAIN_FOUND AND CSHARP_USE_MONO)
add_custom_command(
OUTPUT ${VX_OUTPUT}
COMMAND
${MONO_EXECUTABLE} ${VEXILLOGRAPHER_EXE}
COMMAND ${VEXILLOGRAPHER_COMMAND}
${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options ${VX_LANG}
${VX_OUT}
DEPENDS ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options
@@ -64,12 +79,11 @@ function(vexillographer_compile)
else()
add_custom_command(
OUTPUT ${VX_OUTPUT}
COMMAND
${VEXILLOGRAPHER_EXE}
COMMAND ${VEXILLOGRAPHER_COMMAND}
${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options ${VX_LANG}
${VX_OUT}
DEPENDS ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options
vexillographer
${VEXILLOGRAPHER_DEPENDS}
COMMENT "Generate FDBOptions ${VX_LANG} files")
endif()

View File

@@ -71,6 +71,8 @@ function(dotnet_build project_file_path)
cmake_path(APPEND project_root_directory "bin" OUTPUT_VARIABLE
project_binary_directory)
cmake_path(APPEND project_binary_directory "${project}" OUTPUT_VARIABLE
project_binary_directory)
cmake_path(APPEND project_binary_directory "${project}.dll" OUTPUT_VARIABLE
project_binary_path)
message(
STATUS "Building project ${project} using dotnet, in ${configuration} mode")
@@ -80,6 +82,7 @@ function(dotnet_build project_file_path)
COMMAND
${dotnet_EXECUTABLE} ARGS build ${project_file_path} --configuration
"${configuration}" --output "${project_binary_directory}" --self-contained
"false" -p:UseAppHost=false -p:PublishSingleFile=false
DEPENDS ${ARG_SOURCE}
WORKING_DIRECTORY "${project_root_directory}"
COMMENT "Build ${project} using .NET framework")

View File

@@ -29,6 +29,9 @@ set(PASS_COMPILATION_UNIT
)
function(generate_coverage_xml)
if(NOT COVERAGETOOL_AVAILABLE)
return()
endif()
if(NOT (${ARGC} EQUAL "1"))
message(FATAL_ERROR "generate_coverage_xml expects one argument")
endif()
@@ -85,7 +88,7 @@ function(generate_coverage_xml)
else()
add_custom_command(
OUTPUT ${target_file}
COMMAND ${coveragetool_exe} ${target_file} ${in_files}
COMMAND ${coveragetool_command} ${target_file} ${in_files}
DEPENDS ${in_files}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
COMMENT "Generate coverage xml")
@@ -268,9 +271,11 @@ function(add_flow_target)
set(cs_out_file "${out_file}.cs_gen")
add_custom_command(OUTPUT "${out_file}"
COMMAND ${CMAKE_COMMAND} -E env "PYTHONPATH=${CMAKE_SOURCE_DIR}"
${ACTORCOMPILER_COMMAND} "${in_file}" "${py_out_file}" ${actor_compiler_flags}
${ACTORCOMPILER_PY_COMMAND} "${in_file}" "${py_out_file}" ${actor_compiler_flags}
COMMAND ${ACTORCOMPILER_CSHARP_COMMAND} "${in_file}" "${cs_out_file}" ${actor_compiler_flags}
COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/flow/actorcompiler_py/compare_actor_output.py "${cs_out_file}" "${py_out_file}"
COMMAND ${Python3_EXECUTABLE}
${CMAKE_SOURCE_DIR}/flow/actorcompiler_py/compare_actor_output.py
"${cs_out_file}" "${py_out_file}"
COMMAND ${CMAKE_COMMAND} -E copy "${py_out_file}" "${out_file}"
DEPENDS "${in_file}" actorcompiler
COMMENT "Compile and compare actor: ${src}")
@@ -327,6 +332,17 @@ function(add_flow_target)
set_property(TARGET ${AFT_NAME} PROPERTY COVERAGE_FILTERS ${AFT_SRCS})
add_custom_target(${AFT_NAME}_actors DEPENDS ${generated_files})
if(TARGET fdboptions AND NOT "${AFT_NAME}" STREQUAL "fdboptions")
if(DEFINED FDB_OPTIONS_H)
set_source_files_properties(${sources} ${AFT_ADDL_SRCS}
APPEND PROPERTY OBJECT_DEPENDS ${FDB_OPTIONS_H})
endif()
add_dependencies(${AFT_NAME}_actors fdboptions)
add_dependencies(${AFT_NAME} fdboptions)
if(TARGET fdboptions_vex)
add_dependencies(${AFT_NAME}_actors fdboptions_vex)
endif()
endif()
add_dependencies(${AFT_NAME} ${AFT_NAME}_actors)
generate_coverage_xml(${AFT_NAME})
if(strip_target)

View File

@@ -4,21 +4,42 @@ list(APPEND FDBCLIENT_SRCS sha1/SHA1.cpp)
message(STATUS "FDB version is ${FDB_VERSION}")
message(STATUS "FDB package name is ${FDB_PACKAGE_NAME}")
set(options_srcs ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g.cpp)
if(NOT VEXILLOGRAPHER_COMMAND)
message(FATAL_ERROR "VEXILLOGRAPHER_COMMAND is undefined; ensure CompileVexillographer.cmake is included")
endif()
make_directory(${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/)
vexillographer_compile(TARGET fdboptions_vex LANG cpp OUT ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g.h ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g.cpp)
file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/)
add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/FDBOptions.g.h
DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g.h
COMMAND ${CMAKE_COMMAND} -E copy ${CMAKE_CURRENT_BINARY_DIR}/FDBOptions.g.h ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/FDBOptions.g.h)
set(FDBOPTIONS_GEN_BASE ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/FDBOptions.g)
set(FDBOPTIONS_GEN_H ${FDBOPTIONS_GEN_BASE}.h)
set(FDBOPTIONS_GEN_CPP ${FDBOPTIONS_GEN_BASE}.cpp)
vexillographer_compile(TARGET fdboptions_c LANG c OUT ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/fdb_c_options.g.h
# Generate the C++ option bindings directly into the final include directory so
# the header and source exist before any staging or actor compilation.
add_custom_command(
OUTPUT ${FDBOPTIONS_GEN_H} ${FDBOPTIONS_GEN_CPP}
COMMAND ${VEXILLOGRAPHER_COMMAND}
${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options
cpp
${FDBOPTIONS_GEN_BASE}
DEPENDS ${CMAKE_SOURCE_DIR}/fdbclient/vexillographer/fdb.options
${VEXILLOGRAPHER_DEPENDS}
COMMENT "Generate FDBOptions (C++)"
VERBATIM)
vexillographer_compile(
TARGET fdboptions_c
LANG c
OUT ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/fdb_c_options.g.h
OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/fdb_c_options.g.h)
add_custom_target(fdboptions DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/include/fdbclient/FDBOptions.g.h)
add_dependencies(fdboptions fdboptions_c)
set(FDB_OPTIONS_H ${FDBOPTIONS_GEN_H} CACHE INTERNAL "FDB options header")
set(options_srcs ${FDBOPTIONS_GEN_CPP})
add_custom_target(fdboptions ALL
DEPENDS ${FDBOPTIONS_GEN_H}
${FDBOPTIONS_GEN_CPP}
fdboptions_c)
################################################################################
# Build information

View File

@@ -0,0 +1,419 @@
import argparse
import enum
import pathlib
import sys
import textwrap
import xml.etree.ElementTree as ET
from dataclasses import dataclass
from typing import Iterable, List, Optional
class Scope(enum.Enum):
NetworkOption = "NetworkOption"
DatabaseOption = "DatabaseOption"
TransactionOption = "TransactionOption"
StreamingMode = "StreamingMode"
MutationType = "MutationType"
ConflictRangeType = "ConflictRangeType"
ErrorPredicate = "ErrorPredicate"
@property
def description(self) -> str:
return {
Scope.NetworkOption: "NET_OPTION",
Scope.DatabaseOption: "DB_OPTION",
Scope.TransactionOption: "TR_OPTION",
Scope.StreamingMode: "STREAMING_MODE",
Scope.MutationType: "MUTATION_TYPE",
Scope.ConflictRangeType: "CONFLICT_RANGE_TYPE",
Scope.ErrorPredicate: "ERROR_PREDICATE",
}[self]
class ParamType(enum.Enum):
NoneType = "None"
String = "String"
Int = "Int"
Bytes = "Bytes"
@dataclass
class Option:
scope: Scope
name: str
code: int
param_type: ParamType
param_desc: Optional[str]
comment: str
hidden: bool
persistent: bool
sensitive: bool
default_for: int
def parameter_comment(self) -> str:
return "Option takes no parameter" if self.param_desc is None else f"({self.param_type.value}) {self.param_desc}"
def is_deprecated(self) -> bool:
return self.comment.startswith("Deprecated")
HEADER_NOTICE_C = textwrap.dedent(
"""
#ifndef FDB_C_OPTIONS_G_H
#define FDB_C_OPTIONS_G_H
#pragma once
/*
* FoundationDB C API
*
* This source file is part of the FoundationDB open source project
*
* Copyright 2013-2024 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.
* Do not include this file directly.
*/
"""
).strip()
HEADER_NOTICE_CPP = """\
#ifndef FDBCLIENT_FDBOPTIONS_G_H
#define FDBCLIENT_FDBOPTIONS_G_H
#pragma once
#include "fdbclient/FDBOptions.h"
""".strip()
LICENSE_PY = textwrap.dedent(
"""
# FoundationDB Python API
# Copyright (c) 2013-2017 Apple Inc.
# 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.
import types
"""
).strip()
LICENSE_RB = textwrap.dedent(
"""
# FoundationDB Ruby API
# Copyright (c) 2013-2017 Apple Inc.
# 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.
# Documentation for this API can be found at
# https://apple.github.io/foundationdb/api-ruby.html
module FDB
"""
).strip()
LICENSE_JAVA = textwrap.dedent(
"""
/*
* FoundationDB Java API
* Copyright (c) 2013-2024 Apple Inc.
*
* 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.
*/
"""
).strip()
def parse_options(xml_path: pathlib.Path, binding: str) -> List[Option]:
tree = ET.parse(xml_path)
options_root = tree.getroot()
options: List[Option] = []
for scope_elem in options_root.findall("Scope"):
scope = Scope(scope_elem.get("name"))
for opt_elem in scope_elem.findall("Option"):
disable_on = opt_elem.get("disableOn")
if disable_on:
if binding in [item.strip() for item in disable_on.split(",") if item.strip()]:
continue
param_type = ParamType(opt_elem.get("paramType", "None"))
options.append(
Option(
scope=scope,
name=opt_elem.get("name"),
code=int(opt_elem.get("code")),
param_type=param_type,
param_desc=opt_elem.get("paramDescription"),
comment=opt_elem.get("description", ""),
hidden=opt_elem.get("hidden") == "true",
persistent=opt_elem.get("persistent") == "true",
sensitive=opt_elem.get("sensitive") == "true",
default_for=int(opt_elem.get("defaultFor", "-1")),
)
)
return options
def indent(text: str, prefix: str) -> str:
return "\n".join(prefix + line if line else prefix for line in text.split("\n"))
def write_c(file_path: pathlib.Path, options: Iterable[Option]) -> None:
def option_line(opt: Option, prefix: str) -> str:
parameter_comment = ""
if opt.scope.name.endswith("Option"):
hidden_note = (
"This is a hidden parameter and should not be used directly by applications."
if opt.hidden
else ""
)
parameter_comment = f" /* Parameter: {opt.parameter_comment()} {hidden_note}*/\n"
return f"{parameter_comment} /* {opt.comment} */\n {prefix}{opt.name.upper()}={opt.code}"
with file_path.open("w", newline="\n") as handle:
handle.write(HEADER_NOTICE_C + "\n")
for scope in Scope:
scoped = [o for o in options if o.scope is scope]
if not scoped:
scoped = [
Option(scope, "DUMMY_DO_NOT_USE", -1, ParamType.NoneType, None,
"This option is only a placeholder for C compatibility and should not be used", False, False, False, -1)
]
prefix = f"FDB_{scope.description}_"
handle.write("typedef enum {\n")
handle.write(",\n\n".join(option_line(o, prefix) for o in scoped))
handle.write(f"\n}} FDB{scope.name};\n\n")
handle.write("#endif\n")
def write_cpp(base_path: pathlib.Path, options: Iterable[Option]) -> None:
options = list(options)
# Keep the ".g" stem for generated C++ option artifacts
header_path = base_path.parent / (base_path.name + ".h")
source_path = base_path.parent / (base_path.name + ".cpp")
def option_enum(opt: Option, indent_level: str) -> str:
return f"{indent_level}/* {opt.comment} */\n{indent_level}{opt.name.upper()}={opt.code}"
def option_info(opt: Option, indent_level: str, struct_name: str) -> str:
return (
f"{indent_level}ADD_OPTION_INFO({struct_name}, {opt.name.upper()}, \"{opt.name.upper()}\", "
f"\"{opt.comment}\", \"{opt.parameter_comment()}\", "
f"{str(opt.param_desc is not None).lower()}, {str(opt.hidden).lower()}, "
f"{str(opt.persistent).lower()}, {str(opt.sensitive).lower()}, {opt.default_for}, "
f"FDBOptionInfo::ParamType::{opt.param_type.value})"
)
with header_path.open("w", newline="\n") as header:
header.write(HEADER_NOTICE_CPP + "\n\n")
for scope in Scope:
scoped = [o for o in options if o.scope is scope]
header.write(f"struct FDB{scope.name}s {{\n")
header.write("\tfriend class FDBOptionInfoMap<FDB{0}s>;\n\n".format(scope.name))
header.write("\tenum Option : int {\n")
header.write(",\n\n".join(option_enum(o, "\t\t") for o in scoped))
header.write("\n\t};\n\n")
header.write(f"\tstatic FDBOptionInfoMap<FDB{scope.name}s> optionInfo;\n\n")
header.write("private:\n")
header.write("\tstatic void init();\n")
header.write("};\n\n")
header.write("#endif\n")
with source_path.open("w", newline="\n") as source:
source.write('#include "fdbclient/FDBOptions.g.h"\n\n')
for scope in Scope:
scoped = [o for o in options if o.scope is scope]
source.write(f"FDBOptionInfoMap<FDB{scope.name}s> FDB{scope.name}s::optionInfo;\n\n")
source.write(f"void FDB{scope.name}s::init() {{\n")
source.write("\n".join(option_info(o, "\t", f"FDB{scope.name}s") for o in scoped))
source.write("\n}\n\n")
def write_python(file_path: pathlib.Path, options: Iterable[Option]) -> None:
type_map = {
ParamType.NoneType: "type(None)",
ParamType.Int: "type(0)",
ParamType.String: "type('')",
ParamType.Bytes: "type(b'')",
}
with file_path.open("w", newline="\n") as handle:
handle.write(LICENSE_PY + "\n")
for scope in Scope:
scoped = [o for o in options if o.scope is scope and not o.hidden]
handle.write(f"{scope.name} = {{\n")
lines = []
for o in scoped:
param_desc = "None" if o.param_desc is None else f"\"{o.param_desc}\""
lines.append(
f" \"{o.name}\" : ({o.code}, \"{o.comment}\", {type_map[o.param_type]}, {param_desc}),"
)
handle.write("\n".join(lines))
handle.write("\n}\n\n")
def write_ruby(file_path: pathlib.Path, options: Iterable[Option]) -> None:
type_map = {
ParamType.NoneType: "nil",
ParamType.Int: "0",
ParamType.String: "''",
ParamType.Bytes: "''",
}
with file_path.open("w", newline="\n") as handle:
handle.write(LICENSE_RB + "\n")
for scope in Scope:
if scope is Scope.ErrorPredicate:
continue
scoped = [o for o in options if o.scope is scope and not o.hidden]
handle.write(f" @@{scope.name} = {{\n")
lines = []
for o in scoped:
param_desc = "nil" if o.param_desc is None else f"\"{o.param_desc}\""
lines.append(
f" \"{o.name.upper()}\" => [{o.code}, \"{o.comment}\", {type_map[o.param_type]}, {param_desc}],"
)
handle.write("\n".join(lines))
handle.write("\n }\n\n")
handle.write("end\n")
def write_java(file_path: pathlib.Path, options: Iterable[Option]) -> None:
with file_path.open("w", newline="\n") as handle:
handle.write(LICENSE_JAVA + "\n")
handle.write("package com.apple.foundationdb;\n\n")
handle.write("@SuppressWarnings(\"unused\")\n")
handle.write("public class FDBOptions {\n")
handle.write(" private FDBOptions() { }\n")
# Enums
for scope in Scope:
scoped = [o for o in options if o.scope is scope]
enum_name = scope.name
handle.write(f" public enum {enum_name} {{\n")
handle.write(",\n".join(
f" {o.name}({o.code})" for o in scoped
))
handle.write(";\n\n")
handle.write(" private final int code;\n")
handle.write(f" {enum_name}(int c) {{ this.code = c; }}\n")
handle.write(" public int code() { return this.code; }\n")
handle.write(" }\n\n")
# Options info helper
handle.write(" static final class OptionInfo {\n")
handle.write(" enum ParamType { NONE, INT, STRING, BYTES }\n")
handle.write(" public final String description;\n")
handle.write(" public final String parameterDescription;\n")
handle.write(" public final boolean hasParameter;\n")
handle.write(" public final boolean isDeprecated;\n")
handle.write(" public final boolean isPersistent;\n")
handle.write(" public final boolean isSensitive;\n")
handle.write(" public final int defaultFor;\n")
handle.write(" public final ParamType parameterType;\n")
handle.write(" OptionInfo(String desc, String paramDesc, boolean hasParam, boolean deprecated, boolean persistent, boolean sensitive, int defaultFor, ParamType parameterType) {\n")
handle.write(" this.description = desc;\n")
handle.write(" this.parameterDescription = paramDesc;\n")
handle.write(" this.hasParameter = hasParam;\n")
handle.write(" this.isDeprecated = deprecated;\n")
handle.write(" this.isPersistent = persistent;\n")
handle.write(" this.isSensitive = sensitive;\n")
handle.write(" this.defaultFor = defaultFor;\n")
handle.write(" this.parameterType = parameterType;\n")
handle.write(" }\n")
handle.write(" }\n\n")
# Maps
handle.write(" static final class OptionInfoMap {\n")
for scope in Scope:
scoped = [o for o in options if o.scope is scope]
map_entries_list = []
for o in scoped:
map_entries_list.append(
" new OptionInfo(\"{}\", \"{}\", {}, {}, {}, {}, {}, OptionInfo.ParamType.{})".format(
o.comment,
o.parameter_comment(),
str(o.param_desc is not None).lower(),
str(o.is_deprecated()).lower(),
str(o.persistent).lower(),
str(o.sensitive).lower(),
o.default_for,
o.param_type.value.upper(),
)
)
map_entries = ",\n".join(map_entries_list)
handle.write(f" static final OptionInfo[] {scope.name} = new OptionInfo[] {{\n{map_entries}\n }};\n\n")
handle.write(" }\n")
handle.write("}\n")
WRITERS = {
"c": write_c,
"cpp": write_cpp,
"python": write_python,
"ruby": write_ruby,
"java": write_java,
}
def main() -> int:
parser = argparse.ArgumentParser()
parser.add_argument("input")
parser.add_argument("lang", choices=WRITERS.keys())
parser.add_argument("output")
args = parser.parse_args()
options = parse_options(pathlib.Path(args.input), args.lang)
writer = WRITERS[args.lang]
writer(pathlib.Path(args.output), options)
return 0
if __name__ == "__main__":
sys.exit(main())