With some work, an unlike-compiler% instance can support ongoing builds that perform only the work thats relevant to reported changes.
If you call compile! again, it will simply return the same output it already prepared unless you report that there were changes.
(send compiler compile! #:changed (clarify/multi compiler "/etc/config") #:removed (clarify/multi compiler "leaked-xmas-photo.png" "only-production-database-backup.sql"))
Unlike add!, which can be called at any time, #:changed and #:removed assets can only be declared after a prior successful call to compile!. Each following call to compile! will adapt an underlying graph to reflect any assets that were since changed or removed. Depending on the activity, dependent assets will regress to a value they once had in an attempt to incorporate changes while minimizing rework.
First, #:removed assets are deleted from the graph, as well as any edges connecting that that asset to others. Dependent assets are unconditionally rebuilt, but changes ripple from there according to Ripple Procedures. The compiler will raise exn:fail if any of the names marked as removed are already absent from the system.
#:changed assets then regress to a value returned from delegate. This is functionally equivalent to marking an asset to rebuild from scratch. Changes will ripple to dependents according to Ripple Procedures. This process will raise exn:fail if any #:changed assets that were not marked as removed are absent from the system.
The first argument is the clear name of the asset that has been changed.
The second argument is the clear name of the dependent asset.
The ripple procedure must return an element from this list. Otherwise the compiler will ignore the value, issue a <warning, and rebuild the dependent asset from scratch.
Internally, the compiler will only propogate a change further if the regressed value is not eq? to its previous value. The first element of the asset value’s history is always eq? to its current value, so having a ripple/c procedure return the first element of the history will both maintain the current value of the asset and prevent change from propogating further.
(define (replace-link-node) '...) (define (immune changed/clear dependent/clear dependent/history) (car dependent-history)) (define (rebuild changed/clear dependent/clear dependent/history) (last dependent-history)) (define (partial changed/clear dependent/clear dependent/history) replace-link-node) (send compiler add! "index.html") (send compiler add! "styles.css") (send compiler add! "about.html" "index.html" rebuild) (send compiler add! "contact.html" "about.html" immune) (send compiler add! "styles.css" "about.html" partial) (send compiler add! "styles.css" "contact.html" partial) (send compiler compile!)
In this example we compile a website of interdependent pages and walk through the behavior of each subsequent build.
(send compiler compile! #:changed '("about.html"))
Compile assuming that "about.html" has changed.
Rebuilds both "about.html" and "index.html".
(send compiler compile! #:changed '("contact.html"))
The website is rebuilt assuming only "contact.html" has changed. The relationship between "contact.html" and "about.html" is such that the latter will not change when the former changes. Not only that, "index.html" will also not change because propagation stopped at "about.html". In this scenario, only one webpage is fully rebuilt.
(send compiler compile! #:changed '("styles.css"))
(send compiler compile! #:removed '("about.html"))
The website is rebuilt assuming only "about.html" was removed. "about.html" sat between "index.html" and "contact.html", which means that "contact.html" is no longer a dependency for any HTML asset. The compiler will still attempt to advance all assets, but does not guarentee that the output will function as expected. This state will likely result in a broken or missing link on a web page.
"index.html" is marked to build from scratch, but not because of its declared relationship with "about.html" this time around. Asset removal causes a full rebuild of dependents. If "index.html" had dependents, the change implied by this rebuild would ripple normally.
Always rebuilding dependents of a removed asset allows ripple/c procedures to assume that dependencies exist. Any errors that come as a result of removal would happen as per the user’s implementation and change propogation should not make it harder to find the root cause.
CAUTION: If you make a new instance of an unlike-compiler% subclass and start a fresh build using the same assets, it might not produce the same graph as an existing instance that removed assets from an older graph. This is a source of entropy that may warrant periodically discarding a compiler instance, collecting garbage, and beginning anew.