OpenMiInk/third_party/krita-ci-utilities/components/CommonUtils.py
Hellings b8c8ff98e2 Hi
2026-10-16 09:05:56 -04:00

157 lines
No EOL
6.3 KiB
Python

import os
import sys
import shutil
import hashlib
import collections
import subprocess
import logging
from components import PlatformFlavor
# Returns the absolute path to the base directory of the CI Tooling checkout we are running from
def scriptsBaseDirectory():
# Where is this file located?
fileLocation = os.path.dirname( os.path.realpath(__file__) )
# We are at /components, therefore the relative location of the base directory is ../
relativeLocation = '../'
# Finally we can deduce the absolute path to the base directory
return os.path.realpath( os.path.join(fileLocation, relativeLocation) )
# Determines where the build directory should be located
# This is done based on the provided source directory and the definition of whether this should be an in or out of source build
def buildDirectoryForSources( sources, inSourceBuild ):
# If we are an in source build, then the build and source directories are the same
if inSourceBuild:
return sources
# Otherwise, it is a folder named 'build' within the source tree
return os.path.join( sources, 'build' )
# Determines which of the given files exists first in the specified directory
def firstPresentFile( searchRoot, filesToFind ):
# Let's start searching....
availableFiles = [ filename for filename in filesToFind if os.path.isfile(os.path.join(searchRoot, filename)) ]
# Return the first hit we found, returning an empty string if none were found
return next( iter(availableFiles), '' )
# Determines where the first of a given set of files is in a given set of directories
def firstPresentFileInPaths( searchRoots, filesToFind ):
# Start searching...
validPaths = []
for rootPath in searchRoots:
# Look in this directory
foundFile = firstPresentFile( rootPath, filesToFind )
# If we found something, we're finished
if foundFile != '':
# Make a usable path and return it
return os.path.join( rootPath, foundFile )
# If we failed to find anything, return blank
return ''
def recursiveDirectoryCopy(sourceDirectory, destinationDirectory):
# Copy a directory structure overwriting existing files
for root, dirs, files in os.walk(sourceDirectory):
# Ensure we have a relative path for root as we'll need it quite a bit shortly
currentDirectory = os.path.relpath( root, sourceDirectory )
# Make sure the directory exists in our destination
currentDestination = os.path.join( destinationDirectory, currentDirectory )
if not os.path.isdir(currentDestination):
os.makedirs(currentDestination)
# Now we can copy the various files within in turn
for fileToCopy in files:
# Determine the full path to the source file
fileSource = os.path.join(root, fileToCopy)
# Determine where to copy the file to
fileDestination = os.path.join(currentDestination, fileToCopy)
# Copy it!
shutil.copyfile( fileSource, fileDestination )
# Converts a path to a relative one, to allow for it to be passed to os.path.join
# This is primarily relevant on Windows, where full paths have the drive letter, and thus can be simply joined together as you can on Unix systems
def makePathRelative(path):
# If we're on Windows, chop the drive letter off...
if sys.platform == "win32":
return path[3:]
# Otherwise we just drop the starting slash off
return path[1:]
# Convenience function to generate the SHA-256 hash of a given file
# We read files in small chunks, to ensure we can handle large files if needed
def generateFileChecksum( filenameToHash ):
# Grab our hasher
hasher = hashlib.sha256()
# Open the file
with open(filenameToHash, 'rb') as fileToHash:
# Read chunks until there are no more, passing them to the hasher as we go
fileChunk = fileToHash.read(65336)
while len(fileChunk) > 0:
hasher.update(fileChunk)
fileChunk = fileToHash.read(65336)
# All done, return the generated hash
return hasher.hexdigest()
# Convenience function to recursively merge Python dictionaries
# This is of particular importance to the configuration loading code
def recursiveUpdate(d, u):
# Start by going over all the items in the dictionary that is being merged in
for key, value in u.items():
# If our value contains a mapping (dict, list, etc) then recurse into it
# Otherwise just update the value
if isinstance(value, collections.abc.Mapping):
d[key] = recursiveUpdate( d.get(key, {}), value )
else:
d[key] = value
# Return the merged values
return d
def detectObjdump():
for arg in ("objdump", "llvm-objdump"):
commandToRun = f"{arg} --version"
ret = subprocess.call(commandToRun, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, shell=True)
if ret != 1:
return arg
return None
def boolFromEnv(value : str) -> bool:
return value.lower() in ['true', '1', 't', 'y', 'yes']
def useVerbosePackagingLog() -> bool:
return boolFromEnv(os.environ.get('KRITACI_VERBOSE_PACKAGING', '0'))
def createLogger(logFileName : str):
logger = logging.getLogger()
logger.setLevel(logging.DEBUG)
fileFormatter = logging.Formatter("## %(asctime)s %(levelname)s: %(message)s")
fileHandler = logging.FileHandler(logFileName, mode='w')
fileHandler.setLevel(logging.DEBUG)
fileHandler.setFormatter(fileFormatter)
stdoutFormatter = logging.Formatter("## %(levelname)s: %(message)s")
stdoutHandler = logging.StreamHandler(sys.stdout)
stdoutHandler.setLevel(logging.DEBUG if useVerbosePackagingLog() else logging.INFO)
stdoutHandler.setFormatter(stdoutFormatter)
logger.addHandler(fileHandler)
logger.addHandler(stdoutHandler)
return logger
def globPatternsForBinaries(platform : PlatformFlavor) -> tuple:
if platform.matches(['Windows']):
return ('*.exe', '*.com', '*.dll', '*.pyd')
elif platform.matches(['Linux']):
return ('*.so', '*.so.[0-9]*', 'krita', 'kritarunner', 'ffmpeg', 'ffprobe')
elif platform.matches(['MacOS', 'Android']):
raise Exception(f"Platform '{platform}' is currently not supported for debug splitting")
else:
raise Exception(f"Unknown platform '{platform}'")