def SaveBuildInformation(log: Log, recipeRecord: Optional[RecipeRecord],
                             recipePackageStateCache: RecipePackageStateCache,
                             path: str) -> None:
        if recipeRecord is None or not PackageRecipeUtil.HasBuildPipeline(
                recipeRecord.SourcePackage):
            return
        if recipeRecord.SourceRecipe is None or recipeRecord.SourceRecipe.ResolvedInstallLocation is None:
            return

        installPath = recipeRecord.SourceRecipe.ResolvedInstallLocation.ResolvedPath

        jsonRootDict = BuildInfoFile.TryCreateJsonBuildInfoRootDict(
            log, path, recipeRecord.SourcePackage, recipeRecord.SourceRecipe,
            recipePackageStateCache)
        if jsonRootDict is None:
            return

        jsonText = json.dumps(jsonRootDict,
                              ensure_ascii=False,
                              sort_keys=True,
                              indent=2,
                              cls=BuildInfoComplexJsonEncoder)

        dstFilePath = IOUtil.Join(installPath, path)
        IOUtil.WriteFileIfChanged(dstFilePath, jsonText)
def __TryValidateInstallation(basicConfig: BasicConfig,
                              validationEngine: ValidationEngine,
                              package: Package, packagesToBuild: List[Package],
                              recipePackageStateCache: RecipePackageStateCache,
                              cmakeConfig: GeneratorCMakeConfig) -> bool:
    if package.ResolvedDirectExperimentalRecipe is None:
        raise Exception("Invalid package")
    sourceRecipe = package.ResolvedDirectExperimentalRecipe
    installPath = sourceRecipe.ResolvedInstallLocation
    if installPath is not None:
        if not IOUtil.IsDirectory(installPath.ResolvedPath):
            basicConfig.LogPrintVerbose(
                2, "Installation directory not located: {0}".format(
                    installPath.ResolvedPath))
            return False
        elif basicConfig.Verbosity >= 2:
            basicConfig.LogPrint(
                "Installation directory located at '{0}'".format(
                    installPath.ResolvedPath))

    # Check if the user decided to do a build override by creating the required file.
    # This allows the user to tell the system that it has been build and it should mind its own buisness
    packageHasUserBuildOverride = False
    if not installPath is None:
        overrideFilename = IOUtil.Join(
            installPath.ResolvedPath,
            __g_BuildPackageInformationOverrideFilename)
        packageHasUserBuildOverride = IOUtil.IsFile(overrideFilename)
        if packageHasUserBuildOverride:
            basicConfig.LogPrint(
                "Package {0} contained a build override file '{1}'".format(
                    package.Name, __g_BuildPackageInformationOverrideFilename))

    if not __RunValidationEngineCheck(validationEngine, package):
        if packageHasUserBuildOverride:
            raise Exception(
                "Package {0} contained a build override file '{1}', but it failed validation. Fix the issues or delete the override file '{2}'"
                .format(package.Name,
                        __g_BuildPackageInformationOverrideFilename,
                        overrideFilename))
        basicConfig.LogPrintVerbose(2, "Install validation failed")
        return False

    # If there is a user build override we dont check the build dependency json file
    if packageHasUserBuildOverride:
        return True

    # If there is no build pipeline we consider the validation to be completed, else we need to check the saved build info
    if not PackageRecipeUtil.HasBuildPipeline(package):
        return True

    if not BuildInfoFileUtil.TryValidateBuildInformation(
            basicConfig, package, packagesToBuild, recipePackageStateCache,
            cmakeConfig, __g_BuildPackageInformationFilename):
        basicConfig.LogPrintVerbose(
            2, "Install validator failed to load build information")
        return False
    return True
    def TryLoadBuildInformation(log: Log, sourcePackage: Package,
                                path: str) -> Optional[JsonDictType]:
        try:
            if not PackageRecipeUtil.HasBuildPipeline(sourcePackage):
                return None

            sourceRecipe = sourcePackage.ResolvedDirectExperimentalRecipe
            if sourceRecipe is None or sourceRecipe.ResolvedInstallLocation is None:
                raise Exception("Invalid recipe")

            # Generally this should not be called if there is no pipeline

            srcFilePath = IOUtil.Join(
                sourceRecipe.ResolvedInstallLocation.ResolvedPath, path)

            fileContent = IOUtil.TryReadFile(srcFilePath)
            if fileContent is None:
                log.LogPrint(
                    "Package build information for package {0} not found in the expected file '{1}'"
                    .format(sourcePackage.Name, srcFilePath))
                return None

            jsonBuildInfoDict = json.loads(fileContent)
            if not BuildInfoFile.IsDictValid(jsonBuildInfoDict):
                log.LogPrint(
                    "Package build information for package {0} found in file '{1}' is invalid"
                    .format(sourcePackage.Name, srcFilePath))
                return None

            # Decode the complex element to a object of the right type
            jsonBuildInfoDict[
                BuildInfoFileElements.
                ContentState] = BuildInfoComplexJsonDecoder.DecodeJson(
                    jsonBuildInfoDict[BuildInfoFileElements.ContentState])
            if BuildInfoFileElements.SourceState in jsonBuildInfoDict:
                jsonBuildInfoDict[
                    BuildInfoFileElements.
                    SourceState] = BuildInfoComplexJsonDecoder.DecodeJson(
                        jsonBuildInfoDict[BuildInfoFileElements.SourceState])

            return cast(JsonDictType, jsonBuildInfoDict)
        except Exception as ex:
            log.LogPrintWarning(
                "TryLoadBuildInformation failed for package '{0}' with {1}".
                format(sourcePackage.Name, ex))
            return None
    def TryValidateBuildInformation(
            log: Log, sourcePackage: Package, packagesToBuild: List[Package],
            recipePackageStateCache: RecipePackageStateCache,
            path: str) -> bool:
        if not PackageRecipeUtil.HasBuildPipeline(sourcePackage):
            return False

        loadedBuildInfoDict = BuildInfoFileHelper.TryLoadBuildInformation(
            log, sourcePackage, path)
        if loadedBuildInfoDict is None:
            return False

        if sourcePackage.ResolvedDirectExperimentalRecipe is None:
            raise Exception("Invalid package")

        # Loaded information
        loadedInfo = BuildInfoFile(loadedBuildInfoDict)
        cachedContentState = loadedInfo.ContentState

        # Load current information
        currentInfoDict = BuildInfoFile.TryCreateJsonBuildInfoRootDict(
            log, path, sourcePackage,
            sourcePackage.ResolvedDirectExperimentalRecipe,
            recipePackageStateCache, cachedContentState,
            loadedInfo.SourceState)
        if currentInfoDict is None:
            log.LogPrint(
                "Failed to create Package build information for package {0}".
                format(sourcePackage.Name))
            return False

        currentInfo = BuildInfoFile(currentInfoDict)

        if loadedInfo.PackageName != currentInfo.PackageName:
            log.LogPrint(
                "The current package name {0} did not match the stored package {1}"
                .format(currentInfo.PackageName, loadedInfo.PackageName))
            return False

        if loadedInfo.RecipeHash != currentInfo.RecipeHash:
            log.LogPrint(
                "The current package recipe hash {0} did not match the stored package hash {1}"
                .format(currentInfo.RecipeHash, loadedInfo.RecipeHash))
            return False

        if len(currentInfo.PackageDependencies) != len(
                loadedInfo.PackageDependencies):
            log.LogPrint(
                "The current package dependencies {0} did not match the stored package dependencies {1}"
                .format(currentInfo.PackageDependencies,
                        loadedInfo.PackageDependencies))
            return False

        if currentInfo.ContentStateHash != loadedInfo.ContentStateHash:
            log.LogPrint(
                "The current package contentStateHash {0} did not match the stored package contentStateHash {1}"
                .format(currentInfo.ContentStateHash,
                        loadedInfo.ContentStateHash))
            return False

        # As long as we trust the hash is unique this check wont be necessary
        #if not BuildInfoFile.Compare(loadedInfo.ContentState, currentInfo.ContentState):
        #    log.LogPrint("The current package build content {0} did not match the stored package content {1}".format(currentInfo.ContentState, loadedInfo.ContentState))
        #    return False

        if currentInfo.SourceStateHash != loadedInfo.SourceStateHash:
            log.LogPrint(
                "The current package sourceStateHash {0} did not match the stored package sourceStateHash {1}"
                .format(currentInfo.SourceStateHash,
                        loadedInfo.SourceStateHash))
            return False

        loadedDependencyDict = {
            dep.Name: dep
            for dep in loadedInfo.DecodedPackageDependencies
        }

        rebuildPackages = set(package.Name for package in packagesToBuild)
        for dependency in currentInfo.DecodedPackageDependencies:
            # Future improvement:
            # If the user just deleted a package the 'newly build package' might not actually have changed
            # that can be checked using the ContentStateHash,
            # however the problem is that we would not have that available before we have build the package
            # meaning the validation check would have to occur while building instead of as a prebuild step
            if dependency.Name in rebuildPackages:
                log.LogPrint(
                    "The dependency {0} is being rebuild so we also rebuild {1}"
                    .format(dependency.Name, loadedInfo.PackageName))
                return False
            if not dependency.Name in loadedDependencyDict:
                log.LogPrint(
                    "The dependency {0} did not exist in the stored package {1}"
                    .format(dependency.Name, loadedInfo.PackageName))
                return False
            loadedDep = loadedDependencyDict[dependency.Name]
            currentState = recipePackageStateCache.TryGet(dependency.Name)
            if currentState is not None and loadedDep.Revision != currentState.ContentStateHash:
                log.LogPrint(
                    "The dependency {0} content revision has changed from {1} to {2}, rebuilding"
                    .format(dependency.Name, currentState.ContentStateHash,
                            loadedDep.Revision))
                return False
        return True