diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 6762acb..5a2f0e4 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -154,6 +154,9 @@ jobs: - name: Pylint Lint run: pylint --rcfile pyproject.toml src/ tests/ + # Major bug alert. This is a job with a matrix and there's a 4+ year old bug + # where matrix jobs don't properly report status. + # https://github.com/orgs/community/discussions/26822 test-cov-job: needs: [fight-github-job, lint-types-job] name: "Tests & Coverage" @@ -222,8 +225,29 @@ jobs: # SemVer be damned! include-hidden-files: true - cov-report-job: + # Here's the workaround for GitHub matrix jobs not having proper status + # reporting. + # https://github.com/orgs/community/discussions/26822#discussioncomment-5122101 + # https://github.com/sounisi5011/npm-packages/blob/2a5ca2de696eeb8b40a38de90580441c4c6c96e0/.github/workflows/ci.yaml#L482-L498 + the-sad-did-github-matrix-succeed-job: needs: [fight-github-job, test-cov-job] + name: "Did Test & Coverage Succeed?" + # Note the different condition here. If it wasn't always(), PR's on + # protected branches would never run. + if: ${{always()}} + runs-on: ubuntu-latest + steps: + - name: "Why is this not part of a matrix job?" + if: >- + ${{ + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + }} + run: exit 1 + + + cov-report-job: + needs: [fight-github-job, the-sad-did-github-matrix-succeed-job] name: "Coverage Report" # Only run on changed code files. if: ${{needs.fight-github-job.outputs.code == 'true'}} diff --git a/.vscode/ltex.dictionary.en-US.txt b/.vscode/ltex.dictionary.en-US.txt index 3bacfa4..c10a939 100644 --- a/.vscode/ltex.dictionary.en-US.txt +++ b/.vscode/ltex.dictionary.en-US.txt @@ -63,3 +63,17 @@ Otten IntelliSense Batchelder scriv +jekyll +Wintellect +CodeSpaces +Arie +Bovenberg +actionlint +rhysd +NuMega +monorepo +@dorny +%raw% +%endraw% +hack-arounds +mergeable diff --git a/.vscode/ltex.hiddenFalsePositives.en-US.txt b/.vscode/ltex.hiddenFalsePositives.en-US.txt index fad841a..f4f5663 100644 --- a/.vscode/ltex.hiddenFalsePositives.en-US.txt +++ b/.vscode/ltex.hiddenFalsePositives.en-US.txt @@ -26,3 +26,10 @@ {"rule":"POSSESSIVE_APOSTROPHE","sentence":"^\\QThe examples directory holds sample Tiny BASIC programs for your enjoyment.\\E$"} {"rule":"UNLIKELY_OPENING_PUNCTUATION","sentence":"^\\Q:steam_locomotive: Change Log\\E$"} {"rule":"WORD_CONTAINS_UNDERSCORE","sentence":"^\\Q:steam_locomotive: Change Log\\E$"} +{"rule":"UNLIKELY_OPENING_PUNCTUATION","sentence":"^\\Q:scream_cat: ^6: Why does it seem Ruby is so hard to install on macOS?\\E$"} +{"rule":"WORD_CONTAINS_UNDERSCORE","sentence":"^\\Q:scream_cat: ^6: Why does it seem Ruby is so hard to install on macOS?\\E$"} +{"rule":"UNLIKELY_OPENING_PUNCTUATION","sentence":"^\\Q:joy_cat: Some of you know where this is going.\\E$"} +{"rule":"WORD_CONTAINS_UNDERSCORE","sentence":"^\\Q:joy_cat: Some of you know where this is going.\\E$"} +{"rule":"TOO_LONG_PARAGRAPH","sentence":"^\\Q^8: And pointing the fickle finger of blame!\\E$"} +{"rule":"UNLIKELY_OPENING_PUNCTUATION","sentence":"^\\Q:crossed_fingers: (Thanks for your patience!)\\E$"} +{"rule":"WORD_CONTAINS_UNDERSCORE","sentence":"^\\Q:crossed_fingers: (Thanks for your patience!)\\E$"} diff --git a/CHANGELOG.md b/CHANGELOG.md index fe3ac49..c7ef1cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,16 @@ --- +## 0.9.1 (2024-09-09) + +--- + +- Fixed CI.yml to work around the bug in GitHub Actions where matrix jobs don't have any status of the sub jobs. See [#41](https://github.com/John-Robbins/tbp/issues/41). +- Added the [GitHub and GitHub Actions](https://john-robbins.github.io/tbp/project-notes#github-and-github-actions) section to the documentation. +- Added the pyreadline3 dependency to the [documentation](https://john-robbins.github.io/tbp/project-notes#small-python-friction-points). +- Updated the README and [Getting Started Installation](https://john-robbins.github.io/tbp/getting-started#installation) section to point to the Latest releases. +- This is basically a practice release to ensure I have the steps down. :crossed_fingers: (Thanks for your patience!) + ## 0.9.0 (2024-09-06) --- diff --git a/README.md b/README.md index 342fa0a..00f695a 100644 --- a/README.md +++ b/README.md @@ -60,7 +60,7 @@ It seemed nuts to put this up on PyPI, which is for important modules, not learn ### Cloning and Installing -Download the code from here as a .ZIP file. Expand that file into a directory, then execute the following. +Download the code from the [Latest](https://github.com/John-Robbins/tbp/releases/latest) release. Expand the compressed file into a directory, then execute the following. ```bash % pip install . diff --git a/docs/_data/version.yml b/docs/_data/version.yml index e6b3e5b..5f36498 100644 --- a/docs/_data/version.yml +++ b/docs/_data/version.yml @@ -1 +1 @@ -number: 0.9.0 \ No newline at end of file +number: 0.9.1 \ No newline at end of file diff --git a/docs/docs/getting-started.md b/docs/docs/getting-started.md index 436c43a..5cdce1c 100644 --- a/docs/docs/getting-started.md +++ b/docs/docs/getting-started.md @@ -28,7 +28,7 @@ It seemed nuts to put this up on PyPI, which is for important modules, not learn ### Cloning and Installing -Download the code from here as a .ZIP file. Expand that file into a directory, then execute the following. +Download the code from the [Latest](https://github.com/John-Robbins/tbp/releases/latest) release. Expand the compressed file into a directory, then execute the following. ```bash % pip install . diff --git a/docs/docs/thoughts.md b/docs/docs/thoughts.md index 6df4a5b..12bfada 100644 --- a/docs/docs/thoughts.md +++ b/docs/docs/thoughts.md @@ -19,7 +19,9 @@ permalink: project-notes Through the development of Tiny BASIC in Python (tbp), I kept notes on where I screwed up, got confused, loved something, explained my thinking, or got frustrated. These are not necessary to enjoy the Tiny BASIC life, but I thought they might be interesting to others. -## My Kingdom for an EBNF Grammar Checker +## Code and Problem Thoughts + +### My Kingdom for an EBNF Grammar Checker The very first task I wanted to complete was to build a solid Extended Backus-Naur form (EBNF) grammar for the Tiny BASIC language by hand. The file is [tiny_basic_grammar.ebnf](https://github.com/John-Robbins/tbp/blob/main/docs/tbp/tiny_basic_grammar.ebnf) in the tbp repository. Building robust grammars for a language is hard. There are a million edge cases and surprises that are waiting around dark corners to scare you. All the compiler textbooks discuss creating grammars extensively, and you start to realize that if you screw up the grammar in the beginning, you’ve made your life terrible. @@ -37,7 +39,7 @@ Wrestling with some left recursion problems, I found there are more sites dedica Obviously, I don't have a complete grasp of grammar development. My plan is to work through Chapter 2 in the [Dragon Book](https://www.malaprops.com/book/9780321486813) very carefully. -## The Grade for My Scanner: C- +### The Grade for My Scanner: C- Bob Nystrom's jlox implementation in his book [Crafting Interpreters](http://www.craftinginterpreters.com) *highly* influenced tbp's design. [^3] His was the first implementation I fully understood, so it was obvious I should go down the same path. However, I think I should have done more thinking and designing on the scanner before jumping into the implementation. I'll own the screw-up. @@ -65,7 +67,7 @@ Recall from the [Tiny Basic Language](tb-language) that the [`PRINT`](tb-languag In the end, with too much special casing, I have a scanner that works, so that's good, but I'm not very proud of it. Remember folks, think before you type. -## The Grade for My Parser and Interpreter: B/B- +### The Grade for My Parser and Interpreter: B/B- Again, if you've read [Crafting Interpreters](http://www.craftinginterpreters.com) the tbp parse and interpreter should feel very familiar. As they stand now, both are solid, but it was some work to get there for both. @@ -75,7 +77,7 @@ The interpreter bothers me a little because the class is so big. I don't know if Another thing that bothers me about both the `Parser` and `Interpreter` classes is that they both do output for reporting errors and general execution. It was convenient to do that, but it feels dirty to me. -## The Grade for tbp: B- +### The Grade for tbp: B- The good news is that tbp has all the functionality I set out to build. While I think there are many places, see above, where I could have done better, never underestimate the power of working code. One area I think I did well was in reporting errors. Each error shows the line of code and has the little arrow drawing to the column where the error is on the line. @@ -212,7 +214,9 @@ I do love what mypy is doing, but I think this is a result of bolting on a good Another small stumbling block was that while Python has easy to use [`list`](https://docs.python.org/3/tutorial/datastructures.html#more-on-lists) and [`dict`](https://docs.python.org/3/tutorial/datastructures.html#dictionaries) data structures, I was surprised that sorted lists and dictionaries were not part of the Python Standard Library. I wanted to keep the Tiny BASIC program in a dictionary with the key being the line number and the associated item the AST types. From the Python documentation, it looked like I could use the [OrderedDict](https://docs.python.org/3/library/collections.html#collections.OrderedDict), but that only does insertion order. If someone has a thousand-line program in tbp, and they want to insert a line in the middle, it looks like I'd be inserting and calling [sorted](https://docs.python.org/3/library/functions.html#sorted) constantly which returns a new dictionary every time. I've done a ton of C# programming in the past and have an intimate knowledge of memory management, both native and managed, as well as garbage collection algorithms. I knew using the standard library wasn't correct or scalable for my problem domain. -Fortunately, I found Grant Jenkins' [Python Sorted Containers](https://grantjenks.com/docs/sortedcontainers/), which is massive overkill for what I needed, but it did allow me to focus on my problem domain instead of me spending a lot of time developing my own implementation. If you use Sorted Containers, Hal Blackburn did a great job building the necessary [type stubs](https://github.com/h4l/sortedcontainers-stubs), so you get all the mypy goodness. +Fortunately, I found [Grant Jenkins](https://github.com/grantjenks)' [Python Sorted Containers](https://grantjenks.com/docs/sortedcontainers/), which is massive overkill for what I needed, but it did allow me to focus on my problem domain instead of me spending a lot of time developing my own implementation. If you use Sorted Containers, [Hal Blackburn](https://github.com/h4l) did a great job building the necessary [type stubs](https://github.com/h4l/sortedcontainers-stubs), so you get all the mypy goodness. + +The only other dependency for tbp is [pyreadline3](https://github.com/pyreadline3/pyreadline3), which is necessary to mimic the built-in Python [readline](https://docs.python.org/3/library/readline.html#module-readline) library for Windows. Using arrow keys and line editing is a must-have feature. It is weird that out of the box Python does not have common, simple line editing across all three operating systems. ### Python Struggles @@ -246,7 +250,274 @@ Obviously, the Python community has done and will continue to do wonderful thing Please understand I'm just trying to report my story of trying to develop my first Python module as a novice Python developer in 2024. The problems might be all on me, but it felt much harder than it should have been. Python desperately needs an equivalent to Rust's glorious [Cargo Book](https://doc.rust-lang.org/cargo/). -### What's Next for Tiny BASIC in Python? +## GitHub and GitHub Actions + +While I started development before git was a gleam in Linus Torvalds' eye, I'd used git and GitHub quite a bit back in the olden days. However, in the time I've been away from the world of coding, there's been some huge changes at GitHub. Like, :sparkles:WOW:sparkles: levels of changes! As keeping with my obsessive note-taking inclination, I wrote up my thoughts and bumps with the new amazing GitHub Actions, the security, and project management features. I'll also post this in the GitHub Actions discussion area in the hopes it helps the product team. Especially as I'm a first-time user of GitHub Actions, which must make me a unicorn in today's dev world. + +Just to make clear, I **love** the new to me features in GitHub! Like I mentioned in the [Python](#python) section, this feedback comes from a place of love in my heart. I want to help make GitHub better. + +### Runners on All Three OS's FOR FREE?!?!?! + +My original plan was to post the tbp code, and setup up just enough GitHub Pages for this documentation. I'm using [Jekyll](https://jekyllrb.com) as the site generator. [^6] The truly incredible [Just the Docs](https://just-the-docs.com) theme hand walks you through everything you need to do. When in doubt, I looked and followed what they did in their repository. Totally worth the [donation](https://opencollective.com/just-the-docs)! (I've already donated to pytest and coverage.py). + +After getting my [first](https://github.com/John-Robbins/tbp/blob/5bc6d9a43a74f8c6d5ec254103dd26953a2f15f2/.github/workflows/Build%20and%20Deploy%20Site%20to%20Pages.yml) GitHub Action to build the pages, I thought I'd look a deeper to see what these things were all about. The fact that at no cost I could run all my tests on macOS, Windows, and Linux, was mind-blowing to me! What a wonderful gift to the open-source community. After I got my cross-platform tests with coverage working, I couldn't believe my 280-unit test combined code coverage was 99.92%![^7] Even more, the 2,000 minutes for Actions on personal accounts is huge. I've been pounding on my GitHub Actions for the entire first week of September, and I've used a whole 1 minute so far. I've just got to give a huge thanks to GitHub/Microsoft for giving us this support. + +### Reading the Documentation + +With my [Makefile](https://github.com/John-Robbins/tbp/blob/main/Makefile) that already does all the possible CI steps for tbp, I thought I'd only have to create a workflow file that called it with the right target. However, I knew I should go ahead and approach this as a great opportunity to learn more about GitHub Actions and workflows, because I know when I get to projects bigger than tbp, I'll want to use these wonderful tools. + +The documentation has a [Use cases and examples](https://docs.github.com/en/actions/use-cases-and-examples) section, so I started with the [Building and testing Python](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-python), because that's what I'm doing. That gave me a good idea of the particulars, but each of the sections seemed independent of each other. I wasn't sure what the relationship was because some just list steps and others list jobs. The [Prerequisites](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-python#prerequisites) said I should be familiar with [Writing workflows](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-python#prerequisites), I headed over there. + +In the overall [documentation](https://docs.github.com/en/actions) I found the organization a little confusing. For example, the [Writing workflows](https://docs.github.com/en/actions/writing-workflows) list what I assumed was a table of contents for 27 articles about workflows that you should read in order. For example, it lists [Using workflow templates](https://docs.github.com/en/actions/writing-workflows/using-workflow-templates) followed by [Choosing when your workflow runs](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs). However, reading [Using workflow templates](https://docs.github.com/en/actions/writing-workflows/using-workflow-templates), does not have any link for the [Choosing when your workflow runs](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs), those it does have links for other parts of the documentation. My goal was to read a logical flow through learning about workflows starting from zero, but I was wandering to places that assumed I'd read something else previously. + +The workflow documentation got into details quickly, when I was simply trying to get a handle on [Building and testing Python](https://docs.github.com/en/actions/use-cases-and-examples/building-and-testing/building-and-testing-python). Personally, I feel it would be better to have a step-by-step tutorial of a simple calculator program. You start with the code, and you build the action step by step. The idea is that by the end the entire YAML file shows building, linting, and testing. The repository has everything in place so that PRs trigger the proper workflow and so on. That's what I ended up doing on a throw away repository to see how everything fit together. If this is something that GitHub doesn't feel is a good fit for the documentation, someone should do this in their blog as they would get a lot of traffic. + +In all the documentation is pretty good, but a good walk through at the start would be great to have. The other issue is that the docs go deep fast, and I felt there's a lot of middle ground missing. I ended up with a monolithic workflow [file](https://github.com/John-Robbins/tbp/blob/b14ea797cb76c26c33005a4ce7ec768be4ec7a92/.github/workflows/Code-CI.yml) where I was installing, type checking, linting, testing, and producing code coverage on each operating system in the matrix. I knew that wasn't what I wanted in the long run but wasn't sure what the best practices were for a GitHub Action. + +What I would have *loved* to have had in the docs were links to real world Python projects showing me how they tackled their workflows. The key is that these projects can't be gigantic, but smaller projects that one can learn from. After some quality time with my [search engine](https://kagi.com), I found [Ned Batchelder](https://github.com/nedbat)'s [scriv](https://github.com/nedbat/scriv) and [Arie Bovenberg](https://github.com/ariebovenberg)'s [whenever](https://github.com/ariebovenberg/whenever) to be Goldilocks size Python projects that were great resources to see real world GitHub Actions usage. Along the same lines, having a list of GitHub Actions best practices, and especially links to projects following them would be great also. + +### Yet Another Misstep Looms + +Or is Yesterday's Actions Mislead Later? Either way, YAML is not a configuration language that I enjoyed, but I'll deal with it. These [two](https://ruudvanasseldonk.com/2023/01/11/the-yaml-document-from-hell) different [posts](https://noyaml.com), do a good job of explaining the pain. My plan was to break up my giant job into multiple discrete jobs. For example, I didn't need to type check and lint on every operating system where one would suffice. Like all folks starting out in YAML, I have many, many commits in the edit, debug/find-stupid-error, repeat cycle. I just loved the joy of indention errors. + +In the middle of learning GitHub Actions, I was surprised that there wasn't any graphical editor to help you build GitHub Actions. It feels like a tool that helps produce correct YAML limited to GitHub Actions doesn't seem like it would be at the complexity of a compiler back end. When I looked for such a tool on the Visual Studio Code Market Place, I didn't find any, but did find the [GitHub Actions](https://marketplace.visualstudio.com/items?itemName=GitHub.vscode-github-actions) extension. While it's nice to be able to run workflows from your IDE, I was more interested in the help with authoring. I don't know if I didn't have it installed correctly, or what, but the extension never reported obvious errors like indenting problems, so I uninstalled it. Fortunately, I found the cool [actionlint playground](https://rhysd.github.io/actionlint/) from [rhysd](https://github.com/rhysd), which validates your workflow files. It saved me innumerable mistakes! The ability to run workflows locally for testing would be very useful as well. I looked at the interesting [act](https://nektosact.com) project, but I only found it towards the end of my tribulations. When I work on bigger workflows, I will certainly install and use act. + +Something that still has me confused about a workflow is how much you must repeat. + +```yaml +jobs: + lint-types-job: + name: "Type & Lint Checks" + runs-on: ubuntu-latest + steps: + # You have to love copying and pasting the same 11 lines into each job. + # I tried to make these a reusable job, but GitHub Actions wants to + # force you to ARY: Always Repeat Yourself. 😹😹 + - name: "Checkout Code" + uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + - name: "Install Dependencies" + run: | + python -m pip install --upgrade pip + python -m pip install .[dev] + # The unique part starts here. + . . . + + test-cov-job: + name: "Test & Coverage" + needs: lint-types-job + runs-on: ubuntu-latest + steps: + - name: "Checkout Code" + uses: actions/checkout@v4 + - name: Set up Python 3.12 + uses: actions/setup-python@v5 + with: + python-version: "3.12" + cache: 'pip' + - name: "Install Dependencies" + run: | + python -m pip install --upgrade pip + python -m pip install .[dev] + # The unique part starts here. + . . . +``` + +In the above example, I'm repeating the exact same setup each time. Does the `test-cov-job` always have to repeat the setup? Can the job assume the project is ready to go? I never found anything definitive in the documentation and looking through many examples, it seems you must repeat the setup every time. As I mentioned in the comment above, Always Repeat Yourself feels wrong to me. + +Thinking that [reusable workflows](https://docs.github.com/en/actions/sharing-automations/reusing-workflows) would be the answer, I went down that path. I wanted to encapsulate the common setup work, and pass in the `runs-on` value. Although it's been almost three years since someone asked, there's no official support for [array input type support](https://github.com/orgs/community/discussions/11692). I fumbled around with an [idea](https://github.com/orgs/community/discussions/11692#discussioncomment-3541856) I found in the actions discussions, but I could never get it to work. I gave up on reusable workflows and repeated myself because working code always wins. + +After a bit of trial and error, I had two workflows, one for code and one for docs. They were working great where I could run them manually on branches, and automatically on pull requests and pushes to main. Life was good, and then my code workflow stopped working. My workflow worked on a Sunday and died on a Monday. Being brand new to workflows, I struggled for several hours as to why my artifact uploads no longer worked. Yeah, you know where this is going: [Upcoming breaking change: Hidden Files will be excluded by default](https://github.com/actions/upload-artifact/issues/602). Why did a breaking change not involve a major version bump? I'm old enough to remember when a [GitHub co-founder](http://tom.preston-werner.com/) first proposed [SemVer](https://semver.org/spec/v2.0.0.html), which I thought was a very good attempt at bringing sanity to version numbers. Is SemVer merely a suggestion at GitHub these days? + +While I understand the [security issue](https://unit42.paloaltonetworks.com/github-repo-artifacts-leak-tokens/) that brought about this breaking change, how GitHub handled it was, honestly, abysmal. Why was there no warning about this change in the workflow run output? Why was there no prominent mention of this breaking change on the [`@actions/upload-artifact`](https://github.com/actions/upload-artifact) repository home page? I wonder what else will randomly and silently break in my workflows after this debacle? Please, you can do better. + +### Protected Branches vs. GitHub Actions + +Have the team working on protected branches and the team working on GitHub Actions ever talked to one another? Could you get them together, at least at a company party, so they can meet and finally communicate? :joy_cat: + +The GitHub Actions documentation promotes the use of [filters](https://docs.github.com/en/actions/writing-workflows/choosing-when-your-workflow-runs/triggering-a-workflow#using-filters) to control when various actions run. Following those guidelines, I had the following at the top of my two workflows. + +```yaml +# Top of CodeCI.yml +on: + push: + branches: ["main"] + paths-ignore: + - docs/** + pull_request: + branches: ["main"] + paths-ignore: + - docs/** + +# Top of DocsCI.yml +on: + push: + branches: ["main"] + paths: + - "docs/**" +``` + +This worked great as the right build occurred when code or documentation changed. In all, I was happy how the workflows turned out as I had control and got all the cool badges in the [README](https://github.com/John-Robbins/tbp/blob/main/README.md) file. + +The next new GitHub addition I wanted to look at was the [protected branches](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches). Before I retired, I remember wishing we had something like this because we had to rely on manual steps and pure trust to ensure someone didn't screw up the main branch. Being able to enforce checking and pull request rules is huge help for code quality.[^8] + +Heading over to the Branches setting in my repository, I set up a couple of rules for main: + +- Require a pull request before merging +- Require status checks to pass before merging +- Require signed commits + +For the status checks, I have the following required jobs. + +- Code Jobs + - `Type & Lint Checks` (mypy, pylint, and ruff) + - `Tests & Coverage` (on all three operating systems) + - `Coverage Report` (for the sweet, sweet badges) +- Docs Jobs + - `Build Website` + - `Deploy Website` + +When I'm adding all five jobs as required, I realized my pull request status checks would never pass, thus failing the branch protections and requiring an override merge every time. For example, if I changed this documentation and pushed into main, the `Build Website` and `Deploy Website` jobs would run. However, the three code jobs would never run because the `paths-ignore` filtered them out, so they do not run and are not reported as skipped. + +The docs have a section [Handling skipped but required checks](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#handling-skipped-but-required-checks) but says nothing about what you need to specifically do. Down the page a little is the [Status checks with GitHub Actions and a Merge queue](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/troubleshooting-required-status-checks#status-checks-with-github-actions-and-a-merge-queue) section, which looks like my way to fix this issue. + +Reading the [Merging a pull request with a merge queue](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/merging-a-pull-request-with-a-merge-queue) page, I didn't understand at all how a merge queue fixes the required test issue. Rereading the page carefully for the third time, I notice that merge queues are only available for organizations. It's just little ol' me with a free personal account, so no merge queues for you. + +Since I'm stumped with no solution, I hit the information superhighway to see what others are doing. As small as tbp is, it's considered a monorepo because I've mixed code and documentation. One solution to break docs and code them into two separate repos, but that's just crazy talk. Bumping into a [GitHub discussion](https://github.com/orgs/community/discussions/26251), I see people have been asking about this issue for five years. :eyes: This seems like a very common situation to me, but maybe not that many repositories are relying on branch protections. I'd love to know if there's a way to find out how many are. + +One [solution](https://github.com/orgs/community/discussions/26251#discussioncomment-3250964) is to create dummy jobs. In other words, in my CodeCI.yml file, create doc jobs of the same name and return success on them. That didn't seem like a good practice to follow. After weighing various options, I decided to use [@dorny](https://github.com/dorny)'s much starred [paths-filter](https://github.com/dorny/paths-filter). The action allows you to build conditions based on the file changes. Below is the job that all other jobs need that builds the conditionals used later. Sorry for the job name, but that's what I felt I was doing. + +{%raw%} + +```yaml +fight-github-job: + name: "Detect File Changes" + runs-on: ubuntu-latest + # The permissions necessary for dorny/paths-filter@v3 on pull requests. + permissions: + pull-requests: read + # The outputs for this job. + outputs: + # The first two outputs are what are used in subsequent jobs to determine + # if we are looking at code or docs changes. + code: ${{steps.filter.outputs.code}} + docs: ${{steps.filter.outputs.docs}} + # These two here are for debugging purposes. After much trial and error, + # the ${FILTER_NAME}_files discussed in the dorny/paths-filter + # documentation only works on steps. By doing this one can get the files + # out of the job. + code-files: ${{steps.filter.outputs.code_files}} + docs-files: ${{steps.filter.outputs.docs_files}} + steps: + # Check out the code as that's needed for a push trigger. + - name: "Checkout Code" + uses: actions/checkout@v4 + # Now do the filtering so we can appropriately decide which jobs need to + # be run. + - name: "Filter Files" + uses: dorny/paths-filter@v3 + id: filter + with: + # For debugging, I'm setting the outputs to build a list of files so + # I can print them out in the jobs below. + list-files: shell + # For docs, I only care about all changes in the docs directory. + # For code, I care only about the .toml file and the two code + # directories, src and tests. + filters: | + docs: + - added|modified|deleted: 'docs/**' + code: + - modified:'pyproject.toml' + - added|modified|deleted: 'src/**' + - added|modified|deleted: 'tests/**' + - name: "Changed Code Files" + run: | + echo Code files: ${{steps.filter.outputs.code_files}} + - name: "Changed Docs Files" + run: | + echo Docs files: ${{steps.filter.outputs.docs_files}} +``` + +{%endraw%} + +I put all my jobs in a single [CI.yml](https://github.com/John-Robbins/tbp/blob/main/.github/workflows/CI.yml) file and each one has a conditional on it like the following. + +{%raw%} + +```yaml +lint-types-job: + needs: fight-github-job + name: "Type & Lint Checks" + # Only run if we have changed code files. + if: ${{needs.fight-github-job.outputs.code == 'true'}} + . . . +test-cov-job: + needs: [fight-github-job, lint-types-job] + name: "Tests & Coverage" + # Only run on changed code files. + if: ${{needs.fight-github-job.outputs.code == 'true'}} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.12"] + . . . +build-website-job: + needs: fight-github-job + name: "Build Web Site" + # Are there any document files that have changed? + if: ${{needs.fight-github-job.outputs.docs == 'true'}} + . . . +``` + +{%endraw%} + +Now I have the best of both worlds. With skipped jobs always treated as successful in protected branches, I can require all five jobs for a PR to pass but only run the minimum set of jobs required by the changes...[RECORD SCRATCH!](https://www.myinstants.com/en/instant/record-scratch/) There's another bug in GitHub Actions to deal with. + +In the YAML snippet above, you see that the `Test & Coverage` is one-dimensional matrix job that will run pytest and coverage.py on each of the operating systems. In my branch protections rule, I have `Test & Coverage` set as one of the status checks that must pass. It seems logical to me that the result of the individual matrix jobs would roll up into a result of the containing job. I'm finding out with GitHub Actions, thinking logically doesn't always work. + +When my [CI.yml](https://github.com/John-Robbins/tbp/blob/b97b7a25963bf201d4609d8aa40d6afb47bf61b5/.github/workflows/CI.yml) runs, all appropriate jobs run when code changes, but the status checks fail even though every sub job of `Test & Coverage` job matrix was successful. There's a bug in matrix containing job, such as I have here, that doesn't report status correctly. Thus, all my pull requests remain in purgatory never mergeable unless I manually skip the status checks for the protected branch. That defeats the whole purpose of branch protections in the first place. Of course, the [status checks documentation](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/collaborating-on-repositories-with-code-quality-features/about-status-checks) mentions nothing about this [4+ year old problem](https://github.com/orgs/community/discussions/26822). + +As always, it's GitHub users to the rescue with [hack-around](https://github.com/orgs/community/discussions/26822#discussioncomment-5122101). What you need to do is have a job that needs your matrix job, so it runs afterwards. + +{%raw%} + +```yaml +the-sad-did-github-matrix-succeed-job: + needs: [fight-github-job, test-cov-job] + name: "Did Test & Coverage Succeed?" + # Note the different condition here. If it wasn't always(), PR's on + # protected branches would never run. + if: ${{always()}} + runs-on: ubuntu-latest + steps: + - name: "Why is this not part of a matrix job?" + if: >- + ${{ + contains(needs.*.result, 'failure') + || contains(needs.*.result, 'cancelled') + }} + run: exit 1 +``` + +{%endraw%} + +The above YAML snippet shows the `the-sad-did-github-matrix-succeed-job`, which needs `test-cov-job` and can check to see if there were failures in it. Back in my branch protection rules settings, I removed the malfunctioning `Test & Coverage` job and added `Did Test & Coverage Succeed?` to the required status checks. + +Now I think you can see why at the beginning of this section I asked if the branch protection and GitHub Actions teams had ever talked. It feels like they haven't, based on my experience thus far. What's frustrating is that tbp is a very simple Python module and yet there was this extra friction to do two basic tasks: 1) test tbp and the build the documentation and 2) use branch protections. + +Ideally, GitHub would produce a fully supported solution for branch protections and required checks. However, after five years of asking, I don't think that's on the product backlog. If that's so, having the steps I outlined here to make branch protections and workflow jobs play well together would be invaluable in the documentation. + +Overall, I'm very thankful for GitHub Actions, protected branches, and the other changes. These are the kinds of features that I remember dreaming we could have back when I was working. While I hit long-standing issues, at least the community had hack-arounds. I deeply appreciate everything I'm getting from GitHub/Microsoft for free so am not complaining at all! I just thought it would be valuable for interested parties to see how a brand-new user worked through solving problems with key GitHub technologies. + +If I could have only one ask it would be that the GitHub Actions documentation [Use cases and examples](https://docs.github.com/en/actions/use-cases-and-examples) section take a task-based approach to the introduction. Additionally, *DO* talk about the workarounds necessary to have a fully functioning solution, which I hope I did here. That wholistic approach would end a lot of confusion in learning and get new users off on the right foot. + +## What's Next for Tiny BASIC in Python? My original goal for tbp was to do a project with Tiny BASIC in three phases with all phases having the same features. @@ -264,4 +535,7 @@ When I tackle phases two and three, I'll probably use another language. [^2]: I have no idea who CyberZHG is, but I loved their GitHub tag line: "Knowledge is bacon. Please don't send emails." Bacon is glorious to me and I dislike email. I could be friends with CyberZHG. [^3]: Is it weird that I think of Bob as my patron saint? [^4]: Why a class on Numerical Analysis? I'd retired and went back to college to learn what I should have learned the first time around. In May 2024, I [graduated](https://new.unca.edu) with a double major in Mathematics and Spanish. Yay, me! :tada: My GPA was so much better the second time around, too! -[^5]: Should I attach my resume? :joy: My focus was on Windows where I worked on device drivers and developed or lead the development of error detection tools, profilers, and code coverage products. I started and built a successful consulting company where I focused on solving bugs where a company was losing millions of dollars a day. I've written [books](https://www.amazon.com/stores/John-Robbins/author/B001IXMLF0?ref=ap_rdr&isDramIntegrated=true&shoppingPortalEnabled=true) on debugging. Also, I presented at dozens of major conferences like Microsoft Tech-Ed, where I had my biggest audience ever of 5,000+ people! :scream_cat: +[^5]: Should I attach my resume? :joy: My focus was on Windows where I worked on device drivers and developed or lead the development of error detection tools, profilers, and code coverage products at NuMega. I started and built a successful consulting company, Wintellect, where I focused on solving the kind of bugs where a company was losing millions of dollars a day. I've written [books](https://www.amazon.com/stores/John-Robbins/author/B001IXMLF0?ref=ap_rdr&isDramIntegrated=true&shoppingPortalEnabled=true) on debugging. Also, I presented at dozens of major conferences like Microsoft Tech-Ed, where I had my biggest audience ever of 5,000+ people! :scream_cat: +[^6]: Why does it seem Ruby is so hard to [install](https://www.moncefbelyamani.com/how-to-install-xcode-homebrew-git-rvm-ruby-on-mac/) on macOS? Is this normal? +[^7]: Sorry! A total [humblebrag](https://www.merriam-webster.com/dictionary/humblebrag). +[^8]: And pointing the fickle finger of blame! diff --git a/src/tbp/__init__.py b/src/tbp/__init__.py index 83a6a8b..1a34429 100644 --- a/src/tbp/__init__.py +++ b/src/tbp/__init__.py @@ -12,5 +12,5 @@ # Module information. ############################################################################### -__version__ = "0.9.0" +__version__ = "0.9.1" __author__ = "John Robbins"