Repository navigation
Give the build six names instead of one - #35
Merged
Merged
Conversation
The build was one opaque operation. Something went red and the only thing you knew was that something had gone red. It is now six named steps in a fixed order: resolve, cite, execute, generate, assemble, check. The names are worth having because the failure now has a place. A red execute is a lesson whose code is wrong. A red check is a lesson whose code is right and whose committed page is out of date. Those need different things done about them and they used to look the same. A step that finds a problem is the last step that runs, because every step reads what the one before it produced. Output from a step standing on a failed one is not a second opinion, it is noise with a line number. Inside a step it is the other way round: all the lessons execute even after the first one fails, since those really are independent. resolve is new. It reads pin.json and global.json, asks the SDK which version of itself is about to compile everything, and refuses a build that cannot say what it is standing on: no pin, a second pin below the top of the repository, no global.json, half a pin, or a pin and a global.json naming different SDK versions once the pin lands. Today those two files disagree on purpose, which is a thing the build now says out loud rather than a thing nobody had noticed. Nothing is written until step six. Every generator hands its work to a plan and the plan is settled once at the end, so build and check are one code path with one flag rather than a decision repeated in every generator. That also makes two new things possible for free. A file two generators both want to produce is now an error rather than a race nobody sees. And a generated file the build no longer produces, which is what a renamed block leaves behind, is reported by check and deleted by build instead of sitting on disk forever being a second answer to a question that has one. check --selftest grows two cases for those, and one that deletes the pin and requires the build to stop at step one rather than produce five steps of output anyway. Nine cases now, two of which change nothing and have to pass. CI drops from three check steps to one. It runs the whole repository with --offline, and cite keeps the separate job it already had, because a citation resolves or it does not and the answer does not depend on which of the four machines asked.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The M1 line that says "the six step build: resolve, verify citations, execute, generate, assemble, check". They now exist, they are what runs, and the tool prints them.
What was wrong with one step
The build was one operation. It went red, and what you knew was that it had gone red. Working out which of several unrelated things had happened meant reading the log from the top.
The two failures worth telling apart are these. A lesson whose code is wrong. A lesson whose code is right and whose committed page is out of date. The first one is a bug you go and fix. The second one is a
buildyou forgot to run and commit. They took the same amount of squinting to identify and they should not.The six
Every number there is worth reading. Zero assertions held would mean the assertion checker is running and checking nothing. Zero citations is the honest count today and will be a lie the day after the pin lands.
Written up in docs/the-build.md, with a picture.
A step that finds a problem is the last step that runs
Not tidiness. Every step reads what the one before it produced, so output from a step standing on a failed one is not a second opinion, it is noise with a line number in it. A lesson whose code did not run has no captured output, and the page assembled around the hole where that output belongs reports a second failure that is the first one wearing a hat.
Inside a step it goes the other way. All the lessons execute even after the first one fails, because those really are independent, and hearing about three broken lessons in one run is worth a longer log.
resolve is new
It reads
pin.jsonandglobal.json, asks the SDK which version of itself is about to compile everything, and reports the platform. Then it refuses a build that cannot say what it is standing on:pin.jsonat the top of the repository.pin.jsonsomewhere below it. Two pins is the state where half the pages resolve against one commit and half against another.global.json, or one with no SDK version, which means two people cloning this can be compiling it with two different compilers.global.jsonnaming different SDK versions, once the pin has landed.That last one is worth being plain about. Those two files disagree right now, on purpose: the citations will be written against .NET 11 and the tooling builds on 10. That was true before this pull request and nothing anywhere said it. Now the first line of every build says it, and the day
pin.jsongets a version the disagreement stops being allowed.Nothing is written until step six
Every generator used to write its own files as it went. They now hand their work to a plan, and the plan is settled once, at the end. So
buildandcheckare one code path with one flag rather than the same decision made independently in four places.Two things fall out of that for free.
A file two generators both want to produce is an error. Before, whichever ran second won, the build was green, and half the work quietly did not appear on the page.
A generated file the build no longer produces gets found. Rename a block and its old captured output stays on disk forever, unread and uncompared, and the next person to open the directory finds two files where one of them is the truth.
checknames it,builddeletes it. Only directories this build put something into are swept, so a lesson nobody has built yet is not accused of having leftovers.The self test grows
check --selftestis at nine cases, two of which change nothing and have to pass.The pin case is the one that is not about a generated file. It deletes
pin.jsonand requires two things: that the build says what is missing, and that it stops at step one rather than going on to produce five steps worth of output about a repository it cannot describe. An expectation is now a list rather than a single string, so a case can insist on both.CI
Three steps in the
lessonsjob become one. It runs the whole repository rather than three named directories, so nothing gets left out by a path somebody forgot to add.--offlineskips the cite step, which keeps the separate job it already had: a citation resolves or it does not, and the answer does not depend on which of the four machines asked.Checked locally
Also checked by hand:
buildtwice in a row leaves the tree clean, a planted file underexpected/is reported bycheckand removed bybuild, andcheck ../lessonsfrom insidedocs/still passes, which is the working directory bug #34 fixed and this refactor could have brought back.