During the lifecycle of any long-lived software, circumstances demand nontrivial changes from time to time. Such circumstances can arise in high-performance computing due to, for example, the desire to implement better numerical methods, to use different software stacks, or to adapt the software to run on different platform architectures. As an example, a fundamental paradigm shift in platform hardware architecture occurred in the 1990s from vector machines to RISC machines, and currently another shift is under way due to the proliferation of accelerating devices and other specialized hardware. The extent of necessary modifications and the path to implementing these are more challenging with this latest shift because the codes are much larger and more heterogeneous themselves.