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}'")