Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 68 additions & 0 deletions CMakeUserPresets.json.example
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,74 @@
"rhs": "Darwin"
},
"generator": "Ninja"
},
{
"name": "debug-macos-gcc-asan",
"displayName": "Debug (macOS: gcc) with ASan",
"description": "Preset for building MrDocs in Debug mode with the gcc compiler in macOS.",
"inherits": "debug",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"LLVM_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-asan",
"Clang_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-asan",
"duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-asan",
"Duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-asan",
"libxml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
"LibXml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
"MRDOCS_BUILD_TESTS": true,
"MRDOCS_BUILD_DOCS": false,
"MRDOCS_GENERATE_REFERENCE": false,
"MRDOCS_GENERATE_ANTORA_REFERENCE": false,
"CMAKE_C_COMPILER": "/usr/bin/gcc",
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
"CMAKE_MAKE_PROGRAM": "$env{HOME}/Developer/cpp-libs/ninja/ninja",
"CMAKE_C_FLAGS": "-fsanitize=address -fno-sanitize-recover=address -fno-omit-frame-pointer",
"CMAKE_CXX_FLAGS": "-fsanitize=address -fno-sanitize-recover=address -fno-omit-frame-pointer"
},
"warnings": {
"unusedCli": false
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"generator": "Ninja"
},
{
"name": "debug-macos-gcc-ubsan",
"displayName": "Debug (macOS: gcc) with UBSan",
"description": "Preset for building MrDocs in Debug mode with the gcc compiler in macOS.",
"inherits": "debug",
"binaryDir": "${sourceDir}/build/${presetName}",
"cacheVariables": {
"CMAKE_BUILD_TYPE": "Debug",
"LLVM_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-ubsan",
"Clang_ROOT": "$env{HOME}/Developer/cpp-libs/llvm-project/install/debug-gcc-ubsan",
"duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-ubsan",
"Duktape_ROOT": "$env{HOME}/Developer/cpp-libs/duktape/install/debug-gcc-ubsan",
"libxml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
"LibXml2_ROOT": "$env{HOME}/Developer/cpp-libs/libxml2/install/release-gcc",
"MRDOCS_BUILD_TESTS": true,
"MRDOCS_BUILD_DOCS": false,
"MRDOCS_GENERATE_REFERENCE": false,
"MRDOCS_GENERATE_ANTORA_REFERENCE": false,
"CMAKE_C_COMPILER": "/usr/bin/gcc",
"CMAKE_CXX_COMPILER": "/usr/bin/g++",
"CMAKE_MAKE_PROGRAM": "$env{HOME}/Developer/cpp-libs/ninja/ninja",
"CMAKE_C_FLAGS": "-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer",
"CMAKE_CXX_FLAGS": "-fsanitize=undefined -fno-sanitize-recover=undefined -fno-omit-frame-pointer"
},
"warnings": {
"unusedCli": false
},
"condition": {
"type": "equals",
"lhs": "${hostSystemName}",
"rhs": "Darwin"
},
"generator": "Ninja"
}
]
}
110 changes: 95 additions & 15 deletions bootstrap.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ class InstallOptions:
# Compiler
cc: str = ''
cxx: str = ''
sanitizer: str = ''

# Required tools
git_path: str = ''
Expand All @@ -69,12 +70,12 @@ class InstallOptions:
mrdocs_repo: str = "https://github.com/cppalliance/mrdocs"
mrdocs_branch: str = "develop"
mrdocs_use_user_presets: bool = True
mrdocs_preset_name: str = "<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>"
mrdocs_build_dir: str = "<mrdocs-src-dir>/build/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>"
mrdocs_preset_name: str = "<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
mrdocs_build_dir: str = "<mrdocs-src-dir>/build/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower><\"-\":if(sanitizer)><sanitizer:lower>"
mrdocs_build_tests: bool = True
mrdocs_system_install: bool = field(default_factory=lambda: not running_from_mrdocs_source_dir())
mrdocs_install_dir: str = field(
default_factory=lambda: "<mrdocs-src-dir>/install/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename>" if running_from_mrdocs_source_dir() else "")
default_factory=lambda: "<mrdocs-src-dir>/install/<mrdocs-build-type:lower>-<os:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>" if running_from_mrdocs_source_dir() else "")
mrdocs_run_tests: bool = True

# Third-party dependencies
Expand All @@ -84,14 +85,14 @@ class InstallOptions:
duktape_src_dir: str = "<third-party-src-dir>/duktape"
duktape_url: str = "https://github.com/svaarala/duktape/releases/download/v2.7.0/duktape-2.7.0.tar.xz"
duktape_build_type: str = "<mrdocs-build-type>"
duktape_build_dir: str = "<duktape-src-dir>/build/<duktape-build-type:lower><\"-\":if(cc)><cc:basename>"
duktape_install_dir: str = "<duktape-src-dir>/install/<duktape-build-type:lower><\"-\":if(cc)><cc:basename>"
duktape_build_dir: str = "<duktape-src-dir>/build/<duktape-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
duktape_install_dir: str = "<duktape-src-dir>/install/<duktape-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"

# LLVM
llvm_src_dir: str = "<third-party-src-dir>/llvm-project"
llvm_build_type: str = "<mrdocs-build-type>"
llvm_build_dir: str = "<llvm-src-dir>/build/<llvm-build-type:lower><\"-\":if(cc)><cc:basename>"
llvm_install_dir: str = "<llvm-src-dir>/install/<llvm-build-type:lower><\"-\":if(cc)><cc:basename>"
llvm_build_dir: str = "<llvm-src-dir>/build/<llvm-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
llvm_install_dir: str = "<llvm-src-dir>/install/<llvm-build-type:lower><\"-\":if(cc)><cc:basename><\"-\":if(sanitizer)><sanitizer:lower>"
llvm_repo: str = "https://github.com/llvm/llvm-project.git"
llvm_commit: str = "dd7a3d4d798e30dfe53b5bbbbcd9a23c24ea1af9"

Expand All @@ -116,6 +117,7 @@ class InstallOptions:
INSTALL_OPTION_DESCRIPTIONS = {
"cc": "Path to the C compiler executable. Leave empty for default.",
"cxx": "Path to the C++ compiler executable. Leave empty for default.",
"sanitizer": "Sanitizer to use for the build. Leave empty for no sanitizer. (ASan, UBSan, MSan, TSan)",
"git_path": "Path to the git executable, if not in system PATH.",
"cmake_path": "Path to the cmake executable, if not in system PATH.",
"java_path": "Path to the java executable, if not in system PATH.",
Expand Down Expand Up @@ -301,8 +303,9 @@ def repl(match):
val = val.upper()
elif transform_fn == "basename":
val = os.path.basename(val)
elif transform_fn == "if(cc)":
if self.options.cc:
elif transform_fn.startswith("if(") and transform_fn.endswith(")"):
var_name = transform_fn[3:-1]
if getattr(self.options, var_name, None):
val = val.lower()
else:
val = ""
Expand Down Expand Up @@ -356,6 +359,25 @@ def prompt_build_type_option(self, name):
print(f"Invalid build type '{value}'. Must be one of: {', '.join(valid_build_types)}.")
raise ValueError(f"Invalid build type '{value}'. Must be one of: {', '.join(valid_build_types)}.")

def prompt_sanitizer_option(self, name):
value = self.prompt_option(name)
if not value:
value = ''
return value
valid_sanitizers = ["ASan", "UBSan", "MSan", "TSan"]
for t in valid_sanitizers:
if t.lower() == value.lower():
setattr(self.options, name, t)
return value
print(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")
value = self.reprompt_option(name)
for t in valid_sanitizers:
if t.lower() == value.lower():
setattr(self.options, name, t)
return value
print(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")
raise ValueError(f"Invalid sanitizer '{value}'. Must be one of: {', '.join(valid_sanitizers)}.")

def supports_ansi(self):
if os.name == "posix":
return True
Expand Down Expand Up @@ -552,9 +574,12 @@ def setup_mrdocs_src_dir(self):

# MrDocs build type
self.prompt_build_type_option("mrdocs_build_type")
self.prompt_sanitizer_option("sanitizer")
if self.prompt_option("mrdocs_build_tests"):
self.check_tool("java")



def is_inside_mrdocs_dir(self, path):
"""
Checks if the given path is inside the MrDocs source directory.
Expand Down Expand Up @@ -666,6 +691,23 @@ def install_ninja(self):
os.chmod(ninja_path, 0o755)
self.options.ninja_path = ninja_path

def sanitizer_flag_name(self, sanitizer):
"""
Returns the flag name for the given sanitizer.
:param sanitizer: The sanitizer name (ASan, UBSan, MSan, TSan).
:return: str: The flag name for the sanitizer.
"""
if sanitizer.lower() == "asan":
return "address"
elif sanitizer.lower() == "ubsan":
return "undefined"
elif sanitizer.lower() == "msan":
return "memory"
elif sanitizer.lower() == "tsan":
return "thread"
else:
raise ValueError(f"Unknown sanitizer '{sanitizer}'.")

def is_abi_compatible(self, build_type_a, build_type_b):
if not self.is_windows():
return True
Expand Down Expand Up @@ -719,8 +761,17 @@ def install_duktape(self):
self.options.duktape_build_type = self.options.mrdocs_build_type
self.prompt_dependency_path_option("duktape_build_dir")
self.prompt_dependency_path_option("duktape_install_dir")
self.cmake_workflow(self.options.duktape_src_dir, self.options.duktape_build_type,
self.options.duktape_build_dir, self.options.duktape_install_dir)
extra_args = []
if self.options.sanitizer:
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
extra_args.append(f"-D{arg}=-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer")

self.cmake_workflow(
self.options.duktape_src_dir,
self.options.duktape_build_type,
self.options.duktape_build_dir,
self.options.duktape_install_dir, extra_args)

def install_libxml2(self):
self.prompt_dependency_path_option("libxml2_src_dir")
Expand Down Expand Up @@ -799,8 +850,23 @@ def install_llvm(self):
self.prompt_dependency_path_option("llvm_install_dir")
cmake_preset = f"{self.options.llvm_build_type.lower()}-win" if self.is_windows() else f"{self.options.llvm_build_type.lower()}-unix"
cmake_extra_args = [f"--preset={cmake_preset}"]
self.cmake_workflow(llvm_subproject_dir, self.options.llvm_build_type, self.options.llvm_build_dir,
self.options.llvm_install_dir, cmake_extra_args)
if self.options.sanitizer:
if self.options.sanitizer.lower() == "asan":
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Address")
elif self.options.sanitizer.lower() == "ubsan":
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Undefined")
elif self.options.sanitizer.lower() == "msan":
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Memory")
elif self.options.sanitizer.lower() == "tsan":
cmake_extra_args.append("-DLLVM_USE_SANITIZER=Thread")
else:
raise ValueError(f"Unknown LLVM sanitizer '{self.options.sanitizer}'.")
self.cmake_workflow(
llvm_subproject_dir,
self.options.llvm_build_type,
self.options.llvm_build_dir,
self.options.llvm_install_dir,
cmake_extra_args)

def create_cmake_presets(self):
# Ask the user if they want to create CMake User presets referencing the installed dependencies
Expand Down Expand Up @@ -852,9 +918,13 @@ def create_cmake_presets(self):
display_name = f"{self.options.mrdocs_build_type}"
if self.options.mrdocs_build_type.lower() == "debug" and self.options.llvm_build_type != self.options.mrdocs_build_type:
display_name += " with Optimized Dependencies"
display_name += f" ({OSDisplayName})"
display_name += f" ({OSDisplayName}"
if self.options.cc:
display_name += f" ({os.path.basename(self.options.cc)})"
display_name += f": {os.path.basename(self.options.cc)}"
display_name += ")"

if self.options.sanitizer:
display_name += f" with {self.options.sanitizer}"

new_preset = {
"name": self.options.mrdocs_preset_name,
Expand Down Expand Up @@ -892,6 +962,10 @@ def create_cmake_presets(self):
if self.options.ninja_path:
new_preset["generator"] = "Ninja"
new_preset["cacheVariables"]["CMAKE_MAKE_PROGRAM"] = self.options.ninja_path
if self.options.sanitizer:
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
new_preset["cacheVariables"][arg] = f"-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer"

# Update cache variables path prefixes with their relative equivalents
mrdocs_src_dir_parent = os.path.dirname(self.options.mrdocs_src_dir)
Expand Down Expand Up @@ -967,8 +1041,14 @@ def install_mrdocs(self):
extra_args.extend(["-DMRDOCS_BUILD_DOCS=OFF", "-DMRDOCS_GENERATE_REFERENCE=OFF",
"-DMRDOCS_GENERATE_ANTORA_REFERENCE=OFF"])

if self.options.sanitizer:
flag_name = self.sanitizer_flag_name(self.options.sanitizer)
for arg in ["CMAKE_C_FLAGS", "CMAKE_CXX_FLAGS"]:
extra_args.append(f"-D{arg}=-fsanitize={flag_name} -fno-sanitize-recover={flag_name} -fno-omit-frame-pointer")

self.cmake_workflow(self.options.mrdocs_src_dir, self.options.mrdocs_build_type, self.options.mrdocs_build_dir,
self.options.mrdocs_install_dir, extra_args)

if self.options.mrdocs_build_dir and self.prompt_option("mrdocs_run_tests"):
# Look for ctest path relative to the cmake path
ctest_path = os.path.join(os.path.dirname(self.options.cmake_path), "ctest")
Expand Down
Loading