diff --git a/pym/bob/builder.py b/pym/bob/builder.py index 02792a95..14232b54 100644 --- a/pym/bob/builder.py +++ b/pym/bob/builder.py @@ -65,6 +65,17 @@ def hashWorkspace(step): return hashDirectory(step.getStoragePath(), os.path.join(os.path.dirname(step.getWorkspacePath()), "cache.bin")) +class HashOnce: + def __init__(self, step): + self.step = step + self.result = None + def hashWorkspace(self): + if self.result is None: + self.result = hashWorkspace(self.step) + return self.result + def invalidate(self): + self.result = None + CHECKOUT_STATE_VARIANT_ID = None # Key in checkout directory state for step variant-id CHECKOUT_STATE_BUILD_ONLY = 1 # Key for checkout state of build-only builds @@ -1104,9 +1115,10 @@ async def _cookCheckoutStep(self, checkoutStep, depth): checkoutState = checkoutStep.getScmDirectories().copy() checkoutState[CHECKOUT_STATE_VARIANT_ID] = (checkoutDigest, None) checkoutState[CHECKOUT_STATE_BUILD_ONLY] = checkoutBuildOnlyState(checkoutStep, checkoutInputHashes) + currentResultHash = HashOnce(checkoutStep) if self.__buildOnly and (BobState().getResultHash(prettySrcPath) is not None): inputChanged = checkoutBuildOnlyStateChanged(checkoutState, oldCheckoutState) - rehash = lambda: hashWorkspace(checkoutStep) + rehash = currentResultHash.hashWorkspace if checkoutStep.mayUpdate(inputChanged, BobState().getResultHash(prettySrcPath), rehash): if checkoutBuildOnlyStateCompatible(checkoutState, oldCheckoutState): with stepExec(checkoutStep, "UPDATE", @@ -1115,6 +1127,7 @@ async def _cookCheckoutStep(self, checkoutStep, depth): newCheckoutState = oldCheckoutState.copy() newCheckoutState[CHECKOUT_STATE_BUILD_ONLY] = checkoutState[CHECKOUT_STATE_BUILD_ONLY] BobState().setDirectoryState(prettySrcPath, newCheckoutState) + currentResultHash.invalidate() # force recalculation else: stepMessage(checkoutStep, "UPDATE", "WARNING: recipe changed - cannot update ({})" .format(prettySrcPath), WARNING) @@ -1138,10 +1151,21 @@ async def _cookCheckoutStep(self, checkoutStep, depth): # Do not use None here to distinguish it from a non-existent directory. oldCheckoutState[scmDir] = (False, scmSpec) - if (self.__force or (not checkoutStep.isDeterministic()) or - (BobState().getResultHash(prettySrcPath) is None) or - not compareDirectoryState(checkoutState, oldCheckoutState) or - (checkoutInputHashes != BobState().getInputHashes(prettySrcPath))): + oldResultHash = BobState().getResultHash(prettySrcPath) + checkoutReason = None + if self.__force: + checkoutReason = "forced" + elif not checkoutStep.isDeterministic(): + checkoutReason = "indeterminsitic" + elif not compareDirectoryState(checkoutState, oldCheckoutState): + checkoutReason = "recipe changed" + elif (checkoutInputHashes != BobState().getInputHashes(prettySrcPath)): + checkoutReason = "input changed" + elif (checkoutStep.getMainScript() or checkoutStep.getPostRunCmds()) \ + and (oldResultHash != currentResultHash.hashWorkspace()): + checkoutReason = "workspace changed" + + if checkoutReason: # Switch or move away old or changed source directories. In # case of nested SCMs the loop relies on the property of # checkoutsFromState() to return the directories in a top-down @@ -1219,10 +1243,11 @@ async def _cookCheckoutStep(self, checkoutStep, depth): BobState().setResultHash(prettySrcPath, oldCheckoutHash) with stepExec(checkoutStep, "CHECKOUT", - "{} {}".format(prettySrcPath, overridesString)) as a: + "{} ({}) {}".format(prettySrcPath, checkoutReason, overridesString)) as a: await self._runShell(checkoutStep, "checkout", a) self.__statistic.checkouts += 1 checkoutExecuted = True + currentResultHash.invalidate() # force recalculation # reflect new checkout state BobState().setDirectoryState(prettySrcPath, checkoutState) BobState().setInputHashes(prettySrcPath, checkoutInputHashes) @@ -1233,7 +1258,7 @@ async def _cookCheckoutStep(self, checkoutStep, depth): # We always have to rehash the directory as the user might have # changed the source code manually. - checkoutHash = hashWorkspace(checkoutStep) + checkoutHash = currentResultHash.hashWorkspace() # Generate audit trail before setResultHash() to force re-generation if # the audit trail fails.