Skip to content

Commit

Permalink
Add build.py for easier cross-platform building. (#12)
Browse files Browse the repository at this point in the history
  • Loading branch information
ZCG-coder committed May 1, 2024
2 parents 2fb6a4e + ed356d6 commit 2b12ada
Show file tree
Hide file tree
Showing 12 changed files with 969 additions and 33 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ docs/
# Compiled Object files
build/
cmake-build-*/
*.build/
Debug/
Release/
x64/
Expand Down Expand Up @@ -225,4 +226,4 @@ cython_debug/
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
#.idea/
42 changes: 17 additions & 25 deletions include/util.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -237,31 +237,7 @@ namespace steppable::__internals::numUtils
* @param[in] s The string to be checked.
* @return True if the string is a valid number, false otherwise.
*/
constexpr bool isNumber(const std::string_view& s)
{
if (s.empty())
return false;
int decimalPointCount = 0, minusCount = 0;

for (const char c : s)
{
if (c == '.')
{
if (decimalPointCount > 0)
return false;
decimalPointCount++;
}
else if (c == '-')
{
if (minusCount > 0)
return false;
minusCount++;
}
else if (not isdigit(c))
return false;
}
return true;
}
bool isNumber(const std::string_view& s);

/**
* Splits two numbers represented as strings and returns the result.
Expand Down Expand Up @@ -329,6 +305,22 @@ namespace steppable::__internals::numUtils
* @return The scale of the number
*/
long long determineScale(const std::string_view& number);

/**
* @brief Determines whether the number is an integer or not.
*
* @param number The number.
* @return True if it is an integer, false otherwise.
*/
bool isInteger(const std::string& number);

/**
* @brief Determines whether the number is a decimal or not.
*
* @param number The number.
* @return False if it is an integer, true otherwise.
*/
bool isDecimal(const std::string& number);
} // namespace steppable::__internals::numUtils

namespace steppable::__internals::stringUtils
Expand Down
5 changes: 3 additions & 2 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,9 @@ foreach (COMPONENT IN LISTS COMPONENTS)
# No longer offering executables for each component automatically.

if (DEFINED STP_BUILD_COMPONENT_EXECUTABLE)
add_executable(${COMPONENT} ${COMPONENT}/${COMPONENT}.cpp ${COMPONENT}/${COMPONENT}Report.cpp)
add_executable(${COMPONENT} ${COMPONENT}/${COMPONENT}Report.cpp)
target_include_directories(${COMPONENT} PRIVATE ${STP_BASE_DIRECTORY}/include/)
target_link_libraries(${COMPONENT} calc)
target_link_libraries(${COMPONENT} PRIVATE calc)
endif ()

list(APPEND CALCULATOR_FILES ${CMAKE_CURRENT_SOURCE_DIR}/${COMPONENT}/${COMPONENT}.cpp
Expand All @@ -71,4 +71,5 @@ target_include_directories(util PRIVATE ${STP_BASE_DIRECTORY}/include/)
target_link_libraries(steppable util)
target_link_libraries(calc steppable)
target_compile_definitions(calc PRIVATE NO_MAIN)
target_compile_definitions(steppable PRIVATE NO_MAIN)

40 changes: 39 additions & 1 deletion src/util.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,32 @@

namespace steppable::__internals::numUtils
{
bool isNumber(const std::string_view& s)
{
if (s.empty())
return false;
int decimalPointCount = 0, minusCount = 0;

for (const char c : s)
{
if (c == '.')
{
if (decimalPointCount > 0)
return false;
decimalPointCount++;
}
else if (c == '-')
{
if (minusCount > 0)
return false;
minusCount++;
}
else if (not isdigit(c))
return false;
}
return true;
}

std::string simplifyZeroPolarity(const std::string_view& string)
{
// Check if the string is zero
Expand Down Expand Up @@ -143,7 +169,8 @@ namespace steppable::__internals::numUtils
if (const auto firstNonZero = std::ranges::find_if(out, [](const int num) { return num != 0; });
out.begin() != firstNonZero && out.front() == 0)
{
std::replace_if(out.begin(), firstNonZero, [](const int num) { return num == 0; }, -2);
std::replace_if(
out.begin(), firstNonZero, [](const int num) { return num == 0; }, -2);
}

return out;
Expand Down Expand Up @@ -207,6 +234,17 @@ namespace steppable::__internals::numUtils
long long numberOfZeros = static_cast<long long>(numberDecimal.length()) - newNumberDecimal.length();
return -(numberOfZeros + 1);
}

bool isInteger(const std::string& number)
{
auto splitNumberResult = splitNumber(number, "0", false, false, false).splitNumberArray;
// If the decimal part is zero, it is an integer.
if (isZeroString(splitNumberResult[1]))
return true;
return false;
}

bool isDecimal(const std::string& number) { return not isInteger(number); }
} // namespace steppable::__internals::numUtils

namespace steppable::__internals::stringUtils
Expand Down
23 changes: 23 additions & 0 deletions tools/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#####################################################################################################
# Copyright (c) 2023-2024 NWSOFT #
# #
# 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. #
#####################################################################################################

pass
66 changes: 66 additions & 0 deletions tools/build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
#####################################################################################################
# Copyright (c) 2023-2024 NWSOFT #
# #
# 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. #
#####################################################################################################

"""
This script is used to build the project in case CMake is not available. No dependencies are required.
"""

import logging

from building.directories import create_build_dirs
from building.paths import BUILD_DIR
from building.project import BuildSteppable

logger = logging.getLogger(__name__)


def build() -> None:
"""
This function builds the project. You can add components to the project by calling the add_component method, but do
not forget to call the build method at the end of the function.
Do not call this function directly. Instead, run the this script from the root directory of the project.
"""
a = BuildSteppable(build_component_executables=False)
a.add_component("calc", "add")
a.add_component("calc", "abs")
a.add_component("calc", "subtract")
a.add_component("calc", "multiply")
a.add_component("calc", "comparison")
a.add_component("calc", "division")
a.add_component("calc", "power")

a.add_component("base", "decimalConvert")
a.add_component("base", "baseConvert")
a.build()


if __name__ == "__main__":
create_build_dirs()
logging.basicConfig(
filename=BUILD_DIR / "status.log",
filemode="w",
level=logging.INFO,
format="%(asctime)s - %(module)s->%(funcName)s[%(levelname)s] %(message)s",
)

build()
149 changes: 149 additions & 0 deletions tools/building/compiler_detection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
#####################################################################################################
# Copyright (c) 2023-2024 NWSOFT #
# #
# 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. #
#####################################################################################################

"""
This module is used to detect the C++ compiler on the system, and return the path to it.
If possible, it will cache the compiler path in the build directory, so that it does not have to be detected again.
"""

import json
import logging
import re
import subprocess
from pathlib import Path

from building.paths import BUILD_DIR

GCC_VERSION_RE = re.compile(r"g\+\+ (\(.+?\))? (\d+\.\d+\.\d+)")
CLANG_VERSION_RE = re.compile(r"version (\d+\.\d+\.\d+)")

logger = logging.getLogger(__name__)
logger.setLevel(logging.DEBUG)


def get_identification(compiler_path: Path, compiler_type: int) -> str:
"""
Returns a string that identifies the compiler. This is used to display the compiler in a more user-friendly way.
:param compiler_path: The path to the compiler executable.
:param compiler_type: The type of compiler. 0 for GCC, 1 for Clang, 2 for CL.EXE.
:return: A string that identifies the compiler.
"""
if compiler_type == 0:
# GCC
output = subprocess.check_output(
[compiler_path, "--version"], stderr=subprocess.STDOUT
).decode("utf-8")
matches = GCC_VERSION_RE.search(output)
if matches:
return f"GCC {matches.group(2)}"

elif compiler_type == 1:
# Clang
output = subprocess.check_output(
[compiler_path, "--version"], stderr=subprocess.STDOUT
).decode("utf-8")
matches = CLANG_VERSION_RE.search(output)
if matches:
return f"Clang {matches.group(1)}"
elif compiler_type == 2:
# CL.EXE
output = subprocess.check_output(
[compiler_path, "/?"], stderr=subprocess.STDOUT
).decode("utf-8")
return output.split("\n")[0]

raise ValueError("Unknown compiler type.")


def register_compiler(path: Path) -> None:
"""
Adds the compiler path to the status.json file in the build directory. This is used to cache the compiler path.
"""
with open(BUILD_DIR / "status.json", "w") as f:
json.dump({"compiler": str(path)}, f)


def detect_cxx_compiler() -> Path:
"""
Find a suitable C++ compiler installed on the system. This function will first check the status.json file in the
build directory to see if the compiler has already been detected. If not, it will search for the compiler on the
system.
The function prioritizes Clang over GCC, and GCC over CL.EXE.
:return: The path to the compiler executable.
:raises FileNotFoundError: If no compiler is found.
"""
logger.info("DETECTING C++ COMPILER")
with open(BUILD_DIR / "status.json", "r") as f:
data = json.loads(f.read())
if "compiler" in data.keys():
logger.info(f"USING CACHED COMPILER: {data['compiler']}")
return Path(data["compiler"])

# Is it Clang?
try:
output = (
subprocess.check_output(["which", "clang++"], stderr=subprocess.STDOUT)
.decode("utf-8")
.strip()
)

logger.info(f"FOUND COMPILER: {get_identification(Path(output), 1)}")
path = Path(output)
register_compiler(path)
return path
except (subprocess.CalledProcessError, FileNotFoundError):
pass

# Is it GCC?
try:
output = (
subprocess.check_output(["which", "g++"], stderr=subprocess.STDOUT)
.decode("utf-8")
.strip()
)

logger.info(f"FOUND COMPILER: {get_identification(Path(output), 0)}")
path = Path(output)
register_compiler(path)
return path
except (subprocess.CalledProcessError, FileNotFoundError):
pass

# Is it CL.EXE?
try:
output = (
subprocess.check_output(["where", "cl.exe"], stderr=subprocess.STDOUT)
.decode("utf-8")
.strip()
)

logger.info(f"FOUND COMPILER: {output}")
path = Path(output)
register_compiler(path)
return path
except (subprocess.CalledProcessError, FileNotFoundError):
pass

raise FileNotFoundError("No C++ compiler found.")

0 comments on commit 2b12ada

Please sign in to comment.