Changeset View
Changeset View
Standalone View
Standalone View
contrib/teamcity/build-configurations.py
Show All 24 Lines | |||||
class BuildConfiguration: | class BuildConfiguration: | ||||
def __init__(self, script_root, config_file, build_name=None): | def __init__(self, script_root, config_file, build_name=None): | ||||
self.script_root = script_root | self.script_root = script_root | ||||
self.config_file = config_file | self.config_file = config_file | ||||
self.name = None | self.name = None | ||||
self.config = {} | self.config = {} | ||||
self.script_path = Path() | self.script_path = Path() | ||||
self.build_directory = None | |||||
self.junit_reports_dir = None | |||||
self.test_logs_dir = None | |||||
self.project_root = PurePath( | self.project_root = PurePath( | ||||
subprocess.run( | subprocess.run( | ||||
['git', 'rev-parse', '--show-toplevel'], | ['git', 'rev-parse', '--show-toplevel'], | ||||
capture_output=True, | capture_output=True, | ||||
check=True, | check=True, | ||||
encoding='utf-8', | encoding='utf-8', | ||||
text=True, | text=True, | ||||
▲ Show 20 Lines • Show All 69 Lines • ▼ Show 20 Lines | def load(self, build_name): | ||||
self.script_path = Path(self.script_root.joinpath(script)) | self.script_path = Path(self.script_root.joinpath(script)) | ||||
if not self.script_path.is_file() or not os.access(self.script_path, os.X_OK): | if not self.script_path.is_file() or not os.access(self.script_path, os.X_OK): | ||||
raise FileNotFoundError( | raise FileNotFoundError( | ||||
"The script file {} does not exist or does not have execution permission".format( | "The script file {} does not exist or does not have execution permission".format( | ||||
str(self.script_path) | str(self.script_path) | ||||
) | ) | ||||
) | ) | ||||
# Create the build directory as needed | |||||
self.build_directory = Path( | |||||
self.project_root.joinpath( | |||||
'abc-ci-builds', | |||||
self.name)) | |||||
self.build_directory.mkdir(exist_ok=True, parents=True) | |||||
# Define the junit and logs directories | |||||
self.junit_reports_dir = self.build_directory.joinpath("test/junit") | |||||
self.test_logs_dir = self.build_directory.joinpath("test/log") | |||||
def get(self, key, default): | def get(self, key, default): | ||||
return self.config.get(key, default) | return self.config.get(key, default) | ||||
class UserBuild(): | class UserBuild(): | ||||
def __init__(self, configuration): | def __init__(self, configuration): | ||||
self.configuration = configuration | self.configuration = configuration | ||||
project_root = self.configuration.project_root | project_root = self.configuration.project_root | ||||
build_directory = self.configuration.build_directory | |||||
# Create the build directory as needed | self.artifact_dir = build_directory.joinpath("artifacts") | ||||
self.build_directory = Path( | |||||
project_root.joinpath( | |||||
'abc-ci-builds', | |||||
self.configuration.name)) | |||||
self.build_directory.mkdir(exist_ok=True, parents=True) | |||||
self.artifact_dir = self.build_directory.joinpath("artifacts") | |||||
self.junit_reports_dir = self.build_directory.joinpath("test/junit") | |||||
self.test_logs_dir = self.build_directory.joinpath("test/log") | |||||
# We will provide the required environment variables | # We will provide the required environment variables | ||||
self.environment_variables = { | self.environment_variables = { | ||||
"BUILD_DIR": str(self.build_directory), | "BUILD_DIR": str(build_directory), | ||||
"CMAKE_PLATFORMS_DIR": project_root.joinpath("cmake", "platforms"), | "CMAKE_PLATFORMS_DIR": project_root.joinpath("cmake", "platforms"), | ||||
"THREADS": str(os.cpu_count() or 1), | "THREADS": str(os.cpu_count() or 1), | ||||
"TOPLEVEL": str(project_root), | "TOPLEVEL": str(project_root), | ||||
} | } | ||||
# Build 2 log files: | # Build 2 log files: | ||||
# - the full log will contain all unfiltered content | # - the full log will contain all unfiltered content | ||||
# - the clean log will contain the same filtered content as what is | # - the clean log will contain the same filtered content as what is | ||||
# printed to stdout. This filter is done in print_line_to_logs(). | # printed to stdout. This filter is done in print_line_to_logs(). | ||||
self.logs = {} | self.logs = {} | ||||
self.logs["clean_log"] = self.build_directory.joinpath( | self.logs["clean_log"] = build_directory.joinpath( | ||||
"build.clean.log") | "build.clean.log") | ||||
if self.logs["clean_log"].is_file(): | if self.logs["clean_log"].is_file(): | ||||
self.logs["clean_log"].unlink() | self.logs["clean_log"].unlink() | ||||
self.logs["full_log"] = self.build_directory.joinpath("build.full.log") | self.logs["full_log"] = build_directory.joinpath("build.full.log") | ||||
if self.logs["full_log"].is_file(): | if self.logs["full_log"].is_file(): | ||||
self.logs["full_log"].unlink() | self.logs["full_log"].unlink() | ||||
def copy_artifacts(self, artifacts): | def copy_artifacts(self, artifacts): | ||||
# Find and copy artifacts. | # Find and copy artifacts. | ||||
# The source is relative to the build tree, the destination relative to | # The source is relative to the build tree, the destination relative to | ||||
# the artifact directory. | # the artifact directory. | ||||
# The artifact directory is located in the build directory tree, results | # The artifact directory is located in the build directory tree, results | ||||
# from it needs to be excluded from the glob matches to prevent infinite | # from it needs to be excluded from the glob matches to prevent infinite | ||||
# recursion. | # recursion. | ||||
for pattern, dest in artifacts.items(): | for pattern, dest in artifacts.items(): | ||||
matches = [m for m in sorted(self.build_directory.glob( | matches = [m for m in sorted(self.configuration.build_directory.glob( | ||||
pattern)) if self.artifact_dir not in m.parents and self.artifact_dir != m] | pattern)) if self.artifact_dir not in m.parents and self.artifact_dir != m] | ||||
dest = self.artifact_dir.joinpath(dest) | dest = self.artifact_dir.joinpath(dest) | ||||
# Pattern did not match | # Pattern did not match | ||||
if not matches: | if not matches: | ||||
continue | continue | ||||
# If there is a single file, destination is the new file path | # If there is a single file, destination is the new file path | ||||
▲ Show 20 Lines • Show All 47 Lines • ▼ Show 20 Lines | class UserBuild(): | ||||
async def run_build(self, args=[]): | async def run_build(self, args=[]): | ||||
proc = await asyncio.create_subprocess_exec( | proc = await asyncio.create_subprocess_exec( | ||||
*([str(self.configuration.script_path)] + args), | *([str(self.configuration.script_path)] + args), | ||||
# Buffer limit is 64KB by default, but we need a larger buffer: | # Buffer limit is 64KB by default, but we need a larger buffer: | ||||
limit=1024 * 256, | limit=1024 * 256, | ||||
stdout=asyncio.subprocess.PIPE, | stdout=asyncio.subprocess.PIPE, | ||||
stderr=asyncio.subprocess.STDOUT, | stderr=asyncio.subprocess.STDOUT, | ||||
cwd=self.build_directory, | cwd=self.configuration.build_directory, | ||||
env={ | env={ | ||||
**os.environ, | **os.environ, | ||||
**self.environment_variables, | **self.environment_variables, | ||||
**self.configuration.get("env", {}) | **self.configuration.get("env", {}) | ||||
}, | }, | ||||
) | ) | ||||
await asyncio.wait([ | await asyncio.wait([ | ||||
Show All 17 Lines | async def wait_for_build(self, timeout, args=[]): | ||||
message = "Build {} timed out after {:.1f}s".format( | message = "Build {} timed out after {:.1f}s".format( | ||||
self.configuration.name, round(timeout, 1) | self.configuration.name, round(timeout, 1) | ||||
) | ) | ||||
# The process is killed, set return code to 128 + 9 (SIGKILL) = 137 | # The process is killed, set return code to 128 + 9 (SIGKILL) = 137 | ||||
return_code = 137 | return_code = 137 | ||||
finally: | finally: | ||||
self.print_line_to_logs(message) | self.print_line_to_logs(message) | ||||
build_directory = self.configuration.build_directory | |||||
# Always add the build logs to the root of the artifacts | # Always add the build logs to the root of the artifacts | ||||
artifacts = { | artifacts = { | ||||
**self.configuration.get("artifacts", {}), | **self.configuration.get("artifacts", {}), | ||||
str(self.logs["full_log"].relative_to(self.build_directory)): "", | str(self.logs["full_log"].relative_to(build_directory)): "", | ||||
str(self.logs["clean_log"].relative_to(self.build_directory)): "", | str(self.logs["clean_log"].relative_to(build_directory)): "", | ||||
str(self.junit_reports_dir.relative_to(self.build_directory)): "", | str(self.configuration.junit_reports_dir.relative_to(build_directory)): "", | ||||
str(self.test_logs_dir.relative_to(self.build_directory)): "", | str(self.configuration.test_logs_dir.relative_to(build_directory)): "", | ||||
} | } | ||||
self.copy_artifacts(artifacts) | self.copy_artifacts(artifacts) | ||||
return (return_code, message) | return (return_code, message) | ||||
def run(self, args=[]): | def run(self, args=[]): | ||||
if self.artifact_dir.is_dir(): | if self.artifact_dir.is_dir(): | ||||
▲ Show 20 Lines • Show All 116 Lines • Show Last 20 Lines |